pax_global_header00006660000000000000000000000064132527456430014525gustar00rootroot0000000000000052 comment=26971aaff01b369d3c4eca2748cff44b1cb6e636 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/000077500000000000000000000000001325274564300221645ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/.gitlab-ci.yml000066400000000000000000000011711325274564300246200ustar00rootroot00000000000000stages: - build build-debian: stage: build image: debian:stretch script: | echo "Installing build dependencies..." apt-get update apt-get install -y build-essential apt-get build-dep -y . echo "Converting to native package..." suffix="~$CI_COMMIT_SHA" sed -i "1{s/-1) /$suffix) /}" debian/changelog sed -i 's/3.0 (quilt)/3.0 (native)/' debian/source/format echo "Building LemonLDAP..." dpkg-buildpackage echo "Moving artifacts..." mkdir artifacts mv ../*.tar.xz ../*.dsc ../*.changes ../*.deb artifacts artifacts: paths: - 'artifacts/*' expire_in: 1 week lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/AUTHORS000066400000000000000000000007741325274564300232440ustar00rootroot00000000000000LemonLDAP::NG Core team: * David COUTADEUR * François-Xavier DELTOMBE * Xavier GUIMARD * Clément OUDOT Past and present contributors: * Hamza AISSAT * Casimir ANTUNES * Sébastien BAHLOUL * Oliver BOIREAU * Sandro CAZZANIGA * Jean-Thomas CHECCO * Thomas CHEMINEAU * Sebastien DIAZ * Hubert GAULTIER * Eric GERMAN * Mounir GZADY * Erwan LEGALL * Pascal PEJAC * Daniel RIVIERE * Habib ZITOUNI See http://lemonldap-ng.org/contact#the_team lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/COPYING000066400000000000000000001377521325274564300232360ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: lemonldap-ng Upstream-Contact: Xavier Guimard Source: http://forge.objectweb.org/project/showfiles.php?group_id=274 Files: * Copyright: 2005-2017, Xavier Guimard 2006-2017, Clement Oudot 2008-2011, Thomas Chemineau 2012-2015, François-Xavier Deltombe 2012-2013, Sandro Cazzaniga 2012-2015, David Coutadeur 2006-2015, LINAGORA 2015-2018, Savoir-faire Linux License: GPL-2+ Files: *.js Copyright: 2005-2017, Xavier Guimard 2006-2017, Clement Oudot 2008-2012, Thomas Chemineau License: GPL-2+ Files: lemonldap-ng-portal/example/skins/common/js/portal.js Copyright: 2005-2017, Xavier Guimard 2006-2017, Clement Oudot 2008-2012, Thomas Chemineau License: GPL-2+ Comment: a little part of it comes from JQuery-UI examples (http://snipplr.com/view/29434/) Files: lemonldap-ng-portal/example/skins/common/Apache.png Copyright: Apache Software Foundation (ASF) License: Apache-2.0 Files: lemonldap-ng-portal/example/skins/common/CAS.png Copyright: Jasig License: Apache-2.0 Files: lemonldap-ng-portal/example/skins/common/BrowserID.png Copyright: 2013, Xavier Guimard License: CC-3 Comment: created using images of YellowIcon and Innerer Schweinehund (http://findicons.com/icon/168665/firefox?id=292029 and http://commons.wikimedia.org/wiki/File:Mail-closed.svg) Files: lemonldap-ng-portal/example/skins/common/Google.png lemonldap-ng-portal/example/skins/common/Facebook.png Copyright: http://tempest.deviantart.com/ License: CC-3 Comment: Downloaded from http://www.iconspedia.com/ Files: lemonldap-ng-portal/example/skins/common/OpenIDConnect.png Copyright: http://www.customicondesign.com License: CC-BY-NC-ND-3.0 Comment: Downloaded from http://www.iconspedia.com/ Files: lemonldap-ng-portal/example/skins/common/Twitter.png Copyright: Paul Schulerr, http://schulerr.deviantart.com License: CC-3 Comment: Downloaded from http://www.iconspedia.com/ Files: lemonldap-ng-portal/example/skins/common/WebID.png Copyright: unknown License: W3C Comment: downloaded from http://www.w3.org/2005/Incubator/webid/wiki/Main_Page#Other_resources . Author is unknown and license may be W3C or public-domain Files: lemonldap-ng-portal/example/skins/common/LinkedIn.png Copyright: Public Domain License: Simple-Geo Comment: downloaded from https://commons.wikimedia.org/wiki/File:LinkedIn_logo_initials.png Files: lemonldap-ng-portal/example/skins/common/backgrounds/* Copyright: Various artists License: CC-BY-NC-ND-3.0 or GFDL-1.3 Comment: dowloaded from http://commons.wikimedia.org Files: jquery*.js jquery*.css Copyright: 2010, The jQuery project and the jQuery UI team License: GPL-2 or Expat Files: *jquery.cookie.js Copyright: 2010, Klaus Hartl (stilbuero.de) License: GPL-3+ Files: *jquery.base64.js Copyright: Muhammad Hussein Fattahizadeh License: GPL-2+ Files: lemonldap-ng-portal/example/skins/common/js/jquery-ui-* Copyright: 2008 Paul Bakaus (ui.jquery.com) Brandon Aaron David Bolter Rich Caloggero Chi Cheng (cloudream@gmail.com) Colin Clark (http://colin.atrc.utoronto.ca/) Michelle D'Souza Aaron Eisenberger (aaronchi@gmail.com) Ariel Flesler Bohdan Ganicky Scott González Marc Grabanski (m@marcgrabanski.com) Klaus Hartl (stilbuero.de) Scott Jehl Cody Lindley Eduardo Lundgren (eduardolundgren@gmail.com) Todd Parker John Resig Patty Toland Ca-Phun Ung (yelotofu.com) Keith Wood (kbwood@virginbroadband.com.au) Maggie Costello Wachs Richard D. Worth (rdworth.org) Jörn Zaefferer (bassistance.de) License: GPL-2 or Expat Files: lemonldap-ng-manager/site/static/bwr/angular/* lemonldap-ng-manager/site/static/bwr/angular-cookies/* lemonldap-ng-manager/site/static/bwr/angular-animate/* Copyright: 2010-2015, Google, Inc. http://angularjs.org License: Expat Files: lemonldap-ng-manager/site/static/bwr/angular-bootstrap/* Copyright: 2012-2016, the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112 License: Expat Files: lemonldap-ng-manager/site/static/bwr/angular-ui-tree/* Copyright: 2014, unspecified License: Expat Files: lemonldap-ng-manager/site/static/bwr/file-saver.js/* Copyright: 2015, Eli Grey http://eligrey.com License: Expat Files: lemonldap-ng-portal/example/skins/common/apps/* doc/media/icons/* Copyright: 2006-2007 Everaldo Coelho, Crystal Project License: LGPL-3 Files: doc/media/icons/flags/* Copyright: Mark James License: CC-3 Files: scripts/doxygenfilter scripts/DoxyGen/Filter.pm scripts/DoxyGen/SQLFilter.pm Copyright: 2002, Bart Schuller 2006, Phinex Informatik AG License: Artistic or GPL-1+ Files: doc/lib/tpl/default/images/* doc/lib/images/* doc/lib/plugins/note/images/* doc/media/wiki/dokuwiki-128.png Copyright: 2004-2012 Andreas Gohr and the DokuWiki Community License: GPL-2 Files: doc/media/documentation/google* Copyright: 2007-2011, Andreas Åkre Solberg 2007-2011, Olav Morken License: LGPL-2 Files: doc/media/documentation/lasso.png Copyright: 2004, Entr'ouvert 2004, Florent Monnier License: GPL-2+ Files: lemonldap-ng-portal/example/skins/bootstrap/*/bootstrap* lemonldap-ng-portal/example/skins/bootstrap/fonts/* Copyright: 2013, Twitter Inc. License: Apache-2.0 Comment: version 3.0.3 of bootstrap is released under Apache-2 license earlier versions will be released under Expat license Files: lemonldap-ng-manager/site/static/bwr/bootstrap/* Copyright: 2011-2015, Twitter Inc. License: Expat Files: lemonldap-ng-manager/site/static/bwr/es5-shim/* Copyright: 2009-2015, Kristopher Michael Kowal and contributors License: Expat Files: debian/* Copyright: 2005-2016, Xavier Guimard License: GPL-2+ Files: lemonldap-ng-portal/example/skins/common/js/sha256*.js lemonldap-ng-portal/example/skins/common/js/enc-base64*.js Copyright: 2009-2013 Jeff Mott 2013-2016 Evan Vosberg License: Expat License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . The complete text of the Apache-2 license can be found in http://www.apache.org/licenses/LICENSE-2.0 License: Artistic This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License, which comes with Perl. . The complete text of the Artistic License can be found in http://opensource.org/licenses/Artistic-1.0 License: CC-3 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; to Distribute and Publicly Perform the Work including as incorporated in Collections; and, to Distribute and Publicly Perform Adaptations. . For the avoidance of doubt: Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. . Creative Commons Notice . Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. . Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. . Creative Commons may be contacted at http://creativecommons.org/. License: CC-BY-NC-ND-3.0 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. . "Distribute" means to make available to the public the original and copies of the Work through sale or other transfer of ownership. . "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. . "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. . "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. . "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. . "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. . "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; and, to Distribute and Publicly Perform the Work including as incorporated in Collections. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats, but otherwise you have no rights to make Adaptations. Subject to 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Section 4(d). . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . a) You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works. If You Distribute, or Publicly Perform the Work or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work. The credit required by this Section (c) may be implemented in any reasonable manner; provided, however, that in the case of a Collection, at a minimum such credit will appear, if a credit for all contributing authors of Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. . For the avoidance of doubt: Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and, Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b). Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. . License: GFDL-1.3 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Free Documentation License as published by the Free Software Foundation; version 1.3. . The complete text of version 1.3 of the GNU Free Documentation License can be found in https://www.gnu.org/licenses/fdl.html License: GPL-1+ 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 1, or (at your option) any later version. . The complete text of version 1 of the GNU General Public License can be found in http://opensource.org/licenses/GPL-1.0 License: GPL-2 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; version 2. . The complete text of version 2 of the GNU General Public License can be found in http://opensource.org/licenses/GPL-2.0 License: GPL-2+ 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 2, or (at your option) any later version. . The complete text of version 2 of the GNU General Public License can be found in http://opensource.org/licenses/GPL-2.0 License: GPL-3+ 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, or (at your option) any later version. . The complete text of version 3 of the GNU General Public License can be found in http://opensource.org/licenses/GPL-2.0 License: LGPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 2. . The full text of the GNU Lesser General Public License version 2 can be found in http://opensource.org/licenses/LGPL-2.1 License: LGPL-3 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 3. . The full text of the GNU Lesser General Public License version 3 can be found in http://opensource.org/licenses/LGPL-3.0 License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License: W3C Copyright © 1994-2001 World Wide Web Consortium, (Massachusetts Institute of Technology, Institut National de Recherche en Informatique et en Automatique, Keio University). All Rights Reserved. http://www.w3.org/Consortium/Legal/ . This W3C work (including software, documents, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions: . Permission to use, copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications, that you make: . 1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. 2. Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, a short notice of the following form (hypertext is preferred, text is permitted) should be used within the body of any redistributed or derivative code: "Copyright © [$date-of-software] World Wide Web Consortium, (Massachusetts Institute of Technology, Institut National de Recherche en Informatique et en Automatique, Keio University). All Rights Reserved. http://www.w3.org/Consortium/Legal/" 3. Notice of any changes or modifications to the W3C files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.) THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. . COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. . The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders. License: Simple-Geo These images only consist of simple geometric shapes or text. It does not meet the threshold of originality needed for copyright protection, and is therefore in the public domain. lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/Doxyfile000066400000000000000000001641301325274564300236770ustar00rootroot00000000000000# Doxyfile 1.5.6 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = LemonLDAP::NG # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 1.9.16 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = doc/devel/ # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, # and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the DETAILS_AT_TOP tag is set to YES then Doxygen # will output the detailed description near the top, like JavaDoc. # If set to NO, the detailed description appears after the member # documentation. DETAILS_AT_TOP = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.pm *.pl # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = scripts # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = */.svn/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. #INPUT_FILTER = INPUT_FILTER = ./scripts/doxygenfilter # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = YES # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hiererachy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = YES # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = NO # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = NO # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = NO # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is enabled by default, which results in a transparent # background. Warning: Depending on the platform used, enabling this option # may lead to badly anti-aliased labels on the edges of a graph (i.e. they # become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO # ADDED lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/GPL000066400000000000000000000432541325274564300225410ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/INSTALL000066400000000000000000000227731325274564300232300ustar00rootroot00000000000000 LEMONLDAP::NG INSTALLATION Lemonldap::NG is a modular Web-SSO based on Apache::Session modules. It simplifies the build of a protected area with a few changes in the application. It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. See README file to known how it works. ---------------------- I - QUICK INSTALLATION ---------------------- The proposed example use a protected site named test.example.com. Non authenticated users are redirected to auth.example.com. 1.1 - PREREQ ------------ 1.1.1 - Software To use Lemonldap::NG, you have to run a LDAP server and of course an Apache server compiled with mod-perl (version 1.3 or 2.x). Generaly, the version of Apache proposed with your Linux distribution match, but some distributions used an experimental version of mod_perl with Apache2 (mod_perl-1.99) which does not work with Lemonldap::NG. With such distributions (like Debian-3.1), you have to use Apache-1.3 or to use a mod_perl backport (www.backports.org package for Debian works fine). 1.1.2 - Perl prereq Perl modules: Apache::Session, Net::LDAP, MIME::Base64, CGI, LWP::UserAgent, Cache::Cache, DBI, XML::Simple, SOAP::Lite, HTML::Template, XML::LibXML, XML::LibXSLT With Debian: apt-get install libapache-session-perl libnet-ldap-perl libcache-cache-perl \ libdbi-perl perl-modules libwww-perl libcache-cache-perl \ libxml-simple-perl libhtml-template-perl libsoap-lite-perl \ libxml-libxml-perl libxml-libxslt-perl 1.2 - BUILDING -------------- 1.2.1 - Complete install $ tar xzf lemonldap-ng-*.tar.gz $ cd lemonldap-ng-* $ make && make test $ sudo make install By default, all is installed in /usr/local/lemonldap-ng except Perl libraries which are installed in a directory included in @INC. 1.2.2 - Install on Debian $ tar xzf lemonldap-ng-*.tar.gz $ cd lemonldap-ng-* $ debuild $ sudo dpkg -i ../*lemonldap-ng*.deb Here, all is installed in /var/lib/lemonldap-ng, /etc/lemonldap-ng except Perl libraries which are installed in /usr/share/perl5/Lemonldap/NG/ 1.3 - EXAMPLE CONFIGURATION --------------------------- If you have build Debian packages, configuration is done by Debconf. See /usr/share/doc/liblemonldap-ng-common/README.Debian to use it. After build, you have a new file named example/apache.conf. You just have to include this file in Apache configuration: # in httpd.conf (with Apache1) include /path/to/lemonldap-ng/source/example/apache.conf # or in apache2.conf (with Apache2) include /path/to/lemonldap-ng/source/example/apache2.conf Modify your /etc/hosts file to include: 127.0.0.1 auth.example.com test1.example.com manager.example.com test2.example.com Use a browser to connect to http://manager.example.com/ and specify your LDAP settings. If you don't set managerDn and managerPassword, Lemonldap::NG will use an anonymous bind to find user dn. Next, restart Apache use your prefered browser and try to connect to http://test1.example.com/. You'll be redirect to auth.example.com. Try to authenticate yourself with a valid account and the protected page will appear. You will find other explanations on this page. the file /usr/local/lemonldap-ng/etc/storage.conf (/etc/lemonldap-ng/storage.conf on Debian systems) can be modified to change configuration database. ------------------------- 2 - ADVANCED INSTALLATION ------------------------- It is recommended to install the example first then to adapt it. 2.1 - PREREQ 2.1.1 - Apache To use Lemonldap::NG, you have to run a LDAP server and of course an Apache server compiled with mod-perl (version 1.3 or 2.x). Generaly, the version of Apache proposed with your Linux distribution match, but some distributions used an experimental version of mod_perl with Apache2 (mod_perl-1.99) which does not work with Lemonldap::NG. With such distributions (like Debian-3.1), you have to use Apache-1.3 or to use a mod_perl backport (www.backports.org package for Debian works fine). For Apache2, you can use both mpm-worker and mpm-prefork. Mpm-worker works faster and Lemonldap::NG use the thread system for best performance. If you have to use mpm-prefork (for example if you use PHP), Lemonldap::NG will work anyway. You can use Lemonldap::NG in an heterogene world: the authentication portal and the manager can work in any version of Apache 1.3 or more even if mod_perl is not compiled, with ModPerl::Registry or not... Only the handler (site protector) need mod_perl. The different handlers can run on different servers with different versions of Apache/mod_perl. 2.1.2 - Perl prereq Warning: Handler and Portal parts both need Lemonldap::NG::Manager components to access to configuration. Manager: -------- Apache::Session, MIME::Base64, CGI, LWP::UserAgent, DBI, XML::Simple, SOAP::Lite, XML::LibXML, XML::LibXSLT, Lemonldap::NG::Common With Debian: apt-get install perl-modules libxml-simple-perl libdbi-perl libwww-perl # If you want to use SOAP apt-get install libsoap-lite-perl Portal: ------- Apache::Session, Net::LDAP, MIME::Base64, CGI, Cache::Cache, DBI, XML::Simple, SOAP::Lite, HTML::Template, XML::LibXML, Lemonldap::NG::Common With Debian: apt-get install libapache-session-perl libnet-ldap-perl perl-modules Handler: -------- Apache::Session, MIME::Base64, CGI, LWP::UserAgent, Cache::Cache, DBI, XML::Simple, SOAP::Lite, Lemonldap::NG::Common With Debian: apt-get install libapache-session-perl libwww-perl libcache-cache-perl 2.2 - SOFTWARE INSTALLATION --------------------------- If you just want to install a handler or a portal or a manager: $ tar xzf lemonldap-ng-*.tar.gz $ cd lemonldap-ng-*/Lemonldap-NG-(Portal|Handler|Manager) $ perl Makefile.PL && make && make test $ sudo make install else for a complete install: $ tar xzf lemonldap-ng-*.tar.gz $ cd lemonldap-ng-* $ make && make test $ sudo make install See prereq in §1.1.2 2.3 - LEMONLDAP::NG INSTALLATION -------------------------------- 2.3.1 - Database configuration 2.3.1.1 - Lemonldap::NG Configuration database If you use DBI or another system to share Lemonldap::NG configuration, you have to initialize the database. An example is given in example/lmConfig.mysql for MySQL. 2.3.1.2 - Apache::Session database The choice of Apache::Session::* module is free. See Apache::Session::Store::* or Apache::Session::* to know how to configure the module. For example, if you want to use Apache::Session::MySQL, you can create the database like this: CREATE DATABASE sessions ( id char(32), a_session text ); 2.3.2 - Manager configuration Copy example/manager.cgi and personalize it if you want (see Lemonldap::NG::Manager). You have to set in particular configStorage. For example with MySQL: $my $manager = Lemonldap::NG::Manager->new ( { dbiChain => "DBI:mysql:database=mybase;host=1.2.3.4", dbiUser => "lemonldap-ng", dbiPassword => "mypass", } ); Securise Manager access with Apache: Lemonldap::NG does not securise the manager itself yet: SSLEngine On Order Deny, Allow Deny from all Allow from admin-network/netmask AuthType Basic ... After configuration, you can also protect the manager with an Lemonldap::NG handler. 2.3.3 - Configuration edition Connect to the manager with your browser start configure your Web-SSO. You have to set at least some parameters: a) General parameters : * Authentication parameters -> portal : URL to access to the authentication portal * Domain : the cookie domain. All protected VirtualHosts have to be under it * LDAP parameters -> LDAP Server * LDAP parameters -> LDAP Accout and password : required only if anonymous binds are not accepted * Session Storage -> Apache::Session module : how to store user sessions. You can use all module that inherit from Apache::Session like Apache::Session::MySQL * Session Storage -> Apache::Session Module parameters : see Apache::Session:: b) User groups : Use the "New Group" button to add your first group. On the left, set the keyword which will be used later and set on the right the corresponding rule: you can use : * an LDAP filter (it will be tested with the user uid) or * a Perl condition enclosed with {}. All variables declared in "General parameters -> LDAP attributes" can be used with a "$". For example: MyGroup / { $uid eq "foo" or $uid eq "bar" } c) Virtual hosts You have to create a virtual host for each Apache host (virtual or real) protected by Lemonldap::NG even if just a sub-directory is protected. Else, user who want to access to the protected area will be rejected with a "500 Internal Server Error" message and the apache logs will explain the problem. Each virtual host has 2 groups of parameters: * Headers: the headers added to the apache request. Default : Auth-User => $uid * Rules: subdivised in 2 categories: * default : the default rule * personalized rules: association of a Perl regular expression and a condition. For example: ^/restricted.*$ / $groups =~ /\bMyGroup\b/ ------------- 3 - DEBUGGING ------------- Lemonldap::NG uses simply the Apache log system. So use LogLevel to choose information to display. lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/Makefile000066400000000000000000001247151325274564300236360ustar00rootroot00000000000000#!/usr/bin/make # This Makefile contains 2 main sections # - Variables # - targets # --------- # VARIABLES # --------- # Prefix for packaging DESTDIR= # Flag for optimizations USEDEBIANLIBS=no PROD=$(USEDEBIANLIBS) USEEXTERNALLIBS=no # Perl options #PERLOPTIONS="INSTALLDIRS=vendor" PERLOPTIONS= # Compression JSCOMPRESS=$(PROD) CSSCOMPRESS=$(PROD) # External commands PERL=$$(which perl) PERLVERSION=`perl -e '$$version = $$^V; $$version =~ s/v//; print $$version'` SU=su -c COMPRESS=tar czf UNCOMPRESS=tar xzf LISTCOMPRESSED=tar tzf COMPRESSSUFFIX=tar.gz NGINX=/usr/sbin/nginx # Default directories install # --------------------------- # Common dirs PREFIX=/usr/local LMPREFIX=$(PREFIX)/lemonldap-ng # BIN dirs BINDIR=$(LMPREFIX)/bin SBINDIR=$(LMPREFIX)/sbin INITDIR=$(LMPREFIX)/etc/init.d ETCDEFAULTDIR=$(LMPREFIX)/etc/default DATADIR=$(LMPREFIX)/data # Document roots for Apache VirtualHosts DOCUMENTROOT=$(LMPREFIX)/htdocs PORTALDIR=$(DOCUMENTROOT)/portal PORTALSKINSDIR=$(PORTALDIR)/skins MANAGERDIR=$(DOCUMENTROOT)/manager MANAGERSITEDIR=$(MANAGERDIR) MANAGERSTATICDIR=$(MANAGERSITEDIR)/static MANAGERRELATIVESTATICDIR=/static MANAGERPSGIDIR=$(MANAGERSITEDIR)/psgi MANAGERTEMPLATESDIR=$(MANAGERSITEDIR)/templates DOCDIR=$(DOCUMENTROOT) DEFDOCDIR=$(DOCUMENTROOT)/doc FRDOCDIR=$(DOCUMENTROOT)/fr-doc TESTDIR=$(DOCUMENTROOT)/test EXAMPLESDIR=$(LMPREFIX)/examples TOOLSDIR=$(LMPREFIX)/tools # Handler dir HANDLERDIR=$(LMPREFIX)/handler # Configuration dir CONFDIR=$(LMPREFIX)/etc CRONDIR=$(LMPREFIX)/etc/cron.d CONFFILENAME=lemonldap-ng.ini STORAGECONFFILE=$(CONFDIR)/$(CONFFILENAME) # LL::NG configuration storage dir FILECONFIGDIR=$(DATADIR)/conf # LL::NG sessions storage dir APACHESESSIONFILEDIR=$(DATADIR)/sessions APACHESESSIONFILELOCKDIR=$(APACHESESSIONFILEDIR)/lock # LL::NG persistent sessions storage dir APACHEPSESSIONFILEDIR=$(DATADIR)/psessions APACHEPSESSIONFILELOCKDIR=$(APACHEPSESSIONFILEDIR)/lock # LL::NG notifications storage dir APACHEFILENOTIFDIR=$(DATADIR)/notifications # LL::NG captcha dir CAPTCHADIR=$(DATADIR)/captcha # Apache user/group APACHEUSER= APACHEGROUP= # FastCGI FASTCGISOCKDIR=$(PREFIX)/run FASTCGIUSER=$(APACHEUSER) FASTCGIGROUP=$(APACHEGROUP) # Apache version APACHEVERSION=2.X # DNS Domain for cookie and virtual hosts DNSDOMAIN=example.com # Virtual Host Listen IP and Port (*, *:80, ...) PORT=80 VHOSTLISTEN="*:$(PORT)" TESTWEBSERVER=apache TESTWEBSERVERPORT=19876 # Other SRCCOMMONDIR=lemonldap-ng-common SRCHANDLERDIR=lemonldap-ng-handler SRCPORTALDIR=lemonldap-ng-portal SRCMANAGERDIR=lemonldap-ng-manager ERASECONFIG=1 # Set to 0 if you do not want to replace your configuration # INTERNAL VARIABLES # Internal variables used to install in $(DESTDIR) RLMPREFIX=$(DESTDIR)/$(LMPREFIX) RBINDIR=$(DESTDIR)/$(BINDIR) RSBINDIR=$(DESTDIR)/$(SBINDIR) RINITDIR=$(DESTDIR)/$(INITDIR) RETCDEFAULTDIR=$(DESTDIR)/$(ETCDEFAULTDIR) RDATADIR=$(DESTDIR)/$(DATADIR) RPORTALDIR=$(DESTDIR)/$(PORTALDIR) RPORTALSKINSDIR=$(DESTDIR)/$(PORTALSKINSDIR) RMANAGERDIR=$(DESTDIR)/$(MANAGERDIR) RMANAGERSITEDIR=$(DESTDIR)/$(MANAGERSITEDIR) RMANAGERSTATICDIR=$(DESTDIR)/$(MANAGERSTATICDIR) RMANAGERPSGIDIR=$(DESTDIR)/$(MANAGERPSGIDIR) RMANAGERTEMPLATESDIR=$(DESTDIR)/$(MANAGERTEMPLATESDIR) RDOCDIR=$(DESTDIR)/$(DOCDIR) RDEFDOCDIR=$(DESTDIR)/$(DEFDOCDIR) RTESTDIR=$(DESTDIR)/$(TESTDIR) REXAMPLESDIR=$(DESTDIR)/$(EXAMPLESDIR) RTOOLSDIR=$(DESTDIR)/$(TOOLSDIR) RHANDLERDIR=$(DESTDIR)/$(HANDLERDIR) RCONFDIR=$(DESTDIR)/$(CONFDIR) RCRONDIR=$(DESTDIR)/$(CRONDIR) RFILECONFIGDIR=$(DESTDIR)/$(FILECONFIGDIR) RAPACHESESSIONFILEDIR=$(DESTDIR)/$(APACHESESSIONFILEDIR) RAPACHESESSIONFILELOCKDIR=$(DESTDIR)/$(APACHESESSIONFILELOCKDIR) RAPACHEPSESSIONFILEDIR=$(DESTDIR)/$(APACHEPSESSIONFILEDIR) RAPACHEPSESSIONFILELOCKDIR=$(DESTDIR)/$(APACHEPSESSIONFILELOCKDIR) RFILENOTIFDIR=$(DESTDIR)/$(APACHEFILENOTIFDIR) RCAPTCHADIR=$(DESTDIR)/$(CAPTCHADIR) RFASTCGISOCKDIR=$(DESTDIR)/$(FASTCGISOCKDIR) VERSION=`head -n1 changelog |sed -e 's/lemonldap-ng (//' -e 's/).*$$//'` PORTALSKINS=`ls $(SRCPORTALDIR)/example/skins/` DIFF=diff -aurN -x '*.tpl' -x '*.bak' -x .svn -x '*.map' -x '*.min.js' -x '*.min.css' -x '*.swp' --ignore-matching-lines='.*jquery.*' --ignore-matching-lines='.*lemonldap-ng\.ini.*' DIFFPREFIX= MANAGERLIBSTOREMOVEFORDEBIAN=$(RMANAGERSTATICDIR)/bwr/jquery/ \ $(RMANAGERSTATICDIR)/bwr/angular/ \ $(RMANAGERSTATICDIR)/bwr/angular-animate/ \ $(RMANAGERSTATICDIR)/bwr/angular-cookie/ \ $(RMANAGERSTATICDIR)/bwr/bootstrap/ \ $(RMANAGERSTATICDIR)/bwr/es5-shim/ PORTALLIBSTOREMOVEFORDEBIAN=$(RPORTALSKINSDIR)/bootstrap/fonts \ $(RPORTALSKINSDIR)/bootstrap/css/bootstrap* \ $(RPORTALSKINSDIR)/bootstrap/js/bootstrap* \ $(RPORTALSKINSDIR)/common/js/jquery-* \ $(RPORTALSKINSDIR)/common/js/jquery.cookie* DOCLIBSTOREMOVEFORDEBIAN=pages/documentation/current/lib/tpl/bootstrap3 \ pages/documentation/current/lib/scripts/jquery-ui*.js \ pages/documentation/current/bootswatch/3.3.4/flatly/bootstrap.min.css DOCEXTERNALLIBS=$(DOCLIBSTOREMOVEFORDEBIAN) MANAGEREXTERNALLIBS=$(RMANAGERSTATICDIR)/bwr/ PORTALEXTERNALLIBS=$(PORTALLIBSTOREMOVEFORDEBIAN) $(RPORTALSKINSDIR)/common/js/jquery* # GENERATED SRC FILES MANAGERJSONSRC= scripts/jsongenerator.pl \ $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager/Build.pm \ $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager/Build/Attributes.pm \ $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager/Build/Tree.pm \ $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager/Conf/Zero.pm MANAGERJSONDST=$(SRCMANAGERDIR)/site/static/struct.json \ $(SRCMANAGERDIR)/site/static/js/conftree.js \ $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager/Attributes.pm \ $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager/Constants.pm \ $(SRCCOMMONDIR)/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm \ $(SRCCOMMONDIR)/lib/Lemonldap/NG/Common/Conf/Constants.pm \ _example/conf/lmConf-1.js # Javascript and CSS to minify JSSRCFILES:=$(shell find $(SRCMANAGERDIR)/site/static/js $(SRCPORTALDIR)/example -type f -name '*.js' ! -name '*.min.js') \ $(SRCMANAGERDIR)/site/static/bwr/file-saver.js/FileSaver.js CSSSRCFILES:=$(shell find $(SRCMANAGERDIR)/site/static $(SRCPORTALDIR)/example -type f -name '*.css' ! -name '*.min.css') # Minified files JSDSTFILES=$(JSSRCFILES:.js=.min.js) CSSDSTFILES=$(CSSSRCFILES:.css=.min.css) # ------- # TARGETS # ------- # Targets section contains the following subsections: # - 'all' that must be defined at first # - configure targets # - make targets # - test targets # - end-to-end tests # - install targets # - cleaning targets # - Perl libraries uninstall targets # - packaging targets # - developper corner all: configure common handler manager portal @echo @echo "Building succeed. Now run :" @echo " - 'make test' to verify your installation" @echo " - 'make install PROD=yes' to install all" @echo @echo " MAKE INSTALL OPTIONS:" @echo " - PROD=yes : use js/css minified files" @echo " - USEDEBIANLIBS=yes : use some Debian shared js/css files" @echo " - USEEXTERNALLIBS=yes : use external links for some js/css files" @echo @echo 'Other targets :' @echo " * Partial build :" @echo " - portal, manager, handler" @echo " * Doxygen documentation" @echo " - doxygen (to build Doxygen documentation in doc/devel/)" @echo @echo 'Other targets launched by "make install" :' @echo " * Perl libraries install :" @echo " - install_libs (all Perl libraries)" @echo " - install_portal_libs" @echo " - install_manager_libs" @echo " - install_handler_libs" @echo " * Binaries install :" @echo " - install_bin ($(BINDIR))" @echo " * FastCGI server install (required for Nginx)" @echo " - install_fastcgi_server ($(SBINDIR))" @echo " * Web sites install :" @echo " - install_site (all sites including install_doc_site)" @echo " - install_portal_site ($(PORTALDIR))" @echo " - install_manager_site ($(MANAGERDIR))" @echo " - install_handler_site ($(HANDLERDIR))" @echo " * Documentation install :" @echo " - install_doc_site ($(DEFDOCDIR))" @echo " - install_examples_site ($(EXAMPLESDIR))" @echo @echo "Other languages documentation (fr only for now)" @echo " - fr-doc (needs OmegaT)" @echo " - install_fr_doc_site" @echo # Configure targets # ----------------- configure: common_conf handler_conf portal_conf manager_conf minify: $(JSDSTFILES) $(CSSDSTFILES) %.min.css: %.css @echo "Compressing $*.css" @yui-compressor $*.css > $*.min.css %.min.js: %.js @echo "Compressing $*.js" @yui-compressor $*.js > $*.min.js fastcgi-server/man/llng-fastcgi-server.1p: fastcgi-server/sbin/llng-fastcgi-server @echo Update FastCGI man page @pod2man -name llng-fastcgi-server fastcgi-server/sbin/llng-fastcgi-server >fastcgi-server/man/llng-fastcgi-server.1p # Perl libraries configuration json: $(MANAGERJSONDST) fastcgi-server/man/llng-fastcgi-server.1p @if which yui-compressor; then $(MAKE) minify; fi $(MANAGERJSONDST): $(MANAGERJSONSRC) ./scripts/jsongenerator.pl common_conf: ${SRCCOMMONDIR}/Makefile handler_conf: ${SRCHANDLERDIR}/Makefile portal_conf: ${SRCPORTALDIR}/Makefile manager_conf: ${SRCMANAGERDIR}/Makefile ${SRCCOMMONDIR}/Makefile: @cd ${SRCCOMMONDIR}; LMNGCONFFILE=$(STORAGECONFFILE) $(PERL) Makefile.PL $(PERLOPTIONS) ${SRCHANDLERDIR}/Makefile: @cd ${SRCHANDLERDIR}; $(PERL) Makefile.PL $(PERLOPTIONS) ${SRCPORTALDIR}/Makefile: @cd ${SRCPORTALDIR}; $(PERL) Makefile.PL $(PERLOPTIONS) ${SRCMANAGERDIR}/Makefile: @cd ${SRCMANAGERDIR}; $(PERL) Makefile.PL $(PERLOPTIONS) # Make targets # ------------ common: common_conf @$(MAKE) -C ${SRCCOMMONDIR} handler: handler_conf common @$(MAKE) -C ${SRCHANDLERDIR} portal: portal_conf common @$(MAKE) -C ${SRCPORTALDIR} manager: manager_conf handler $(MAKE) -C ${SRCMANAGERDIR} # Test targets # ------------ test: all common_test handler_test portal_test manager_test extra_test common_test: common @$(MAKE) -C ${SRCCOMMONDIR} test handler_test: handler @$(MAKE) -C ${SRCHANDLERDIR} test FULLPERL="$(PERL) -I../${SRCCOMMONDIR}/blib/lib/" portal_test: portal @$(MAKE) -C ${SRCPORTALDIR} test FULLPERL="$(PERL) -I../${SRCCOMMONDIR}/blib/lib/ -I../${SRCHANDLERDIR}/blib/lib/" manager_test: manager @$(MAKE) -C ${SRCMANAGERDIR} test FULLPERL="$(PERL) -I../${SRCCOMMONDIR}/blib/lib/ -I../${SRCHANDLERDIR}/blib/lib/" extra_test: all cd ${SRCPORTALDIR} && prove -b -I ../$(SRCCOMMONDIR)/blib/lib -I ../$(SRCHANDLERDIR)/blib/lib -I../${SRCPORTALDIR}/blib/lib/ xt # PERL_DL_NONLAZY=1 $(PERL) "-MExtUtils::Command::MM" "-e" "test_harness(0, 'lemonldap-ng-common/blib/lib', 'lemonldap-ng-handler/blib/lib', 'lemonldap-ng-manager/blib/lib', 'lemonldap-ng-portal/blib/lib')" extra-tests/*.t # End-to-end tests # ---------------- e2e_test: all prepare_test_server start_web_server launch_protractor stop_web_server prepare_test_server: @mkdir -p e2e-tests/conf/sessions/lock e2e-tests/conf/persistents/lock $(MAKE) install_webserver_conf install_test_site install_fastcgi_server \ CONFDIR=`pwd`/e2e-tests/conf \ RCONFDIR=e2e-tests/conf \ ERASECONFIG=1 \ VHOSTLISTEN='*:$(TESTWEBSERVERPORT)' \ PORT=$(TESTWEBSERVERPORT) \ FASTCGISOCKDIR=`pwd`/e2e-tests/conf \ PORTALDIR=`pwd`/$(SRCPORTALDIR)/example \ MANAGERDIR=`pwd`/$(SRCMANAGERDIR)/site \ TESTDIR=`pwd`/e2e-tests/conf/site \ MANAGERPSGIDIR=`pwd`/e2e-tests \ DEFDOCDIR=`pwd`/doc \ FRDOCDIR=`pwd`/po-doc/fr \ SBINDIR=`pwd`/e2e-tests/conf/sbin \ INITDIR=`pwd`/e2e-tests/conf/init \ ETCDEFAULTDIR=`pwd`/e2e-tests/conf/def @cp e2e-tests/lmConf-1.js e2e-tests/lemonldap-ng.ini e2e-tests/env.conf e2e-tests/test-nginx.conf e2e-tests/conf/ @cp e2e-tests/form.html e2e-tests/conf/site @perl -i -pe 'BEGIN{$$p=`pwd`;chomp $$p}s#__pwd__#$$p#;s#__port__#$(TESTWEBSERVERPORT)#;s#__FASTCGISOCKDIR__#$(FASTCGISOCKDIR)#;' \ e2e-tests/conf/lemonldap-ng.ini \ e2e-tests/conf/lmConf-1.js \ e2e-tests/conf/env.conf \ e2e-tests/conf/test-nginx.conf e2e-tests/conf/apache2.pid: start_web_server start_web_server: all prepare_test_server # Clean old server if launched -@[ -e e2e-tests/conf/apache2.pid ] && kill `cat e2e-tests/conf/apache2.pid` || true -@[ -e e2e-tests/conf/nginx.pid ] && kill `cat e2e-tests/conf/nginx.pid` || true -@[ -e e2e-tests/conf/llng-fastcgi.pid ] && kill `cat e2e-tests/conf/llng-fastcgi.pid` && rm -f e2e-tests/conf/llng-fastcgi.pid || true # Start web server (designed for Debian, path may be broken else) @if test "$(TESTWEBSERVER)" = "apache"; then \ LLNG_DEFAULTCONFFILE=`pwd`/e2e-tests/conf/lemonldap-ng.ini /usr/sbin/apache2 -d `pwd`/e2e-tests -f apache2.conf -k start; \ elif test "$(TESTWEBSERVER)" = "nginx"; then \ echo "Testing nginx conf"; \ $(NGINX) -t -p `pwd`/e2e-tests \ -g 'error_log '`pwd`'/e2e-tests/conf/nginx.log;' \ -c `pwd`/e2e-tests/nginx.conf \ 2>&1 | grep -v 'Permission denied' || true; \ echo "Launching nginx"; \ $(NGINX) -p `pwd`/e2e-tests \ -g 'error_log '`pwd`'/e2e-tests/conf/nginx.log;' \ -c `pwd`/e2e-tests/nginx.conf \ 2>&1 | grep -v 'Permission denied' || true; \ echo "Launching plackup"; \ $(MAKE) plackup; \ else \ echo "!!!!! Unknown test server: $(TESTWEBSERVER) !!!!!" >&2; \ exit 1; \ fi reload_web_server: @if [ -e e2e-tests/conf/apache2.pid ]; then \ LLNG_DEFAULTCONFFILE=`pwd`/e2e-tests/conf/lemonldap-ng.ini \ /usr/sbin/apache2 -d `pwd`/e2e-tests -f apache2.conf -k graceful; \ elif [ -e e2e-tests/conf/nginx.pid ]; then \ echo "Reloading Nginx"; \ kill -HUP `cat e2e-tests/conf/nginx.pid`; \ kill `cat e2e-tests/conf/llng-fastcgi.pid` || true; \ $(MAKE) plackup; \ else \ $(MAKE) start_web_server; \ fi launch_protractor: all e2e-tests/conf/apache2.pid # Start e2e tests # NB: you must have protractor installed (using npm install -g protractor) # and have run update-webdriver at least once and have a node.js > 4.0 @TESTWEBSERVERPORT=$(TESTWEBSERVERPORT) protractor e2e-tests/protractor-conf.js stop_web_server: # Stop web server -@[ -e e2e-tests/conf/apache2.pid ] && kill `cat e2e-tests/conf/apache2.pid` || true -@[ -e e2e-tests/conf/nginx.pid ] && kill `cat e2e-tests/conf/nginx.pid` ||true -@[ -e e2e-tests/conf/llng-fastcgi.pid ] && kill `cat e2e-tests/conf/llng-fastcgi.pid` && rm -f e2e-tests/conf/llng-fastcgi.pid || true # Clean @rm -rf e2e-tests/conf restart_web_server: start_web_server plackup: @LLNG_DEFAULTCONFFILE=`pwd`/e2e-tests/conf/lemonldap-ng.ini \ perl -I. -I`pwd`/lemonldap-ng-common/blib/lib/ \ -I`pwd`/lemonldap-ng-handler/blib/lib/ \ -I`pwd`/lemonldap-ng-portal/blib/lib/ \ -I`pwd`/lemonldap-ng-manager/blib/lib/ \ e2e-tests/conf/sbin/llng-fastcgi-server \ -f e2e-tests/custom.pm \ -F >e2e-tests/conf/fastcgi.log 2>&1 & install_test: @TESTWEBSERVERPORT=$(PORT) protractor e2e-tests/protractor-conf.js # Install targets # --------------- install: install_libs install_bin install_fastcgi_server install_site # Perl libraires install install_libs: common_install_libs install_handler_libs install_portal_libs install_manager_libs common_install_libs: common @$(MAKE) -C ${SRCCOMMONDIR} install install_handler_libs: handler @$(MAKE) -C ${SRCHANDLERDIR} install install_portal_libs: portal @$(MAKE) -C ${SRCPORTALDIR} install install_manager_libs: manager @$(MAKE) -C ${SRCMANAGERDIR} install install_bin: install_conf_dir # Binary install @install -v -d $(RBINDIR) @cp -f\ ${SRCHANDLERDIR}/example/scripts/purgeLocalCache \ ${SRCPORTALDIR}/example/scripts/purgeCentralCache \ ${SRCPORTALDIR}/example/scripts/buildPortalWSDL \ ${SRCCOMMONDIR}/scripts/convertConfig \ ${SRCCOMMONDIR}/scripts/lmMigrateConfFiles2ini \ ${SRCCOMMONDIR}/scripts/rotateOidcKeys \ ${SRCMANAGERDIR}/scripts/lmConfigEditor \ ${SRCCOMMONDIR}/scripts/lemonldap-ng-cli \ $(RBINDIR) @if [ ! "$(APACHEUSER)" ]; then \ $(PERL) -i -pe 's#__APACHEUSER__#nobody#g;' $(RBINDIR)/lmConfigEditor $(RBINDIR)/lemonldap-ng-cli; \ else \ $(PERL) -i -pe 's#__APACHEUSER__#$(APACHEUSER)#g;' $(RBINDIR)/lmConfigEditor $(RBINDIR)/lemonldap-ng-cli; \ fi @if [ ! "$(APACHEGROUP)" ]; then \ $(PERL) -i -pe 's#__APACHEGROUP__#nobody#g;' $(RBINDIR)/lmConfigEditor $(RBINDIR)/lemonldap-ng-cli; \ else \ $(PERL) -i -pe 's#__APACHEGROUP__#$(APACHEGROUP)#g;' $(RBINDIR)/lmConfigEditor $(RBINDIR)/lemonldap-ng-cli; \ fi @chmod +x $(RBINDIR)/* install_fastcgi_server: @install -v -d $(RSBINDIR) $(RINITDIR) $(RETCDEFAULTDIR) $(RFASTCGISOCKDIR) @cp -f fastcgi-server/sbin/llng-fastcgi-server $(RSBINDIR) @chmod +x $(RSBINDIR)/llng-fastcgi-server @cp -f fastcgi-server/rc/llng-fastcgi-server $(RINITDIR) @cp -f fastcgi-server/default/llng-fastcgi-server $(RETCDEFAULTDIR) @$(PERL) -pi -e 's#__SBINDIR__#$(SBINDIR)#;s#__DEFAULTDIR__#$(ETCDEFAULTDIR)#;s#__FASTCGISOCKDIR__#$(FASTCGISOCKDIR)#g;' \ $(RETCDEFAULTDIR)/llng-fastcgi-server \ $(RSBINDIR)/llng-fastcgi-server \ $(RINITDIR)/llng-fastcgi-server @if [ ! "$(FASTCGIUSER)" ]; then \ $(PERL) -pi -e 's#__USER__#nobody#' $(RETCDEFAULTDIR)/llng-fastcgi-server; \ else \ $(PERL) -pi -e 's#__USER__#$(FASTCGIUSER)#' $(RETCDEFAULTDIR)/llng-fastcgi-server; \ fi @if [ ! "$(FASTCGIGROUP)" ]; then \ $(PERL) -pi -e 's#__GROUP__#nobody#' $(RETCDEFAULTDIR)/llng-fastcgi-server; \ else \ $(PERL) -pi -e 's#__GROUP__#$(FASTCGIGROUP)#' $(RETCDEFAULTDIR)/llng-fastcgi-server; \ fi @if [ "$(FASTCGIUSER)" != "" ]; then \ chown $(FASTCGIUSER) $(RFASTCGISOCKDIR) || exit 1; \ if [ "$(FASTCGIGROUP)" != "" ]; then \ chgrp $(FASTCGIGROUP) $(RFASTCGISOCKDIR) || exit 1; \ fi; \ chmod 770 $(RFASTCGISOCKDIR); \ else \ chmod 777 $(RFASTCGISOCKDIR); \ fi # Site install install_site: install_manager_site install_portal_site install_handler_site install_test_site install_examples_site install_doc_site install_webserver_conf # Site install @install -v -d $(RCONFDIR) # Check if erase is wanted @if [ "$(ERASECONFIG)" -eq "1" ]; then \ cp -f _example/etc/for_etc_hosts $(RCONFDIR); \ fi @$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g' $(RCONFDIR)/for_etc_hosts # Fix a lost of rights on the main directory @chmod 755 $(RBINDIR) $(RDOCUMENTROOT) $(REXAMPLESDIR) $(RHANDLERDIR) $(RPORTALSKINSDIR) $(RMANAGERSITEDIR) $(RTOOLSDIR) $(RCONFDIR) $(RDATADIR) @echo @echo "LemonLDAP::NG v${VERSION} is installed with these parameters:" @echo " - System configuration: ${CONFDIR}" @echo " - DNS domain (for cookies and virtual hosts): ${DNSDOMAIN}" @echo @echo "To finish configuration:" @echo @echo "1 - Add this in your Apache $(APACHEVERSION) configuration file:" @echo " include ${CONFDIR}/portal-apache$(APACHEVERSION).conf" @echo " include ${CONFDIR}/handler-apache$(APACHEVERSION).conf" @echo " include ${CONFDIR}/manager-apache$(APACHEVERSION).conf" @echo " include ${CONFDIR}/test-apache$(APACHEVERSION).conf" @echo @echo "2 - Restart Apache:" @echo " apache$(APACHEVERSION)ctl restart" @echo @echo "3 - Run 'make postconf' as root to update /etc/hosts if your DNS service does not known auth.$(DNSDOMAIN) and manager.$(DNSDOMAIN)" @echo @echo "4 - Try to connect to http://test1.${DNSDOMAIN}/ or http://test2.${DNSDOMAIN}/ with demonstration accounts:" @echo " - rtyler/rtyler" @echo " - msmith/msmith" @echo " - dwho/dwho" @echo @echo "5 - Connect to Manager at http://manager.${DNSDOMAIN}/ to edit configuration" @echo @if [ ! "$(APACHEUSER)" ]; then \ echo;echo " Warning, since APACHEUSER was not set, $(APACHESESSIONFILEDIR), $(APACHEPSESSIONFILEDIR), $(CAPTCHADIR) and $(CONFDIR) have permissive permissions."; \ echo " Fix them by yourself to restrict their view to apache process only"; \ fi @echo install_webserver_conf: @install -m 755 -v -d $(RCONFDIR) @if [ "$(ERASECONFIG)" -eq "1" ]; then \ cp -f _example/etc/portal-apache$(APACHEVERSION).conf $(RCONFDIR); \ cp -f _example/etc/handler-apache$(APACHEVERSION).conf $(RCONFDIR); \ cp -f _example/etc/manager-apache$(APACHEVERSION).conf $(RCONFDIR); \ cp -f _example/etc/test-apache$(APACHEVERSION).conf $(RCONFDIR); \ cp -f _example/etc/*nginx*.conf $(RCONFDIR); \ fi @$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g; \ s#__PORTALDIR__#$(PORTALDIR)/#g; \ s#__MANAGERDIR__#$(MANAGERDIR)/#g; \ s#__MANAGERSTATICDIR__#$(MANAGERSTATICDIR)/#g; \ s#__MANAGERPSGIDIR__#$(MANAGERPSGIDIR)/#g; \ s#__TESTDIR__#$(TESTDIR)/#g; \ s#__PORT__#$(PORT)#g; \ s#__CONFDIR__#$(CONFDIR)#g; \ s#__FASTCGISOCKDIR__#$(FASTCGISOCKDIR)#g; \ s#__VHOSTLISTEN__#$(VHOSTLISTEN)#g; \ s#__DEFDOCDIR__#$(DEFDOCDIR)/#g; \ s#__FRDOCDIR__#$(FRDOCDIR)/#g;' $(RCONFDIR)/*apache*.conf $(RCONFDIR)/*nginx*.conf install_manager_site: install_conf_dir # Manager install @install -v -d $(RMANAGERDIR) $(RMANAGERSTATICDIR) $(RMANAGERPSGIDIR) \ $(RMANAGERTEMPLATESDIR) @cp -pR $(SRCMANAGERDIR)/site/static/* $(RMANAGERSTATICDIR) @for f in $(SRCMANAGERDIR)/site/templates/*.tpl; do \ ./scripts/transform-templates \ usedebianlibs $(USEDEBIANLIBS) \ useexternallibs $(USEEXTERNALLIBS) \ jsminified $(JSCOMPRESS) \ cssminified $(CSSCOMPRESS) <$$f \ > $(RMANAGERTEMPLATESDIR)/`basename $$f`; \ done @if test "$(USEEXTERNALLIBS)" = "yes"; then \ rm -rvf $(MANAGEREXTERNALLIBS); \ elif test "$(USEDEBIANLIBS)" = "yes"; then \ rm -rvf $(MANAGERLIBSTOREMOVEFORDEBIAN); \ fi @cp -pR $(SRCMANAGERDIR)/eg/* $(RMANAGERPSGIDIR) # Clean svn files @rm -rf $$(find ${RMANAGERSTATICDIR} $(RMANAGERPSGIDIR) \ $(RMANAGERTEMPLATESDIR) $(RCONFDIR) -type d -name .svn) $(PERL) -i -pe 's#__MANAGERSTATICDIR__#$(MANAGERRELATIVESTATICDIR)#g' $(RCONFDIR)/$(CONFFILENAME) $(PERL) -i -pe 's#__MANAGERTEMPLATESDIR__#$(MANAGERTEMPLATESDIR)#g' $(RCONFDIR)/$(CONFFILENAME) install_portal_site: install_conf_dir # Portal install @install -v -d $(RPORTALDIR) $(RPORTALSKINSDIR) \ $(RPORTALDIR)/skins/ \ $(RCRONDIR) $(RCONFDIR) @for skin in $$(ls $(SRCPORTALDIR)/example/skins/); do \ [ -h $(RPORTALDIR)/skins/$$skin ] && rm -f $(RPORTALDIR)/skins/$$skin; \ install -v -d $(RPORTALSKINSDIR)/$$skin; \ done @cp -pR -f ${SRCPORTALDIR}/example/index_skin.pl ${RPORTALDIR}/index.pl @cp -pR -f ${SRCPORTALDIR}/example/mail.pl ${RPORTALDIR} @cp -pR -f ${SRCPORTALDIR}/example/metadata.pl ${RPORTALDIR} @cp -pR -f ${SRCPORTALDIR}/example/openid-configuration.pl ${RPORTALDIR} @cp -pR -f ${SRCPORTALDIR}/example/cdc.pl ${RPORTALDIR} @cp -pR -f ${SRCPORTALDIR}/example/register.pl ${RPORTALDIR} @cp -pR -f ${SRCPORTALDIR}/example/public.pl ${RPORTALDIR} @tar -cf - -C ${SRCPORTALDIR}/example/skins/ $$(ls ${SRCPORTALDIR}/example/skins/) |tar -xf - -C $(RPORTALSKINSDIR) @for f in `find $(RPORTALSKINSDIR) -type f -name '*.tpl'`; do \ ./scripts/transform-templates \ usedebianlibs $(USEDEBIANLIBS) \ useexternallibs $(USEEXTERNALLIBS) \ jsminified $(JSCOMPRESS) \ cssminified $(CSSCOMPRESS) <$$f \ >$$f.tmp; \ mv -f $$f.tmp $$f; \ done @if test "$(USEEXTERNALLIBS)" = "yes"; then \ rm -rvf $(PORTALEXTERNALLIBS); \ elif test "$(USEDEBIANLIBS)" = "yes"; then \ rm -rvf $(PORTALLIBSTOREMOVEFORDEBIAN); \ fi @if [ "$(PORTALDIR)/skins/" != "$(PORTALSKINSDIR)/" ]; then \ for skin in $$(ls $(SRCPORTALDIR)/example/skins/); do \ rm -rf $(RPORTALDIR)/skins/$$skin/; \ ln -s $(PORTALSKINSDIR)/$$skin $(RPORTALDIR)/skins/$$skin; \ done; \ fi # Cron files @cp -f $(SRCPORTALDIR)/example/scripts/purgeCentralCache.cron.d $(RCRONDIR)/lemonldap-ng-portal @if [ ! "$(APACHEUSER)" ]; then \ $(PERL) -i -pe 's#__APACHEUSER__#nobody#g;' $(RCRONDIR)/lemonldap-ng-portal; \ else \ $(PERL) -i -pe 's#__APACHEUSER__#$(APACHEUSER)#g;' $(RCRONDIR)/lemonldap-ng-portal; \ fi @$(PERL) -i -pe 's#__BINDIR__#$(BINDIR)#g;' $(RCRONDIR)/lemonldap-ng-portal # Clean SVN files @rm -rf $$(find ${RPORTALDIR} $(RPORTALSKINSDIR) $(RCRONDIR) $(RCONFDIR) -type d -name .svn) install_handler_site: install_conf_dir # Handler install @install -v -d ${RHANDLERDIR} @cp -f $(SRCHANDLERDIR)/example/scripts/purgeLocalCache.cron.d $(RCRONDIR)/lemonldap-ng-handler @if [ ! "$(APACHEUSER)" ]; then \ $(PERL) -i -pe 's#__APACHEUSER__#nobody#g;' $(RCRONDIR)/lemonldap-ng-handler; \ else \ $(PERL) -i -pe 's#__APACHEUSER__#$(APACHEUSER)#g;' $(RCRONDIR)/lemonldap-ng-handler; \ fi @$(PERL) -i -pe 's#__BINDIR__#$(BINDIR)#g;' $(RCRONDIR)/lemonldap-ng-handler @rm -rf $$(find $(RHANDLERDIR) -type d -name .svn) install_test_site: # Test site install @install -v -d $(RTESTDIR) @cp -pR -f _example/test/* $(RTESTDIR) @rm -rf $$(find $(RTESTDIR) -type d -name .svn) @$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g' $(RTESTDIR)/index.pl @rm -rf $$(find $(RTESTDIR) -type d -name .svn) install_examples_site: # Examples install @install -v -d $(REXAMPLESDIR) @for i in portal handler; do \ cp -a -f lemonldap-ng-$$i/example $(REXAMPLESDIR)/$$i; \ done @for i in manager; do \ cp -a -f lemonldap-ng-$$i/eg $(REXAMPLESDIR)/$$i; \ done @rm -rf $(REXAMPLESDIR)/portal/skins \ $(REXAMPLESDIR)/manager/skins \ @rm -rf $$(find $(REXAMPLESDIR) -type d -name .svn) @$(PERL) -i -pe 's#__SESSIONDIR__#$(APACHESESSIONFILEDIR)/#g;' $(REXAMPLESDIR)/portal/*.pl @$(PERL) -i -pe 's#__PSESSIONDIR__#$(APACHEPSESSIONFILEDIR)/#g;' $(REXAMPLESDIR)/portal/*.pl install_doc_site: # Offline documentation install @rm -rf $(RDEFDOCDIR) # Install doc directories @install -v -d -m 755 $(RDEFDOCDIR) @cd doc && find * -type d |(cd $(RDEFDOCDIR); xargs install -v -d -m 755) && cd - # Install HTML files @cd doc && for f in `find * -type f -name '*.html'`; do \ echo "Installing $$f"; \ ../scripts/transform-templates \ usedebianlibs $(USEDEBIANLIBS) \ useexternallibs $(USEEXTERNALLIBS) \ jsminified $(JSCOMPRESS) \ cssminified $(CSSCOMPRESS) <$$f \ > $(RDEFDOCDIR)/$$f; \ done && cd - # Install other files @cd doc && for f in `find * -type f ! -name '*.html'`; do \ install -v -m 644 $$f $(RDEFDOCDIR)/$$f; \ done && cd - # Install symlinks @cd doc && tar cf - `find * -type l` | tar xvf - -C $(RDEFDOCDIR) && cd - # Remove js @cd $(RDEFDOCDIR) && if test "$(USEEXTERNALLIBS)" = "yes"; then \ rm -rvf $(DOCEXTERNALLIBS); \ elif test "$(USEDEBIANLIBS)" = "yes"; then \ rm -rvf $(DOCLIBSTOREMOVEFORDEBIAN); \ fi && cd - install_conf_dir: install_sessions_dir install_notif_dir install_captcha_dir # Configuration files install @install -v -d $(RCONFDIR) $(RFILECONFIGDIR) $(RTOOLSDIR) @if [ "$(ERASECONFIG)" -eq "1" ]; then \ cp -f $(SRCCOMMONDIR)/$(CONFFILENAME) $(RCONFDIR); \ $(PERL) -i -pe 's#^dirName\s*=\s*.*#dirName = $(FILECONFIGDIR)#g' $(RCONFDIR)/$(CONFFILENAME); \ fi @cp _example/conf/lmConf-1.js $(RFILECONFIGDIR) @$(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g;\ s#__SESSIONDIR__#$(APACHESESSIONFILEDIR)#g;\ s#__PSESSIONDIR__#$(APACHEPSESSIONFILEDIR)#g;\ s#__NOTIFICATIONDIR__#$(APACHEFILENOTIFDIR)#g;' $(RFILECONFIGDIR)/lmConf-1.js @if [ "$(APACHEUSER)" != "" ]; then \ chown $(APACHEUSER) $(RFILECONFIGDIR) || exit 1; \ if [ "$(APACHEGROUP)" != "" ]; then \ chgrp $(APACHEGROUP) $(RFILECONFIGDIR) || exit 1; \ fi; \ chmod 770 $(RFILECONFIGDIR); \ else \ chmod 777 $(RFILECONFIGDIR); \ fi @cp $(SRCCOMMONDIR)/tools/lmConfig.* $(SRCCOMMONDIR)/tools/apache-session-mysql.sql $(RTOOLSDIR) @cp $(SRCCOMMONDIR)/tools/sso.schema $(RTOOLSDIR) $(PERL) -i -pe 's/__DNSDOMAIN__/$(DNSDOMAIN)/g' $(RCONFDIR)/$(CONFFILENAME) @rm -rf $$(find $(RCONFDIR) $(RFILECONFIGDIR) $(RTOOLSDIR) -type d -name .svn) install_sessions_dir: @install -m 777 -v -d $(RAPACHESESSIONFILEDIR) $(RAPACHESESSIONFILELOCKDIR) $(RAPACHEPSESSIONFILEDIR) $(RAPACHEPSESSIONFILELOCKDIR) # Fix Apache::Session directories permissions @if [ "$(APACHEUSER)" != "" ]; then \ chown $(APACHEUSER) $(RAPACHESESSIONFILEDIR) $(RAPACHESESSIONFILELOCKDIR) $(RAPACHEPSESSIONFILEDIR) $(RAPACHEPSESSIONFILELOCKDIR) || exit 1; \ if [ "$(APACHEGROUP)" != "" ]; then \ chgrp $(APACHEGROUP) $(RAPACHESESSIONFILEDIR) $(RAPACHESESSIONFILELOCKDIR) $(RAPACHEPSESSIONFILEDIR) $(RAPACHEPSESSIONFILELOCKDIR) || exit 1; \ fi; \ chmod 770 $(RAPACHESESSIONFILEDIR) $(RAPACHESESSIONFILELOCKDIR) $(RAPACHEPSESSIONFILEDIR) $(RAPACHEPSESSIONFILELOCKDIR); \ else \ chmod 777 $(RAPACHESESSIONFILEDIR) $(RAPACHESESSIONFILELOCKDIR) $(RAPACHEPSESSIONFILEDIR) $(RAPACHEPSESSIONFILELOCKDIR); \ fi install_notif_dir: @install -m 777 -v -d $(RFILENOTIFDIR) # Fix notifications directory permissions @if [ "$(APACHEUSER)" != "" ]; then \ chown $(APACHEUSER) $(RFILENOTIFDIR) || exit 1; \ if [ "$(APACHEGROUP)" != "" ]; then \ chgrp $(APACHEGROUP) $(RFILENOTIFDIR) || exit 1; \ fi; \ chmod 770 $(RFILENOTIFDIR); \ else \ chmod 777 $(RFILENOTIFDIR); \ fi install_captcha_dir: @install -m 777 -v -d $(RCAPTCHADIR) # Fix captcha directory permissions @if [ "$(APACHEUSER)" != "" ]; then \ chown $(APACHEUSER) $(RCAPTCHADIR) || exit 1; \ if [ "$(APACHEGROUP)" != "" ]; then \ chgrp $(APACHEGROUP) $(RCAPTCHADIR) || exit 1; \ fi; \ chmod 770 $(RCAPTCHADIR); \ else \ chmod 777 $(RCAPTCHADIR); \ fi postconf_hosts: @cat ${CONFDIR}/for_etc_hosts >> /etc/hosts @echo "/etc/hosts was updated" postconf: postconf_hosts @echo "Post configuration done" debian-install: @echo "You have now to choose between:" @echo " - make debian-install-for-apache" @echo " - make ubuntu-install-for-apache" @echo " - make debian-install-for-nginx" @echo " - make ubuntu-install-for-nginx" @echo @echo "All packages will be built in /tmp/ but only those needed by the" @echo "server you will choose will be installed" @exit 1 debian-install-for-apache: debian-packages perl -i -ne 'next if/fastcgi.*deb$$/;s/lemonldap-ng-fastcgi-server//;print' /tmp/lemonldap-ng_$(VERSION)*.changes cd /tmp/lemonldap-ng-$(VERSION) && \ $(SU) debi debian-install-for-nginx: debian-packages cd /tmp/lemonldap-ng-$(VERSION) && \ $(SU) debi ubuntu-install: debian-install ubuntu-install-for-apache: $(MAKE) debian-install-for-apache SU=sudo ubuntu-install-for-nginx: $(MAKE) debian-install-for-nginx SU=sudo # Cleaning targets # ---------------- distclean: clean clean: common_clean handler_clean portal_clean manager_clean omegat-clean stop_web_server @rm -f $$(find */ -name '*bak' -delete) @rm -rf doc/devel @rm -vf *gz *zip @rm -rf lemonldap-ng-$(VERSION) @echo "Cleaned" common_clean: @if test -e ${SRCCOMMONDIR}/Makefile;then $(MAKE) -C ${SRCCOMMONDIR} distclean;fi @rm -vf common* handler_clean: @if test -e ${SRCHANDLERDIR}/Makefile;then $(MAKE) -C ${SRCHANDLERDIR} distclean;fi @rm -vf handler* portal_clean: @if test -e ${SRCPORTALDIR}/Makefile;then $(MAKE) -C ${SRCPORTALDIR} distclean;fi @rm -vf portal* manager_clean: @if test -e ${SRCMANAGERDIR}/Makefile;then $(MAKE) -C ${SRCMANAGERDIR} distclean;fi @rm -vf manager* # Perl libraries uninstall targets # -------------------------------- uninstall: configure handler_uninstall portal_uninstall manager_uninstall common_uninstall: common @$(MAKE) -C ${SRCCOMMONDIR} uninstall @rm -vf common_uninstall handler_uninstall: handler @$(MAKE) -C ${SRCHANDLERDIR} uninstall @rm -vf handler_uninstall portal_uninstall: portal @$(MAKE) -C ${SRCPORTALDIR} uninstall @rm -vf portal_uninstall manager_uninstall: manager @$(MAKE) -C ${SRCMANAGERDIR} uninstall @rm -vf manager_uninstall # Packaging target # ---------------- dist: clean @mkdir -p lemonldap-ng-$(VERSION) @cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|debian\|rpm\)") lemonldap-ng-$(VERSION) @rm -rf $$(find lemonldap-ng-$(VERSION) -name .svn -print) @find $$dir -name '*.bak' -delete @rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION) @$(COMPRESS) lemonldap-ng-$(VERSION).$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION) rpm-dist: clean @mkdir -p lemonldap-ng-$(VERSION) @cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|debian\)") lemonldap-ng-$(VERSION) @rm -rf $$(find lemonldap-ng-$(VERSION) -name .svn -print) @find $$dir -name '*.bak' -delete @rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION) @$(COMPRESS) lemonldap-ng-$(VERSION).$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION) debian-dist: clean @mkdir -p lemonldap-ng-$(VERSION) @cp -pRH $$(find * -maxdepth 0|grep -v -e "\(lemonldap-ng-$(VERSION)\|rpm\)") lemonldap-ng-$(VERSION) @rm -rf $$(find lemonldap-ng-$(VERSION) -name .svn -print) @find $$dir -name '*.bak' -delete @cp lemonldap-ng-$(VERSION)/_example/etc/handler-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/handler-apache2.conf @cp lemonldap-ng-$(VERSION)/_example/etc/manager-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/manager-apache2.conf @cp lemonldap-ng-$(VERSION)/_example/etc/portal-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/portal-apache2.conf @cp lemonldap-ng-$(VERSION)/_example/etc/test-apache2.X.conf lemonldap-ng-$(VERSION)/_example/etc/test-apache2.conf @rm -rf lemonldap-ng-$(VERSION)/lemonldap-ng-$(VERSION) @$(COMPRESS) lemonldap-ng_$(VERSION).orig.$(COMPRESSSUFFIX) lemonldap-ng-$(VERSION) @rm -rf lemonldap-ng-$(VERSION) zip-dist: $(MAKE) dist "COMPRESS=zip -r" COMPRESSSUFFIX=zip manifest: configure @for i in ${SRCCOMMONDIR} ${SRCHANDLERDIR} ${SRCPORTALDIR} ${SRCMANAGERDIR}; do \ cd $$i; \ make manifest; \ rm -vf MANIFEST.*; \ cd -; \ done cpan: clean configure common_cpan handler_cpan portal_cpan manager_cpan for i in Common Portal Handler Manager; do \ $(UNCOMPRESS) Lemonldap-NG-$$i-*.$(COMPRESSSUFFIX) \ $$($(LISTCOMPRESSED) Lemonldap-NG-$$i-*.$(COMPRESSSUFFIX) |grep META.yml); \ mv Lemonldap-NG-$$i-*/META.yml lemonldap-ng-$$($(PERL) -e "print lc('$$i')")/; \ rm -rf Lemonldap-NG-$$i*/; \ done debian-local-packages: debian-packages debian-packages: debian-dist mv lemonldap-ng_$(VERSION).orig.$(COMPRESSSUFFIX) /tmp/ version=$(VERSION) && \ cd /tmp/ && \ rm -rf lemonldap-ng-$$version && \ $(UNCOMPRESS) lemonldap-ng_$$version.orig.$(COMPRESSSUFFIX) && \ cd lemonldap-ng-$$version && \ dpkg-buildpackage -us -uc # Developper corner # ----------------- common_cpan: common_conf @$(MAKE) -C ${SRCCOMMONDIR} dist @mv ${SRCCOMMONDIR}/Lemonldap*.gz . handler_cpan: handler_conf @$(MAKE) -C ${SRCHANDLERDIR} dist @mv ${SRCHANDLERDIR}/Lemonldap*.gz . portal_cpan: portal_conf @$(MAKE) -C ${SRCPORTALDIR} dist @mv ${SRCPORTALDIR}/Lemonldap*.gz . manager_cpan: manager_conf @$(MAKE) -C ${SRCMANAGERDIR} dist @mv ${SRCMANAGERDIR}/Lemonldap*.gz . documentation: @cd doc/ && ../scripts/doc.pl doxygen: clean $(PERL) -i -pe 's/^(PROJECT_NUMBER\s*=\s*)\d.*$$/$${1}'$(VERSION)'/' Doxyfile COLLABORATIVE_GRAPH=1 doxygen Doxyfile mkdir doc/devel/tmp mv doc/devel/html/inherit* doc/devel/tmp/ COLLABORATIVE_GRAPH=0 doxygen Doxyfile mv -f doc/devel/tmp/* doc/devel/html/ rm -rf doc/devel/tmp $(PERL) -i -pe 's/Graphical Class Hierarchy/Class Collaboration Graph/' doc/devel/html/inherits.html doc/devel/html/tree.html # Some files are not generated for i in doc/devel/html/*dot; do dot -T png -o $${i/.dot/.png} $$i; rm -f $$i; done diff: debian-diff debian-diff: @# Portal @$(DIFF) $(SRCPORTALDIR)/lib/Lemonldap/NG/Portal $(DIFFPREFIX)/usr/share/perl5/Lemonldap/NG/Portal ||true @$(DIFF) $(SRCPORTALDIR)/example/scripts/purgeCentralCache $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/purgeCentralCache ||true @$(DIFF) $(SRCPORTALDIR)/example/scripts/buildPortalWSDL $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/buildPortalWSDL ||true @for i in $(PORTALSKINS); do \ $(DIFF) -x 'jquery*' $(SRCPORTALDIR)/example/skins/$$i $(DIFFPREFIX)/usr/share/lemonldap-ng/portal-skins/$$i; \ done ||true @$(DIFF) $(SRCPORTALDIR)/example/index_skin.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/index.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/mail.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/mail.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/metadata.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/metadata.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/openid-configuration.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/openid-configuration.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/cdc.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/cdc.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/register.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/register.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/public.pl $(DIFFPREFIX)/var/lib/lemonldap-ng/portal/public.pl ||true @# Handler @$(DIFF) $(SRCHANDLERDIR)/lib/Lemonldap/NG/Handler $(DIFFPREFIX)/usr/share/perl5/Lemonldap/NG/Handler ||true @$(DIFF) $(SRCHANDLERDIR)/example/scripts/purgeLocalCache $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/purgeLocalCache ||true @# Common @$(DIFF) $(SRCCOMMONDIR)/lib/Lemonldap/NG/Common $(DIFFPREFIX)/usr/share/perl5/Lemonldap/NG/Common ||true @$(DIFF) $(SRCCOMMONDIR)/lib/Lemonldap/NG/Common.pm $(DIFFPREFIX)/usr/share/perl5/Lemonldap/NG/Common.pm ||true @$(DIFF) $(SRCCOMMONDIR)/scripts/lmMigrateConfFiles2ini $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/lmMigrateConfFiles2ini ||true @$(DIFF) $(SRCCOMMONDIR)/scripts/convertConfig $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/convertConfig ||true @$(DIFF) $(SRCCOMMONDIR)/scripts/rotateOidcKeys $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/rotateOidcKeys ||true @# Manager @$(DIFF) $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager $(DIFFPREFIX)/usr/share/perl5/Lemonldap/NG/Manager ||true @$(DIFF) $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager.pm $(DIFFPREFIX)/usr/share/perl5/Lemonldap/NG/Manager.pm ||true @$(DIFF) $(SRCMANAGERDIR)/site/static $(DIFFPREFIX)/usr/share/lemonldap-ng/manager/static ||true @$(DIFF) $(SRCMANAGERDIR)/site/templates $(DIFFPREFIX)/user/share/lemonldap-ng/manager/templates ||true @$(DIFF) --ignore-matching-lines='set.*get.*\[2\]' $(SRCMANAGERDIR)/scripts/lmConfigEditor $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/lmConfigEditor ||true @$(DIFF) --ignore-matching-lines='set.*get.*' $(SRCCOMMONDIR)/scripts/lemonldap-ng-cli $(DIFFPREFIX)/usr/share/lemonldap-ng/bin/lemonldap-ng-cli ||true default-diff: @# Portal @$(DIFF) $(SRCPORTALDIR)/lib/Lemonldap/NG/Portal /usr/local/share/perl/$(PERLVERSION)/Lemonldap/NG/Portal ||true @$(DIFF) $(SRCPORTALDIR)/example/scripts/purgeCentralCache $(LMPREFIX)/bin/purgeCentralCache ||true @$(DIFF) $(SRCPORTALDIR)/example/scripts/buildPortalWSDL $(LMPREFIX)/bin/buildPortalWSDL ||true @$(DIFF) $(SRCPORTALDIR)/example/skins $(LMPREFIX)/htdocs/portal/skins ||true @$(DIFF) $(SRCPORTALDIR)/example/index_skin.pl $(LMPREFIX)/htdocs/portal/index.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/mail.pl $(LMPREFIX)/htdocs/portal/mail.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/register.pl $(LMPREFIX)/htdocs/portal/register.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/metadata.pl $(LMPREFIX)/htdocs/portal/metadata.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/openid-configuration.pl $(LMPREFIX)/htdocs/portal/openid-configuration.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/cdc.pl $(LMPREFIX)/htdocs/portal/cdc.pl ||true @$(DIFF) $(SRCPORTALDIR)/example/public.pl $(LMPREFIX)/htdocs/portal/public.pl ||true @# Handler @$(DIFF) $(SRCHANDLERDIR)/lib/Lemonldap/NG/Handler /usr/local/share/perl/$(PERLVERSION)/Lemonldap/NG/Handler ||true @$(DIFF) $(SRCHANDLERDIR)/example/scripts/purgeLocalCache $(LMPREFIX)/bin/purgeLocalCache ||true @# Common @$(DIFF) $(SRCCOMMONDIR)/lib/Lemonldap/NG/Common /usr/local/share/perl/$(PERLVERSION)/Lemonldap/NG/Common ||true @$(DIFF) $(SRCCOMMONDIR)/lib/Lemonldap/NG/Common.pm /usr/local/share/perl/$(PERLVERSION)/Lemonldap/NG/Common.pm ||true @$(DIFF) $(SRCCOMMONDIR)/scripts/lmMigrateConfFiles2ini $(LMPREFIX)/bin/lmMigrateConfFiles2ini ||true @$(DIFF) $(SRCCOMMONDIR)/scripts/convertConfig $(LMPREFIX)/bin/convertConfig ||true @$(DIFF) $(SRCCOMMONDIR)/scripts/rotateOidcKeys $(LMPREFIX)/bin/rotateOidcKeys ||true @# Manager @$(DIFF) $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager /usr/local/share/perl/$(PERLVERSION)/Lemonldap/NG/Manager ||true @$(DIFF) $(SRCMANAGERDIR)/lib/Lemonldap/NG/Manager.pm /usr/local/share/perl/$(PERLVERSION)/Lemonldap/NG/Manager.pm ||true @$(DIFF) $(SRCMANAGERDIR)/site/static $(LMPREFIX)/htdocs/manager/static ||true @$(DIFF) $(SRCMANAGERDIR)/site/templates $(LMPREFIX)/htdocs/manager/templates ||true @$(DIFF) --ignore-matching-lines='set.*get.*\[2\]' $(SRCMANAGERDIR)/scripts/lmConfigEditor $(LMPREFIX)/bin/lmConfigEditor ||true @$(DIFF) --ignore-matching-lines='set.*get.*' $(SRCCOMMONDIR)/scripts/lemonldap-ng-cli $(LMPREFIX)/bin/lemonldap-ng-cli ||true test-diff: @for file in `find lemonldap-ng-*/lib -type f`; do \ $(DIFF) $$file `echo $$file|sed -e s/lib/blib\\\/lib/`; \ done tidy: clean find lemon*/ -type f \( -name '*.pm' -or -name '*.pl' -or -name '*.t' \) -print -exec perltidy -b {} \; find lemon*/ -name '*.bak' -delete $(MAKE) json tidy-js: clean @find $(SRCMANAGERDIR)/site/static/js/ e2e-tests/ $(SRCPORTALDIR)/example \ -type f \ -name '*.js' \ ! -name 'jq*' \ ! -name 'bootstrap*' \ ! -name '*.min.js' \ ! -name conftree.js \ -print \ -exec js_beautify -o -s=2 {} \; # Translation targets # ------------------- language_code = perl -e ' \ print { \ fr=> "FR-FR", \ }->{$(1)}' test_omegat_%_dir: @if [ ! -d omegat.files/$* ]; then \ echo "omegat.files/$* does not exist"; \ exit 1; \ fi omegat-configuration-file: perl -pe 'BEGIN{$$p=`pwd`;chomp $$p;}s/__LANG__/$(OMEGATCODE)/;s/__PWD__/$$p/o;' omegat.files/_base.project >omegat.files/$(LANGCODE)/omegat.project translation: omegat-configuration-file omegat omegat.files/$(LANGCODE) --no-team --quiet translated-doc: omegat-configuration-file omegat omegat.files/$(LANGCODE) --mode=console-translate --quiet 2>/dev/null # 2. Public targets %-translation: test_omegat_%_dir omegat-%-clean $(MAKE) translation LANGCODE=$* OMEGATCODE=`$(call language_code,$*)` %-doc: test_omegat_%_dir omegat-%-clean mkdir -p po-doc/$* $(MAKE) translated-doc LANGCODE=$* OMEGATCODE=`$(call language_code,$*)` omegat-clean: rm -rf omegat.files/*/omegat.project omegat.files/*/omegat/project_save*.bak omegat-%-clean: rm -rf po-doc/$* install_%_doc_site: @rm -rf $(RDOCDIR)/$*-doc || true # Install doc directories @install -v -d -m 755 $(RDOCDIR)/$*-doc @cd po-doc/$* && find * -type d |(cd $(RDOCDIR)/$*-doc; xargs install -v -d -m 755) && cd - # Install HTML files @cd po-doc/$* && for f in `find * -type f -name '*.html'`; do \ echo "Installing $$f"; \ ../../scripts/transform-templates \ usedebianlibs $(USEDEBIANLIBS) \ useexternallibs $(USEEXTERNALLIBS) \ jsminified $(JSCOMPRESS) \ cssminified $(CSSCOMPRESS) <$$f \ > $(RDOCDIR)/$*-doc/$$f; \ done && cd - # Install other files @cd po-doc/$* && for f in `find * -type f ! -name '*.html'`; do \ install -v -m 644 $$f $(RDOCDIR)/$*-doc/$$f; \ done && cd - # Install symlinks (no symlinks) @#cd po-doc/$* && tar cf - `find * -type l` | tar xvf - -C $(RDOCDIR)/$*-doc && cd - # Remove js @cd $(RDOCDIR)/$*-doc && if test "$(USEEXTERNALLIBS)" = "yes"; then \ rm -rvf $(DOCEXTERNALLIBS); \ elif test "$(USEDEBIANLIBS)" = "yes"; then \ rm -rvf $(DOCLIBSTOREMOVEFORDEBIAN); \ fi && cd - html_spelling: @for i in $$(find doc/ -type f -name '*.html'); do \ text=$$(html2text $$i|spellintian --picky); \ if [ "$$text" != "" ]; then echo "### $$i ###"; echo $$text; fi \ done spelling: @for i in $$(find * -type f -name '*.pm'); do \ if grep '=head1' $$i >/dev/null; then \ text=$$(pod2text $$i|spellintian --picky); \ if [ "$$text" != "" ]; then echo "### $$i ###"; echo $$text; fi \ fi \ done lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/README000066400000000000000000000015251325274564300230470ustar00rootroot00000000000000LemonLDAP::NG ==================== LemonLDAP::NG is a modular Web-SSO based on Apache::Session modules. You can find documentation here: * for administrators: http://lemonldap-ng.org/ * for developers: see embedded perldoc LemonLDAP::NG is a 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 2, 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 L. Copyright: see COPYING lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/RELEASE000066400000000000000000000056321325274564300231750ustar00rootroot00000000000000How to build a release for LemonLDAP::NG ======================================== The version ----------- - The release version should be updated in the following location: * changelog (add a changelog from JIRA for the target version) * Main modules (Common.pm/Handler.pm/Portal.pm/Manager.pm) * Makefile.PL for cross-dependencies * Doxyfile - Then update packages information with: $ make clean && make cpan - Version must also be updated in RPM and Debian build files - rpm/lemonldap-ng.spec: update versions and add changelog entry - debian/changelog: add changelog entry Before release -------------- - Update documentation: $ make documentation - Translate documentation $ make fr-doc - Update debian/changelog launch just `dch -r` and force save (":w" and ot ":x") For minor release ----------------- - Go on gitlab and create a new tag: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/tags/new For major release ----------------- - Go on gitlab and create a new branch: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/branches/new - Go on gitlab and create a new tag: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/tags/new - Change "latest" symlink in dokuwiki - Edit scripts/doc.pl in trunk to point on the new documentation path Make the distribution --------------------- - CPAN packages: $ make clean && make cpan - Tarball: $ make clean && make dist - RedHat packaging: Create the RPM specific tarball: $ make clean && make rpm-dist Next steps: see rpm/README - Debian packaging: Create the debian specific tarball: $ make clean && make debian-dist Untar the debian archive and launch: $ make debian-packages Packages are in /tmp Sign packages: $ dpkg-sig -p --sign builder /tmp/*.deb Upload the distribution --------------------- - CPAN: Upload modules tarballs (generated by make cpan) - OW2 Forge: Upload dist and bundles - RPM: see rpm/REDAME - DEB: The DEB repository is hosted on https://lemonldap-ng.org/deb Copy all generated files (*.deb, *.dsc, *.changes, ...): $ scp *.deb *.dsc *.changes *.debian.tar.gz *.orig.tar.gz lemonldapng@lemonldap-ng.org:incoming/ Then connect on the server and launch reprepro: $ ssh lemonldapng@lemonldap-ng.org lemonldapng@lemonldap-ng.org$ cd deb/ lemonldapng@lemonldap-ng.org$ reprepro --ask-passphrase -Vb . include squeeze ../incoming/lemonldap-ng_VERSION_i386.changes or lemonldapng@sd-22107:~/deb$ reprepro --ask-passphrase -Vb . includedeb squeeze ../incoming/*VERSION*deb See also reprepro configuration file: 'distributions' Site ---- - Update links on the download page - Close the mileston on Gitlab and create a new one Spread the word --------------- - News on OW2 projects page - Twitter account - IRC and Mattemost channel subject - User and Announces mailing lists - Optional: blogs and news sites (LinuxFR, etc.) After release ------------- - Update debian/changelog $ dch -v -1 (and write "New release") - Update $VERSION anywhere lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/000077500000000000000000000000001325274564300237565ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/conf/000077500000000000000000000000001325274564300247035ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/conf/lmConf-1.js000066400000000000000000000125231325274564300266200ustar00rootroot00000000000000{ "applicationList" : { "1sample" : { "catname" : "Sample applications", "test1" : { "options" : { "description" : "A simple application displaying authenticated user", "display" : "auto", "logo" : "demo.png", "name" : "Application Test 1", "uri" : "http://test1.__DNSDOMAIN__/" }, "type" : "application" }, "test2" : { "options" : { "description" : "The same simple application displaying authenticated user", "display" : "auto", "logo" : "thumbnail.png", "name" : "Application Test 2", "uri" : "http://test2.__DNSDOMAIN__/" }, "type" : "application" }, "type" : "category" }, "2administration" : { "catname" : "Administration", "manager" : { "options" : { "description" : "Configure LemonLDAP::NG WebSSO", "display" : "auto", "logo" : "configure.png", "name" : "WebSSO Manager", "uri" : "http://manager.__DNSDOMAIN__/manager.html" }, "type" : "application" }, "notifications" : { "options" : { "description" : "Explore WebSSO notifications", "display" : "auto", "logo" : "database.png", "name" : "Notifications explorer", "uri" : "http://manager.__DNSDOMAIN__/notifications.html" }, "type" : "application" }, "sessions" : { "options" : { "description" : "Explore WebSSO sessions", "display" : "auto", "logo" : "database.png", "name" : "Sessions explorer", "uri" : "http://manager.__DNSDOMAIN__/sessions.html" }, "type" : "application" }, "type" : "category" }, "3documentation" : { "catname" : "Documentation", "localdoc" : { "options" : { "description" : "Documentation supplied with LemonLDAP::NG", "display" : "on", "logo" : "help.png", "name" : "Local documentation", "uri" : "http://manager.__DNSDOMAIN__/doc/" }, "type" : "application" }, "officialwebsite" : { "options" : { "description" : "Official LemonLDAP::NG Website", "display" : "on", "logo" : "network.png", "name" : "Offical Website", "uri" : "http://lemonldap-ng.org/" }, "type" : "application" }, "type" : "category" } }, "authentication" : "Demo", "cfgAuthor" : "The LemonLDAP::NG team", "cfgNum" : 1, "cookieName" : "lemonldap", "demoExportedVars" : { "cn" : "cn", "mail" : "mail", "uid" : "uid" }, "domain" : "__DNSDOMAIN__", "exportedHeaders" : { "test1.__DNSDOMAIN__" : { "Auth-User" : "$uid" }, "test2.__DNSDOMAIN__" : { "Auth-User" : "$uid" } }, "exportedVars" : { "UA" : "HTTP_USER_AGENT" }, "globalStorage" : "Apache::Session::File", "globalStorageOptions" : { "Directory" : "__SESSIONDIR__", "LockDirectory" : "__SESSIONDIR__/lock", "generateModule" : "Lemonldap::NG::Common::Apache::Session::Generate::SHA256" }, "groups" : {}, "localSessionStorage" : "Cache::FileCache", "localSessionStorageOptions" : { "cache_depth" : 3, "cache_root" : "/tmp", "default_expires_in" : 600, "directory_umask" : "007", "namespace" : "lemonldap-ng-sessions" }, "locationRules" : { "manager.__DNSDOMAIN__" : { "(?#Configuration)^/(manager\\.html|conf/)" : "$uid eq \"dwho\"", "(?#Notifications)/notifications" : "$uid eq \"dwho\" or $uid eq \"rtyler\"", "(?#Sessions)/sessions" : "$uid eq \"dwho\" or $uid eq \"rtyler\"", "default" : "$uid eq \"dwho\"" }, "test1.__DNSDOMAIN__" : { "^/logout" : "logout_sso", "default" : "accept" }, "test2.__DNSDOMAIN__" : { "^/logout" : "logout_sso", "default" : "accept" } }, "loginHistoryEnabled" : 1, "macros" : { "_whatToTrace" : "$_auth eq 'SAML' ? \"$_user\\@$_idpConfKey\" : \"$_user\"" }, "mailUrl" : "http://auth.__DNSDOMAIN__/mail.pl", "notification" : 1, "notificationStorage" : "File", "notificationStorageOptions" : { "dirName" : "__NOTIFICATIONDIR__" }, "passwordDB" : "Demo", "persistentStorage" : "Apache::Session::File", "persistentStorageOptions" : { "Directory" : "__PSESSIONDIR__", "LockDirectory" : "__PSESSIONDIR__/lock" }, "portal" : "http://auth.__DNSDOMAIN__/", "portalSkin" : "bootstrap", "portalSkinBackground" : "1280px-Cedar_Breaks_National_Monument_partially.jpg", "registerDB" : "Demo", "registerUrl" : "http://auth.__DNSDOMAIN__/register.pl", "reloadUrls" : { "reload.__DNSDOMAIN__" : "http://reload.__DNSDOMAIN__/reload" }, "securedCookie" : 0, "sessionDataToRemember" : {}, "timeout" : 72000, "userDB" : "Demo", "whatToTrace" : "_whatToTrace" } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/000077500000000000000000000000001325274564300245315ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/experimental/000077500000000000000000000000001325274564300272265ustar00rootroot00000000000000handler-nginx.conf000066400000000000000000000024121325274564300325530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/experimental#==================================================================== # Nginx configuration for LemonLDAP::NG Handler #==================================================================== # Load LemonLDAP::NG Handler perl_require Lemonldap/NG/Handler.pm; perl_set $lmstatus Lemonldap::NG::Handler::handler; # Log format similar to "combined", but with remote_user found by LL::NG log_format lm_combined '$remote_addr - $lmremote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; # Common error page and security parameters error_page 500 http://auth.__DNSDOMAIN__/?lmError=500; error_page 503 http://auth.__DNSDOMAIN__/?lmError=503; server { listen __VHOSTLISTEN__; server_name reload.__DNSDOMAIN__; # Configuration reload mechanism (only 1 per physical server is # needed): choose your URL to avoid reloading Nginx when # configuration change location /reload { allow 127.0.0.0/8; allow ::1; deny all; perl Lemonldap::NG::Handler::reload; } # Uncomment this to activate status module #location /status { # allow 127.0.0.0/8; # allow ::1; # deny all; # perl Lemonldap::NG::Handler::status; #} } nginx-access-control000066400000000000000000000017601325274564300331360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/experimental# Following directives must be included in server blocks # or location blocks in order to run LL::NG access control # Vars used by LL::NG handler set $lmremote_user ""; set $lmlocation ""; # Apply status code computed by LL::NG handler if ($lmstatus = 302) { return 302 $lmlocation; } if ($lmstatus = 401) { return 401; } if ($lmstatus = 403) { return 403; } if ($lmstatus = 500) { return 500; } if ($lmstatus = 503) { return 503; } # Security: prevent clients from sending custom headers # that could interfere with headers added by LL::NG handler # For example, if LL::NG handler adds a request header "Auth-User", # request header "Auth-User" sent by a malicious client would be # overwritten by LL::NG handler, but "Auth_User" and "auth-user" would not, # and Nginx does not permit case-sensitive header comparison. # If $lmparanoid is set to 1, any suspicious request header would result in a 403 error; # if set to 0, a warning will be sent in error logs; # default value is 0. #set $lmparanoid 1; nginx-fcgi-accounting000066400000000000000000000006531325274564300332570ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/experimental# Directives to send user data to FastCGI protected apps # and hide LL::NG cookie set $cookie ""; fastcgi_param COOKIE $cookie; # Add here HTTP headers defined in LL::NG manager # For example, if you set an exported header 'Auth-User', set $lm_auth_user ""; fastcgi_param AUTH_USER $lm_auth_user; # That is: variable containing header value is obtained from lowercase # header name, - replaced with _, and preceded from $lm_ nginx-http-accounting000066400000000000000000000006561325274564300333310ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/experimental# Directives to send user data to HTTP protected apps # and hide LL::NG cookie set $cookie ""; proxy_set_header Cookie $cookie; # Add here HTTP headers defined in LL::NG manager # For example, if you set an exported header 'Auth-User', set $lm_auth_user ""; proxy_set_header Auth-User $lm_auth_user; # That is: variable containing header value is obtained from lowercase # header name, - replaced with _, and preceded from $lm_ test-nginx.conf000066400000000000000000000024131325274564300321160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/experimental#==================================================================== # Nginx configuration for LemonLDAP::NG sample applications #==================================================================== # Sample reverse-proxy virtualhost server { listen __VHOSTLISTEN__; server_name test1.__DNSDOMAIN__; location / { # Prepare user data transmission to protected HTTP app include lemonldap-ng/http-accounting; # Trigger Lemonldap::NG access control include lemonldap-ng/access-control; # Transfer request to backend proxy_pass http://target.__DNSDOMAIN__/; } #access_log /var/log/nginx/access.log lm_combined; } # Sample FastCGI application server { listen __VHOSTLISTEN__; server_name test2.__DNSDOMAIN__; location / { # Prepare user data transmission to protected FastCGI app include lemonldap-ng/fcgi-accounting; # Trigger Lemonldap::NG access control include lemonldap-ng/access-control; # Transfer request to backend - assume fcgiwrap is installed root __TESTDIR__; try_files $uri $uri/index.pl; include fastcgi_params; fastcgi_pass unix:/var/run/fcgiwrap.socket; } #access_log /var/log/nginx/access.log lm_combined; } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/for_etc_hosts000066400000000000000000000001611325274564300273130ustar00rootroot00000000000000127.0.0.1 auth.__DNSDOMAIN__ manager.__DNSDOMAIN__ test1.__DNSDOMAIN__ test2.__DNSDOMAIN__ reload.__DNSDOMAIN__ lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/handler-apache.conf000066400000000000000000000041241325274564300302350ustar00rootroot00000000000000#======================================================================== # Apache configuration for LemonLDAP::NG Handler #======================================================================== # This file implements the reload virtualhost that permits to reload # configuration without restarting server, and some common instructions. # You need then to declare this vhost in reloadUrls (in the manager # interface if this server doesn't host the manager itself): # # KEY : VALUE # host-or-IP:port : http://reload.example.com/reload # # IMPORTANT: # To protect applications, see test-apache.conf template in example files # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Load LemonLDAP::NG Handler PerlRequire Lemonldap/NG/Handler.pm # Common error page and security parameters ErrorDocument 403 http://auth.__DNSDOMAIN__/?lmError=403 ErrorDocument 404 http://auth.__DNSDOMAIN__/?lmError=404 ErrorDocument 500 http://auth.__DNSDOMAIN__/?lmError=500 ErrorDocument 502 http://auth.__DNSDOMAIN__/?lmError=502 ErrorDocument 503 http://auth.__DNSDOMAIN__/?lmError=503 ServerName reload.__DNSDOMAIN__ # Configuration reload mechanism (only 1 per physical server is # needed): choose your URL to avoid restarting Apache when # configuration change Order deny,allow Deny from all Allow from 127.0.0.0/8 ::1 SetHandler perl-script PerlResponseHandler Lemonldap::NG::Handler->reload # Uncomment this to activate status module # # Order deny,allow # Deny from all # Allow from 127.0.0.0/8 ::1 # SetHandler perl-script # PerlHandler Lemonldap::NG::Handler->status # # You may have to uncomment the next directive to skip # # an upper PerlHeaderParserHandler directive # #PerlHeaderParserHandler Apache::Constants::DECLINED # # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/handler-apache2.4.conf000066400000000000000000000040031325274564300304550ustar00rootroot00000000000000#======================================================================== # Apache configuration for LemonLDAP::NG Handler #======================================================================== # This file implements the reload virtualhost that permits to reload # configuration without restarting server, and some common instructions. # You need then to declare this vhost in reloadUrls (in the manager # interface if this server doesn't host the manager itself): # # KEY : VALUE # host-or-IP:port : http://reload.example.com/reload # # IMPORTANT: # To protect applications, see test-apache.conf template in example files # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Load LemonLDAP::NG Handler PerlOptions +GlobalRequest PerlModule Lemonldap::NG::Handler # Common error page and security parameters ErrorDocument 403 http://auth.__DNSDOMAIN__/?lmError=403 ErrorDocument 404 http://auth.__DNSDOMAIN__/?lmError=404 ErrorDocument 500 http://auth.__DNSDOMAIN__/?lmError=500 ErrorDocument 502 http://auth.__DNSDOMAIN__/?lmError=502 ErrorDocument 503 http://auth.__DNSDOMAIN__/?lmError=503 ServerName reload.__DNSDOMAIN__ # Configuration reload mechanism (only 1 per physical server is # needed): choose your URL to avoid restarting Apache when # configuration change Require ip 127 ::1 SetHandler perl-script PerlResponseHandler Lemonldap::NG::Handler->reload # Uncomment this to activate status module # # Require ip 127 ::1 # SetHandler perl-script # PerlResponseHandler Lemonldap::NG::Handler->status # # You may have to uncomment the next directive to skip # # an upper PerlHeaderParserHandler directive # #PerlHeaderParserHandler Apache2::Const::DECLINED # # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/handler-apache2.X.conf000066400000000000000000000046161325274564300305330ustar00rootroot00000000000000#======================================================================== # Apache configuration for LemonLDAP::NG Handler #======================================================================== # This file implements the reload virtualhost that permits to reload # configuration without restarting server, and some common instructions. # You need then to declare this vhost in reloadUrls (in the manager # interface if this server doesn't host the manager itself): # # KEY : VALUE # host-or-IP:port : http://reload.example.com/reload # # IMPORTANT: # To protect applications, see test-apache.conf template in example files # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Load LemonLDAP::NG Handler PerlOptions +GlobalRequest PerlModule Lemonldap::NG::Handler # Common error page and security parameters ErrorDocument 403 http://auth.__DNSDOMAIN__/?lmError=403 ErrorDocument 404 http://auth.__DNSDOMAIN__/?lmError=404 ErrorDocument 500 http://auth.__DNSDOMAIN__/?lmError=500 ErrorDocument 502 http://auth.__DNSDOMAIN__/?lmError=502 ErrorDocument 503 http://auth.__DNSDOMAIN__/?lmError=503 ServerName reload.__DNSDOMAIN__ # Configuration reload mechanism (only 1 per physical server is # needed): choose your URL to avoid restarting Apache when # configuration change = 2.3> Require ip 127 ::1 Order Deny,Allow Deny from all Allow from 127.0.0.0/8 ::1 SetHandler perl-script PerlResponseHandler Lemonldap::NG::Handler->reload # Uncomment this to activate status module # # = 2.3> # Require ip 127 ::1 # # # Order Deny,Allow # Deny from all # Allow from 127.0.0.0/8 ::1 # # SetHandler perl-script # PerlResponseHandler Lemonldap::NG::Handler->status # # You may have to uncomment the next directive to skip # # an upper PerlHeaderParserHandler directive # #PerlHeaderParserHandler Apache2::Const::DECLINED # # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/handler-apache2.conf000066400000000000000000000041631325274564300303220ustar00rootroot00000000000000#======================================================================== # Apache configuration for LemonLDAP::NG Handler #======================================================================== # This file implements the reload virtualhost that permits to reload # configuration without restarting server, and some common instructions. # You need then to declare this vhost in reloadUrls (in the manager # interface if this server doesn't host the manager itself): # # KEY : VALUE # host-or-IP:port : http://reload.example.com/reload # # IMPORTANT: # To protect applications, see test-apache.conf template in example files # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Load LemonLDAP::NG Handler PerlOptions +GlobalRequest PerlModule Lemonldap::NG::Handler # Common error page and security parameters ErrorDocument 403 http://auth.__DNSDOMAIN__/?lmError=403 ErrorDocument 404 http://auth.__DNSDOMAIN__/?lmError=404 ErrorDocument 500 http://auth.__DNSDOMAIN__/?lmError=500 ErrorDocument 502 http://auth.__DNSDOMAIN__/?lmError=502 ErrorDocument 503 http://auth.__DNSDOMAIN__/?lmError=503 ServerName reload.__DNSDOMAIN__ # Configuration reload mechanism (only 1 per physical server is # needed): choose your URL to avoid restarting Apache when # configuration change Order deny,allow Deny from all Allow from 127.0.0.0/8 ::1 SetHandler perl-script PerlResponseHandler Lemonldap::NG::Handler->reload # Uncomment this to activate status module # # Order deny,allow # Deny from all # Allow from 127.0.0.0/8 ::1 # SetHandler perl-script # PerlResponseHandler Lemonldap::NG::Handler->status # # You may have to uncomment the next directive to skip # # an upper PerlHeaderParserHandler directive # #PerlHeaderParserHandler Apache2::Const::DECLINED # # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/handler-nginx.conf000066400000000000000000000025741325274564300301460ustar00rootroot00000000000000#======================================================================= # Nginx configuration for LemonLDAP::NG Handler #======================================================================= # This file implements the reload virtualhost that permits to reload # configuration without restarting server. # You need then to declare this vhost in reloadUrls (in the manager # interface if this server doesn't host the manager itself): # # KEY : VALUE # host-or-IP:port : http://reload.example.com/reload # # IMPORTANT: # To protect applications, see test-nginx.conf template in example files # Log format include __CONFDIR__/nginx-lmlog.conf; #access_log /var/log/nginx/access.log lm_combined; server { listen __PORT__; server_name reload.__DNSDOMAIN__; root /var/www/html; location = /reload { allow 127.0.0.1; deny all; include /etc/nginx/fastcgi_params; fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; fastcgi_param LLTYPE reload; } # Client requests location / { deny all; # Uncomment this if you use https only #add_header Strict-Transport-Security "15768000"; } # Uncomment this if status is enabled #location = /status { # allow 127.0.0.1; # deny all; # include /etc/nginx/fastcgi_params; # fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; # fastcgi_param LLTYPE status; #} } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/manager-apache2.4.conf000066400000000000000000000065021325274564300304600ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Manager #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Manager virtual host (manager.__DNSDOMAIN__) ServerName manager.__DNSDOMAIN__ LogLevel notice #ErrorLog ${APACHE_LOG_DIR}/lm_err.log #CustomLog ${APACHE_LOG_DIR}/lm.log combined # FASTCGI CONFIGURATION # --------------------- # 1) URI management RewriteEngine on RewriteRule "^/$" "/psgi/manager-server.fcgi" [PT] # For performances, you can delete the previous RewriteRule line after # puttings html files: simply put the HTML results of differents modules # (configuration, sessions, notifications) as manager.html, sessions.html, # notifications.html and uncomment the 2 following lines: # DirectoryIndex manager.html # RewriteCond "%{REQUEST_FILENAME}" "!\.html$" # REST URLs RewriteCond "%{REQUEST_FILENAME}" "!^/(?:static|doc|fr-doc|lib|javascript|favicon).*" RewriteRule "^/(.+)$" "/psgi/manager-server.fcgi/$1" [PT] Alias /psgi/ __MANAGERPSGIDIR__/ # 2) FastCGI engine # You can choose any FastCGI system. Here is an example using mod_fcgid # mod_fcgid configuration FcgidMaxRequestLen 2000000 SetHandler fcgid-script Options +ExecCGI # If you want to use mod_fastcgi, replace lines below by: #FastCgiServer __MANAGERPSGIDIR__manager-server.fcgi # Or if you prefer to use CGI, use /psgi/manager-server.cgi instead of # /psgi/manager-server.fcgi and adapt the rewrite rules. # GLOBAL CONFIGURATION # -------------------- DocumentRoot __MANAGERDIR__ Require all granted AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary # Static files (javascripts, HTML forms,...) Alias /static/ __MANAGERSTATICDIR__/ Require all granted Options +FollowSymLinks # On-line documentation Alias /doc/ __DEFDOCDIR__ Alias /lib/ __DEFDOCDIR__pages/documentation/current/lib/ Require all granted ErrorDocument 404 /notfound.html Options +FollowSymLinks DirectoryIndex index.html start.html # French version (needs fr-doc installation) Alias /fr-doc/ __FRDOCDIR__ Require all granted ErrorDocument 404 /notfoundfr.html Options +FollowSymLinks DirectoryIndex index.html start.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/manager-apache2.X.conf000066400000000000000000000077161325274564300305340ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Manager #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Manager virtual host (manager.__DNSDOMAIN__) ServerName manager.__DNSDOMAIN__ LogLevel notice #ErrorLog ${APACHE_LOG_DIR}/lm_err.log #CustomLog ${APACHE_LOG_DIR}/lm.log combined # FASTCGI CONFIGURATION # --------------------- # 1) URI management RewriteEngine on RewriteRule "^/$" "/psgi/manager-server.fcgi" [PT] # For performances, you can delete the previous RewriteRule line after # puttings html files: simply put the HTML results of differents modules # (configuration, sessions, notifications) as manager.html, sessions.html, # notifications.html and uncomment the 2 following lines: # DirectoryIndex manager.html # RewriteCond "%{REQUEST_FILENAME}" "!\.html$" # REST URLs RewriteCond "%{REQUEST_FILENAME}" "!^/(?:static|doc|fr-doc|lib|javascript|favicon).*" RewriteRule "^/(.+)$" "/psgi/manager-server.fcgi/$1" [PT] Alias /psgi/ __MANAGERPSGIDIR__/ # 2) FastCGI engine # You can choose any FastCGI system. Here is an example using mod_fcgid # mod_fcgid configuration FcgidMaxRequestLen 2000000 SetHandler fcgid-script Options +ExecCGI # If you want to use mod_fastcgi, replace lines below by: #FastCgiServer __MANAGERPSGIDIR__manager-server.fcgi # Or if you prefer to use CGI, use /psgi/manager-server.cgi instead of # /psgi/manager-server.fcgi and adapt the rewrite rules. # GLOBAL CONFIGURATION # -------------------- DocumentRoot __MANAGERDIR__ = 2.3> Require all granted Order Deny,Allow Allow from all Options +FollowSymLinks AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary # Static files (javascripts, HTML forms,...) Alias /static/ __MANAGERSTATICDIR__/ = 2.3> Require all granted Order Deny,Allow Allow from all Options +FollowSymLinks # On-line documentation Alias /doc/ __DEFDOCDIR__ Alias /lib/ __DEFDOCDIR__pages/documentation/current/lib/ = 2.3> Require all granted Order Deny,Allow Allow from all ErrorDocument 404 /notfound.html Options +FollowSymLinks DirectoryIndex index.html start.html # French version (needs fr-doc installation) Alias /fr-doc/ __FRDOCDIR__ = 2.3> Require all granted Order Deny,Allow Allow from all ErrorDocument 404 /notfoundfr.html Options +FollowSymLinks DirectoryIndex index.html start.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/manager-apache2.conf000066400000000000000000000066211325274564300303200ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Manager #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Manager virtual host (manager.__DNSDOMAIN__) ServerName manager.__DNSDOMAIN__ LogLevel notice #ErrorLog ${APACHE_LOG_DIR}/lm_err.log #CustomLog ${APACHE_LOG_DIR}/lm.log combined # FASTCGI CONFIGURATION # --------------------- # 1) URI management RewriteEngine on RewriteRule "^/$" "/psgi/manager-server.fcgi" [PT] # For performances, you can delete the previous RewriteRule line after # puttings html files: simply put the HTML results of differents modules # (configuration, sessions, notifications) as manager.html, sessions.html, # notifications.html and uncomment the 2 following lines: # DirectoryIndex manager.html # RewriteCond "%{REQUEST_FILENAME}" "!\.html$" # REST URLs RewriteCond "%{REQUEST_FILENAME}" "!^/(?:static|doc|fr-doc|lib|javascript|favicon).*" RewriteRule "^/(.+)$" "/psgi/manager-server.fcgi/$1" [PT] Alias /psgi/ __MANAGERPSGIDIR__/ # 2) FastCGI engine # You can choose any FastCGI system. Here is an example using mod_fcgid # mod_fcgid configuration FcgidMaxRequestLen 2000000 SetHandler fcgid-script Options +ExecCGI # If you want to use mod_fastcgi, replace lines below by: #FastCgiServer __MANAGERPSGIDIR__manager-server.fcgi # Or if you prefer to use CGI, use /psgi/manager-server.cgi instead of # /psgi/manager-server.fcgi and adapt the rewrite rules. # GLOBAL CONFIGURATION # -------------------- DocumentRoot __MANAGERDIR__ Order Deny,Allow Allow from all AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary # Static files (javascripts, HTML forms,...) Alias /static/ __MANAGERSTATICDIR__/ Order deny,allow Allow from all Options +FollowSymLinks # On-line documentation Alias /doc/ __DEFDOCDIR__ Alias /lib/ __DEFDOCDIR__pages/documentation/current/lib/ Order allow,deny Allow from all ErrorDocument 404 /notfound.html Options +FollowSymLinks DirectoryIndex index.html start.html # French version (needs fr-doc installation) Alias /fr-doc/ __FRDOCDIR__ Order deny,allow Allow from all ErrorDocument 404 /notfoundfr.html Options +FollowSymLinks DirectoryIndex index.html start.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/manager-nginx.conf000066400000000000000000000023011325274564300301270ustar00rootroot00000000000000server { listen __PORT__; server_name manager.__DNSDOMAIN__; root __MANAGERDIR__; if ($uri !~ ^/(manager\.psgi|static|doc|fr-doc|lib|javascript|favicon)) { rewrite ^/(.*)$ /manager.psgi/$1 break; } location /manager.psgi { include /etc/nginx/fastcgi_params; fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; fastcgi_param LLTYPE manager; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_split_path_info ^(.*\.psgi)(/.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; # Uncomment this if you use https only #add_header Strict-Transport-Security "15768000"; } location / { index manager.psgi; allow 127.0.0.0/8; deny all; try_files $uri $uri/ =404; } location /doc/ { alias __DEFDOCDIR__; index index.html start.html; } location /lib/ { alias __DEFDOCDIR__pages/documentation/current/lib/; } location /fr-doc/ { alias __FRDOCDIR__; index index.html start.html; } location /static/ { alias __MANAGERSTATICDIR__; } # DEBIAN # If install was made with USEDEBIANLIBS (official releases), uncomment this #location /javascript/ { # alias /usr/share/javascript/; #} } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/nginx-lmlog.conf000066400000000000000000000002251325274564300276320ustar00rootroot00000000000000log_format lm_combined '$remote_addr - $lmremote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/nginx-lua-headers.conf000066400000000000000000000031331325274564300307130ustar00rootroot00000000000000 auth_request_set $headername1 $upstream_http_headername1; auth_request_set $headervalue1 $upstream_http_headervalue1; auth_request_set $headername2 $upstream_http_headername2; auth_request_set $headervalue2 $upstream_http_headervalue2; auth_request_set $headername3 $upstream_http_headername3; auth_request_set $headervalue3 $upstream_http_headervalue3; auth_request_set $headername4 $upstream_http_headername4; auth_request_set $headervalue4 $upstream_http_headervalue4; auth_request_set $headername5 $upstream_http_headername5; auth_request_set $headervalue5 $upstream_http_headervalue5; auth_request_set $headername6 $upstream_http_headername6; auth_request_set $headervalue6 $upstream_http_headervalue6; auth_request_set $headername7 $upstream_http_headername7; auth_request_set $headervalue7 $upstream_http_headervalue7; auth_request_set $headername8 $upstream_http_headername8; auth_request_set $headervalue8 $upstream_http_headervalue8; auth_request_set $headername9 $upstream_http_headername9; auth_request_set $headervalue9 $upstream_http_headervalue9; auth_request_set $headername10 $upstream_http_headername10; auth_request_set $headervalue10 $upstream_http_headervalue10; auth_request_set $lmcookie $upstream_http_cookie; access_by_lua ' i = 1 ngx.req.set_header("Cookie",ngx.var.lmcookie) while true do if ngx.var["headername"..i] ~= nil then ngx.req.set_header(ngx.var["headername"..i],ngx.var["headervalue"..i]) else break end i = i +1 end '; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/portal-apache.conf000066400000000000000000000074521325274564300301300ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Portal #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Portal Virtual Host (auth.__DNSDOMAIN__) ServerName auth.__DNSDOMAIN__ # DocumentRoot DocumentRoot __PORTALDIR__ Order allow,deny Allow from all Options +ExecCGI +FollowSymLinks # Perl script SetHandler perl-script PerlHandler Apache::Registry # Directory index DirectoryIndex index.pl index.html # SOAP functions for sessions management (disabled by default) Order deny,allow Deny from all # SOAP functions for sessions access (disabled by default) Order deny,allow Deny from all # SOAP functions for configuration access (disabled by default) Order deny,allow Deny from all # SOAP functions for notification insertion (disabled by default) Order deny,allow Deny from all # SAML2 Issuer RewriteEngine On RewriteRule ^/saml/metadata /metadata.pl RewriteRule ^/saml/.* /index.pl # CAS Issuer RewriteEngine On RewriteRule ^/cas/.* /index.pl # OpenID Issuer RewriteEngine On RewriteRule ^/openidserver/.* /index.pl # OpenID Connect Issuer RewriteEngine On #RewriteCond %{HTTP:Authorization} . #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule ^/oauth2/.* /index.pl RewriteRule ^/.well-known/openid-configuration$ /openid-configuration.pl # Get Issuer RewriteEngine On RewriteRule ^/get/.* /index.pl # Public pages RewriteEngine On RewriteRule ^/public* /public.pl AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary ExpiresActive On ExpiresDefault "access plus 1 month" # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 # Best performance under ModPerl::Registry # Uncomment this to increase performance of Portal #require Lemonldap::NG::Portal::SharedConf; #Lemonldap::NG::Portal::SharedConf->compile( # qw(delete header cache read_from_client cookie redirect unescapeHTML)); # Uncomment this line if you use Lemonldap::NG menu #require Lemonldap::NG::Portal::Menu; # Uncomment this line if you use portal SOAP capabilities #require SOAP::Lite; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/portal-apache2.4.conf000066400000000000000000000074651325274564300303600ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Portal #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Portal Virtual Host (auth.__DNSDOMAIN__) ServerName auth.__DNSDOMAIN__ # DocumentRoot DocumentRoot __PORTALDIR__ Require all granted Options +ExecCGI +FollowSymLinks # Perl script SetHandler perl-script PerlResponseHandler ModPerl::Registry #CGIPassAuth on DirectoryIndex index.pl index.html # SOAP functions for sessions management (disabled by default) Require all denied # SOAP functions for sessions access (disabled by default) Require all denied # SOAP functions for configuration access (disabled by default) Require all denied # SOAP functions for notification insertion (disabled by default) Require all denied # SAML2 Issuer RewriteEngine On RewriteRule ^/saml/metadata /metadata.pl RewriteRule ^/saml/.* /index.pl # CAS Issuer RewriteEngine On RewriteRule ^/cas/.* /index.pl # OpenID Issuer RewriteEngine On RewriteRule ^/openidserver/.* /index.pl # OpenID Connect Issuer RewriteEngine On #RewriteCond %{HTTP:Authorization} . #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule ^/oauth2/.* /index.pl RewriteRule ^/.well-known/openid-configuration$ /openid-configuration.pl # Get Issuer RewriteEngine On RewriteRule ^/get/.* /index.pl # Public pages RewriteEngine On RewriteRule ^/public* /public.pl AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary ExpiresActive On ExpiresDefault "access plus 1 month" # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 ############################################## ## Best performance under ModPerl::Registry ## ############################################## # Uncomment this to increase performance of Portal: #require Lemonldap::NG::Portal::SharedConf; #Lemonldap::NG::Portal::SharedConf->compile( # qw(delete header cache read_from_client cookie redirect unescapeHTML)); # Uncomment this line if you use Lemonldap::NG menu #require Lemonldap::NG::Portal::Menu; # Uncomment this line if you use portal SOAP capabilities #require SOAP::Lite; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/portal-apache2.X.conf000066400000000000000000000110701325274564300304070ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Portal #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Portal Virtual Host (auth.__DNSDOMAIN__) ServerName auth.__DNSDOMAIN__ # DocumentRoot DocumentRoot __PORTALDIR__ = 2.3> Require all granted Order Deny,Allow Allow from all Options +ExecCGI +FollowSymLinks # Perl script SetHandler perl-script PerlResponseHandler ModPerl::Registry #CGIPassAuth on DirectoryIndex index.pl index.html # SOAP functions for sessions management (disabled by default) = 2.3> Require all denied Order Deny,Allow Deny from all # SOAP functions for sessions access (disabled by default) = 2.3> Require all denied Order Deny,Allow Deny from all # SOAP functions for configuration access (disabled by default) = 2.3> Require all denied Order Deny,Allow Deny from all # SOAP functions for notification insertion (disabled by default) = 2.3> Require all denied Order Deny,Allow Deny from all # SAML2 Issuer RewriteEngine On RewriteRule ^/saml/metadata /metadata.pl RewriteRule ^/saml/.* /index.pl # CAS Issuer RewriteEngine On RewriteRule ^/cas/.* /index.pl # OpenID Issuer RewriteEngine On RewriteRule ^/openidserver/.* /index.pl # OpenID Connect Issuer RewriteEngine On #RewriteCond %{HTTP:Authorization} . #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule ^/oauth2/.* /index.pl RewriteRule ^/.well-known/openid-configuration$ /openid-configuration.pl # Get Issuer RewriteEngine On RewriteRule ^/get/.* /index.pl # Public pages RewriteEngine On RewriteRule ^/public* /public.pl AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary ExpiresActive On ExpiresDefault "access plus 1 month" # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 ############################################## ## Best performance under ModPerl::Registry ## ############################################## # Uncomment this to increase performance of Portal: #require Lemonldap::NG::Portal::SharedConf; #Lemonldap::NG::Portal::SharedConf->compile( # qw(delete header cache read_from_client cookie redirect unescapeHTML)); # Uncomment this line if you use Lemonldap::NG menu #require Lemonldap::NG::Portal::Menu; # Uncomment this line if you use portal SOAP capabilities #require SOAP::Lite; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/portal-apache2.conf000066400000000000000000000076321325274564300302120ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG Portal #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Portal Virtual Host (auth.__DNSDOMAIN__) ServerName auth.__DNSDOMAIN__ # DocumentRoot DocumentRoot __PORTALDIR__ Order allow,deny Allow from all Options +ExecCGI +FollowSymLinks # Perl script SetHandler perl-script PerlResponseHandler ModPerl::Registry #CGIPassAuth on DirectoryIndex index.pl index.html # SOAP functions for sessions management (disabled by default) Order deny,allow Deny from all # SOAP functions for sessions access (disabled by default) Order deny,allow Deny from all # SOAP functions for configuration access (disabled by default) Order deny,allow Deny from all # SOAP functions for notification insertion (disabled by default) Order deny,allow Deny from all # SAML2 Issuer RewriteEngine On RewriteRule ^/saml/metadata /metadata.pl RewriteRule ^/saml/.* /index.pl # CAS Issuer RewriteEngine On RewriteRule ^/cas/.* /index.pl # OpenID Issuer RewriteEngine On RewriteRule ^/openidserver/.* /index.pl # OpenID Connect Issuer RewriteEngine On #RewriteCond %{HTTP:Authorization} . #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule ^/oauth2/.* /index.pl RewriteRule ^/.well-known/openid-configuration$ /openid-configuration.pl # Get Issuer RewriteEngine On RewriteRule ^/get/.* /index.pl # Public pages RewriteEngine On RewriteRule ^/public* /public.pl AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary ExpiresActive On ExpiresDefault "access plus 1 month" # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 ############################################## ## Best performance under ModPerl::Registry ## ############################################## # Uncomment this to increase performance of Portal: #require Lemonldap::NG::Portal::SharedConf; #Lemonldap::NG::Portal::SharedConf->compile( # qw(delete header cache read_from_client cookie redirect unescapeHTML)); # Uncomment this line if you use Lemonldap::NG menu #require Lemonldap::NG::Portal::Menu; # Uncomment this line if you use portal SOAP capabilities #require SOAP::Lite; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/portal-nginx.conf000066400000000000000000000037771325274564300300400ustar00rootroot00000000000000server { listen __PORT__; server_name auth.__DNSDOMAIN__; root __PORTALDIR__; location ~ \.pl(?:$|/) { include /etc/nginx/fastcgi_params; fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; fastcgi_param LLTYPE cgi; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; set $sn $request_uri; if ($sn ~ "^(.*)\?") { set $sn $1; } if ($sn ~ "^/index.pl") { set $sn "/index.pl"; } fastcgi_param SCRIPT_NAME $sn; fastcgi_split_path_info ^(.*\.pl)(/.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; # Uncomment this if you use Auth SSL: #map $ssl_client_s_dn $ssl_client_s_dn_cn { # default ""; # ~/CN=(?[^/]+) $CN; #} #fastcgi_param SSL_CLIENT_S_DN_CN $ssl_client_s_dn_cn } index index.pl; location / { try_files $uri $uri/ =404; # Uncomment this if you use https only #add_header Strict-Transport-Security "15768000"; } # SOAP functions for sessions management (disabled by default) location /index.pl/adminSessions { deny all; } # SOAP functions for sessions access (disabled by default) location /index.pl/sessions { deny all; } # SOAP functions for configuration access (disabled by default) location /index.pl/config { deny all; } # SOAP functions for notification insertion (disabled by default) location /index.pl/notification { deny all; } # SAML2 Issuer rewrite ^/saml/metadata /metadata.pl last; rewrite ^/saml/.* /index.pl last; # CAS Issuer rewrite ^/cas/.* /index.pl; # OpenID Issuer rewrite ^/openidserver/.* /index.pl last; # OpenID Connect Issuer rewrite ^/oauth2/.* /index.pl last; rewrite ^/.well-known/openid-configuration$ /openid-configuration.pl last; # Get Issuer rewrite ^/get/.* /index.pl; # Public pages rewrite ^/public.* /public.pl; # DEBIAN # If install was made with USEDEBIANLIBS (official releases), uncomment this #location /javascript/ { # alias /usr/share/javascript/; #} } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/test-apache.conf000066400000000000000000000020141325274564300275730ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG sample applications #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ # Sample application ServerName test1.__DNSDOMAIN__ ServerAlias test2.__DNSDOMAIN__ # SSO protection PerlHeaderParserHandler Lemonldap::NG::Handler # DocumentRoot DocumentRoot __TESTDIR__ Order allow,deny Allow from all Options +ExecCGI # Perl script (application test is written in Perl) SetHandler perl-script PerlHandler Apache::Registry # Directory index DirectoryIndex index.pl index.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/test-apache2.4.conf000066400000000000000000000022041325274564300300200ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG sample applications #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ PerlModule Lemonldap::NG::Handler::Menu # Sample application ServerName test1.__DNSDOMAIN__ ServerAlias test2.__DNSDOMAIN__ # SSO protection PerlHeaderParserHandler Lemonldap::NG::Handler # DocumentRoot DocumentRoot __TESTDIR__ Require all granted Options +ExecCGI # Perl script (application test is written in Perl) SetHandler perl-script PerlResponseHandler ModPerl::Registry # Display Menu PerlOutputFilterHandler Lemonldap::NG::Handler::Menu->run # Directory index DirectoryIndex index.pl index.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/test-apache2.X.conf000066400000000000000000000024371325274564300300740ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG sample applications #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ PerlModule Lemonldap::NG::Handler::Menu # Sample application ServerName test1.__DNSDOMAIN__ ServerAlias test2.__DNSDOMAIN__ # SSO protection PerlHeaderParserHandler Lemonldap::NG::Handler # DocumentRoot DocumentRoot __TESTDIR__ = 2.3> Require all granted Order Deny,Allow Allow from all Options +ExecCGI # Perl script (application test is written in Perl) SetHandler perl-script PerlResponseHandler ModPerl::Registry # Display Menu PerlOutputFilterHandler Lemonldap::NG::Handler::Menu->run # Directory index DirectoryIndex index.pl index.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/test-apache2.conf000066400000000000000000000022301325274564300276550ustar00rootroot00000000000000#==================================================================== # Apache configuration for LemonLDAP::NG sample applications #==================================================================== # Uncomment this if no previous NameVirtualHost declaration #NameVirtualHost __VHOSTLISTEN__ PerlModule Lemonldap::NG::Handler::Menu # Sample application ServerName test1.__DNSDOMAIN__ ServerAlias test2.__DNSDOMAIN__ # SSO protection PerlHeaderParserHandler Lemonldap::NG::Handler # DocumentRoot DocumentRoot __TESTDIR__ Order allow,deny Allow from all Options +ExecCGI # Perl script (application test is written in Perl) SetHandler perl-script PerlResponseHandler ModPerl::Registry # Display Menu PerlOutputFilterHandler Lemonldap::NG::Handler::Menu->run # Directory index DirectoryIndex index.pl index.html # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security 15768000 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/etc/test-nginx.conf000066400000000000000000000053161325274564300275050ustar00rootroot00000000000000server { listen __PORT__; server_name test1.__DNSDOMAIN__ test2.__DNSDOMAIN__; root __TESTDIR__; # Internal authentication request location = /lmauth { internal; include /etc/nginx/fastcgi_params; fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; # To use AuthBasic handler, uncomment this and remove "error_page 401" # fastcgi_param LLTYPE authbasic; # Drop post datas fastcgi_pass_request_body off; fastcgi_param CONTENT_LENGTH ""; # Keep original hostname fastcgi_param HOST $http_host; # Keep original request (LLNG server will received /llauth) fastcgi_param X_ORIGINAL_URI $request_uri; } # Client requests location / { # Local application index index.pl; try_files $uri $uri/ =404; # Reverse proxy #proxy_pass http://remote.server/; #include /etc/nginx/proxy_params; ################################## # CALLING AUTHENTICATION # ################################## auth_request /lmauth; auth_request_set $lmremote_user $upstream_http_lm_remote_user; auth_request_set $lmlocation $upstream_http_location; # Uncomment this if CDA is used #auth_request_set $cookie_value $upstream_http_set_cookie; #add_header Set-Cookie $cookie_value; # Remove this for AuthBasic handler error_page 401 $lmlocation; ################################## # PASSING HEADERS TO APPLICATION # ################################## # IF LUA IS SUPPORTED #include __CONFDIR__/nginx-lua-headers.conf; # ELSE # Set manually your headers #auth_request_set $authuser $upstream_http_auth_user; #proxy_set_header Auth-User $authuser; # OR in the correspondinc block #fastcgi_param HTTP_AUTH_USER $authuser; # Then (if LUA not supported), change cookie header to hide LLNG cookie #auth_request_set $lmcookie $upstream_http_cookie; #proxy_set_header Cookie: $lmcookie; # OR in the corresponding block #fastcgi_param HTTP_COOKIE $lmcookie; # Uncomment this if you use https only #add_header Strict-Transport-Security "15768000"; # Set REMOTE_USER (for FastCGI apps only) #fastcgi_param REMOTE_USER $lmremote_user; } # Handle test CGI location ~ \.pl$ { include /etc/nginx/fastcgi_params; fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; fastcgi_param LLTYPE cgi; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_split_path_info ^(.*\.pl)(/.+)$; fastcgi_param REMOTE_USER $lmremote_user; } #location = /status { # allow 127.0.0.1; # deny all; # include /etc/nginx/fastcgi_params; # fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; # fastcgi_param LLTYPE status; #} } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/form.html000066400000000000000000000005031325274564300256050ustar00rootroot00000000000000
lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/test/000077500000000000000000000000001325274564300247355ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/_example/test/index.pl000077500000000000000000000150011325274564300264010ustar00rootroot00000000000000#!/usr/bin/perl #================================================ # LemonLDAP::NG default test page # Display headers and environment #================================================ # Init CGI use CGI; my $cgi = CGI->new; # GET parameters my $name = $cgi->param("name") || "LemonLDAP::NG sample protected application"; my $color = $cgi->param("color") || "#ddd"; # Local parameters my $portal_url = "http://auth.__DNSDOMAIN__"; my $manager_url = "http://manager.__DNSDOMAIN__"; # CSS my $css = <{$a} = $_; } } # Display page print $cgi->header( -charset => 'utf-8' ); print "\n"; print qq{\n}; print "\n"; print qq{\n}; print "$name\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "
\n"; print "
\n"; print "
\n"; print "

$name

\n"; print "
\n"; print "
\n"; print "
\n"; print "

Main informations

\n"; print "
\n"; print "
\n"; print "
    \n"; print "
  • Authentication status: Success
  • \n"; print "
  • Connected user:
      \n"; print "
    • \$ENV{HTTP_AUTH_USER}: $ENV{HTTP_AUTH_USER}
    • \n"; print "
    • \$ENV{REMOTE_USER}: $ENV{REMOTE_USER}
    • \n"; print "
  • \n"; print "
\n"; print "
Be carefull, the \$ENV{REMOTE_USER} is set only if your script is in the same server than LemonLDAP::NG Handler (\$whatToTrace parameter). If you use it behind a reverse-proxy or in another server than Apache, REMOTE_USER is not set. See this documentation page to know how to convert an HTTP header into an environment variable.
\n"; print "
\n"; print "
\n"; print "
\n"; print "
\n"; print "

HTTP headers

\n"; print "
\n"; print "
\n"; print "

To know who is connected in your applications, you can read HTTP headers:

\n"; print "
\n"; print "\n"; print "\n"; foreach ( sort keys %$headers ) { next if $_ =~ /(Accept|Cache|User-Agent|Connection|Keep-Alive)/i; print qq{\n"; } print "
HeaderCGI environment variablePHP scriptValue
$_ $headers->{$_} \$_SERVER{$headers->{$_}} }; my @tmp; if ( $ENV{ $headers->{$_} } and @tmp = grep /\S/, split( /;/, $ENV{ $headers->{$_} } ) ) { if ($#tmp) { print '
    '; foreach (@tmp) { print "
  • $_
  • "; } print '
'; } else { print $ENV{ $headers->{$_} }; } } else { print "☒",; } print "
\n"; print "

\n"; print "
Note that LemonLDAP::NG cookie is hidden. So that application developpers can not spoof sessions.
\n"; print "
You can access to any information (IP address or LDAP attribute) by customizing exported headers with the LemonLDAP::NG Management interface.
\n"; print "
\n"; print "
\n"; print "
\n"; print "
\n"; print "

Scripts parameters

\n"; print "
\n"; print "
\n"; print "

Find here all GET or POST parameters sent to this page:

\n"; print "
\n"; print "\n"; print "\n"; foreach ( sort $cgi->param() ) { my $tmp = $cgi->param($_); print qq{\n}; } print "
ParameterValue
$_☞ $tmp
\n"; print "
\n"; print "
POST parameters can be forged by LemonLDAP::NG to autosubmit forms.
\n"; print "
\n"; print "
\n"; print "
\n"; print "
\n"; print "

Environment for Perl CGI

\n"; print "
\n"; print "
\n"; print "
\n"; print "\n"; print "\n"; foreach ( sort keys %ENV ) { my $tmp = $ENV{$_}; $tmp =~ s/&/&/g; $tmp =~ s/>/>/g; $tmp =~ s/\n"; } print "
Environment variableValue
$_☞ $tmp
\n"; print "
\n"; print "
\n"; print "
\n"; print "
\n"; print "
\n"; print $cgi->end_html; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/changelog000066400000000000000000002722631325274564300240520ustar00rootroot00000000000000lemonldap-ng (1.9.16) stable; urgency=high * #1390: Choice module allows XSS attack * #1389: Kerberos ticket revalidated in Multi mode * #1382: Kerberos - Username / Session uncorrectly set * #1378: lemonldap-ng-doc unable to install on Debian 7 * #1372: Action "update-cache" in lemonldap-ng-cli does not work * #1371: incompatibility between 1.4 portal and 1.9/2.0 handler : _utime not defined * #1368: Impossible to configure IssuerDB Get Parameters with RDBI backend * #1366: Problem with kerberos and ajax and ldap ... * #1363: Bad equality operator in Handler::Main::Jail * #1362: Allow CAS 3.0 endpoints (/p3/serviceValidate and /p3/proxyValidate) * #1360: Using "force" and "cfgNum" with lemonldap-ng-cli does not work * #1063: lemonldap-ng-fastcgi-server has a hard dependency on nginx * #1253: Default values not saved by Manager (complex nodes) lemonldap-ng (1.9.15) stable; urgency=high * #1358: Encoding issues with LDAP configuration backend * #1357: Wrong return status for processLogoutRequestMsg in SAML module * #1356: Prevent infinite loop in LDAP group recursive search * #1355: local session storage not being cleaned up * #1352: Encoding issues with MySQL configuration backend * #1351: missing dependency LWP::Protocol::https on CentOS 7 packaging * #1349: Initial url lost during reset password workflow * #1347: Do not allow "/" or ".." in skin parameter to avoid directory traversal attack * #1346: Check that skin directory exists before trying to open it * #1345: Autoredirect does not work after session expiration * #1343: Captcha code not removed after successful verification * #1341: llng-fastcgi-server: Allow to listen on TCP * #1337: mailFrom and mailReplyTo directives : bad default address * #1281: purgeLocalCache should use conf from manager lemonldap-ng (1.9.14) stable; urgency=high * #707: Kerberos authentication module * #1308: make saml work with POST sso binding and multiple authentication * #1310: Form replay javascript generates error for fields with a dot * #1315: Missing Mouse dependency in Debian packages * #1316: In docs, for Alfresco, said they need to add an exclusion for ressources path * #1324: Allow SAML with Office365 multidomains * #1326: SessionIndex should not be mandatory in SAML SingleLogoutRequest * #1328: Value 0 can not be set in hidden field * #1329: No need to 'warn' if no IDP or SP is present in configuration * #1331: Manage UTF-8 values in HTTP headers lemonldap-ng (1.9.13) stable; urgency=high * [LEMONLDAP-1209] - [UTF8-Enconding] Issues with mysql backend and saml attributes * [LEMONLDAP-1303] - Debian 9 and JSON parsing error - OpenID Connect * [LEMONLDAP-1304] - make saml tolerant to issuerDBSAMLPath lemonldap-ng (1.9.12) stable; urgency=high * [LEMONLDAP-1293] - Unable to delete "Exported Attributes" in SAML SP * [LEMONLDAP-1294] - Debian - JSON - Apache::Session module failed * [LEMONLDAP-1295] - Bad UserInfo response wihen attribute values are Perl references * [LEMONLDAP-1297] - Restrict reload url to the localhost * [LEMONLDAP-1299] - Unable to use LemonLDAP on Debian Stretch - Portal issue * [LEMONLDAP-1298] - CAS logout redirect service lemonldap-ng (1.9.11) stable; urgency=high * [LEMONLDAP-1244] - CGIPassAuth not usable in CentOS 7.3.1611 because of old Apache version * [LEMONLDAP-1255] - Issue with openid-configuration.pl when updating Perl * [LEMONLDAP-1262] - Session expired on Handler * [LEMONLDAP-1277] - Missing screen shot in documentation * [LEMONLDAP-1288] - Empty hash configuration parameters are converted to empty scalar trough SOAP * [LEMONLDAP-1289] - Proxy authentication module does not catch authentication error * [LEMONLDAP-1245] - adding salt feature for database backend * [LEMONLDAP-1254] - APT warning on weak digest algo on lemonldap repository * [LEMONLDAP-1256] - Avoid 'forcedSAML' in Choice module * [LEMONLDAP-1261] - SAML SessionIndex may leak SSO data and cause interoperability issues * [LEMONLDAP-1263] - No error message when backend is in ReadOnly * [LEMONLDAP-1270] - Logout_* * [LEMONLDAP-1243] - LinkedIn authentication module * [LEMONLDAP-1286] - httpd dependency lemonldap-ng (1.9.10) stable; urgency=high * [LEMONLDAP-1202] - CSS an JS not correctly loaded in FR offline doc * [LEMONLDAP-1203] - NginX handler and CDA does not work * [LEMONLDAP-1207] - GUI Error (HTTP 500) on Issuer module "GET" * [LEMONLDAP-1214] - No display type selected when session expired and authentication done via Mutli or Choice * [LEMONLDAP-1218] - Warning on expired session can break transparent authentication * [LEMONLDAP-1231] - debian wheezy doc package not working * [LEMONLDAP-1233] - redirect_uri parameter validity should be checked first to avoid unwanted redirections * [LEMONLDAP-1211] - Provide error page / error message for error 404 and 502 * [LEMONLDAP-1219] - Reject same SAML EntityID for Service Providers * [LEMONLDAP-1225] - Lost Password error message lemonldap-ng (1.9.9) stable; urgency=high * [LEMONLDAP-1081] - SAML artifact server double encode UTF-8 characters * [LEMONLDAP-1193] - entityID not found in metadata if value is between simple quotes instead of double quotes * [LEMONLDAP-1195] - JS error when clicking on export configuration * [LEMONLDAP-1197] - CSP errors in Manager * [LEMONLDAP-1199] - Compilation error in IssuerDBOpenIDConnect.pm * [LEMONLDAP-1187] - Make crypto functions available in safe jail * [LEMONLDAP-1191] - Brute force protection for OIDC * [LEMONLDAP-1200] - Force AllowCreate in NameIDPolicy for broken SAML clients lemonldap-ng (1.9.8) stable; urgency=high * [LEMONLDAP-1121] - Fail to require customNginxHandler * [LEMONLDAP-1130] - SOAP request fail (FCGI) - missing path info * [LEMONLDAP-1136] - Mail reset form allows email enumaration * [LEMONLDAP-1139] - Errors "Session cannot be tied" * [LEMONLDAP-1141] - Bad encoding in reset password emails * [LEMONLDAP-1145] - Missing user identifier in mail reset log messages * [LEMONLDAP-1147] - SAML session ID * [LEMONLDAP-1149] - lemonldap-ng-fastcgi-server not working on CentOS7 * [LEMONLDAP-1152] - jquery-ui.min.js not found * [LEMONLDAP-1155] - Typo in OIDC OP for keeping acr_values parameter * [LEMONLDAP-1159] - Session concurrency issue with SAML + OpenID Connect flow * [LEMONLDAP-1166] - Typo in bootstrap footer.tpl * [LEMONLDAP-1170] - Browse sessions by ip address duplicates entries * [LEMONLDAP-1179] - Bad session count in sessions explorer multi IP tab * [LEMONLDAP-1086] - Make Debian packages autopkgtestable * [LEMONLDAP-1120] - Add public pages concept in LemonLDAP::Portal * [LEMONLDAP-1122] - Enclose expressions * [LEMONLDAP-1125] - Avoid using unsafe eval Javascript * [LEMONLDAP-1127] - SAML: Reject same entityID on different Metadata * [LEMONLDAP-1132] - Warn users about session expired in portal * [LEMONLDAP-1135] - Warnings in unit tests * [LEMONLDAP-1143] - Manage doc indexing using robots.txt to avoid indexing old doc * [LEMONLDAP-1144] - Add vhost in reject log message * [LEMONLDAP-1156] - Export OpenIDConnect request parameters in %ENV * [LEMONLDAP-1158] - Export CAS request parameters in %ENV * [LEMONLDAP-1129] - Extract CN field from SSL certificate (authSSL) * [LEMONLDAP-1177] - Custom skin lost when submitting login form lemonldap-ng (1.9.7) stable; urgency=high * [LEMONLDAP-1097] - invalid base64 encoding on openidconnect key2jwks * [LEMONLDAP-1099] - FCGI: reload method return Internal Server Error * [LEMONLDAP-1101] - SAML IDP-initiated : Federation not found on login * [LEMONLDAP-1102] - Random access denied * [LEMONLDAP-1105] - Broken openidconect oidcRPMetaDataOptionsExtraClaims parsing (or saving) when using sql datastore * [LEMONLDAP-1107] - Use of uninitialized value in pattern match...Simple.pm line 1561 * [LEMONLDAP-1109] - Notification DBI backend has compilation error * [LEMONLDAP-1117] - Corrupted persistent session when value has accentued characters and storage is LDAP * [LEMONLDAP-1096] - Use manager libraries for doc with "external" hook * [LEMONLDAP-1098] - Allow access tokens to be gathered as parameters too * [LEMONLDAP-1100] - Create custom lltype for custom handler * [LEMONLDAP-1104] - Allow the parameters for the reload url to contain basic credentials * [LEMONLDAP-1106] - returnJSONError on _OpenIDConnect.pm should return a 400 status not a 200 * [LEMONLDAP-1108] - caFile/caPathc options should be available for LDAPS, not only for LDAP+TLS * [LEMONLDAP-1110] - Provide autopkgtest tests * [LEMONLDAP-1114] - Missing DirectoryIndex in offline documentation * [LEMONLDAP-1116] - Change how we check signatures on SAML messages * [LEMONLDAP-173] - Token for cross domain authentication * [LEMONLDAP-1115] - Documentation error lemonldap-ng (1.9.6) stable; urgency=high * [LEMONLDAP-1058] - Timeout on save conf * [LEMONLDAP-1060] - Missing reload target for nginx * [LEMONLDAP-1064] - getApacheSession not working with id * [LEMONLDAP-1068] - Error in logout request * [LEMONLDAP-1069] - start-stop-daemon warning in lemonldap-ng-fastcgi-server init script * [LEMONLDAP-1071] - OpenID Connect discovery: LLNG does not use booleans * [LEMONLDAP-1075] - Unable to add rule or header in a vhost using lemonldap-ng-cli * [LEMONLDAP-1076] - IDP resolution rule is no more available in Manager * [LEMONLDAP-1078] - CryptoJS URL have changed * [LEMONLDAP-1079] - Security options for SAML are set to Off by default * [LEMONLDAP-1080] - Typo is URL matching for Auth OpenID * [LEMONLDAP-1093] - /run/llng-fastcgi-server is deleted on reboot * [LEMONLDAP-1094] - typo in error_pt.al * [LEMONLDAP-1001] - Possibility to configure the update interval used for timeout activity * [LEMONLDAP-1065] - Provide SSL options for AuthBasic * [LEMONLDAP-1082] - Return explicit error if no token endpoint auth method is set * [LEMONLDAP-1083] - Create an option to not store SAML/OIDC tokens in session * [LEMONLDAP-1084] - Disable SAML SLO request when LL::NG configured as SP and IDP does not support SLO * [LEMONLDAP-1087] - Allow to check audience and time conditions separately in SAML flow * [LEMONLDAP-1088] - Allow relayState to be a redirection URI * [LEMONLDAP-1089] - Option to bypass consent in OpenID Connect Issuer * [LEMONLDAP-1067] - Authbasic handler for Nginx lemonldap-ng (1.9.5) stable; urgency=high * [LEMONLDAP-966] - RSA Keys generated from Manager are incomplete * [LEMONLDAP-1028] - SAML SP SOAP logout does not happen * [LEMONLDAP-1046] - Default value for samlIDPMetaDataOptionsSSOBinding should be undef * [LEMONLDAP-1047] - SAML SLO from IDP does not work when SP is LL::NG * [LEMONLDAP-1048] - Unable to upgrade a configuration from 1.4 to 1.9 using lmConfigEditor * [LEMONLDAP-1049] - Unable to read LDAP session in 1.4 format with 1.9 version * [LEMONLDAP-1050] - signing in to chrome devices via sso is broken * [LEMONLDAP-1054] - test_config not found in lemonldap-ng-fastcgi-server init script * [LEMONLDAP-1059] - Portal disconnection warning * [LEMONLDAP-1043] - Display total number of sessions * [LEMONLDAP-1045] - Wrong SAML attributes encoding issued by IDP * [LEMONLDAP-1052] - Use Lasso 'thin-sessions' * [LEMONLDAP-1055] - Remove network access attempts during tests * [LEMONLDAP-1057] - Change displayed message when sending confirmation mail after password reset * [LEMONLDAP-1056] - SAML SLO relay URL not catched lemonldap-ng (1.9.4) stable; urgency=high * [LEMONLDAP-1034] - Missing dependencies in documentation * [LEMONLDAP-1036] - LDAP sessions are not purged * [LEMONLDAP-1037] - Using LDAP as conf backend, IssuerDBGetParameters with wrong value inserted after conf save * [LEMONLDAP-1038] - All information is lost when vhost or SAML/OIDC partner is renamed in Manager * [LEMONLDAP-1039] - Error not displayed correctly for notification browsing * [LEMONLDAP-1040] - Session browsing not working if _whatToTrace is missing * [LEMONLDAP-1041] - ldapAttributeId not used everywhere in _LDAPGKFAS * [LEMONLDAP-1035] - Manage Plack engines in FastCGI server * [LEMONLDAP-1042] - Some information are lost when renaming OIDC/SAML partner lemonldap-ng (1.9.3) stable; urgency=low * [LEMONLDAP-985] - authForce is not well called trough AuthMulti * [LEMONLDAP-997] - Circular dependency for liblemonldap-ng-handler-perl package * [LEMONLDAP-1003] - Replace Mouse by Moose if ModPerl::Registry is used with Perl 5.22 * [LEMONLDAP-1006] - Typo in Common/Apache/Session.pm on LDAP disconnect * [LEMONLDAP-1008] - Bad comment in lemonldap-ng.ini * [LEMONLDAP-1009] - Version shown in Manager is not the one of the main module * [LEMONLDAP-1010] - Problem with persistent sessions and MongoDB backend * [LEMONLDAP-1012] - AuthTwitter is not working anymore * [LEMONLDAP-1013] - AuthFacebook is not working anymore * [LEMONLDAP-1014] - Example values for LDAP backend configuration are wrong * [LEMONLDAP-1016] - Can't configure OpenID Connect RP Extra claims in lemonldap web manager * [LEMONLDAP-1018] - Slave authentication error (Can't locate object method "checkHeader") * [LEMONLDAP-1020] - Can't define SMTP server with port * [LEMONLDAP-1022] - The path of the request is lost when using the url parameter of a Choice module * [LEMONLDAP-1026] - lemonldap-ng-fastcgi-server is missing libfcgi-procmanager-perl as a dependency * [LEMONLDAP-1029] - Missing images in Debian packaging * [LEMONLDAP-1030] - Cannot start Manager with zero conf in LDAP backend * [LEMONLDAP-983] - Import encrypt in functions * [LEMONLDAP-1004] - Es, it, pt, ne and de translations * [LEMONLDAP-1011] - Option to allow a user to reset an expired password * [LEMONLDAP-1023] - Add documentation to nginx handler * [LEMONLDAP-1025] - provide additional GET parameters while redirecting to handler * [LEMONLDAP-1031] - Be less restrictive on service parameter check in CAS issuer lemonldap-ng (1.9.2) stable; urgency=low * [LEMONLDAP-985] - authForce is not well called trough AuthMulti * [LEMONLDAP-988] - CPAN Tests fails for Lemonldap-NG-Common * [LEMONLDAP-989] - CPAN Tests fails for Lemonldap-NG-Portal * [LEMONLDAP-991] - LDAP TCP connections is still not closed * [LEMONLDAP-992] - LL:NG use wrong variables with Multi auth * [LEMONLDAP-994] - Can't call method "add_output_filter" on an undefined value when I logout * [LEMONLDAP-995] - Encoding problem in menu categories and applications * [LEMONLDAP-996] - logout_app_sso URL rejected * [LEMONLDAP-1000] - Session errors with persistent sessions * [LEMONLDAP-1002] - Show sent headers in debug mode * [LEMONLDAP-986] - Propose packages for SLES 12 SP1 lemonldap-ng (1.9.1) stable; urgency=low * [LEMONLDAP-961] - PAUSE indexer report * [LEMONLDAP-962] - Applications logos and portal background not displayed in Manager * [LEMONLDAP-964] - Links to change * [LEMONLDAP-965] - Syntax checking on certificate must be more tolerant * [LEMONLDAP-968] - Headers corrupted when authenticating with HTTP basic authentication on a protected application * [LEMONLDAP-969] - /var/run is a tmpfs so FastCGI pid can't be written after reboot * [LEMONLDAP-972] - Missing test for exportedHeaders * [LEMONLDAP-974] - keyMsgFail are missing in Manager/Attributes.pm * [LEMONLDAP-976] - $ENV is replaced by $datas->{ENV} * [LEMONLDAP-978] - CPAN Tests fails for Lemonldap-NG-Common * [LEMONLDAP-980] - Error "password must be changed" when user not found in AD * [LEMONLDAP-984] - Allow to set replica for MongoDB configuration backend * [LEMONLDAP-973] - Activate maintenance mode if reval() fails * [LEMONLDAP-185] - Check configuration uploaded by lmConfigEditor lemonldap-ng (1.9.0) stable; urgency=low * [LEMONLDAP-176] - POST Handler feature does not work with mod_proxy * [LEMONLDAP-395] - LL::NG::Handler::CGI ignores some config parameters * [LEMONLDAP-729] - Handler Jail may be inconsistent with its attributes * [LEMONLDAP-759] - Cannot store Conf or Sessions in AD (was Storable appears to not work on 64-bit OS) * [LEMONLDAP-767] - future deprecated dependency * [LEMONLDAP-777] - Password fiedls in Manager * [LEMONLDAP-802] - Apache2::Connection remote_ip not supported in Apache 2.4 * [LEMONLDAP-825] - Error when session is not in backend but only in cookie * [LEMONLDAP-827] - Error encoding of passwords when using special characters in file lmconf. * [LEMONLDAP-828] - wrong Makefile target for translation * [LEMONLDAP-835] - Interface with unicode * [LEMONLDAP-840] - Auth-User HTTP Header appears even if no HTTP Headers defined on VHost * [LEMONLDAP-854] - Manager returns "Not authorized" with Apache 2.4 and fr-doc not installed * [LEMONLDAP-858] - Error 500 at Save (on virtualHost Rules), when the displayName of one Category Portal Menu contains accentuated Character * [LEMONLDAP-866] - Configuration deletion does not work * [LEMONLDAP-867] - 404 errors in documentation * [LEMONLDAP-870] - _lastSeen should be updated when a issuer module (ex: CAS) is called * [LEMONLDAP-872] - Omegat does not end * [LEMONLDAP-914] - Password expiration interception in Multi mode * [LEMONLDAP-922] - SAML Error on update session * [LEMONLDAP-923] - Error save conf SlaveMasterIp * [LEMONLDAP-948] - openid userinfo endpoints need Authorization header * [LEMONLDAP-954] - GLPI link is broken * [LEMONLDAP-955] - GRR link is broken * [LEMONLDAP-958] - Infinite redirection loop when redirected from Handler for an error (403/500/503) * [LEMONLDAP-428] - Ergonomic items * [LEMONLDAP-534] - splice not necessary to parse @_ in subroutines * [LEMONLDAP-633] - unify var substitution in locationRules and exportedHeaders * [LEMONLDAP-717] - Handler init management * [LEMONLDAP-733] - Form replay refactoring * [LEMONLDAP-776] - Use Bootstrap for Manager * [LEMONLDAP-787] - [UserDB][LDAP] Allow alias dereferencing in search * [LEMONLDAP-790] - Portal should not return HTML for AJAX requests * [LEMONLDAP-794] - Default values must be set before storing in local cache * [LEMONLDAP-795] - Propose JSON serialization in Apache::Session to be able to access to sessions with other languages * [LEMONLDAP-796] - Replace our own serializer by JSON in Conf/File.pm * [LEMONLDAP-798] - Avoid opening local cache when root * [LEMONLDAP-815] - Improve the cookie name regexp * [LEMONLDAP-821] - JSON File as new default configuration backend * [LEMONLDAP-824] - autocomplete=off does not prevent anymore password manager use * [LEMONLDAP-833] - Manager - Multi : display only the selected modules * [LEMONLDAP-865] - Check conditions in AuthSlave and UserDBSlave * [LEMONLDAP-877] - Replace Storable by JSON to be arch independent * [LEMONLDAP-908] - Replace own minifier by external * [LEMONLDAP-911] - Possibility to set a specific logo for a choice module * [LEMONLDAP-917] - Possibility to define finely sessions timeout activity * [LEMONLDAP-924] - Manager not checking regex before saving * [LEMONLDAP-930] - Scripts must have POD * [LEMONLDAP-946] - Set cfgAuthor to lmConfigEditor * [LEMONLDAP-24] - Browse configuration versions and apply them * [LEMONLDAP-183] - OAuth 2.0 / OpenID Connect authentication module * [LEMONLDAP-184] - OAuth 2.0 / OpenID Connect provider module * [LEMONLDAP-227] - VirtualHost Copy/paste functions in Manager * [LEMONLDAP-287] - Implement HTTP Strict Transport Security * [LEMONLDAP-495] - Persistent sessions Explorer * [LEMONLDAP-583] - Nginx handler * [LEMONLDAP-630] - Modularization of Handler code * [LEMONLDAP-770] - Configuration of portal background * [LEMONLDAP-773] - Implement CAS 3.0 Protocol (attributes exchange) * [LEMONLDAP-800] - MongoDB configuration and session backend * [LEMONLDAP-820] - New Manager interface with AngularJS * [LEMONLDAP-836] - Add Choice to included X509 certificate in Signature of SAML Messages, when LL::NG acts as IDP * [LEMONLDAP-915] - Portal message customization * [LEMONLDAP-925] - New Notification Explorer * [LEMONLDAP-935] - Capability to duplicate virtualhost * [LEMONLDAP-864] - SAML and manager translations(utf8) * [LEMONLDAP-859] - Perl-Digest-SHA is not listed at dependencies documentation * [LEMONLDAP-873] - Change screenshots in doc * [LEMONLDAP-891] - Remove "return to SP link" * [LEMONLDAP-909] - Push French translation into sources * [LEMONLDAP-932] - Packages for RHEL / CentOS * [LEMONLDAP-871] - Manager protection * [LEMONLDAP-874] - Add portal and logout links, add current version * [LEMONLDAP-878] - Button to download file * [LEMONLDAP-879] - Possibility to have a certificate instead of a public key * [LEMONLDAP-880] - Bug in Logs node * [LEMONLDAP-881] - Load metadata from file * [LEMONLDAP-882] - Problem with radio buttons in samlAttributeContainer component * [LEMONLDAP-883] - Bug with choices modules confguration * [LEMONLDAP-884] - Optional URL in AuthChoices module * [LEMONLDAP-885] - Unable to register OpenID Connect metadata * [LEMONLDAP-886] - favicon disappear when using configuration tab * [LEMONLDAP-888] - SAML attributes and other options not saved * [LEMONLDAP-889] - Saving an old configuration leads to "No such file or directory" * [LEMONLDAP-892] - Set OpenID Connect standard attributes in default values * [LEMONLDAP-893] - Unable to download configuration * [LEMONLDAP-894] - Get another default component for nodes * [LEMONLDAP-895] - Associated help is not displayed in SAML SP/IDP * [LEMONLDAP-896] - Labels for samlSP and samlSPName not displayed * [LEMONLDAP-897] - Handler Status does not work * [LEMONLDAP-898] - Handler Menu does not work * [LEMONLDAP-899] - Button to show/hide documentation panel * [LEMONLDAP-900] - Fill the domain when creating a new virtual host * [LEMONLDAP-901] - Propose default names for IDP/SP/OP/RP * [LEMONLDAP-902] - Replace javascript prompts by dialogs/modals * [LEMONLDAP-903] - ZeroConf * [LEMONLDAP-904] - Open IDP/SP node after its creation * [LEMONLDAP-905] - Login is displayed in errors * [LEMONLDAP-906] - Hide inaccessible modules in manager interface * [LEMONLDAP-907] - Deleting a menu entry isn't detected * [LEMONLDAP-913] - XS mode: menu never visible when tree is displayed * [LEMONLDAP-916] - missing semicolons in Makefile * [LEMONLDAP-919] - Choosing Multi module should not lock passwordDB configuration * [LEMONLDAP-920] - Clear cfgLog when using lmConfigEditor * [LEMONLDAP-921] - Implement lemonldap-ng-cli wth new configuration code * [LEMONLDAP-926] - Error is not displayed to user * [LEMONLDAP-927] - Use modal instead of alert * [LEMONLDAP-928] - Bad notification encoding * [LEMONLDAP-929] - Manage other portal CGIs * [LEMONLDAP-934] - LLNG status for Nginx * [LEMONLDAP-936] - Extra headers sent to protected applications * [LEMONLDAP-938] - Can't save conf due to bad custom function name * [LEMONLDAP-940] - Timout for reloadUrls * [LEMONLDAP-941] - Aliases not taken into account * [LEMONLDAP-942] - Session explorer not usable with Apache::Session::Browseable::MySQL * [LEMONLDAP-943] - Zimbra Handler * [LEMONLDAP-944] - Notifications - invalid date * [LEMONLDAP-945] - Auto-protected CGI not working * [LEMONLDAP-947] - Notifications cannot be purged for DBI and LDAP * [LEMONLDAP-949] - Handler PSGI should set LMREMOTE_USER * [LEMONLDAP-950] - spelling * [LEMONLDAP-952] - Errors not displayed in Notifications Explorer * [LEMONLDAP-953] - Notifications are mixed under the same letter * [LEMONLDAP-956] - Custom functions don't work with useSafeJail * [LEMONLDAP-957] - Replace $http.success() by .then() lemonldap-ng (1.4.11) stable; urgency=low * [LEMONLDAP-1068] - Error in logout request * [LEMONLDAP-1080] - Typo is URL matching for Auth OpenID * [LEMONLDAP-1092] - Net::LDAP does not have an uri method in el5 * [LEMONLDAP-1001] - Possibility to configure the update interval used for timeout activity * [LEMONLDAP-1052] - Use Lasso 'thin-sessions' * [LEMONLDAP-1083] - Create an option to not store SAML/OIDC tokens in session * [LEMONLDAP-1084] - Disable SAML SLO request when LL::NG configured as SP and IDP does not support SLO lemonldap-ng (1.4.10) stable; urgency=low * [LEMONLDAP-985] - authForce is not well called trough AuthMulti * [LEMONLDAP-1034] - Missing dependencies in documentation * [LEMONLDAP-1047] - SAML SLO from IDP does not work when SP is LL::NG * [LEMONLDAP-1050] - signing in to chrome devices via sso is broken * [LEMONLDAP-1059] - Portal disconnection warning * [LEMONLDAP-1057] - Change displayed message when sending confirmation mail after password reset lemonldap-ng (1.4.9) stable; urgency=low * [LEMONLDAP-1003] - Replace Mouse by Moose if ModPerl::Registry is used with Perl 5.22 * [LEMONLDAP-1006] - Typo in Common/Apache/Session.pm on LDAP disconnect * [LEMONLDAP-1022] - The path of the request is lost when using the url parameter of a Choice module * [LEMONLDAP-1027] - Can't locate object method "client_ip" via package "Apache2::Connection" * [LEMONLDAP-1004] - Es, it, pt, ne and de translations * [LEMONLDAP-1031] - Be less restrictive on service parameter check in CAS issuer lemonldap-ng (1.4.8) stable; urgency=low * [LEMONLDAP-985] - authForce is not well called trough AuthMulti * [LEMONLDAP-991] - LDAP TCP connections is still not closed * [LEMONLDAP-992] - LL:NG use wrong variables with Multi auth * [LEMONLDAP-1000] - Session errors with persistent sessions * [LEMONLDAP-986] - Propose packages for SLES 12 SP1 lemonldap-ng (1.4.7) stable; urgency=low * [LEMONLDAP-802] - Apache2::Connection remote_ip not supported in Apache 2.4 * [LEMONLDAP-842] - manager configuration tree does not display correctly * [LEMONLDAP-866] - Configuration deletion does not work * [LEMONLDAP-958] - Infinite redirection loop when redirected from Handler for an error (403/500/503) * [LEMONLDAP-964] - Links to change * [LEMONLDAP-968] - Headers corrupted when authenticating with HTTP basic authentication on a protected application * [LEMONLDAP-976] - $ENV is replaced by $datas->{ENV} * [LEMONLDAP-980] - Error "password must be changed" when user not found in AD lemonldap-ng (1.4.6) stable; urgency=low * [LEMONLDAP-705] - SAML with Signature Method rsa-sha256 * [LEMONLDAP-715] - Multi with # in the module name: error while calling authLogout * [LEMONLDAP-720] - Error with CPAN tests * [LEMONLDAP-823] - duplicated groups when recursive groups enabled * [LEMONLDAP-841] - Error in extract_lang with a value with * * [LEMONLDAP-843] - localStorage replaced by localSessionStorage * [LEMONLDAP-845] - Session activity not updated * [LEMONLDAP-846] - Session cache not purged * [LEMONLDAP-848] - Do not call 'perl' directly (see RT#107205) * [LEMONLDAP-849] - Syntax checking on domain name is too restrictive * [LEMONLDAP-850] - SOAP data not well formatted * [LEMONLDAP-768] - Fixed with for application boxes in menu in bootstrap skin * [LEMONLDAP-771] - Adapt foot size in mobile mode for Bootstrap skin * [LEMONLDAP-822] - checking pwdLastSet in AD is not sufficient * [LEMONLDAP-781] - Lasso package * [LEMONLDAP-785] - Display password expiration management with Active Directory * [LEMONLDAP-792] - Support for multivaluated attributes in LDAP for groups lemonldap-ng (1.4.5) stable; urgency=low * [LEMONLDAP-816] - Wrong definition of getAttributes in Portal WSDL * [LEMONLDAP-817] - Wrong parameter order for error SOAP operation in Portal WSDL * [LEMONLDAP-818] - Skin rules on mail reset and register page lemonldap-ng (1.4.4) stable; urgency=low * [LEMONLDAP-763] - purgeCentralCache sometimes hangs * [LEMONLDAP-783] - Test error with SOAP::Lite 1.12 * [LEMONLDAP-784] - reset password in AD not working * [LEMONLDAP-788] - Captcha not working using multiple backends... * [LEMONLDAP-793] - Common/Conf/File must return an error if file can't be opened * [LEMONLDAP-801] - Multi and Kerberos does not work with a positive LocationMatch * [LEMONLDAP-805] - Update session failure on high load if idle timeout is configured * [LEMONLDAP-806] - ErrorDocument conflicts with CentOS's default apache vhost * [LEMONLDAP-799] - parameter notOnOrAfter should be computed against SAML message emission date * [LEMONLDAP-807] - End of OpenID 2.0 support for Google on April 20, 2015 lemonldap-ng (1.4.3) stable; urgency=low * [LEMONLDAP-775] - Cas Service Ticket should be used only once * [LEMONLDAP-772] - Collapse menu on click in mobile mode in Bootstrap skin * [LEMONLDAP-774] - Use portal bootstrap theme for test pages * [LEMONLDAP-765] - Provide packages for CentOS 7 * [LEMONLDAP-780] - Remove old captcha dirs lemonldap-ng (1.4.2) stable; urgency=low * [LEMONLDAP-740] - TCP connections never closed on LDAP * [LEMONLDAP-743] - Password reset doesn't work with Apache::Session::MySQL::NoLock * [LEMONLDAP-745] - notifyDeleted ignored with the new bootstrap theme * [LEMONLDAP-747] - Apache::Session::Postgres.pm * [LEMONLDAP-750] - Exported variable name vs LDAP attr name * [LEMONLDAP-751] - Login page on Bootstrap thème * [LEMONLDAP-752] - Portal URL is treated as Bad URL * [LEMONLDAP-753] - OpenID provider broken * [LEMONLDAP-754] - Error when configuring captcha trough Manager * [LEMONLDAP-758] - SAML metadata are not valid (NameIDFormat not in the rigth place) * [LEMONLDAP-761] - SOAP cannot be used with DBI backend * [LEMONLDAP-762] - Don't call data() on unavailable session * [LEMONLDAP-746] - Doc: update id size for DBI sessions backend * [LEMONLDAP-748] - Possibility to start with empty configuration masks errors loading conf backend * [LEMONLDAP-749] - AuthBasic doesn't support HTTPS with self-signed certificate * [LEMONLDAP-755] - check aliases when computing vhost rules on portal * [LEMONLDAP-760] - Apache2.4-style syntax lemonldap-ng (1.4.1) stable; urgency=low * [LEMONLDAP-719] - AuthBasic handler doesn't check password when using AuthMulti (SSL;LDAP) * [LEMONLDAP-721] - Portal cipher object unavailable with useLocalConf = 1 * [LEMONLDAP-722] - Error on session explorer and notification explorer on CentOS * [LEMONLDAP-723] - Error 500 on portal when mpm worker enabled on RHEL6.5 * [LEMONLDAP-725] - [Password reset] Reset pwd with pwdReset cause empty $groups * [LEMONLDAP-727] - /status page not working since upgrade * [LEMONLDAP-728] - Skirt header cleaning with unprotect * [LEMONLDAP-730] - lmConfigEditor do not save conf with ldap backend * [LEMONLDAP-731] - convertConfig fail to migrate conf to LDAP from File * [LEMONLDAP-732] - Soap communication broken since upgrade * [LEMONLDAP-734] - lemonldap-ng-cli not working with LDAP conf backend * [LEMONLDAP-735] - IssuerDB modules do not work with Kerberos failback login script * [LEMONLDAP-736] - Do not force default value in SMTPServer * [LEMONLDAP-739] - dpkg error while installing fresh LemonLDAP::NG 1.4.0 on wheezy * [LEMONLDAP-738] - Add a portal button on the Manager * [LEMONLDAP-741] - Store errors in Common session module to display them in logs * [LEMONLDAP-742] - Do not make lock calls when session found in cache * [LEMONLDAP-737] - Possibilty to configure NotOnOrAfter and SessionNotOnOrAfter attributes in SAML messages lemonldap-ng (1.4.0) stable; urgency=low * [LEMONLDAP-663] - Connections to auth backends not closed on errors * [LEMONLDAP-664] - Connections to LDAP not closed with the Multi plugin * [LEMONLDAP-670] - Bootstrap theme * [LEMONLDAP-693] - loginHistory and Session Explorer : Error * [LEMONLDAP-694] - Duplicate entry '1-globalStorage' for key 'PRIMARY' when using RDBI configuration * [LEMONLDAP-695] - Vulnerability on the size of session identifiers. * [LEMONLDAP-698] - error at reading last config number with RDBI config storage * [LEMONLDAP-699] - MySQL config storage lock does not work * [LEMONLDAP-700] - Unable to handle SAML session * [LEMONLDAP-701] - missing debian dependency to Mouse * [LEMONLDAP-704] - Unable to change password with Active Directory backend * [LEMONLDAP-708] - Memory leak in portal when notifications are enabled * [LEMONLDAP-709] - The cipher decrypt method breaks carriage returns * [LEMONLDAP-710] - sessionDatas not reinitialized from request to request in a thread * [LEMONLDAP-711] - Read a session in remote session backend causes an update request * [LEMONLDAP-712] - strange behaviour with session cache * [LEMONLDAP-386] - use LL::NG::Handler instead of custom perl module in apache config * [LEMONLDAP-430] - httpSession and updateSession + deleteSessionFromLocalStorage optimization * [LEMONLDAP-591] - Portal should refresh their configuration cache on expiration * [LEMONLDAP-600] - Rewrite object libs with Moo or Mouse * [LEMONLDAP-636] - Manage exported variables per UserDB module * [LEMONLDAP-648] - Build French documentation in Makefile * [LEMONLDAP-657] - [SAML] NameID format customizable per SP * [LEMONLDAP-658] - Portal keepalive should be desactivable and configurable * [LEMONLDAP-671] - Cache management for configuration and sessions * [LEMONLDAP-675] - Password should not be send trough email * [LEMONLDAP-681] - Add option in SP configuration to specify which query_string method to use. * [LEMONLDAP-683] - Externalize all JS code and use make tidy-js * [LEMONLDAP-686] - Centralize default configuration values * [LEMONLDAP-702] - Possibility to start with empty configuration * [LEMONLDAP-703] - Do not use files for Captcha * [LEMONLDAP-26] - Auto-register page * [LEMONLDAP-208] - Build SAML IDP SSO initiated URL on IDP side for registered SP * [LEMONLDAP-629] - Handler with mpm_event lemonldap-ng (1.3.3) stable; urgency=low * [LEMONLDAP-665] - level parameter not used in userLog with syslog * [LEMONLDAP-684] - syslog: invalid level/facility: warn * [LEMONLDAP-685] - /var/lib/lemonldap-ng/psessions is not created on rpm based install * [LEMONLDAP-687] - 404 error : jquery-1.10.2.min.map is not found * [LEMONLDAP-688] - lemonldap-cli-ng apps-set-* and vhost-del not working properly * [LEMONLDAP-690] - Cannot register more than on POST URL in Manager * [LEMONLDAP-692] - lemonldap-ng-cli config encoding * [LEMONLDAP-689] - Remove compressed js file from Debian distribution * [LEMONLDAP-691] - Manage apache configuration during install lemonldap-ng (1.3.2) stable; urgency=low * [LEMONLDAP-655] - Password change not working for DBI password backend with option "require old password" enabled * [LEMONLDAP-656] - UserDB Multi does not accept any module * [LEMONLDAP-660] - Missing PID in syslog messages * [LEMONLDAP-661] - lemonldap ng dependancy not installed for debian wheezy * [LEMONLDAP-662] - lemonldap ng psession directory not created in package * [LEMONLDAP-665] - level parameter not used in userLog with syslog * [LEMONLDAP-666] - Lemonldap NG (1.2.5) Control XSS problem with logonid have apostrophe * [LEMONLDAP-669] - [LDAP] Authentication process stopped if a user must change its password and expiration warning is displayed * [LEMONLDAP-674] - Remove Facebook script in offline doc * [LEMONLDAP-676] - Privacy break * [LEMONLDAP-677] - Signature Problem using ADFS as SP * [LEMONLDAP-679] - Javascript error in Manager when loading a metadata from URL * [LEMONLDAP-680] - CDA does not work for http with "double cookie for single session" * [LEMONLDAP-682] - Permissions for lemonldap-ng-cli * [LEMONLDAP-647] - Hide message div if no message to display * [LEMONLDAP-650] - logout tab in menu should display by default only if no other tab is present * [LEMONLDAP-654] - DBI authentication not working with Unix passwords in DB * [LEMONLDAP-659] - The user input field in password.tpl should be readonly or hidden * [LEMONLDAP-668] - Performance improvement with DNS cache * [LEMONLDAP-649] - Total rewrite of lemonldap-ng-cli tool * [LEMONLDAP-678] - Provide non minified versions of javascript libraries lemonldap-ng (1.3.1) stable; urgency=low * [LEMONLDAP-635] - Extra tests fails on new install * [LEMONLDAP-637] - Missing XML::Simple dependency in Manager CPAN package * [LEMONLDAP-638] - Lemonldap::NG::Manager::Cli requires perl(feature), which is not available in EL5 * [LEMONLDAP-639] - portal/captcha_output directory has 777 permissions * [LEMONLDAP-640] - /var/lib/lemonldap-ng/captcha is not created when installed from RPM * [LEMONLDAP-642] - Captcha directories not installed with DEB packages * [LEMONLDAP-644] - Captcha required in MailReset when asking to resend confirmation mail * [LEMONLDAP-645] - Captcha not displayed in AuthChoice with form based modules * [LEMONLDAP-646] - Manager broken for MSIE-8 * [LEMONLDAP-641] - [SAML] Possibility to use IDP Name instead of IDP entityID in URL for IDP selection * [LEMONLDAP-643] - Launch initCaptcha only when needed lemonldap-ng (1.3.0) stable; urgency=low * [LEMONLDAP-471] - Incompatibility with Config::IniFiles 2.72 * [LEMONLDAP-499] - purgeLocalCache does not work * [LEMONLDAP-513] - AD password field for userModifyPassword is not userPassword but unicodePwd and must be quoted and unicoded * [LEMONLDAP-520] - Manager requires custom functions to be run with arguments * [LEMONLDAP-590] - Memory Leak in Lemonldap::NG::Common::Conf * [LEMONLDAP-592] - Encoding problems in POD * [LEMONLDAP-593] - Auth Multi getDisplayType error when using # in Multi line configuration * [LEMONLDAP-599] - Missing some dependencies with Debian packaging * [LEMONLDAP-603] - Portal's display broken with MS IE 8 * [LEMONLDAP-605] - skin rules are not applied on mail reset page * [LEMONLDAP-611] - Build failure on EL5 * [LEMONLDAP-614] - Configuration is broken when adding a form replay node without post data * [LEMONLDAP-616] - logout_sso do not stop on the "you are disconnected" page * [LEMONLDAP-618] - Lasso error with AuthChoice * [LEMONLDAP-625] - remote_ip() not available with some mod_perl and may not be required for LLNG * [LEMONLDAP-626] - Manager's display broken with Internet Explorer 8 * [LEMONLDAP-627] - Sessions explorer broken with Browseable backends * [LEMONLDAP-634] - Wrong rights on notifications dir in Debian * [LEMONLDAP-241] - Test for cryptographic functions * [LEMONLDAP-366] - [Notifications] Move Notifications code from Portal to Common * [LEMONLDAP-412] - Passwrd policy expiration warning time not friendly displayed * [LEMONLDAP-493] - Make LL::NG's rpm spec file more portable * [LEMONLDAP-500] - do not burden config in memory with useless things * [LEMONLDAP-524] - minimize weight of relaystate in SAML session backend * [LEMONLDAP-559] - Refine useXForwardedForIP option by setting trusted proxies * [LEMONLDAP-585] - Split SSO sessions and persistent sessions at installation * [LEMONLDAP-586] - Allow mail reset to be tested with Demo backend * [LEMONLDAP-589] - Debug info always printed in Lemonldap::NG::Common::Conf::LDAP * [LEMONLDAP-594] - Remove debian repository from distribution * [LEMONLDAP-596] - compute macros and local groups in a certain order * [LEMONLDAP-607] - Die and add error information if LDAP server is not reachable * [LEMONLDAP-619] - Add AuthFacebook module * [LEMONLDAP-620] - Centralize LWP::UserAgent in one file * [LEMONLDAP-628] - Optimization of configuration reload in Portal * [LEMONLDAP-61] - FastCGI portal * [LEMONLDAP-217] - Captcha in portal * [LEMONLDAP-291] - Support secondary Apache authentication in a "choice" authentication configuration * [LEMONLDAP-409] - Specific AD authentication module * [LEMONLDAP-457] - [Notifications] LDAP backend to store notifications * [LEMONLDAP-503] - vhost aliases * [LEMONLDAP-558] - Vhost alias * [LEMONLDAP-584] - BrowserID authentication module * [LEMONLDAP-588] - Include lemonldap-ng-cli * [LEMONLDAP-604] - Upgrade jQuery and jQuery UI built-in dependencies * [LEMONLDAP-612] - Hide password in logs when password is stored in session * [LEMONLDAP-613] - Log applied rule in debug mode * [LEMONLDAP-615] - Add AuthGoogle module * [LEMONLDAP-617] - [SAML] Allow to skip the IDP selection * [LEMONLDAP-621] - Config storage in JSON file * [LEMONLDAP-623] - WebID authentication and user DB modules * [LEMONLDAP-632] - Rename liblemonldap-ng-conf-perl to lemonldap-ng-common-perl * [LEMONLDAP-631] - Minimize jQuery-UI lemonldap-ng (1.2.5) stable; urgency=low * [LEMONLDAP-532] - SOAP not working with SSL * [LEMONLDAP-597] - Wrong evaluation of $ENV{REMOTE_ADDR} in Auth::Multi when safe jail is enabled * [LEMONLDAP-599] - Missing some dependencies with Debian packaging * [LEMONLDAP-603] - Portal's display broken with MS IE 8 * [LEMONLDAP-605] - skin rules are not applied on mail reset page * [LEMONLDAP-608] - Could not configure different config file in Portal thru SharedConf * [LEMONLDAP-609] - case insensitive comparison in vhost * [LEMONLDAP-596] - compute macros and local groups in a certain order * [LEMONLDAP-598] - Sessions Explorer should use the browseable indexes * [LEMONLDAP-607] - Die and add error information if LDAP server is not reachable lemonldap-ng (1.2.4) stable; urgency=low * [LEMONLDAP-590] - Memory Leak in Lemonldap::NG::Common::Conf * [LEMONLDAP-592] - Encoding problems in POD * [LEMONLDAP-593] - Auth Multi getDisplayType error when using # in Multi line configuration * [LEMONLDAP-589] - Debug info always printed in Lemonldap::NG::Common::Conf::LDAP * [LEMONLDAP-594] - Remove debian repository from distribution lemonldap-ng (1.2.3) stable; urgency=low * [LEMONLDAP-316] - Accentued letters in application list raise an error when configuration is stored in LDAP * [LEMONLDAP-536] - Password reset by mail do not work with DBI backend * [LEMONLDAP-537] - Web service deleteNotification do not work with DBI backend * [LEMONLDAP-538] - Bad log level in _DBI.pm * [LEMONLDAP-539] - Add SOAP::Lite dependency for Handler CPAN module * [LEMONLDAP-543] - LL:NG::Handler::AuthBasic fails to manage persistent connections * [LEMONLDAP-544] - Bad indexes in Browseable doc * [LEMONLDAP-545] - "none" target does not work in Handler/CGI.pm * [LEMONLDAP-548] - Error when displaying password policy messages (grace or expiration) * [LEMONLDAP-550] - Cannot use Target Url in Form Replay * [LEMONLDAP-551] - Invalid GET Request after Form Replay * [LEMONLDAP-552] - Error on configuration save if no reloadUrls defined * [LEMONLDAP-553] - SOAP Error: id is required at /usr/share/perl5/Lemonldap/NG/Portal/_SOAP.pm line 165 * [LEMONLDAP-555] - Rules field stay in readonly with JQuery 1.7.2 * [LEMONLDAP-556] - Cookie sent to untrusted domain with CDA * [LEMONLDAP-557] - Get Key From All Sessions in File backend can fail on corrupted sessions * [LEMONLDAP-561] - SAML transient NameID does not work * [LEMONLDAP-562] - CAS Authn + SAML IDP: authLogout error * [LEMONLDAP-570] - SAML messages signatures are not verified - SECURITY ISSUE * [LEMONLDAP-574] - Local cache purge script does not work * [LEMONLDAP-579] - missing dir in handler debian package * [LEMONLDAP-580] - Mail subject is not correctly encoded * [LEMONLDAP-412] - Passwrd policy expiration warning time not friendly displayed * [LEMONLDAP-512] - free size for cipher key * [LEMONLDAP-554] - Some improvements on lmConfigEditor * [LEMONLDAP-559] - Refine useXForwardedForIP option by setting trusted proxies * [LEMONLDAP-563] - CAS Authn + SAML IDP: Passing request parameters to redirect * [LEMONLDAP-566] - Allow to sort categories in the application list * [LEMONLDAP-568] - Split Test and Handler Apache configuration * [LEMONLDAP-569] - Fix application div height in application list * [LEMONLDAP-572] - Add X-Forwarded-For Header in SOAP request sent by LL::NG::Handler::AuthBasic * [LEMONLDAP-573] - Do not send void HTTP headers * [LEMONLDAP-576] - Hide post form when using Form Replay * [LEMONLDAP-577] - Display "Password changed" in Menu * [LEMONLDAP-549] - Display LL::NG version in Manager * [LEMONLDAP-560] - logging SAML authn response * [LEMONLDAP-578] - Rules to display a skin depending on called URL or IP address * [LEMONLDAP-535] - Force the ip adress when calling the webservice urn:/Lemonldap::NG::Common::CGI::SOAPService * [LEMONLDAP-546] - Form replay: POST request is not sent * [LEMONLDAP-541] - Handler SOAP errors : setAttributes is not an authorizated function * [LEMONLDAP-547] - Update Browseable documentation in case of SAML in use * [LEMONLDAP-565] - Update META.yml files * [LEMONLDAP-581] - Clean Perl dependencies * [LEMONLDAP-582] - Update .pm copyrights lemonldap-ng (1.2.2) stable; urgency=low * [LEMONLDAP-436] - LDAP Search error when authenticating and identifying on two LDAP (AD) with Multi modules * [LEMONLDAP-490] - bad error log when user sends wrong login * [LEMONLDAP-497] - CDA not working * [LEMONLDAP-498] - DBI config storage does not use transactions * [LEMONLDAP-506] - When working with 2 LDAP in Multi Mode, LDAP connexion not reinitialized on second LDAP if user not found in first LDAP * [LEMONLDAP-509] - regex for ldapServer on storing in Manager is too string/wrong * [LEMONLDAP-510] - javascript: $('...').attr('checked')==true never neems to evaluate to TRUE * [LEMONLDAP-515] - Parameter portalRequireOldPassword not checked in DBI * [LEMONLDAP-516] - date popup in notification manager * [LEMONLDAP-517] - typo in cookie name in portal WSDL * [LEMONLDAP-518] - SAML session purge * [LEMONLDAP-519] - SOAP webservice getCookies() should work with Auth Multi * [LEMONLDAP-522] - Cross-domain authentication and http cookies * [LEMONLDAP-523] - RelayState is not sent in SAML logout requests by POST method * [LEMONLDAP-527] - Error with CDA when redirecting to other domain with lemon cookie as a get parameter * [LEMONLDAP-528] - With CDA, even if service url is https, cookie secure flag is not set for the second domain * [LEMONLDAP-529] - getDisplayType not well called in Multi backend * [LEMONLDAP-530] - on androïd device, accept language misunderstood * [LEMONLDAP-491] - Don't import all functions of POSIX * [LEMONLDAP-494] - Lemonldap::NG::Portal::_DBI::hash_password and wrong log type * [LEMONLDAP-501] - All sessions browsed at SAML authentication * [LEMONLDAP-505] - Make portal W3C compliant for html validation * [LEMONLDAP-507] - It's better to "warn" the user when we create a fake jail * [LEMONLDAP-508] - Add armel architecture for debian repository * [LEMONLDAP-514] - Enable notifications by default * [LEMONLDAP-521] - arguments of custom functions * [LEMONLDAP-249] - Manage apply key with the manager * [LEMONLDAP-511] - A new SOAP webservice for deleting notifications * [LEMONLDAP-504] - CLONE - Verify that oldPassword is not empty lemonldap-ng (1.2.1) stable; urgency=low * [LEMONLDAP-479] - LDAP groups are not stored in the session anymore * [LEMONLDAP-481] - option --latest doesn't work in script convertConfig * [LEMONLDAP-486] - X Forwarded For option is not used in login history * [LEMONLDAP-487] - lmMigrateConfFiles2ini do not support continuation lines in ini file * [LEMONLDAP-488] - Quote not escaped when converting old application list XML file * [LEMONLDAP-484] - Use CSS3 standard attribute for shadow and rounded corners * [LEMONLDAP-485] - Template inclusion error when sending an HTML mail * [LEMONLDAP-483] - Remove all defined() on @array or %hash of LL::NG code lemonldap-ng (1.2.0) stable; urgency=low * [LEMONLDAP-251] - Error on form based UserDB modules afeter an non formed based Auth module display the form * [LEMONLDAP-320] - Unprotect rule does not delete headers * [LEMONLDAP-367] - Debian package on a fresh install still need upgrade procedure * [LEMONLDAP-368] - user root can't have lmConfigEditor running because of wrong file permissions * [LEMONLDAP-369] - perl error reported in logs when HTTP header "Accept-Language" not defined * [LEMONLDAP-370] - behaviour of tree menu in manager * [LEMONLDAP-371] - custom function declaration doesn't work through management UI * [LEMONLDAP-373] - Field values lost in manager * [LEMONLDAP-375] - empty query string in redirect url * [LEMONLDAP-376] - wrong authentication mode stored in session with authMulti when SSLRequire set to 0 * [LEMONLDAP-380] - Mail reset session not destroyed when password is changed * [LEMONLDAP-384] - When force password reset form is incomplete, user is redirected to main authentication screen * [LEMONLDAP-390] - Saml Attribute form not reset in Manager * [LEMONLDAP-391] - [Choice] No choice should return PE_FIRSTACCESS and not PE_FORMEMPTY * [LEMONLDAP-392] - Bad URL error when connected to the menu display the login form instead of the menu * [LEMONLDAP-393] - Can't create samlIDPMetaDataExportedAttributes or samlSPMetaDataExportedAttributes * [LEMONLDAP-394] - RelayState is sometimes not transferred by SAML IdP * [LEMONLDAP-397] - [SAML] server error when SOAP SLO request is sent by IDP, and SOAP access is not possible on SP * [LEMONLDAP-399] - invalid syntax of wsdl made by buildPortalWSDL * [LEMONLDAP-401] - SOAP method getMenuApplications lock the session * [LEMONLDAP-405] - No redirect with impact skin * [LEMONLDAP-407] - Missing dependency Crypt::OpenSSL::Bignum * [LEMONLDAP-410] - Manager should reject vhost value like test.example.com:8080 * [LEMONLDAP-411] - LDAP change password as user and extended modify password change are not working * [LEMONLDAP-418] - Typo bug in Debian control file * [LEMONLDAP-420] - Unable to access to http virtualhosts * [LEMONLDAP-425] - Error code: 200, SyntaxError: JSON.parse in Manager * [LEMONLDAP-426] - Unused perl-Apache-AuthNetLDAP dependency in spec file * [LEMONLDAP-427] - _deleteSessionFromLocalStorage should exit directly if no $id given * [LEMONLDAP-429] - links to css and js in html broken if portal url is not a root url * [LEMONLDAP-437] - SAML: redirect binding not working * [LEMONLDAP-441] - Manager do not display a correct error when configuration store fails * [LEMONLDAP-445] - Portal personalized messages are UTF8 doubled encoded * [LEMONLDAP-446] - Server error when a password mail reset session is unavailable and the token is passed to mail.pl * [LEMONLDAP-447] - Bad identifier in grantSession logs * [LEMONLDAP-448] - defined(%hash) is deprecated * [LEMONLDAP-450] - SAML Authn not working with binding HTTP Redirect * [LEMONLDAP-454] - Replace $ip with client IP in forging HTTP headers doesn't work * [LEMONLDAP-455] - Notification error because text is not valid UTF-8 * [LEMONLDAP-464] - LL::NG::Handler::AuthBasic displays login / password in error log * [LEMONLDAP-465] - Error messages with portal SOAP services * [LEMONLDAP-466] - SAML logout not working with js redirection * [LEMONLDAP-467] - SAML redirection seen as CDA requests * [LEMONLDAP-469] - No CAS authentication with CDA enabled * [LEMONLDAP-470] - Zimbra PreAuth Handler syntax error * [LEMONLDAP-472] - Debian package not signed * [LEMONLDAP-473] - SOAP items * [LEMONLDAP-478] - CAS Issuer do not work with CAS v1 * [LEMONLDAP-276] - Parameters to specify sub directories for portal and manager URL * [LEMONLDAP-377] - Add error cases in mail reset by mail management * [LEMONLDAP-382] - Move session update on password change in the main modifyPassword method * [LEMONLDAP-383] - Update local cache when session is updated * [LEMONLDAP-387] - prompt custom messages when ungrant session * [LEMONLDAP-398] - Old value 'ldap' for authentication is not accepted in Manager * [LEMONLDAP-400] - Reload SAML server cache on new configuration * [LEMONLDAP-403] - Alphabetical order in authentication modules select * [LEMONLDAP-404] - Check only path in the URI instead of full URL to match an IssuerDB action path * [LEMONLDAP-408] - Allow CAS to be on other urls than /cas * [LEMONLDAP-421] - Double cookie but single session * [LEMONLDAP-422] - Telling the authenticated user that he will be redirected * [LEMONLDAP-432] - Check conditions in AuthSlave and UserDBSlave * [LEMONLDAP-438] - User is not informed of SAML single logout success * [LEMONLDAP-453] - Add authentication mode in auth log * [LEMONLDAP-458] - Force FollowSymLinks option in Apache configuration * [LEMONLDAP-468] - optimize default structure of notifications table and requests * [LEMONLDAP-474] - textarea instead of text input * [LEMONLDAP-475] - Text items for session display * [LEMONLDAP-476] - Allow execution of portal's and manager's CGI in shell * [LEMONLDAP-236] - SSO with public/auth Website * [LEMONLDAP-249] - Manage apply key with the manager * [LEMONLDAP-342] - Create a "maintenance" rule target to disallow an application * [LEMONLDAP-378] - Display confirmation mail creation date and expiration date in mail reset screens * [LEMONLDAP-379] - Use session attributes in templates * [LEMONLDAP-385] - Option to send a mail when the password is changed * [LEMONLDAP-389] - store and display login history * [LEMONLDAP-396] - Radius authentication module * [LEMONLDAP-416] - Create Auth/UserDB/PasswordDB Demo * [LEMONLDAP-417] - Apache Fitler to add application panel on protected pages * [LEMONLDAP-424] - keyword 'skip' in access rules, to skip access control * [LEMONLDAP-442] - Keep only current version documentation offline * [LEMONLDAP-443] - Option to bypass XSS checks on fields or URL * [LEMONLDAP-449] - Possibility to set custom template parameters * [LEMONLDAP-456] - Allow to set false value of a customized error message to test it in a template * [LEMONLDAP-459] - Translate cookie domain in internat proxy (lmProxy) * [LEMONLDAP-477] - Refuse authentication if 2 entries match the authentication filter * [LEMONLDAP-406] - missing dependency on a basic portal installation * [LEMONLDAP-413] - Verify that oldPassword is not empty * [LEMONLDAP-435] - Move contribs modules to github * [LEMONLDAP-444] - Reorganize files in SVN repository lemonldap-ng (1.1.2) stable; urgency=low * [LEMONLDAP-355] - The "basic($uid,$_password)" extended function makes an error 500 in Apache * [LEMONLDAP-356] - Wrong language when user has already a session and gets redirected * [LEMONLDAP-357] - CPAN tester report: missing dependency for SecureToken Handler * [LEMONLDAP-358] - [SecureToken] Check if cached connection is alive before using it * [LEMONLDAP-359] - [SecureToken] Add an option to raise error if token could not be generated * [LEMONLDAP-360] - Fix Debian dependencies * [LEMONLDAP-361] - [CAS Issuer] check authorization on CAS service * [LEMONLDAP-362] - Portal grant function returns -1 on undefined vhost. It should return 0. * [LEMONLDAP-363] - Lasso Debian dependency need to be updated * [LEMONLDAP-364] - Configure httpOnly option in Manager * [LEMONLDAP-365] - Log sent headers in debug mode lemonldap-ng (1.1.1) stable; urgency=low * [LEMONLDAP-350] - remote SOAP handlers errors on reload * [LEMONLDAP-351] - Cannot get LDAP groups for DN with '\' into it * [LEMONLDAP-352] - Notifications needs to be accepted twice * [LEMONLDAP-353] - Configure notification filename value separator lemonldap-ng (1.1.0) stable; urgency=low * [LEMONLDAP-303] - Form replay filter is not compatible with recent Safe module version * [LEMONLDAP-314] - [Password Reset] Manage special characters in mail subject * [LEMONLDAP-315] - No error is displayed if configuration is not stored * [LEMONLDAP-317] - Errors "setKeyToH... is not a reference" are not errors but debug information * [LEMONLDAP-318] - Do not toggle opacity between tabs * [LEMONLDAP-319] - Custom functions and SafeLib ignored if Safe jail is disabled * [LEMONLDAP-322] - notificationStorageOptions parameter is ignored * [LEMONLDAP-323] - Undefined subroutine Lemonldap::NG::Portal::SharedConf::newNotification * [LEMONLDAP-324] - SAML IDP does no with Google Apps and Lasso 2.3.5 * [LEMONLDAP-325] - Persistent sessions are deleted by portal cron job * [LEMONLDAP-327] - Notifications retrieved from DBI backend are reencoded in UTF8 * [LEMONLDAP-329] - Error " Day '00' out of range 1..31" with DBI notifications getDone subroutine * [LEMONLDAP-330] - Syntax check on managerDn is too restrictive * [LEMONLDAP-331] - Reference is not decoded in File notification backend, in function getAll * [LEMONLDAP-333] - Password policy reset password is not possible if password tab is not allowed * [LEMONLDAP-334] - Some LDAP directories do not return password policy control when bind failed * [LEMONLDAP-335] - MIME subject encoding does not work with every mailer * [LEMONLDAP-337] - Target URL is lost in password policy reset workflow * [LEMONLDAP-338] - Handler::Proxy raise error with POST request without content-length * [LEMONLDAP-344] - purgeCentralCache abort if session cannot be deleted * [LEMONLDAP-15] - Reload configuration tree after configuration save in Manager * [LEMONLDAP-203] - Persistent Storage configuration * [LEMONLDAP-222] - Replace old slavePortal.pl example by AuthSlave+UserDBSlave * [LEMONLDAP-238] - Comment in AuthChoice keys * [LEMONLDAP-295] - Add an option to support old application list objects in Menu * [LEMONLDAP-332] - Configure mailSessionKey in Manager * [LEMONLDAP-336] - Create an option to touch the pwdReset attribute if the password was generated on reset form * [LEMONLDAP-339] - Create a category in Sessions explorer for notifications done * [LEMONLDAP-340] - Store URL origin in session * [LEMONLDAP-349] - Specific error message when password form is empty in mail reset workflow * [LEMONLDAP-288] - Secure Token Handler * [LEMONLDAP-296] - Yubikey authentication module * [LEMONLDAP-299] - Default notification for all users * [LEMONLDAP-300] - [Password Reset] Allow other fields than email * [LEMONLDAP-301] - [Password Reset] Allow to resend a confirmation mail * [LEMONLDAP-302] - [Password Reset] Allow to change the password on the portal * [LEMONLDAP-306] - Add a customheader.tpl and customfooter.tpl in skins * [LEMONLDAP-308] - Remeber user password when password reset is required by LDAP server * [LEMONLDAP-309] - [Password Reset] Option to set password reset request timeout * [LEMONLDAP-310] - Test if mail templates are defined in the skin before using the common ones * [LEMONLDAP-311] - [Password Reset] Option to set HTML mail charset * [LEMONLDAP-312] - [Password Reset] Option to set reply to field * [LEMONLDAP-313] - [Password Reset] Include images and CSS in MIME mail * [LEMONLDAP-326] - Allow to set titles and subtitles in notification messages * [LEMONLDAP-328] - Notification explorer * [LEMONLDAP-341] - Notifications with conditions * [LEMONLDAP-343] - Delete session in local Handler cache in portal logout process * [LEMONLDAP-345] - Open SSO session after successful password reset from ppolicy * [LEMONLDAP-346] - Possibility to configure XSLT used to display notifications * [LEMONLDAP-347] - Possibility to customize messages from the portal * [LEMONLDAP-348] - Possibility to access menu tab with an URL lemonldap-ng (1.0.6) stable; urgency=low * [LEMONLDAP-297] - LDAP attributes are not explicitely requested * [LEMONLDAP-298] - Multi option with # not accepted in Manager * [LEMONLDAP-304] - Cannot use spaces between values of Multi authentication parameter * [LEMONLDAP-305] - Parameters are not overriden in the first Multi module * [LEMONLDAP-307] - Base64 encoded IDs can contain more than one "/", but only the first is escaped lemonldap-ng (1.0.5) stable; urgency=low * [LEMONLDAP-292] - Application menu is not well displayed with multiple users having differents rights * [LEMONLDAP-294] - Subroutines can not be overriden in lemonldap-ng.ini * [LEMONLDAP-293] - Password Manager - Sending Mail lemonldap-ng (1.0.4) stable; urgency=low * [LEMONLDAP-285] - Macro are not always recalculated * [LEMONLDAP-286] - CPAN Testers report * [LEMONLDAP-289] - Dark skin seems broken, but it is just "art" lemonldap-ng (1.0.3) stable; urgency=low * [LEMONLDAP-282] - Class::Inspector is needed to build RPM * [LEMONLDAP-283] - CPAN Testers report * [LEMONLDAP-284] - Applications with 'display auto' are always hidden in Menu lemonldap-ng (1.0.2) stable; urgency=low * [LEMONLDAP-263] - Common::Apache::Session uses wrong serialization algorithm with Postgres * [LEMONLDAP-264] - sessions explorer is not protected by LemonLDAP * [LEMONLDAP-265] - authenticationLevel not honored * [LEMONLDAP-266] - logout_app in rules break the manager * [LEMONLDAP-267] - portalOpenLinkInNewWindow has no effect * [LEMONLDAP-268] - logout_app and logout_app_sso does not work with Lemonldap::NG::Handler::Proxy * [LEMONLDAP-269] - Reset password feature does not work with AuthChoice * [LEMONLDAP-270] - Safe.pm 2.27 restrict the usage of custom functions * [LEMONLDAP-271] - Portal configuration cache not reset after configuration change in Manager * [LEMONLDAP-272] - DBI authentication level not honored * [LEMONLDAP-274] - Redirection URL is not good in Handler::CGI::_uri function * [LEMONLDAP-277] - Debian packaging requires libnet-ldap-perl >=1:0.38 * [LEMONLDAP-278] - Pb in Debian package liblemonldap-ng-conf-perl * [LEMONLDAP-279] - handler-apache2.conf not shipped with Debian * [LEMONLDAP-280] - Empty menu categories are not hidden * [LEMONLDAP-281] - [Debian bug #612719] Package description outdated * [LEMONLDAP-273] - Require jQuery 1.4+ in Debian packaging * [LEMONLDAP-275] - use $ENV{SCRIPT_FILENAME} instead of $ENV{DOCUMENT_ROOT} to referer to different htdocs directories lemonldap-ng (1.0.1) stable; urgency=low * [LEMONLDAP-258] - Portal with $vhost in Handler does not work * [LEMONLDAP-261] - Session explorer does not work with LDAP backend * [LEMONLDAP-262] - Sessions not purged with Apache::Session::File * [LEMONLDAP-263] - Common::Apache::Session uses wrong serialization algorithm with Postgres * [LEMONLDAP-257] - Integrate manager access directly in portal * [LEMONLDAP-240] - Translation framework for doc lemonldap-ng (1.0) stable; urgency=low * [LEMONLDAP-1] - ldapGroupAttributeNameSearch not well Serialized by Manager * [LEMONLDAP-11] - Manager is not working with jQuery 1.4 * [LEMONLDAP-17] - reloadAuthParams function can destroy configuration values * [LEMONLDAP-45] - logout_app_sso not accepted by Manager * [LEMONLDAP-63] - Error when selecting a deleted session in Sessions Explorer * [LEMONLDAP-65] - Cannot set empty values in textarea in Manager * [LEMONLDAP-92] - Cannot change password from menu * [LEMONLDAP-93] - LDAP connection error on high load * [LEMONLDAP-99] - Special UTF-8 characters cannot be sent in HTTP-BASIC * [LEMONLDAP-117] - Invalid use of Safe to access APR::Table module (LL::NG not working on RHEL5.5) * [LEMONLDAP-118] - Cannot store configuration in Postgresql DB * [LEMONLDAP-125] - SAML request is lost in portal user interaction (remove other sessions for example) * [LEMONLDAP-127] - Can not set samlStorageOptions from Manager * [LEMONLDAP-128] - LemonLDAP::NG not compatible with perl-LDAP 0.4001 * [LEMONLDAP-132] - Can't refuse SAML federation * [LEMONLDAP-133] - SAML sessions are displayed as "other sessions" * [LEMONLDAP-134] - Sessions created by AuthSAML are not displayed in sessions explorer * [LEMONLDAP-136] - Metadatas bad displayed in manager * [LEMONLDAP-137] - Portal value is not used to fill default values in Manager * [LEMONLDAP-138] - Password of a private key is not erased when generating a new key without password * [LEMONLDAP-142] - Sessions explorer hides password value stored in sessions datas * [LEMONLDAP-143] - Invalid message with artefact POST from SP to IDP * [LEMONLDAP-144] - Signature verification fail on SP side received artifact message * [LEMONLDAP-145] - Double utf-8 encoding in SOAP requests * [LEMONLDAP-150] - Error code: 200, SyntaxError: JSON.parse with value with spaces * [LEMONLDAP-156] - confirm parameter is not secured * [LEMONLDAP-161] - RelayState value given by SP is HTML reencoded * [LEMONLDAP-167] - Bug with trunk installed from scratch * [LEMONLDAP-169] - IssuerDB CAS : ticket is added 2 times in URL with a service URL containing parameters * [LEMONLDAP-170] - SAML: artifact resolution URL is not in authForce method * [LEMONLDAP-172] - Google Apps SSO not working with Lasso 2.3.2 * [LEMONLDAP-177] - OpenID provider cache login/password information: cannot login after bad password * [LEMONLDAP-179] - OpenID provider does not honor SREG request if only optional attributes * [LEMONLDAP-182] - Pages displayed by confirm return a 500 error under cgi-script * [LEMONLDAP-187] - lmAttrOrMacro test in Manager is not suitable for OpenID SREG attributes * [LEMONLDAP-189] - Cleanup process slows down considerably the Apache server * [LEMONLDAP-190] - Display must display the menu when process() returns an eror but user is authenticated * [LEMONLDAP-198] - Cross domain does not work anymore * [LEMONLDAP-200] - Restore persistent session does not work if whatToTrace is a macro * [LEMONLDAP-201] - OpenID tests are not correctly skipped if no OpenID module * [LEMONLDAP-202] - searchOn no working with SAML and Apache::Session::File * [LEMONLDAP-207] - Confirm stamp is not used everywhere in SAML IDP selection * [LEMONLDAP-214] - Auth choice is not working with several authentication forms * [LEMONLDAP-215] - DBI authentication not working with prepared statements * [LEMONLDAP-216] - getLocalConf called without 2nd argument * [LEMONLDAP-223] - Offline doc css referer to unexistant directory /lib/ * [LEMONLDAP-224] - Manager window size is bigger than screen * [LEMONLDAP-228] - Apache::Session::Browseable searchOn functions broken by new Apache::Session wrapper * [LEMONLDAP-229] - Multi not useable on Manager * [LEMONLDAP-230] - SOAP config backend broken * [LEMONLDAP-232] - Cannot configure several LDAP servers in Manager * [LEMONLDAP-233] - Debian manager broken with jquery-ui 1.8.6 * [LEMONLDAP-235] - Session creation test in Manager does not work with SOAP session backend * [LEMONLDAP-237] - Single logout broken by AuthChoice * [LEMONLDAP-239] - key type of portalDisplayAppList must be boolean * [LEMONLDAP-242] - CAS proxy ticket is always asked with CAS authentication * [LEMONLDAP-16] - Use parameterized statements in DBI to prevent SQL injection * [LEMONLDAP-58] - Catch ENV variables to fill session for all UserDB modules * [LEMONLDAP-97] - Add configuration parameters for private keys passwords * [LEMONLDAP-103] - String encoding in sessions * [LEMONLDAP-120] - Force UTF-8 in File backend * [LEMONLDAP-130] - Create a "reload" vhost independent from test applications * [LEMONLDAP-131] - SAML documentation * [LEMONLDAP-147] - Add an activation parameter for each IssuerDB * [LEMONLDAP-148] - Register SSO session_id in SAML sessions * [LEMONLDAP-149] - Add auhtForce, authFinish and authLogout methods in all authentication modules * [LEMONLDAP-152] - Configure authenticationLevel for authentication backends * [LEMONLDAP-154] - Work on session manager eyecandy * [LEMONLDAP-157] - Warning messages in make test * [LEMONLDAP-160] - Display lib for portal * [LEMONLDAP-168] - Delete local session when logout URL is cached * [LEMONLDAP-178] - Use same Apache conf files for default and Debian install * [LEMONLDAP-180] - Explain messages displayed in error.log (except debug) * [LEMONLDAP-181] - Manager must warn when portal is not in "domain" * [LEMONLDAP-186] - CAS Issuer parameters in Manager * [LEMONLDAP-188] - Use autoloader to reduce handler size * [LEMONLDAP-191] - Use persistent storage for SAML persistent NameID * [LEMONLDAP-194] - Delete AuthLA * [LEMONLDAP-195] - Anti-frame * [LEMONLDAP-196] - Remove .sql files for Conf::DBI * [LEMONLDAP-199] - Require Lasso 2.3.0 for SAML * [LEMONLDAP-204] - abort() instead of die in handlers * [LEMONLDAP-211] - Debian : use packaged jquery-ui * [LEMONLDAP-212] - Use jquery-ui style popup to display errors and upload result * [LEMONLDAP-213] - Network errors are not catched by "error" target oj jQuery.ajax() function * [LEMONLDAP-218] - Upgrade to jquery-ui 1.8 and use dialog for Manager popup * [LEMONLDAP-221] - Allow to set a custom portal skin from Manager * [LEMONLDAP-225] - /favicon.ico is missing for new web site * [LEMONLDAP-234] - Tree style image transparency problem with obsur theme * [LEMONLDAP-5] - Configure use of HTTPS and redirection port per virtual host * [LEMONLDAP-6] - Change 403 error into 302 error for ungranted access * [LEMONLDAP-12] - Zimbra authentication * [LEMONLDAP-18] - [SAML] Common domain cookie support * [LEMONLDAP-19] - Select authentication module on authentication portal * [LEMONLDAP-22] - Session explorer should use the new Manager elements (i18n, templates, etc.) * [LEMONLDAP-25] - Provide authorized application trough SOAP * [LEMONLDAP-27] - OpenID provider * [LEMONLDAP-28] - Read user information from OpenID provider * [LEMONLDAP-29] - Improve application menu configuration * [LEMONLDAP-57] - Local Handler macros * [LEMONLDAP-101] - CAS Provider (IssuerDBCAS) * [LEMONLDAP-102] - IssuerDB contextual selection * [LEMONLDAP-121] - Fake SLO process for standard applications * [LEMONLDAP-123] - Store Lasso Identity Dump in UserDB * [LEMONLDAP-129] - LDAP timeout configuration * [LEMONLDAP-135] - Propagate domain change to all keys * [LEMONLDAP-139] - Use default values for SAML URL if they are not defined in configuration * [LEMONLDAP-141] - Disable timer on IDP list * [LEMONLDAP-146] - Request PGT in AuthCAS * [LEMONLDAP-159] - Manage comment in rule regexp * [LEMONLDAP-174] - Configure auto POST in Manager * [LEMONLDAP-210] - Ajax request in menu to check if session is always available * [LEMONLDAP-4] - Documentation for POST Handler functionnality * [LEMONLDAP-7] - Doxygen Portal/MailReset.pm * [LEMONLDAP-13] - Check that authLogout is well managed in AuthMulti * [LEMONLDAP-30] - [SAML] Unit tests * [LEMONLDAP-162] - Replace help system by offline doc * [LEMONLDAP-171] - Documentation for version 1.0 on new wiki * [LEMONLDAP-192] - Use the new wiki to generate offline documentation * [LEMONLDAP-206] - Upgrade spec file to build RPMs for 1.00 * [LEMONLDAP-209] - Update copyright and URLs in PODs * [LEMONLDAP-231] - Tidy Manager skin directory * [LEMONLDAP-164] - Trusted domains for OpenID * [LEMONLDAP-165] - Manage extensions in is_trusted hook * [LEMONLDAP-166] - Create a storage for agreements lemonldap-ng (1.0rc2) unstable; urgency=low * Debian policy 3.9.1 * [LEMONLDAP-20] - Parameter remoteCookieName is not available in Manager * [LEMONLDAP-21] - Special characters from SAML attribute statement are not well encoded * [LEMONLDAP-41] - Lasso CRITICAL error in AuthSAML logout process * [LEMONLDAP-42] - [SAML][SP] Attrubtes sent trought IDP initiated SSO are not registered into session * [LEMONLDAP-43] - [SAML][SP] IDP should not be read from IDP cookie, but from SAML request or response * [LEMONLDAP-50] - [SAML][SP] OneTimeUse flag should not reduce session duration * [LEMONLDAP-53] - [SAML][IDP] sendLogoutResponseAfterLogoutRequest method does not exists * [LEMONLDAP-54] - Handler parameters (https, port, etc.) are not taken into account if only defined in Manager, and not in ini file * [LEMONLDAP-62] - [SAML] samldate2timestamp is not returning correct timestamp * [LEMONLDAP-64] - SLO error with simpleSAMLphp * [LEMONLDAP-68] - Failed to load signing key for http://urlIDP/saml/metadata * [LEMONLDAP-69] - domain cannot contain "-" in Manager * [LEMONLDAP-71] - samlIDPSSODescriptorArtifactResolutionServiceArtifact wrong binding in Manager * [LEMONLDAP-72] - [SAML] UTF-8 encoded attributes are reencoded * [LEMONLDAP-73] - [SAML] Initial URL is not kept when IDP is choosen in AuthSAML * [LEMONLDAP-74] - [error] Unable to open relaystate session * [LEMONLDAP-75] - SSO HTTP-POST profile not declared in IDP metadata * [LEMONLDAP-76] - [SAML] SOAP SLO denied on IDP * [LEMONLDAP-77] - Error when no SessionNotOnOrAfter value in authn statement * [LEMONLDAP-78] - Request Denied on SOAP SLO request on IDP * [LEMONLDAP-79] - Mandatory attributes are not requested * [LEMONLDAP-81] - SessionNotOnOrAfter should be set explicitely * [LEMONLDAP-82] - CDA always use secured cookie even if requested site is a http one * [LEMONLDAP-100] - Secondary SAML session should be destroyed when primary session is deleted * [LEMONLDAP-105] - Error on SLO request for already closed session * [LEMONLDAP-109] - Do not send AttributeStatement when no attribute should be sent * [LEMONLDAP-112] - Handler/AuthBasic does not use local cache * [LEMONLDAP-113] - Lemonldap::NG is not compatible with the use of a LDAP server using a different encoding than UTF-8 for storing passwords * [LEMONLDAP-114] - Bad usage of Apache::Session::searchOn() on portal * [LEMONLDAP-115] - In info page, when clicking on "Continue", we are not redirected to urldc * [LEMONLDAP-119] - Special UTF-8 characters raise error in metadata * [LEMONLDAP-122] - Secondary SAML session are not deleted on local IDP logout * [LEMONLDAP-124] - Stop info/confirm timer at 0 * [LEMONLDAP-37] - [SAML] Proxy restriction should include all known IDP, and not only target IDP * [LEMONLDAP-44] - [SAML][SP] IDP list when unknown IDP in IDP cookie * [LEMONLDAP-46] - [logout] verify referer into logout process * [LEMONLDAP-47] - [SAML] RequestedAuthnContext should always be translated into authenticationLevel * [LEMONLDAP-51] - [SAML][IDP] SAML sessionIndex value should be a crypted value of LL::NG session_id * [LEMONLDAP-55] - Distribute SympaAutoLogin Handler * [LEMONLDAP-70] - Do not throw error if no SP or no IDP configured * [LEMONLDAP-80] - POST fields should be hidden * [LEMONLDAP-87] - Attribute format selection in Manager * [LEMONLDAP-89] - Security keys in service metadata * [LEMONLDAP-90] - Group IDP and SP options * [LEMONLDAP-91] - SOAP configuration parameter is not needed in SAML * [LEMONLDAP-98] - Add option to disable SAML conditions checks * [LEMONLDAP-104] - Store entities metadata in raw format * [LEMONLDAP-106] - Display OK or ERROR icons on HTTP REDIRECT and HTTP POST SLO iframes * [LEMONLDAP-107] - Manage asynchronous SLO request on closed SSO session (SAML IDP) * [LEMONLDAP-126] - Put SAML parameters in Manager * [LEMONLDAP-2] - [SAML] Attribute authority * [LEMONLDAP-10] - [SAML] Manage certificate in service metadata * [LEMONLDAP-31] - [SAML] Proxy IDP * [LEMONLDAP-32] - [SAML] Manage Artifact methods for SAML messages emission in SP * [LEMONLDAP-33] - [SAML] Check "Destination" attribute * [LEMONLDAP-35] - [SAML] Manage SLO trough SOAP * [LEMONLDAP-36] - [SAML] Check dates and other conditions in SLO requests * [LEMONLDAP-40] - [SAML] Dedicated portal errors code for SAML errors * [LEMONLDAP-49] - [SAML][IDP] Manage encrypted NameID * [LEMONLDAP-52] - IssuerDB activation rule * [LEMONLDAP-56] - [SAML][IDP] SLO trough HTTP-POST * [LEMONLDAP-66] - [SAMl][IDP] Options to check message signatures * [LEMONLDAP-67] - [SAML][IDP] Map NameID Format to local session keys * [LEMONLDAP-86] - Do not parse metadata on each authentication * [LEMONLDAP-88] - Better signature management * [LEMONLDAP-108] - NameID unspecified format should use the default NameID format * [LEMONLDAP-110] - Store SAML token in session * [LEMONLDAP-111] - Build SLO response request with other SLO request status * [LEMONLDAP-116] - Allow metadata edition in Manager * [LEMONLDAP-3] - [SAML] Attribute authority declaration in metadata * [LEMONLDAP-83] - Set NameID in attribute request * [LEMONLDAP-84] - Check format and friendly name of requested attribute * [LEMONLDAP-85] - Check requested attribute values * [LEMONLDAP-96] - Add encryptionkey in Attribute Authority metadata * Upgrade to JQuery-1.4.2 lemonldap-ng (1.0rc1) unstable; urgency=low * Little Debian changes (see 0.9.4.1-2 Debian changelog) * AuthCAS: URL redirection and module load test * Change multiple configuration files into lemonldap-ng.ini * New manager * New conf storage modules : CDBI and RDBI * DBI conf storage module is deprecated * convertConfig and lmMigrateConfFiles2ini tools * childInit() is called only 1 time * Update JQuery to 1.3 and JQueri-UI 1.7.2 (Closes: #314394) * New authentication and userDB modules : - DBI - Proxy - Env (UserDB only) - SAML - OpenID - Twitter * Portal index.pl use lemonldap-ng.ini to get parameters * CSS and Javascript minification capability * Apache configuration splitted into portal/manager/handler * XML Menu is deprecated * LDAP: recursive groups * unprotect target in rules * Force authentication parameter * Store in user session Auth/UserDB/PasswordDB/IssuerDB used module * Use a confirmation token and HTML templates for password reset by mail * SOAP: isAuthorizedUri Web Service * Confirm and Info stages in Portal * Possibility to define a rule to grant session * Configuration parameters for portal customization (skin, ...) * Possibility to set cookie expiration * LDAP: option to modify password as user * Correct bugs in Handler::Proxy * New portal skin: impact -- Xavier Guimard Wed, 24 Mar 2010 23:00:00 +0100 lemonldap-ng (0.9.4.1) unstable; urgency=low * Safe jail update * Many little bugs in Handler/CGI.pm * Apache::Session::LDAP was not usable with session explorer * syslog facility was not taken in account in Common/CGI.pm * require failed in _Multi.pm * doc update * russian debconf translation (Closes: #550552 / bugs.debian.org) -- Xavier Guimard Sun, 11 Oct 2009 09:36:35 +0200 lemonldap-ng (0.9.4) unstable; urgency=low * Bugs : - ldap+tls uri was not working (Closes: #312418) - Session timeout is in seconds and not in minutes in Manager/Help.pm (Closes: #312339) - Missing dependency in Debian package (Closes: #521959 / bugs.debian.org) * Logs : - CGI's log subroutine : now if a CGI runs under ModPerl::Registry, it stores it's log using Apache2::Log - handler logs written in PerlLogHandler * SOAP : - New SOAP architecture : the portal serves now all webservices and the security is based on Apache system (different locations) - WSDL generation * New features : - LDAP backend for configuration and sessions storage - portal can be a Perl expression in handlers - POST requests generation in handler (used to post login/password in non compatible applications) - Sympa auto login handler - New auth and userDB modules for the portal : Multi, Remote, Null (for UserDB only) - New module system for passwords - Notification system - Double session mechanism (1 secured and the other not) - New fonctions for rules (stored in lemonldap-ng-common/lib/Lemonldap/NG/Common/Safelib.pm) : * checkLogonHours * checkDate * Other : - Pre-compilation in Apache's configuration files - Cross-domain now included in core - handler AuthBasic now uses SOAP -- Xavier Guimard Mon, 29 Jun 2009 10:28:09 +0200 lemonldap-ng (0.9.3.4) unstable; urgency=low * Security bug fix (macros and groups can be evaluated for an other user in multi-thread environment). Closes: #312627 * XSS filter can now accept URL with a port. Closes: #312625 -- Xavier Guimard Thu, 05 Feb 2009 16:12:55 +0100 lemonldap-ng (0.9.3.3) unstable; urgency=low * ldap+tls uri was not working (Closes: #312418) * Session timeout is in seconds and not in minutes in Manager/Help.pm (Closes: #312339) -- Xavier Guimard Thu, 22 Jan 2009 11:00:10 +0100 lemonldap-ng (0.9.3.2) unstable; urgency=low * Debian install failed (Closes: #510562, Closes: #510563 / bugs.debian.org) -- Xavier Guimard Sat, 03 Jan 2009 09:47:21 +0100 lemonldap-ng (0.9.3.1) unstable; urgency=low * Bug in Debian build -- Xavier Guimard Wed, 31 Dec 2008 14:16:06 +0100 lemonldap-ng (0.9.3) unstable; urgency=low [ Security ] * XSS protection [ Clement Oudot ] * New menu and skin (pastel). Menu calculates rights before displaying URL [ Xavier Guimard ] * Authentication and UserDB separation * New session explorer system * Backport of debian storage.conf file to normal installation * Errors are now displayed in the browser for portal and manager * Custom functions for rules, macros, headers and groups * Manager protection * New configuration access with local cache system * AuthBasic handler * MRTG scripts to read LmNG status * UserDB mechanism : LDAP is not required now * Portal SOAP functions -- Xavier Guimard Wed, 31 Dec 2008 11:55:57 +0100 lemonldap-ng (0.9.2.2) unstable; urgency=low * Bug in default rule (Closes: #310938) -- Xavier Guimard Mon, 25 Aug 2008 22:08:58 +0200 lemonldap-ng (0.9.2.1) unstable; urgency=low * New documentation page on advanced access rules -- Xavier Guimard Fri, 04 Jul 2008 11:54:57 +0200 lemonldap-ng (0.9.2) unstable; urgency=low * New css in manager * cleaning Handler code * Status system for Lemonldap::NG::Handler and for the portal * Debian Czech translation for debconf (Closes: #483301 / bugs.debian.org) * Debian Swedish translation for debconf (Closes: #487713 / bugs.debian.org) * Romanian translation for portal * Distinct Liberty-Alliance SP installation * Password policy included now * Bugs in redirections * Perl 5.10 check-in * More tests in "test" target * Bug in purgeCentralCache (DBI only): datas where never purged -- Xavier Guimard Tue, 24 Jun 2008 15:07:04 +0200 lemonldap-ng (0.9.1) unstable; urgency=low * logout bug : logout_sso target was not running (Closes: #308856) * javascript update : the manager was not running with MSIE7 (Closes: #308775) * Debian corrections issued from lintian (full) * 2 Net::LDAP password policy controls in the portal: - account locked - password expired -- Xavier Guimard Mon, 07 Apr 2008 11:13:06 +0200 lemonldap-ng (0.9) unstable; urgency=low * Liberty Alliance module issued of the FederID project is now included. -- Xavier Guimard Mon, 25 Feb 2008 15:05:08 +0100 lemonldap-ng (0.8.3.2) unstable; urgency=low * purgeCentralCache was not correctly installed in Debian (Closes: #461572 / bugs.debian.org) * debconf translation for german and portuguese (Closes: #451820 and #462807 bugs.debian.org) * HTML documentation update * Option +ExecCGI was missing in lemonldap-ng-handler/example/lmH-apache2.conf (Closes: #307891) * Local overload was not taken in account in handlers * Sessions could not be stored in SOAPServer (Closes: #308181) * Attributes could not be deleted in SOAP session client (Closes: #308214) * Sessions timeout can now be managed by the Manager * AuthSSL doesn't work without SSLvar parameter -- Xavier Guimard Fri, 08 Feb 2008 17:27:15 +0100 lemonldap-ng (0.8.3.1) unstable; urgency=low * New feature: LDAP groups are now available in $groups -- Xavier Guimard Wed, 07 Nov 2007 16:41:07 +0100 lemonldap-ng (0.8.3) unstable; urgency=high * Syntax errors in configuration are now displayed * Security fix: authentication could be replayed with another uid * Debian package uses po-debconf * TLS is now supported in LDAP connections (thanks to Baptiste Grenier) * New logout system: logout urls can be now intercepted in Manager * Documentation -- Xavier Guimard Fri, 07 Sep 2007 07:14:35 +0200 lemonldap-ng (0.8.2.4) unstable; urgency=low * Bug in manager javascript. -- Xavier Guimard Tue, 19 Jun 2007 22:25:10 +0200 lemonldap-ng (0.8.2.3) unstable; urgency=low * Change configuration storage format (Storable bug). Closes: #307173/objectweb.org * CDA little bug correction * Documentation update -- Xavier Guimard Wed, 13 Jun 2007 15:33:56 +0200 lemonldap-ng (0.8.2.2) unstable; urgency=low * Debian packages modifications due to Lintian control. * New Debian package: lemonldap-ng-doc * Little bug correction in Portal/CDA.pm * Bug between Handler dependencies and Debian organization: Lemonldap::NG::Handler::SharedConf must not depend from Lemonldap::NG::Manager but Lemonldap::NG::Manager::Conf -- Xavier Guimard Tue, 01 June 2007 07:18:43 +0200 lemonldap-ng (0.8.2.1) unstable; urgency=low * More documentation * Virtual host names control * Portal can now use more than one LDAP server -- Xavier Guimard Mon, 14 May 2007 07:14:10 +0200 lemonldap-ng (0.8.2) unstable; urgency=low * Little bug fix if whatToTrace parameter is not defined and display it in Manager interface * New: port is now checked in portal redirection * Different configurations can now be used on the same server at the same time * Help in english * New debian structure: lemonldap-ng is splitted in 5 packages, default configuration file has moved to /var/lib/lemonldap-ng/conf/ and first configuration file is managed by debconf * Buttons to manage configurations in manager (next, previous, last, delete). Closes: #306566 / forge.lemonldap.org. * SOAP: HTTP basic authentication and little bug correction in 'sessions' mode -- Xavier Guimard Mon, 07 May 2007 19:06:52 +0200 lemonldap-ng (0.8.1.1) unstable; urgency=low * Little bug fix in test -- Xavier Guimard Fri, 20 Apr 2007 08:57:40 +0200 lemonldap-ng (0.8.1) unstable; urgency=low * New features : - Logout system - Configuration check before saving in Manager -- Xavier Guimard Sun, 15 Apr 2007 19:18:29 +0200 lemonldap-ng (0.8.0.7) unstable; urgency=low * Bug fix in manager javascript (Closes: #306776 ?) * Display bug fix in manager -- Xavier Guimard Sun, 15 Apr 2007 13:21:43 +0200 lemonldap-ng (0.8.0.6) unstable; urgency=low * Little bug fix in unprotect function * Bug fix in authentication scheme different than default -- Xavier Guimard Thu, 12 Apr 2007 07:03:51 +0200 lemonldap-ng (0.8.0.5) unstable; urgency=low * i18n bug: Lemonldap::NG works does not fall in english but creates a bug -- Xavier Guimard Wed, 28 Mar 2007 21:26:16 +0200 lemonldap-ng (0.8.0.4) unstable; urgency=low * Multi-valued attributes in HTTP headers (Closes: #306792 / forge.objectweb.org) * Warning in Manager/Conf.pm: the same type of storage has to be used for all Lemonldap::NG parts in a same server. * Apache-1.3 configuration reload (Closes: #306761 / forge.objectweb.org) -- Xavier Guimard Thu, 22 Mar 2007 22:42:23 +0100 lemonldap-ng (0.8.0.3) unstable; urgency=low * New feature in Manager : "Delete VHost" button (Closes: #306761) * Typo correction in Makefile : (Closes: #306775) * Correction of build-depends : (Closes: #306773) * Bug correction : existingSessions was not called in Portal.pm -- Xavier Guimard Tue, 13 Mar 2007 07:55:42 +0100 lemonldap-ng (0.8.0.2) unstable; urgency=low * Bug correction: lock doesn't work with File.pm (Closes: #306760 / forge.objectweb.org) -- Xavier Guimard Sun, 11 Mar 2007 21:08:38 +0100 lemonldap-ng (0.8.0.1) unstable; urgency=medium * Closes: #306756 / forge.objectweb.org -- Xavier Guimard Fri, 10 Mar 2007 08:49:01 +0100 lemonldap-ng (0.8) unstable; urgency=low * Release 0.8: - corrects differents little bugs issued from test in real life. - on line documentation in english -- Xavier Guimard Fri, 9 Mar 2007 20:29:01 +0100 lemonldap-ng (0.7b12) unstable; urgency=low * New features: - session access via SOAP - authentication via CAS - 'apply changes' button in Manager used to reload configuration in handlers (by calling reload sub via HTTP) (Closes: #306565 / forge.objectweb.org) - i18n module in portal (for displaying errors) - lock in DBI configuration system (NOT YET TESTED) -- Xavier Guimard Sun, 4 Mar 2007 15:50:38 +0100 lemonldap-ng (0.7b11) unstable; urgency=low * New features: - Cross Domain Authentication - SOAP configuration access - READMEs and documentation update -- Xavier Guimard Tue, 27 Feb 2007 15:01:09 +0100 lemonldap-ng (0.7b10) unstable; urgency=low * Corrections in Manager issued from the first test in real life: - Close #306573 / forge.objectweb.org - Close #306574 / forge.objectweb.org -- Xavier Guimard Wed, 17 Jan 2007 20:57:33 +0100 lemonldap-ng (0.7b9) unstable; urgency=low * Internationalization of javascripts (close #306564 / forge.objectweb.org) * Help in "General Parameters" -- Xavier Guimard Sun, 14 Jan 2007 21:50:39 +0100 lemonldap-ng (0.7b8) unstable; urgency=low * Correction of the use of Safe in portal: &share doesn't work with a variable declared with my. * New system in the configuration: 'macro' section can be used to add custom exported variables. So configuration is more simple in heavy case. -- Xavier Guimard Sat, 13 Jan 2007 20:19:19 +0100 lemonldap-ng (0.7b7) unstable; urgency=low * Correction of a bug in internal redirections: now internal redirections are not examined: for example,http://test.example.com/ is internaly redirected to /index.pl, but only the first request (/) is tested. * Help in french -- Xavier Guimard Fri, 5 Jan 2007 18:22:32 +0100 lemonldap-ng (0.7b6) unstable; urgency=low * Help system skeleton -- Xavier Guimard Thu, 4 Jan 2007 09:04:05 +0100 lemonldap-ng (0.7b5) unstable; urgency=low * Localization in Manager interface (only fr and en) -- Xavier Guimard Sun, 31 Dec 2006 16:39:06 +0100 lemonldap-ng (0.7b4) unstable; urgency=low * Safe jail runs now * example runs now -- Xavier Guimard Sun, 31 Dec 2006 14:00:08 +0100 lemonldap-ng (0.7b3) unstable; urgency=low * Replacement of eval by Safe for external expressions -- Xavier Guimard Sat, 30 Dec 2006 22:23:22 +0100 lemonldap-ng (0.7b) unstable; urgency=low * Corrections in example * Example installation in debian * Revision in documentation -- Xavier Guimard Sun, 17 Dec 2006 18:37:39 +0100 lemonldap-ng (0.6) unstable; urgency=low * Initial release built starting from the three modules of the CPAN. -- Xavier Guimard Sun, 17 Dec 2006 17:46:47 +0100 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/000077500000000000000000000000001325274564300234065ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/NEWS000066400000000000000000000070271325274564300241130ustar00rootroot00000000000000lemonldap-ng (1.9.3-1) unstable; urgency=medium liblemonldap-ng-handler-perl package has been split into: - lemonldap-ng-handler that provides web server configuration - liblemonldap-ng-handler-perl that provides Perl libraries only -- Xavier Guimard Sat, 17 May 2016 22:25:43 +0200 lemonldap-ng (1.9.0-1) unstable; urgency=low 1) Configuration and sessions storage From now, Lemonldap::NG uses JSON serialization to store configuration and sessions instead of Storable::nfreeze Perl function. This permits one to have heterogenous servers connected to the same LLNG organization (32/64 bits or different Perl versions). Old format still works but: * configuration backends: new format is applied at first configuration save, * sessions storages: new format is applied for each new session or when updating an existing session. You can force LemonLDAP::NG to keep the old serialization method by setting useStorable to 1 in sessions backend options if you have some custom hooks. Note that this behaviour only affects modules Apache::Session::File, SQL database and Apache::Session::LDAP If you have more than one server and don't want to stop the SSO service, start upgrading in the following order: * servers that have only handlers; * portal servers (all together if your load balancer doesn't keep state by user or client IP and if users use the menu); * manager server 2) Manage Ajax requests when sessions expires To request for authentication, handlers sent a 302 HTTP code even if request was an Ajax one. From now, after redirection, portal will send a 401 code with a WWW-Authenticate header containing "SSO portal-URL". This is a little HTTP protocol hook created because browsers follow redirection transparently. If you want to keep old behaviour, set noAjaxHook to 1 (in General Parameters -> Advanced -> Handler redirections -> Keep redirections for Ajax). 3) New "Multi" authentication scheme The Multi backend configuration has changed. Now the stacks are defined in separate attributes: * multiAuthStack * multiUserDBStack So an old configuration like this: authentication = Multi LDAP;DBI userDB = Multi LDAP;DBI Must be replaced by: authentication = Multi userDB = Multi multiAuthStack = LDAP;DBI multiUserDBStack = LDAP;DBI 4) Form replay Management of form replay has been rewritten. If you uses this experimental feature, you must edit your configuration and rewrite it. -- Xavier Guimard Thu, 21 Jan 2016 17:13:07 +0100 lemonldap-ng (1.4.6-1) unstable; urgency=medium Handler files "My::Package" are no longer installed by default as a module "Lemonldap::NG::Handler" generic is now available. It is therefore necessary either to modify Apache configuration files to use "Lemonldap::NG::Handler" or create your own Perl modules using the provided examples files. -- Xavier Guimard Mon, 29 Dec 2014 17:10:00 +0100 lemonldap-ng (1.2.2-1) unstable; urgency=low Examples files (Apache configuration and default handler files) are now not installed in /var/lib/lemonldap-ng/handler but available as examples files Since 1.2.2, LemonLDAP::NG uses 'Demo' authentication backend by default and the manager is protected by default by LemonLDAP::NG. So for an unconfigured installation, you have to use dwho account to access to the manager (password dwho) -- Xavier Guimard Thu, 29 Nov 2012 06:22:45 +0100 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/changelog000066400000000000000000000161521325274564300252650ustar00rootroot00000000000000lemonldap-ng (1.9.16-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Fri, 16 Mar 2018 12:00:00 +0100 lemonldap-ng (1.9.15-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Tue, 23 Jan 2018 12:00:00 +0100 lemonldap-ng (1.9.14-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Fri, 24 Nov 2017 12:00:00 +0100 lemonldap-ng (1.9.13-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Fri, 29 Sep 2017 12:00:00 +0100 lemonldap-ng (1.9.12-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Tue, 12 Sep 2017 12:00:00 +0100 lemonldap-ng (1.9.11-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Fri, 01 Sep 2017 12:00:00 +0100 lemonldap-ng (1.9.10-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Fri, 19 May 2017 12:00:00 +0100 lemonldap-ng (1.9.9-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Thu, 16 Mar 2017 18:28:03 +0100 lemonldap-ng (1.9.8-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Thu, 02 Mar 2017 12:00:00 +0200 lemonldap-ng (1.9.7-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Mon, 12 Dec 2016 12:11:31 +0200 lemonldap-ng (1.9.6-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clement OUDOT Fri, 14 Oct 2016 12:11:31 +0200 lemonldap-ng (1.4.11-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Mon, 10 Oct 2016 12:00:00 +0200 lemonldap-ng (1.9.5-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Wed, 13 Jul 2016 14:33:46 +0200 lemonldap-ng (1.4.10-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Wed, 13 Jul 2016 12:00:00 +0200 lemonldap-ng (1.9.4-1) unstable; urgency=high * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Xavier Guimard Tue, 14 Jun 2016 15:41:55 +0200 lemonldap-ng (1.9.3-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Tue, 07 Jun 2016 17:33:45 +0200 lemonldap-ng (1.4.9-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Fri, 03 Jun 2016 15:13:12 +0200 lemonldap-ng (1.9.2-1) unstable; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Sun, 01 May 2016 21:16:30 +0200 lemonldap-ng (1.4.8-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Wed, 27 Apr 2016 15:47:12 +0100 lemonldap-ng (1.9.1-1) wily; urgency=medium * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Tue, 05 Apr 2016 17:50:19 +0200 lemonldap-ng (1.4.7-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Thu, 17 Mar 2016 12:00:00 +0100 lemonldap-ng (1.9.0-1) wily; urgency=low [ Clément Oudot ] * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément OUDOT Wed, 02 Mar 2016 10:34:19 +0100 lemonldap-ng (1.4.6-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Wed, 09 Oct 2015 12:00:00 +0100 lemonldap-ng (1.4.5-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Mon, 11 May 2015 12:00:00 +0100 lemonldap-ng (1.4.4-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Wed, 15 Apr 2015 12:00:00 +0100 lemonldap-ng (1.4.3-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Thu, 18 Dec 2014 12:00:00 +0100 lemonldap-ng (1.4.2-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Wed, 5 Nov 2014 12:00:00 +0100 lemonldap-ng (1.4.1-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Fri, 25 Jul 2014 12:00:00 +0100 lemonldap-ng (1.4.0-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Sun, 03 Nov 2013 06:59:37 +0100 lemonldap-ng (1.3.3-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Fri, 07 Mar 2014 18:50:11 +0200 lemonldap-ng (1.3.2-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Thu, 23 Jan 2014 18:50:11 +0200 lemonldap-ng (1.3.1-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Mon, 11 Nov 2013 18:50:11 +0200 lemonldap-ng (1.3.0-1) unstable; urgency=low * New release. See changes on our website: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng -- Clément Oudot Sun, 24 Jun 2012 18:50:11 +0200 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/compat000066400000000000000000000000021325274564300246040ustar00rootroot000000000000009 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/control000066400000000000000000000316641325274564300250230ustar00rootroot00000000000000Source: lemonldap-ng Maintainer: Debian Perl Group Uploaders: Xavier Guimard Section: perl Priority: optional Rules-Requires-Root: binary-targets Build-Depends: debhelper (>= 9.20160709), po-debconf Build-Depends-Indep: libapache-session-perl, libauthcas-perl, libauthen-captcha-perl, libauthen-captcha-perl (>= 1.024-2) | libpng16-16 (<< 1.6.32-1) | libpng12-0, libcache-cache-perl, libcgi-pm-perl, libclone-perl, libconfig-inifiles-perl, libconvert-pem-perl, libcrypt-openssl-rsa-perl, libcrypt-openssl-x509-perl, libcrypt-rijndael-perl, libdbd-sqlite3-perl, libdbi-perl, libdigest-hmac-perl, libglib-perl, libhtml-template-perl, libio-string-perl, libjson-perl, liblasso-perl, libmime-lite-perl, libmoose-perl, libmouse-perl, libnet-cidr-lite-perl, libnet-ldap-perl, libnet-openid-consumer-perl, libnet-openid-server-perl, libregexp-assemble-perl, libregexp-common-perl, libsoap-lite-perl, libstring-random-perl, libtest-mockobject-perl, libtest-pod-perl, libunicode-string-perl, liburi-perl, libhttp-message-perl, libwww-perl, libxml-libxml-perl, libxml-libxslt-perl, libxml-simple-perl, perl Standards-Version: 4.1.3 Vcs-Browser: https://anonscm.debian.org/cgit/pkg-perl/packages/lemonldap-ng.git Vcs-Git: https://anonscm.debian.org/git/pkg-perl/packages/lemonldap-ng.git Homepage: https://lemonldap-ng.org/ Package: lemonldap-ng Architecture: all Depends: ${misc:Depends}, lemonldap-ng-handler (= ${binary:Version}), liblemonldap-ng-manager-perl (= ${binary:Version}), liblemonldap-ng-portal-perl (= ${binary:Version}) Provides: openid-connect-provider, openid-connect-relying-party, saml-identity-provider, saml-service-provider Description: OpenID-Connect, CAS and SAML compatible Web-SSO system Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . This package is a metapackage that install handler, manager and portal. Package: lemonldap-ng-doc Architecture: all Section: doc Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends} Description: Lemonldap::NG Web-SSO system documentation Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . This package contains html documentation. Package: lemonldap-ng-fr-doc Architecture: all Section: doc Depends: ${misc:Depends}, lemonldap-ng-doc (= ${binary:Version}) Description: French documentation of Lemonldap::NG Web-SSO system Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . This package contains French html documentation. Package: lemonldap-ng-fastcgi-server Architecture: all Depends: ${misc:Depends}, ${perl:Depends}, lsb-base, libfcgi-procmanager-perl, liblemonldap-ng-handler-perl (= ${binary:Version}), libplack-perl Recommends: libhttp-parser-xs-perl, nginx-extras | nginx Section: web Description: Lemonldap::NG FastCGI server Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . Lemonldap::NG FastCGI server provides a Nginx auth_request server. Package: lemonldap-ng-handler Architecture: all Depends: ${misc:Depends}, lemonldap-ng-fastcgi-server (= ${binary:Version}) | libapache2-mod-perl2, liblemonldap-ng-handler-perl (= ${binary:Version}) Breaks: liblemonldap-ng-handler-perl (<< 1.9.1-2~) Replaces: liblemonldap-ng-handler-perl (<< 1.9.1-2~) Description: Lemonldap::NG handler part Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . This package provides configuration files for Apache and Nginx used to protect web areas. Package: liblemonldap-ng-handler-perl Architecture: all Depends: ${misc:Depends}, ${perl:Depends}, liblemonldap-ng-common-perl (= ${binary:Version}), libmoose-perl, liburi-perl, libwww-perl Recommends: libhttp-message-perl, liblwp-protocol-https-perl Suggests: libcache-memcached-perl, libdigest-hmac-perl, liblemonldap-ng-portal-perl, libsoap-lite-perl Description: Lemonldap::NG handler common libraries Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . Lemonldap::NG::Handler provides Perl libraries used by web server handlers. Package: liblemonldap-ng-common-perl Architecture: all Depends: ${misc:Depends}, ${perl:Depends}, debconf, libapache-session-perl, libcache-cache-perl, libconfig-inifiles-perl, libcrypt-openssl-rsa-perl, libcrypt-openssl-x509-perl, libcrypt-rijndael-perl, libdbi-perl, libjson-perl, libmoose-perl, libnet-cidr-lite-perl, libsoap-lite-perl Recommends: libapache-session-browseable-perl, libhtml-template-perl, libhttp-message-perl, libmouse-perl, libnet-ldap-perl, liblwp-protocol-https-perl, libxml-libxml-perl, libxml-simple-perl Conflicts: liblemonldap-ng-cli-perl Description: Lemonldap::NG common files Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . Lemonldap::NG::Common contains common files used by other Lemonldap::NG modules. Package: liblemonldap-ng-manager-perl Architecture: all Depends: ${misc:Depends}, ${perl:Depends}, libconvert-pem-perl, libcrypt-openssl-rsa-perl, libhtml-template-perl, libjson-perl, liblemonldap-ng-common-perl (= ${binary:Version}), liblemonldap-ng-handler-perl (= ${binary:Version}), libmoose-perl, libplack-perl, libwww-perl Recommends: lemonldap-ng-doc (= ${binary:Version}), libapache-session-browseable-perl, lemonldap-ng-fastcgi-server (= ${binary:Version}) | libapache2-mod-fcgid | libapache2-mod-fastcgi, libjson-xs-perl, libhttp-parser-xs-perl, liblwp-protocol-https-perl, libxml-libxml-perl, libxml-libxslt-perl, libxml-simple-perl Suggests: libclone-perl Pre-Depends: debconf Description: Lemonldap::NG manager part Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . Lemonldap::NG::Manager provides the administration interface. Package: liblemonldap-ng-portal-perl Architecture: all Depends: ${misc:Depends}, ${perl:Depends}, nginx | apache2 | httpd-cgi, libcgi-pm-perl, libclone-perl, libhtml-template-perl, liblemonldap-ng-common-perl (= ${binary:Version}), libmime-lite-perl, libnet-ldap-perl, libregexp-assemble-perl, libstring-random-perl, libunicode-string-perl, liburi-perl Recommends: libwww-perl Suggests: libauthcas-perl, libauthen-captcha-perl, libauthen-captcha-perl (>= 1.024-2) | libpng16-16 (<< 1.6.32-1) | libpng12-0, libdbi-perl, libglib-perl, libgssapi-perl, liblasso-perl, liblemonldap-ng-handler-perl (= ${binary:Version}), libnet-facebook-oauth2-perl, libnet-openid-consumer-perl, libnet-openid-server-perl, libnet-oauth-perl, libsoap-lite-perl, libweb-id-perl, libhttp-message-perl, liblwp-protocol-https-perl, libxml-libxml-perl, libxml-libxslt-perl, libxml-simple-perl, slapd Pre-Depends: debconf Description: Lemonldap::NG authentication portal part Lemonldap::NG is a complete Web-SSO system that can run with reverse-proxies or directly on application webservers. It can be used in conjunction with OpenID-Connect, CAS and SAML systems as identity or service provider. It can also be used as proxy between those federation systems. . It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection. Authorization are built by associating a regular expression and a rule. Regular expression is applied on the requested URL and the rule calculates if the user is authorized. . Lemonldap::NG::Portal provides the authentication portal. lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/copyright000066400000000000000000001373151325274564300253530ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: lemonldap-ng Upstream-Contact: Xavier Guimard Source: http://forge.objectweb.org/project/showfiles.php?group_id=274 Files: * Copyright: 2005-2017, Xavier Guimard 2006-2017, Clement Oudot 2008-2011, Thomas Chemineau 2012-2015, François-Xavier Deltombe 2012-2013, Sandro Cazzaniga 2012-2015, David Coutadeur 2006-2015, LINAGORA License: GPL-2+ Files: *.js Copyright: 2005-2017, Xavier Guimard 2006-2017, Clement Oudot 2008-2012, Thomas Chemineau License: GPL-2+ Files: lemonldap-ng-portal/example/skins/common/js/portal.js Copyright: 2005-2017, Xavier Guimard 2006-2017, Clement Oudot 2008-2012, Thomas Chemineau License: GPL-2+ Comment: a little part of it comes from JQuery-UI examples (http://snipplr.com/view/29434/) Files: lemonldap-ng-portal/example/skins/common/Apache.png Copyright: Apache Software Foundation (ASF) License: Apache-2.0 Files: lemonldap-ng-portal/example/skins/common/CAS.png Copyright: Jasig License: Apache-2.0 Files: lemonldap-ng-portal/example/skins/common/BrowserID.png Copyright: 2013, Xavier Guimard License: CC-3 Comment: created using images of YellowIcon and Innerer Schweinehund (http://findicons.com/icon/168665/firefox?id=292029 and http://commons.wikimedia.org/wiki/File:Mail-closed.svg) Files: lemonldap-ng-portal/example/skins/common/Google.png lemonldap-ng-portal/example/skins/common/Facebook.png Copyright: http://tempest.deviantart.com/ License: CC-3 Comment: Downloaded from http://www.iconspedia.com/ Files: lemonldap-ng-portal/example/skins/common/OpenIDConnect.png Copyright: http://www.customicondesign.com License: CC-BY-NC-ND-3.0 Comment: Downloaded from http://www.iconspedia.com/ Files: lemonldap-ng-portal/example/skins/common/Twitter.png Copyright: Paul Schulerr, http://schulerr.deviantart.com License: CC-3 Comment: Downloaded from http://www.iconspedia.com/ Files: lemonldap-ng-portal/example/skins/common/WebID.png Copyright: unknown License: W3C Comment: downloaded from http://www.w3.org/2005/Incubator/webid/wiki/Main_Page#Other_resources . Author is unknown and license may be W3C or public-domain Files: lemonldap-ng-portal/example/skins/common/LinkedIn.png Copyright: Public Domain License: Simple-Geo Comment: downloaded from https://commons.wikimedia.org/wiki/File:LinkedIn_logo_initials.png Files: lemonldap-ng-portal/example/skins/common/backgrounds/* Copyright: Various artists License: CC-BY-NC-ND-3.0 or GFDL-1.3 Comment: dowloaded from http://commons.wikimedia.org Files: jquery*.js jquery*.css Copyright: 2010, The jQuery project and the jQuery UI team License: GPL-2 or Expat Files: *jquery.cookie.js Copyright: 2010, Klaus Hartl (stilbuero.de) License: GPL-3+ Files: *jquery.base64.js Copyright: Muhammad Hussein Fattahizadeh License: GPL-2+ Files: lemonldap-ng-portal/example/skins/common/js/jquery-ui-* Copyright: 2008 Paul Bakaus (ui.jquery.com) Brandon Aaron David Bolter Rich Caloggero Chi Cheng (cloudream@gmail.com) Colin Clark (http://colin.atrc.utoronto.ca/) Michelle D'Souza Aaron Eisenberger (aaronchi@gmail.com) Ariel Flesler Bohdan Ganicky Scott González Marc Grabanski (m@marcgrabanski.com) Klaus Hartl (stilbuero.de) Scott Jehl Cody Lindley Eduardo Lundgren (eduardolundgren@gmail.com) Todd Parker John Resig Patty Toland Ca-Phun Ung (yelotofu.com) Keith Wood (kbwood@virginbroadband.com.au) Maggie Costello Wachs Richard D. Worth (rdworth.org) Jörn Zaefferer (bassistance.de) License: GPL-2 or Expat Files: lemonldap-ng-manager/site/static/bwr/angular/* lemonldap-ng-manager/site/static/bwr/angular-cookies/* lemonldap-ng-manager/site/static/bwr/angular-animate/* Copyright: 2010-2015, Google, Inc. http://angularjs.org License: Expat Files: lemonldap-ng-manager/site/static/bwr/angular-bootstrap/* Copyright: 2012-2016, the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112 License: Expat Files: lemonldap-ng-manager/site/static/bwr/angular-ui-tree/* Copyright: 2014, unspecified License: Expat Files: lemonldap-ng-manager/site/static/bwr/file-saver.js/* Copyright: 2015, Eli Grey http://eligrey.com License: Expat Files: lemonldap-ng-portal/example/skins/common/apps/* doc/media/icons/* Copyright: 2006-2007 Everaldo Coelho, Crystal Project License: LGPL-3 Files: doc/media/icons/flags/* Copyright: Mark James License: CC-3 Files: scripts/doxygenfilter scripts/DoxyGen/Filter.pm scripts/DoxyGen/SQLFilter.pm Copyright: 2002, Bart Schuller 2006, Phinex Informatik AG License: Artistic or GPL-1+ Files: doc/lib/tpl/default/images/* doc/lib/images/* doc/lib/plugins/note/images/* doc/media/wiki/dokuwiki-128.png Copyright: 2004-2012 Andreas Gohr and the DokuWiki Community License: GPL-2 Files: doc/media/documentation/google* Copyright: 2007-2011, Andreas Åkre Solberg 2007-2011, Olav Morken License: LGPL-2 Files: doc/media/documentation/lasso.png Copyright: 2004, Entr'ouvert 2004, Florent Monnier License: GPL-2+ Files: lemonldap-ng-portal/example/skins/bootstrap/*/bootstrap* lemonldap-ng-portal/example/skins/bootstrap/fonts/* Copyright: 2013, Twitter Inc. License: Apache-2.0 Comment: version 3.0.3 of bootstrap is released under Apache-2 license earlier versions will be released under Expat license Files: lemonldap-ng-manager/site/static/bwr/bootstrap/* Copyright: 2011-2015, Twitter Inc. License: Expat Files: lemonldap-ng-manager/site/static/bwr/es5-shim/* Copyright: 2009-2015, Kristopher Michael Kowal and contributors License: Expat Files: debian/* Copyright: 2005-2016, Xavier Guimard License: GPL-2+ Files: lemonldap-ng-portal/example/skins/common/js/sha256*.js lemonldap-ng-portal/example/skins/common/js/enc-base64*.js Copyright: 2009-2013 Jeff Mott 2013-2016 Evan Vosberg License: Expat License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the complete text of the Apache License 2.0 can be found in "/usr/share/common-licenses/Apache-2.0" License: Artistic This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License, which comes with Perl. . On Debian systems, the complete text of the Artistic License can be found in `/usr/share/common-licenses/Artistic'. License: CC-3 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; to Distribute and Publicly Perform the Work including as incorporated in Collections; and, to Distribute and Publicly Perform Adaptations. . For the avoidance of doubt: Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. . Creative Commons Notice . Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. . Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. . Creative Commons may be contacted at http://creativecommons.org/. License: CC-BY-NC-ND-3.0 CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. . License . THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. . "Distribute" means to make available to the public the original and copies of the Work through sale or other transfer of ownership. . "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. . "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. . "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. . "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. . "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. . "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. . 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; and, to Distribute and Publicly Perform the Work including as incorporated in Collections. . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats, but otherwise you have no rights to make Adaptations. Subject to 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights set forth in Section 4(d). . 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . a) You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in connection with the exchange of copyrighted works. If You Distribute, or Publicly Perform the Work or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work. The credit required by this Section (c) may be implemented in any reasonable manner; provided, however, that in the case of a Collection, at a minimum such credit will appear, if a credit for all contributing authors of Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. . For the avoidance of doubt: Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and, Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(b). Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. . 5. Representations, Warranties and Disclaimer . UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. . License: GFDL-1.3 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Free Documentation License as published by the Free Software Foundation; version 1.3. . On Debian systems, the complete text of version 1.3 of the GNU Free Documentation License can be found in `/usr/share/common-licenses/GFDL-1.3'. License: GPL-1+ 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 1, or (at your option) any later version. . On Debian systems, the complete text of version 1 of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-1'. License: GPL-2 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; version 2. . On Debian systems, the complete text of version 2 of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-2' License: GPL-2+ 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 2, or (at your option) any later version. . On Debian systems, the complete text of version 2 of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-2' License: LGPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 2. . On Debian systems, the full text of the GNU Lesser General Public License version 2 can be found in the file `/usr/share/common-licenses/LGPL-2'. License: LGPL-3 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 3. . On Debian systems, the full text of the GNU Lesser General Public License version 3 can be found in the file `/usr/share/common-licenses/LGPL-3'. License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License: W3C Copyright © 1994-2001 World Wide Web Consortium, (Massachusetts Institute of Technology, Institut National de Recherche en Informatique et en Automatique, Keio University). All Rights Reserved. http://www.w3.org/Consortium/Legal/ . This W3C work (including software, documents, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions: . Permission to use, copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications, that you make: . 1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. 2. Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, a short notice of the following form (hypertext is preferred, text is permitted) should be used within the body of any redistributed or derivative code: "Copyright © [$date-of-software] World Wide Web Consortium, (Massachusetts Institute of Technology, Institut National de Recherche en Informatique et en Automatique, Keio University). All Rights Reserved. http://www.w3.org/Consortium/Legal/" 3. Notice of any changes or modifications to the W3C files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.) THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. . COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. . The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders. License: Simple-Geo These images only consist of simple geometric shapes or text. It does not meet the threshold of originality needed for copyright protection, and is therefore in the public domain. lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/distributions000066400000000000000000000004301325274564300262300ustar00rootroot00000000000000Label: LemonLDAP::NG Suite: oldstable Codename: oldstable Architectures: i386 amd64 powerpc sparc armel source Components: main SignWith: yes Label: LemonLDAP::NG Suite: stable Codename: stable Architectures: i386 amd64 powerpc sparc armel source Components: main SignWith: yes lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-doc.doc-base000066400000000000000000000005151325274564300301260ustar00rootroot00000000000000Document: lemonldap-ng-doc Title: Lemonldap::NG documentation Author: Clément Oudot Abstract: Those HTML documents contains all Lemonldap::NG documentation imported from https://lemonldap-ng.org Section: Web Development Format: HTML Index: /usr/share/doc/lemonldap-ng-doc/index.html Files: /usr/share/doc/lemonldap-ng-doc/*.html lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-doc.docs000066400000000000000000000000541325274564300273770ustar00rootroot00000000000000debian/tmp/usr/share/doc/lemonldap-ng-doc/* lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-doc.maintscript000066400000000000000000000001301325274564300307770ustar00rootroot00000000000000symlink_to_dir /usr/share/doc/lemonldap-ng-doc/pages/documentation/current 1.3 1.9.7-3~ lemonldap-ng-fastcgi-server.default000066400000000000000000000005021325274564300321710ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian# Number of process (default: 7) #NPROC = 7 # Unix socket to listen to SOCKET=/var/run/llng-fastcgi-server/llng-fastcgi.sock # Pid file PID=/var/run/llng-fastcgi-server/llng-fastcgi-server.pid # User and GROUP USER=www-data GROUP=www-data # Custom functions file #CUSTOM_FUNCTIONS_FILE=/var/lib/lemonldap-ng/myfile.pm lemonldap-ng-fastcgi-server.init000077500000000000000000000062571325274564300315300ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/sh ### BEGIN INIT INFO # Provides: lemonldap-ng-fastcgi-server # Required-Start: $local_fs $remote_fs $network $syslog $named # Required-Stop: $local_fs $remote_fs $network $syslog $named # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: starts the Lemonldap::NG FastCGI server # Description: starts Lemonldap::NG FastCGI server using start-stop-daemon ### END INIT INFO PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/sbin/llng-fastcgi-server NAME=llng-fastcgi-server DESC=llng-fastcgi-server # Include llng-fastcgi-server defaults if available if [ -r /etc/default/lemonldap-ng-fastcgi-server ]; then . /etc/default/lemonldap-ng-fastcgi-server fi STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}" test -x $DAEMON || exit 0 . /lib/init/vars.sh . /lib/lsb/init-functions # Try to extract llng-fastcgi-server pidfile if [ -z "$PID" ]; then PID=/var/run/llng-fastcgi-server/llng-fastcgi-server.pid fi if [ -z "$SOCKET" ]; then SOCKET=/var/run/llng-fastcgi-server/llng-fastcgi.sock fi for f in "$PID" "$SOCKET"; do DIR=`dirname "$f"` if [ ! -d "$DIR" ]; then mkdir -p "$DIR" if [ -n "$USER" ]; then chown "$USER" "$DIR" fi if [ -n "$GROUP" ]; then chgrp "$GROUP" "$DIR" fi fi done DAEMON_OPTS="-p ${PID} -u ${USER} -g ${GROUP} -s ${SOCKET}" if [ -z "$CUSTOM_FUNCTIONS_FILE" ]; then DAEMON_OPTS="$DAEMON_OPTS -f ${CUSTOM_FUNCTIONS_FILE}" fi start_server() { # Start the daemon/service # # Returns: # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON -- \ $DAEMON_OPTS 2>/dev/null \ || return 2 } stop_server() { # Stops the daemon/service # # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PID --name $NAME RETVAL="$?" sleep 1 return "$RETVAL" } reload_server() { # Function that sends a SIGHUP to the daemon/service start-stop-daemon --stop --signal HUP --quiet --pidfile $PID --name $NAME return 0 } case "$1" in start) log_daemon_msg "Starting $DESC" "$NAME" start_server case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; stop) log_daemon_msg "Stopping $DESC" "$NAME" stop_server case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" stop_server case "$?" in 0|1) start_server case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; reload|force-reload) log_daemon_msg "Reloading $DESC configuration" "$NAME" reload_server log_end_msg $? ;; status) status_of_proc -p $PID "$DAEMON" "$NAME" && exit 0 || exit $? ;; *) echo "Usage: $NAME {start|stop|restart|reload|force-reload|status}" >&2 exit 3 ;; esac lemonldap-ng-fastcgi-server.install000066400000000000000000000000671325274564300322210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/usr/sbin/llng-fastcgi-server /etc/lemonldap-ng/nginx* lemonldap-ng-fastcgi-server.manpages000066400000000000000000000000521325274564300323400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debianfastcgi-server/man/llng-fastcgi-server.1p lemonldap-ng-fastcgi-server.service000066400000000000000000000010001325274564300321770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian[Unit] Description=FastCGI server for Lemonldap::NG websso system After=network.target Documentation=https://lemonldap-ng.org/documentation/latest/fastcgiserver [Service] Type=forking EnvironmentFile=/etc/default/lemonldap-ng-fastcgi-server PIDFile=/var/run/llng-fastcgi-server/llng-fastcgi-server.pid ExecStart=/usr/sbin/llng-fastcgi-server ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile ${PID} KillMode=mixed [Install] Alias=llng-fastcgi-server.service WantedBy=multi-user.target lemonldap-ng-fastcgi-server.tmpfile000066400000000000000000000000661325274564300322120ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debiand /run/llng-fastcgi-server 0755 www-data www-data - - lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-fr-doc.doc-base000066400000000000000000000005621325274564300305350ustar00rootroot00000000000000Document: lemonldap-ng-fr-doc Title: Lemonldap::NG French documentation Author: Xavier Guimard Abstract: Those HTML documents contains all Lemonldap::NG documentation imported from https://lemonldap-ng.org translated in French Section: Web Development Format: HTML Index: /usr/share/doc/lemonldap-ng-fr-doc/index.html Files: /usr/share/doc/lemonldap-ng-fr-doc/*.html lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-fr-doc.docs000066400000000000000000000000661325274564300300070ustar00rootroot00000000000000debian/tmp/usr/share/doc/lemonldap-ng-fr-doc/fr-doc/* lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-fr-doc.links000066400000000000000000000006411325274564300301760ustar00rootroot00000000000000/usr/share/doc/lemonldap-ng-doc/pages/documentation/current/documentation /usr/share/doc/lemonldap-ng-fr-doc/pages/documentation/current/documentation /usr/share/doc/lemonldap-ng-doc/pages/documentation/current/icons /usr/share/doc/lemonldap-ng-fr-doc/pages/documentation/current/icons /usr/share/doc/lemonldap-ng-doc/pages/documentation/current/lib /usr/share/doc/lemonldap-ng-fr-doc/pages/documentation/current/lib lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-handler.examples000066400000000000000000000003371325274564300311410ustar00rootroot00000000000000debian/tmp/etc/lemonldap-ng/handler-apache2.conf debian/tmp/etc/lemonldap-ng/handler-nginx.conf debian/tmp/etc/lemonldap-ng/test-apache2.conf debian/tmp/etc/lemonldap-ng/test-nginx.conf debian/tmp/var/lib/lemonldap-ng/test lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-handler.install000066400000000000000000000002551325274564300307700ustar00rootroot00000000000000/etc/lemonldap-ng/handler-apache2.conf /etc/lemonldap-ng/handler-nginx.conf /etc/lemonldap-ng/test-apache2.conf /etc/lemonldap-ng/test-nginx.conf /var/lib/lemonldap-ng/test lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng-handler.links000066400000000000000000000005141325274564300304400ustar00rootroot00000000000000/etc/lemonldap-ng/handler-apache2.conf /etc/apache2/sites-available/handler-apache2.conf /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/sites-available/handler-nginx.conf /etc/lemonldap-ng/test-apache2.conf /etc/apache2/sites-available/test-apache2.conf /etc/lemonldap-ng/test-nginx.conf /etc/nginx/sites-available/test-nginx.conf lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng.README.Debian000066400000000000000000000045411325274564300300270ustar00rootroot00000000000000FIRST CONFIGURATION STEPS 1 - Change default DNS domain ----------------------------- By default, DNS domain is example.com. You can change using dpkg-reconfigure or with a quick sed command. For example, we change it to ow2.org: sed -i 's/example\.com/ow2.org/g' /etc/lemonldap-ng/* \ /var/lib/lemonldap-ng/conf/lmConf-1.js /var/lib/lemonldap-ng/test/index.pl 2 - Enable LL::NG sites ----------------------- 2.1 - Apache Enable the components you've installed: # Portal a2ensite portal-apache2.conf # Manager a2ensite manager-apache2.conf # Handler a2ensite handler-apache2.conf a2ensite test-apache2.conf customize them and enable mod_perl if not already loaded: a2enmod perl Then restart Apache: apache2ctl configtest apache2ctl restart 2.2 - Nginx Enable the components you've installed: cd /etc/nginx/sites-enabled # Portal ln -s ../sites-available/portal-nginx.conf # Manager ln -s ../sites-available/manager-nginx.conf # Handler # Warning: if no site is protected (see example files for this), the # $lmremote_user variable will not be set. So you can't load this file since # it includes /etc/lemonldap/nginx-lmlog.conf that requires at least one # protected virtual host. ln -s ../sites-available/handler-nginx.conf # Test site ln -s ../site-available/test-nginx.conf Customize then, then reload nginx service nginx reload 3 - Check your DNS ------------------ Be sure that your browser can join (adapt it with your domain): - auth.example.com : the authentication portal - manager.example.com: the configuration interface 4 - Connect to the manager -------------------------- Go to http://manager.example.com/, you'll be redirected to the portal. Then enjoy! 5 - Default accounts -------------------- By default, LemonLDAP::NG use "Demo" authentication backend, so you can use the following accounts: Login Password Role rtyler rtyler user msmith msmith user dwho dwho administrator 6 - Base configuration file --------------------------- The configuration is managed by the manager with the exception of some basic parameters such as the storage type configuration. These parameters are defined in the file /etc/lemonldap-ng/lemonldap-ng.ini. This file can also be used to override the global configuration locally 6 - See more ------------ https://lemonldap-ng.org/ lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng.dirs000066400000000000000000000000451325274564300266450ustar00rootroot00000000000000/usr/share/doc/lemonldap-ng/examples lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng.links000066400000000000000000000004441325274564300270270ustar00rootroot00000000000000/usr/share/doc/liblemonldap-ng-handler-perl/examples /usr/share/doc/lemonldap-ng/examples/handler /usr/share/doc/liblemonldap-ng-portal-perl/examples /usr/share/doc/lemonldap-ng/examples/portal /usr/share/doc/liblemonldap-ng-manager-perl/examples /usr/share/doc/lemonldap-ng/examples/manager lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/lemonldap-ng.lintian-overrides000066400000000000000000000004511325274564300313430ustar00rootroot00000000000000# These 3 demo accounts use their name as password lemonldap-ng: spelling-error-in-readme-debian rtyler rtyler (duplicate word) rtyler lemonldap-ng: spelling-error-in-readme-debian msmith msmith (duplicate word) msmith lemonldap-ng: spelling-error-in-readme-debian dwho dwho (duplicate word) dwho liblemonldap-ng-common-perl.config000066400000000000000000000012051325274564300320060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/sh set -e . /usr/share/debconf/confmodule db_input medium liblemonldap-ng-common-perl/domain || true db_input medium liblemonldap-ng-common-perl/ldapServer || true db_input medium liblemonldap-ng-common-perl/ldapPort || true db_input medium liblemonldap-ng-common-perl/ldapBase || true db_input medium liblemonldap-ng-common-perl/managerDn || true db_input medium liblemonldap-ng-common-perl/managerPassword || true db_input medium liblemonldap-ng-common-perl/portal || true if [ "$2" != "" ]; then if dpkg --compare-versions "$2" lt 1.0; then db_input high liblemonldap-ng-common-perl/migrate || true fi fi db_go || true liblemonldap-ng-common-perl.dirs000066400000000000000000000001441325274564300315030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/var/lib/lemonldap-ng/conf /var/lib/lemonldap-ng/sessions/lock /var/lib/lemonldap-ng/psessions/lock liblemonldap-ng-common-perl.install000066400000000000000000000007221325274564300322120ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/etc/lemonldap-ng/lemonldap-ng.ini /etc/lemonldap-ng/for_etc_hosts /usr/share/man/man1/convertConfig.1p /usr/share/man/man1/lemonldap-ng-cli.1p /usr/share/man/man3/Lemonldap::NG::Common* /usr/share/perl5/auto/Lemonldap/NG/Common /usr/share/perl5/Lemonldap/NG/Common* /usr/share/lemonldap-ng/ressources /usr/share/lemonldap-ng/bin/convertConfig /usr/share/lemonldap-ng/bin/lmMigrateConfFiles2ini /usr/share/lemonldap-ng/bin/rotateOidcKeys /var/lib/lemonldap-ng/conf/ liblemonldap-ng-common-perl.lintian-overrides000066400000000000000000000022411325274564300342000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian# lemonldap-ng.ini must be readable by www-data but not by other (db passwords # can be set here liblemonldap-ng-common-perl: non-standard-file-perm etc/lemonldap-ng/lemonldap-ng.ini 0640 != 0644 # If file storage is used for configuration, DB passwords can be stored here # so this directory must not be readable by all liblemonldap-ng-common-perl: non-standard-dir-perm var/lib/lemonldap-ng/conf/ 0750 != 0755 # If file storage is used for configuration, later configuration files will be # in 0640 mode. So the first is adjusted so liblemonldap-ng-common-perl: non-standard-file-perm var/lib/lemonldap-ng/conf/lmConf-1.js 0640 != 0644 # If file storage is used for sessions, user passord may be stored in this # directory, so it must not be readable by all but must be writable by www-data liblemonldap-ng-common-perl: non-standard-dir-perm var/lib/lemonldap-ng/sessions/ 0770 != 0755 liblemonldap-ng-common-perl: non-standard-dir-perm var/lib/lemonldap-ng/sessions/lock/ 0770 != 0755 liblemonldap-ng-common-perl: non-standard-dir-perm var/lib/lemonldap-ng/psessions/ 0770 != 0755 liblemonldap-ng-common-perl: non-standard-dir-perm var/lib/lemonldap-ng/psessions/lock/ 0770 != 0755 liblemonldap-ng-common-perl.postinst000077500000000000000000000020721325274564300324320ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/bash set -e . /usr/share/debconf/confmodule CONFDIR=/etc/lemonldap-ng SESSIONSDIR=/var/lib/lemonldap-ng/sessions CONFSTORAGEDIR=/var/lib/lemonldap-ng/conf FIRSTCONFFILE=$CONFSTORAGEDIR/lmConf-1.js LMINIFILE=/etc/lemonldap-ng/lemonldap-ng.ini MIGRATION=/usr/share/lemonldap-ng/bin/lmMigrateConfFiles2ini if [ "$1" == "configure" ] then for i in domain ldapServer ldapPort ldapBase managerDn managerPassword portal; do db_get liblemonldap-ng-common-perl/$i || true perl -000 -i -pe "s#^$i(\\n\\s+)('?)[^\\n]*?('?)\$#$i\${1}\${2}$RET\${3}#m" $FIRSTCONFFILE done # Run migration script to convert menu format if old version is 0.9.* if [ "$2" != "" ]; then if dpkg --compare-versions "$2" lt 1.0; then if [ -e $CONFDIR/storage.conf -o -e $CONFDIR/apply.conf -o -e $CONFDIR/apps-list.xml ] ; then db_get liblemonldap-ng-common-perl/migrate #if [ "$RET" ]; then # $MIGRATION 2>&1 > /dev/null || : #fi fi fi fi fi #DEBHELPER# exit 0 liblemonldap-ng-common-perl.templates000066400000000000000000000035051325274564300325440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debianTemplate: liblemonldap-ng-common-perl/ldapServer Type: string Default: localhost _Description: LDAP server: Set here name or IP address of the LDAP server that has to be used by Lemonldap::NG. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/domain Type: string Default: example.com _Description: Lemonldap::NG DNS domain: Set here the main domain protected by Lemonldap::NG. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/portal Type: string Default: http://auth.example.com/ _Description: Lemonldap::NG portal: Set here the Lemonldap::NG portal URL. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/ldapPort Type: string Default: 389 _Description: LDAP server port: Set here the port used by the LDAP server. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/ldapBase Type: string Default: dc=example,dc=com _Description: LDAP search base: Set here the search base to use in LDAP queries. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/managerDn Type: string _Description: LDAP account: Set here the account that Lemonldap::NG has to use for its LDAP requests. Leaving it blank causes Lemonldap::NG to use anonymous connections. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/managerPassword Type: string _Description: LDAP password: Set here the password for the Lemonldap::NG LDAP account. You can modify this value later using the Lemonldap::NG manager. Template: liblemonldap-ng-common-perl/migrate Type: boolean _Description: Lemonldap::NG configuration files have changed, try to migrate your files? liblemonldap-ng-handler-perl.cron.d000066400000000000000000000002531325274564300320530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian# # Regular cron jobs for the Lemonldap::NG portal # 1 * * * * www-data test -x /usr/share/lemonldap-ng/bin/purgeLocalCache && /usr/share/lemonldap-ng/bin/purgeLocalCache liblemonldap-ng-handler-perl.install000066400000000000000000000002531325274564300323360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/usr/share/perl5/Lemonldap/NG/Handler* /usr/share/perl5/auto/Lemonldap/NG/Handler* /usr/share/man/man3/Lemonldap::NG::Handler* /usr/share/lemonldap-ng/bin/purgeLocalCache liblemonldap-ng-handler-perl.maintscript000066400000000000000000000002601325274564300332230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian# Conf files have moved to lemonldap-ng-handler package rm_conffile /etc/lemonldap-ng/handler-nginx.conf 1.9.1-2~ rm_conffile /etc/lemonldap-ng/handler-apache2.X.conf 1.9.1-2~ liblemonldap-ng-manager-perl.docs000066400000000000000000000000321325274564300316100ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debianlemonldap-ng-manager/*.md liblemonldap-ng-manager-perl.examples000066400000000000000000000001001325274564300324720ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debiandebian/tmp/usr/share/lemonldap-ng/manager/psgi/manager-server.* liblemonldap-ng-manager-perl.install000066400000000000000000000005251325274564300323350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/etc/lemonldap-ng/manager-apache2.conf /etc/lemonldap-ng/manager-nginx.conf /usr/share/perl5/Lemonldap/NG/Manager* /usr/share/lemonldap-ng/manager/static /usr/share/lemonldap-ng/manager/templates /usr/share/lemonldap-ng/manager/psgi/manager-server.fcgi /usr/share/lemonldap-ng/bin/lmConfigEditor /usr/share/lemonldap-ng/bin/lemonldap-ng-cli liblemonldap-ng-manager-perl.links000066400000000000000000000003701325274564300320050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/etc/lemonldap-ng/manager-apache2.conf /etc/apache2/sites-available/manager-apache2.conf /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/sites-available/manager-nginx.conf /usr/share/lemonldap-ng/manager/static /var/lib/lemonldap-ng/manager/static liblemonldap-ng-manager-perl.postrm000066400000000000000000000001671325274564300322150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/bash set -e . /usr/share/debconf/confmodule if [ "$1" == "configure" ] then db_purge fi #DEBHELPER# exit 0 liblemonldap-ng-portal-perl.cron.d000066400000000000000000000002621325274564300317370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian# # Regular cron jobs for the Lemonldap::NG portal # */10 * * * * www-data test -x /usr/share/lemonldap-ng/bin/purgeCentralCache && /usr/share/lemonldap-ng/bin/purgeCentralCache liblemonldap-ng-portal-perl.dirs000066400000000000000000000002131325274564300315110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/var/lib/lemonldap-ng/captcha /var/lib/lemonldap-ng/sessions/lock /var/lib/lemonldap-ng/psessions/lock /var/lib/lemonldap-ng/notifications liblemonldap-ng-portal-perl.examples000066400000000000000000000000351325274564300323700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debiandebian/tmp/examples/portal/* liblemonldap-ng-portal-perl.install000066400000000000000000000006001325274564300322160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/usr/share/lemonldap-ng/bin/purgeCentralCache /usr/share/lemonldap-ng/portal-skins /usr/share/man/man3/Lemonldap::NG::Portal* /usr/share/perl5/auto/Lemonldap/NG/Portal /usr/share/perl5/Lemonldap/NG/Portal* /usr/share/lemonldap-ng/bin/buildPortalWSDL /usr/share/lemonldap-ng/portal /var/lib/lemonldap-ng/portal /etc/lemonldap-ng/portal-apache2.conf /etc/lemonldap-ng/portal-nginx.conf liblemonldap-ng-portal-perl.links000066400000000000000000000015571325274564300317040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/etc/lemonldap-ng/portal-apache2.conf /etc/apache2/sites-available/portal-apache2.conf /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/sites-available/portal-nginx.conf /usr/share/lemonldap-ng/portal/cdc.pl /var/lib/lemonldap-ng/portal/cdc.pl /usr/share/lemonldap-ng/portal/index.pl /var/lib/lemonldap-ng/portal/index.pl /usr/share/lemonldap-ng/portal/mail.pl /var/lib/lemonldap-ng/portal/mail.pl /usr/share/lemonldap-ng/portal/metadata.pl /var/lib/lemonldap-ng/portal/metadata.pl /usr/share/lemonldap-ng/portal/register.pl /var/lib/lemonldap-ng/portal/register.pl /usr/share/lemonldap-ng/portal/openid-configuration.pl /var/lib/lemonldap-ng/portal/openid-configuration.pl /usr/share/lemonldap-ng/portal/public.pl /var/lib/lemonldap-ng/portal/public.pl /usr/share/doc/liblemonldap-ng-portal-perl/examples/index_skin.pl /usr/share/doc/liblemonldap-ng-portal-perl/examples/index.pl liblemonldap-ng-portal-perl.lintian-overrides000066400000000000000000000013451325274564300342150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian# If file storage is used for sessions, user passord may be stored in this # directory, so it must not be readable by all but must be writable by www-data liblemonldap-ng-portal-perl: non-standard-dir-perm var/lib/lemonldap-ng/captcha/ 0770 != 0755 liblemonldap-ng-portal-perl: non-standard-dir-perm var/lib/lemonldap-ng/notifications/ 0770 != 0755 liblemonldap-ng-portal-perl: non-standard-dir-perm var/lib/lemonldap-ng/sessions/lock/ 0770 != 0755 liblemonldap-ng-portal-perl: non-standard-dir-perm var/lib/lemonldap-ng/sessions/ 0770 != 0755 liblemonldap-ng-portal-perl: non-standard-dir-perm var/lib/lemonldap-ng/psessions/lock/ 0770 != 0755 liblemonldap-ng-portal-perl: non-standard-dir-perm var/lib/lemonldap-ng/psessions/ 0770 != 0755 liblemonldap-ng-portal-perl.postinst000066400000000000000000000004021325274564300324330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/bash set -e . /usr/share/debconf/confmodule BUILDPORTALWSDL=/usr/share/lemonldap-ng/bin/buildPortalWSDL WSDLFILE=/var/lib/lemonldap-ng/portal/portal.wsdl if [ "$1" == "configure" ] then $BUILDPORTALWSDL > $WSDLFILE || true fi #DEBHELPER# exit 0 liblemonldap-ng-portal-perl.postrm000066400000000000000000000003071325274564300321000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/bash set -e . /usr/share/debconf/confmodule if [ "$1" == "configure" ] then db_purge fi if [ "$1" == "purge" ] then rm -f /var/lib/lemonldap-ng/portal/portal.wsdl fi #DEBHELPER# exit 0 liblemonldap-ng-portal-perl.preinst000066400000000000000000000005221325274564300322370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian#!/bin/bash set -e . /usr/share/debconf/confmodule if [ "$1" == "configure" ] then if [ -f /var/lib/lemonldap-ng/portal/index.pl ]; then \ diff /var/lib/lemonldap-ng/portal/index.pl \ /usr/share/doc/liblemonldap-ng-portal-perl/examples/index_skin.pl && \ rm -rf /var/lib/lemonldap-ng/portal/index.pl fi fi #DEBHELPER# exit 0 lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/migrating.sql000066400000000000000000000000551325274564300261100ustar00rootroot00000000000000ALTER TABLE lmConfig ADD COLUMN timeout int; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/patches/000077500000000000000000000000001325274564300250355ustar00rootroot00000000000000replace-mouse-by-moose.patch000066400000000000000000000135051325274564300322740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/patchesDescription: Replace Mouse by Moose Using Mouse under ModPerl::Registry provides a 'No package name defined' error. This patch replace Mouse by Moose to avoid it. Author: Xavier Guimard Bug-Debian: https://bugs.debian.org/823217 Forwarded: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues/1003 Last-Update: 2017-11-16 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Captcha.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Captcha.pm @@ -10,7 +10,7 @@ use strict; use Lemonldap::NG::Common::Session; -use Mouse; +use Moose; use Digest::MD5 qw(md5_hex); has 'storageModule' => ( @@ -117,6 +117,6 @@ return 0; } -no Mouse; +no Moose; 1; --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI.pm @@ -1,7 +1,7 @@ package Lemonldap::NG::Common::PSGI; use 5.10.0; -use Mouse; +use Moose; use JSON; use Lemonldap::NG::Common; use Lemonldap::NG::Common::PSGI::Constants; --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Request.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Request.pm @@ -1,7 +1,7 @@ package Lemonldap::NG::Common::PSGI::Request; use strict; -use Mouse; +use Moose; use JSON; use URI::Escape; --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Router.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Router.pm @@ -1,6 +1,6 @@ package Lemonldap::NG::Common::PSGI::Router; -use Mouse; +use Moose; use Lemonldap::NG::Common::PSGI; use Lemonldap::NG::Common::PSGI::Constants; --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Session.pm @@ -8,7 +8,7 @@ our $VERSION = '1.9.1'; -use Mouse; +use Moose; use Lemonldap::NG::Common::Apache::Session; has 'id' => ( @@ -202,6 +202,6 @@ return 1; } -no Mouse; +no Moose; 1; --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm @@ -5,7 +5,7 @@ use Safe; use Lemonldap::NG::Common::Safelib; #link protected safe Safe object use constant SAFEWRAP => ( Safe->can("wrap_code_ref") ? 1 : 0 ); -use Mouse; +use Moose; use Lemonldap::NG::Handler::Main::Logger; has customFunctions => ( is => 'rw', isa => 'Maybe[Str]' ); --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Nginx.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Nginx.pm @@ -3,7 +3,7 @@ package Lemonldap::NG::Handler::Nginx; use strict; -use Mouse; +use Moose; use Lemonldap::NG::Handler::SharedConf qw(:tsv); extends 'Lemonldap::NG::Handler::PSGI'; --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI.pm @@ -1,7 +1,7 @@ package Lemonldap::NG::Handler::PSGI; use 5.10.0; -use Mouse; +use Moose; extends 'Lemonldap::NG::Handler::PSGI::Base', 'Lemonldap::NG::Common::PSGI'; --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI/Base.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI/Base.pm @@ -1,7 +1,7 @@ package Lemonldap::NG::Handler::PSGI::Base; use 5.10.0; -use Mouse; +use Moose; use Lemonldap::NG::Handler::SharedConf qw(:tsv :variables :jailSharedVars); our $VERSION = '1.9.6'; --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI/Router.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI/Router.pm @@ -1,7 +1,7 @@ package Lemonldap::NG::Handler::PSGI::Router; use 5.10.0; -use Mouse; +use Moose; extends 'Lemonldap::NG::Handler::PSGI::Base', 'Lemonldap::NG::Common::PSGI::Router'; --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI/Server.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGI/Server.pm @@ -1,7 +1,7 @@ package Lemonldap::NG::Handler::PSGI::Server; use strict; -use Mouse; +use Moose; use Lemonldap::NG::Handler::SharedConf qw(:tsv); extends 'Lemonldap::NG::Handler::PSGI'; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm @@ -12,7 +12,7 @@ use 5.10.0; use utf8; -use Mouse; +use Moose; our $VERSION = '1.9.16'; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build.pm @@ -2,7 +2,7 @@ use strict; use utf8; -use Mouse; +use Moose; use Lemonldap::NG::Manager::Build::Attributes; use Lemonldap::NG::Manager::Build::Tree; use Lemonldap::NG::Manager::Build::CTrees; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf.pm @@ -8,7 +8,7 @@ use 5.10.0; use utf8; -use Mouse; +use Moose; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; use Lemonldap::NG::Manager::Constants; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Conf/Parser.pm @@ -20,7 +20,7 @@ use strict; use utf8; -use Mouse; +use Moose; use Lemonldap::NG::Manager::Constants; use Lemonldap::NG::Manager::Attributes; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Lib.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Lib.pm @@ -2,7 +2,7 @@ use 5.10.0; use utf8; -use Mouse; +use Moose; use Lemonldap::NG::Common::Conf; use Lemonldap::NG::Common::Conf::Constants; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Notifications.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Notifications.pm @@ -2,7 +2,7 @@ use 5.10.0; use utf8; -use Mouse; +use Moose; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Sessions.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Sessions.pm @@ -3,7 +3,7 @@ use 5.10.0; use utf8; use strict; -use Mouse; +use Moose; use Lemonldap::NG::Common::Session; use Lemonldap::NG::Common::Conf::Constants; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/patches/series000066400000000000000000000000351325274564300262500ustar00rootroot00000000000000replace-mouse-by-moose.patch lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/000077500000000000000000000000001325274564300240245ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/POTFILES.in000066400000000000000000000001001325274564300255700ustar00rootroot00000000000000[type: gettext/rfc822deb] liblemonldap-ng-common-perl.templates lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/cs.po000066400000000000000000000106171325274564300247760ustar00rootroot00000000000000# Czech translation of lemonldap-ng debconf messages. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0-2\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2010-12-08 20:03+0100\n" "Last-Translator: Daniel Kavan \n" "Language-Team: Czech \n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Nastavte jméno nebo IP adresu LDAP serveru, který má být používán Lemonldap::" "NG. Tuto adresu můžete později změnit pomocí Lemonldap::NG manažeru." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Lemonldap::NG DNS doména:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Vložte doménu, kterou bude Lemonldap::NG chránit. Tuto doménu můžete později " "změnit pomocí Lemonldap::NG manažeru." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NG portál:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Vložte URL portálu Lemonldap::NG. URL můžete později změnit pomocí " "Lemonldap::NG manažeru." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Port LDAP serveru:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Vložte port používaný LDAP serverem. Port můžete později změnit pomocí " "Lemonldap::NG manažeru." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Kořen LDAP stromu pro prohledávání:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Nastavte kořen LDAP stromu pro použití v LDAP dotazech. Hodnotu můžete " "později změnit pomocí Lemonldap::NG manažeru." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP účet:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Nastavte účet, který bude Lemonldap::NG používat pro své LDAP dotazy. Pokud " "necháte prázdné, Lemonldap::NG bude používat anonymní spojení. Účet můžete " "později změnit pomocí Lemonldap::NG manažeru." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP heslo:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Vložte heslo pro Lemonldap::NG LDAP účet. Hodnotu můžete později změnit " "pomocí Lemonldap::NG manažeru." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Konfigurační soubory Lemonldap::NG byly změněny, zkusíte své soubory převést?" #, fuzzy #~| msgid "Lemonldap::NG DNS domain:" #~ msgid "Lemonldap::NG migration:" #~ msgstr "Lemonldap::NG DNS doména:" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/da.po000066400000000000000000000106571325274564300247610ustar00rootroot00000000000000# Danish translation lemonldap-ng. # Copyright (C) 2011 lemonldap-ng & nedenstående oversættere. # This file is distributed under the same license as the lemonldap-ng package. # Joe Hansen , 2011. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2012-03-16 17:30+01:00\n" "Last-Translator: Joe Hansen \n" "Language-Team: Danish \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP-server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Angiv her navn eller IP-adresse for LDAP-serveren, som skal bruges af " "Lemonldap::NG. Du kan ændre denne værdi senere ved at bruge " "Lemonldap::NG-håndteringen." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "DNS-domæne for Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Angiv her hoveddomænet beskyttet af Lemonldap::NG. Du kan ændre denne " "værdi senere ved at bruge Lemonldap::NG-håndteringen." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NG-portal:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Angiv her portaladressen for Lemonldap::NG. Du kan ændre denne værdi senere " "med Lemonldap::NG-håndteringen." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "LDAP-serverport:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Angiv her porten brugt af LDAP-serveren. Du kan ændre denne værdi senere " "med Lemonldap::NG-håndteringen." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "LDAP-søgebase:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Angiv her søgebasen at bruge i LDAP-forespørgsler. Du kan ændre " "denne værdi senere med Lemonldap::NG-håndteringen." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP-konto:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Angiv her kontoen som Lemonldap::NG skal bruge for sine LDAP-forespørgsler. " "Blank får Lemonldap::NG til at bruge anonyme forbindelser. Du kan ændre " "denne værdi senere med Lemonldap::NG-håndteringen." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP-adgangskode:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Angiv her adgangskoden for Lemonldap::NG LDAP-kontoen. Du kan ændre denne " "værdi senere med Lemonldap::NG-håndteringen." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Lemonldap::NG-konfigurationsfilerne har ændret sig, prøv at migrere dine filer?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/de.po000066400000000000000000000111151325274564300247530ustar00rootroot00000000000000# Copyright (C) Helge Kreutzmann , 2008, 2010. # This file is distributed under the same license as the lemonldap-ng package. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0-1\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2008-01-23 23:12+0100\n" "Last-Translator: Helge Kreutzmann <debian@helgefjell.de>\n" "Language-Team: de <debian-l10n-german@lists.debian.org>\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP-Server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Setzen Sie hier den Namen oder die IP-Adresse des LDAP-Servers, der von " "Lemonldap::NG verwendet werden muss. Sie können diesen Wert später mit dem " "Lemonldap::NG-Manager verändern." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Lemonldap::NG DNS-Domain:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Setzen Sie hier die Haupt-Domain, die von Lemonldap::NG geschützt wird. Sie " "können diesen Wert später mit dem Lemonldap::NG-Manager verändern." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NG-Portal:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Setzen Sie hier die Portal-URL von Lemonldap::NG. Sie können diesen Wert " "später mit dem Lemonldap::NG-Manager verändern." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Port des LDAP-Servers:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Setzen Sie hier den Port des LDAP-Servers. Sie können diesen Wert später mit " "dem Lemonldap::NG-Manager verändern." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "LDAP-Suchbasis:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Setzen Sie hier die Suchbasis für LDAP-Abfragen. Sie können diesen Wert " "später mit dem Lemonldap::NG-Manager verändern." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP-Konto:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Setzen Sie hier das Konto, das Lemonldap::NG für seine LDAP-Anfragen " "verwenden muss. Bleibt dieses Feld leer, verwendet Lemonldap::NG anonyme " "Verbindungen. Sie können diesen Wert später mit dem Lemonldap::NG-Manager " "verändern." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP-Passwort:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Setzen Sie hier das Passwort für das LDAP-Konto von Lemonldap::NG. Sie " "können diesen Wert später mit dem Lemonldap::NG-Manager verändern." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Lemonldap::NG-Konfigurationsdateien wurden geändert, soll versucht werden, " "die Dateien zu migrieren?" #~ msgid "Lemonldap::NG migration:" #~ msgstr "Lemonldap::NG-Migration:" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/es.po000066400000000000000000000124201325274564300247720ustar00rootroot00000000000000# lemonldap-ng po-debconf translation to Spanish # Copyright (C) 2010 Software in the Public Interest # This file is distributed under the same license as the lemonldap-ng package. # # Changes: # - Initial translation # Camaleón , 2010 # # - Updates # # # Traductores, si no conocen el formato PO, merece la pena leer la # documentación de gettext, especialmente las secciones dedicadas a este # formato, por ejemplo ejecutando: # info -n '(gettext)PO Files' # info -n '(gettext)Header Entry' # # Equipo de traducción al español, por favor lean antes de traducir # los siguientes documentos: # # - El proyecto de traducción de Debian al español # http://www.debian.org/intl/spanish/ # especialmente las notas y normas de traducción en # http://www.debian.org/intl/spanish/notas # # - La guía de traducción de po's de debconf: # /usr/share/doc/po-debconf/README-trans # o http://www.debian.org/intl/l10n/po-debconf/README-trans # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0.2\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2010-12-09 08:56+0100\n" "Last-Translator: Camaleón \n" "Language-Team: Debian Spanish \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-Language: Spanish\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "Servidor LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Introduzca el nombre o la dirección IP del servidor LDAP que usará " "Lemonldap::NG. Podrá modificar este valor utilizando el administrador de " "Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Dominio DNS de Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Introduzca el dominio principal protegido por Lemonldap::NG. Podrá modificar " "este valor desde el administrador de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Portal de Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Introduzca la URL del portal de Lemonldap::NG. Podrá modificar este valor " "desde el administrador de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Puerto del servidor LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Introduzca el puerto que usará el servidor LDAP. Podrá modificar este valor " "desde el administrador de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Base de búsqueda LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Introduzca la base de búsqueda para las consultas LDAP. Podrá modificar este " "valor desde el administrador de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "Cuenta LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Introduzca la cuenta que usará Lemonldap::NG para las solicitudes LDAP. Si " "lo deja en blanco, Lemonldap::NG utilizará conexiones anónimas. Podrá " "modificar este valor desde el administrador de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "Contraseña LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Introduzca la contraseña de la cuenta LDAP de Lemonldap::NG. Podrá modificar " "este valor desde el administrador de Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Los archivos de configuración de Lemonldap::NG han cambiado. ¿Desea intentar " "migrar sus archivos?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/fi.po000066400000000000000000000107131325274564300247640ustar00rootroot00000000000000# Translation of the lemonldap-ng debconf strings to Finnish # Copyright (C) 2011 # This file is distributed under the same license as the lemonldap-ng package. # Esko Arajärvi , 2011. msgid "" msgstr "" "Project-Id-Version: lemonldap-ng\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2011-03-26 20:34+0300\n" "Last-Translator: Esko Arajärvi \n" "Language-Team: debian-10n-finnish@lists.debian.org\n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.6.1\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP-palvelin:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Anna sen LDAP-palvelimen nimi tai IP-osoite, jota ohjelman Lemonldap::NG " "tulisi käyttää. Voit muokata tätä asetusta myöhemmin käyttäen ohjelmaa " "Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Lemonldap::NGn DNS-verkkoalue:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Anna pääverkkoalue, jota Lemonldap::NG suojaa. Voit muokata tätä asetusta " "myöhemmin käyttäen ohjelmaa Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NGn portaali:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Anna Lemonldap::NG -portaalin URL. Voit muokata tätä asetusta myöhemmin " "käyttäen ohjelmaa Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "LDAP-palvelimen portti:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Anna LDAP-palvelimen portti. Voit muokata tätä asetusta myöhemmin käyttäen " "ohjelmaa Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "LDAP-hakukanta:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Anna LDAP-hakujen hakukanta. Voit muokata tätä asetusta myöhemmin käyttäen " "ohjelmaa Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP-tunnus:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Anna tunnus, jota Lemonldap::NG käyttää LDAP-pyynnöissään. Jos jätät kentän " "tyhjäksi Lemonldap::NG käyttää anonyymejä yhteyksiä. Voit muokata tätä " "asetusta myöhemmin käyttäen ohjelmaa Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP-salasana:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Anna Lemonldap::NGn käyttämän LDAP-tunnuksen salasana. Voit muokata tätä " "asetusta myöhemmin käyttäen ohjelmaa Lemonldap::NG manager." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/fr.po000066400000000000000000000116651325274564300250040ustar00rootroot00000000000000# translation of fr.po to French # Copyright (C) 2007 # This file is distributed under the same license as the lemonldap-ng package. # # Xavier Guimard , 2007. # Christian Perrier , 2010. msgid "" msgstr "" "Project-Id-Version: fr\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-01 06:25+0100\n" "PO-Revision-Date: 2010-12-05 09:38+0100\n" "Last-Translator: Christian Perrier \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 1.0\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "Serveur LDAP :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Veuillez indiquer le nom ou l'adresse IP du serveur LDAP que Lemonldap::NG " "utilisera. Vous pourrez modifier cette valeur ultérieurement dans le " "gestionnaire de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Domaine DNS pour Lemonldap::NG :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Veuillez indiquer le domaine principal protégé par lemonldap::NG. Vous " "pourrez modifier cette valeur ultérieurement dans le gestionnaire de " "Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Portail de Lemonldap::NG :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Veuillez indiquer l'URL du portail de Lemonldap::NG. Vous pourrez modifier " "cette valeur ultérieurement dans le gestionnaire de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Port du serveur LDAP :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Veuillez indiquer le numéro du port du serveur LDAP. Vous pourrez modifier " "cette valeur ultérieurement dans le gestionnaire de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Base de recherche LDAP :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Veuillez indiquer la base de recherche des requêtes LDAP. Vous pourrez " "modifier cette valeur ultérieurement dans le gestionnaire de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "Identifiant de connexion LDAP :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Veuillez indiquer l'identifiant que Lemonldap::NG utilisera pour les " "requêtes LDAP. Vous pouvez laisser ce champ vide pour utiliser des " "connexions anonymes. Vous pourrez modifier cette valeur ultérieurement dans " "le gestionnaire de Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "Mot de passe de connexion LDAP :" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Veuillez indiquer le mot de passe de l'identifiant de connexion LDAP pour " "Lemonldap::NG. Vous pourrez modifier cette valeur ultérieurement dans le " "gestionnaire de Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Faut-il tenter une migration des fichiers de configuration de Lemonldap::NG ?" #, fuzzy #~| msgid "Lemonldap::NG DNS domain:" #~ msgid "Lemonldap::NG migration:" #~ msgstr "Domaine DNS de Lemonldap::NG:" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/it.po000066400000000000000000000113611325274564300250020ustar00rootroot00000000000000# Italian translation of lemonldap-ng debconf messages # Copyright (C) 2017, lemonldap-ng package's copyright holder # This file is distributed under the same license as the lemonldap-ng package. # Beatrice Torracca , 2017. msgid "" msgstr "" "Project-Id-Version: lemonldap-ng\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2017-08-20 17:12+0200\n" "Last-Translator: Beatrice Torracca \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.7.1\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "Server LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Impostare qui il nome o l'indirizzo IP del server LDAP che deve essere usato " "da Lemonldap::NG. Si può modificare questo valore successivamente usando il " "gestore di Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Dominio DNS di Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Impostare qui il dominio principale protetto da Lemonldap::NG. Si può " "modificare questo valore successivamente usando il gestore di Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Portale di Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Impostare qui l'URL del portale di Lemonldap::NG. Si può modificare questo " "valore successivamente usando il gestore di Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Porta del server LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Impostare qui la porta usata dal server LDAP. Si può modificare questo " "valore successivamente usando il gestore di Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Base di ricerca LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Impostare qui la base di ricerca da usare in interrogazioni LDAP. Si può " "modificare questo valore successivamente usando il gestore di Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "Account LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Impostare qui l'account che deve essere usato da Lemonldap::NG per le sue " "richieste LDAP. Se viene lasciato vuoto Lemonldap::NG usa connessioni " "anonime. Si può modificare questo valore successivamente usando il gestore " "di Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "Password LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Impostare qui la password per l'account LDAP di Lemonldap::NG. Si può " "modificare questo valore successivamente usando il gestore di Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "I file di configurazione di Lemonldap::NG sono stati modificati; provare a " "migrare i propri file?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/ja.po000066400000000000000000000116651325274564300247670ustar00rootroot00000000000000# Copyright (C) 2009-2011 Xavier Guimard # This file is distributed under the same license as lemonldap-ng package. # Hideki Yamane (Debian-JP) , 2009-2011. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0.4-1\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2011-05-02 06:26+0900\n" "Last-Translator: Hideki Yamane \n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP サーバ:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Lemonldap::NG が利用する LDAP サーバの名前、あるいは IP アドレスをここで設定" "してください。Lemonldap::NG マネージャを使えば、後ほどこの値を変更できます。" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Lemonldap::NG DNS ドメイン名:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "ここで、Lemonldap::NG で保護するメインのドメイン名を設定してください。" "Lemonldap::NG マネージャを使えば、後ほどこの値を変更できます。" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NG ポータル::" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "ここで、Lemonldap::NG ポータルの URL を設定してください。Lemonldap::NG マネー" "ジャを使えば、後ほどこの値を変更できます。" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "LDAP サーバのポート番号:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "ここで、LDAP サーバが使うポート番号を設定してください。Lemonldap::NG マネー" "ジャを使えば、後ほどこの値を変更できます。" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "LDAP 検索ベース:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "ここで、LDAP クエリで利用する検索ベースを設定してください。Lemonldap::NG マ" "ネージャを使えば、後ほどこの値を変更できます。" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP アカウント:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "ここで、LDAP のリクエストに対して Lemonldap::NG が使う必要があるアカウントを" "設定してください。この欄を空白のままにしておくと、Lemonldap::NG は匿名での接" "続を行うようになります。Lemonldap::NG マネージャを使えば、後ほどこの値を変更" "できます。" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP パスワード:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Lemonldap::NG の LDAP アカウントのパスワードをここで設定してください。" "Lemonldap::NG マネージャを使えば、後ほどこの値を変更できます。" #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Lemonldap::NG の設定ファイルが変更されました。設定の移行を試してますか?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/nl.po000066400000000000000000000113401325274564300247740ustar00rootroot00000000000000# Dutch translation of lemonldap-ng debconf templates. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the lemonldap-ng package. # Frans Spiesschaert , 2014. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2014-10-09 15:09+0200\n" "Last-Translator: Frans Spiesschaert \n" "Language-Team: Debian Dutch l10n Team \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP-server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Vul hier de naam of het IP-adres in van de LDAP-server die gebruikt moet " "worden door Lemonldap::NG. U kunt deze waarde later wijzigen met behulp van " "de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "DNS-domein van Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Vul hier het hoofddomein in dat door Lemonldap::NG beschermd wordt. U kunt " "deze waarde later wijzigen met behulp van de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Webportaal van Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Vul hier de URL in van het portaal van Lemonldap::NG. U kunt deze waarde " "later wijzigen met behulp van de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Poort van de LDAP-server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Vul hier de poort in die door de LDAP-server gebruikt wordt. U kunt deze " "waarde later wijzigen met behulp van de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Zoekbasis van LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Vul hier de zoekbasis in die gebruikt moet worden bij LDAP-opzoekingen. U " "kunt deze waarde later wijzigen met behulp van de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP-account:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Vul hier het account in dat Lemonldap::NG moet gebruiken bij zijn " "opzoekingen in LDAP. Indien u dit leeg laat, zal Lemonldap::NG gebruik maken " "van anonieme verbindingen. U kunt deze waarde later wijzigen met behulp van " "de Lemonldap::NG manager." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP-wachtwoord:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Vul hier het wachtwoord in voor het LDAP-account van Lemonldap::NG. U kunt " "deze waarde later wijzigen met behulp van de Lemonldap::NG manager." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "De configuratiebestanden van Lemonldap::NG zijn veranderd. Proberen uw " "bestanden om te zetten?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/pt.po000066400000000000000000000114511325274564300250110ustar00rootroot00000000000000# translation of lemonldap-ng debconf to Portuguese # Copyright (C) 2007 Américo Monteiro # This file is distributed under the same license as the lemonldap-ng package. # # Américo Monteiro , 2007, 2010. msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0-1\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2010-12-04 21:13+0000\n" "Last-Translator: Américo Monteiro \n" "Language-Team: Portuguese \n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 1.0\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "Servidor LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Indique aqui o nome ou endereço IP do servidor LDAP que vai ser usado pelo " "Lemonldap::NG. Você pode modificar este valor posteriormente usando o gestor " "Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Domínio DNS do Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Indique aqui o domínio principal protegido pelo Lemonldap::NG. Você pode " "modificar este valor posteriormente usando o gestor Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Portal do Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Indique aqui o URL do portal do Lemonldap::NG. Você pode modificar este " "valor posteriormente usando o gestor Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Porto do servidor LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Indique aqui o porto usado pelo servidor LDAP. Você pode modificar este " "valor posteriormente usando o gestor Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Base de busca LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Indique aqui a base de busca a usar em pesquisas LDAP: Você pode modificar " "este valor posteriormente usando o gestor Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "Conta LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Indique aqui a conta que o Lemonldap::NG terá que usar para os seus pedidos " "LDAP. Deixar isto vazio causa a utilização de ligações anónimas pelo " "Lemonldap::NG. Você pode modificar este valor posteriormente usando o gestor " "Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "Palavra passe do LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Indique aqui a palavra passe para a conta do Lemonldap::NG. Você pode " "modificar este valor posteriormente usando o gestor Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Os ficheiros de configuração do Lemonldap::NG foram alterados, tentar migrar " "os seus ficheiros?" #, fuzzy #~| msgid "Lemonldap::NG DNS domain:" #~ msgid "Lemonldap::NG migration:" #~ msgstr "Domínio DNS do Lemonldap::NG:" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/pt_BR.po000066400000000000000000000112301325274564300253670ustar00rootroot00000000000000# Debconf translations for lemonldap-ng. # Copyright (C) 2012 THE lemonldap-ng'S COPYRIGHT HOLDER # This file is distributed under the same license as the lemonldap-ng package. # Adriano Rafael Gomes , 2012. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.2.2-1\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2012-11-15 18:04-0200\n" "Last-Translator: Adriano Rafael Gomes \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "Servidor LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Informe aqui o nome ou endereço IP do servidor LDAP que deve ser usado pelo " "Lemonldap::NG. Você pode modificar esse valor mais tarde usando o " "gerenciador do Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Domínio DNS do Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Informe aqui o domínio principal protegido pelo Lemonldap::NG. Você pode " "modificar esse valor mais tarde usando o gerenciador do Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Portal do Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Informe aqui a URL do portal do Lemonldap::NG. Você pode modificar esse " "valor mais tarde usando o gerenciador do Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Porta do servidor LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Informe aqui a porta usada pelo servidor LDAP. Você pode modificar esse " "valor mais tarde usando o gerenciador do Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Base de busca LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Informe aqui a base de busca (\"search base\") para usar nas pesquisas LDAP. " "Você pode modificar esse valor mais tarde usando o gerenciador do Lemonldap::" "NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "Conta LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Informe aqui a conta que o Lemonldap::NG deve usar para suas requisições " "LDAP. Deixá-la em branco faz com que o Lemonldap::NG use conexões anônimas. " "Você pode modificar esse valor mais tarde usando o gerenciador do Lemonldap::" "NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "Senha LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Informe aqui a senha para a conta LDAP do Lemonldap::NG. Você pode modificar " "esse valor mais tarde usando o gerenciador do Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Os arquivos de configuração do Lemonldap::NG mudaram, tentar migrar os seus " "arquivos?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/ru.po000066400000000000000000000126531325274564300250210ustar00rootroot00000000000000# translation of lemonldap-ng_0.9.4-1_ru.po to Russian # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Yuri Kozlov , 2009, 2010. msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0-2\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2010-12-08 21:11+0300\n" "Last-Translator: Yuri Kozlov \n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 1.0\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "Сервер LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Введите имя или IP-адрес сервера LDAP, который будет использовать Lemonldap::" "NG. Позже вы можете изменить это значение через менеджер Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "DNS домен Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Введите главный домен, защищаемый Lemonldap::NG. Позже вы можете изменить " "это значение через менеджер Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Портал Lemonldap::NG:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Введите URL портала Lemonldap::NG. Позже вы можете изменить это значение " "через менеджер Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Порт сервера LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Введите используемый порт сервера LDAP. Позже вы можете изменить это " "значение через менеджер Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "База поиска LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Введите поисковую базу, используемую в запросах LDAP. Позже вы можете " "изменить это значение через менеджер Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "Учётная запись в LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Введите имя учётной записи, которое Lemonldap::NG будет использовать в " "запросах LDAP. Оставьте поле пустым, если Lemonldap::NG должен подключаться " "анонимно. Позже вы можете изменить это значение через менеджер Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "Пароль к LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Введите пароль к учётной записи Lemonldap::NG в LDAP. Позже вы можете " "изменить это значение через менеджер Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Файлы настройки lemonldap::NG были изменены, попытаться использовать ваши " "файлы?" #, fuzzy #~| msgid "Lemonldap::NG DNS domain:" #~ msgid "Lemonldap::NG migration:" #~ msgstr "DNS домен Lemonldap::NG:" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/sk.po000066400000000000000000000111261325274564300250020ustar00rootroot00000000000000# Slovak translations for lemonldap-ng package # Slovenské preklady pre balík lemonldap-ng. # Copyright (C) 2011 THE lemonldap-ng'S COPYRIGHT HOLDER # This file is distributed under the same license as the lemonldap-ng package. # Automatically generated, 2011. # Slavko , 2011. # msgid "" msgstr "" "Project-Id-Version: lemonldap-ng 1.0.1-1\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2011-03-22 16:49+0100\n" "Last-Translator: Slavko \n" "Language-Team: Slovak \n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Zadajte meno alebo IP adresu servera LDAP, ktorý má byť použitý Lemonldap::" "NG. Túto hodnotu môžete neskôr zmeniť pomocou manažéra Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Lemonldap::NG DNS doména:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Zadajte doménu, ktorú bude chrániť Lemonldap::NG. Túto hodnotu môžete neskôr " "zmeniť pomocou manažéra Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NG portál:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Zadajte URL portálu Lemonldap::NG portal. Túto hodnotu môžete neskôr zmeniť " "pomocou manažéra Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "Port LDAP servera:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Zadajte port používaný serverom LDAP. Túto hodnotu môžete neskôr zmeniť " "pomocou manažéra Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Koreň LDAP vyhľadávania:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Zadajte koreň stromu LDAP, používaný vo vyhľadávacích dopytoch. Túto hodnotu " "môžete neskôr zmeniť pomocou manažéra Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP účet:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Zdajte účet, ktorý má Lemonldap::NG používať na dopyty LDAP. Ak pole necháte " "prázdne, Lemonldap::NG bude používať anonymné spojenie. Túto hodnotu môžete " "neskôr zmeniť pomocou manažéra Lemonldap::NG." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP heslo:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Zadajte heslo LDAP účtu Lemonldap::NG. Túto hodnotu môžete neskôr zmeniť " "pomocou manažéra Lemonldap::NG." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Konfiguračné súbory Lemonldap::NG boli zmenené, skúsiť previesť vaše súbory?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/sv.po000066400000000000000000000110261325274564300250140ustar00rootroot00000000000000# translation of lemonldap-ng_0.9.1-1_templates.po to swedish # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Martin Bagge , 2008. msgid "" msgstr "" "Project-Id-Version: lemonldap-ng_0.9.1-1_templates\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: 2011-01-03 14:00+0100\n" "Last-Translator: Martin Bagge / brother \n" "Language-Team: swedish \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "X-Poedit-Language: Swedish\n" "X-Poedit-Country: Sweden\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "LDAP-server:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" "Här anger du namnet eller IP-adressen på den LDAP-server som Lemonldap::NG " "ska använda. Du kan ändra detta vid ett senare tillfälle." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "Lemonldap::NG DNS domän:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" "Här anger du huvuddomänen som Lemonldap::NG ska skydda. Detta kan värde kan " "ändras vid ett senare tillfälle." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "Lemonldap::NG-portal:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" "Här anger du adressen till Lemonldap::NG-portalen. Detta värde kan ändras " "vid ett senare tillfälle." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "LDAP-server-port:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" "Här anger du porten som LDAP-servern ska använda. Detta värde kan ändras vid " "ett senare tillfälle." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "Sökbas för LDAP:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" "Här anger du basen vid LDAP-frågor. Detta värde kan ändras vid ett senare " "tillfälle." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "LDAP-konto:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" "Här anger du användarnamnet som Lemonldap::NG använder för att ställa LDAP-" "frågor. Lämna fältet tomt för att låta Lemonldap::NG ansluta anonymt. Detta " "värde kan ändras vid ett senare tillfälle." #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "LDAP-lösenord:" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" "Här anger du det lösenord som Lemonldap::NG använder för att ställa LDAP-" "frågor. Detta värde kan ändras vid ett senare tillfälle." #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" "Inställningsfilerna för Lemonldap::NG har ändrat format, vill försöka " "migrera filerna till nya formatet?" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/po/templates.pot000066400000000000000000000063141325274564300265520ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: lemonldap-ng@packages.debian.org\n" "POT-Creation-Date: 2010-12-04 23:10+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "LDAP server:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:1001 msgid "" "Set here name or IP address of the LDAP server that has to be used by " "Lemonldap::NG. You can modify this value later using the Lemonldap::NG " "manager." msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "Lemonldap::NG DNS domain:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:2001 msgid "" "Set here the main domain protected by Lemonldap::NG. You can modify this " "value later using the Lemonldap::NG manager." msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "Lemonldap::NG portal:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:3001 msgid "" "Set here the Lemonldap::NG portal URL. You can modify this value later using " "the Lemonldap::NG manager." msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "LDAP server port:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:4001 msgid "" "Set here the port used by the LDAP server. You can modify this value later " "using the Lemonldap::NG manager." msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "LDAP search base:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:5001 msgid "" "Set here the search base to use in LDAP queries. You can modify this value " "later using the Lemonldap::NG manager." msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "LDAP account:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:6001 msgid "" "Set here the account that Lemonldap::NG has to use for its LDAP requests. " "Leaving it blank causes Lemonldap::NG to use anonymous connections. You can " "modify this value later using the Lemonldap::NG manager." msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "LDAP password:" msgstr "" #. Type: string #. Description #: ../liblemonldap-ng-common-perl.templates:7001 msgid "" "Set here the password for the Lemonldap::NG LDAP account. You can modify " "this value later using the Lemonldap::NG manager." msgstr "" #. Type: boolean #. Description #: ../liblemonldap-ng-common-perl.templates:8001 msgid "" "Lemonldap::NG configuration files have changed, try to migrate your files?" msgstr "" lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/rules000077500000000000000000000062111325274564300244660ustar00rootroot00000000000000#!/usr/bin/make -f # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 LMSHAREDIR=/usr/share/lemonldap-ng LMVARDIR =/var/lib/lemonldap-ng TMP = $(CURDIR)/debian/tmp CONFDIR=/etc/lemonldap-ng SESSIONSDIR=$(LMVARDIR)/sessions PSESSIONSDIR=$(LMVARDIR)/psessions NOTIFICATIONSDIR=$(LMVARDIR)/notifications CONFSTORAGEDIR=$(LMVARDIR)/conf FIRSTCONFFILE=$(CONFSTORAGEDIR)/lmConf-1.js LMINIFILE=$(CONFDIR)/lemonldap-ng.ini CAPTCHADIR=$(LMVARDIR)/captcha %: dh $@ --with systemd override_dh_auto_configure: $(MAKE) configure STORAGECONFFILE=/etc/lemonldap-ng/lemonldap-ng.ini \ PERLOPTIONS="INSTALLDIRS=vendor" override_dh_auto_build: $(MAKE) all override_dh_auto_install: $(MAKE) install \ DESTDIR=$(CURDIR)/debian/tmp \ PREFIX=/usr \ LMPREFIX=/usr/share/lemonldap-ng/ \ BINDIR=$(LMSHAREDIR)/bin \ SBINDIR=/usr/sbin \ FASTCGISOCKDIR=/var/run/llng-fastcgi-server \ DOCUMENTROOT=$(LMVARDIR) \ EXAMPLESDIR=/examples \ HANDLERDIR=$(LMVARDIR)/handler \ PORTALSKINSDIR=$(LMSHAREDIR)/portal-skins \ MANAGERDIR=$(LMSHAREDIR)/manager \ STORAGECONFFILE=/etc/lemonldap-ng/lemonldap-ng.ini \ TOOLSDIR=$(LMSHAREDIR)/ressources \ CONFDIR=/etc/lemonldap-ng \ CRONDIR=/etc/cron.d \ DATADIR=$(LMVARDIR) \ APACHEUSER=www-data \ APACHEGROUP=www-data \ DEFDOCDIR=/usr/share/doc/lemonldap-ng-doc \ FRDOCDIR=/usr/share/doc/lemonldap-ng-fr-doc \ PROD=yes $(MAKE) install_fr_doc_site \ DESTDIR=$(CURDIR)/debian/tmp \ DOCDIR=/usr/share/doc/lemonldap-ng-fr-doc \ PROD=yes rm -rvf debian/tmp/usr/share/doc/lemonldap-ng-fr-doc/fr-doc/pages/documentation/current/documentation \ debian/tmp/usr/share/doc/lemonldap-ng-fr-doc/fr-doc/pages/documentation/current/icons \ debian/tmp/usr/share/doc/lemonldap-ng-fr-doc/fr-doc/pages/documentation/current/lib mkdir $(TMP)/$(LMSHAREDIR)/portal mv $(TMP)/$(LMVARDIR)/portal/*.pl $(TMP)/$(LMSHAREDIR)/portal/ for i in handler portal manager test; do \ mv $(TMP)/etc/lemonldap-ng/$$i-apache2.X.conf $(TMP)/etc/lemonldap-ng/$$i-apache2.conf; \ done override_dh_compress: dh_compress -X favicon.ico # Fix lemonldap-ng dirs permissions and owner since dh_fixperms change them: # * global configuration dirs must be writable by www-data but not readable # by all (also sessions, captcha,... dirs) # * lemonldap-ng.ini must not be readable by all override_dh_fixperms: dh_fixperms chown www-data:www-data \ debian/*/$(SESSIONSDIR) \ debian/*/$(SESSIONSDIR)/lock \ debian/*/$(PSESSIONSDIR) \ debian/*/$(PSESSIONSDIR)/lock \ debian/*/$(NOTIFICATIONSDIR) \ debian/liblemonldap-ng-common-perl/$(CONFSTORAGEDIR) \ debian/liblemonldap-ng-portal-perl/$(CAPTCHADIR) chgrp www-data debian/liblemonldap-ng-common-perl/$(LMINIFILE) \ debian/liblemonldap-ng-common-perl/$(FIRSTCONFFILE) chmod 770 debian/*/$(SESSIONSDIR) debian/*/$(SESSIONSDIR)/lock \ debian/*/$(PSESSIONSDIR) debian/*/$(PSESSIONSDIR)/lock \ debian/*/$(NOTIFICATIONSDIR) \ debian/liblemonldap-ng-portal-perl/$(CAPTCHADIR) chmod 750 debian/liblemonldap-ng-common-perl/$(CONFSTORAGEDIR) chmod 640 debian/liblemonldap-ng-common-perl/$(FIRSTCONFFILE) \ debian/liblemonldap-ng-common-perl/$(LMINIFILE) lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/source/000077500000000000000000000000001325274564300247065ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/source/format000066400000000000000000000000141325274564300261140ustar00rootroot000000000000003.0 (quilt) lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/source/lintian-overrides000066400000000000000000000017101325274564300302660ustar00rootroot00000000000000# False positive: these long lines are in source files lemonldap-ng source: source-is-missing lemonldap-ng-manager/site/static/bwr/angular-bootstrap/ui-bootstrap-tpls.js line length is 680 characters (>512) lemonldap-ng source: source-is-missing lemonldap-ng-manager/site/static/bwr/es5-shim/es5-shim.js line length is 290 characters (>256) # Very simple source file (but serialized) lemonldap-ng source: source-is-missing doc/pages/documentation/current/lib/exe/js.php.t.bootstrap3.js line length is 1787 characters (>512) lemonldap-ng source: source-is-missing po-doc/fr/pages/documentation/current/lib/exe/js.php.t.bootstrap3.js line length is 1787 characters (>512) # Unused libraries in debian packages lemonldap-ng source: source-is-missing doc/pages/documentation/current/lib/tpl/bootstrap3/assets/bootstrap/js/bootstrap.min.js lemonldap-ng source: source-is-missing po-doc/fr/pages/documentation/current/lib/tpl/bootstrap3/assets/bootstrap/js/bootstrap.min.js lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/000077500000000000000000000000001325274564300245505ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/control000066400000000000000000000007341325274564300261570ustar00rootroot00000000000000Test-Command: ./debian/tests/runner build-deps Depends: @, @builddeps@, pkg-perl-autopkgtest, libmouse-perl Test-Command: ./debian/tests/runner runtime-deps Depends: @, pkg-perl-autopkgtest, libmouse-perl Test-Command: ./debian/tests/runner runtime-deps-and-recommends Depends: @, pkg-perl-autopkgtest, libmouse-perl Restrictions: needs-recommends #Test-Command: ./debian/tests/runner heavy-deps #Depends: @, pkg-perl-autopkgtest, pkg-perl-autopkgtest-heavy, libmouse-perl lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/lib/000077500000000000000000000000001325274564300253165ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/lib/build-deps.d/000077500000000000000000000000001325274564300275705ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/lib/build-deps.d/smoke000077500000000000000000000066621325274564300306460ustar00rootroot00000000000000#!/bin/sh set -e cleanup() { cd $PDIR #Clean up after smoke-setup cleanup_file=debian/tests/pkg-perl/smoke-cleanup if [ -x $cleanup_file ] then ( export TDIR; $cleanup_file ) fi rm -rf $TDIR } TEMP=${ADTTMP:-${TMPDIR:-/tmp}} TDIR=$(mktemp -d $TEMP/smokeXXXXXX) PDIR=`pwd` trap cleanup EXIT file_list=debian/tests/pkg-perl/smoke-files if [ ! -r $file_list ]; then # backward compatibility squared for now file_list=debian/tests/pkg-perl/test-files fi if [ ! -r $file_list ]; then # backward compatibility for now file_list=debian/tests/test-files fi export AUTOMATED_TESTING=1 export NONINTERACTIVE_TESTING=1 # overridable with smoke-env PKG_PERL_PROVE_ARGS="--merge" env_list=debian/tests/pkg-perl/smoke-env if [ ! -r $env_list ]; then env_list=debian/tests/pkg-perl/env-smoke fi if [ -r $env_list ]; then eval $(sed '/^ *\(#\|$\)/d; s/^/export /' $env_list) fi for dir in common handler portal manager; do TDIR2=${TDIR}/lemonldap-ng-$dir mkdir -p $TDIR2/blib/lib \ $TDIR2/blib/arch \ $TDIR2/lib/Debian/pkgperl \ $TDIR2/blib/lib/Debian/pkgperl cp -a lemonldap-ng-$dir/t $TDIR2/ if [ -d lemonldap-ng-$dir/site ]; then cp -a lemonldap-ng-$dir/site $TDIR2/ fi cat <<'EOF' > $TDIR2/lib/Debian/pkgperl/Foobar.pm package Debian::pkgperl::Foobar; our $VERSION = '0.01'; 1; __END__ =head1 NAME Debian::pkgperl::Foobar - dummy module for test checkers =cut EOF cp $TDIR2/lib/Debian/pkgperl/Foobar.pm $TDIR2/blib/lib/Debian/pkgperl if [ ! -e $TDIR2/MANIFEST ]; then cat <<'EOF' > $TDIR2/MANIFEST lib/Debian/pkgperl/Foobar.pm EOF fi if [ ! -e $TDIR2/MANIFEST.SKIP ]; then cp /dev/null $TDIR2/MANIFEST.SKIP fi done # Missing file cp lemonldap-ng-common/lemonldap-ng.ini ${TDIR}/lemonldap-ng-common # Test that have no interest rm -f $TDIR/*/t/99-pod.t \ $TDIR/lemonldap-ng-manager/t/03-HTML-forms.t \ $TDIR/lemonldap-ng-manager/t/20-test-coverage.t \ $TDIR/lemonldap-ng-manager/t/80-attributes.t \ $TDIR/lemonldap-ng-manager/t/90-translations.t for INI in $(find $TDIR/lemonldap-ng-* -name lemonldap-ng.ini); do echo "Change $INI logLevel to debug" perl -pi -e 's/^logLevel\s*=\s*\w+$/logLevel=debug/' $INI || true done # For now, too many bugs with Moose in autopkg jail, so manager isn't tested for dir in common handler portal manager; do TDIR2=${TDIR}/lemonldap-ng-$dir # this is intended to be a last resort, please use it responsibly setup_file=debian/tests/pkg-perl/smoke-setup if [ -x $setup_file ] then ( export TDIR2; $setup_file ) # Evaluate skip list a second time since smoke-setup might have # generated some of the to-be-skipped files. if [ -r $skip_list ]; then egrep -v '^ *(#|$)' $skip_list | while read file; do rm -f $TDIR2/$file done fi fi tests_file=$(pwd)/debian/tests/pkg-perl/smoke-tests cd $TDIR2 if [ -r $tests_file ]; then test_targets=$(eval ls -d $(egrep -v '^#' $tests_file) 2>/dev/null || true) fi if command -v xvfb-run >/dev/null then XVFB="xvfb-run -a" else XVFB= fi test_targets=$(ls -d t/*.t 2>/dev/null || true) if [ ! -n "$test_targets" ]; then echo 'Nothing to prove, skipping.' else $XVFB prove -I"$TDIR2" --blib --verbose $PKG_PERL_PROVE_ARGS $test_targets 2>&1 fi cd - done runtime-deps-and-recommends.d/000077500000000000000000000000001325274564300327675ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/libsyntax.t000066400000000000000000000051721325274564300345070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/lib/runtime-deps-and-recommends.d#!/usr/bin/perl -w use strict; use Test::More; use Getopt::Std; sub usage { my $exit = shift; $exit = 0 if !defined $exit; print "Usage: $0 [ package ] ...\n"; print "\tpackages are read from debian/control if not given as arguments\n"; exit $exit; } my %opts; getopts('h', \%opts) or usage(); usage(0) if $opts{h}; sub getpackages { my @p; my $c = "debian/control"; -f $c or BAIL_OUT("no packages listed and $c not found"); open(C, '<', $c) or BAIL_OUT("opening $c for reading failed:$!"); while () { chomp; /^\s*Package:\s+(\S+)/ and push @p, $1; } close C or BAIL_OUT("closing $c failed: $!"); return @p; } sub readskip { my $skip = "debian/tests/pkg-perl/syntax-skip"; $skip = "debian/tests/pkg-perl/skip-syntax" if ! -r $skip; -r $skip or return (); open (S, '<', $skip) or BAIL_OUT("$skip exists but can't be read"); my @ret; while () { chomp; next if !/\S/; next if /^\s*#/; push @ret, qr/\Q$_\E/; } close S; return @ret; } my @packages = @ARGV ? @ARGV : getpackages(); usage() if !@packages; plan tests => 4 * scalar @packages; my @to_skip = readskip(); for my $package (@packages) { SKIP: { ok(!system(qq(dpkg-query --list "$package" >/dev/null 2>&1)), "Package $package is known to dpkg"); skip "$package not installed", 3 if $?; my @status_info = qx{dpkg-query --status "$package"}; ok(@status_info, "Got status information for package $package"); skip "$package has Suggestions and no explicit skip list", 2 if grep /^Suggests:/, @status_info and ! @to_skip; my @files = qx{dpkg-query --listfiles "$package" 2>/dev/null}; ok(@files, "Got file list for package $package"); skip "nothing to test", 1 if !@files; my @pms; F: for (@files) { chomp; next if !/\.pm$/; for my $skip_re (@to_skip) { note "skipping $_", next F if /$skip_re/; } my $oninc = 0; for my $incdir (@INC) { $oninc++, last if /^\Q$incdir/; } next if !$oninc; push @pms, $_; } skip "no perl modules to test in $package", 1 if !@pms; subtest "all modules in $package pass the syntax check" => sub { plan tests => scalar @pms; for (@pms) { my @out = grep !/syntax OK/, qx($^X -wc $_ 2>&1); note(@out) if @out; ok(!$?, "$^X -wc $_ exited successfully"); } } } } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/lib/runtime-deps.d/000077500000000000000000000000001325274564300301545ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/lib/runtime-deps.d/use.t000066400000000000000000000034611325274564300311410ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use Test::More; use Getopt::Std; use CPAN::Meta; sub usage { my $exit = shift; $exit = 0 if !defined $exit; print "Usage: $0 [ Some::Module] ...\n"; print "\tthe perl module is guessed from META.{json,yml} if not given as argument\n"; exit $exit; } my %opts; getopts('h', \%opts) or usage(); usage(0) if $opts{h}; sub getmodule { my $module; my $conffile = "debian/tests/pkg-perl/use-name"; $conffile = "debian/tests/pkg-perl/module-name" if ! -e $conffile; # backcompat $conffile = "debian/tests/module-name" if ! -e $conffile; # backcompat squared if ( -f $conffile ) { open(M, "<", $conffile) or BAIL_OUT("$conffile exists but can't be read"); while () { chomp; next if /^\s*#/; /(\S+)/ and $module = $1, last; } close M; BAIL_OUT("can't find a module name in $conffile") if !defined $module; return $module; } my $meta; my $dist; eval { $meta = CPAN::Meta->load_file('META.json') }; eval { $meta = CPAN::Meta->load_file('META.yml' ) } if !$meta; plan skip_all => "can't guess package from META.json or META.yml" if !$meta; $dist = $meta->name; $module = $dist; $module =~ s,-,::,g; my $file = "$dist.pm"; $file =~ s,-,/,g; my $basefile = $file; $basefile =~ s,.*/,,; ( -f $basefile ) or (-f "lib/$file") or plan skip_all => "$basefile or lib/$file not found"; return $module; } my @modules = @ARGV ? @ARGV : getmodule(); usage() if !@modules; plan tests => 2 * scalar @modules; for my $mod (@modules) { my $cmd = qq($^X -w -M"$mod" -e 1 2>&1); my @out = qx($cmd); note(@out) if @out; ok(!$?, "$cmd exited successfully"); ok(!@out, "$cmd produced no output"); } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/tests/runner000077500000000000000000000012301325274564300260030ustar00rootroot00000000000000#!/bin/sh BASE=debian/tests/lib TYPE=$1 [ -n "$TYPE" ] || exit 1 TESTDIR=${BASE}/${TYPE}.d [ -d "$TESTDIR" ] || exit 1 SKIPLIST=debian/tests/pkg-perl/SKIP SKIPTMP=$(mktemp) if [ -f "$SKIPLIST" ]; then grep -v '^ *#' "$SKIPLIST" |grep -v '^ *$' > "$SKIPTMP" fi EXITCODE=0 for T in $(run-parts --list --regex '(^[a-z0-9.]+$)' ${TESTDIR} | \ grep -v -F -f "$SKIPTMP") ; do if echo "$T" | grep -q '\.t$' then prove --norc -v "$T" RET=$? if [ $EXITCODE = 0 ]; then EXITCODE=$RET; fi else "$T" RET=$? if [ $EXITCODE = 0 ]; then EXITCODE=$RET; fi fi done rm -f "$SKIPTMP" exit $EXITCODE lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/upstream/000077500000000000000000000000001325274564300252465ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/upstream/metadata000066400000000000000000000006451325274564300267560ustar00rootroot00000000000000--- Archive: OW2 Bug-Database: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues Bug-Submit: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues Changelog: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/tags Contact: lemonldap-ng-users@ow2.org Name: LemonLDAP::NG Repository: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng.git Repository-Browse: https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/tree/master lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/debian/watch000066400000000000000000000003021325274564300244320ustar00rootroot00000000000000version=3 opts=filenamemangle=s/.*\/v(\d\S+)\/archive\.tar\.gz/lemonldap-ng-$1\.tar\.gz/g \ https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/tags?sort=updated_desc .*v(\d\S+)/archive\.tar\.gz lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/000077500000000000000000000000001325274564300227315ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/index.html000066400000000000000000000006521325274564300247310ustar00rootroot00000000000000 LemonLDAP::NG offline documentation

LemonLDAP::NG offline documentation


Documentation
lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/000077500000000000000000000000001325274564300240305ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/000077500000000000000000000000001325274564300267015ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/000077500000000000000000000000001325274564300303635ustar00rootroot00000000000000activedirectoryminihowto.html000066400000000000000000000076771325274564300363510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:activedirectoryminihowto

Using LemonLDAP::NG with Active-Directory

Authentication with login/password

To use Active Directory as LDAP backend, you must change few things in the manager :

  • Use “Active Directory” as authentication, userDB and passwordDBbackends,
  • Export sAMAccountName in a variable declared in exported variables
  • Change the user attribute to store in Apache logs (“General Parameters » Logs » REMOTE_USER”): use the variable declared above

Authentication with Kerberos

  • Choose “Apache” as authentication module (“General Parameters » Authentication modules » Authentication module”)
  • Configure the Apache server that host the portal to use the Apache Kerberos authentication module
applications.html000066400000000000000000000401271325274564300336640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:applications

Table of Contents

  • Known supported applications
    • Mail, Agenda, Groupware
    • Wiki
    • CMS, Portal, ECM
    • Bugtracker, Service Management
    • Other
  • Frameworks
  • Connectors
  • SAML connectors

Applications

Known supported applications

Applications listed below are known to be easy to integrate in LL::NG. As LL::NG works like classic WebSSO (like Siteminder™), many other applications are easy to integrate.

Mail, Agenda, Groupware

OBM Sympa Zimbra RoundCube

Wiki

Dokuwiki Mediawiki

CMS, Portal, ECM

Drupal Liferay Alfresco Wordpress

Bugtracker, Service Management

Bugzilla GLPI

Other

GRR phpLDAPadmin LimeSurvey SAP
LimeSurvey SAP
FusionDirectory
fusiondirectory-logo.jpg  

Frameworks

Java (Spring) Python (Django) PHP (Symfony)

Connectors

HTTP Auth-Basic Tomcat Nginx
Some applications using it
Outlook Web App
IBM Lotus iNotes
Probe
Lutece

SAML connectors

This requires to configure LL::NG as an SAML Identity Provider.
Google Apps Cornerstone SalesForce simpleSAMLphp
NextCloud ADFS Office365 AWS
logo_amazon_web_services.jpg
Gitlab
applications/000077500000000000000000000000001325274564300327725ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/currentadfs.html000066400000000000000000000106031325274564300345750ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:adfs

Active Directory Federation Services

Presentation

Microsoft ADFS (Active Directory Federation Services) is an Identity/Service Provider, compatible with several protocols, including SAML 2.0.

This documentation does not explains how to setup ADFS, but give only tricks to make it works with LL::NG

ADFS as Identity Provider

When ADFS is declared as an Identity Provider in LemonLDAP::NG, you need to take care of the following items:

  • HTTPS is mandatory on LL::NG portal
  • You need to use a certificate in LL::NG SAML metadata instead of a raw public key
  • Activate option Use specific query_string method in SAML Service
  • Use SHA1 instead of SHA256 as signature algorithm on ADFS if using a Lasso version < 2.5.0
  • Force SAML response to be sent by POST and not Artifact (signature verification fails with Artifact)
  • Enable Allow proxy authentication in IDP options on LL::NG side
alfresco.html000066400000000000000000001340071325274564300354630ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:alfresco

Table of Contents

  • Presentation
  • HTTP headers
    • Alfresco
    • LL::NG
      • Headers
      • Rules
  • SAML2
    • Alfresco
    • LL::NG
  • Other resources

Alfresco

Presentation

Alfresco is an ECM/BPM software.

Since 4.0 release, it offers an easy way to configure SSO thanks to authentication subsystems.

Authentication against LL::NG can be done trough:

  • HTTP headers (LL::NG Handler)
  • SAML 2 (LL::NG as SAML2 IDP)
Alfresco now recommends SAML2 method

HTTP headers

Alfresco

The official documentation can be found here: http://docs.alfresco.com/4.0/tasks/auth-alfrescoexternal-sso.html

You need to find the following files in your Alfresco installation:

  • alfresco-global.properties (ex: tomcat/shared/classes/alfresco-global.properties)
  • share-config-custom.xml (ex: tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml)

The first will allow one to configure SSO for the alfresco webapp, and the other for the share webapp.

Edit first alfresco-global.properties and add the following:

### SSO ###
authentication.chain=external1:external
external.authentication.enabled=true
external.authentication.defaultAdministratorUserNames=
external.authentication.proxyUserName=
external.authentication.proxyHeader=Auth-User
external.authentication.userIdPattern=

Edit then share-config-custom.xml and uncomment the last part. In the <endpoint>, change <connector-id> value to alfrescoHeader and change the <userHeader> value to Auth-User:

   <config evaluator="string-compare" condition="Remote">
      <remote>
          <keystore>
             <path>alfresco/web-extension/alfresco-system.p12</path>
             <type>pkcs12</type>
             <password>alfresco-system</password>
         </keystore>
 
         <connector>
            <id>alfrescoCookie</id>
            <name>Alfresco Connector</name>
            <description>Connects to an Alfresco instance using cookie-based authentication</description>
            <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class>
         </connector>
 
         <connector>
            <id>alfrescoHeader</id>
            <name>Alfresco Connector</name>
            <description>Connects to an Alfresco instance using header and cookie-based authentication</description>
            <class>org.alfresco.web.site.servlet.SlingshotAlfrescoConnector</class>
            <userHeader>Auth-User</userHeader>
         </connector>
 
         <endpoint>
            <id>alfresco</id>
            <name>Alfresco - user access</name>
            <description>Access to Alfresco Repository WebScripts that require user authentication</description>
            <connector-id>alfrescoHeader</connector-id>
            <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url>
            <identity>user</identity>
            <external-auth>true</external-auth>
         </endpoint>
      </remote>
   </config>

You need to restart Tomcat to apply changes.

Now you can log in with a simple HTTP header. You need to restrict access to Alfresco to LL::NG.

LL::NG

Headers

Just set the Auth-User header with the attribute that carries the user login, for example $uid.

Rules

Set the default rule to what you need.

Other rules:

  • Unprotect access to some resources: ^/share/res ⇒ unprotect
  • Catch logout: ^/share/page/dologout ⇒ logout_app_sso

SAML2

Alfresco

Install SAML Alfresco module package:

cp alfresco-saml-repo-1.0.1.amp <ALFRESCO_HOME>/amps
cp alfresco-saml-share-1.0.1.amp <ALFRESCO_HOME>/amps_share
./bin/apply_amp.sh

Generate SAML certificate:

keytool -genkeypair -alias my-saml-key -keypass change-me -storepass change-me -keystore my-saml.keystore -storetype JCEKS

Export the keystore:

mv my-saml.keystore alf_data/keystore
cat <<EOT > alf_data/keystore/my-saml.keystore-metadata.properties
aliases=my-saml-key
keystore.password=change-me
my-saml-key.password=change-me
EOT
cat <<EOT >> tomcat/shared/classes/alfresco-global.properties

saml.keystore.location=\${dir.keystore}/my-saml.keystore
saml.keystore.keyMetaData.location=\${dir.keystore}/my-saml.keystore-metadata.properties
EOT

Edit then share-config-custom.xml:

    ...
        <config evaluator="string-compare" condition="CSRFPolicy" replace="true">
 
 
 
        <!--
            If using https make a CSRFPolicy with replace="true" and override the properties section.
            Note, localhost is there to allow local checks to succeed.
 
 
 
            I.e.
            <properties>
                <token>Alfresco-CSRFToken</token>
                <referer>https://your-domain.com/.*|http://localhost:8080/.*</referer>
                <origin>https://your-domain.com|http://localhost:8080</origin>
            </properties>
        -->
 
 
 
            <filter>
 
 
 
                <!-- SAML SPECIFIC CONFIG -  START -->
 
 
 
                <!--
                 Since we have added the CSRF filter with filter-mapping of "/*" we will catch all public GET's to avoid them
                 having to pass through the remaining rules.
                 -->
                <rule>
                    <request>
                        <method>GET</method>
                        <path>/res/.*</path>
                    </request>
                </rule>
 
 
 
                <!-- Incoming posts from IDPs do not require a token -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/saml-authnresponse|/page/saml-logoutresponse|/page/saml-logoutrequest</path>
                    </request>
                </rule>
 
 
 
                <!-- SAML SPECIFIC CONFIG -  STOP -->
 
 
 
                <!-- EVERYTHING BELOW FROM HERE IS COPIED FROM share-security-config.xml -->
 
 
 
                <!--
                 Certain webscripts shall not be allowed to be accessed directly form the browser.
                 Make sure to throw an error if they are used.
                 -->
                <rule>
                    <request>
                        <path>/proxy/alfresco/remoteadm/.*</path>
                    </request>
                    <action name="throwError">
                        <param name="message">It is not allowed to access this url from your browser</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Certain Repo webscripts should be allowed to pass without a token since they have no Share knowledge.
                 TODO: Refactor the publishing code so that form that is posted to this URL is a Share webscript with the right tokens.
                 -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/proxy/alfresco/api/publishing/channels/.+</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Certain Surf POST requests from the WebScript console must be allowed to pass without a token since
                 the Surf WebScript console code can't be dependent on a Share specific filter.
                 -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/caches/dependency/clear|/page/index|/page/surfBugStatus|/page/modules/deploy|/page/modules/module|/page/api/javascript/debugger|/page/console</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!-- Certain Share POST requests does NOT require a token -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/dologin(\?.+)?|/page/site/[^/]+/start-workflow|/page/start-workflow|/page/context/[^/]+/start-workflow</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!-- Assert logout is done from a valid domain, if so clear the token when logging out -->
                <rule>
                    <request>
                        <method>POST</method>
                        <path>/page/dologout(\?.+)?</path>
                    </request>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                    <action name="clearToken">
                        <param name="session">{token}</param>
                        <param name="cookie">{token}</param>
                    </action>
                </rule>
 
 
 
                <!-- Make sure the first token is generated -->
                <rule>
                    <request>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                            <attribute name="{token}"/>
                            <!-- empty attribute element indicates null, meaning the token has not yet been set -->
                        </session>
                    </request>
                    <action name="generateToken">
                        <param name="session">{token}</param>
                        <param name="cookie">{token}</param>
                    </action>
                </rule>
 
 
 
                <!-- Refresh token on new "page" visit when a user is logged in -->
                <rule>
                    <request>
                        <method>GET</method>
                        <path>/page/.*</path>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                            <attribute name="{token}">.+</attribute>
                        </session>
                    </request>
                    <action name="generateToken">
                        <param name="session">{token}</param>
                        <param name="cookie">{token}</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Verify multipart requests from logged in users contain the token as a parameter
                 and also correct referer & origin header if available
                 -->
                <rule>
                    <request>
                        <method>POST</method>
                        <header name="Content-Type">multipart/.+</header>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                        </session>
                    </request>
                    <action name="assertToken">
                        <param name="session">{token}</param>
                        <param name="parameter">{token}</param>
                    </action>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
 
 
 
                <!--
                 Verify that all remaining state changing requests from logged in users' requests contains a token in the
                 header and correct referer & origin headers if available. We "catch" all content types since just setting it to
                 "application/json.*" since a webscript that doesn't require a json request body otherwise would be
                 successfully executed using i.e."text/plain".
                 -->
                <rule>
                    <request>
                        <method>POST|PUT|DELETE</method>
                        <session>
                            <attribute name="_alf_USER_ID">.+</attribute>
                        </session>
                    </request>
                    <action name="assertToken">
                        <param name="session">{token}</param>
                        <param name="header">{token}</param>
                    </action>
                    <action name="assertReferer">
                        <param name="referer">{referer}</param>
                    </action>
                    <action name="assertOrigin">
                        <param name="origin">{origin}</param>
                    </action>
                </rule>
            </filter>
        </config>
    ...

Configure SAML service provider using the Alfresco admin console (/alfresco/s/enterprise/admin/admin-saml).

Set the following parameters:

  • Enable SAML Authentication (SSO): on
  • Authentication service URL: https://auth.example.com/saml/singleSignOn
  • Single Logout URL: https://auth.example.com/saml/singleLogout
  • Single logout return URL: https://auth.example.com/saml/singleLogoutReturn
  • Entity identification: http://alfresco.myecm.org:8080/share
  • User ID mapping: Subject/NameID

To finish with Alfresco configuration, tick the “Enable SAML authentication (SSO)” box.

LL::NG

Configure SAML service and set a certificate as signature public key in metadata.

Export Alfresco SAML Metadata from admin console and import them in LL::NG.

In the authentication response option, set:

  • Default NameID Format: Unspecified
  • Force NameID session key: uid

And you can define these exported attributes:

  • GivenName
  • Surname
  • Email

Other resources

  • DevCon 2012: Unlocking the Secrets of Alfresco Authentication, Mehdi Belmekki
  • Setting up Alfresco SAML authentication with LemonLDAP::NG
authbasic.html000066400000000000000000000136351325274564300356330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:authbasic

HTTP Basic Authentication

Presentation

For now, this feature is only supported by Apache handler.

Extract from the Wikipedia article:

In the context of an HTTP transaction, the basic access authentication is a method designed to allow a web browser, or other client program, to provide credentials – in the form of a user name and password – when making a request.

Before transmission, the username and password are encoded as a sequence of base-64 characters. For example, the user name Aladdin and password open sesame would be combined as Aladdin:open sesame – which is equivalent to QWxhZGRpbjpvcGVuIHNlc2FtZQ== when encoded in Base64. Little effort is required to translate the encoded string back into the user name and password, and many popular security tools will decode the strings “on the fly”.

So HTTP Basic Authentication is managed trough an HTTP header (Authorization), that can be forged by LL::NG, with this precautions:

  • Data should not contains accents or special characters, as HTTP protocol only allow ASCII values in header (but depending on the HTTP server, you can use ISO encoded values)
  • You need to forward the password, which can be the user main password (if password is stored in session, or any user attribute (if you keep secondary passwords in users database).

Configuration

The Basic Authentication relies on a specific HTTP header, as described above. So you have just to declare this header for the virtual host in Manager.

For example, to forward login ($uid) and password ($_password if password is stored in session):

Authorization => "Basic ".encode_base64("$uid:$_password", "")
Don't forget to add an empty string as second argument of encode_base64 to avoid insert of “newline” characters

LL::NG provides a special function named basic to build this header.

So the above example can also be written like this:

Authorization => basic($uid,$_password)
The basic function will also force conversion from UTF-8 to ISO-8859-1, which should be accepted by most of HTTP servers.
aws.html000066400000000000000000000241031325274564300344520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:aws

Amazon Web Services

Amazon Web Services allows to delegate authentication through SAML2.

SAML

  • Make sure you have followed the steps here.
  • Go to https://your.portal.com/saml/metadata and save the resulting file locally.
  • In each AWS account, go to IAM → Identity providers → Create Provider.
  • Select SAML as the provider type
  • Choose a name (best if kept consistent between accounts), and then choose the metadata file you saved above.
  • Looking again at the links on the left side of the page, go to Roles → Create role
  • Choose SAML / Saml 2.0 federation
  • Select the provider you just configured, click Allow programmatic and AWSManagement Console access which will fill in the rest of the form for you, then click next.
  • Set whatever permissions you need to and then click Review.
  • Choose a name for the role. These will shown to people when they log in, so make them descriptive. We have different accounts for different regions of the world, so I put the region into the role name so people know which account is which.
If you have only one role, the configuration is simple. If you have multiple roles for different people, it is a little trickier. As you will see, the SAML attributes are not dynamic, so you have to set them in the session when a user logs in or use a custom function. In this example, I wanted to avoid managing custom functions on all the servers, so the SAML attributes are set in the session. We also use LDAP for user information, so I will describe that. In our LDAP tree, each user has attributes which are used quite heavily for dynamic groups and authorisation. You will want something similar, using whatever attribute makes sense to you. For example:
  dn: uid=user,ou=people,dc=your,dc=com
  ...
  ou: sysadmin
  ou: database
  ou: root
  • Assuming you use the web interface to manage lemonldap, go to General Parameters → Authentication parameters → LDAP parameters → Exported variables. Here set the key to the LDAP attribute and the value to something sensible. I keep them the same to make it easy.
  • Now go to *Variables → Macros*. Here set up variables which will be computed based on the attributes you exported above. You will need to emit strings in this format arn:aws:iam::account-number:role/role-name1,arn:aws:iam::account-number:saml-provider/provider-name. The parts you need to change are account-number, role-name1 and provier-name. The last two will be the provider name and role names you just set up in AWS.
  • Perl works in here, so something like this is valid: aws_eu_role → $ou =~ sysadmin ? “arn:aws…” : “arn:…”
  • If it easier, split multiple roles into different macros. Then tie all the variables you define together into one string concatenating them with whatever is in General Parameters → Advanced Parameters → Separator. Actually click into this field and move around with the arrow keys to see if there is a space, since spaces can be part of the separator.
  • Remember macros are defined alphanumerically, so you want one right at the end, like z_aws_roles → join(“; ”, $role_name1, $role_name2, …)
  • On the left again, click SAML service providers, then Add SAML SP.
  • Enter a name, click ok, then select it on the left. Select Metadata, then enter `https://signin.aws.amazon.com/static/saml-metadata.xml` in the URL field, then click load.
  • Click Exported attributes on the left, then Add attribute twice to add two attributes. The first field is the name of a variable set in the user's session:
    • _whatToTrace → https://aws.amazon.com/SAML/Attributes/RoleSessionName (leave the rest)
    • z_aws_roles (the macro name you defined above) → https://aws.amazon.com/SAML/Attributes/Role (leave the rest)
  • On the left, select Options → Security → Enable use of IDP initiated URL → On
  • Select General Parameters → Portal → Menu → Categories and applications
  • Select a category or create a new one if you need to. Then click New application.
  • Enter a name etc. For the URL, use https://your.portal.com/saml/singleSignOn?IDPInitiated=1&sp=urn:amazon:webservices
  • Display application should be set to Enabled
  • Go to your portal, click on the link, and check that it works!
bugzilla.html000066400000000000000000000166021325274564300354760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:bugzilla

Table of Contents

  • Presentation
  • Configuration
    • Bugzilla administration
    • Bugzilla virtual host
    • Bugzilla virtual host in Manager

Bugzilla

Presentation

Bugzilla is server software designed to help you manage software development.

Bugzilla can authenticate a user with HTTP headers, and auto-create its account with a few information:

  • User ID
  • Email
  • Real name

Configuration

Bugzilla administration

In Bugzilla administration interface, go in Parameters » User authentication

Then set:

  • auth_env_id: HTTP_AUTH_USER
  • auth_env_email: HTTP_AUTH_MAIL
  • auth_env_realname: HTTP_AUTH_CN
  • user_info_class: Env or Env,CGI

Bugzilla virtual host

Configure Bugzilla virtual host like other protected virtual host.

  • For Apache:
<VirtualHost *:80>
       ServerName bugzilla.example.com
 
       PerlHeaderParserHandler Lemonldap::NG::Handler
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name bugzilla.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

Bugzilla virtual host in Manager

Go to the Manager and create a new virtual host for Bugzilla.

Configure the access rules.

Configure the following headers.

  • Auth-User: $uid
  • Auth-Mail: $mail
  • Auth-Cn: $cn
bugzilla_logo.png_documentation_1.9_applications_bugzilla.html000066400000000000000000000117041325274564300472560ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:bugzilla_logo.png [LemonLDAP::NG] />

applications:bugzilla_logo.png

bugzilla_logo.png

bugzilla_logo.png

Date:
2016/07/19 12:15
Filename:
bugzilla_logo.png
Format:
PNG
Size:
6KB
Width:
61
Height:
80


Back to documentation:1.9:applications:bugzilla

cornerstone.html000066400000000000000000000232061325274564300362240ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:cornerstone

Table of Contents

  • Presentation
  • Configuration
    • New Service Provider
    • CSOD control panel
      • Certificate
      • SAML assertion

Cornerstone On Demand

Presentation

CornerStone On Demand (CSOD) allows one to use SAML to authenticate users. It works by default with IDP intiated mechanism, but can works with the standard SP initiated cinematic.

To work with LL::NG it requires:

  • An enterprise account
  • LL::NG configured as SAML Identity Provider
  • Registered users on CSOD with the same email than those used by LL::NG (email will be the NameID exchanged between CSOD and LL::NG)

Configuration

New Service Provider

You should have configured LL::NG as an SAML Identity Provider,

Now we will add CSOD as a new SAML Service Provider:

  1. In Manager, click on SAML service providers and the button New service provider.
  2. Set csod as Service Provider name.
  3. Set Email in Options » Authentication Response » Default NameID format
  4. Select Metadata, and unprotect the field to paste the following value:
<md:EntityDescriptor entityID="mycompanyid.csod.com" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <KeyDescriptor use="signing">
      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
	 <ds:X509Data>
	  <ds:X509Certificate>
Base64 encoded CSOD certificate
	    </ds:X509Certificate>
	  </ds:X509Data>
      </ds:KeyInfo>
    </KeyDescriptor>
    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mycompanyid.csod.com/samldefault.aspx" index="1" />
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
  </SPSSODescriptor>
</md:EntityDescriptor>
Change mycompanyid (in AssertionConsumerService markup, parameter Location) into your CSOD company ID and put the certificate value inside the ds:X509Certificate markup

CSOD control panel

CSOD needs two things to configure LL::NG as an IDP:

  • Certificate
  • SAML assertion

Certificate

See SAML security parameters to know how generate a certificate from you SAML private key.

SAML assertion

You need to use the IDP initiated feature of LL::NG. Just call this URL:

https://auth.example.com/saml/singleSignOn?IDPInitiated=1&sp=mycompanyid.csod.com
csod_logo.png_documentation_1.9_applications_cornerstone.html000066400000000000000000000116701325274564300471270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:csod_logo.png [LemonLDAP::NG] />

applications:csod_logo.png

csod_logo.png

csod_logo.png

Date:
2016/07/19 12:15
Filename:
csod_logo.png
Format:
PNG
Size:
32KB
Width:
293
Height:
108


Back to documentation:1.9:applications:cornerstone

django.html000066400000000000000000000063301325274564300351240ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:django

Django

Presentation

Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.

Connector

The Django connector is available on GitHub: https://github.com/rclsilver/django-lemonldap

See the README to know how install and configure it.

dokuwiki.html000066400000000000000000000207471325274564300355200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:dokuwiki

Table of Contents

  • Presentation
  • Installation
  • Configuration
    • Dokuwiki configuration
    • Dokuwiki virtual host
    • Dokuwiki virtual host in Manager

Dokuwiki

Presentation

DokuWiki is a standards compliant, simple to use Wiki, mainly aimed at creating documentation of any kind. It is targeted at developer teams, workgroups and small companies. It has a simple but powerful syntax which makes sure the data files remain readable outside the Wiki and eases the creation of structured texts. All data is stored in plain text files – no database is required.

LemonLDAP::NG wiki uses Dokuwiki!

You will need to install a Dokuwiki plugin, available on Dokuwiki plugins registry: https://www.dokuwiki.org/plugin:authlemonldap

Installation

Install the plugin using the Plugin Manager.

Configuration

Dokuwiki configuration

As administrator, go in Dokuwiki parameters and set:

  • Authentication backend: authlemonldap
  • Manager: set which users and/or groups will be admin

Dokuwiki virtual host

Configure Dokuwiki virtual host like other protected virtual host.

  • For Apache:
<VirtualHost *:80>
       ServerName dokuwiki.example.com
 
       PerlHeaderParserHandler Lemonldap::NG::Handler
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name dokuwiki.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

Dokuwiki virtual host in Manager

Go to the Manager and create a new virtual host for Dokuwiki.

Configure the access rules.

Configure the headers:

  • Auth-User $uid
  • Auth-Cn: $cn
  • Auth-Mail: $mail
  • Auth-Groups: encode_base64($groups,'')
To allow execution of encode_base64() method, you must deactivate the Safe jail.
dokuwiki_logo.png_documentation_1.9_applications_dokuwiki.html000066400000000000000000000117051325274564300473110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:dokuwiki_logo.png [LemonLDAP::NG] />

applications:dokuwiki_logo.png

dokuwiki_logo.png

dokuwiki_logo.png

Date:
2016/07/19 12:15
Filename:
dokuwiki_logo.png
Format:
PNG
Size:
14KB
Width:
80
Height:
80


Back to documentation:1.9:applications:dokuwiki

drupal.html000066400000000000000000000240631325274564300351540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:drupal

Table of Contents

  • Presentation
  • Installation
  • Configuration
    • Drupal module activation
    • Drupal virtual host
    • Drupal virtual host in Manager
    • Protect only the administration pages

Drupal

Presentation

Drupal is a CMS written in PHP. It can works with external modules to extends its functionalities. One of this module can be used to delegate authentication server to the web server: Webserver Auth.

Installation

Install Webserver Auth module, by downloading it, and unarchive it in the drupal modules/ directory.

Configuration

Drupal module activation

Go on Drupal administration interface and enable the Webserver Auth module.

Drupal virtual host

Configure Drupal virtual host like other protected virtual host.

If you are protecting Drupal with LL::NG as reverse proxy, convert header into REMOTE_USER environment variable.
  • For Apache:
<VirtualHost *:80>
       ServerName drupal.example.com
 
       PerlHeaderParserHandler Lemonldap::NG::Handler
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name drupal.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

Drupal virtual host in Manager

Go to the Manager and create a new virtual host for Drupal.

Just configure the access rules.

If using LL::NG as reverse proxy, configure the Auth-User header, else no headers are needed.

Protect only the administration pages

With the above solution, all the Drupal site will be protected, so no anonymous access will be allowed.

You cannot use the unprotect rule because Drupal navigation is based on query strings (?q=admin, ?q=user, etc.), and unprotect rule only works on URL patterns.

You can create a special virtual host and use Apache rewrite module to switch between open and protected hosts:

<VirtualHost *:80>
    ServerName drupal.example.com
 
    # DocumentRoot
    DocumentRoot /var/www/html/drupal/
    DirectoryIndex index.php
 
    # Redirect admin pages
    RewriteEngine On
    RewriteCond  %{QUERY_STRING} q=(admin|user)
    RewriteRule ^/(.*)$ http://admindrupal.example.com/$1 [R]
 
    LogLevel warn
    ErrorLog /var/log/httpd/drupal-error.log
    CustomLog /var/log/httpd/drupal-access.log combined
</VirtualHost>
<VirtualHost *:80>
    ServerName admindrupal.example.com
 
    # SSO protection
    PerlHeaderParserHandler Lemonldap::NG::Handler
 
    # DocumentRoot
    DocumentRoot /var/www/html/drupal/
    DirectoryIndex index.php
 
    LogLevel warn
    ErrorLog /var/log/httpd/admindrupal-error.log
    CustomLog /var/log/httpd/admindrupal-access.log combined
</VirtualHost>
drupal_logo.png_documentation_1.9_applications_drupal.html000066400000000000000000000116521325274564300464140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:drupal_logo.png [LemonLDAP::NG] />

applications:drupal_logo.png

drupal_logo.png

drupal_logo.png

Date:
2016/07/19 12:15
Filename:
drupal_logo.png
Format:
PNG
Size:
6KB
Width:
70
Height:
80


Back to documentation:1.9:applications:drupal

fusiondirectory.html000066400000000000000000000111601325274564300371070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:fusiondirectory

Table of Contents

  • Presentation
  • Configuration
    • FusionDirectory
    • LL::NG

FusionDirectory

Presentation

FusionDirectory provides a solution to daily management of data stored in an LDAP directory.

Configuration

FusionDirectory

Go in Configuration and in Login and Session panel. Set:

  • HTTP Header authentication: Activate
  • Header name: Auth-User

See also https://documentation.fusiondirectory.org/en/documentation/admin_installation/core_configuration#login-and-session

LL::NG

Just set the Auth-User header with the attribute that carries the user login, for example $uid.

gitlab.html000066400000000000000000000224151325274564300351260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:gitlab

Table of Contents

  • Presentation
  • SAML
    • Gitlab configuration
    • LL::NG configuration
    • Manage groups

Gitlab

Presentation

See Gitlab page for product presentation.

Gitlab allows to use SAML to authenticate users, see official documentation

SAML

For this example, we use these sample values: * Gitlab URL : https://gitlab.example.com * LL::NG portal URL : https://auth.example.com

Gitlab configuration

Find the gitlab.rb file and add these settings:

vi /etc/gitlab/gitlab.rb
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
gitlab_rails['omniauth_auto_link_saml_user'] = true
gitlab_rails['omniauth_block_auto_created_users'] = false
 
gitlab_rails['omniauth_providers'] = [
  {
    name: 'saml',
    args: {
      assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
      idp_cert_fingerprint: '99:BE:7B:68:3F:XX:7D:EF:6B:C3:XX:C0:0E:XX:D4:EA:02:XX:83:2A',
      idp_sso_target_url: 'https://auth.example.com/saml/singleSignOn',
      issuer: 'https://gitlab.example.com',
      name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
    },
    label: 'Login with LL::NG' # optional label for SAML login button
  }
]
To get the fingerprint of IDP certificate, copy SAML certificate from LL::NG configuration in a file and use openssl:
openssl x509 -in CERT.pem -noout -fingerprint

You can force SAML by default with this option:

gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml'

In this case, users won't be able to log directly on gitlab. Set it once you are sure the SAML configuration is valid.

To apply changes:

gitlab-ctl reconfigure

LL::NG configuration

We suppose LL::NG is configured as SAML IDP, and that you converted the public key into a certificate for SAML signature. You must enable the option to send certificates in response. If you don't want to, you need to copy the certificate value into Gitlab configuration, in `idp_cert` parameter.

You can get Gitlab SAML metadata on https://gitlab.example.com/users/auth/saml/metadata

Register them in LL::NG and send these SAML attributes:

  • mail ⇒ email
  • uid ⇒ uid
  • cn ⇒ name
The value from LL::NG mail session attribute must be the email of the user in Gitlab database, in order to associate accounts.

Manage groups

You can pass groups to Gitlab. For this, declare groups attribute in gitlab.rb:

...
gitlab_rails['omniauth_providers'] = [
  {
    name: 'saml',
    groups_attribute: 'groups',
...

And in LL::NG, export the groups attribute:

  • groups ⇒ groups
glpi.html000066400000000000000000000100111325274564300346040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:glpi

GLPI

Presentation

GLPI is the Information Resource-Manager with an additional Administration- Interface. You can use it to build up a database with an inventory for your company (computer, software, printers…). It has enhanced functions to make the daily life for the administrators easier, like a job-tracking-system with mail-notification and methods to build a database with basic information about your network-topology.

Configuration

For GLPI >= 0.71, it is a simple configuration in GLPI: Setup → Authentication. In “External authentications” click “Others” and in “Field holding the login in the _SERVER array” select “REMOTE_USER”

For older version, check http://wiki.glpi-project.org/doku.php?id=en:authautoad

If you use Nginx, you need to add this in configuration:

proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
googleapps.html000066400000000000000000000337531325274564300360330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:googleapps

Table of Contents

  • Presentation
  • Configuration
    • Google Apps control panel
    • Certificate
    • New Service Provider
    • Application menu
    • Logout

Google Apps

Presentation

Google Apps can use SAML to authenticate users, behaving as an SAML service provider, as explained here.

To work with LL::NG it requires:

  • An enterprise Google Apps account
  • LL::NG configured as SAML Identity Provider
  • Registered users on Google Apps with the same email than those used by LL::NG (email will be the NameID exchanged between Google Apps and LL::NG)

Configuration

Google Apps control panel

This part is based on SimpleSAMLPHP documentation.

As administrator, go in Google Apps control panel and click on Advanced tools:

Then select Set up single sign-on (SSO):

Now configure all SAML parameters:

  • Enable Single Sign-On: check the box. Uncheck it to disable SAML authentication (for example, if your Identity Provider is down).
  • Sign-in page URL: SSO access point (HTTP-Redirect binding). Example: http://auth.example.com/saml/singleSignOn
  • Sign-out page URL: this in not the SLO access point (Google Apps does not support SLO), but the main logout page. Example: http://auth.example.com/?logout=1
  • Change password URL: where users can change their password. Example: http://auth.example.com

Certificate

For the certificate, you can build it from the signing private key registered in Manager. Select the key, and export it (button Download). This will download the public and the private key.

Keep the private key in a file, for example lemonldap-ng-priv.key, then use openssl to generate an auto-signed certificate:

openssl req -new -key lemonldap-ng-priv.key -out cert.csr
openssl x509 -req -days 3650 -in cert.csr -signkey lemonldap-ng-priv.key -out cert.pem

You can now the upload the certificate (cert.pem) on Google Apps.

You can also use the certificate instead of public key in SAML metadata, see SAML service configuration

New Service Provider

You should have configured LL::NG as an SAML Identity Provider,

Now we will add Google Apps as a new SAML Service Provider:

  1. In Manager, click on SAML service providers and the button New service provider.
  2. Set GoogleApps as Service Provider name.
  3. Set Email in Options » Authentication Response » Default NameID format
  4. Disable all signature flags in Options » Signature, except Sign SSO message which should be to On
  5. Select Metadata, and unprotect the field to paste the following value:
<md:EntityDescriptor entityID="google.com" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.google.com/a/mydomain.org/acs" index="1" />
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
  </SPSSODescriptor>
</md:EntityDescriptor>
Change mydomain.org (in AssertionConsumerService markup, parameter Location) into your Google Apps domain. Also adapt your entityID to match the Assertion issuer: google.com/a/mydomain.org

Application menu

You can add a link in application menu to display Google Apps to users.

You need to adapt some parameters:

  • Address: set one of Google Apps URL (all Google Apps product a distinct URL), for example http://www.google.com/calendar/hosted/mydomain.org/render
  • Display: As Google Apps is not a protected application, set to On to always display it
Change mydomain.org into your Google Apps domain

Logout

Google Apps does not support Single Logout (SLO).

Google Apps has a configuration parameter to redirect user on a specific URL after Google Apps logout (see Google Apps control panel).

To manage the other way (LL::NG → Google Apps), you can add a dedicated logout forward rule:

GoogleApps => http://www.google.com/calendar/hosted/mydomain.org/logout
Change mydomain.org into your Google Apps domain
googleapps_logo.png_documentation_1.9_applications_googleapps.html000066400000000000000000000117371325274564300501420ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:googleapps_logo.png [LemonLDAP::NG] />

applications:googleapps_logo.png

googleapps_logo.png

googleapps_logo.png

Date:
2016/07/19 12:15
Filename:
googleapps_logo.png
Format:
PNG
Size:
12KB
Width:
81
Height:
80


Back to documentation:1.9:applications:googleapps

grr.html000066400000000000000000000111201325274564300344450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:grr

Table of Contents

  • Presentation
    • Configuration
    • GRR virtual host in LL::NG

GRR

Presentation

GRR is a room booking software.

Configuration

GRR has a SSO configuration page in its administration panel.

Do not use Lemonldap mode, which is for a very old Lemonldap version, but HTTP authentication.

Set the default profile of connected users and which headers contains surname, firstname and mail.

GRR will check the username in REMOTE_USER, so use remote header conversion if you are in proxy mode.

GRR virtual host in LL::NG

Access rules:

  • ^/index.php ⇒ accept
  • default ⇒ unprotect

Headers:

  • Auth-User $uid
  • Auth-Sn: $sn
  • Auth-GivenName: $givenName
  • Auth-Mail: $mail
http_logo.png_documentation_1.9_applications_authbasic.html000066400000000000000000000116541325274564300465620ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:http_logo.png [LemonLDAP::NG] />

applications:http_logo.png

http_logo.png

http_logo.png

Date:
2016/07/19 12:15
Filename:
http_logo.png
Format:
PNG
Size:
15KB
Width:
107
Height:
80


Back to documentation:1.9:applications:authbasic

img/000077500000000000000000000000001325274564300335465ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applicationsicons.png000066400000000000000000000240201325274564300353650ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications/img documentation:1.9:applications:img:icons.png [LemonLDAP::NG]
LemonLDAP::NG LemonLDAP::NG
  • Download
  • Documentation
  • Screenshots
  • Contact
    • Mails, IRC and more
    • The team
    • Professional Services
    • References
    • Sponsors
  • Login

You are here: start » documentation » 1.9 » applications » img » icons.png

documentation:1.9:applications:img:icons.png

This topic does not exist yet

You've followed a link to a topic that doesn't exist yet. If permissions allow, you may create it by clicking on “Create this page”.

Sidebar

Hosted by


Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 3.0 Unported

loader.gif000066400000000000000000000240341325274564300355060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications/img documentation:1.9:applications:img:loader.gif [LemonLDAP::NG]
LemonLDAP::NG LemonLDAP::NG
  • Download
  • Documentation
  • Screenshots
  • Contact
    • Mails, IRC and more
    • The team
    • Professional Services
    • References
    • Sponsors
  • Login

You are here: start » documentation » 1.9 » applications » img » loader.gif

documentation:1.9:applications:img:loader.gif

This topic does not exist yet

You've followed a link to a topic that doesn't exist yet. If permissions allow, you may create it by clicking on “Create this page”.

Sidebar

Hosted by


Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 3.0 Unported

liferay.html000066400000000000000000000250051325274564300353150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:liferay

Table of Contents

  • Presentation
  • Configuration
    • Liferay administration
    • Liferay virtual host
    • Liferay virtual host in Manager

Liferay

Presentation

Liferay is an enterprise portal.

Liferay can use LL::NG as an SSO provider but you have to manage how users are created:

  • By hand in Liferay administration screens
  • Imported from an LDAP directory

Of course, integration will be full if you use the LDAP directory as users backend for LL::NG and Liferay.

If the user is not created, or can not be created via LDAP import, the connection to Liferay will be refused. With LDAP, login, mail, first name and last name are required attributes. If one is missing, the user is not created.

This documentation just explains how to set up the SSO part. Please refer to Liferay documentation to enable LDAP provisionning.

Configuration

Liferay administration

Access to Liferay (first time):

Login as administrator:

Go to My Account:

Go to Portal » Settings:

Go to Configuration » Authentication:

In General, fill at least the following information:

  • How do users authenticate?: by login
We advice to deactivate other options, cause users will use LL::NG portal to modify or reset their password.

You need to activate LDAP authentication, else SSO authentication will not work. Do this in the control panel or in the configuration file:
ldap.auth.enabled=true

Then use the SiteMinder tab to configure SSO:

  • Enabled: Yes
  • Import from LDAP: Yes (see presentation)
  • User Header: Auth-User (case sensitive)

Do not forget to save your changes!

Liferay virtual host

Configure Liferay virtual host like other protected virtual host.

  • For Apache:
<VirtualHost *:80>
       ServerName liferay.example.com
 
       PerlHeaderParserHandler Lemonldap::NG::Handler
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name liferay.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

Liferay virtual host in Manager

Go to the Manager and create a new virtual host for Liferay.

Just configure the access rules. You can add a rule for logout:

 ^/c/portal/logout => logout_sso

Configure the Auth-User header.

liferay_logo.png_documentation_1.9_applications_liferay.html000066400000000000000000000116711325274564300467250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:liferay_logo.png [LemonLDAP::NG] />

applications:liferay_logo.png

liferay_logo.png

liferay_logo.png

Date:
2016/07/19 12:15
Filename:
liferay_logo.png
Format:
PNG
Size:
7KB
Width:
160
Height:
80


Back to documentation:1.9:applications:liferay

limesurvey.html000066400000000000000000000264321325274564300360730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:limesurvey

Table of Contents

  • Presentation
  • Configuration
    • LimeSurvey configuration
    • LimeSurvey virtual host
    • LimeSurvey virtual host in Manager
      • Headers
      • Rules

LimeSurvey

Presentation

LimeSurvey is a web survey software written in PHP. LimeSurvey has a webserver authentication mode that allows one to integrate it directly into LemonLDAP::NG.

To have a stronger integration, we will configure LimeSurvey to autocreate unknown users and use HTTP headers to fill name and mail.

Configuration

We suppose that LimeSurvey is installed in /var/www/html/limesurvey

LimeSurvey configuration

In Administration panel, go in Configuration > Parameters > Extensions manager. Select the WebServer module and configure it.

This is enough for the authentication part.

If you are blocked, you can deactivate the plugin with this request in database:
update lime_plugins SET active=0 where name="Authwebserver";

To configure account autocreation, you need to edit application/config/config.php: The configuration is done in config.php:

vi /var/www/html/limesurvey/application/config/config.php
        'config'=>array(
        // debug: Set this to 1 if you are looking for errors. If you still get no errors after enabling this
        // then please check your error-logs - either in your hosting provider admin panel or in some /logs directory
        // on your webspace.
        // LimeSurvey developers: Set this to 2 to additionally display STRICT PHP error messages and get full access to standard templates
                'debug'=>0,
                'debugsql'=>0, // Set this to 1 to enanble sql logging, only active when debug = 2
                // Update default LimeSurvey config here
                'auth_webserver_autocreate_user' => true,
                'auth_webserver_autocreate_profile' => Array('full_name' => $_SERVER['HTTP_AUTH_CN'],'email' => $_SERVER['HTTP_AUTH_MAIL'],'lang'=>'en'),
                'auth_webserver_autocreate_permissions' => Array('surveys' => array('create'=>true,'read'=>false,'update'=>false,'delete'=>false)),
                )

See also https://manual.limesurvey.org/Optional_settings#Authentication_delegation_with_automatic_user_import

LimeSurvey virtual host

Configure LimeSurvey virtual host like other protected virtual host.

LimeSurvey virtual host in Manager

Go to the Manager and create a new virtual host for LimeSurvey.

Headers

Header name Description
Auth-User user login
Auth-Cn user full name
Auth-Mail user email

Rules

Rule name Expression Description
Logout /sa/logout$ Logout rule (for example logout_app_sso)
Admin ^/(index\.php/)?admin Allow only admin and superadmin users
Default default Allow only users with a LimeSurvey role
You can set the default access to:
  • accept: all authenticated users will access surveys
  • unprotect: no authentication will be asked to access surveys
limesurvey_logo.png_documentation_1.9_applications_limesurvey.html000066400000000000000000000117431325274564300502470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:limesurvey_logo.png [LemonLDAP::NG] />

applications:limesurvey_logo.png

limesurvey_logo.png

limesurvey_logo.png

Date:
2016/07/19 12:15
Filename:
limesurvey_logo.png
Format:
PNG
Size:
32KB
Width:
208
Height:
155


Back to documentation:1.9:applications:limesurvey

mediawiki.html000066400000000000000000000364431325274564300356350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:mediawiki

Table of Contents

  • Presentation
  • Installation
  • Configuration
    • MediWiki local configuration
    • MediaWiki virtual host
    • MediaWiki virtual host in Manager

MediaWiki

Presentation

MediaWiki is a wiki software, used by the well known Wikipedia.

Several extensions allows one to configure SSO on MediaWiki:

  • Automatic REMOTE_USER
  • Siteminder Authentication

We will explain how to use Automatic REMOTE_USER extension.

Installation

The extension is presented here: http://www.mediawiki.org/wiki/Extension:AutomaticREMOTE_USER

You can download the code here: https://www.mediawiki.org/wiki/Special:ExtensionDistributor/Auth_remoteuser

You have to install Auth_remoteuser in the extensions/ directory of your MediaWiki installation:

cp -a Auth_remoteuser/ extensions/

Configuration

MediWiki local configuration

Then edit MediaWiki local settings

vi LocalSettings.php
require_once "$IP/extensions/Auth_remoteuser/Auth_remoteuser.php";
$wgAuth = new Auth_remoteuser();

Add then extension configuration, for example:

$wgAuthRemoteuserAuthz = true; /* Your own authorization test */
$wgAuthRemoteuserName = $_SERVER["HTTP_AUTH_CN"]; /* User's name */
$wgAuthRemoteuserMail = $_SERVER["HTTP_AUTH_MAIL"]; /* User's Mail */
$wgAuthRemoteuserNotify = false; /* Do not send mail notifications */
//$wgAuthRemoteuserDomain = "NETBIOSDOMAIN"; /* Remove NETBIOSDOMAIN\ from the beginning or @NETBIOSDOMAIN at the end of a IWA username */
/* User's mail domain to append to the user name to make their email address */
//$wgAuthRemoteuserMailDomain = "example.com";
 
// see http://www.mediawiki.org/wiki/Manual:Hooks/SpecialPage_initList
// and http://www.mediawiki.org/w/Manual:Special_pages
// and http://lists.wikimedia.org/pipermail/mediawiki-l/2009-June/031231.html
// disable login and logout functions for all users
function LessSpecialPages(&$list) {
    unset( $list['Userlogout'] );
    unset( $list['Userlogin'] );
    return true;
}
$wgHooks['SpecialPage_initList'][]='LessSpecialPages';
 
// http://www.mediawiki.org/wiki/Extension:Windows_NTLM_LDAP_Auto_Auth
// remove login and logout buttons for all users
function StripLogin(&$personal_urls, &$wgTitle) {
    unset( $personal_urls["login"] );
    unset( $personal_urls["logout"] );
    unset( $personal_urls['anonlogin'] );
    return true;
}
$wgHooks['PersonalUrls'][] = 'StripLogin';
In last version of Auth_remoteuser and Mediawiki, empty passwords are not authorized, so you may need to patch the extension code if you get the error: “Unexpected REMOTE_USER authentication failure. Login Error was:EmptyPass”.

If necessary, use the code below to patch the extension:

sed -i "s/'wpPassword' => ''/'wpPassword' => 'none'/" extensions/Auth_remoteuser/Auth_remoteuser.body.php
In last version of Auth_remoteuser and Mediawiki, auto-provisioning requires REMOTE_USER to match the normalized mediawiki username (for example: john_doe → john doe), so you may need to patch the extension code if you get the error: “Unexpected REMOTE_USER authentication failure. Login Error was:WrongPluginPass”

You can use the code below for normalizing logins containing “_” in the extension:

sed -i '/$usertest = $this->getRemoteUsername();/a\                $usertest = str_replace( "_"," ", $usertest );' extensions/Auth_remoteuser/Auth_remoteuser.body.php

MediaWiki virtual host

Configure MediaWiki virtual host like other protected virtual host.

If you are protecting MediaWiki with LL::NG as reverse proxy, convert header into REMOTE_USER environment variable.
  • For Apache:
<VirtualHost *:80>
       ServerName mediawiki.example.com
 
       PerlHeaderParserHandler Lemonldap::NG::Handler
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name mediawiki.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

MediaWiki virtual host in Manager

Go to the Manager and create a new virtual host for MediaWiki.

Just configure the access rules. You can also add a rule for logout:

Userlogout => logout_sso

You can create these two headers to fill user name and mail (see extension configuration):

Auth-Cn => $cn
Auth-Mail => $mail

If using LL::NG as reverse proxy, configure also the Auth-User header,

mediawiki_logo.png_documentation_1.9_applications_mediawiki.html000066400000000000000000000117221325274564300475420ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:mediawiki_logo.png [LemonLDAP::NG] />

applications:mediawiki_logo.png

mediawiki_logo.png

mediawiki_logo.png

Date:
2016/07/19 12:15
Filename:
mediawiki_logo.png
Format:
PNG
Size:
12KB
Width:
80
Height:
80


Back to documentation:1.9:applications:mediawiki

my_domain_salesforce-resize-web.png_documentation_1.9_applications_salesforce.html000066400000000000000000000121431325274564300531740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:my_domain_salesforce-resize-web.png [LemonLDAP::NG] />

applications:my_domain_salesforce-resize-web.png

my_domain_salesforce-resize-web.png

my_domain_salesforce-resize-web.png

Date:
2016/07/19 12:15
Filename:
my_domain_salesforce-resize-web.png
Format:
PNG
Size:
88KB
Width:
800
Height:
413


Back to documentation:1.9:applications:salesforce

nextcloud.html000066400000000000000000000222641325274564300356730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:nextcloud

Table of Contents

  • Presentation
  • Pre-requisites
    • NextCloud
    • LL:NG
  • NextCloud, SAML 2.0 configuration
  • LL:NG, SAML 2.0 Service Provider configuration

NextCloud

Presentation

NextCloud is a fork of Owncloud, suite of client-server software for creating file hosting services and using them.

This documentation explains how to interconnect LemonLDAP::NG and NextCloud using SAML 2.0 protocol.

Pre-requisites

NextCloud

You need to install the software.

If your NextCloud is behind a proxy (thus having a private IP), metadata generated by NextCloud won't work.

Consider changing the configuration of NextCloud to force the domain, in $nextcloudrootwww/config/config.php, add the following:

'overwritehost' => 'nextcloud.example.com',

You also need to enable the “SAML authentication” plugin in your NextCloud.

 + Apps -> Not enabled -> SAML authentication

LL:NG

You need to enable SAML 2.0 issuer module in LL:NG:

"General Parameters -> Issuer modules -> SAML -> Activation"

NextCloud, SAML 2.0 configuration

Configuration of SAML 2.0 in NextCloud is pretty straightforward.

Administration -> SAML authentication

You will find the following fields:

  • Attribute to map the UID to: Identity attribute provided by your LL:NG that will be used as UID in NextCloud.
  • Identity Provider Data:
    • Identifier of the IdP entity: SAML Metadata URL of your LL:NG
    • URL Target of the IdP where the SP will send the Authentication Request Message: SingleSignOn URL of your LL:NG
    • URL Location of the IdP where the SP will send the SLO Request: SingleLogOut URL of your LL:NG
    • Public X.509 certificate of the IdP: Certificate of your LL:NG (see below for instructions)

We need a few steps to generate our LL:NG certificate (unless you already have one). You first need to create a pair of SSH Keys in LL:NG:

SAML 2 Service -> Security Parameters -> Signature

and click “New keys”

Take the private key in a private.key file, and run the following:

openssl req -new -key private.key -out cert.csr
openssl x509 -req -days 3650 -in cert.csr -signkey private.key -out cert.pem

Copy/Paste the content of your new cert.pem in the “Public X.509 certificate of the IdP” field of your NextCloud.

Your fields should look like this:

You can now download your metadata xml file.

LL:NG, SAML 2.0 Service Provider configuration

We now have to define a service provider (e.g our nextcloud) in LL:NG.

Go to “SAML service providers”, click on “Add SAML SP” and name it as you want (example : 'NextCloud')

In the new subtree 'NextCloud', open 'Metadata' and paste the content of your previously downloaded file (or upload the file)

Now go in “Exported attributes” and add, at least, the 'uid'

Don't forget to save your configuration.

You are now good to go, and you can add the application in your menu and your virtual hosts.

nginx.html000066400000000000000000000060131325274564300350030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:nginx

Nginx

Nginx is fully supported by LemonLDAP::NG since version 1.9.

Presentation

Nginx is a very fast web server. It can be used to host the portal or the manager through its FastCGI support and can be used to protect applications using the auth_request module (dialing with a FastCGI authorization server). See installation pages to know how install and use it.

obm.html000066400000000000000000000521051325274564300344400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:obm

Table of Contents

  • Presentation
  • Configuration
    • OBM
    • LL::NG
      • Attributes and macros
      • Virtual host
      • Other

OBM

Presentation

OBM is enterprise-class messaging and collaboration platform for workgroup or enterprises with many thousands users. OBM includes Groupware, messaging server, CRM, LDAP, Windows Domain, smartphone and PDA synchronization…

OBM is shipped with a LL::NG plugin with these features:

  • SSO on OBM web interface
  • Logout
  • User provisioning (account auto creation at first connection)

Configuration

OBM

To enable LL::NG authentication plugin, go in /etc/obm/obm_conf.inc:

$auth_kind = 'LemonLDAP';
 
$lemonldap_config = Array(
                "auto_update"           => true,
                "auto_update_force_user" => true,
                "auto_update_force_group" => false,
                "url_logout"            => "https://OBMURL/logout",
                "server_ip_address"     => "localhost",
                "server_ip_check"       => false,
                "debug_level"           => "NONE",
//                "debug_header_name"     => "HTTP_OBM_UID",
//                "group_header_name"     => "HTTP_OBM_GROUPS",
                "headers_map"           => Array(
                        //"userobm_gid"                   => "HTTP_OBM_GID",
                        //"userobm_domain_id"           => ,
                        "userobm_login"                 => "HTTP_OBM_UID",
                        "userobm_password"              => "HTTP_OBM_USERPASSWORD",
                        //"userobm_password_type"       => ,
                        "userobm_perms"                 => "HTTP_OBM_PERMS",
                        //"userobm_kind"                => ,
                        "userobm_lastname"              => "HTTP_OBM_SN",
                        "userobm_firstname"             => "HTTP_OBM_GIVENNAME",
//                        "userobm_title"                 => "HTTP_OBM_TITLE",
                        "userobm_email"                 => "HTTP_OBM_MAIL",
                        "userobm_datebegin"             => "HTTP_OBM_DATEBEGIN",
                        //"userobm_account_dateexp"     => ,
                        //"userobm_delegation_target"   => ,
                        //"userobm_delegation"          => ,
                        "userobm_description"           => "HTTP_OBM_DESCRIPTION",
                        //"userobm_archive"             => ,
                        //"userobm_hidden"              => ,
                        //"userobm_status"              => ,
                        //"userobm_local"               => ,
                        //"userobm_photo_id"            => ,
                        "userobm_phone"                 => "HTTP_OBM_TELEPHONENUMBER",
                        //"userobom_phone2"             => ,
                        //"userobm_mobile"              => ,
                        "userobm_fax"                   => "HTTP_OBM_FACSIMILETELEPHONENUMBER",
                        //"userobm_fax2"                => ,
                        "userobm_company"               => "HTTP_OBM_O",
                        //"userobm_direction"           => ,
                        "userobm_service"               => "HTTP_OBM_OU",
                        "userobm_address1"              => "HTTP_OBM_POSTALADDRESS",
                        //"userobm_address2"            => ,
                        //"userobm_address3"            => ,
                        "userobm_zipcode"               => "HTTP_OBM_POSTALCODE",
                        "userobm_town"                  => "HTTP_OBM_L",
                        "userobm_zipcode"               => "HTTP_OBM_POSTALCODE",
                        "userobm_town"                  => "HTTP_OBM_L",
                        //"userobm_expresspostal"       => ,
                        //"userobm_host_id"             => ,
                        //"userobm_web_perms"           => ,
                        //"userobm_web_list"            => ,
                        //"userobm_web_all"             => ,
                        //"userobm_mail_perms"          => ,
                        //"userobm_mail_ext_perms"      => ,
                        //"userobm_mail_server_id"      => ,
                        //"userobm_mail_server_hostname" => ,
                        "userobm_mail_quota"            => "HTTP_OBM_MAILQUOTA",
                        //"userobm_nomade_perms"        => ,
                        //"userobm_nomade_enable"       => ,
                        //"userobm_nomade_local_copy"   => ,
                        //"userobm_email_nomade"        => ,
                        //"userobm_vacation_enable"     => ,
                        //"userobm_vacation_datebegin"  => ,
                        //"userobm_vacation_dateend"    => ,
                        //"userobm_vacation_message"    => ,
                        //"userobm_samba_perms"         => ,
                        //"userobm_samba_home"          => ,
                        //"userobm_samba_home_drive"    => ,
                        //"userobm_samba_logon_script"  => ,
                        // ---- Unused values ? ----
                        "userobm_ext_id"                => "HTTP_OBM_SERIALNUMBER",
                        //"userobm_system"              => ,
                        //"userobm_nomade_datebegin"    => ,
                        //"userobm_nomade_dateend"      => ,
                        //"userobm_location"            => ,
                        //"userobm_education"           => ,
                        ),
        );

Parameters:

  • url_logout: URL used by OBM to logout, will be caught by LL::NG
  • headers_map: map OBM internal field to LL::NG header

Edit also OBM configuration to enable LL::NG Handler:

  • For Apache:
<VirtualHost *:80>
    ServerName obm.example.com
 
    # SSO protection
    PerlHeaderParserHandler Lemonldap::NG::Handler
 
    DocumentRoot /usr/share/obm/php
 
    ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name obm.example.com;
  root /usr/share/obm/php;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location ~ \.php$ {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

LL::NG

Attributes and macros

You will need to collect all attributes needed to create a user in OBM, this includes:

  • First name
  • Last name
  • Login
  • Mail
  • …

To add these attributes, go in Manager, Variables » Exported Variables.

If you plan to forward user's password to OBM, then you have to keep the password in session.

You may also create these macros to manage OBM administrator account (Variables » Macros):

field value
uidR ($uid =~ /^admin0/i)[0] ? "admin0\@global.virt" : $uid
mailR ($uid =~ /^admin0/i)[0] ? "" : ($mail =~ /^([^@]+)/)[0] . "\@example.com"

Virtual host

Create OBM virtual host (for example obm.example.com) in LL::NG configuration: Virtual Hosts » New virtual host.

Then edit rules and headers.

Rules

Define at least:

  • Default rule: who can access to the application
  • Logout rule: catch OBM logout
  • Exceptions: allow anonymous access for specific URLs (connectors, etc.)
field value
^/logoutlogout_sso
^/obm-syncunprotect
^/minigunprotect
^/Microsoft-Server-ActiveSyncunprotect
^/caldavunprotect
defaultaccept (or whatever you want)
Headers

Define headers used in OBM mapping, for example:

field valeur
OBM_GIVENNAME$givenName
OBM_GROUPS$groups
OBM_UID$uidR
OBM_MAIL$mailR
OBM_USERPASSWORD$_password

Other

Do not forget to add OBM in applications menu.

obm_logo.png_documentation_1.9_applications_obm.html000066400000000000000000000116061325274564300451670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:obm_logo.png [LemonLDAP::NG] />

applications:obm_logo.png

obm_logo.png

obm_logo.png

Date:
2016/07/19 12:15
Filename:
obm_logo.png
Format:
PNG
Size:
13KB
Width:
145
Height:
80


Back to documentation:1.9:applications:obm

office365.html000066400000000000000000000206211325274564300353520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:office365

Table of Contents

  • Presentation
  • Configuration
    • Office 365
    • LemonLDAP::NG

Office 365

Presentation

Office 365 provides online access to Microsoft products like Office, Outlook or Yammer. Authentication is done on https://login.microsoftonline.com/ and can be forwarded to an SAML Identity Provider.

Configuration

Office 365

You first need to install AzureAD PowerShell to be able to run administrative commands.

Then run this script:

$dom = "mycompany.com"
$brand = "My Company"
$url = "https://auth.example.com/saml/singleSignOn"
$uri = "https://auth.example.com/saml/metadata"
$logouturl = "https://auth.example.com/?logout=1"
$cert = "xxxxxxxxxxxxxxxxxxx"
 
Set-MsolDomainAuthentication –DomainName $dom -FederationBrandName $brand -Authentication Federated  -PassiveLogOnUri $url -SigningCertificate $cert -IssuerUri $uri  -LogOffUri $logouturl -PreferredAuthenticationProtocol SAMLP

Where parameters are:

  • dom: Your Office 365 domain
  • brand: Simple label
  • url: The SAML SSO endpoint
  • uri: The SAML metadata endpoint
  • logouturl: Logout URL
  • cert: The SAML certificate containing the signature public key

If you have several Office365 domains, you can't use the same URLs for each domains. To be able to have a single SAML IDP for several domains, you must add the 'domain' GET parameters at the end of SSO endpoint and metadata URLs, for example:

  • domain 'mycompany.com':
    • url: https://auth.example.com/saml/singleSignOn?domain=mycompany
    • uri: https://auth.example.com/saml/metadata?domain=mycompany
  • domain 'myfirm.com':
    • url: https://auth.example.com/saml/singleSignOn?domain=myfirm
    • uri: https://auth.example.com/saml/metadata?domain=myfirm

LemonLDAP::NG

Create a new SAML Service Provider and import Microsoft metadata from https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml

Set the NameID value to persistent, or any immutable value for the user.

Create a SAML attribute named IDPEmail which contains the user principal name (UPN).

phpldapadmin.html000066400000000000000000000204621325274564300363250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:phpldapadmin

Table of Contents

  • Presentation
  • Configuration
    • phpLDAPadmin local configuration
    • phpLDAPadmin virtual host
    • phpLDAPadmin virtual host in Manager

phpLDAPadmin

Presentation

phpLDAPadmin is an LDAP administration tool written in PHP.

phpLDAPadmin will connect to the directory with a static DN and password, and so will not request authentication anymore. The access to phpLDAPadmin will be protected by LemonLDAP::NG with specific access rules.

phpLDAPadmin will have no idea of the user connected to the WebSSO. So a simple user can have admin rights on the LDAP directory if your access rules are too lazy.

Configuration

phpLDAPadmin local configuration

Just set the authentication type to config and indicate DN and password inside the file config.php:

$ldapservers->SetValue($i,'server','auth_type','config');
$ldapservers->SetValue($i,'login','dn','cn=Manager,dc=example,dc=com');
$ldapservers->SetValue($i,'login','pass','secret');

phpLDAPadmin virtual host

Configure phpLDAPadmin virtual host like other protected virtual host.

  • For Apache:
<VirtualHost *:80>
       ServerName phpldapadmin.example.com
 
       PerlHeaderParserHandler Lemonldap::NG::Handler
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name phpldapadmin.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

phpLDAPadmin virtual host in Manager

Go to the Manager and create a new virtual host for phpLDAPadmin.

Just configure the access rules.

No headers are required.

phpldapadmin_logo.png_documentation_1.9_applications_phpldapadmin.html000066400000000000000000000117731325274564300507440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:phpldapadmin_logo.png [LemonLDAP::NG] />

applications:phpldapadmin_logo.png

phpldapadmin_logo.png

phpldapadmin_logo.png

Date:
2016/07/19 12:15
Filename:
phpldapadmin_logo.png
Format:
PNG
Size:
12KB
Width:
136
Height:
80


Back to documentation:1.9:applications:phpldapadmin

roundcube.html000066400000000000000000000124511325274564300356510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:roundcube

Table of Contents

  • Presentation
  • Configuration
    • LemonLDAP::NG
    • RoundCube

RoundCube

Presentation

RoundCube webmail is a browser-based multilingual IMAP client with an application-like user interface. It provides full functionality you expect from an email client, including MIME support, address book, folder manipulation, message searching and spell checking.

Configuration

LemonLDAP::NG

  • Add a new virtual host webmail.domain.tld
  • Add a new rule:
"^/\?_task\=logout" -> "logout_app https://auth.domain.tld"
  • in HTTP headers, you need Auth-User ($mail) and Auth-Pw ($_password).
To be able to forward password to RoundCube, see how to store password in session
  • Configure Apache or Nginx virtual host

RoundCube

  • install http_authentication plugin
  • Patch it to replace PHP_AUTH_* by HTTP_AUTH_*
  • enable http_authentication plugin in main.inc.php :
$rcmail_config['plugins'] = array('http_authentication');
salesforce-logo.jpg_documentation_1.9_applications_salesforce.html000066400000000000000000000117441325274564300500260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:salesforce-logo.jpg [LemonLDAP::NG] />

applications:salesforce-logo.jpg

salesforce-logo.jpg

salesforce-logo.jpg

Date:
2014/12/22 18:05
Filename:
salesforce-logo.jpg
Format:
JPEG
Size:
15KB
Width:
150
Height:
95


Back to documentation:1.9:applications:salesforce

salesforce.html000066400000000000000000000255771325274564300360260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:salesforce

Table of Contents

  • Presentation
  • Configuration
    • Create Salesforce domain
    • SAML settings
    • Configure Federation ID

SalesForce

Presentation

Salesforce Salesforce Inc. is a cloud computing company. It is best known for their CRM products and social networking applications.

It allows one to use SAML to authenticate users. It can deal with both SP and IdP initiated modes.

This page presents the SP initiated mode.

To work with LL::NG it requires:

  • LL::NG configured as SAML Identity Provider

Configuration

You should have configured LL::NG as a SAML Identity Provider.

Create Salesforce domain

For using SP-initiated mode, you must create your salesforce domain. Creation can take up to 1 hour. (if it is superior to 1h, then there is a problem. Problems are generally resolved in up to 72 hours)

Then you must deploy this domain in order to go on with the configuration.

Finally, just ensure that at least:

  • Login policy
  • Redirect policy
  • domain name
  • authentication service

match with the correct values. (adapt the domain if necessary)

For now, the authentication service parameter has no domain available. You must come back later to fill this parameter. Once SAML cinematics are working, you can then put your domain, and delete the login form, and you'll have an automatic redirection to your Identity Provider (no need for the user to click). Note that you can always access Salesforce by the general login page: https://login.salesforce.com

SAML settings

Salesforce is not able to read metadata, you must fill the information into a form.

Go to the SAML Single Sign On settings, and fill these information:

  • Name: should be filled automatically with your organization or domain
  • SAML Version: check that version 2.0 is used
  • Issuer: this is the LemonLDAP::NG (our IdP) Entity Id, which is by default #PORTAL#/saml/metadata
  • Identity Provider Certificate: whereas it is mentioned that this is the authentication certificate, you must give your LemonLDAP::NG (IdP) signing certificate. If you don't have one, create it with the signing key pair already generated (you could do this with openssl). SSL authentication (https) does not seem to be checked anyway.
  • Signing Certificate: choose a certificate for SP signature. (create one if none is present)
  • Assertion decryption Certificate: choose a certificate only if you want to cipher your assertion. (default is not to cipher)
  • SAML Identity Type: choose Federation ID. This means that the user Name ID will be mapped to the Federation ID field. (see next section)
  • SAML Identity Location: choose if the user Name ID is held in the subject or in some attribute
  • Identity Provider Login URL: the user/password SAML portal location on the IdP
  • Identity Provider Logout URL: the logout location on the IdP
  • Custom Error URL: you can redirect the user to a special page when an error is happening
  • SP Initiated Binding: chose any of the supported binding (every one listed there is currently supported on LemonLDAP::NG) HTTP POST is a good choice
  • Salesforce Login URL: generated automatically. This is the entry point of our login cinematic.
  • OAuth 2.0 Token Endpoint: not used here
  • API Name: filled automatically
  • User Provisioning Enabled: should create automatically the user in Salesforce (not functionnal right now)
  • EntityId: Salesforce (the SP) Entity ID. Fill this field accordingly. It should be the same value as the organization domain url, displayed on the previous section

Configure Federation ID

Finally, configure for each user his Federation ID value. It will be the link between the SAML assertion coming from LemonLDAP::NG (the IdP) and a given user in Salesforce. Here, the mail has been chosen as the user Name ID.

Once this is completed, click to export the Salesforce metadata and import them into LemonLDAP::NG, into the declaration of the Salesforce Service Provider.

See Register partner Service Provider on LemonLDAP::NG configuration chapter.

saml_sso_settings-resize-web.png_documentation_1.9_applications_salesforce.html000066400000000000000000000121141325274564300525500ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:saml_sso_settings-resize-web.png [LemonLDAP::NG] />

applications:saml_sso_settings-resize-web.png

saml_sso_settings-resize-web.png

saml_sso_settings-resize-web.png

Date:
2016/07/19 12:15
Filename:
saml_sso_settings-resize-web.png
Format:
PNG
Size:
106KB
Width:
800
Height:
415


Back to documentation:1.9:applications:salesforce

screenshot_dokuwiki_configuration.png_documentation_1.9_applications_dokuwiki.html000066400000000000000000000121511325274564300534510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:screenshot_dokuwiki_configuration.png [LemonLDAP::NG] />

applications:screenshot_dokuwiki_configuration.png

screenshot_dokuwiki_configuration.png

screenshot_dokuwiki_configuration.png

Date:
2017/11/23 14:30
Filename:
screenshot_dokuwiki_configuration.png
Format:
PNG
Size:
35KB
Width:
898
Height:
317


Back to documentation:1.9:applications:dokuwiki

simplesamlphp.html000066400000000000000000000357761325274564300365600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:simplesamlphp

Table of Contents

  • Presentation
  • Pre-requisites
    • simpleSAMLphp
    • LemonLDAP::NG
  • simpleSAMLphp as Service Provider
  • simpleSAMLphp as Identity Provider

simpleSAMLphp

Presentation

simpleSAMLphp is an identity/service provider written in PHP. It supports a lot of protocols like CAS, OpenID and SAML.

This documentation explains how to interconnect LemonLDAP::NG and simpleSAMLphp using SAML 2.0 protocol.

Pre-requisites

simpleSAMLphp

You need to install the software. If using Debian, just do:

apt-get install simplesamlphp

We suppose that configuration is done in /etc/simplesamlphp and that simpleSAMLphp is accessible at http://localhost/simplesamlphp.

To be able to sign SAML messages, you need to create a certificate. First set where certificates are stored:

vi /etc/simplesamlphp/config.php
   'certdir' => '/etc/simplesamlphp/certs/',

Create directory and generate the certificate

mkdir /etc/simplesamlphp/certs/
cd /etc/simplesamlphp/certs/
openssl req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.pem

Then associate this certificate to the default SP:

vi /etc/simplesamlphp/authsources.php
    'default-sp' => array(
        'saml:SP',
        'privatekey' => 'saml.pem',
        'certificate' => 'saml.crt',

LemonLDAP::NG

You need to configure SAML Service. Be sure to convert public key in a certificate, as described in the security chapter as simpleSAMLphp can't use the public key.

simpleSAMLphp as Service Provider

We suppose you configured LemonLDAP::NG as SAML Identity Provider and want to use simpleSAMLphp as Service Provider.

In LL::NG Manager, create an new SP and load simpleSAMLphp metadata trough URL (by default: http://localhost/simplesamlphp/module.php/saml/sp/metadata.php/default-sp):

Then set some attributes that will be sent to simpleSAMLphp:

Set Mandatory to On to force attributes in authentication response.

You can also force all signatures:

On simpleSAMLphp side, use the metadata converter (by default: http://localhost/simplesamlphp/admin/metadata-converter.php) to convert LL::NG metadata (by default: http://auth.example.com/saml/metadata) into internal PHP representation. Copy the saml20-idp-remote content:

vi /etc/simplesamlphp/metadata/saml20-idp-remote.php
<?php
$metadata['http://auth.example.com/saml/metadata'] = array (
  'entityid' => 'http://auth.example.com/saml/metadata',
...
   // Add this option to force SLO requests signature
   'sign.logout' => true,
);
?>
Don't forget PHP start and end tag to have a valid PHP file.

All is ready, you can now test the authentication (by default: http://localhost/simplesamlphp/module.php/core/authenticate.php). You should see something like that:

simpleSAMLphp as Identity Provider

We suppose you configured LemonLDAP::NG as SAML Service Provider and want to use simpleSAMLphp as Identity Provider.

First, you need to activate IDP feature in simpleSAMLphp:

vi /etc/simplesamlphp/config.php
    'enable.saml20-idp' => true,

And create a default IDP configuration:

vi /etc/simplesamlphp/metadata/saml20-idp-hosted.php
<?php
$metadata['__DYNAMIC:1__'] = array(
    /*
     * The hostname for this IdP. This makes it possible to run multiple
     * IdPs from the same configuration. '__DEFAULT__' means that this one
     * should be used by default.
     */
    'host' => '__DEFAULT__',
 
    /*
     * The private key and certificate to use when signing responses.
     * These are stored in the cert-directory.
     */
    'privatekey' => 'saml.pem',
    'certificate' => 'saml.crt',
 
    /*
     * The authentication source which should be used to authenticate the
     * user. This must match one of the entries in config/authsources.php.
     */
    'auth' => 'admin',
    // Sign SLO messages
    'sign.logout' => true,
);
?>
You need to configure your own certificates and authentication scheme

Now in LL::NG Manager, create a new IDP and import metadata with URL (by default: http://localhost/simplesamlphp/saml2/idp/metadata.php):

List attributes you want to collect:

You can keep Mandatory to Off to not fail if attribute is not sent by IDP

And activate all signatures:

To finish, you need to declare LL::NG SP in simpleSAMLphp. Use the metadata converter (by default: http://localhost/simplesamlphp/admin/metadata-converter.php) to convert LL::NG metadata (by default: http://auth.example.com/saml/metadata) into internal PHP representation. Copy the saml20-sp-remote content:

vi /etc/simplesamlphp/metadata/saml20-sp-remote.php
<?php
$metadata['http://auth.example.com/saml/metadata'] = array (
  'entityid' => 'http://auth.example.com/saml/metadata',
...
);
?>
Don't forget PHP start and end tag to have a valid PHP file.

All is ready, you can now test the authentication from LL::NG portal.

spring.html000066400000000000000000000154361325274564300351730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:spring

Spring Security (ACEGI)

Presentation

Spring Security is the new ACEGI name. This is a well known security framework for J2EE applications.

Spring Security provides a default pre-authentication mechanism that can be used to connect your J2EE application to LL::NG.

Configuration

You can find all suitable information here: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/preauth.html

To summarize, to get the user connected trough the Auth-User HTTP Header, use this Sping Security configuration:

<bean id="LemonLDAPNGFilter" class=
"org.springframework.security.web.authentication.preauth.header.RequestHeaderPreAuthenticatedProcessingFilter">
    <security:custom-filter position="PRE_AUTH_FILTER" />
    <property name="principalRequestHeader" value="Auth-User"/>
    <property name="authenticationManager" ref="authenticationManager" />
</bean>
 
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
  <security:custom-authentication-provider />
    <property name="preAuthenticatedUserDetailsService">
    <bean id="userDetailsServiceWrapper" class="org.springframework.security.userdetails.UserDetailsByNameServiceWrapper">
      <property name="userDetailsService" ref="userDetailsService"/>
    </bean>
  </property>
</bean>
 
<security:authentication-manager alias="authenticationManager" />
spring_logo.png_documentation_1.9_applications_spring.html000066400000000000000000000116541325274564300464440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:spring_logo.png [LemonLDAP::NG] />

applications:spring_logo.png

spring_logo.png

spring_logo.png

Date:
2016/07/19 12:15
Filename:
spring_logo.png
Format:
PNG
Size:
4KB
Width:
166
Height:
80


Back to documentation:1.9:applications:spring

symfony.html000066400000000000000000000402711325274564300353700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:symfony

Table of Contents

  • Presentation
  • Configuration
  • References

PHP (Symfony)

Presentation

Symfony is the well-known PHP framework. It is intended to ease the development of PHP applications.

Symfony provides many methods conventions to authenticate users (basic, ldap,…) and to load external user sources (ldap, database). The method presented here relies on the “remote_user” method. (in security firewall)

Configuration

Follow these step to protect your application using the “REMOTE_USER” HTTP header.

1. Adapt the app/config/security.yml configuration file as below:

security:
 
    encoders:
        AppBundle\Security\User\HeaderUser: plaintext
 
    providers:
        header:
            id: AppBundle\Security\User\HeaderUserProvider
 
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
 
        main:
            pattern: ^/
            remote_user:
                user: HTTP_REMOTE_USER
            provider: header
  • encoders : define a password hashing scheme (useless in our case, but the parameter is mandatory)
  • providers : define the user providers (even virtual)
  • remote_user : define the authentication method to “assume the user is already authenticated and get an http variable to know his username”
  • user : define the HTTP header containing the username
  • provider : references the previously defined provider owning the user data (in our scenario, a virtual)

2. Define a “header user” class

Create the file src/AppBundle/Security/User/HeaderUser.php :

<?php
 
// src/Security/User/HeaderUser.php
namespace AppBundle\Security\User;
 
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
 
class HeaderUser implements UserInterface, EquatableInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;
 
    public function __construct($username, $password, $salt, array $roles)
    {
        $this->username = $username;
        $this->password = $password;
        $this->salt = $salt;
        $this->roles = $roles;
    }
 
    public function getRoles()
    {
        return $this->roles;
    }
 
    public function getPassword()
    {
        return $this->password;
    }
 
    public function getSalt()
    {
        return $this->salt;
    }
    public function getUsername()
    {
        return $this->username;
    }
 
    public function eraseCredentials()
    {
    }
 
    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof HeaderUser) {
            return false;
        }
 
        if ($this->username !== $user->getUsername()) {
            return false;
        }
 
        //if ($this->password !== $user->getPassword()) {
        //    return false;
        //}
 
        return true;
    }
}
?>

3. Define a “header user provider” class relying on the previous class

Create the file src/AppBundle/Security/User/HeaderUserProvider.php :

<?php
 
// src/Security/User/HeaderUserProvider.php
namespace AppBundle\Security\User;
 
use AppBundle\Security\User\HeaderUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
 
class HeaderUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
 
        if ($username) {
 
            $password = "dummy";
            $salt = "";
            $roles = array('ROLE_USER');
 
            return new HeaderUser($username, $password, $salt, $roles);
        }
 
        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }
 
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof HeaderUser) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }
 
        return $this->loadUserByUsername($user->getUsername());
    }
 
    public function supportsClass($class)
    {
        return HeaderUser::class === $class;
    }
}
 
?>

References

  • http://symfony.com/doc/current/security/pre_authenticated.html#remote-user-based-authentication
  • https://symfony.com/doc/current/security/custom_provider.html
symfony_logo.png_documentation_1.9_applications_symfony.html000066400000000000000000000116711325274564300470470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:symfony_logo.png [LemonLDAP::NG] />

applications:symfony_logo.png

symfony_logo.png

symfony_logo.png

Date:
2018/03/02 11:11
Filename:
symfony_logo.png
Format:
PNG
Size:
4KB
Width:
300
Height:
77


Back to documentation:1.9:applications:symfony

sympa.html000066400000000000000000000200201325274564300350030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:sympa

Table of Contents

  • Presentation
  • Configuration
    • Sympa configuration
    • Sympa virtual host
    • Sympa virtual host in Manager

Sympa

Presentation

Sympa is a mailing list manager.

To configure SSO with Sympa, use Magic authentication: a special SSO URL is protected by LL::NG, Sympa will display a button for users who wants to use this feature.

Since version 1.9 of LLNG, old Auto-Login feature has been removed since it works only with Sympa-5 which has been deprecated

Configuration

Sympa configuration

Edit the file “auth.conf”, for example:

vi /etc/sympa/auth.conf

And fill it:

generic_sso
        service_name                   Centralized auth service
        service_id                          lemonldapng
        email_http_header            HTTP_MAIL
        netid_http_header             HTTP_AUTH_USER
        internal_email_by_netid    1
        logout_url                          http://sympa.example.com/wws/logout
You can also disable internal Sympa authentication to keep only LemonLDAP::NG by removing user_table paragraph

Note that if you use FastCGI, you must restart Apache to enable changes.

You can also use <portal>?logout=1 as logout_url to remove LemonLDAP::NG session when “disconnect” is chosen.

Sympa virtual host

Configure Sympa virtual host like other protected virtual host but protect only magic authentication URL.

The location URL end is based on the service_id defined in Sympa apache configuration.
  • For Apache:
<VirtualHost *:80>
       ServerName sympa.example.com
 
       <Location /wws/sso_login/lemonldapng>
       PerlHeaderParserHandler Lemonldap::NG::Handler
       </Location>
 
       ...
 
</VirtualHost>
  • For Nginx:
server {
  listen 80;
  server_name sympa.example.com;
  root /path/to/application;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location /wws/sso_login/lemonldapng {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
 
    include /etc/lemonldap-ng/nginx-lua-headers.conf;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

Sympa virtual host in Manager

Go to the Manager and create a new virtual host for Sympa.

Configure the access rules and define the following headers:

  • Auth-User
  • Mail
sympa_logo.png_documentation_1.9_applications_sympa.html000066400000000000000000000116371325274564300461230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:sympa_logo.png [LemonLDAP::NG] />

applications:sympa_logo.png

sympa_logo.png

sympa_logo.png

Date:
2016/07/19 12:15
Filename:
sympa_logo.png
Format:
PNG
Size:
7KB
Width:
180
Height:
80


Back to documentation:1.9:applications:sympa

tomcat.html000066400000000000000000000226511325274564300351550ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:tomcat

Table of Contents

  • Presentation
  • Installation
  • Configuration
  • Compilation

Apache Tomcat

The Tomcat Valve is only available for tomcat 5.5 or greater.

Presentation

Apache Tomcat is an open source software implementation of the Java Servlet and JavaServer Pages technologies.

As J2EE servlet container, Tomcat provides standard security feature, like authentication: the application deployed in Tomcat can delegate its authentication to Tomcat.

By default, Tomcat provides a file called users.xml to manage authentication:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
</tomcat-users>
 

LL::NG provides a valve, available on download page. This valve will check an HTTP header to set the authenticated user on the J2EE container.

Installation

Copy ValveLemonLDAPNG.jar in <TOMCAT_HOME>/server/lib:

cp ValveLemonLDAPNG.jar server/lib/
If needed, you can recompile the valve from the sources.

Configuration

Add on your server.xml file a new valve entry like this (in host section):

<Valve className="org.lemonLDAPNG.SSOValve" userKey="AUTH-USER" roleKey="AUTH-ROLE" roleSeparator="," allows="127.0.0.1"/>

Configure attributes:

  • userKey: key in the HTTP header containing user login.
  • roleKey: key in the HTTP header containing roles. If LL::NG send some roles split by some commas, configure roleSeparator.
  • roleSeparator (optional): role values separator.
  • allows (optional): Define allowed remote IP (use “,” separator for multiple IP). Just set the LL::NG Handler IP on this attribute in order to add more security. If this attribute is missed all hosts are allowed.
  • passThrough (optional): Allow anonymous access or not. When it takes “false”, HTTP headers have to be sent by LL::NG to make authentication. So, if the user is not recognized or HTTP headers not present, a 403 error is sent.
For debugging, this valve can print some helpful information in debug level. See how configure logging in Tomcat .

Compilation

The sources are available on download page.

Required :

  • ant
  • jre > 1.4
  • tomcat >= 5.5

Configure your tomcat home in build.properties files.

Be careful for Windows user, path must contains “/”. Example:
c:/my hardisk/tomcat/

Next run ant command:

ant

ValveLemonLDAPNG.jar is created under /dist directory.

tomcat_logo.png_documentation_1.9_applications_tomcat.html000066400000000000000000000116541325274564300464160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:tomcat_logo.png [LemonLDAP::NG] />

applications:tomcat_logo.png

tomcat_logo.png

tomcat_logo.png

Date:
2016/07/19 12:15
Filename:
tomcat_logo.png
Format:
PNG
Size:
9KB
Width:
113
Height:
80


Back to documentation:1.9:applications:tomcat

user_federation_id-resize-web.png_documentation_1.9_applications_salesforce.html000066400000000000000000000121231325274564300526420ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:user_federation_id-resize-web.png [LemonLDAP::NG] />

applications:user_federation_id-resize-web.png

user_federation_id-resize-web.png

user_federation_id-resize-web.png

Date:
2016/07/19 12:15
Filename:
user_federation_id-resize-web.png
Format:
PNG
Size:
67KB
Width:
800
Height:
410


Back to documentation:1.9:applications:salesforce

wordpress.html000066400000000000000000000141251325274564300357130ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:wordpress

Table of Contents

  • Presentation
  • CAS
    • Plugin installation
    • Plugin configuration
      • General settings
      • User Roles Settings

Wordpress

Presentation

Wordpress is a famous tool to create websites.

A lot of authentication plugins are available. We propose here to use CAS protocol and WP Cassify plugin.

CAS

Plugin installation

Go in Wordpress admin and install WP Cassify plugin.

Plugin configuration

The full documentation is available on https://wpcassify.wordpress.com/

General settings

Configure CAS server and CAS version:

  • CAS Server base url : https://auth.example.com/cas/
  • CAS Version protocol: 2

Other options are correct by default.

User Roles Settings

You can assign WP Roles depending on values sent by CAS.

The rules syntax is quite special, you can use it or you can just define macros on LL::NG side and send them trough CAS to keep simple rules on WP side.

For example create a macro role_wordpress_admin which contains 1 if the user is admin on WP, and send it in CAS attributes.

Then create this rule on WP side:

administrator|(CAS{role_wordpress_admin} -EQ "1")
zimbra.html000066400000000000000000000261361325274564300351540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications documentation:1.9:applications:zimbra

Table of Contents

  • Presentation
  • Configuration
    • Zimbra preauth key
    • Zimbra application in menu
    • Zimbra virtual host
      • Apache
      • Nginx
    • Zimbra virtual host in Manager
    • Zimbra Handler parameters

Zimbra

Presentation

Zimbra is open source server software for email and collaboration - email, group calendar, contacts, instant messaging, file storage and web document management. The Zimbra email and calendar server is available for Linux, Mac OS X and virtualization platforms. Zimbra syncs to smartphones (iPhone, BlackBerry) and desktop clients like Outlook and Thunderbird. Zimbra also features archiving and discovery for compliance. Zimbra can be deployed on-premises or as a hosted email solution.

Zimbra use a specific preauthentication protocol to provide SSO on its application. This protocol is implemented in an LL::NG specific Handler.

Zimbra can also be connected to LL::NG via SAML protocol (see Zimbra blog).
For now, Zimbra isn't supported by Nginx handler. You have to use Apache.

Configuration

The integration with LL::NG is the following:

  • A special URL is declared in application menu (like http://zimbra.example.com/zimbrasso)
  • A Zimbra Handler is called
  • Handler build the preauth request and redirect user on Zimbra preauth URL
  • Then Zimbra do the SSO by setting a cookie in user's browser

Zimbra preauth key

You need to get a preauth key from Zimbra server.

See how to do this on Zimbra wiki.

Zimbra application in menu

Choose for example http://zimbra.example.com/zimbrasso as SSO URL and set it in application menu.

Zimbra virtual host

Apache

You will configure Zimbra virtual host like other protected virtual host but you will use Zimbra Handler instead of default Handler.

PerlModule Lemonldap::NG::Handler::Specific::ZimbraPreAuth
<VirtualHost *>
        ServerName zimbra.example.com
 
       # Load Zimbra Handler
       PerlHeaderParserHandler Lemonldap::NG::Handler::Specific::ZimbraPreAuth
       ...
 
</VirtualHost>

Nginx

Zimbra Handler cannot be used in Nginx for the moment.

Zimbra virtual host in Manager

Go to the Manager and create a new virtual host for Zimbra.

Just configure the access rules.

Zimbra Handler parameters

Zimbra parameters are the following:

  • Preauthentication key: the one you grab from zmprov command
  • Account session key: session field used as Zimbra user account (by default: uid)
  • Account type: for Zimbra this can be name, id or foreignKey (by default: id)
  • Preauthentication URL: Zimbra preauthentication URL, either with full URL (ex: http://zimbra.lan/service/preauth), either only with path (ex: /service/preauth) (by default: /service/preauth)
  • Local SSO URL pattern: regular expression to match the SSO URL (by default: ^/zimbrasso$)
Due to Handler API change in 1.9, you need to set these attributes in lemonldap-ng.ini and not in Manager, for example:
[handler]
zimbraPreAuthKey = XXXX
zimbraAccountKey = uid
zimbraBy =id
zimbraUrl = /service/preauth
zimbraSsoUrl = ^/zimbrasso$
zimbra_logo.png_documentation_1.9_applications_zimbra.html000066400000000000000000000116551325274564300464110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/applications applications:zimbra_logo.png [LemonLDAP::NG] />

applications:zimbra_logo.png

zimbra_logo.png

zimbra_logo.png

Date:
2016/07/19 12:15
Filename:
zimbra_logo.png
Format:
PNG
Size:
14KB
Width:
167
Height:
80


Back to documentation:1.9:applications:zimbra

authad.html000066400000000000000000000142751325274564300324510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authad

Table of Contents

  • Presentation
  • Configuration
  • AD password policy

Active Directory

Authentication Users Password
✔ ✔ ✔

Presentation

The Active Directory module is based on the LDAP module, with these features:

  • Specific default values for filters to match AD schema
  • Compatible password modification
  • Reset password on next logon workflow

Configuration

The configuration is the same as the LDAP module.

AD password policy

AD password policy does not follow the LDAP RFC, but Microsoft has implemented its own policy. LemonLDAP::NG implements partially the policy:

  • when pwdLastSet = 0 in the user entry, it means that password has been reset, and a form is presented to the user for him to change his password.
  • when computed virtual attribute 'msDS-User-Account-Control-Computed' as 6th flag set to 8, the password is considered expired. (support from Windows Server 2003) It is too late for the user to do anything. He must contact his administrator.
  • a warning before password expiration is possible in AD, but only in GPO (Computer Configuration\Windows Settings\Local Policies\Security Options under Interactive Logon: Prompt user to change password before expiration) However it as no reality in LDAP referential. A “password warning time before password expiration” variable can be specified in LemonLDAP::NG to do so.
Note: since AD 2012, each user can have a specific password expiration policy. Then, the “maximum password age” can have different values. This is currently unsupported in LemonLDAP::NG because every policy must be computed with their precedence to know which maximum password age to apply.

To configure warning before password expiration, you must set two variables in Active Directory parameters in Manager:

  • Password expire warning : number of seconds between password expiration and the date from which user is warned his password will expire.
  • Password max age : number of seconds after the last password change, before it expires. It must match AD policy
authapache.html000066400000000000000000000170301325274564300332760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authapache

Table of Contents

  • Presentation
  • Configuration
    • LL::NG
    • Apache
  • Tips
    • Kerberos
    • Compatibility with Identity Provider modules

Apache

Authentication Users Password
✔

Presentation

LL::NG can delegate authentication to Apache, so it is possible to use any Apache authentication module, for example Kerberos, Radius, OTP, etc.

Apache authentication module will set the REMOTE_USER environment variable, which will be used by LL::NG to get authenticated user.

Configuration

LL::NG

In General Parameters > Authentication modules, choose Apache as authentication backend.

You may want to failback to another authentication backend in case of the Apache authentication fails. Use then the Multiple authentication module, for example:

Apache;LDAP
In this case, the Apache authentication module should not require a valid user and not be authoritative, else Apache server will return an error and not let LL::NG Portal manage the failback authentication.

Apache

The Apache configuration depends on the module you choose, you need to look at the module documentation, for example:

  • Kerberos
  • NTLM
  • Radius
  • …

Tips

Kerberos

The Kerberos configuration is quite complex. You can find some configuration tips on this page.

Compatibility with Identity Provider modules

When using IDP modules (like CAS or SAML), the activation of Apache authentication can alter the operation. This is because the client often need to request directly the IDP, and the Apache authentication will block the request.

In this case, you can add in the Apache authentication module:

      Satisfy any 
      Order allow,deny 
      allow from APPLICATIONS_IP

This will bypass the authentication module for request from APPLICATIONS_IP.

authbrowserid.html000066400000000000000000000134451325274564300340630ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authbrowserid

BrowserID

Authentication Users Password
✔

Presentation

BrowserID is also known as Mozilla Persona. It is a Single Sign On protocol similar to OpenID, but based on user email address rather than a URL. Of course, there are other differences.

See the full description of the protocol here: https://developer.mozilla.org/en-US/docs/Mozilla/Persona

Configuration

In Manager, go in General Parameters > Authentication modules and choose BrowserID for authentication.

You can then choose any other module for users and password but if you want to totally delegate authentication to BrowserID, choose None for users and password.

Then, go in BrowserID parameters:

  • Authentication level: authentication level for this module.
  • Auto login: set to 1 to use auto login. This mode is not compatible with a lot of browsers which block pop-ups.
  • Verification URL: URL used to verifiy the BrowserID assertion. Leave blank to use Mozilla default verification URL (https://verifier.login.persona.org/verify)
  • Site Name: Name that will be displayed in the BrowserID login window
  • Site Logo: Logo that will be displayed in the BrowserID login window. You must provide a full URI (for example /skins/common/lemonldap-ng_square.png) and use HTTPS on the portal
  • Backgound color: Background color displayed in the BrowserID login window
authcas.html000066400000000000000000000175201325274564300326270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authcas

Table of Contents

  • Presentation
  • Perl-CAS module installation
  • Configuration

CAS

Authentication Users Password
✔

Presentation

LL::NG can delegate authentication to a CAS server. This requires Perl CAS module.

LL::NG can also act as CAS server, that allows one to interconnect two LL::NG systems.

LL::NG can also request proxy tickets for its protected services. Proxy tickets will be collected at authentication phase and stored in user session under the form:

_casPTserviceID = Proxy ticket value

They can then be forwarded to applications trough HTTP headers.

CAS authentication will automatically add a logout forward rule on CAS server logout URL in order to close CAS session on LL::NG logout.

Perl-CAS module installation

Download the latest version:

wget https://sourcesup.cru.fr/frs/download.php/2476/AuthCAS-1.4.tar.gz

Extract and build the module:

tar zxvf AuthCAS-1.4.tar.gz 
cd AuthCAS-1.4/
perl Makefile.PL
make
make test

Install the module:

sudo make install

Configuration

In Manager, go in General Parameters > Authentication modules and choose CAS for authentication.

You can then choose any other module for users and password.

Then, go in CAS parameters:

  • Authentication level: authentication level for this module.
  • Server URL: CAS server URL (must use https://)
  • CA file: CA certificate used to validate CAS server certificate
  • Renew authentication: force authentication renewal on CAS server
  • Gateways authentication: force transparent authentication on CAS server
  • PGT file: temporary file where proxy tickets are stored (by default, /tmp/pgt.txt)
  • Proxied services: list of services for which a proxy ticket is requested:
    • Key: Service ID
    • Value Service URL (CAS service identifier)
If no proxied services defined, CAS authentication will not activate the CAS proxy mode.
If you activate proxy mode, you must create the PGT file on your system, for example:
touch /tmp/pgt.txt
authchoice.html000066400000000000000000000137151325274564300333150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authchoice

Backend choice by users

Authentication Users Password
✔ ✔ ✔

Presentation

By default, only the configured authentication backend is available for users.

Contrary to multiple backend stacking, backend choice will present all available authentication methods to users, who will choose the one they want.

The choice will concern three backends:

  • Authentication
  • Users
  • Password

The chosen backends will be registered in session:

  • $_auth
  • $_userDB
  • $_passwordDB

Authentication choice will also be registered in session:

  • $_authChoice

Configuration

In Manager, go in General Parameters > Authentication modules and choose Choice for authentication.

When Choice is selected for authentication, values for Users and Password modules are also forced to Choice.

Then, go in Choice Parameters:

  • URL parameter: parameter name used to set choice value (default: lmAuth)
  • Allowed modules: click on New chain to add a choice.

Define here:

  • Name: Text displayed on choice tab.
  • Authentication module
  • Users module
  • Password module
  • URL: optional, can be used to redirect on another URL (for example https://authssl.example.com). This is mandatory if you want to use an Apache authentication module, which is run by Apache before showing the LemonLDAP::NG portal page.
You can prefix the key name with a digit to order them. The digit will not be shown on portal page. Underscore characters are also replaced by spaces.
authdbi.html000066400000000000000000000347071325274564300326250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authdbi

Table of Contents

  • Presentation
    • Drivers
    • Schema
      • Example 1: two tables
      • Example 2: single table
    • SQL
  • Configuration
    • Authentication level
    • Exported variables
    • Connection
    • Schema
    • Password

Databases

Authentication Users Password
✔ ✔ ✔

Presentation

Drivers

LL::NG can use a lot of databases as authentication, users and password backend:

  • MySQL
  • PostGreSQL
  • Oracle
  • …

Indeed, any Perl DBD driver can be used.

Schema

LL::NG can use two tables:

  • Authentication table: where login and password are stored
  • User table: where user data are stored (mail, name, etc.)
Authentication table and user table can be the same.

The password can be in plain text, or encoded with a standard SQL method:

  • SHA
  • SHA1
  • MD5

Example 1: two tables

Authentication table
id login password
0 coudot 1f777a6581e478499f4284e54fe2d4a4e513dfff
1 xguimard a15a18c8bb17e6f67886a9af1898c018b9f5a072
2 tchemineau 1f777a6581e478499f4284e54fe2d4a4e513dfff
User table
id user name mail
0 coudot Clément OUDOT coudot@example.com
1 tchemineau Thomas CHEMINEAU tchemineau@example.com
2 xguimard Xavier GUIMARD xguimard@example.com

Example 2: single table

id user password name mail
0 coudot 1f777a6581e478499f4284e54fe2d4a4e513dfff Clément OUDOT coudot@example.com
1 tchemineau 1f777a6581e478499f4284e54fe2d4a4e513dfff Thomas CHEMINEAU tchemineau@example.com
2 xguimard a15a18c8bb17e6f67886a9af1898c018b9f5a072 Xavier GUIMARD xguimard@example.com

SQL

LL::NG will operate some SQL queries:

  • Authentication: select row in authentication table matching user and password
  • Search user: select row in user table matching user
  • Change password: update password column in authentication table matching user

Configuration

In Manager, go in General Parameters > Authentication modules and choose Database (DBI) for authentication, users and/or password modules.

Authentication level

The authentication level given to users authenticated with this module.

As DBI is a login/password based module, the authentication level can be:
  • increased (+1) if portal is protected by SSL (HTTPS)
  • decreased (-1) if the portal autocompletion is allowed (see portal customization)

Exported variables

List of columns to query to fill user session. See also exported variables configuration.

Connection

Connection settings can be configured differently for authentication process and user process. This allows one to use different databases for these process. By default, if user process connection settings are empty, authentication process connection settings will be used.
  • Chain: DBI chain, including database driver name and database name (for example: dbi:mysql:database=lemonldapng;host=localhost).
  • User: Connection user
  • Password: Connection password

Schema

  • Authentication table: authentication table name
  • User table: user table name
  • Login field name: name of authentication table column hosting login
  • Password field name: name of authentication table column hosting password
  • Mail field name: name of authentication table column hosting mail (for password reset)
  • Login field name in user table: name of user table column hosting login

Password

  • Hash schema: SQL method for hashing password. Can be left blank for plain text passwords.
  • Dynamic hash activation: Activate dynamic hashing. With dynamic hashing, the hash scheme is recovered from the user password in the database during authentication.
  • Supported non-salted schemes: List of whitespace separated hash schemes. Every hash scheme MUST match a non-salted hash function in the database. LemonLDAP::NG relies on this hashing function for computing user password hashes. These hashes MUST NOT be salted (no random data used in conjunction with the password).
  • Supported salted schemes: List of whitespace separated salted hash schemes, of the form “sscheme”, where scheme MUST match a non-salted hash function in the database. LemonLDAP::NG relies on this hashing function for computing user password hashes. Salted and non-salted scheme lists are not necessarily equivalent. (for example: non-salted=“sha256” and salted=“ssha ssha512” is valid)
  • Dynamic hash scheme for new passwords: LemonLDAP::NG is able to store new passwords in the database (while modifying or reinitializing the password). You can choose a salted or non salted dynamic hashed password. The value must be an element of “Supported non-salted schemes” or “Supported salted schemes”.
The SQL function MUST have hexadecimal values as input AND output
Here is an example for creating a postgreSQL SHA256 function. 1. Install postgresql-contrib. 2. Activate extension:
CREATE EXTENSION pgcrypto;

3. Create the hash function:

CREATE OR REPLACE FUNCTION sha256(varchar) returns text AS $$
SELECT encode(digest(decode($1, 'hex'), 'sha256'), 'hex')
$$ LANGUAGE SQL STRICT IMMUTABLE;
authdemo.html000066400000000000000000000123261325274564300330040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authdemo

Demonstration

Authentication Users Password
✔ ✔ ✔

Presentation

This mode allows one to test LemonLDAP::NG without any third-party software.

This mode must not be used for other purpose than test and demonstration!

Demonstration backend has hard coded user accounts:

Login Password Mail Role
rtyler rtyler rtyler@badwolf.org user
msmith msmith msmith@badwolf.org user
dwho dwho dwho@badwolf.org administrator
As you may have guessed, these accounts are famous characters from the TV show Doctor Who.

The AuthDemo and UserDBDemo will allow you to log in and get the standard attributes (uid, cn and mail). The PasswordDBDemo will allow you to change the password with some basic checks, but as the data are hard coded, the password will never be really changed.

Configuration

Select Demonstration for authentication, user and password backend.

You can also modify list of exported variables. Only uid, cn and mail attributes are available. See also exported variables configuration.

authfacebook.html000066400000000000000000000137601325274564300336340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authfacebook

Facebook

Authentication Users Password
✔ ✔

Presentation

Facebook is a famous social network service. Facebook uses OAuth2 protocol to allow applications to reuse its own authentication process (it means, if your are connected to Facebook, other applications can trust Facebook and let you in).

You need Net::Facebook::Oauth2 package.

You need to register a new application on Facebook to get an application ID and a secret. See https://developers.facebook.com/apps on how to do that.

Configuration

In Manager, go in General Parameters > Authentication modules and choose Facebook for authentication module. You can also use Facebook as user database.

Then, go in Facebook parameters:

  • Authentication level: authentication level for this module.
  • Facebook application ID: the application ID you get
  • Facebook application secret: the corresponding secret

If you use Facebook as user database, declare values in exported variables:

  • use any key name you want. If you want to refuse access when a data is missing, just add a “!” before the key name
  • in the value field, set the field name. You can show them using Facebook Graph API explorer and have a list of supported fields in the Graph API User reference. For example:
    • cn ⇒ name
    • mail ⇒ email
    • sn ⇒ last_name
Do not query id field in exported variables, as it is already registered by the authentication module in $_user.
You can use the same Facebook access token in your applications. It is stored in session datas under the name $_facebookToken
authgoogle.html000066400000000000000000000162231325274564300333340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authgoogle

Table of Contents

  • Presentation
  • Configuration
  • Google Migration

Google

Authentication Users Password
✔ ✔

Presentation

Google proposes to allow applications to reuse its own authentication process using OpenID protocol (it means, if your are connected to Google, other applications can trust Google and let you in).

OpenID 2.0 support is closed since 20th April 2015. If you still need to use Google login after this date, use OpenID Connect authentication module.

Configuration

In Manager, go in General Parameters > Authentication modules and choose Google for authentication module. This will use email as login name (for accounting, session explorer,…). If you want to access to other datas, you have to use Google in General Parameters > Authentication modules > User module. Then in exported variables, you can ask only for :

  • country
  • email
  • firstname
  • language
  • lastname

Use the name you want but this values in the value field. If you want to require that a field is set, add “!” before the key name :

  • “myfield ⇒ firstname” can be “”
  • “!myfield ⇒ lastname” must be set

See also exported variables configuration.

A specific persistent session is created with this module, to store attribute values returned by Google. If this session is lost, Google will ask a confirmation for each requested attribute.

Google Migration

A Google Migration workaround is available since LemonLDAP::NG 1.4.4. It provides a specific and lightweight OpenID Connect module that will replace the current Google module.

This module is not available in version 1.9 and superior, you must use instead the OpenID Connect authentication module.

To use it, edit lemonldap-ng.ini (this is not available trough Manager) and configure:

[portal]
authentication = GoogleMigration
googleClientId = XXXX
googleClientSecret = XXXX

You need to register your LemonLDAP::NG application to Google in order to obtain the Client ID and the Client Secret, see https://developers.google.com/

You also need to register to Google the redirect URI. You have to set your portal URL with the googlecb=1 GET parameter, for example:

http://auth.example.com/?googlecb=1
authkerberos.html000066400000000000000000000062721325274564300336770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authkerberos

Kerberos

Authentication Users Password
✔

Presentation

A backport of 2.0 new Kerberos authentication module has been done for 1.9.14. See Kerberos to see how to use it.

authldap.html000066400000000000000000000341371325274564300330040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authldap

Table of Contents

  • Presentation
  • Configuration
    • Authentication level
    • Exported variables
    • Connection
    • Filters
    • Groups
    • Password

LDAP

Authentication Users Password
✔ ✔ ✔

Presentation

LL::NG can use an LDAP directory to:

  • authenticate user
  • get user attributes
  • get groups where user is registered
  • change password (with server side password policy management)

This works with every LDAP v2 or v3 server, including Active Directory.

LL::NG is compatible with LDAP password policy:

  • LDAP server can check password strength, and LL::NG portal will display correct errors (password too short, password in history, etc.)
  • LDAP sever can block brute-force attacks, and LL::NG will display that account is locked
  • LDAP server can force password change on first connection, and LL::NG portal will display a password change form before opening SSO session

Configuration

In Manager, go in General Parameters > Authentication modules and choose LDAP for authentication, users and/or password modules.

For Active Directory, choose Active Directory instead of LDAP.

Authentication level

The authentication level given to users authenticated with this module.

As LDAP is a login/password based module, the authentication level can be:
  • increased (+1) if portal is protected by SSL (HTTPS)
  • decreased (-1) if the portal autocompletion is allowed (see portal customization)

Exported variables

List of attributes to query to fill user session. See also exported variables configuration.

Connection

  • Server host: LDAP server hostname or URI (by default: localhost). Accept some specificities:
    • More than one server can be set here separated by spaces or commas. They will be tested in the specified order.
    • To use TLS, set ldap+tls://server and to use LDAPS, set ldaps://server instead of server name.
    • If you use TLS, you can set any of the Net::LDAP start_tls() sub like ldap+tls://server/verify=none&capath=/etc/ssl. You can also use cafile and capath parameters.
  • Server port: TCP port used by LDAP server. Can be overridden by an LDAP URI in server host.
  • Users search base: Base of search in the LDAP directory.
  • Account: DN used to connect to LDAP server. By default, anonymous bind is used.
  • Password: password to used to connect to LDAP server. By default, anonymous bind is used.
  • Timeout: server idle timeout.
  • Version: LDAP protocol version.
  • Binary attributes: regular expression matching binary attributes (see Net::LDAP documentation).

Filters

In LDAP filters, $user is replaced by user login, and $mail by user email.
  • Default filter: default LDAP filter for searches, should not be modified.
  • Authentication filter: Filter to find user from its login (default: (&(uid=$user)(objectClass=inetOrgPerson)))
  • Mail filter: Filter to find user from its mail (default: (&(mail=$mail)(objectClass=inetOrgPerson)))
  • Alias dereference: How to manage LDAP aliases. (default: find)
For Active Directory, the default authentication filter is:
(&(sAMAccountName=$user)(objectClass=person))

And the mail filter is:

(&(mail=$mail)(objectClass=person))

Groups

  • Search base: DN of groups branch. If no value, disable group searching.
  • Object class: objectClass of the groups (default: groupOfNames).
  • Target attribute: name of the attribute in the groups storing the link to the user (default: member).
  • User source attribute: name of the attribute in users entries used in the link (default: dn).
  • Searched attributes: name(s) of the attribute storing the name of the group, spaces separated (default: cn).
  • Recursive: activate recursive group functionality (default: 0). If enabled, if the user group is a member of another group (group of groups), all parents groups will be stored as user's groups.
  • Group source attribute: name of the attribute in groups entries used in the link, for recursive group search (default: dn).

Password

  • Password policy control: enable to use LDAP password policy. This requires at least Net::LDAP 0.38. (see ppolicy workflow below)
  • Password modify extended operation: enable to use the LDAP extended operation password modify instead of standard modify operation.
  • Change as user: enable to perform password modification with credentials of connected user. This requires to request user old password (see portal customization).
  • LDAP password encoding: can allow one to manage old LDAP servers using specific encoding for passwords (default: utf-8).
  • Use reset attribute: enable to use the password reset attribute. This attribute is set by LemonLDAP::NG when password was reset by mail and the user choose to generate the password (default: enabled).
  • Reset attribute: name of password reset attribute (default: pwdReset).
  • Reset value: value to set in reset attribute to activate password reset (default: TRUE).
  • Allow a user to reset his expired password: if activated, the user will be prompted to change password if his password is expired (default: 0)

Password expiration warning workflow
Password expiration workflow

authlinkedin.html000066400000000000000000000117611325274564300336570ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authlinkedin

LinkedIn

Authentication Users Password
✔

Presentation

LinkedIn is a professional social network. It uses OAuth2 protocol to allow applications to reuse its own authentication process (see https://developer.linkedin.com/docs/oauth2).

You need to register a new application on LinkedIn to get an application ID and a secret. See https://www.linkedin.com/developer/apps/ on how to do that.

Configuration

In Manager, go in General Parameters > Authentication modules and choose LinkedIn for authentication module.

Then, go in LinkedIn parameters:

  • Authentication level: authentication level for this module.
  • Client ID: the application ID you get
  • Client secret: the corresponding secret
  • Searched fields: Fields requested on People endpoint
  • Field containing user identifier: Field that will be used as main user identifier in LL::NG
  • Scope: OAuth 2.0 scopes
Collected fields are stored in session in linkedIn_ keys
authmulti.html000066400000000000000000000365261325274564300332220ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authmulti

Table of Contents

  • Presentation
  • Configuration
    • Advanced configuration
  • Known problems
    • AuthApache authentication
    • SSL authentication
    • Complex use case

Multiple backends stack

Authentication Users Password
✔ ✔

Presentation

This backend allows one to chain authentication method, for example to failback to LDAP authentication if Remote authentication failed…

Configuration

You have to use Multiple as authentication modul (this will also force Multiple for the users module). Then go in Multiple parameters to define the modules to chain for authentication and users. Modules are separated by semi-colons/

For example:

CAS;LDAP

If CAS failed, LDAP will be used.

You can also add a condition. Example:

Remote $ENV{REMOTE_ADDR}=~/^192/;LDAP $ENV{REMOTE_ADDR}!~/^192/'
Multiple will try to use the same module for authentication and users. Example, if you have DBI;LDAP and DBI failed for authentication, it will try first to call LDAP as user database.

Advanced configuration

The Multiple system can :

  • stack several times the same module with a different name
  • overload any LL::NG parameter when a specific backend is used
Overloading is not available trough the Manager

To stack several times the same module, use “#name” with different names. Example:

LDAP#Openldap; LDAP#ActiveDirectory

Then you can have different parameters for each stored in a Perl hash entry named multi:

multi => {
    'LDAP#Openldap' => {
      'ldapServer' => 'ldap1.example.com',
      'LDAPFilter' => '(uid=$user)',
    },
    'LDAP#ActiveDirectory' => {
      'ldapServer' => 'ldaps://ad.example.com',
      'LDAPFilter' => '(&(sAMAccountName=$user)(objectClass=person))',
    }
},

This key must be stored directly in lemonldap-ng.ini:

[portal]
multi = {'LDAP#Openldap'=>{'ldapServer'=>'ldap1.example.com','LDAPFilter'=>'(uid=$user)'},'LDAP#ActiveDirectory'=>{'ldapServer'=>'ldaps://ad.example.com','LDAPFilter'=>'(&(sAMAccountName=$user)(objectClass=person))'}}

Known problems

AuthApache authentication

When using this module, LL::NG portal will be called only if Apache does not return “401 Authentication required”, but this is not the Apache behaviour: if the auth module fails, Apache returns 401.

To bypass this, follow the documentation of AuthApache module

SSL authentication

To chain SSL, you have to set “SSLRequire optional” in Apache configuration, else users will be authenticated by SSL only.

Complex use case

Here is a complex use case involving :

  • multiple authentication with
    1. SSL
    2. Kerberos
    3. LDAP
  • LemonLDAP::NG as a SAML IdP

The URLs will be:

  • https://auth.example.com/kerberos: call to SSL, then Kerberos authentication
  • https://auth.example.com/: call to LDAP authentication
  • https://auth.example.com/kerberos/saml: official path to SAML request for LemonLDAP::NG IdP. IdP Metadatas contain URL of this form.
  • https://auth.example.com/saml: redirected SAML path

In this case, redirection script described in the kerberos configuration page is insufficient. You have to transfer every parameter in SAML request, so rather use this redirection script instead:

#!/usr/bin/perl
use CGI ':cgi-lib';
use strict;
use MIME::Base64;
use CGI::Carp 'fatalsToBrowser';
 
my $uri = $ENV{"REDIRECT_URL"};
$uri .= "?".$ENV{"REDIRECT_QUERY_STRING"};
$uri =~ s/\/kerberos//;
print CGI::header(-Refresh => '0; URL=https://auth.example.com'.$uri);
exit(0);

You also have to make LemonLDAP::NG tolerant to the Path in order to have SAML request correctly detected. To do this, go in the manager, and configure the SAML Path (General Parameters > Issuer modules > SAML > Path) with a regular expression:

^/(kerberos/saml/|saml/)

Don't forget to configure your authentication modules accordingly. Especially the chained authentications: General Parameters > Authentication parameters > Multi parameters > Authentication stack string

SSL;Apache;LDAP

Finally, don't forget to configure the portal virtual host with all the authentication parameters needed. Take a special care to the added RewriteRule in the SAML issuer section:

<VirtualHost "*:443">
    ServerName auth.example.com

    SSLEngine on

    SSLCertificateFile      /etc/httpd/ssl/auth.example.com.crt
    SSLCertificateKeyFile   /etc/httpd/ssl/auth.example.com.key
    SSLCertificateChainFile /etc/httpd/ssl/chain.pem

    SSLVerifyClient optional
    SSLCACertificateFile    /etc/httpd/ssl/ca.crt
    SSLVerifyDepth 10
    SSLOptions +StdEnvVars

    LogLevel warn
    ErrorLog /var/log/httpd/error_log

    # DocumentRoot
    DocumentRoot /var/lib/lemonldap-ng/portal/
    <Directory /var/lib/lemonldap-ng/portal/>
        Require all granted
        Options +ExecCGI +FollowSymLinks
    </Directory>

    Alias /kerberos /var/lib/lemonldap-ng/portal/
    <Location /kerberos>
      Options +execCGI
      ErrorDocument 401 /redirectKRB.pl

      AuthType Kerberos
      KrbMethodNegotiate On
      KrbMethodK5Passwd Off
      AuthName "REALM.COM"
      KrbAuthRealms REALM.COM
      Krb5KeyTab /etc/httpd/keytabs/auth.keytab
      KrbVerifyKDC Off
      KrbServiceName Any
      Require valid-user
    </Location>

[...]

    # SAML2 Issuer
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^/saml/metadata /metadata.pl
        RewriteRule ^/saml/.* /index.pl
        RewriteRule ^/kerberos/saml/.* /index.pl
    </IfModule>

[...]
</VirtualHost>
authnull.html000066400000000000000000000100541325274564300330260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authnull

Null

Authentication Users Password
✔ ✔ ✔

Presentation

LL::NG Null backend is a transparent backend:

  • Authentication: will create session without prompting any credentials (but will register client IP and creation date)
  • Users: will not collect any data (but you can still register environment variables in session)
  • Password: will not change any password

You can use Null backend to bypass some authentication process steps.

Configuration

In Manager, go in General Parameters > Authentication modules and choose Null for authentication, users or password module.

Then, go in Null parameters:

  • Authentication level: authentication level for this module.
authopenid.html000066400000000000000000000144251325274564300333400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authopenid

OpenID

Authentication Users Password
✔ ✔
OpenID protocol is deprecated. You should now use OpenID Connect.

Presentation

LL::NG can delegate authentication to an OpenID server. This requires Perl OpenID consumer module with at least version 1.0.

LL::NG can also act as OpenID server, that allows one to interconnect two LL::NG systems.

LL::NG will then display a form with an OpenID input, wher users will type their OpenID login.

OpenID authentication can proposed as an alternate authentication scheme using the authentication choice method.

LL::NG can use a white list or a black list to filter allowed OpenID domains.

If OpenID is used as users database, attributes will be requested to the server with SREG extension.

Configuration

In Manager, go in General Parameters > Authentication modules and choose OpenID for authentication and/or users.

Then, go in OpenID parameters:

  • Authentication level: authentication level for this module.
  • Secret token: used to check integrity of OpenID response.
  • Authorizated domain:
    • List type: choose white list to define allowed domains or black list to define forbidden domains
    • List: domains list (comma separated values)

To configure requested attributes, edit Exported variables and define attributes:

  • Key: internal session key, can be prefixed by ! to make the attribute required
  • Value: SREG attribute name:
    • fullname
    • nickname
    • language
    • postcode
    • timezone
    • country
    • gender
    • email
    • dob

See also exported variables configuration.

authopenidconnect.html000066400000000000000000000503031325274564300347050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authopenidconnect

Table of Contents

  • Presentation
  • Configuration
    • OpenID Connect Service
    • Authentication and UserDB
    • Register LL::NG to an OpenID Connect Provider
    • Declare the OpenID Connect Provider in LL::NG
      • Metadata
      • JWKS data
      • Exported attributes
      • Options

OpenID Connect

Authentication Users Password
✔ ✔

Presentation

OpenID Connect is a protocol based on REST, OAuth 2.0 and JOSE stacks. It is described here: http://openid.net/connect/.

LL::NG can act as an OpenID Connect Relying Party (RP) towards multiple OpenID Connect Providers (OP). It will get the user identity trough an ID Token, and grab user attributes trough UserInfo endpoint.

As an RP, LL::NG supports a lot of OpenID Connect features:

  • Authorization Code flow
  • Automatic download of JWKS
  • JWT signature verification
  • Access Token Hash verification
  • ID Token validation
  • Get UserInfo as JSON or as JWT
  • Logout on EndSession end point

You can use this authentication module to link your LL::NG server to any OpenID Connect Provider. Here are some examples, witch their specific documentation:

Google France Connect

Configuration

OpenID Connect Service

See OpenIDConnect service configuration chapter.

Authentication and UserDB

In General Parameters > Authentication modules, set:

  • Authentication module: OpenID Connect
  • Users module: OpenID Connect
As passwords will not be managed by LL::NG, you can disable menu password module.

Then in General Parameters > Authentication modules > OpenID Connect parameters, you can set:

  • Authentication level: level of authentication to associate to this module
  • Callback GET parameter: name of GET parameter used to intercept callback (default: openidconnectcallback)
  • State session timeout: duration of a state session (used to keep state information between authentication request and authentication response) in seconds (default: 600)

Register LL::NG to an OpenID Connect Provider

To register LL::NG, you will need to give some information like application name or logo. One of mandatory information is the redirect URL (one or many).

To know this information, just take the portal URL and the Callback GET parameter, for example:

  • http://auth.example.com/?openidcallback=1
  • http://auth.example.com/index.pl?openidcallback=1
  • http://auth.example.com/?lmAuth=oidc&openidcallback=1
If you use the choice backend, you need to add the choice parameter in redirect URL

After registration, the OP must give you a client ID and a client secret, that will be used to configure the OP in LL::NG.

Declare the OpenID Connect Provider in LL::NG

In the Manager, select node OpenID Connect Providers and click on Add OpenID Connect Provider. Give a technical name (no spaces, no special characters), like “sample-op”;

You can then access to the configuration of this OP.

Metadata

The OP should publish its metadata in a JSON file (see for example Google metadata). Copy the content of this file in the textarea.

If no metadata is available, you need to write them in the textarea. Mandatory fields are:

  • issuer
  • authorization_endpoint
  • token_endpoint
  • userinfo_endpoint

You can also define:

  • jwks_uri
  • endsession_endpoint

Example template:

{
  "issuer": "https://auth.example.com/",
  "authorization_endpoint": "https://auth.example.com/oauth2/authorize",
  "token_endpoint": "https://auth.example.com/oauth2/token",
  "userinfo_endpoint": "https://auth.example.com/oauth2/userinfo",
  "end_session_endpoint":"https://auth.example.com/oauth2/logout"
}

JWKS data

JWKS is a JSON file containing public keys. LL::NG can grab them automatically if jwks_uri is defined in metadata. Else you can paste the content of the JSON file in the textarea.

If the OpenID Connect provider only uses symmetric encryption, JWKS data is not useful.

Exported attributes

Define here the mapping between the LL::NG session content and the fields provided in UserInfo response. The fields are defined in OpenID Connect standard, and depends on the scope requested by LL::NG (see options in next chapter).

Claim name Type Example of corresponding LDAP attribute
sub string uid
name string cn
given_name string givenName
family_name string sn
middle_name string
nickname string
preferred_username string displayName
profile string labeledURI
picture string
website string
email string mail
email_verified boolean
gender string
birthdate string
zoneinfo string
locale string preferredLanguage
phone_number string telephoneNumber
phone_number_verified boolean
updated_at string
formatted string registeredAddress
street_address string street
locality string l
region string st
postal_code string postalCode
country string co

So you can define for example:

  • cn ⇒ name
  • sn ⇒ family_name
  • mail ⇒ email
  • uid ⇒ sub

Options

  • Configuration:
    • Configuration endpoint: URL of OP configuration endpoint
    • JWKS data timeout: After this time, LL::NG will do a request to get a fresh version of JWKS data. Set to 0 to disable it.
    • Client ID: Client ID given by OP
    • Client secret: Client secret given by OP
    • Store ID token: Allows one to store the ID token (JWT) inside user session. Don't enable it unless you need to replay this token on an application, or if you need the id_token_hint parameter when using logout.
  • Protocol:
    • Scope: Value of scope parameter (example: openid profile). The openid scope is mandatory.
    • Display: Value of display parameter (example: page)
    • Prompt: Value of prompt parameter (example: consent)
    • Max age: Value of max_age parameter (example: 3600)
    • UI locales: Value of ui_locales parameter (example: en-GB en fr-FR fr)
    • ACR values: Value acr_values parameters (example: loa-1)
    • Token endpoint authentication method: Choice between client_secret_post and client_secret_basic
    • Check JWT signature: Set to 0 to disable JWT signature checking
    • ID Token max age: If defined, LL::NG will check the date of ID token and refuse it if it is too old
    • Use Nonce: If enabled, a nonce will be sent, and verified from the ID Token
  • Display:
    • Display name: Name of the application
    • Logo: Logo of the application
authopenidconnect_franceconnect.html000066400000000000000000000165531325274564300376060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authopenidconnect_franceconnect

Table of Contents

  • Presentation
  • Register on France Connect
  • Declare France Connect in your LL::NG server

France Connect

Presentation

France Connect is an authentication platform made by French government.

It is for the moment only in BETA stage. This documentation will explain how to configure LL::NG with the developer reserved space.

Register on France Connect

Once OpenID Connect service is configured, you need to register to France Connect.

Use the following form: https://doc.integ01.dev-franceconnect.fr/inscription.

You need to provide the callback URLs, for example https://auth.domain.com/?openidcallback=1.

You will then get a client_id and a client_secret.

Declare France Connect in your LL::NG server

Go in Manager and create a new OpenID Connect provider. You can call it france-connect for example.

Click on Metadata and set manually the metadata of the service, using France Connect endpoints. For example:

{
"issuer": "https://fcp.integ01.dev-franceconnect.fr",
"authorization_endpoint": "https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize",
"token_endpoint": "https://fcp.integ01.dev-franceconnect.fr/api/v1/token",
"userinfo_endpoint": "https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo",
"end_session_endpoint":"https://fcp.integ01.dev-franceconnect.fr/api/v1/logout"
}

You can skip JWKS data, they are not provided by France Connect. The security relies on the symmetric key client_secret.

Go in Exported attributes to choose which attributes from “identité pivot” you want to collect. See https://doc.integ01.dev-franceconnect.fr/identite-pivot

Now go in Options:

  • In Configuration, register the client_id and client_secret given by France Connect
  • In Protocol, adapt the scope to the exported attributes you want. See https://doc.integ01.dev-franceconnect.fr/fs-scopes
  • In Display, you can set the name and the logo
authopenidconnect_google.html000066400000000000000000000162021325274564300362410ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authopenidconnect_google

Table of Contents

  • Presentation
  • Register on Google
  • Declare Google in your LL::NG server

Google

Presentation

Do you we have to present Google? The good news is that Google is a standard OpenID Provider, and so you can easily delegate the authentication of LL::NG to Google: https://developers.google.com/identity/protocols/OpenIDConnect

Google does not support logout trough OpenID Connect. If you close your session on LL::NG side, your Google session will still be open.

Register on Google

You need a Google developer account to access to https://console.developers.google.com/

Here you can go in API Manager and get new credentials (client_id and client_secret).

You need to provide the callback URLs, for example https://auth.domain.com/?openidcallback=1.

Declare Google in your LL::NG server

Go in Manager and create a new OpenID Connect provider. You can call it google for example.

Click on Metadata, and use the OpenID Connect configuration URL to load them: https://accounts.google.com/.well-known/openid-configuration.

You can also load the JWKS data from the URL https://www.googleapis.com/oauth2/v3/certs. But as Google rotate their keys, we will also configure a refresh interval on JKWS data.

Go in Exported attributes to choose which attributes you want to collect. Google supports these claims:

  • email
  • email_verified
  • family_name
  • given_name
  • locale
  • name
  • picture
  • sub

Now go in Options:

  • In Configuration, register the client_id and client_secret given by Google. Set also the configuration URI with https://accounts.google.com/.well-known/openid-configuration, and JWKS refresh, for example every day: 86400.
  • In Protocol, adapt the scope to the exported attributes you want. You can for example use openid profile email.
  • In Display, you can set the name and the logo
authproxy.html000066400000000000000000000127121325274564300332400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authproxy

Table of Contents

  • Presentation
  • Configuration
    • External portal
    • Internal portal

Proxy

Authentication Users Password
✔ ✔

Presentation

LL::NG is able to transfer (trough SOAP) authentication credentials to another LL::NG portal, like a proxy.

The difference with remote authentication is that the client will never be redirect to the main LL::NG portal. This configuration is usable if you want to expose your internal SSO portal to another network (DMZ).

Configuration

External portal

In Manager, go in General Parameters > Authentication modules and choose Proxy for authentication and users.

Then, go in Proxy parameters:

  • Portal URL: URL of internal portal
  • Cookie name (optional): name of the cookie of internal portal, if different from external portal
  • SOAP sessions end point (optional): SOAP end point, if not based on internal portal URL with index.pl/sessions suffix

Internal portal

The portal must be configured to accept SOAP authentication requests. See SOAP session backend documentation.

authradius.html000066400000000000000000000126241325274564300333500ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authradius

Table of Contents

  • Presentation
  • Configuration
    • Install Authen::Radius
    • Configuration of LemonLDAP::NG

Radius

Authentication Users Password
✔

Presentation

LL::NG uses Perl Authen::Radius as a simple authentication backend.

Currently, the module is simply handling a Radius Authentication request and has been tested only against a FreeRadius server.

Configuration

Install Authen::Radius

You have to install the corresponding Perl module.

For CentOS/RHEL:

yum install perl-Authen-Radius

In Debian/Ubuntu, install the library through apt-get command

apt-get install libauthen-radius-perl

Configuration of LemonLDAP::NG

In Manager, go in General Parameters > Authentication modules and choose Radius for authentication.

You can then choose any other module for users and password.

Then, go in Radius parameters:

  • Authentication level: authentication level for Radius module
  • Shared secret: this is the passphrase to use to connect to the Radius server
  • Server hostname: this is the hostname or IP address of the Radius server
authremote.html000066400000000000000000000243151325274564300333540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authremote

Table of Contents

  • Presentation
  • Configuration
    • Main LL::NG structure
    • Secondary LL::NG structure
    • Example: interoperability between 2 organizations

Remote

Authentication Users Password
✔ ✔
This module is a LL::NG specific identity federation protocol. You may rather use standards protocols like SAML, OpenID Connect or CAS.

Presentation

  • The main portal is configured to use CDA. The secondary portal is declared in the Manager of the main LL::NG structure (else user will be rejected).
  • The portal of the secondary LL::NG structure is configured to delegate authentication to a remote portal. A request to the main session database is done (trough SOAP session backend) to be sure that the session exists.
  • If exportedAttr is set, only those attributes are copied in the session database of the secondary LL::NG structure. Else, all data are copied in the session database.

  1. User tries to access to an application in the secondary LL::NG structure without having a session in this area
  2. Redirection to the portal of the secondary area (transparent)
  3. Redirection to the portal of the main area and normal authentication (if not done before)
  4. Redirection to the portal of the secondary area (transparent)
  5. Secondary portal check if remote session is available. It can be done via direct access to the session database or using SOAP access. Then it creates the session (with attribute filter)
  6. User can now access to the protected application
Note that if the user is already authenticated on the first portal, all redirections are transparent.

Configuration

Main LL::NG structure

Go in Manager, and:

  • activate CDA in General Parameters » Cookies » Multiple domains
  • declare secondary portal in General Parameters » Advanced Parameters » Security » Trusted domains

Secondary LL::NG structure

Configure the portal to use the remote LL::NG structure.

In Manager, go in General Parameters » Authentication modules and choose Remote for authentication and users.

Then, go in Remote parameters:

  • Portal URL: remote portal URL
  • Cookie name (optional): name of the cookie of primary portal, if different from secondary portal
  • Sessions module: set Lemonldap::NG::Common::Apache::Session::SOAP for SOAP session backend.
  • Sessions module options:
    • proxy: SOAP sessions end point (see SOAP session backend documentation)

Example: interoperability between 2 organizations

Using this, we can do a very simple interoperability system between 2 organizations using two LL::NG structures:

  • each area has 2 portals:
    • One standard portal
    • One remote portal that delegates authentication to the second organization (just another file on the same server)
  • The normal portal has a link included in the authentication form pointing to the remote portal for the users of the other organization

So on each main portal, internal users can access normally, and users issued from the other organization have just to click on the link:

  1. One user tries to access to the portal
  2. External user clicks to be redirected to the remote type portal
  3. After redirection, normal authentication in the remote portal
  4. Redirection to the remote type portal
  5. Validation of the session: external user has now a local session
authsaml.html000066400000000000000000000337121325274564300330160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authsaml

Table of Contents

  • Presentation
  • Configuration
    • SAML Service
    • Authentication and UserDB
    • Register LemonLDAP::NG on partner Identity Provider
    • Register partner Identity Provider on LemonLDAP::NG
      • Metadata
      • Exported attributes
      • Options

SAML

Authentication Users Password
✔ ✔

Presentation

LL::NG can use SAML2 to get user identity and grab some attributes defined in user profile on its Identity Provider (IDP). In this case, LL::NG acts like an SAML2 Service Provider (SP).

Several IDPs are allowed, in this case the user will choose the IDP he wants. You can preselect IDP with an IDP resolution rule.

For each IDP, you can configure attributes that are collected. Some can be mandatory, so if they are not returned by IDP, the session will not open.

LL::NG can also act as SAML IDP, that allows one to interconnect two LL::NG systems.

Configuration

SAML Service

See SAML service configuration chapter.

Authentication and UserDB

In General Parameters > Authentication modules, set:

  • Authentication module: SAML
  • Users module: SAML
As passwords will not be managed by LL::NG, you can disable menu password module.

Register LemonLDAP::NG on partner Identity Provider

After configuring SAML Service, you can export metadata to your partner Identity Provider.

They are available at the EntityID URL, by default: http://auth.example.com/saml/metadata.

Register partner Identity Provider on LemonLDAP::NG

In the Manager, select node SAML identity providers and click on Add SAML IDP. The IDP name is asked, enter it and click OK.

Metadata

You must register IDP metadata here. You can do it either by uploading the file, or get it from IDP metadata URL (this require a network link between your server and the IDP):

You can also edit the metadata directly in the textarea

Exported attributes

For each attribute, you can set:

  • Key name: name of the key in LemonLDAP::NG session (for example “uid” will then be used as $uid in access rules)
  • Mandatory: if set to On, then session will not open if this attribute is not given by IDP.
  • Name: SAML attribute name.
  • Friendly Name: optional, SAML attribute friendly name.
  • Format (optional): SAML attribute format.

Options

General options
  • Resolution Rule: rule that will be applied to preselect an IDP for a user. You have access to all environment variable, like user IP address.

For example, to preselect this IDP for users coming from 129.168.0.0/16 network:

$ENV{REMOTE_ADDR} =~ /^192\.168/
Authentication request
  • NameID format: force NameID format here (email, persistent, transient, etc.). If no value, will use first NameID Format activated in metadata.
  • Force authentication: set ForceAuthn flag in authentication request
  • Passive authentication: set IsPassive flag in authentication request
  • Allow proxied authentication: allow an authentication response to be issued from another IDP that the one we register (proxy IDP). If you disallow this, you should also disallow direct login form IDP, because proxy restriction is set in authentication requests.
  • Allow login from IDP: allow a user to connect directly from an IDP link. In this case, authentication is not a response to an issued authentication request, and we have less control on conditions.
  • Requested authentication context: this context is declared in authentication request. When receiving the request, the real authentication context will be mapped ton an internal authentication level (see how configure the mapping), that you can check to allow or deny session creation.
  • Allow URL as RelayState: Set to On if the RelayState value sent by IDP is the URL where the user must be redirected after authentication.
Session
  • Adapt session lifetime: session lifetime will be adapted from SessionNotOnOrAfter value found in authentication response. It means that if the IDP propose to close session earlier than the default LemonLDAP::NG timeout, the session _utime will be modified so that session is erased at the date indicated by the IDP.
  • Force UTF-8: this will force UTF-8 conversion of attributes values collected from IDP.
  • Store SAML Token: allows one to keep SAML token (assertion) inside user session. Don't enable it unless you need to replay this token on an application.
Signature

These options override service signature options (see SAML service configuration).

  • Sign SSO message: sign SSO message
  • Check SSO message signature: check SSO message signature
  • Sign SLO message: sign SLO message
  • Check SLO message signature: check SLO message signature
Binding
  • SSO binding: force binding to use for SSO (http-redirect, http-post, etc.)
  • SLO binding: force binding to use for SLO (http-redirect, http-post, etc.)
If no binding defined, the default binding in IDP metadata will be used.
Security
  • Encryption mode: set the encryption mode for this IDP (None, NameID or Assertion).
  • Check time conditions: set to Off to disable time conditions checking on authentication responses.
  • Check audience conditions: set to Off to disable audience conditions checking on authentication responses.
authslave.html000066400000000000000000000136611325274564300331750ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authslave

Slave

Authentication Users Password
✔ ✔

Presentation

LL::NG Slave backend relies on HTTP headers to retrieve user login and/or attributes.

  • Authentication: will check user login in a header and create session without prompting any credentials (but will register client IP and creation date)
  • Users: collect data transferred in HTTP headers by the “master”.

It allows one to put LL::NG::portal behind another web SSO, or behind a SSL hardware to delegate SSL authentication to that hardware.

Configuration

In Manager, go in General Parameters > Authentication modules and choose Slave for authentication or users module.

Then, go in Slave parameters:

  • Authentication level: authentication level for this module.
  • Header for user login: header that contains the user main login
  • Master's IP address: the IP addresses of servers which are accredited to authenticate user. This is a security point, to prevent someone to create a session by sending custom headers. You can set one or several IP addresses, separated by spaces, or let this parameter empty to disable the checking.
  • Control header name: header that contains a value to control. Let this parameter empty to disable the checking.
  • Control header content: value to control. Let this parameter empty to disable the checking.

You have then to declare HTTP headers exported by the main SSO (in Exported Variables). Example :

Key (LL::NG name) Value (HTTP header name)
uid Auth-User
mail User-Email

See also exported variables configuration.

authssl.html000066400000000000000000000406121325274564300326600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authssl

Table of Contents

  • Presentation
  • Configuration
    • With Apache
      • Enable SSL in Apache
      • Apache SSL global configuration
      • Apache portal SSL configuration
    • With Nginx
    • Configuration of LemonLDAP::NG
    • Auto reloading SSL Certificates

SSL

Authentication Users Password
✔

Presentation

LL::NG uses Apache SSL module, like any other Apache authentication module, with extra features:

  • Choice of any certificate attribute as user main login
  • Allow no certificate to chain with other authentication methods

Configuration

With Apache

Enable SSL in Apache

You have to install mod_ssl for Apache.

For CentOS/RHEL:

yum install mod_ssl
In Debian/Ubuntu mod_ssl is already shipped in apache*-common package.
For CentOS/RHEL, We advice to disable the default SSL virtual host configured in /etc/httpd/conf.d/ssl.conf.

Apache SSL global configuration

You can then use this default SSL configuration, for example in the head of /etc/lemonldap-ng/portal-apache2.conf:

SSLProtocol all -SSLv2
SSLCipherSuite HIGH:MEDIUM
SSLCertificateFile /etc/httpd/certs/ow2.cert
SSLCertificateKeyFile /etc/httpd/certs/ow2.key
SSLCACertificateFile /etc/httpd/certs/ow2-ca.cert
Put your own files instead of ow2.cert, ow2.key, ow2-ca.cert:
  • SSLCertificateFile: Server certificate
  • SSLCertificateKeyFile: Server private key
  • SSLCACertificateFile: CA certificate to validate client certificates

If you specify port in virtual host, then declare SSL port:

NameVirtualHost *:80
NameVirtualHost *:443

Apache portal SSL configuration

Edit the portal virtual host to enable SSL double authentication:

SSLEngine On
SSLVerifyClient optional
SSLVerifyDepth 10
SSLOptions +StdEnvVars
SSLUserName SSL_CLIENT_S_DN_CN

All SSL options are documented in Apache mod_ssl page.

Here are the main options used by LL::NG:

  • SSLVerifyClient: set to optional to allow user with a bad certificate to access to LL::NG portal page. To switch to another authentication backend, use the Multi module, for example: Multi SSL;LDAP
  • SSLOptions: set to +StdEnvVars to get certificate fields in environment variables
  • SSLUserName (optional): certificate field that will be used to identify user in LL::NG portal virtual host

With Nginx

Enable SSL:

ssl on;
ssl_verify_client optional;
ssl_certificate /etc/letsencrypt/live/my/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/my/privkey.pem;
ssl_verify_depth 3;
ssl_client_certificate /etc/nginx/ssl/ca.pem;
ssl_crl /etc/nginx/ssl/crl/my.crl;

You must also export SSL_CLIENT_S_DN_CN in FastCGI params:

map $ssl_client_s_dn  $ssl_client_s_dn_cn {
           default           "";
           ~/CN=(?<CN>[^/]+) $CN;
      }
fastcgi_param  SSL_CLIENT_S_DN_CN $ssl_client_s_dn_cn;

Configuration of LemonLDAP::NG

In Manager, go in General Parameters > Authentication modules and choose SSL for authentication.

You can then choose any other module for users and password.

Then, go in SSL parameters:

  • Authentication level: authentication level for this module
  • Extracted certificate field: field of the certificate affected to $user internal variable

Auto reloading SSL Certificates

A known problematic is that many browser (Firefox, Chrome) remembers the fact that the certificate is not available at a certain time. It is particularly important for smart cards: when the card is not inserted before the browser starts, the user must restart his browser, or at least refresh (F5) the page.

It is possible with AJAX code and 3 Apache locations to bypass this limitation.

1. Modify the portal virtual host to match this example:

    SSLEngine On
    SSLCACertificateFile /etc/apache2/ssl/ca.crt
    SSLCertificateKeyFile /etc/apache2/ssl/lemonldap.key
    SSLCertificateFile /etc/apache2/ssl/lemonldap.crt
 
    SSLVerifyDepth 10
    SSLOptions +StdEnvVars
    SSLUserName SSL_CLIENT_S_DN_CN
 
    # DocumentRoot
    DocumentRoot /var/lib/lemonldap-ng/portal/
    <Directory /var/lib/lemonldap-ng/portal/>
        Order Deny,Allow
        Allow from all
        Options +ExecCGI +FollowSymLinks
        SSLVerifyClient none
    </Directory>
 
    <Location /index>
        Order Deny,Allow
        Allow from all
        SSLVerifyClient none
    </Location>
 
    <Location /testssl>
        Order Deny,Allow
        Allow from all
        SSLVerifyClient require
    </Location>
 
    Alias /sslok /var/lib/lemonldap-ng/portal
    <Location /sslok>
        Order Deny,Allow
        Allow from all
        SSLVerifyClient require
    </Location>
  • /index/ is an unprotected page to display a SSL test button
  • /testssl/ is a SSL protected page to check the certificate
  • /sslok/ is the new LemonLDAP::NG portal. You need to declare the new url in the manager: Portal → URL: https://auth.example.com/sslok/

2. Then you need to construct the Ajax page, for example in /index/bouton.html. It looks like this:

<body>
<script src="./jquery-2.1.4.min.js"             type="text/javascript"> </script>
<!--<script src="./jquery-ui-1.8-rass.js"   type="text/javascript">  </script>-->
 
 
<a href="http://www.google.fr" class="enteteBouton" id="continuerButton"><img src=authent.png></a>
<script>
$('.enteteBouton').click( function (e) {
  var b=navigator.userAgent.toLowerCase();
  if(b.indexOf("msie")!==-1){
    document.execCommand("ClearAuthenticationCache")
  }
  e.preventDefault();
  $.ajax({
        url:"https://auth.example.com/testssl",
        beforeSend:function(){},
        type:"GET",
        dataType:"html",
        success:function(c,a){ 
          if (c !== "") {
                alert("Carte OK");
                window.location.href = "https://auth.example.com/sslok/";
          }
          else {
              alert('Carte KO');
          }
        },
        error:function (xhr, ajaxOptions, thrownError){
          if(xhr.status==404) {
                alert("Carte OK");
                window.location.href = "https://auth.example.com/sslok/";
          }
          else {
              alert('Carte KO');
          }
        },
        complete:function(c,a){}
  });
});
</script>
</body>
It is incompatible with authentication chaining (see Stack Multiple backends), because of Apache parameter “SSLVerifyClient”, which must have the value “require”
authtwitter.html000066400000000000000000000120761325274564300335640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authtwitter

Twitter

Authentication Users Password
✔

Presentation

Twitter is a famous microblogging server. Twitter use OAuth protocol to allow applications to reuse its own authentication process (it means, if your are connected to Twitter, other applications can trust Twitter and let you in).

You need Net::Twitter package, with a very recent version (>3).

You need to register a new application on Twitter to get API key and API secret. See Twitter FAQ on how to do that:.

Configuration

In Manager, go in General Parameters > Authentication modules and choose Twitter for authentication module.

You can then choose any other module for users and password.

Then, go in Twitter parameters:

  • Authentication level: authentication level for this module.
  • API key: API key from Twitter
  • API secret: API secret from Twitter
  • Application name (optional): Application name (visible in Twitter)
authwebid.html000066400000000000000000000157011325274564300331520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authwebid

Table of Contents

  • Presentation
  • Configuration
    • Apache configuration
    • Tests

WebID

Authentication Users Password
✔ ✔

Presentation

WebID is a way to uniquely identify a person, company, organisation, or other agent using a URI and a certificate.

You need Web::ID package.

Configuration

In Manager, go in General Parameters > Authentication modules and choose WebID for authentication module. You can also use WebID as user database.

Then, go in WebID parameters:

  • Authentication level: authentication level for this module.
  • WebID whitelist: list of space separated hosts granted to host FOAF document. You can use '*' character. Example :
    *.partner.com

If you use WebID as user database, declare values in exported variables :

  • use any key name you want. If you want to refuse access when a data is missing, just add a “!” before the key name
  • in the value field, set the field name. Take a look at http://xmlns.com/foaf/spec/#sec-crossref. Example :
    name => foaf:name

See also exported variables configuration.

Apache configuration

Portal host must be configured to use SSL and must ask for client certificate. It is recommended to use optional_no_ca since WebID doesn't use certificate authorities :

<VirtualHost _default_:443>
ServerName auth.example.com
SSLEngine on
SSLCertificateFile ...
SSLCertificateKeyFile ...
SSLVerifyClient optional_no_ca
...
</VirtualHost>

Tests

To test this, you can build your own WebID certificate using one of :

  • Web::ID::Certificate::Generator
  • my-profile.eu
  • gen-webid-cert.sh
authyubikey.html000066400000000000000000000123351325274564300335410ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:authyubikey

Yubikey

Authentication Users Password
✔

Presentation

The Yubikey is a small material token shipped by Yubico. It sends an OTP, which is validated against Yubico server.

You need Auth::Yubikey_WebClient package.

You need to get an client ID and a secret key from Yubico. See Yubico API page.

Configuration

In Manager, go in General Parameters > Authentication modules and choose Yubikey for authentication module.

You can then choose any other module for users and password.

Then, go in Yubikey parameters:

  • Authentication level: authentication level for this module.
  • API client ID: API client ID from Yubico
  • API secret key: API secret key from Yubico
  • OTP public ID part size: Part of Yubikey OTP that will be used as the media identifier (default: 12)
You have to register the media identifier in your user backend (LDAP or SQL) to match the yubikey with a real user. For example it can be stored as a second value of the uid attribute in the LDAP directory:
  • uid: coudot
  • uid: 123456789012
bootswatch/000077500000000000000000000000001325274564300324615ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current3.3.4/000077500000000000000000000000001325274564300331265ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/bootswatchflatly/000077500000000000000000000000001325274564300344215ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/bootswatch/3.3.4bootstrap.min.css000066400000000000000000000235401325274564300377360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/bootswatch/3.3.4/flatly bootswatch:3.3.4:flatly:bootstrap.min.css [LemonLDAP::NG]
LemonLDAP::NG LemonLDAP::NG
  • Download
  • Documentation
  • Screenshots
  • Contact
    • Mails, IRC and more
    • The team
    • Professional Services
    • References
    • Sponsors
  • Login

You are here: start » bootswatch » 3.3.4 » flatly » bootstrap.min.css

bootswatch:3.3.4:flatly:bootstrap.min.css

This topic does not exist yet

You've followed a link to a topic that doesn't exist yet. If permissions allow, you may create it by clicking on “Create this page”.

Sidebar

Hosted by


Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 3.0 Unported

browseablesessionbackend.html000066400000000000000000000361341325274564300362420ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:browseablesessionbackend

Table of Contents

  • Presentation
  • Browseable NoSQL
  • Browseable SQL
    • Prepare database
    • Manager
  • Browseable LDAP
  • Security

Browseable session backend

Presentation

Browseable session backend (Apache::Session::Browseable) works exactly like Apache::Session::* corresponding module but add index that increase session explorer and session restrictions performances.

If you use features like SAML (authentication and issuer), CAS (issuer) and password reset self-service, you also need to index some fields.

Without index, LL::NG will have to retrieve all sessions stored in backend and parse them to find the needed sessions. With index, LL::NG wil be able to get only wanted sessions from the backend.

The following table list fields to index depending on the feature you want to increase performance:

Feature Fields to index
Session explorer ipAddr WHATTOTRACE
Session explorer (persistent sessions) _session_uid
Session restrictions ipAddr WHATTOTRACE
SAML authentication and issuer _saml_id ProxyID _nameID _assert_id _art_id _session_id
CAS issuer _cas_id
Password reset user
WHATTOTRACE must be replaced by the attribute or macro configured in the What To Trace parameter (REMOTE_USER)
It is advised to use separate session backends for standard sessions, SAML sessions and CAS sessions, in order to manage index separately.
Documentation below explains how set index on ipAddr and _whatToTrace. Adapt it to configure the index you need.

Browseable NoSQL

You can use Redis and set up the database like explained in Redis session backend.

You then just have to add the Index parameter in General parameters » Sessions » Session storage » Apache::Session module :

Required parameters
Name Comment Example
server Redis server 127.0.0.1:6379
Index Index _whatToTrace ipAddr

Browseable SQL

This documentation concerns MySQL. Some adaptations are needed with other databases.

Prepare database

Database must be prepared exactly like in SQL session backend except that a field must be added for each data to index.

CREATE TABLE sessions (
    id CHAR(32) NOT NULL PRIMARY KEY,
    a_session BLOB,
    _whatToTrace VARCHAR(255),
    ipAddr VARCHAR(15),
    KEY _whatToTrace (_whatToTrace),
    KEY ipAddr (ipAddr)
    );
Change char(32) by char(64) if you use the now recommended SHA256 hash algorithm. See Sessions for more details

Manager

Go in the Manager and set the session module (Apache::Session::Browseable::MySQL for MySQL) in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

Required parameters
Name Comment Example
DataSource The DBI string dbi:mysql:dbname=sessions
UserName The database username lemonldapng
Password The database password mysuperpassword
Index Index _whatToTrace ipAddr
Apache::Session::Browseable::MySQL doesn't use locks so performances are keeped.

For databases like PostgreSQL, don't forget to add “Commit” with a value of 1

Browseable LDAP

Go in the Manager and set the session module to Apache::Session::Browseable::LDAP. Then configure the options like in LDAP session backend.

You need to add the Index field and can also configure the ldapAttributeIndex field to set the attribute name where index values will be stored.

Required parameters
Name Comment Example
ldapServer URI of the server ldap://localhost
ldapConfBase DN of sessions branch ou=sessions,dc=example,dc=com
ldapBindDN Connection login cn=admin,dc=example,dc=password
ldapBindPassword Connection password secret
Index Index list _whatToTrace ipAddr
Optional parameters
Name Comment Default value
ldapObjectClass Objectclass of the entry applicationProcess
ldapAttributeId Attribute storing session ID cn
ldapAttributeContent Attribute storing session content description
ldapAttributeIndex Attribute storing index ou

Security

Restrict network access to the backend.

You can also use different user/password for your servers by overriding parameters globalStorage and globalStorageOptions in lemonldap-ng.ini file.

captcha.html000066400000000000000000000106221325274564300325760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:captcha

Captcha

Presentation

Captcha is a security mechanism aimed to prevent robots to submit forms.

Captchas are available on the following forms:

  • Login form: where user enters login and password to authenticate
  • Password reset by mail form: where user enters mail to recover a lost password
  • Register form: where user enters information to create a new account

We use the Perl module Authen::Captcha to generate codes and images, which look like this:

You need to install Perl Authen::Captcha module if you enable Captcha feature.

Configuration

Go in General parameters > Portal > Captcha:

  • Activation in login form: set to 1 to display captcha in login form
  • Activation in password reset by mail form: set to 1 to display captcha in password reset by mail form
  • Activation in register form: set to 1 to display captcha in register form
  • Size: length of captcha
  • Captcha module name: Apache session module used to store generated captchas
  • Captcha module options: options for above module
cda.html000066400000000000000000000122541325274564300317250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:cda

Cross Domain Authentication

Presentation

For security reason, a cookie provided for a domain cannot be sent to another domain. To extend SSO on several domains, a cross-domain mechanism is implemented in LemonLDAP::NG.
  1. User owns SSO cookies on the main domain (see Login kinematics)
  2. User tries to access a protected application in a different domain
  3. Handler does not see SSO cookies (because it is not in main domain) and redirects user on Portal
  4. Portal recognizes the user with its SSO cookies, and see he is coming from a different domain
  5. Portal redirects user on protected application with a token as URL parameter. The token is linked to a session which contains the real session ID
  6. Handler detects URL parameter, gets the real session ID, delete the token session and creates a SSO cookies on its domain, with session ID as value

Configuration

Go in Manager, General Parameters » Cookies » Multiple domains and set to On.

To use this feature only locally, edit lemonldap-ng.ini in section [all]:

[all]
cda = 1
changeconfbackend.html000066400000000000000000000121261325274564300345770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:changeconfbackend

Table of Contents

  • How it works
  • Let's go
  • See also

How to change configuration backend

LemonLDAP::NG provides a script to change configuration backend easily keeping history. It is set in LemonLDAP::NG utilities directory (convertConfig).

How it works

The convertConfig utility reads 2 LL::NG configuration files (lemonldap-ng.ini):

  • Current: to extract all configuration history
  • New: to write all configuration history

Let's go

  • Prepare your new lemonldap-ng.ini file
  • Configure your new backend (create SQL database,…)
  • Launch that:
convertConfig --current=/etc/lemonldap-ng/lemonldap-ng.ini --new=/new/lemonldap-ng.ini
  • Install the new lemonldap-ng.ini file at the place of the old file in all LL::NG servers
  • Restart all your Apache servers

See also

Documentation is available for configuration backends :

  • SQL
  • File
  • LDAP
  • SOAP proxy mechanism
configapache.html000066400000000000000000000103621325274564300336030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:configapache

Deploy Apache configuration

This step should already have been if you installed LL::NG with packages.

Files

With tarball installation, Apache configuration files will be installed in /usr/local/lemonldap-ng/etc/, else they are in /etc/lemonldap-ng.

You have to include them in Apache main configuration, for example:

include /usr/local/lemonldap-ng/etc/portal-apache2.conf
include /usr/local/lemonldap-ng/etc/handler-apache2.conf
include /usr/local/lemonldap-ng/etc/manager-apache2.conf
include /usr/local/lemonldap-ng/etc/test-apache2.conf
  • You can also use symbolic links in conf.d or sites-available Apache directory.
  • If you have run the Debian/Ubuntu install command, just use:
a2ensite manager-apache2.conf
a2ensite portal-apache2.conf
a2ensite handler-apache2.conf
a2ensite test-apache2.conf

Modules

You will also need to load some Apache modules:

  • mod_rewrite
  • mod_perl
  • mod_alias
  • mod_fcgid
With Debian/Ubuntu:
a2enmod fcgid perl alias rewrite
configlocation.html000066400000000000000000001076201325274564300341760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:configlocation

Table of Contents

  • Backends
  • Manager
  • Configuration text editor
  • Command Line Interface (CLI)
  • Apache
    • Portal
    • Manager
    • Handler
  • Nginx
    • Portal
    • Manager
    • Handler
  • Configuration reload
  • Local file

Configuration overview

Backends

LemonLDAP::NG configuration is stored in a backend that allows all modules to access it.

Note that all LL::NG components must have access:
  • to the configuration backend
  • to the sessions storage backend

Detailed configuration backends documentation is available here.

By default, configuration is stored in files, so access trough network is not possible. To allow this, use SOAP for configuration access, or use a network service like SQL database or LDAP directory.

Configuration backend can be set in the local configuration file, in configuration section.

For example, to configure the File configuration backend:

[configuration]
type=File
dirName = /usr/local/lemonldap-ng/data/conf
See How to change configuration backend to known how to change this.

Manager

Most of configuration can be done trough LemonLDAP::NG Manager (by default http://manager.example.com).

By default, Manager is protected to allow only the demonstration user “dwho”.

This user will not be available anymore if you configure a new authentication backend! Remember to change the access rule in Manager virtual host to allow new administrators.

If you can not access the Manager anymore, you can unprotect it by editing lemonldap-ng.ini and changing the protection parameter:

[manager]
 
# Manager protection: by default, the manager is protected by a demo account.
# You can protect it :
# * by Apache itself,
# * by the parameter 'protection' which can take one of the following
# values :
#   * authenticate : all authenticated users can access
#   * manager      : manager is protected like other virtual hosts: you
#                    have to set rules in the corresponding virtual host
#   * rule: <rule> : you can set here directly the rule to apply
#   * none         : no protection
See Manager protection documentation to know how to use Apache modules or LL::NG to manage access to Manager.

The Manager displays main branches:

  • General Parameters: Authentication modules, portal, etc.
  • Variables: User information, macros and groups used to fill SSO session
  • Virtual Hosts: Access rules, headers, etc.
  • SAML 2 Service: SAML metadata administration
  • SAML identity providers: Registered IDP
  • SAML service providers: Registered SP
  • OpenID Connect Service: OpenID Connect service configuration
  • OpenID Connect Providers: Registered OP
  • OpenID Connect Relying Parties: Registered RP

LemonLDAP::NG configuration is mainly a key/value structure, so Manager will present all keys into a structured tree. A click on a key will display the associated value.

When all modifications are done, click on Save to store configuration.

LemonLDAP::NG will do some checks on configuration and display errors and warnings if any. Configuration is not saved if errors occur.

Configuration text editor

LemonLDAP::NG provide a script that allows one to edit configuration without graphical interface, this script is called lmConfigEditor and is stored in the LemonLDAP::NG bin/ directory, for example /usr/share/lemonldap-ng/bin:

/usr/share/lemonldap-ng/bin/lmConfigEditor
This script must be run as root, it will then use the Apache user and group to access configuration.

The script uses the editor system command, that links to your favorite editor. To change it:

update-alternatives --config editor

The configuration is displayed as a big Perl Hash, that you can edit:

$VAR1 = {
          'ldapAuthnLevel' => '2',
          'notificationWildcard' => 'allusers',
          'loginHistoryEnabled' => '1',
          'key' => 'q`e)kJE%<&wm>uaA',
          'samlIDPSSODescriptorSingleSignOnServiceHTTPPost' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;#PORTAL#/saml/singleSignOn;',
          'portalSkin' => 'pastel',
          'failedLoginNumber' => '5',
          ...
          };

If a modification is done, the configuration is saved with a new configuration number. Else, current configuration is kept.

Command Line Interface (CLI)

This an experimental tool that may evolve in next releases.

LemonLDAP::NG provide a script that allows one to edit configuration items in non interactive mode. This script is called lemonldap-ng-cli and is stored in the LemonLDAP::NG bin/ directory, for example /usr/share/lemonldap-ng/bin:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli
This script must be run as root, it will then use the Apache user and group to access configuration.

To see available actions, do:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli help

You can force an update of configuration cache with:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli update-cache

To get information about current configuration:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli info

To view a configuration parameter, for example portal URL:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli get portal

To set a parameter, for example domain:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli set domain example.org

You can use accessors (options) to change the behavior:

  • -sep: separator of hierarchical values (by default: /).
  • -iniFile: the lemonldap-ng.ini file to use if not default value.
  • -yes: do not prompt for confirmation before saving new configuration.
  • -cfgNum: the configuration number. If not set, it will use the latest configuration.
  • -force: set it to 1 to save a configuration earlier than latest.

Some examples:

/usr/share/lemonldap-ng/bin/lemonldap-ng-cli -cfgNum 10 get exportedHeaders/test1.example.com
/usr/share/lemonldap-ng/bin/lemonldap-ng-cli -yes 1 set notification 1
/usr/share/lemonldap-ng/bin/lemonldap-ng-cli -sep ',' get macros,_whatToTrace

Apache

LemonLDAP::NG does not manage Apache configuration

LemonLDAP::NG ships 3 Apache configuration files:

  • portal-apache2.conf: Portal virtual host, with SOAP and Issuer end points
  • manager-apache2.conf: Manager virtual host
  • handler-apache2.conf : Handler declaration, reload and sample virtual hosts

See how to deploy them.

Mod Perl must be loaded before LemonLDAP::NG, so include configuration after the mod_perl LoadModule directive.

Portal

In Portal virtual host, you will find several configuration parts:

  • Standard virtual host directives, to serve portal pages:
    ServerName auth.example.com
 
    # DocumentRoot
    DocumentRoot /usr/local/lemonldap-ng/htdocs/portal/
    <Directory /usr/local/lemonldap-ng/htdocs/portal/>
        Order allow,deny
        Allow from all
        Options +ExecCGI
    </Directory>
 
    # Perl script
    <Files *.pl>
        SetHandler perl-script
        PerlResponseHandler ModPerl::Registry
    </Files>
 
    # Directory index
    <IfModule mod_dir.c>
        DirectoryIndex index.pl index.html
    </IfModule>
  • SOAP end points (inactivated by default):
    # SOAP functions for sessions management (disabled by default)
    <Location /index.pl/adminSessions>
        Order deny,allow
        Deny from all
    </Location>
 
    # SOAP functions for sessions access (disabled by default)
    <Location /index.pl/sessions>
        Order deny,allow
        Deny from all
    </Location>
 
    # SOAP functions for configuration access (disabled by default)
    <Location /index.pl/config>
        Order deny,allow
        Deny from all
    </Location>
 
    # SOAP functions for notification insertion (disabled by default)
    <Location /index.pl/notification>
        Order deny,allow
        Deny from all
    </Location>
  • Issuer rewrite rules (requires mod_rewrite):
    # SAML2 Issuer
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^/saml/metadata /metadata.pl
        RewriteRule ^/saml/.* /index.pl
    </IfModule>
 
    # CAS Issuer
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^/cas/.* /index.pl
    </IfModule>
 
    # OpenID Issuer
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^/openidserver/.* /index.pl
    </IfModule>
 
    # OpenID Connect Issuer
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^/oauth2/.* /index.pl
        RewriteRule ^/.well-known/openid-configuration$ /openid-configuration.pl
    </IfModule>
 
    # Get Issuer
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteRule ^/get/.* /index.pl
    </IfModule>
  • Some Perl optimizations:
# Best performance under ModPerl::Registry
# Uncomment this to increase performance of Portal
<Perl>
    require Lemonldap::NG::Portal::SharedConf;
    Lemonldap::NG::Portal::SharedConf->compile(
        qw(delete header cache read_from_client cookie redirect unescapeHTML));
    # Uncomment this line if you use Lemonldap::NG menu
    require Lemonldap::NG::Portal::Menu;
    # Uncomment this line if you use portal SOAP capabilities
    require SOAP::Lite;
</Perl>

Manager

Manager virtual host is used to serve configuration interface and local documentation. It is run as a FastCGI application:

    # FASTCGI CONFIGURATION
    # ---------------------
 
    # 1) URI management
    RewriteEngine on
 
    RewriteRule "^/$" "/psgi/manager-server.fcgi" [PT]
    # For performances, you can delete the previous RewriteRule line after
    # puttings html files: simply put the HTML results of different modules
    # (configuration, sessions, notifications) as manager.html, sessions.html,
    # notifications.html and uncomment the 2 following lines:
    # DirectoryIndex manager.html
    # RewriteCond "%{REQUEST_FILENAME}" "!\.html$"
 
    # REST URLs
    RewriteCond "%{REQUEST_FILENAME}" "!^/(?:static|doc|fr-doc|lib).*"
    RewriteRule "^/(.+)$" "/psgi/manager-server.fcgi/$1" [PT]
 
    Alias /psgi/ /var/lib/lemonldap-ng/manager/psgi/
 
    # 2) FastCGI engine
 
    # You can choose any FastCGI system. Here is an example using mod_fcgid
    # mod_fcgid configuration
    <Directory /var/lib/lemonldap-ng/manager/psgi/>
        SetHandler fcgid-script
        Options +ExecCGI
    </Directory>
 
    # If you want to use mod_fastcgi, replace lines below by:
    #FastCgiServer /var/lib/lemonldap-ng/manager/psgi/manager-server.fcgi
 
    # Or if you prefer to use CGI, use /psgi/manager-server.cgi instead of
    # /psgi/manager-server.fcgi and adapt the rewrite rules.

Configuration interface access is not protected by Apache but by LemonLDAP::NG itself (see lemonldap-ng.ini).

Handler

  • Load Handler in Apache memory:
PerlOptions +GlobalRequest
PerlModule Lemonldap::NG::Handler
  • Catch error pages:
ErrorDocument 403 http://auth.example.com/?lmError=403
ErrorDocument 500 http://auth.example.com/?lmError=500
ErrorDocument 503 http://auth.example.com/?lmError=503
  • Reload virtual host:
<VirtualHost *:80>
    ServerName reload.example.com
 
    # Configuration reload mechanism (only 1 per physical server is
    # needed): choose your URL to avoid restarting Apache when
    # configuration change
    <Location /reload>
        Order deny,allow
        Deny from all
        Allow from 127.0.0.0/8
        SetHandler perl-script
        PerlResponseHandler Lemonldap::NG::Handler->reload
    </Location>
 
    # Uncomment this to activate status module
    #<Location /status>
    #    Order deny,allow
    #    Deny from all
    #    Allow from 127.0.0.0/8
    #    SetHandler perl-script
    #    PerlResponseHandler Lemonldap::NG::Handler->status
    #</Location>
 
</VirtualHost>

Then, to protect a standard virtual host, the only configuration line to add is:

PerlHeaderParserHandler Lemonldap::NG::Handler

Nginx

LemonLDAP::NG does not manage Nginx configuration

LemonLDAP::NG ships 3 Nginx configuration files:

  • portal-nginx.conf: Portal virtual host, with SOAP and Issuer end points
  • manager-nginx.conf: Manager virtual host
  • handler-nginx.conf : Handler reload virtual hosts

See how to deploy them.

LL::NG FastCGI server must be loaded separately.

Portal

In Portal virtual host, you will find several configuration parts:

  • Standard virtual host directives, to serve portal pages:
server {
  listen 80;
  server_name auth.example.com;
  root /var/lib/lemonldap-ng/portal/;
 
  location ~ \.pl(?:$|/) {
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    fastcgi_param LLTYPE cgi;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    set $sn $request_uri;
    if ($sn ~ "^(.*)\?") {
        set $sn $1;
    }
    fastcgi_param SCRIPT_NAME $sn;
    fastcgi_split_path_info ^(.*\.pl)(/.+)$;
  }
 
  index index.pl;
 
  location / {
    try_files $uri $uri/ =404;
  }
}
  • SOAP end points (inactivated by default):
  # SOAP functions for sessions management (disabled by default)
  location /index/adminSessions {
    deny all;
  }
 
  # SOAP functions for sessions access (disabled by default)
  location /index.pl/sessions {
    deny all;
  }
 
  # SOAP functions for configuration access (disabled by default)
  location /index.pl/config {
    deny all;
  }
 
  # SOAP functions for notification insertion (disabled by default)
  location /index.pl/notification {
    deny all;
  }
  • Issuer rewrite rules:
    # SAML2 Issuer
    rewrite ^/saml/metadata /metadata.pl last;
    rewrite ^/saml/.* /index.pl last;
 
    # CAS Issuer
    rewrite ^/cas/.* /index.pl;
 
    # OpenID Issuer
    rewrite ^/openidserver/.* /index.pl last;
 
    # OpenID Connect Issuer
    rewrite ^/oauth2/.* /index.pl last;
    rewrite ^/.well-known/openid-configuration$ /openid-configuration.pl last;
 
    # Get Issuer
    rewrite ^/get/.* /index.pl;

Manager

Manager virtual host is used to serve configuration interface and local documentation.

server {
  listen 80;
  server_name manager.example.com;
  root /usr/share/lemonldap-ng/manager/;
 
  if ($uri !~ ^/(static|doc|fr-doc|lib|javascript)) {
    rewrite ^/(.*)$ /manager.psgi/$1 break;
  }
 
  location /manager.psgi {
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    fastcgi_param LLTYPE manager;
    fastcgi_param SCRIPT_NAME /manager.psgi;
  }
 
  location / {
    index manager.psgi;
    try_files $uri $uri/ =404;
  }
}

By default, configuration interface access is not protected by Nginx but by LemonLDAP::NG itself (see lemonldap-ng.ini).

Handler

Nginx handler is provided by the LemonLDAP::NG FastCGI server.

  • Handle errors:
error_page 403 http://auth.example.com/?lmError=403;
error_page 500 http://auth.example.com/?lmError=500;
error_page 503 http://auth.example.com/?lmError=503;
  • Reload virtual host:
server {                                                               
  listen 80;                                                        
  server_name reload.example.com;                                      
  root /var/www/html;
 
  location = /reload {                                                 
    allow 127.0.0.1; 
    deny all;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
  } 
 
  # Other requests                                                    
  location / {                                                         
    deny all;
  } 
 
  # Uncomment this if status is enabled                                
  #location = /status {                                              
  #  allow 127.0.0.1;
  #  deny all;
  #  include /etc/nginx/fastcgi_params;                                
  #  fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
  #  fastcgi_param LLTYPE status;
  #}
}

Then, to protect a standard virtual host, you must insert this (or create an included file):

  # Insert $_user in logs
  include /etc/lemonldap-ng/nginx-lmlog.conf;
  access_log /var/log/nginx/access.log lm_combined;
 
  # Internal call to FastCGI server
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    fastcgi_param HOST $http_host;
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  }
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    # Set REMOTE_USER (for FastCGI apps only)
    #fastcgi_param REMOTE_USER $lmremote_user
 
    ##################################
    # PASSING HEADERS TO APPLICATION #
    ##################################
 
    # IF LUA IS SUPPORTED
    #include /path/to/nginx-lua-headers.conf
 
    # ELSE
    # Set manually your headers
    #auth_request_set $authuser $upstream_http_auth_user;
    #proxy_set_header Auth-User $authuser;
    # OR
    #fastcgi_param HTTP_AUTH_USER $authuser;
 
    # Then (if LUA not supported), change cookie header to hide LLNG cookie
    #auth_request_set $lmcookie $upstream_http_cookie;
    #proxy_set_header Cookie: $lmcookie;
    # OR
    #fastcgi_param HTTP_COOKIE $lmcookie;
 
    # Insert then your configuration (fastcgi_* or proxy_*)

Configuration reload

As Handlers keep configuration in cache, when configuration change, it should be updated in Handlers. An Apache restart will work, but LemonLDAP::NG offers the mean to reload them through an HTTP request. Configuration reload will then be effective in less than 10 minutes.

After configuration is saved by Manager, LemonLDAP::NG will try to reload configuration on distant Handlers by sending an HTTP request to the servers. The servers and URLs can be configured in Manager, General Parameters > reload configuration URLs: keys are server names or IP the requests will be sent to, and values are the requested URLs.

These parameters can be overwritten in LemonLDAP::NG ini file, in the section apply.

You only need a reload URL per physical servers, as Handlers share the same configuration cache on each physical server.

The reload target is managed in Apache or Nginx configuration, inside a virtual host protected by LemonLDAP::NG Handler (see below examples in Apache→handler or Nginx→Handler).

You must allow access to declared URLs to your Manager IP.

Local file

LemonLDAP::NG configuration can be managed in a local file with INI format. This file is called lemonldap-ng.ini and has the following sections:

  • configuration: where configuration is stored
  • apply: reload URL for distant Hanlders
  • all: parameters for all modules
  • portal: parameters only for Portal
  • manager: parameters only for Manager
  • handler: parameters only for Handler

When you set a parameter in lemonldap-ng.ini, it will override the parameter from the global configuration.

For example, to override configured skin for portal:

[portal]
portalSkin = dark
You need to know the technical name of configuration parameter to do this. You can refer to parameter list to find it.
confignginx.html000066400000000000000000000134301325274564300335040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:confignginx

Table of Contents

  • FastCGI server
    • Debian/Ubuntu
    • Red Hat/CentOS
  • Files
    • Debian/Ubuntu
    • Red Hat/CentOS

Deploy Nginx configuration

FastCGI server

To use Nginx, you must install LemonLDAP::NG FastCGI server, which is not installed by default lemonldap-ng metapackage.

Debian/Ubuntu

apt install lemonldap-ng-fastcgi-server

Enable and start the service :

systemctl enable llng-fastcgi-server
systemctl start llng-fastcgi-server

Red Hat/CentOS

yum install lemonldap-ng-fastcgi-server

Enable and start the service :

systemctl enable llng-fastcgi-server
systemctl start llng-fastcgi-server

Files

With tarball installation, Nginx configuration files will be installed in /usr/local/lemonldap-ng/etc/, else they are in /etc/lemonldap-ng.

You have to include them in Nginx main configuration.

Debian/Ubuntu

Link files into sites-available directory (should already have been done if you used packages):

ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/sites-available/
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/sites-available/

Enable sites:

ln -s /etc/nginx/sites-available/handler-nginx.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/manager-nginx.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/portal-nginx.conf /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/test-nginx.conf /etc/nginx/sites-enabled/

Red Hat/CentOS

Link files directly in conf.d directory:

ln -s /etc/lemonldap-ng/handler-nginx.conf /etc/nginx/conf.d/
ln -s /etc/lemonldap-ng/manager-nginx.conf /etc/nginx/conf.d/
ln -s /etc/lemonldap-ng/portal-nginx.conf /etc/nginx/conf.d/
ln -s /etc/lemonldap-ng/test-nginx.conf /etc/nginx/conf.d/
configvhost.html000066400000000000000000000404541325274564300335320ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:configvhost

Table of Contents

  • Apache configuration
    • Hosted application
    • Reverse proxy
    • Add a floating menu
  • Nginx configuration
    • Hosted application
    • Reverse proxy
  • LemonLDAP::NG configuration
    • Access rules and HTTP headers
    • POST data
    • Options

Manage virtual hosts

LemonLDAP::NG configuration is build around Apache or Nginx virtual hosts. Each virtual host is a protected resource, with access rules, headers, POST data and options.

Apache configuration

To protect a virtual host in Apache, the LemonLDAP::NG Handler must be activated (see Apache global configuration).

Then you can take any virtual host, and simply add this line to protect it:

PerlHeaderParserHandler Lemonldap::NG::Handler

Hosted application

Example of a protected virtual host for a local application:

<VirtualHost *:80>
        ServerName localsite.example.com
 
        PerlHeaderParserHandler Lemonldap::NG::Handler
 
        DocumentRoot /var/www/localsite
 
        ErrorLog /var/log/apache2/localsite_error.log
        CustomLog /var/log/apache2/localsite_access.log combined
 
</VirtualHost>

Reverse proxy

Example of a protected virtual host with LemonLDAP::NG as reverse proxy:

<VirtualHost *:80>
        ServerName application.example.com
 
        PerlHeaderParserHandler Lemonldap::NG::Handler
 
        # Reverse-Proxy
        ProxyPass / http://private-name/
        # Change "Location" header in redirections
        ProxyPassReverse / http://private-name/
        # Change domain cookies
        ProxyPassReverseCookieDomain private-name application.example.com
 
        ErrorLog /var/log/apache2/proxysite_error.log
        CustomLog /var/log/apache2/proxysite_access.log combined
</VirtualHost>

Same with remote server configured with the same host name:

<VirtualHost *:80>
        ServerName application.example.com
 
        PerlHeaderParserHandler Lemonldap::NG::Handler
 
        # Reverse-Proxy
        ProxyPass / http://APPLICATION_IP/
 
        ProxyPreserveHost on
 
        ErrorLog /var/log/apache2/proxysite_error.log
        CustomLog /var/log/apache2/proxysite_access.log combined
</VirtualHost>
The ProxyPreserveHost directive will forward the Host header to the protected application.
To learn more about using Apache as reverse-proxy, see Apache documentation.
Some applications need the REMOTE_USER environment variable to get the connected user, which is not set in reverse-proxy mode. In this case, see how convert header into environment variable.

Add a floating menu

A little floating menu can be added to application with this simple Apache configuration:

PerlModule Lemonldap::NG::Handler::Menu
PerlOutputFilterHandler Lemonldap::NG::Handler::Menu->run

Pages where this menu is displayed can be restricted, for example:

<Location /var/www/html/index.php>
PerlOutputFilterHandler Lemonldap::NG::Handler::Menu->run
</Location>
You need to disable mod_deflate to use the floating menu

Nginx configuration

To protect a virtual host in Nginx, the LemonLDAP::NG FastCGI server must be launched (see LemonLDAP::NG FastCGI server).

Then you can take any virtual host and modify it:

  • Declare the /lmauth endpoint
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
 
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
 
    # Keep original hostname
    fastcgi_param HOST $http_host;
 
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  }
  • Protect the application (/ or /path/to/protect):
  location /path/to/protect {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    # Uncomment this if CDA is used
    #auth_request_set $cookie_value $upstream_http_set_cookie;
    #add_header Set-Cookie $cookie_value;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
 
    ...
  }
  • Use LUA or set manually the headers:
  location /path/to/protect {
 
    ...
 
    # IF LUA IS SUPPORTED
    #include /etc/lemonldap-ng/nginx-lua-headers.conf;
 
    # ELSE
    # Set manually your headers
    #auth_request_set $authuser $upstream_http_auth_user;
    #proxy_set_header Auth-User $authuser;
    # OR
    #fastcgi_param HTTP_AUTH_USER $authuser;
 
    # Then (if LUA not supported), change cookie header to hide LLNG cookie
    #auth_request_set $lmcookie $upstream_http_cookie;
    #proxy_set_header Cookie: $lmcookie;
    # OR in the corresponding block
    #fastcgi_param HTTP_COOKIE $lmcookie;
 
    # Set REMOTE_USER (for FastCGI apps only)
    #fastcgi_param REMOTE_USER $lmremote_user;
  }

Hosted application

Example of a protected virtual host for a local application:

# Log format
include /path/to/lemonldap-ng/nginx-lmlog.conf;
server {
  listen 80;
  server_name myserver;
  root /var/www/html;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass /path/to/llng-fastcgi-server.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location ~ \.php$ {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    try_files $uri $uri/ =404;
    include fastcgi_params;
    try_files $fastcgi_script_name =404;
    fastcgi_pass /path/to/php-fpm/socket;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_intercept_errors on;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_hide_header X-Powered-By;
 
    ##################################
    # PASSING HEADERS TO APPLICATION #
    ##################################
    # IF LUA IS SUPPORTED
    #include /path/to/nginx-lua-headers.conf
 
    # ELSE
    # Set manually your headers
    #auth_request_set $authuser $upstream_http_auth_user;
    #fastcgi_param HTTP_AUTH_USER $authuser;
  }
  location / {
    try_files $uri $uri/ =404;
  }
}

Reverse proxy

Example of a protected reverse-proxy:

# Log format
include /path/to/lemonldap-ng/nginx-lmlog.conf;
server {
  listen 80;
  server_name myserver;
  root /var/www/html;
  # Internal authentication request
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass /path/to/llng-fastcgi-server.sock;
    # Drop post datas
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";
    # Keep original hostname
    fastcgi_param HOST $http_host;
    # Keep original request (LLNG server will received /llauth)
    fastcgi_param X_ORIGINAL_URI  $request_uri;
  } 
 
  # Client requests
  location / {
    auth_request /lmauth;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
 
    proxy_pass http://remote.server/;
    include /etc/nginx/proxy_params;
 
    ##################################
    # PASSING HEADERS TO APPLICATION #
    ##################################
    # IF LUA IS SUPPORTED
    #include /path/to/nginx-lua-headers.conf
 
    # ELSE
    # Set manually your headers
    #auth_request_set $authuser $upstream_http_auth_user;
    #proxy_set_header HTTP_AUTH_USER $authuser;
  }
}

LemonLDAP::NG configuration

An apache virtual host protected by LemonLDAP::NG Handler must be registered in LemonLDAP::NG configuration.

To do this, use the Manager, and go in Virtual Hosts branch. You can add, delete or modify a virtual host here.

A virtual host contains:

  • Access rules: check user's right on URL patterns
  • HTTP headers: forge information sent to protected applications
  • POST data: use form replay
  • Options: redirection port and protocol

Access rules and HTTP headers

See Writing rules and headers to learn how to configure access control and HTTP headers sent to application by LL::NG.

POST data

See Form replay to learn how to configure form replay to POST data on protected applications.

Options

Some options are available:

  • Port
  • HTTPS
  • Maintenance mode

These options are used to build redirection URL (when user is not logged, or for CDA requests). By default, default values are used. These options are only here to override default values.

customfunctions.html000066400000000000000000000202071325274564300344360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:customfunctions

Table of Contents

  • Write custom functions library
  • Import custom functions in LemonLDAP::NG
    • Declare module in handler server
      • Apache
      • FastCGI server (Nginx)
    • Declare custom functions
  • Use it

Custom functions

Custom functions allow one to extend LL::NG, they can be used in headers, rules or form replay data.

Write custom functions library

Create your Perl module with custom functions. You can name your module as you want, for example SSOExtensions.pm:

vi /root/SSOExtensions.pm
package SSOExtensions;
 
sub function1 {
  my $url = shift;
  my $param = shift;
 
  # Your nice code here
 
  return $param;
}
 
1;
First parameter passed to the custom function is the requested URL, that is
  • portal full URL if custom function is run by portal (e.g. https://auth.example.com/)
  • absolute URL if it is run by handler (e.g. /admin/index.php?param=foo).

Import custom functions in LemonLDAP::NG

Declare module in handler server

Apache

Your module has to be loaded by Apache (for example after Handler load):

# Perl environment
PerlRequire Lemonldap::NG::Handler
PerlRequire /root/SSOExtensions.pm
PerlOptions +GlobalRequest

FastCGI server (Nginx)

You've just to incicate to LLNG FastCGI server the file to read using either -f option or CUSTOM_FUNCTIONS_FILE environment variable. Using packages, you just have to modify your /etc/default/llng-fastcgi-server (or /etc/default/lemonldap-ng-fastcgi-server) file:

# Number of process (default: 7)
#NPROC = 7
 
# Unix socket to listen to
SOCKET=/var/run/llng-fastcgi-server/llng-fastcgi.sock
 
# Pid file
PID=/var/run/llng-fastcgi-server/llng-fastcgi-server.pid
 
# User and GROUP
USER=www-data
GROUP=www-data
 
# Custom functions file
CUSTOM_FUNCTIONS_FILE=/root/SSOExtensions.pm

Declare custom functions

Go in Manager, General Parameters » Advanced Parameters » Custom functions and set:

SSOExtensions::function1
If your function is not compliant with Safe jail, you will need to disable the jail.

Use it

You can now use your function in a macro, an header or an access rule, for example:

Custom-Header => function1($uid)
customhandlers.html000066400000000000000000000070031325274564300342250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:customhandlers

Custom handlers

LLNG provides Perl libraries that can be easily used by inheritance. To launch them:

  • with Apache: replace simply Lemonldap::NG::Handler by your own package in Apache configuration file
  • with Nginx: since 1.9.7, you can declare them as follow.

Use custom handler with Nginx

Three actions are needed:

  • declare them in the manager “General Parameters » Advanced Parameters » Custom handlers (Nginx)”. Key is the name that will be used below and value is the name of the custom package,
  • in your Nginx configuration file, add LLTYPE=<name>; in the location = /lmauth {…} paragraph
  • restart FastCGI server(s) (reload is not enough here)
docker.html000066400000000000000000000104021325274564300324360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:docker

LemonLDAP::NG in Docker

Presentation

Docker allows do run application into containers.

You can find a Docker image for LemonLDAP::NG in this repository: https://hub.docker.com/r/coudot/lemonldap-ng/

See also this github project: https://github.com/LemonLDAPNG/lemonldap-ng-docker

Usage

Prerequisites:

  • Add auth.example.com/manager.example.com/test1.example.com/test2.example.com to /etc/hosts on the host
echo "127.0.0.1 auth.example.com manager.example.com test1.example.com test2.example.com" | sudo tee -a /etc/hosts
  • Map the container port 80 to host port 80 (option -p)
  • Add reload.example.com to /etc/hosts in the container (option –add-host)
docker run -d --add-host reload.example.com:127.0.0.1 -p 80:80 coudot/lemonldap-ng

Then connect to http://auth.example.com with your browser and log in with dwho/dwho.

documentation/000077500000000000000000000000001325274564300331555ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/currentcaptcha.0d9721f0f9f539c4baa1b2bf3f8617bc.png000066400000000000000000000114321325274564300420040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR#APLTE̙fff333㼼뾾MMM'''GGGRRRTTTXXX)))555\\\WWW111VVV777DDD@@@QQQ!!!BBBHHH𫫫:::FFF***LLLAAAOOO888SSSIII&&&,,,(((<<<"""%%%222KKKYYYEEE666JJJPPP...999CCC===///rrrNNN---???>>>444###UUUmmm___+++[[[qqq|||ppp 000zzz iiiwwwjjjddd;;;]]]$$$^^^bbblllZZZuuuaaacccnnnkkk ```{{{}}} gggssseeeyyyhhhtttqF1 pHYs+IDATXwyv@ qB4 \H@VH%9ڲTɶ|]Ҭ8M59u˶[^vukw;uv9ox>x׻^P0UP\щ`=ԑ'L`b(_ 9@k=MCjOG&]i id)09]{%tAGQ۶ogQ*-*@.S!L Aꅨ.ɞ(45a,QQ,,twӑLVֹ(|O$28tu qvV`ueVQtES>E&4IE:,!VK:KBdEr6e?`oc-=zo,eQҜ uE7KI29jEv*0Q +e1{bV4mp2Y3ux hɨ6#իDIAUJ$SEOu=9r1نXJn,ȇ2C*p51*K4"Z|=OhL-](N%zu"7(gJQQUȌ2 <0'RJRPf14cTώXsޖ&CV /j PS<v8Lz0/V:jc31?^qh vxI2 3[0f.ŊEԇwUAHR-qX1p_ä-]m^,A |Սw <()CgH5VQaY7(hkňV8)8|,A86 lq7$Ayt6,ᮞV3Go?o/ɲ?rw0>?m̗or5T<:hFPy_ _4LPE!:Wox|Q|f5VjK}T"{HM͌cuY*d$&}xoRobGS>: Y<;k Ab55 ] Y;%s{WU?y^ڵYU{íGvg &1щAdSF4ݮxg?=|4x/ zI_xnڠI}HuAFp6mPrXb0DWE4$z5 ZZuƕ909^P5#W412wetǤ) tk_b>a|U!=G?:_8Owl&GDR) ^ `u9=|:C6)+GW+>uoM NgK`ͥ}muٱ=MVcTߥ'lN=ͻS|"wmó>5Fk-N`s|Y KzSuOIvI|nRK 7NR˰Emga"U^\I~XmDesi] CC}}^,M>-B2foubZLwXBV}~ZȺJ:w7Js8yUz !+YÚzEIk`X];ܾ:Y"9طR~ζ)路ΖKX=;jR[&)kC@J Cu+R3vo[ysOxWEDX^&}3LV7{yЋөl9ٹEĿ[]oBZ*8qe~pp>OlCT5TKs $s6m8q,LHg,ۺ3>89COu'zz8S.-}H{[gPC)U;,RZDWehپ럳(0r`3:8Dk=IA ֩a!.H{GyQr45fsH6y/XR2LV!NRy{LLzT{ạ_zS[Tv^"UmK|{oK1>F Ā)DN7")%0<Ԓ[9I&w'KM}d \g_!2薧_) (o=s}3e9e'CV"coW,H@={b庡}[ R:@֜.l8Ӳ{9jn t 1( m9%ĠFα v_voFàO85>_ޭs HRjaǛO|vr,'>x{V`~50kb]| 3;ԑ YU p0,/Lk;2wLt)@t+̳d~S'׋fQ" oL[T83|숭KLz&G o]{D2RN8=~c C(<. r(7N'5+"z(hCeɔV 0PhMf%1Sq fTz͎ .3dS^CL12pTRV2$g)|\@'\]Zx?6XT#)F45=x"M!pۉ9>ȉa-*ME8HcGI\\6TPJ?}1ML)dka!љ$Ow?1Sƶ7|;(0ktwjT%j8C.gj:QY<em'bD[l0h8O]DGW7&/?R ư$&`%=y!kw=MX>LēScBܠO~3vN"{ҵJaڪz$]!tɂfwɼ7Vuhhc Ҕ{t!-h4AwkT?/tޏ7oGܷb/^k_9[LWwwbﰶê=Z-yEN\AِwXTȒf]y5HTՂ6_6.:LXp,r\nO51Ndf{!EXEprd">f5u%C54@&Zin?P ~ϛ V<;ڝ2E$t=z#@VuB, NHٿ T|{|%Ѯ`:qW i#<ya4dx2{OIX =%[~8+$mEG6Qo+|drT }VAeAjYk#8Nrye#hc!ZlVEuq0MuFTU cc3:c e96zq04P cVSQP։޹>ѸW`4͌  ;߹п7p4̄&"c!y OG $n4 6~pK?ėΏwD蚶 AQh5q8LHډ2ʍ]saq?섶IENDB`captcha.0fea6a13c52b4d4725368f24b045ca84.png000077700000000000000000000000001325274564300503722captcha.0d9721f0f9f539c4baa1b2bf3f8617bc.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationcaptcha.png000077700000000000000000000000001325274564300441072captcha.0d9721f0f9f539c4baa1b2bf3f8617bc.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationcaptcha.png_documentation_1.9_captcha.html000066400000000000000000000116021325274564300432340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:captcha.png [LemonLDAP::NG] />

documentation:captcha.png

captcha.png

captcha.png

Date:
2016/07/19 12:15
Filename:
captcha.png
Format:
PNG
Size:
5KB
Width:
150
Height:
35


Back to documentation:1.9:captcha

configuration-ldap.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000733331325274564300437440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR^/VsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxwTN/w]ޤ XA c[K4hDQI%&(((EA, lߩqge`zkwgnyܽ<99Ĥڻ&&&&𚘘1𚘘1𚘘1𚘘1𚘘1𚘘1nICDRz-U)Oy@X)RT@ADpĽ+Ўؽms'´xMґR_My.po u{E$ " Ed|K*"N,o)&&&mx_u"rP w8$1] &4"8c8H)]L| 0RJmW@wF#X2!f`Pi&1aRje08!^G`c|#{C"ۍQJUC?cZ)&,"WJ`P R7U@ "rRjać;,y;P 1y8[N ᑟ\D D71FOdԛ@OC`Y0"m6Ȱtӛk<$"5xυaQ""9iJ~<}!Rꮈ%o!' 쪔Ж@RVD:K1|%NW+."PLxGDr)X̎^SJ|=xO)uzd߽1D020$]E$NDP` >FW"ȉ 0S)Ucrm9C >1ޏRgB1&O&+JE h;v,~86-~yf֭[dž Xn+WTWFuN)56r|bI~#*"Dr랣id]׻:C8ex?|r-[Ʒ~o۶MQJݢ9Rwﮪ2K^iVZ)Z)U.oD^zƍƌCAAAw;{-&'r'.';.]PWWƍY~}KmذAxF]׿b{t՛k!"CD\+??_?餓cңG6i& 2zhoSJ&'OO8+1cc2pC)֭[YrelTb N4=SxM Dč1\)u;L&LQGMH "TWWsSSSsR6i>L2.3u]ϱQc2rH,KRΥ:֭cҥ̘1C-Z(uSJݞ3$eMu=#++K?~!2y\ k0᩵Մ'iB! ['01l[ݎbE.evrrrp:A|>_=] "2ND^QJۄ'0m6[#QE#LL"}"iM~ FVVP(,oK9꩔ʸ8q0bp4kBt?IbL;[l6~nJ)ꨫ#$77J0 q miZXpĵ--60|p,KN`:¦brdbZ&& [,eX$77%K q8nnnPjck~?ǃx"(v p(DŽVDb,[v-]0=pE/01Z bp5ֲuV0v}Bي 4Mt,bAuuuDD sB`J|>_L-KX,plDP(%6BR"*-:$}l6zvv{l!CSXujkk^"oZ6Yv-V5agnJl6[lT4-&JpD iyiJk(`݄&&&lژX ##&Jneeel4kBX,B`0ij01I% &VWbp:JNVTS  qdΝ;X,䐟Ν;``b{tF)R ]fel[p@[ؽck6= )wIE#LL"8v{|ͳz1ENESaFq b(MӚ{ӇB! q8Sq"DZh^Z4$f GcDa4:CcTnݚb@tB6-:ISxM҅ʵh= ZSxE_VIy4$Xb꽍h۩#///]M-QJuZ ,Yh>cш$M5IPoEi֨rm˖-}"/^G@WȽ DM^tx+* QWWbp8m0'Sx̙fg߀U+JTLCDD,MmrQ^^o覵S)&1弇jK][[KUUU̟vNK/qmE%@XD9ɭ"2F)>i'M/UՈ m8:Ia kbRh춈xbh`*|>_,5d|~_,'} @4 }D* zIkP?>3>no=Ԗ+) 3ML]i4)TtcǎtN0W?1b3f`֭ψ\s-Eea{x"2)) Jc4Mt\ݓNoWDDdMxEG\&51WC8&z)(( ?? G0 D۶dK/ C!S.g<>n=zS"y`P(V-vvF2Mnw&-t5,~dxVT(;wRZZJyy9PINN:t ''8عsgG\XX@nnّxaetC=AqRP?(v!#y'CxsDVQ'|n+nr{ (8<46I/bd(XXAAyyyddd`ۛ'Źvs54ܧHFI~x;o~cLJo%0&D$7r<ՑφD>|&0Diu`CK0j /3|֯_s/D};wd۶mܹχjEӴfx-ZĊ+c(/2{l:uߧ?x/cӧQrp ?}) jVjw3'{=<ۮD~aH{唖vި$~D _#"R<Y&R~`;5ʸ E3p#0'On_$!33aÆb ,YvؑN:QXXjkjh!}gͪ 0-YYʶr 0{ux2㮹#v~s-]zQJpZHD@mܙB -y$emIv3"R5 Թo#9eF"?J)'a粫;(E~ڔ,o%$oEEGy$cǎeŊ̘1 JKK)--eɒ%hFQQz_~@1Co˹?G"3'͛~&3+N]ؼnÎ;D4s[,l߱-[K9]}uEMIͣbNg,)z]]@,nVرqRChW]? nnɑPGIO0p3<`0DAI`?)5."yk F  p56umrZQ4p~ѻwo-Z[ پ};[lnDŽw70\`0ȱO?Ν;yH\sɅՇrXͺ/>x}j=+wEdN2;;K""P\\LII EEEl6`,W4v{ do0_YCm+*J\ 62_)o7HF8ÕpR.Qq~ "R#~11? X5I*{ ܹň#McZ>|HS&LbYm ?})κ’fÓ[}_e9Ϝ}U)f(PJzJYj| [noM(,,:tyZZ51 =7oYC UU8,%'K_pDd&e} RcyX*"oGc!3Ĉ[("E?ejD]2VUU.[,&n=ppWqƹSbofqШw?P[͚ٸK*vPгO EmՋWP@YV"rRuFcСCYx1f͊_ۼy37of…3iR|v#XJr-tt %˘!͟clǀőoa ڸk#1v0 ބ"HdT .(D.iRIZʞB bywOްȘeˏQwp}. ߷4 ҧS18nNa Jt% Rs RJݜܫM="t***`С <@ @("sH?ᾭ!a.ź%R=n7'PulU(G[ "S"qV`!_Խ:!"zOjR~sp8t!J~k4b۩E\Ni4.;eO}`֋H|'ϋLSm _]ɷLs1ch8mz޶,~ip4N"V+%%% vn~UǎgϞ]u!ݶmk׮;p2lXw2QҌu k%̝#nbK}RxMҋ~ZYY9NN:o߾̛7ɓ'ӻwVkѢEዜsoLYV:uu B*nú{xv/ҵ[ɝMwFYxr녝yr 7$V|>DWJv_ޣGrrrlɓ'SPPm%R9)=e˖q뭷ҷo_nXbҒy._Ԭ00on_,VCF+4#n%xdL5I)={lyWVKp8{dddpclthz 7gj*.?L?,kV^|_ ӧ#VMFfo/N;0xzظ>O™7_sEVҿgvq1c^z O>̛7)SCѧOKz@#||ܙaР=Ͼ:}@x#O]`Rvl^< .բϰaرc{/_},r59fW^ᤓNb\ut0CξnÎĨX̢Y\}uޱV/[=α_=Ϊ$[%$ݻ_} ;zr[fcԨQx^z{9Ԑ7 ?8J),fM}a,I9JD4xnOС "{(&Ri7"&"Da.]T%{,=n;p4Z!vnݺƚ嚝ytD8?yRYQəHmex-XTd{VˮHMO4բϡJϞ=پ};ZQV!emgj6mJ~l iez0l0Ǝѣvcƍ_>_bF^JgNNNJr_Da22_3_~%=E8].,/>גQP\oG{.ڒ3>~HnUJלL!'W'}QxGbI뫪z*pnBaŠrX|E.+wհز*vHZƳE\aK7N8aw 'VD)%^˗|˖-;j| } >pd7_/c?t& (䚓3;;;%b!##}:ovZ&bX:t(Ŭ]fg픔ЫWkA)e{x8Sذa7\q }G+'^\.'?z <䶮uF">4 |ԣFҥKYvm[7i 8^z\ UUUTVV""8νNkX,^Q 3ys-KWSmK|-_'- m7Jʅ7li:ꨣOIj8N [oe&i)..& i&n݊狽&m6NӉ㉥l1Gzy饗=z4pfSbޜ9|:!P5o=+n[ܱ@IV78X,9Z)l6իWjYYYcգ;n+[Jn\?elYFn od2M&O;4N;4[v[yA#WDDȚs6fѭ[X4A`0i){8dEY)SpXBtoF&g9| #Ͻ@]-[V. 6}ވR7|)^~KйJRrAI$QXWWblXbCǎ),,LԨ4jV5X G;vF0SRaרY -`7  l%I^qbn/΄ 8ꨣ" ++ ˊ%B$IzOO?+@p7۳=̧|LAÙH_> ő=R.>-eOGӴfݣ"TWWt:qIi˙9s"n}~ۋgOl/cUU,~+-7JR1D7u{9ʄ ()i^V,^/p86tmM+K@SO=ŴiZ!"bTNvN0r <]t2Yɽ+Gk73-v˞,^|^{ B! ,]YE5,(^M>~8q"w\ndggV5r zlUU>ӧs]weݩ妛nw JrHGy'>~5 2F"0Wj'0ӧOCӴRˉ+E7 wKI9•('~;ݻwoU'`%77]כ4`b@-FQ^^νNfI}|5+F:/e۶m{ME)͛袋+Wja]6+1V z? ]t-g7j!RPP@^^^앛K^^x<-Zą^WUUR/Jvk]$".g+ϊ>C(ޖ_UU<LVf&}t0o<=`,_~;6l`ʔ)ri}FiaפBQv;^7&ā@@ T<OLp`-lжެ]^^ԩS9up>}zS/n!dz`m籇#NS""?++mNRG6FDD RE Zƌ39|P}w_}oW_ 7СC'>K.}p ?s/]x6/q%N8O!$E _>*SJmo$}Ut[Bee%?3g2rP}+iӟ` 3~>jCaưa(**"##cٔRVbjt:c B@ߏǘ4r3dggc 66M1Jg}O>G{odօm9sU|~@1rĞzt3BE|d_~ xlBQ㩬dڴ5s /ޥ3'_'M_vV&x|?) .Zpᘯ:vX,*''G%'''Zf o"2S3R;}Fp$l6n7l߾`0jv'P(Duu5Նp8bB[bb Lo_=N;49cKTvԣS9\3QߵsTo@0Ï=^xqŔ3~-ُPJ}-"ws9Sx ުyz-,_޽Q]S˔·?`aC_ g z)paYYYaYYYѪURRJ;>HkZckסP& qҭ.))iO!qkԫ"2o̙7; 2$8iҤ쒒)..#r kC,Zg$3ËȾ#swl/mǫwQJ=""?O=T9ܹn۹Np|`a;Lo!{y+/HoS?qQ䕐HBE)xӯ6Hś-O3'ڇHkDOOkjj DDZ G=G_z]}n];,ZR~˺su=H(PfIzJ"̙3ozw/4hP`ҤI9]tcǎ7jǍ/ip"rvPK09A.qޤa5U;kj{*1$5ѓ5"m_R"v>ns~rί+ީ%ɵ93XbiJ-D \|SGM$r9K?k@>󿮸2.9] `$>OkKԋjhu⢢":w+)u 1] RBDzo}wņbuYlH-&G)Uzzۻ|>W]]]P0$3ѾzYyޕO󦜃>jsxݑcvH卮f/\Rt5 "a K>`{G)Uא_Wn,V,VV 1D$ǫiZLܢ7%rCLb tF A eUDUeF' u&1ZMG'ϼ^/oζmرcG1T$sEz >M2TUR:z8D8 #W?`ܯo٭_ERiM{_dtQN1duKHJW#"x<,i0:R\nnk۷3g;.]ʦMp8iJ]hv*fy}C>{Wo󬙙R a?`a]s5ukKՀ-ǔROs/֑˖h `B U=gqV9EZ#n"r?O0lP^^N|aϞ=-;np8̬YxW5kV_t`8Yf7ݎH N<ʾܾf-^ܽTϱ+CZ-P << _2D[)uqx޿+=|UoTlb6gg~ߧ=S}gR;x 9 躎RY\K={7)ŋȰC\(/O\S:P5N>\Zƛ9uO qY69uuU5u7u @vJW܏SR*#"3޿z䫯8vγj/+>sJ=jΊ յ7*oӋ؇h^geȑ,^TlՙX,xN$=z Uxs 躢k )УS0& vjVV _o!L3벵?"f\ _oz|9fсfj)NPJ% !ۧwfpX?ԱG}բ UվREeR㍖w rb]{+']v}pLw_+#MT ]3 `PO(? 'Üa=0Ě#oȗ ^1E@D.DC RjpzOVJ}}Lv8^٫HZXgl۶P(~h`+>b/kۋj'JQN8Gf'%eQ%?*\""R/ luޛD-½?IX`20N`lSZ \YTC<"*++c ƒ>ȬY(~vta X"6 ``f'"ppKkS{!SD+ݬ4I R5"HI<o R6A%fK BXǦ f#''~z}YVBan8Yٸ3rpgF^92qypz3qz2q8\^Npr/"("$&)C)uRkZkU܏PJ} >]Q̕)%)G GŸj*++[S7& 2XQPX';˃ӓӓioFvLtdp{q<؜nlN6ݹy<0ٍ7IWa^+"Gwc&*Uyrڳ];)O hXǃ}ްM$Vbƌ8ahg799\1tcwr96& B@)n#n"X^ qL2wQJ#."2@*^K H![$9{\X]0ؾ};J)G}g̓6;V`!pP_3V GBViź,0j67Lw" ~vnR{qV4(SKBeoҎVJJJ())Y1{lZq302"Y bp{qypgfn[&xi75{Zi0 TDƶwcډ0"?>ilC5ZOiE$6׍ZZJzBX`nt)%IMC Xm.LV;(3iݧ9N2BF67G, imFD}WR/5I)D'לN'Q]]MEEGn۶`0H 4f]71m8" RW]A03B¡vJE_j׋=[yJ бE_IZYpP&CA_lf( IDATBбa"+bu|u_G(#/s54ɾRjcvnR>vEMӆiXt:|(EXWuHy}#EEr4WCyy9vÁE4pL^deBIwB:""*"0z8 F+DCu׳|Z>C܍34Jk6٧PJ='"EdRnSk b9M**鞳~E<~~}{:x@ pO_@v||wՊnQ81Q'h aj 5]6Ě;uZ7#0WC(##٣䧝0ăL .YJۻA%fefֻż1d~zhr?AiVx!hB!B555Hv:8p0ltrJ0:u5pp8++kٸPHl(C_߱rAJs},o%AmK |mwv:(Aߠyn\m+@)ơ> ! $KS8iΔވRIy8EDSJs"8h1oJqQ!J)~?k;3Ӯ}ѥj.^F>S󩪪o߾TUU1Mp8Vv1 M8p 5554G8E"0vJMa^DMc["lIs nܻqS݂Esv]"2K)'MӧoUrgLT׬@)t܄͎ch\*vhxn89vXȗ_~_ŋYx1EEETUUѯ_?pݙ6;f}4ޚࠁe=achMaV6X1h[a_34A_ծM6R Z9x xXDlI)QJ5v zo~~7xZZ>>OYӸҒv7MGz??~OiAlyݚm&Ȋ}UUU ::f͚vQuֱn:+DTVV?x;{qq7J*ey>W~RUwӿ8s۹`yov'iQJ,"^X6L^ ~t3u\;m6f8eO#`Ht!Yx#LEE'OΔ,p8L]]u\lii)ƍ7u} 0ev) 7Gn)xqjvF~ "/+c^σaØ˽qߞxi\7. ~J&J [ _b̷8f"eu6VFb1iiipd< رc3Ylё^zJ.uKߤ\ƛ0l HXV1cR. E]n%\g~> kv]S +gfGDz=]sE@$徿+WsٿHp[s\pUECh֙g'Cv-1 Ӄ2(0b@+oӥm3ZNWxꭾۋj򏷉$J)R}>謁ܗo>/D4?Q\6)',yy99fQ`ݷ pp1nb-tZ6^uUXw {6އi'#ַD핹ֻEJ 4;"rxYQQ.3|OfLujP(>{i|ٜaw@NbJ,P]"o6QmnYf;(cq =i"=G߭_bƫk u8 -b~/Y}5˪sMwmrP(5nOK=lqm Eve%f"aY&2阬xMӤ ӉT6Zo8z$SQ\O_-d; y%<r.)OXR6H}Z }9sWqK\D T ٩׷gXQrY\>3TޙMsKP[7|IIiJޝ=jf͹Zzݕ=b+*mC d4%gnuNv."Q NbV^MSSa2~C-婧*.N1cX{|+ΙOv5h/:#d֬Y <~|Y'pvLў/^ᦻ+bEm^ҧL0D~`E;yʹҿ_eW\{7:at1.. -e]^}#S '7+n|'Mp20 B=. ߏvrls-3iƐ:יWCZp.tv㡢D"A("HH$477rNߐ\ uuu|8ss^^~Ӆ+B(iF?= 䥿?L?'bY7;֭ / ?h4jnj>t,ULnkفI /.\گNwˌD;JwZ.ʊ~^yqQs$˖{ϵ]knUVd Lsˎ/"J)*ޓ<_^ۃegœIbeE$f,X]w݅%/AaA>v?GqAI,_3jR~rb8//OG1jؐV- . UVaiǖ|DZ7ޛo'8^MUUyyyDQ.}s+;g{r:p;]].n7>99u{8]Ncx 0 >I$Rǘ1>o9ˋSNjΩ؈bbXӍN#ΪGg3`(|·-=z]X8TnϑH Rѯ_?*++)++˸찖-e˖W_,*C #]:x\N'ޗ>o3x>(,Vbi{SUQ&@;PGM7~f .PtV JZpVᠱx|g|jW+^{_ge)+F+iN:PV&.LX't1vöZ t2D`0Hssf۾goGu}sHZ& $LO$b#aCAA0hhgdq]*`f3l./jk;r(,,mM ;O, EC444#B(X,#Dx2IRs0j˴~o׬/1p+LD Dd4]Rjv,;/>^ÎnJ-ӎ\qdDwc&:t^߅±I]1WUD6RRJU+VJ}l$JהRǯZ*qYgYּo/-/#H`$B vCAS[K8H &xhȟr|eHRvVDdL3 c;"sDdTd!eY|2e  zm9zN>|/TD嶋BLL") G#bQ( _|IpwQ?>{?\ۂ!l4W?@⫕RYCH>STxa'v /*ERDdܛ,:y?wj_7g)Lpi`8{gyJ4 ǿPJ0h 5j(Yv-TWW[@1eF@)P)1 RlIdExR_ "VJo?5e1bVgW,￟ӧc5~xc„ xfynP(ŋYh-/4{.QJ= <5[Lօ73H!p RJDvM1B)((0,//f^o7LJ|ÆR4.XRԮ]my*0$ '8 .//t:nֲ>avp8yE$ퟭ{FDdo,0N,p#Gʄ =z6U눕+WkꫯZpRH]&mn"G1hp8TAA*--5)))ojiiI(~<y;4 G&[H)8ߔn~? ,b͚3&>?yG̜k^uT'Q| G[m?٭o _|:@_Z;cƍn kn LMVo6HE# ")F>裏#8͌8GuPJԟmNJ0JjHDb/ ffڵk+֭[W)"UJ2+}a+R)>Z+R񄤢 PJO&!e$-h$KLx`.0*G'RZ_\u]{|~ 5\ӝrvU&q 3DXmOW~dS&MbܸqpPXXXK.?Rk.&YV$β/mZZ!Cᥗ\ۄʆtlxӹ2H4>D.c ͞R&+StJ /_)7Ή}3?\]zş|ty1zv{YVoK ^ LT=[q2OGy2i$ڭᠠ<xs zDDIԮ-Qgi!U8cӋkp% 4cg =ݤ88?J|{6_>-Y]Sdx_6zU[).D"%:9?TUUaf;Q5]aSXXiD^Ӫvnb^ԇ"?p>䚦5Moo8s1r|~m oxn_σc&@Z`_T] q\v{J7%7Hl+CDZx%Uūx[qȟj’%LOî?#JFM]8^N]L G:nǸ PC˙3ga٨eYB!B!QPP!L2Br(**BDH&Y(b7S ذ RJO_a'D<(n,n(R=:ŝTUUH$דL&q\ q" H-m>D"6Z PB=) *Kݟ4`[Tea%(e# y;Q9T&3].~=ۊ-((mY!-QRRBiifTW4IDATi:^rIƍJ),+e%QL/qGyK$tnOk[+p8ۍlk5LƁ?|_O[& ):&$aDD,3ϿN0?:EoB4P/"ѩ;+-JOII J)/_N,7Uwg?ٮۋin 8iwGv( ̴0 O>1oGEF}JJR?vZ [%dqqvU;rl?6-999]1H x -Ԝ#6'fJt0O=~+oUjn 0=)nQJ܀x>>P-"ȶ7>p\Ig" D21[CWX)wnB ؼ'q}=5jo[JpH)uRjjsp ,kEdl?c7v\mx0p\4 455ܜ,-E)^j&fCDOw]#94-h Fbۉ{ 0zډ@IȈ;R/00:tz7yeYA(J)\.nϴ),,W)Ř1cוRdm`MO8M(GȰ'yh,`ս.on"G[.logGQ3]«$)>$&JD|lOM%"ob3J~YBDiM/m%]͵*7]tTC}TD)R0xu΃jeYŵa ^'f@)Ղݧa)Ay~])sUZaJ,"gZn^ol[״vZx5;)"Eil~O)ck9i(k׮4MDߏoW0sM[݈v5hvhR˕R)"2CD8" :g I ^)))!LRWWGMMMk2tD2LtZx5; JoR)ð#"MJ?D٫<'"ea*tu-\3 ˀp[0槅«)QJUJ; JD"K ;GjɾU(-m\keŻt-B fFVJ®0X*"SnU>T!mvzqMkHscX6Qk"2:W%x}KIO&-nݺ2׺To7-^MD)SJ=0n`_- к:HgӧO~?NjLԋk݈x5)h+İ-=DdoW[0u֋ki֙kVjs2ҮT vH}ZlZx5K QJUox.hjeŰ, ˲p\r'tUTi̙3wyp8l8J|D4M+ Zp؈bd2v\M^SƺuVJ-IhvRJݗ: ua}15hlΞ=￟/" q'7YDz,@',[lB<=sMMMSRu4^^MF)Щ.n:,}bkE7LcF9C8s;%5.((`}p666Λ7y松.4hol/@ F]o͐vHk1YY\5k^{-n#GnmRRR9C}ف|k#F=zIvۇY^&XvA4msqN&[miike֬Y?}w|>G^9s즦}7vO'hM&Y5k 2aceeedbuDX;ȹ˧~ɓEt[)9sik֬`ԩ:-MvȔ5MH$BSS[X,㡨r(mkXv-SNeҥr)EO0bƌO?}@(=uT«d.q 444d"rrr(..B|>Ry|駜|ݖ)k/~!hڞj4٥],z}RYYIee%>˲FDvpE:񐟟9W?39sHcc# ,@D(++᭨@)b;Yt)?e 0?~}5jEhWA.=.`qׯ_0 ʡ]/u7ǫdND=Çg]va۴h}aۮ9j4YD)D*[\gآ9 ֲb ZZZy""ү_?ooA F}v6`}Z\.>_yyy 2!CdDvAII-D F}eYmL[CEE ]y^~?^S OjvZRb1"H&ԭad{^|>_PgGD FeDIJ,k#u:TVV B! `vg,ǮX6m4]^&< |P\\L<'HH$H&$L2}E F}|> i 7''S o=Zx5ӡ:|>O(ʸZZZ2_3;(==B FeR>B!C,pd%l{,.\,xnb{"RjAN«d)]<O&agp5XœO>݇,}V1p@{||/> 9:eX>VuuuflC $f-',3gr#q׌XǼ3k&{ٕ58y1|=-~q$nb4}E{o'hW2eoo~f̘!yyygVE.td]d,rq܏dz|74 ۾ F#""_رcbAyyF[[[)K|Fp>nv?h  5@c&a׫W"D䧆aoYVA)SǏߦ҈ '|™giBĠA3gJA)œO>}K)_S.MiIQƻ뭙O=??*Wx5.B)eYTWWꪫ8S^xlc=Ʃraķ} c JKKp]\}0baG¼ uj BD"hq4 ceYvm7kĈ9:5 2b]w?8"R$`}gUOcټln^͟朽;=~ݺvq+ z&]L&^H FӍ%"G+t8@())b3 @D8쳭s%J)SD;r %kZ oޘ-3cyN;~}m[ōޣ5?sx Bf9-r(0,򬒒ӧ,YjllL*TJ=x*,,, o[ns統zkVqm!] rX8DPN F9DdPfF_,dgxz뮻|{wV)M3뭏?saRSwQTT21Q0:{^Fvu\O]wuqemowI NO.NpbI0X``-> jhvBpxUW]귿mp>NEA~b80 ˏp0 KΧhzJϣ.zj㪹sZ-&"bۏ8 K%H k4;1Jpޚ{M812~C %o]PjYd Pv«RkۀDd3gώUJPXXs=#{QRRWXX#%I @Ll (KY(²Xf eƱ$ʲ0M[{C/i41!@(vX)U@ik}TUe+.t{a U_X';Ӈa8$h>pK u0=z;@ F"VrSVv s+k&#c]n?K3Ͽ-w<"KJW{Iz-fH p^^έ}J˯zQ^rXX_w8,m饂F F&:u|7GB-M!"Ӂ&ԟ:88K,TJյs@`&_ԭ;.hSJ5oӴ>eFgW#VhhzyhPJ㋀k vRj_ (B~e-0G)uxKrrgB?kNlO)u_"2 xxTD+~&SIco=V/DdR:V5TjӴCLhvPRa. `V+y)s-#4YA Fq>MSJ5~ "u4xD`Xѹ죅Wh@1R +%" ƘzWS>W-"*}A`t$PZMsִB Fq<~셴{ZŽ9pj]t^fADrcyDd_` ;R o4G>^g9]D6ئ:~Mj_ ol_n:b 6@]-F'Ph4=shRj)`-L)h5A@@)M; vG@ C)AChh4nF4«h4݌^Ffj4M7G,ΆIENDB`configuration-ldap.7ab80c7e7fdfb0825e1c703da31ed9b0.png000077700000000000000000000000001325274564300550102configuration-ldap.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationconfiguration-ldap.png000077700000000000000000000000001325274564300502212configuration-ldap.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationconfiguration-ldap.png_documentation_1.9_ldapconfbackend.html000066400000000000000000000120051325274564300471070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:configuration-ldap.png [LemonLDAP::NG] />

documentation:configuration-ldap.png

configuration-ldap.png

configuration-ldap.png

Date:
2016/07/19 12:15
Filename:
configuration-ldap.png
Format:
PNG
Size:
30KB
Width:
350
Height:
250


Back to documentation:1.9:ldapconfbackend

googleapps-menu.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000222231325274564300432510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR2Rũ} }iCCPICC ProfilexiTg[طwQDe' *!B!*J U^E XTBED@D@YdUDF@Dèh{ϜȠl  |鎆R989IF@|z] @|TK%Y~džU!ˎ> (c=cJ"$860@̉ MxE>w@r8TrB,W`b3h~OF,p3jPP=7 *ג" J$RoK$sg$nTaF~c# 8<@ HRd4UF&'/DWVQUSE [XZ/rXq2gW7w+WyyW Z.mظ)>(,Lڒ5};w}={3:Cd|G=N:}&g*8w¢ /\ղ_~v?n44ltN{]t=yxg/&_̾{> HRd,,&'D+khjjizz ML,,?XՍ1_? !ED1>mINI|OX /\.rco44jzn{Gg=}G=sɩsO DBReddirr JteU5ulttQ=ˀ x @VPPꐐ5aaQk׮fc8\.o}l\܆Df0I1K;>uۃ>YlP9).K镲_U^fcSsk[{G׃޾ѱg⩗s$@r8 `Zs38〤a@Ta؀P AtWA~FZW8Kŵqx;< L  [ńDb,8J2%IWHoKeK ɇɏ).c) T*uQT怌Xvl5͘M{'/-+_PhxIVAoUVdVŨԋ45f5Ӳju~堲h^~˂ YJND[&91fyEeUnu.\Isٙ<˽yݞ+^M O 8X &v_s6?\/"!,jv)Ǜ{n=1v]\FM d~j|(atJFyk6;_}n=;fu:xhȉi?4۟qdVJ~*uR嫥_lzxjk{!ѲIHKܻ;q:=v+H[.x(,h}dtٌ *!,_.8 50PE9 83w׃¯ m4Q( ֑4I"RT 2\NA),Ki='uJVPH MvZ@|[BbN2PܫzU-Tibc$_.KWͮC=QN'd2~:{gj)8x|sa`Q _J0mzJ Q{#zc孬ے;^m;OvHJC#3FuGߌU'O8<WLnZ7>C~: 'SyO$bҘ3c˜/LÙwrpezv<N{Ť<xzt<ϐ g*fw+^b/ g3ZgΝx{cgDPg&' Qj2\!\DD >@P` ! Pw@_=]"^(Hn 2[D<9ϱ4Gm3.I Zґy_~i{eIDATx{|SOrNnm&-rX (NЁCcn(s=L78(c (-ҤMr.'i& mm?_]w7$B!$4;BHJKBB%B!!!BiBHD!$PZ"B(-B !o/~[:ut/(-ZuG[m E+Qۛ ݅!OZ#Gh% `{pۣy۱-/ E+Qۛ@iмXt/ E+Q@cQZ BJFi=мX~/&qP<]oj{w2oY'Nᖓ|k!;u6U[sϱc ;cZ[{~;SaF>/[Baay5v~aοqom.roSUWϘV{M;mD 4{YFwROx^Vij6o3b~n}p{mgH̦ɺS_N?j]dA,&16},9utm!X_e +x^|xlD-^WY҄bvBDaKE7QjnksY:)k7y?o&Z7RbJzΦo@;- ZmPB[dAU>6k'n ]ԉ]]M-'-u˩v1I#:VJ]!5Js~z8zz.)\\CVq+ _yS0i4F҅/W]*ÕM2 ۤFijVʮ䄅g!5DQ ~-w~E>x٤{#'*TX=VWq wmHOSu#G-~J|cOоG xa0;#'*L]ڧRX_(vJ50>Y?sTiSGj{t+%#c S!B!\aD%#SA)p^#2Mg} K~LR$NxQRW1>|PkG 8Fΰr悍 A=&eC>\IRqkYQ)ۺ A?(Gtf kȘSrj]-˨!O`Ndטxy=#G=Zl_Ժf<}Ur{E_MsؗFhH2+1BN w䗲7:GP`2 &G$*c]kf WEzAW;.ݓwO*t[SA-?iI`)%gWac50Y]S;Z~͆D95'(Zi}N@kOTb֑[6qQr&VDON>/sOPxrDZXN]a^=^)H5Wg ;l(恪7Y|J毡c.O Θq2#\Ymo1wK _9mDZZ.ߎd/t^uCZT !rz+p݌ ;biG mG`'hԉ2z/w'(a˺⅛ ?*"a Wz:dpk}IJ=#M{~dU0V'œ&WEhktj`&soD;/o4 =C>-X#ݹ|kM1##+ee:|?5".,ZÅA:ˬ&j968,{72A㷾, wj@ oQ=G2^TDrأzQ+bUe5MόyM~ZtTMa΃EN$=8;Q<9Z y~ uFE~>(!F 4F" .ڇ Q(b-Ϝ\{Ҧ3^L_o. *{/UE/Y:-B,mx2j*\!L]Z"D B0#)PzռBuk&hwZС;U,.!X dt>ѯMBdÌ p8(rV3}GjN)+ 6HѐޛCIzs-P}습HwU?]y=OUtuG%~`U(mtbgTV1M*UM4iAQeGWdX]+ؽJ}ޢPZD(Zܲ!ԯMĝ6͍ZC(\ӹr{ǝMJՊv}Y뿹ڂ]vVup!3BoW$UzlQ>")p5+o3@k~aw2oYy0!;we}U}WkIK}1[{Fl$RE{-~@k?*l:獉؁Z1][|( E+QTZm&*:Y6j;M[ BJƸo=мӵZCoV7JK큂ڱ-PѯӶ~6 E+Qۛ@!nԎN +mB!i !BiBHD!$PZ"B(-B !!JKBB%B!!!҆iɴ1&F}O]ذ F<:+&p?+iֶay^7䴶*Bk$Sz׏O4jڪvs~rY+͗[TX\,I!?mN|Ae/<_bml}?@R 7!PQZ.nYvێ6^´gł c._9sk\8wY٫3Νpgv+=fLiŘ-\3[4sc ew)Å˷Vz®wf羮*ʔn)|?Y>LؽYs_l?z1:&F<<|:_g۳l{.[c~B:F|xM~{?7j9`䊼cpnٶ0j#W>9;Zܓ\U>=ec5ymGO'gť2?]0ಖO(S7a4F/<2SsoӚs] n޲=e Ƿ~rat`"M[nWɫ !|ْe{M~Oeg<7>pұߴUڼk]ywpው1s@{~e敘*?׾2-̮IWe)'M^~(hR/4gSI3W~WZ~C,z9p?&ow3;Xw(sm:`-z[lYe7-KgӋ7s !|EZ*LH_2%R:ύboϮj`70Pݍ0{ԽiqeDU۶aǞhOSҺF`IU,`//zo\=s~L|ҊCƛor= ؑs^A'@3F dKro{w侣\GJi+:gzջ^mJz؇rF5f7_} I6!|_šߗGYP~t W SqTq7ڵw~Xc4g`/ư`9sI޷t+:'(Qz8쁉3s\1t~z5p($ νY w{a?W ū?DhRg ێ0vNtsg4aF:l>qN.,4xR)'\v7EG?}}p$سֽm ڙ]7oߔyhr/]CwI3 C"_m+fnsܿP@hmZ2gX Dzwޝw'+iqT T^ {].{dD@@#~rpo]g[Z+mTAΕekgO^}eT=ӽ6i/KU\q{lG^(7kОFuDWk;bJ.j4\xm{ʴd cSSNK<(5eg/ʷ{͑tLa?;ڴ6=NQR? IӪP{n{L~{䮛J@w L̘+ctLW{C݂~6p@_=Lt2wQu=_k_JB;Ы+ӣFe/Bk]Z'Mkt] ٖqּe=6 $j6Rx@ɋw)n'`Zj5AfVYMf;Ve V3hO~rvjWkqvFܤU^Mv,3^B˷!-!JKBB%B!!!BiBHD!$PZ"B(-B !!JKBBo}RIC!cN documentation:googleapps-menu.png [LemonLDAP::NG] />

documentation:googleapps-menu.png

googleapps-menu.png

googleapps-menu.png

Date:
2016/07/19 12:14
Filename:
googleapps-menu.png
Format:
PNG
Size:
9KB
Width:
562
Height:
82


Back to documentation:1.9:applications:googleapps

googleapps-sso.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000325501325274564300431150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRUBh }iCCPICC ProfilexiTg[طwQDe' *!B!*J U^E XTBED@D@YdUDF@Dèh{ϜȠl  |鎆R989IF@|z] @|TK%Y~džU!ˎ> (c=cJ"$860@̉ MxE>w@r8TrB,W`b3h~OF,p3jPP=7 *ג" J$RoK$sg$nTaF~c# 8<@ HRd4UF&'/DWVQUSE [XZ/rXq2gW7w+WyyW Z.mظ)>(,Lڒ5};w}={3:Cd|G=N:}&g*8w¢ /\ղ_~v?n44ltN{]t=yxg/&_̾{> HRd,,&'D+khjjizz ML,,?XՍ1_? !ED1>mINI|OX /\.rco44jzn{Gg=}G=sɩsO DBReddirr JteU5ulttQ=ˀ x @VPPꐐ5aaQk׮fc8\.o}l\܆Df0I1K;>uۃ>YlP9).K镲_U^fcSsk[{G׃޾ѱg⩗s$@r8 `Zs38〤a@Ta؀P AtWA~FZW8Kŵqx;< L  [ńDb,8J2%IWHoKeK ɇɏ).c) T*uQT怌Xvl5͘M{'/-+_PhxIVAoUVdVŨԋ45f5Ӳju~堲h^~˂ YJND[&91fyEeUnu.\Isٙ<˽yݞ+^M O 8X &v_s6?\/"!,jv)Ǜ{n=1v]\FM d~j|(atJFyk6;_}n=;fu:xhȉi?4۟qdVJ~*uR嫥_lzxjk{!ѲIHKܻ;q:=v+H[.x(,h}dtٌ *!,_.8 50PE9 83w׃¯ m4Q( ֑4I"RT 2\NA),Ki='uJVPH MvZ@|[BbN2PܫzU-Tibc$_.KWͮC=QN'd2~:{gj)8x|sa`Q _J0mzJ Q{#zc孬ے;^m;OvHJC#3FuGߌU'O8<WLnZ7>C~: 'SyO$bҘ3c˜/LÙwrpezv<N{Ť<xzt<ϐ g*fw+^b/ g3ZgΝx{cgDPg&' Qj2\!\DD >@P` ! Pw@_=]"^(Hn 2[D<9ϱ4Gm3.I Zґy_~i{e IDATxxי"6I] GB nD\*m+ v[b"[V$Xl6+MmlNvMvctk r8 C$r#<33ٲy]8\:Z4ü}t%zO|/ȉʿs(. U|艅>*WO_RɳJn-[Wzs rekՏv|67:wGb|ͱ঒| &ˇ9o'߸̟}VIw7=]ۿOP|7^Oa`:FhH>K-sS̳r/Óէ-+mz8Wmvp8BPt"̙{zOy/4#v̉ϋbPi ` q{`0,^UnMLу{ `wcgUEu[ŐrhS{=j-z'"̹/|~,~}>n^S|@kkwS.zwω]]uϾ_Q7TmtGy>.<UG7t6 @("*^AZ1\kocktyzˉkܻ1:Xn/:,Vj }wt1V>?\f~i>=ĝC=m*zϚy/79DV<LE @M˹%4g^"=;|g[ C|-{{ww;k\O-X{x213'@9g'C68:i~i罊Vm{"v&5]kӪСb)B=Ejy*5*<0X u)y1=vQ6o \.;R]Fpk6Yf՞SfwYy[B9b!] U\Xt w}a @d<}1(<ןRtqh8y@Eֹ+֔9?W̝WEІe."Nݎmf{Ο[?"ɵ<>I҈00oW38\࿎$d!F BhZ(˿Rg߾;ww)=mi}:{`/?|a˖S'יhSU5X|lf`Z[{TJTt=Knӆ]6`cQEC˷jߙgX0k kxܩSNigoM1 /?Ak ɕړCYKMv,otyLOxw,0+jNyk;^&LN#mRRҪE~ oSڃ?0Pt-y׍@}16ƻvN] Tm XK B\ugCOxJnYkH]B0a[`m+>}'xoZxgVxe&F jH;UԞX3 V=Qn55]0Si aWmc _y1kҎO'ŏy88R=_~^Q `r}~E)"gz8@WooTh=3l9_W?Z 8e nКS B# 0 cye- 6y1]#=65y^W! *6ȟe4 E2Ἐ9`)؎xT 9^iÚGzl)&A膧Qʳr$ 𧢐vp)  Ic#$p˽ɟ,nmCr@ RBc@'7O7]6H.]RMMÉƀ奅zm,l*"fӌ E$t>ܛ{} e1#bqxU/(c=ڏE#&)cH&Q=|"XrDF@+ M %fذjWܢ=Ma*vLujt2,ޥbacaqmA"{* ;78 Gii0WO/'[[\j6 ۛm SUb~㓘b0jjb6lA<'F:v~T1/v ;#=#Hr"3]f01si.t/ }̚꺶 fT[*&ΐ{ر)YnZ֓\R `JqDS!Ȧgsd7һ _iv?{`XO'g=ѲH1yTU4^ێ-:HsBI?'k*33"=$*+=Cy˴ןKј܍nИ=mkls 񥂰$I,YNA$s =m?sn`Y8Ò$IaGw $Iopď[xI$e"(] 6OP̴cYX{cg2~cn5c XƀغEIm`5|%YS{ne˿q,NDy^6w<Ȋ!>}IIyXQ$bqI$t7:^!Ĩ%F$YI;Xk8hBn/e JyC:je1dFWuz$ ZB#.tڙ']k״d>rt4Xm[S7A]9$W`a~7$N:q)赃d`oEzZ>'n.}~][Pw:[c h݂$ XxI 8yI 8k[Hn'kcw!>5_>G݀t{,xv^`x' zU& Âd1`xI#ϹgHk5|I$I{78zf!: , $IF󉜼,-^0I;)d1dǼv B,wVX$qnN|+/IR`|o?fxaI$d%_4^Z s0g\Z3!]%d .'̷ 4$$[̅F64]0`a j>;kZƩ:;S&T] J-nݗ$/t9̜(]}]sⅥPA0/7|Dtfܒ&Lؔs(XyۓTo(K4C&`^~r ٸYһI@rzf타{cFo@妣ѵ{"mيrPqL&njnWˋ!m5P XT}W}ϴ8Og'àm[bO1^'7zW[qS]@բij(L_=TnNGZ xSsnV&(Ļc= {qqo9^x [BS211p`~IO"s kdDpovfyq$ZϏ6lKP {i45nq(dR]4O92PKBE\hdCk? Wn凒ʥ4|5-rtxKK@ĠSELo*g"9)衫~HEWg 2תp ԼJ< 0%U.@$_ IR_XY0Nl&{[So8Dx)5s}bsn0>Νx+I~$òǐsUezFؔ1g_L$Jg ~ 4$I 4.CNY,d)I$-8pczI%d1]$'.`gS1E 6;[(Ia xa3 :fŀےdWp26^!Q(L9t97˺9<'!BcHF^D#*$(Uђ{.4] s JKikZq)2[Y=-*I֛V㓒-t/,"ݪ` d?&cSaQt7:  7E =j׶(yd2Tr}`ݜ$G,A$|Q(2&=R>A4ɣ ~D!j0J (ȑgywyNt8GXJIPs,14yt ר۱e8H(ȧx2ΡT2B=M%րaucs( -5-"*̶>PA[o /|=MU솣Y#5`ʵG}y)i-tէhO,`.;+N'\R^>NbB)b=̹7ʄ$Ofj~Ax W,Do_0T *g$[E\p>o{Ps*=[We7@6:22]-..1bCŞGľ.)u,F(HO R ;EڋVd,yH*vrj(}}%] dq1=U`ށlfnP߮0<>r_wHPwzMi>=:)po_znw:C(m +nUAuIa\ۍ@ G-OQn$0er ; 3}>8`\XNcQ@Icڱ *_Vtt/PcU8_O H{Psk$u{5"/u6:|bBi+KB"&-dΰCC;ckgRn ɼ+ikk(I`YKF 16/ IyR;P{ 8w|OUAX;5tI"tsWD%a.P'~Po~h _t/ Bͧ97 0oK{/n |( B0/)Uj0u@\5BMtOC[<|;OJRj ~ `vnݣ##!IԐ'5sx~ua Tj=} kKV:*Nù:>U#Sɚb:vI >h%h&7|2c~MY$t[5VepF];6t1D^;.d1z' ح˧o/>-2t9et8@ַ;O*Ka)`g#/-CC\:HHض^.!&QCR>7SsM9*tǰ/U:-Vv|ȣA%޳Pl!p`lΆnڋ{}{41zvI >ҭv7F [T@}z"x@5c۽r*R3\ K$tzd:,EA)tjh^Z=~ݻΰno~lEx_[z*ҐvTΎ61$rm~-iDO^Jc~A:*Itvڹ&*8>Gtl5:vI 2AaFQBDe{C%t/ Q%֪3@ٌhhteΥ~!5.ҺtӁH$Ӿ3iR̨onZ9w'ǡbd4uEho;_Q0BUGbсC{߮ǚ/SBG 0+]5:b9,Մ@"LHkk{ro6ܹx'ᾨ)ýQ/<,u=W`{@f/6"ߛ9b&Ż|VGf(96@hY%@];P1h8:*(;o%VSH,9X;5MsOAIλBJ֞hNX]N")wjaYbR"/8!#}5u(__dcbg8Jބe]E~W;8.l1>._̓]tf~ߎf:* Odnq_GeqӭS(cÄԜP(W8Ka%G"<tIݏtT)=W<|θj'}4 '<6\_T8ex2 R]Y)dĜG:. 0sD#S 7eGǤKYYs>90EpaP,\x(0łk-lsdz!ᬪ=KIj9)Jndr.Y@d9Ls;_T$IncDI|.&IZ}Ŵ `l@ ZCUN29Es^@g:5G/ZTkg6J ts*ثYuX:'a;؎v'$IҠŎx @TO>ipzZݎmG`;>s*4~q#a+ζÒڙy)&媄VWhO$OjzDjԹ<hSr]@ù Q$ K]w\p$sp~v<<l1 (sIrhv0Qڜ RZ^K_KW|Ԣ*1(mC^꯺y PW-9m~~q:KшsFy 8lG lG\o}vbuy~ `;Ra;G,> [OΆ#We$c #\ ){%L2r{\oOvGI@O+)uNB-$}r iQvB6|4K(w*aVpcZzpCFd~ΩKVǬ-Nˡ9?07X(CCՏܭC߱Y]W|Ԣyy(oC^~Wͭ2r`Ւyet5Sɋۀ? /S睁O;~]@J7<|`,}| Fܰsg,; odOvs˟}"`SO0gss"7WFs‘--'e@IbZM{\v~d0:PSu 2iqY~QﳰgsbϼEɅL&MzSY%rceלo}7Le-h}ۥbh9 /e=v rjQA#g`!ݟ+0U^Yӆ'fxӿ_fXfRWɄL+o23dǐG+ R[weBY[{<*R#'!2!(wZ[vx2}%2ك\Վѳ@ל϶ܶm~mgt(h9IN=LrxBy*8*%}+ӄE>z?*L1ΉL~o{poyv'ᑼ @;);] M.0LBǪ`qWiI[ _w/S2+fŁ߯Ji}-3u|#aur/<"iuOXg0r "wv4ǥ.#B)sj8SBt,]E:0~ԟ]2d |kCs"b,s5P?F!w $V4UoYpf¿ ؗ(/"['yu\m+RW혙:8K1PfԪsM; _&+ErNbEDZ%f9WL8t6^?0X'ci=ݹo0?sLM֩fhH1M|4)+Řr^$* RvWc'r@Uz I,Pb&99ZbEZ~E#dۈ LoikcP\l2fiЪ]m7̜7/4^,Ш!B0;?V.[8}zUEĘr^2['B ])*DɖbHA%ПCU(Vr ʕܵ扯P5b ɧ@.EHX bbBu)Oe1PBOhOSɼ5`Xp`]\~if7+ HNR[&&W~ U.ڥ\yUʗ: D2@@UBP(}BP(]) B(UBP(]) B(UBP(]) B(UBP(]) B(UBP(]) B(UBP(]) B(UBP(]) B(UBP(]) B(UBP(]) B(>}r@5kBP(Q z%e =IENDB`googleapps-sso.dc9f0b35c58d8bb90e2264d01b0bc9b2.png000077700000000000000000000000001325274564300532562googleapps-sso.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationgoogleapps-sso.png000077700000000000000000000000001325274564300465532googleapps-sso.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationgoogleapps-sso.png_documentation_1.9_applications_googleapps.html000066400000000000000000000120131325274564300500730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:googleapps-sso.png [LemonLDAP::NG] />

documentation:googleapps-sso.png

googleapps-sso.png

googleapps-sso.png

Date:
2016/07/19 12:15
Filename:
googleapps-sso.png
Format:
PNG
Size:
13KB
Width:
679
Height:
85


Back to documentation:1.9:applications:googleapps

googleapps-ssoconfig.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000002035671325274564300443130ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRA]V }iCCPICC ProfilexiTg[طwQDe' *!B!*J U^E XTBED@D@YdUDF@Dèh{ϜȠl  |鎆R989IF@|z] @|TK%Y~džU!ˎ> (c=cJ"$860@̉ MxE>w@r8TrB,W`b3h~OF,p3jPP=7 *ג" J$RoK$sg$nTaF~c# 8<@ HRd4UF&'/DWVQUSE [XZ/rXq2gW7w+WyyW Z.mظ)>(,Lڒ5};w}={3:Cd|G=N:}&g*8w¢ /\ղ_~v?n44ltN{]t=yxg/&_̾{> HRd,,&'D+khjjizz ML,,?XՍ1_? !ED1>mINI|OX /\.rco44jzn{Gg=}G=sɩsO DBReddirr JteU5ulttQ=ˀ x @VPPꐐ5aaQk׮fc8\.o}l\܆Df0I1K;>uۃ>YlP9).K镲_U^fcSsk[{G׃޾ѱg⩗s$@r8 `Zs38〤a@Ta؀P AtWA~FZW8Kŵqx;< L  [ńDb,8J2%IWHoKeK ɇɏ).c) T*uQT怌Xvl5͘M{'/-+_PhxIVAoUVdVŨԋ45f5Ӳju~堲h^~˂ YJND[&91fyEeUnu.\Isٙ<˽yݞ+^M O 8X &v_s6?\/"!,jv)Ǜ{n=1v]\FM d~j|(atJFyk6;_}n=;fu:xhȉi?4۟qdVJ~*uR嫥_lzxjk{!ѲIHKܻ;q:=v+H[.x(,h}dtٌ *!,_.8 50PE9 83w׃¯ m4Q( ֑4I"RT 2\NA),Ki='uJVPH MvZ@|[BbN2PܫzU-Tibc$_.KWͮC=QN'd2~:{gj)8x|sa`Q _J0mzJ Q{#zc孬ے;^m;OvHJC#3FuGߌU'O8<WLnZ7>C~: 'SyO$bҘ3c˜/LÙwrpezv<N{Ť<xzt<ϐ g*fw+^b/ g3ZgΝx{cgDPg&' Qj2\!\DD >@P` ! Pw@_=]"^(Hn 2[D<9ϱ4Gm3.I Zґy_~i{e IDATx}\S?! IPVЇm5b[5nJqAwƻöa[cжvTPPAM4HBNr~$@H$(Ѻ%\~\\9yB!#Z'@!@B!$h4 BHЄ]?|~q8ml:pU_lh\,n4cXi D]q)܀h } ul'G㖐x4+u`sbи%$(t +D8h1h@A]qM [By 7md_:h wGtqʫ X ræm<"@ōB_rjOX27k_1] 7⢻G-x=oC;N-_YFL upK}G:xJnT0Xp:Zb(aHCDl|Ul;o*il,fε Y1.*iɅ zn[]Hq /i=T\GW*rp`c(h#v߃7xJnT*~M[a0n}qF25a%OY:>skxCK-{ZM< O8SLUC±Dt@ hj;SIby6^y9'B5Ȭ%B'B\*"D!3 p<tEZ;x7F4‚_3tm혣p1`t|~߾|aST,>3UEf͒ħen,G)}`ώ/Qd\iϛ"ρ}Epp&pNGϏjr~1*k+maBVo6rccGκC #=BXn|OKMh:/k!{"H%|H۞_kn~8~vٝ񑟥O`:<Ը [;wDl&.;N9 Zd,I(\bІ+Quʚ]+p@Lt7˒gNeg84'Esn;eh>0QZWvdFP,Ev4% -[:E,wp6lbw(}C q#f?-ޗ(tXl\A6`Wt՛ xs}#Ee (Wp$/Ƈ - bi/EY2__ qx&F &.&jv2#w6t]td!@w q`l =cw㥶&CJ00,JN!%"γ1vJ}BGm_= aRaq@ݮ30ECs O;s V˼9ÄuZ>g5q{Q#E\q{񓗜!{x>Rrktx{Ǖ>U]?Շ<oF2oa8f; Bc%bFl 8yv3U7:fK+\6{1Cܴsp߻֋ɬX9-|аzp^3GRK|ЧE+ܿmVu: @(I; > !rӧ,8#tPIv ;fGW"aβVU h̃=}U#l}Wj56,ۭUo_;vkl~[=0XouuQGTGwwō+K]ň8uF=LpmR r`ƝQGY}Ot4-[$\1m(1;J:wd8\ڼ5;O/.XD w$IZ}dŲo2 1 f٬~"v #QuӉ;'Ń=Cf$M.TV t{|܋99oaXo=eڐE‘IXxWxdND8e' ~ϾsWUo;\õڅ\z>n&7މ]֚<6VnO,pPoJlPpp|>v8y`wWw]}xO dc»ȳ_<&FT垑IΉil6 NW#'{MCUI n~[.=-py!v9Tw:y16{H: I:y%ljm6#D@D ]ۑqv;Zy.tOw&htP-w {OPvC |GsH^|8Z![{~5B6&?g`w4psIXQ|.&7~5"#%-%:O:Gߵ^-Ώ=AӗO(]#W{Dt5f!op.9%+".rQ#B8=+l!!޹Q|='Ntd TP]q>S ϺCtN \m) %MH)5ַ]DIxCB=wF Rcx&%0/jx^tֹP%]8!ygܯXעɇhou5o;F? tc q?d3y1qiq glÎ fn}U\RF=~sOHxtY"cI{YS__1DK/o!9SZ'ttsHxC}\h3Q!Ad0n- vY]'}BM lݥZ頻 吘0硃}CGxT}ե.8rϕR=մ,60GDLOoiߘ([GGWtm*SYN)M,8}~sp󞽳iY'9n2n}w8[as[;X]$gGL;♳~ÁEޗ$òYgqLE::v;գǰ#b]X];PBwʅ67eԮs\%loSsQ/]}m:P1d2HwV\{cܾ's{\-L`f}^V8~Riai#' ].=~5>h?U0+1]ϖ))Ga8SB$bAm^2>CxqYklJf mNR߆( 8crFR`MZ7Me -|_cl,.]6.zL sE䢱bm%CrH}F 'ok {F d FG06ŀ;~(Õ'-6ĥV[tZgFCLkD UeWzKҪt-i4nc+Drsp^i:ߠ ԫ4\ɍj¸QW\!8N[1\ɍjQW\!F@JHPq~PW\!MIhk a+D8؜Ń:t =B HXE!Bz !BFB!O ZN?{?j NfukNRضsGs7c)C:lC~#\o@G?@!\ܓ5I?̦#mNw0*ROnqgo͠_M>ο<]J!NW>~PA bR!%SYT`8ǎ;*>]cw|8ת~ݏ>m }׆M>:Z8kD/#=]ʭt]ԎQn׿-:`8 6 ^=>0b?W$x}Rem 挋ۯG'7Y+w{(.;Vkv-qξ" _yl}kO#| v;?7Ǎ8/{tbbEg.: ӎ<7’  +Z¦ `҉B{?'$}sfVX@?Ky"COtÒk3B+=%x%gxlKo<8rc?9Ѧ$@suQs@܎0Bh #1B( ::>uָ B _F(e`TT/1TTnhiz6~ƂY !×Ɲor`ʜa{ۈ`R2;7 "ujO:s'#̿w7`ߦEc[\ k[82kh-Bȵ6#+dBY ~eqqsgĥ5~*ںxo4|v:3汮c 3&5p^81{ĝCKC"{k?: Y[#,kڲOb 1F ܹxg #nKJ#/{OwҢn2[Ev;B8eĊڞzJ'j`;wrۥv7Y2z^"4_ꈉ?\u;~w뎿u∅Ng&k*fMGolhz{tG-1,gIҳ&<ˤ?-o-FLFIX@0gw8_ On6&F=v|QϮF7,Р's#ez.!/9k͍Q{48;k)Lw;e8'N;n<)E %:vs˰gQi(}/kRwbɝb뎔L?zl `rA NsSCӛ0g{ñ'O'ůxW1^ Vb`lqӻ'3,!˯o#R_8G\Lg,st %!IprKp[ ٥m 7z+TZ#rיBd!9![a%gRlq}~5(\4Fs.[ `&$CڱhK+6OzT缀{ǫ*miucukߨ[ B}3ϛKs괡]gE͵%jٶ&䑍<]W{{cU?q ״%i:}е?^9:.o Įl8b!-*ѧqB} #qj{suanݒU;8]%?5l8'cw'q3T))}T,˨ܿH8?˓q͌OY/w:or-5&.gU  S 2g͊qi V'>>zo@ 0ߜoH_j͚5k֬J_Sp1 ƂIRx߽__*OgMpO9pwͷa=r8BX(&RWS?E/Ң\b}yDe?v=+]ZfG/jN6X}fWU%kt?r\y}e'n[KܗsFǏ3\?/vE_${6h"oTug^YT`k]+~!X _oB\i.ށu {e7<ϛU݋J{T=EWA Qky(͠ ipffg=}56O^,:Oh\=]~[ @Soз0!dЅ"Jw%Z*Px^3rӺ^ʴl}sV"Xݵo[5Pjs3\֗]*.;NN RU^.Iܷ(E]++w]'YX`.ѺO7$г;O^$ ^]D%'Wd#(>@1ިYF)7<1[@'4!d1<χ.gm1BL!kjrm㬎3qWxB`t>Krށ+eO \F$RBX ғERy\.b [I./V1LGb&uyP ]ۆT\._3^*F]Yqo;,}FVCc~wX5'O1zZ]vJ/kX_lebC}c}}cJ+T;u +-KsuPU]J@o4Z,| Z@{n{>TPf7OJ(Kx$˰נ QW5盠9XhhXԚ~|{X}Lfګ G>NBS;e1^4_l(9 Mee9RSYz !܉ybTЖ|}rKzQ6WwgI_^q:ByXC6녃Try+UFAnavM :2zY ]%Z%Ҋw/  ϵ\N77\R_RWkֳ-TFsVuaovgj]/5{ۥcu6)mkk~]!]ԅ<_Y_qW -j#}G16-+5kk8!Fs.X]5wL7%e]c' ܙcȀߕ70Q1+@o YBYң/h6}kuyySne{.뽨׈3>[7)/;o% ־)%H r[QAguz#rΈMKV{_A%}n#QLKVwTvѬ1Ĺ3C2,:^h \+HT2)dcTN6_Ӈߚ{$ɋ6꾩i'-,K΁ovADEUSSʲ[+s&/i{>?yˍ"lXLK6@̊gMUhY7ù#S %^M~fwrm :,âݩy`mc[RsG w@uǏnV{2|}T\$F^;{kM7?m &]xyښy_7 ^Y[ӧuҴ2*i~#zjG4Hn# r-ӷ; xokJC~/Y\+FzdH/AsW؋V@O`p?|cqo#ujd@԰#{l>oͽ(a8jݷQe~M]*[WXeaӿ+'4?-/{ /,|9#;[m =Ÿ\RL4]:|Uupacź}'_69X%->%1WbS=^qG]ew4Lx[@2nu5#vq(wmߗ<2ņUm@_8z-G8Bȍ,d'K,ՅIEONz^1oo,zsSBmHOέ0=lKss*Vۇ=3ԚٝG*+; ~$ TyX]WW+\zChۮ%F+w-'1軯h]}6:W\}V]%XuE_1l֨&Ƴm21Bp6A:Ng,#}Ȣ}]@ !Fr#ʚ̓τ_=XO6ʒ29tC\]LZ |k!dpl +Sl"iBzA!0-B! M !4@B!$h4 BHhA!B!A !BFB!&B M !4@B!$h4 BHhA!B!A^&<׿:t;{s!BH? g%;>㘘z΅B!2Xu'|@07]gm?\.eJBwjË~=x@;47?\S9"""BR8+Z $? e,B!ʄ˲MJKb8۪η}tG1mmm+Vmb*`p0>W-XňTfSmC?+I>4! ݇]66Ŀ?wLw.yvþnj&a6O;«^Yޛ|RMr֮[SMdM;! 7&ڽm3|ͥߘ)3P;+V^z )Lʪ5) 0)9[kkڲf EҼiW] =kݴU~U^ vK|ۜ/]WT|\GZv $/MMNYU^X~%v,MvfW !>Tܳ~|/# 㙿SwoV4rꍝ ӊtiJje../-),).)((KZ$ F+-m1hK4R-)+Zt*Hve-B!$@;|SDy3_3b.,ZrNUFcaf+r+6,qv1f+?{%oIb^8Kqϐ܍2ft^i))0݂1ki1}Gɧ;l]q򲵕-푑W^1W+ydgO?e%dk$QVݼ'M?^iC2-J!?XNa8NyT脡7ݬ"X3wsb*ʣ8N='WX1k+E"noϛFgJ  u0;tіMJ͖U m^+8l7hoL+:࢝݀+ڞ [/t]$\ pIkB!#ȘhHE cbUQ #TN~WN@>}L?e._8(TThƉǕ?򬕀rb NTSky+SbN/pF#Jח?FM y:pqYf|uփlq_.tVlxMoW[F)p ! n!r&$aapH[>eDGaaEPqeY;Sb#SF@VBWyv@d@^wcydZN 774x(ٛ7,;z* -ek5YiuK]I^ĔBe+ @]3%~"BI;w:l_ݙ00@&>b"qVkJ܉ڤ^mf=K6ӛ͐]i@ge-vLF"runaX~*|XXX"[ny'.\(0o~в75~7|ۊoBtן={}CWtD0v!C;C65sP?&.װZB!dB7W-!rB!pOB!䚡 !BFB!&B M !4@B!$h4 BHhA!B!A !BFB!&B M !4@B!$h4 BHhA!pYYrY9B!?@XbDRyL\*J][U0̆Jjկ[a\V,eJֽ[rJ*֚- Yދdj+*k!2pİV7oz&7硛] gɚ%X"~lXWs9ܧn܎ukQ&Z`\wZqbfU?_=$ۄB"8a ɷY›uE˧>~nw`&95}ijrʪ*֚)Lʪ5) 0)9[k+\֚-)Lt׫{kڲfAҼ~s{7q[Up{իV^bꗵZ53,}WR3_\FB!? |X R#ϛ zcsi6F[R PWݯiE4%ңʮ.PWQP4uK 3hkX@Օe{U]ՀZߕRQ{5O4^n暫/7[ B3!+Vw|s./[[Y[bzW3WBԹ^nņų3nۼvu{@nf}z$=_-/|b.l ԖdCp&?9v~ (G;_`Lϊ{a0ƺ2X0C}I NHBu 0jbemZaAxSحfϥ.zP3tϵ=K"Įfj-FGRc.rss+Ko:lWXpR/5B!(G bGݝ^3F v~,5}&eejd /.pB8X eZ 8hT?9 y0ãVUw~MU V.[#μ͒e@Ю :m"$feZ~н Vk]i g%B!%Juț FTf)4YiJϋ F i=j\T*WbF}:M49|sqVlFm@cEcWViwlP6 !!CujbBT&q؃ڤ^mf=K6ӛ͐]lU\<`N&bМ@&eujZw!]'iٛ?OZ[mܫU7[A>senq,ŵʁB\`9@(w>pV"T(d"!u2 BI?3r\$2Bf7޸։B njk!$ @L2eڴiQQQ!Ha|2} "4&B &B 5@pM7moA[4Ƕ?ͻZ*l=puT7oͪ5dP *o9483s5 d4 ֖ؖެlKK^s: 26ЃM ,, { YZ񧙭n[}K#ʺ<>>>^.MC5`9G\v^?nV1L)4l.y s`ƀKO{{++stF$T IDATKu=uEHflok[v2(@͎FΤlq 뎼T<>>>F.mȨmְ333X{^|Hk_5h(sG+3}_~9K5ۏgfRK髝__A$wY|g4۹oeY/]eϕ$^QOݎ=o} B\ H\&Iu9@-SaCyPv+@&לTצ/~bdn˦ zk2 ]U8zE"T(ǣRhE<2VhqtO)#{Ool_mIeaׁyn2@2omyS5Rҷnߐ0 ìRV2.c{ĆKr-HgINyfyY{йI.8&o8z`ijAeܴeSf=?r y[S7ؑ0 uТaoA20LgC%jԖɫ4k(HwI?hؑ:9}oپԵ(uS1nY6ERfiAO`m-eҽItkTeU[ɫq暶rm:fզ<ϭ[*SP35[Rزa&yՁʜ=f[WbR 6u;60 $o:P2$VVOsI͝(0N9w~_pk.נHYVVY#,qڴ1B?KOg-W?1oX֗M , L<-I!Լ[&w2Kp6&T5-kCŵu {;G߾ZLP(N2۾o`{mOtX_u;P~ffP~mffffK5|yԆgfff{#74\fy/%Y{;ҿw;2nt}yb >v]&r2I:Qe;^W-oefffў=#&3 W[ֻ)9|֣&uQO+G(bbČ_?d9o?[nKϡulaOZ2wK~̼=k޽.]rbѧ@Zi}85|ci6,m6[?f:W\] ۛKʛ흵۫Ӡx`U-Yb1V窠yo,GhUyVU]򻟿i/Vٍ^M7W[xިjysŀ%v{sI |3ϻ7- CgyCaEo6T-ו^D)M]FsQES)ARfVHFGjYcHuQbT CH )yfCC[P|!k՝򝗆>:rȑ#Uy;= 3V%W 1048I-. WKrő6O6]jw_!;50MUC 3tJ.j;\^|U;Du)ךּj~RPE\U*w$Q8~! L<#C h)CW*afjӫg+g86yYCwӖTi\&jj\qaryiaLry%۞ӶjN˫Rs!ØZ-E ,!Q0cj_z}IÏ_ -}an[Ǯ\;Sr<3JW}|;B<77|GׅS[rXs02'5/\=翾m mLAC ^% =.r ^9#]'* p=䶯K'W, ×- )E޽Ozwv_5xC;(`tLXc=m'/m\hoha=md`{i!%kǝd_U~$]=cu{f>~@ͿyM^Ҷ\Y"A{ݕZ/$uzY h_&Q, A. 6>tͿy+mRtD^Dq׈cE&_R9GsXi{=J\xb_K}5uE7AC6<9mk\PIdFIdv<"zڟ7ru8FzUWj2$loYQDq,o[yaB{Q>v}KPf:?5zb_~oy>K haϬH0 )0L' {/5/' lmPė:\Pl`Y-cٱXܠǼ -@c>Z&5~Lo0xSoq;ʣ-@\kinڥlϳ22:~=O,9U7tIzhgo,Cwwww[>_[XmAȒ^sC 8](.C]un?v {^_sDFFF󁩳ZF0nG;/^^P"Z|eaaۮOqcye~|x0w/rKAz]2NJUEÍ /+#e7 Gx(OZZo((+ _WAq97;.c|Ǝ“±c9-*C>m NYl2tђ2M 5: MY _Ό-l4],- -LA4[!F#NSI˟ sYr^Xl7t?}?'222roa >0ޚr 6=l~)//83-tlk doZ{A>QF=~/D _Z Q?ʓ:oeR:5U۪@.D1'l>ف#OW 'q!rΜb@"U_|Iu(Z%eȼx="(hF *L3kE41B9DRTTFInmq;򕊬bBi߰3](ytQr$ dRuf2CH DB@՞t%]hk>ϖaϞ/|N#EeT3kpc?+9N2qVŁ {,5"my/Uv^n=]l9HHC*kj7yoNQb)W-'icuDA4zmޑȉXS SRFMߎM*oe-︢B9VqVS_N8sEip4& h% NÆjW[]M[;j< {|\}[`/Kol)+ۿg-Q3<{ZdHQj0MQlN) )qxk<{bU=DY`Ualr+Go^O,L$|0RZ,xNo}}}.]Y-VֺtڨH5mFz(S1e@5zqWTW(g&~~~J(2&0mL- @ 0L@@G!@ L @ L2 0i@ ¤!@ L @ L2 0i@ ¤!@ L @ L2 0i@ ¤!@ L @ L2 0i@ ¤ Ť&YbL L χTcEp uS`^P[:-'E$6m#Dl`jw)@  3S_RH+97?+%k>wߺr60S[zvR EX]NDz57ӭOgﮧ[@!Sb*&4/|;><*/I\m'*15)1*>ESke<'>cxÉiVe]}Ti@ NTFIiEFZsRS t۞d]#OmãotU,C`p$r֭r{Ʊ兯p~$ǝ@ f 0SF sj C4ijrR +I#e/ -WHE$MF6@b0HH)&G "iES{wHJT)">C%SQ_S"Rѷ,)2UZIAՇ&G8",+K"q ![+}+@&;RhSW;q28;.u?o|8! Ťgh,ܰXs, 4m:]XW VهM-]7Ȯl*K~'\3k86ڷUw?pk|}F'nkFݵ:{"yPSN I%o!,7@ L!Sq u_mi3FԸa[ ü Z/m݃`BQ, nQΆ%E{j/j$*|ć@ 6S?|ej+_~?D z;Kəud5 ʋ7cU¼=, 3_J$\!2~ŷ IS)l|G ^9 DzAIJ뗧Ǘ_q_ؘ̟]f: 0yq8 f3LҵWJ@1%7J"âH2@Qa%Y",y@m7 X))oca6#bi0t[D-btVamSmA" '">ˑ]j,dWR2-Xi&Snu[9FA O LO XL:=x,{[J%b׿)HzMԡhDuꍴ@ѤC pݔ`b(HMF&K8{ Lz= 's=T@ Q h7$-W_I m6ͅ~1F|@ ?}~k,/(,p?I3rBKS@ ?y~"@ Sw~˖-~~~<"@ MEٳg!3u'x",,w,{ h 0!n@ IDATLEpoq-rA +L @ CA&@ &4M ,݇Kk,`pItjU3\ѵ7nH^:=Ow< ݜ'˽P2SN?l0LxC,5/8 9I-NBNbSu'oLPe{e瓸8Ϝ6;sgt W,ad}xs9\xª3͹?L]KM3ɫ;oip|8]Ɣc5'hQ&rK  ?^5kNdf^38^_{7k34 3<+ӝ;qf8nf5ZEff(zrwff|c?02ť 'R'|$e +.Nxq4OF9ݦH2R Wmt˰Q4sxnUL٢qN~]'S$}9'*|{xyl[ҕl`lߢ;®5Qs8}-yE17$..Reb\8YcԺ/1V}#~wFҰQ gZ^|bbTTFϗm`j-O $e}_D u>Җ~0У+zV 8s mQfeTVP  3\"Gke'z|N Z.+.8_45ggf0`Z'60\fpeѶ=u8F9lvaĻ5V3{r`Tggffff{T ,.zp]d=xF/ fgfff9kF+>|wсa'Py$[vZcEgͼy"{rftg\ a Avi2ejhhy텱I Қ>u Ph&UQF$$+ mF+Ril+e& w+ 1M tUˀLi*C\biCs6U_0ڦ@dd*@A}[ߕ @H1L_5V!*(22@F]"0}FuaEb cFۧ]1vThv\TSP@1xҥJ-JڌΚEҢ> i<ticWHQuxBW5.1j0LSLTPE.mBlXWS-c*NQxpCfsا.a lac$v|@Xձ%JVQ-i}G_S(2̈̈NU=2G%"Quib > HD9KlS:dXQC1t[֨(j:X_ d.1LSXhb}qEKif \P5m! Y}@62 c,x͆P|!k՝򝗆>:rȑ#Uy;= 3V%W 10߹ssX.ϻ3d^-G\?=t_T.]ZSgyΞWCڶ;*1{/M=CryUېÓW*afjӫժSC T.Q50l޲'l\qazrT0mU;&\1Qm^Fs ӫDtʓ820I.qif"TٝrΪc8vvi1m+]@uε. @7kX9)@BBt#@ !gE"EָE1֌yOz*ŒەinHB^{iacZte@X6D=(iO\X࢘oZ*9 V);N3Pwad9hy- wl>Ļ5^n3 ֻ8s︔l1϶t~4P U.UAl$'T-f©ֆ gF͂sНۨJgǃ= =}+gC|3MD|lP;-@ыgkn_:]UٹN3zե+}˖b-{٫`_?p_'#flr?l/ض`kd-?"/U{|ܸ>0n-gMȳ` |YncW. )Z93ss|szt]8U ʆ5Yݗ4B_Av߳4QM@mݎ?h# KWIWtúXz,s= Y0}%=|ЮU7b6kVǤ)Tkbg_t0;EDst{V|So^h_tD4[^kʾ+K2Di0y8Ri2TfY~/`m׾nPP;N pŸ =u僟:v{K EHh̸m0d|6O9wNѨz| 3g0+}9?fctFqsiws0ӹ7C] kD `ѵ4lTRo|6|DWZn~tD~s^zM'_AU ?* U$Nsa47Zt-춠S] dIW/١.C]un?v {^_sDFFF󁩳ZF0nG;/^^P"Z|eaaۮOqcye~|x0w/rKAz]2NJS, 7~Hs;ޤt:  ' ulvcaOI:-U:, PIS<2>=Zkt&D{j{XV*]*u)ÿ fntJ~AM zz{{{\ms/?so6yK?o>0O=xPCq ú4f'ٽfΚenm7?.HacWfpOwu5vh؃Nv 7|//FF/#85Qptt=|x=|`g͋,ph= X{m\8u3Ƽ(fJG ?"/-IԇR72)o@mU kz~6}+Xܰ3g(&H`h Y }#&PKVːyQz7P=EX/QDm ~Ug׊ib$rv2.vwG#_ڿ.&4 ;U;IqU)-GL&Ug*;D*@$_IOGQΗlIȪ9d:b\[IK3zvNM$p~E' g\|ɹn fp:enכ_θenKIbFG~|5a7gBWlr ~niw8( aTl&1RF$E=tiRDjmP\hJ5m>dHQj0MQlN) )qxk<{bU=DY`Uwjf3^O2L0vu~G-Q CiC5 np+6:v52 c˰kh4ekF}}}n%[&J!4d폳 +g1۵АּO= \fk[mn͘njz|=%\~߳,;奧[AOz>;c8Lp0{ <-fӃip)C[_tb )*`@.+[ 1z| c[ ׎/vp=qoǐ vmlƩ8a aWy:r-md‘.=h5tMuej JW d;9Yo1'w9[^|m=ˋe؋?;͍x`N4-67fL7F5=3{ ?`њeَϜz8f=Of[ M0XFI ֭[---˖-nGİwhfh~ʬCgT2+S6_!= 4tKz|ah_!5na0_7(@( po0u0@ 0A L @ L2 0i@ ¤!@ L @ L2 0i@ ¤!@ L @ L2 0i@ ¤!@ L @ L2 0i@ ¤!@ fJ'&^[?9DM ;TM >~~~~SOfhKs;D)͏{q =w[7aN%5P~[*-ߐY2%)Ҕ!}ZW㧞.֟F% [)U T3_-@Nͅ­qB4MLESHi*Џ-B~1B D[۹o/+p /0oy¡Vi[DNJyp MڍAsB(H|ϗo¶OE928?}]"i ʐRT ۵q.X0>=yꂠG^Z4؃>2Iўڋ" bi$@ @, bRsK)[W Yf|7HKao~"P?ʌ:k" "x¯<3@o^:P;= V/_?"ݪz}3xQ&W>oyOx@buچ/%as?겲K7&܂\ԥ'oWW<;ӱl%8YTnqRYXXX[y{!G3EJjԾ 71 CK5i> IhJlrDTb cl,dIEƾ,͊$G^Ϭ 5x流h0^@ZrarD:hDfôoR=iΐ%j#Q"M:$%j@ ; a7Lz= q,&"RIodm ?xnu(ZsoQҊTj$Oo {0#.F7@ ʔ'xz;թvթvh{Vh3[ f l5Lcw9@ ʔ@i> UC3rQ@ L{zA az-[x;34MSgϞv@ M x≰0__)H - Fn/„ DDDDdd<0e ½ŭ[5p0U&@@44t.=/xo><{%'y_;]{C@  LxC,5QSXv` IDATqJ l|wxuglc3 9M'Ƭ9{x}w/dW0>hLwąȆG=u]et 'bp"3.#1x7HyMi@xbݾii-p3 N*pȯ*ӥROB> M~JE1 Uv52٩~a!70jz_ں[wa /[o6=#̣2j5i=5Nc'`?{qϏm>O0 yܾ7M-"ȇ_*ُK_2^{JNI*o55'd۟X>߿K)M.>CM i꺀4BTx*u1ϘNm]YCqaD t"hr׮TDž~_jiUTݱJh!5ㅼoyltEV.XV(㹫2D)ąpI uN~%GGZޛ:5@Sc2̳'#H)i෋D뾌>^Dqe|t۶ec ~o&;YBW.ܣ5PO;~oVKyCOIY w?Ք}t2ٱS^^H͉<7Ӿ0SfWemK`hhj~ m[o𘻼aX/GËsCf~Xܖ/}e~'k)˃ť4p=8+rsH~drKzw^G8r{fkOt^ݴBwW52<6;󷼱4K]{E殚%m!aFDb^,3o=0ٳo։|pD~ϣeF3n#w}ma/<M-Nϔc6Qn[;Lwᶚ@T_9uhuJT+vm9n7xkK[EO8늱qq t,Oh.n~n궾="Xq5*Fi;;hRc]}ht*荾>LYqYXo x1H'/O?LO.OT?4CܠB~sʒ# #/j4]Lsѹ^-V) =(g8J`P&c$n{k ,z;#vF cG"ӶTwkC cho Doo [ЦW,Cc>Kh ^/.t`> ގ-ůʑpZK邮2]LMX,YH}VZz:Uwm 4M'ER\X:fjN-,'퍃ˠ41 V^uA;KH))YA M'Xqx\ca( ` ޛpX4hL "$.@قad0-6cyYq/||fQ/5vP_ w>| ̸ti p4SX-d Į^zhn|%: v}VE_Y指Ĺ@, ~E|W.i) .}a,+BmEk#8A+k-wWٹbxE,_UŽ>7 hq)VV#pC}^'7/BlIgZ;^v] ts|))zL}{) }YY2Ǯ\0o~Hċ^Fwi!F1I6t}UwUNcuk.!X{@+`x?+K$({ڡ])!%oD9l`?K׬IS)ϾavZ>Kׯ߼"@蔉iEY3k}W֗eBTFl8Ri2TfY~]=}4Qj°ËX_z[h絯/o>%GA԰_ÝOA\5 ≠mAO_Njf̃6Z]rϏqtù_ꪇ- . K~z1/5ܷsliIlx-K 3g0+}|ԡZwI Kns{x1}J>!M*X]s]> zXEgjz#?FJە$ox~eT/PK뤯?@s8{c*Nn :@tbj8# }tMܒ1k@sÞl9|`VAQ8Ѩhii΋HA#`:_YXXyuc< _2PeOdtdY><]Y;]֥ =Y^'}%<}p}ՁsY[S^F{:%~FS=ށ-Z̡ f\c̽ 'OffA|c뼯:gsΙjԈYyGˮ+Za+.Yͱu𗉇-ρo`U&  cɆnȘpZ*}`Ϊ='G%wQU A 7VmˠU.AP,jj~jV_RtSn{ߋ*9OmPN?+`R{r>Ѐ2ٟ8[9#$XNO?Gj)0g3u"-NZ/xq+8knZ'Lx-OEH+3lV̄#lU鋓#i NٓyMKƎ \ ʔrz*bɪ-O0o*QG?qgͰ'l:T8Ycڶ/?#){Ogr&,lA;isZTwrD.|)L Ngh!jNJ>8j.ʫ=f [ y]g>d9cqRSU@f&FGC6! >P^iVV)a!PNԗ~_. "fnj\0԰w2iǘ⒬0! +8o5*(ly)e0@ҍkKOcNdA2l0Scإ?B*r'2@Čk:mw#bVsy gQ񱨺8}^ЖA..uQ."cOg>鹿 >H+W)֣1Sިj{D**| _u2jzU[{ZV5OFV݁mg{NTv=nR ꗛ=%79rʕ*wG :Nga#&dKLE],uYICΓ9',ٶy(Y򣳺\ɚC;oSN" c Wg<ylݣ=rю>l ݽ(IB.-@"@F#d r㘥yOM= K"+S yz/ $S(<2@ܧ_la{o=H{{"n0s+L%el}%u5׽W"Zz+(:B1%3JW?fdr#"RF~?ml>`1 &a˻L8s\$ S&MBX:|0I~ᇓ'ON6miu7_C"Bh[/ V+6cfta7|0;VAGDHREDMs* ϟ [ %ZVw* =  '`AAm AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AAQ@X`l'n` M>]"HVmn%ɖN-ޖxmv;kC0ww9to} 蛛_@&$$?SY>25;OLTxtϪUwt"2ck͙prǗ|btvQx@ѲYk\?䂿6AqGk;0X$%3Tm[-H$I*`ߑ(H~xỺWOgN\6-'twuWF'?ݐ(H$NvKl-,ᛯ8\Օ[J$IՕj~g$1ՉD뾫-xWv+ ];J-"IA9G &(ii޻ @Z~yEI&jwf32ʪ-5]]ɹ%Tr<@N١+r@\Vj"!$? xJI5= my֟{~B^/{i7G} wU[WQvߛ9[ WY>>;Tr*yFU2TrlOuy7nH6(WH$YZuBvjP$o/ 77~M?(_3k1 ڡ ߴ Uh)􋊷Wc3¼x6 A?0fUJ>rjKeJjwX7}X\Plŋ3J$ jd-}͟,|icϥKʴ Fމ煰t=-m ;s--is 5 ܳkAOMPl G>mȶje#@QVP A5*o/{61BY5,!FCd7NٽmWW|? h8{/DYXpVkIEL sهϯ\j7$i,_4zi3M_>9~$y|׿yD(_z$2e(U+ebB,|3-ksՆ|8WI[3=RĽ7#[yWϿ?Sܚ $\4 9UƆ_w%C!aw j_AƀC] W׆({"8Sƫ|.(ݕ.FpCWm=17f A6|Ē֡BQam{\ gp^+ ۅxaTv{_)w2;mt0rߔe{˗M469cO=2' bêC[L"e 4[ՁTW>k 0iJڞFW'TX4GJUZc|sQү~,Y 6)S/kY:DR1V c# 1͍Sq;F9vFǬ{c!~bqohþSF׭f{Փ4ms:<啱O\ںpFughj7®v_m ݳkAa/Zʭeoz??~@kV-PI$*+HwW-$hJ<0,Ay`Vʯ۾dY?S 2I y.j.WCN%s yj7Y_R56)i[sA"yJ iP&'-W Ig _@ŸK2Z&7- R 2fA3'$! R(}.2±~u53_ÂTh \?]!NHN QnH5Oeg(_ !f#r-,$LBi֛*/3$w A!p/>ТyEqp,emb8ma2=FML~\m0aѐmllpЖOXAKtawD=d"s>L$Q `Ϝ*hBgb64tHC$\ " 3CbDAĭέR@7vCdt+ؿ| /޿!g`F߆OͦdD}S'  ilEAĀBAA>CAA>CAA>CAA>CAA>CAA>CAA>CAA>CAA>CAA>) z3.j$&0A׉n ߑ+^IXBM1}da# ek6=ѻ7+{ێY~엓0jxAm nbD^tCztyȏ-w<$#ATяUm_>sW? k:>$^7؞S7!7+-|eNk\=7m!n\ke93SSRNf?2>$BlIY?gۥcM7}@BߧK8 b\-{P J }Qy.1mkp9/ݡYWS2̀&C3P@@B c,+}u9}i0P: ӞT^R 2~asq \([N_6zN j(+r FWm'Z0KS O\ӼbGs,FWW4NDϝoTX?,},99 (+*>~3OT۴OYP+w"rk'zL)炻w˼Pg|>hE: M!s7Q**.cfWC?|dGAy7K gO&|K/{9.nk.xGv9u3*1{5`ebњW `"~U3 $_.Ң?VVx+ȡYhiwڬ26\L0ېgb^ jyQݨLka:"A\/6`cjAmIg[z*S&q͡'QڄϜrlA@O}ݚ{C0!0U|Ѻ@>bv `LǦZ+#ώeөǯaaq߽og1^`ּ:5 :GyWbpw NҜ5ia* ][ZEw,MifcTu/YtZ/"limmxraʂė(eiOs xqBLҲwIv^,JS]6OiyDY%+zZ5'bҹ=Udg7>vm՚c67+>ٹ+]+=vv%UE9ojxqȷΨw_jxp˗/➙~/aZ:>$5Ӻ:K]69׊ Ufġջk9mcONu@0#*i;t:>$uMΚԇ/vf$(:r1ziGKFQw\۶IZ~SBVMM#[ ;zO3N)_G>34Z~\ FPwyX ; ;n0T1Xӭ8:fXUv5_`*.L!{L.W*|mvjkG`\j<Â0VM9F}#M: ^&xECƭWZW#ѡ 7F$&*Ro:Kg/̘P)0}VƪS=c]01;fWs9tWU s3gDt bEۏ'')\"h/:a6>n「N"%{;W ז5L|ZtSƔb˭icw(K(QS@'jnUϏAѫ΋GG8Rh9- eژk`)ԲL{ {\bzJY&qBq$ //hR hųWCcƑ:j3|̓w0"yw3 a N_xȆ΋gAwHJIɣF>"ڱtO[;>0|\uʀk#i"rχ(ZOcė9O1w@s+sٽv21 ? ZVUGυ:ļĐ֢⃜'Lsy4ZۦcfLaZe^ܰc-DGrՕ : xƒs~H=̔"QJ6?nq?n˯j/FhD[]]wDNaO"luL/u^k䚯24uֺ78 ];{+@|51~zVmܾZN%w|AU`1pw 2?\gOL6cqRSuYMK}=lKs/Z@375.Xpڮ4/凜IfH6 }).*GZ4d/w畋ZKD#G~52ȣX. P0CyYY+֠ݥ%evZg>ӽiǘCzqcb>@gƵ§1'Rnf\07 29PׂC愑tΫ~dWrs<(|v`@L?un4X1xjls.4"xZu7_C"B=gYR1PAA|6" ߠ                            =M"Ħ4Nn[,̯eŅ5U6쬿a[$w6naAq[00w 8*[sk8eK>o1USxAq30ZW6H$$sKoreKopr[D`kc%NPWnY+H$WWYdݗG$JWjߑxCjD"ILXD"IRe`jٱzťYDK;Zler72A񓅿hU$)Ij|U+Jrd7V$2F7@F+?26wPZ"!$? VU+6HI VlQVm-IP3ؚ QU PIȭhhpT.A 2pVUcI2(K7C (s3[$y:@޲dX?KV)&O{yȯXɩh[QLg$ydKiVFsR `E޹0`ҳ`2;~7>g{mD *+P6-1>Vܭ AAD؃On/LV8mƃA%YYG0{?mAħ3{ l+I08#AO/   *  ~~K. [ %FQn޼y!ox###rY$Bk4 bĉw1` ;A.;AA AA`&ͯ lGKGVphvQؖ Lo 'ŠX_`AM6_bgQk #J \w8Y8|ȑ#%o}n{ʠ0_4rd NJ 12(qC,1h>O 7sq@2rX_`R:  ~ ^a27:(S7755kC<ϟ)I^Ny:7amVhǑv{Τ/Yԩ2穵<ȚWᬲ%oSx٪xjlow.ONNVm|-wkd  n/|'gNP=fĈё=%s:*<}Y^1bEP<t|}w:v2`2" k9d0  .ye>l_\4úx'|br ga\!kpd*oG^[ZD"y|M$0 2'J&g|"_=~D2#Z(m_q;S;aj9ZV> Yn%`G L`TPѝ;*Zh9sC&^p y~ZZU3#б[A(\b况S v}O1 ZU!-k:XxGUۜ{F0H(r{ /)s"1%sfT{wuTm65Ǧx&R!m;] -v*  7p龉/GIȇ@Th~W:P: Ӟ_guK0ly++JL_9qKOYT}|]CW.ȎEo2jM(wi; n3>%`'Z0KS 3kʊrbSY8if.X++u 񐺍tSPv9G$Qe'>w"rkB.d7vu9}iecَ_\a3uM##̃k!앮!lqͭX2B9bW**EHߦP-J;'aq?94/q@i^\s*;DTyMSis2k% t߽G͵,nSL=:\EA 7Y!-Dcyi- Ywmi)ol7E@.K?qxm@㝝_w}Ŏ {InQfvweƈ',c; .MWV͉XtzO5ٙ]l;Ae99ˢ4Օja_q]_Y8kܶ7HHӞ\5;Gm3talm<6/3{"M5+luKvb]&-LSUa-ۏ}XRս03;g¶=jkQQacT,lvhgߚL<5K` pa.d$-{YX<޵e<ɡ[W~j={4lf݌j;b&uZW'{h>r5K|(-z/& EUSCVM]+^6NY8cSNo洝-cwI.ߧ&jvm\3gĄ67b|u:Gtu8m՚c6 Géo}(`W*|J댚X=Y!sиe J5w(Cs??N9t=;$xLX0 .|YSNOr O/Kuu"g1^d"fΈ;{`?3:(ĄX\|@V6'5s}Gu@ AQ ~drX9ff F<ѐ=]&J[1:DmK( l1[܌ _*`Z,֧mLU\C\Ω.VXFa-3],LEA^6-:Xsc?YvU"ŜȾ0Kw"绳{':нE`ppXFݩ)նi ϊXu¤@{ jΣ5 dsUebjƜha3w[. pib]r>I]GKpɲJ^(v`UQw' \g –(wd]DZQJca+@dtTPg3P@vÍ"cOypJG=Ҋ jUV9.,5g`I4pM"cmA\6lhB+n uo_s:2lwq[IaE `=75ɂ{fs<`P*3,Q\kvsdhE]>/s3>Go& rDDsǿFI.ZT|[LyjԈYyGˮ+a32]:fq@ȘpZU{/+ MHԋc8/USNWOw<~15ݚW!|dXcHt(olUv5F~:B6\k\o9ᄁ,g ܋YlS8$ʷ [u`oڸ"7ElZL [&vjtGHCSS2]3Fi0~ƌs^]%j'{*&BK'=de W@؞CyYY+|X:i^(1+u< 1Ҕ宇ZNq~tfQ FN}iwE(bC-& uMcˮyfѴzKe3^.vg3 {]"ߘ &&g3nzDljЍQ񱨺8}Zd$QցQ(Q,2#4wGQhΑ>w?܅Ssc?Usw"7!2sB<}ז.:cEޖ1!~%/d*m(bO/Ut| dg R?, ,Zԅ\&%yԣ!eg\^lK)`3XpTk3!IAK= Fdt.:ЇN\t: .꣇Hn~Tht:-Fn ‘) JM+2$M3>5%uz&>46KA#waם,w[$!'BŜ; g@d!aIpC. ]B7{¼pݹ  nic[u}wYjv#^u.u"rRAqK3 @irdn6pYY~Mݹ  nah;oភw\܂:^n$!쮻عV' c@@Oem2/Lrm:"7sl bpD1Aq0` I93Y礌\d^z, jBؓfx vV$ g  ~JPA׋So{5 # No+-r o5ȫu}Ow׽3ǻ9+n~O2:Z&Uc 3%%ŘAysVwfIT7Tqb^ew|_ya>u\@3YR}Eխbf̊-Θ[]X9n߯lm}}*[Bkgt-"j* S_-yS+PYOy='OL}eNC]MNp[ꂋ+rSq[f90w &#pm ]}~e#Iv}l2ܴV=u|RPDpFl,z8e;K7~N/n?^ݵu9oY/UeW^x8jз6.hV~ec5+].T'?4u墦LR_--ZlME/߯,liiU<4VY-N=$k(sb5˒.Tu.zs\`B0:҈Iu۝%ogkʊrbS pQ\~4mؽuEѾKChDdIfOu+)Diw[08e' +v3y_Ӱ۔ ABz7^9[sNznܑD>1zQ3 \a.:&*[o6ΐ5Y_Pbj5,]tخ/]l+s ~2~S &PWW2SRXk JB\$. QNa/LztQڳP9y'1#][gm.AԱ@d\z@˙塑ѡ@̂#ֻ}|n. .b!.Ahrk@;=$Js /Mb<̀لH'ϙ^^mƫAB}1#;7w»v~JO/YT>i՜k5KjT_߸E3t{afvΊmե{67;wnas͛}@.d/S\,6G{quH!B'{ˈ7m~o͛7oڴ{|Xqd{A9jj#6mz+La3gpJtLظ^ƍyme޹ sC!LN1̚,>>lWrNr@DЀP@dZJjIg鋵*Z͵vݜ<2ij_m,mmJEeZBH!8$'qB9'1AȮ~>@}^r#eP]˖} rn <;sX79t~lٲ?5\N]x^: P @_h6N 򓦻ƪxFԁ;7A/{)bj0Tz÷3rI%u:x oh PS"(:Z0>\FL~RpJ3A%$R&8PK楨d ̊/Z>@J@5%~q);NW>%9ԬJ[h8Hc$k^o_mUYap5[A3c DTjW Om^>5?;ïp/rg bYi;Rɋ_ύ"eWn[G^}{tXkuԅ=3m+i=25-,н G}gO$>߰e N}qJK[͸9E+ L4P$N e:XEW _o^ S:M=1stRf'(\i(|D!&}Rnho3^S-͛׮ W4d rݡ 2ڪǺ'Y 2̗,!)wn(ÄY3S`Ĭ6[p[(;],zNM d f7:Y{%H!` h6}vgѪQ}zut&cT{ '\&LZ6"5V|"} ,|}uiaf0x?Suʷ=f}:-RpI`!J Hq<g\} {]3~+V@g{<[]"+eQԏC)x>Hx凕 R~NwoaIDAT.-ѺOg{dnVe{<]0-_Xi"0f#7.FTr~+iET> C>@o6k,/AM@t[+. yg' ;MƼkʶ9>)!Uкۋ7O"LԵ-Qր $@@Xdlm/AzCr="gI*:zE=zܥxoHzyrX}r! '7|/)7r7+_hW|AZgn-Y3tsc? |2=gK4W"yzkN:$X8:q EC>%n-"XYCxgo>Sò'G `Qj (sU9d:/~z| [ wZhw{nכȗ^̔]mSꦁ1w3B hH3WApuF=p e2ʓ7]C~ &XDDAFb0m]~,0,i7D"UwϏkS;}#b4l\nOݴG(%W),[79ðiWm߿~;NCm>U-d?*rhjL٤w0=޽hFN( Frov0ΩgbM~hc;VmدGo. @w7ɏ>Zj'"(yUTcYz]Լ9Tժ;[R.͵;?ߩUO]\ uE c\OCs!oqvzBet㿊)"93!2<@WZ [\?TU?T6C@T-X{ R /!Y$@oSР.?ikJ"!kX[@] 1Ą)X, '.%*^^ *0!Tg$o Md2w\mAj^B B'әO.ON|*kL˨T} XLxJ>\h )\Ȟ&~,p`)>:p@Sup)A=/[ܔ f}!apl mg? njY rה4kz `T>`61#2Ezƪi }3VbVC1cQM> #\B@=z^A_e5!gss}6wbǶ%~DxA6SԵx;@7p%Pr9 DwAZnݝvep GcE^Qy,tx 4|}C\K?pC§o.b)nDm̞A7ER%5h(h8G Ey[b7+iD7P`htˆ_u&x@Y 0-<` ">z݆eerM O]'_ߙ=ҟ3@;)/z_0e  >⫬ic H|C Pր d(. Ci{ &p0膲v&Z=Js-o!V`1u Oà*sg+ ʹw=+wfݶ EGqS?fՊO_!X0?[#=ԇfOfGէJO+ A[@-+TY٣Ybƍ>uW GP|Pr4~r}:}+Y28{xcZ=j[ qyǛׯ|`6eP/^r ˿ڸ(v}} r7dQA W+:͹tݍ&k cnog& ֗^Rٲ,1@m:Rz>gH5_[WzO /sŴCQ-EY=SLݵa\rn{7;zQrxUz 35@r擳vm×Ur_Zq_YWOPN[^Ro19W@x5S' >1CZZde+_;&Vx'*Fv "RP1;_;yAZ^}lx}a5 qa11/{  CHpDeLJHTxhWe/_:_ۿ4Y)X5".Oڦ헬jW3A`m4+s"$M׻^а6uO~aUQ"zSuWCڶtfS2̮cvqb%rA[\0S aNT-b-6tw[pM>R RL<;%(t`6e,|؋e\'zVX2PQ EW>2/s`1H.#r ^LS1@;G%q9Sx-P f؇ Nfu;V[6,O]evw%DOnxp*/_;tUWGs1}p+K Otl;["&um@_4ҷUpa= R>|ٳ-X4ßmt Rv,y{B/\ոdg>=qWoVaADZ $;V/HG}c#xAM U{JA=<7ُ?9CSrշ}y}ݦ3dqwW7߮K7wEb٠FM~nj~CQw U ᫮ޕE"x.} G/m:(6d1wkAv4 m=;I_o0 ʏhOv,_d.Aɓz'˕f'kN&lbbRڸ颵#'F ~hnSjE溶. \rƀ2[nO;٠0]sɆa@I, wǹ@5L %˵KvGqrYj,dٰ@d=;c] 뛮s9}2u 0a"B܊pF[L`RrrǺ+9zB5=a„#GTAOVF@QAr2vw$ 2qvL޶gN<3f̘ۢU|fl`utW{COﭿq;799d*++}abvTޯ#G6f\Rm#Ց *LQgق_DA!I@l˗/GDDΓio*VqbT Yfsv]<)5vM tvvEQ$IFDD\|f_OvqHmItjNK&#J;8q  C]`/jd2I tttLWZںãUv˼IKu~3Ɩ LZBSミR/kjqh4O.ț\z{N~@B끌9/kjʏ.O F]xoدw~{H$t_m/g$o~{Uݚ߾/W sΞa$I|NW9__H$[~]xGApfX,Db ~To!#YwF|=l;=dDy3w|q&# IA!JP( JI A??_Iyi^Pr0ev }dۦ˗N)/V,_Оp1]+kZQpϾZa%.,P`'@+waw&&IFQB{wo. (~]l7Epμ(:XL+m)j g"SDvaܮ+ѬI־v(vB 9>SVvpA=kdםid2€T, 8>|*:cӏ̸3hψ$ :|ǀ$  =RT(JRX,Hs;(,Q =>p$AQ"Âc"`soq=&{yN+I(HD Q_E.(JD ;TT_?ߟY !2[NsϒRHq!1s_q&[}jܜnHdN6~ r'|Z f$~dkl"N0Z?~L\*ŤdzAA$[@(J@xFߞx{B?_pȬYÆ6^WTD~L$)zQnW S(Fr Q|U>^"LJJ@ȞIK  CӽXRDc˟|YHI&&W{y &u+P(Bk>peCBwSV05AkG+FJnS3 1UmA ,^XRHxŔi8mH$^PY{S%o?tBP(G}Vk%B)rLw}  Cʚaݻ733\"{g_ljoo<4XvwApVI0EyX&#n * &{DZwXM&\.oIp0i8I$X3AW{QVa/66+iҥn],0LRlqMq  C0vM)Sr@p?Íol}R documentation:googleapps-ssoconfig.png [LemonLDAP::NG] />

documentation:googleapps-ssoconfig.png

googleapps-ssoconfig.png

googleapps-ssoconfig.png

Date:
2016/07/19 12:15
Filename:
googleapps-ssoconfig.png
Format:
PNG
Size:
66KB
Width:
704
Height:
509


Back to documentation:1.9:applications:googleapps

ha-apache.08f224bb5f50a37525ccf872af8b895e.png000066400000000000000000000624431325274564300420730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR^sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxw|TUgfR BoA 4, Qbk}WUׂXV]DR ){gs{gb3{cPJ)>HW@)j ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3 ^ 3W+jiD:e?+1GQMD7Ƙ#]UiJ%"@Hץ[1G"zUFD?+*agS4i qDpT Kna6[Œdž~`1VPUs҈$IFMxWL Nów7f`z1ʩjLWU 3pJۯusţB[}W+ՠ*8 giNܚ1qD3GO4x1U .@ӁH ^CjE#UUiGllU}u.*RU80Fa'4xUDUi *8x~vO*HWNKCcUMhdtTUN8ЮUa4xUXwZ2FeTEWX'תsQTUNWܪƽ$ Ua"˺w_U-I[xqy|~&1X-XÁ!8K8W5t߷w|&~}%$P~" NtѳBQszp{}%s}ALhh3$áj. ^UY,k-tMpg2|Y CVNʿʟ F^N3\WU5xUCw0=pst4bbpDG[@FGWN;1֤ i p<{73N2s /s KG6xUEy`|W 눉Abbp}3.K WV<EW]s璕GfV6O|TEUMDFWvJ |C phg`˖5UQ4xqz\`nD t9G !3.5g$ RAW; 45 .GoV#,;x%* Gl,k {f0GRAW3>BB7QQk"zZ l3.q4a֌l}]XvEiqđ `eV37!ޢ#!m?|>s^5*:&b}sv`YQhГk$4xN$*S+gSJU ^u4a=B84 |CC74Ncط/Y*:V]p@.x-*@7CQ-E!}UɵU Wc5g@@9VۏB`;|ak~"כ/p%4`r+0M~/ZU&"NI@}/"V8bc1~? `}hhB~N:ѵe󦰶Bū*:'׳[p]`Cz`jN=*:RR(c |ط(~?~?SnXPcWU ^u,XZ;T1l[32ձ#;u"sgBx˦))amzЗ::f\D$Z`@3 а h S^=h߾,w)[DzbF`=S#)U>Ue QiBnrn>jn{{]\\'O&55+`E!sF~{nnvbp89';'#G&7'}7#&333urcDDc1Ccc̖reDWUs(_b <>' IaBi("87oNnݘ0a]t"¸qHiQˆ sޙ3#Gq`W:AtL pCuz<$%'iǃvٻ7-mae\@4pq -^UV  u©D9CJb"B՟݆yK/qW}xMnE s7zxxP Y9zoo.|^vź5V x~Ue _=1k'~B" YxSyB~ZRRR9r$>(F@.g,qqﷱqkߏK!;!#"~ dgy&nbe~CDcVRrPKN~Ռ7>Mr?rdy,|9g ##V]!%4Dp 8rƓzr6뿇|ITB(*\ ml8̽dC 233ٗ{n 0"1c߿UeU(]_BtT|~^ `+g֯4xUYm&f (mҿuuD|~}J?>wZü?>= G[}3uRtU-iZ }^D68ߍtX-[;q8i`v-GC@!;-cTu[D X/L'ą; iVChK8$}Ù;|}֨jIWG~8{`V Iv^&qщ8 8ൻ\hۡ9 q)E'Z0} Ю!\!/c׍7cW"hrwvXNhAک1$OnlѮ:  zfk[6cC{ÍƘYzJU ^uLD$0hc,"QEi1{! Cd!1Id'uXrY9Xdn3)`x3W*. V;%hаl`1HeSpUNN@:N) 2.0Sc̾WV0UJ0qJ)fJ)f*DgN摨REWUoVUaRLW)LWU+"2XDDd_`"2[D";d])"'"򭈜wj; ^UmH21T2 | IvXuUt>^UcY~ȿ^6L fr17UnU*N>elcViu%\R`?V~U*v5 ~nPDb&0Ś `ho`?g!3 R-^Un?~ Y Y1 X3<J+uū"X\#"H :] "CDkȦIX'жcX}Wh("~*xUUq9,Q=-~] @DktBf1v?n}(U*LU)vn+OcLV]@s }=NcjTi*TiRJRJRJRJ LDޠǼVqئ$UV"MDE^"Cп Zmº ̌t%TdhsK8e^HHWDxE=$"1!ZDWQNa qHo{Z_٣2B5"rDx?C^B%uD^mDeuuE$w+9[D9'^ú,Z6}6!慼.saȲ-5c2y@rH,Qy G/uP&+6!e>^x;^yX]u'ǁ . Y> <5[ښ{β^Q3>"f!ˆ(Wj`Twڡxl}f~=+Kۋqn@3@ e>Ś{K6XZcHsމuד+9n}?Y%.{ۗ:vb>߮k@Rם}] [7`1+ #"uZwsP`1fjP` ͑5]f}Kycu;LD~{fy7gӁ"j²k4Z-E c 5p?Z'jW5o 2dcP`Wim/_V&ք6wHnڷ@׭Kw(ݒ۴j~ng?/ړkE{e_vWD:`}x'a-p?~h P`] Nn wcb "RwuN工/ne OV6'xO tE7>Fއ11f6@oV(#VǺ2U5N^, -G52a1fWHٿ_&-@ o pXū)~} Ky4VweZ.b?c" Cنu­>V>mw߁0.X?2/sJy19S5[ˉ XCX~mj c+P>Vإa}cYex+hbsƘ%lpⰆF'`MqX'2|]L? ʹ:Nj@\[?R !."'c&_?~sPD:cG_ \m4xk9b(cL@rE@1HErƺٹݠ* m*TiW)LW)LW)LW)LW)LW)L/VՂ1iueXYqՕV..)c9X+U%iWRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJRJ+P1i 4-cH>&=X+Q}+u4xU$?R=~X >R%Ү1t3zVj) ^iw:W1:4xUDV8t$UJWU dx>0O|4xUĹaq᪏RJWU7(uLĔ[ٵ^ 4RkiG܆uP]HG*5x{@BI2MHWI,;^ ժlNXHEU `մiuTW r[."rrrMƚz+z;WEWMA54keڴieTY0vXzy5SUGNNwuTo03`u=Ẕ@:t@r6xk 6l Xm+4xr\}LnںHק<95kj sN{Q+|z(U^5ۗWDaJ7Hס2:3)Ҫ3l.m -u>ራʟ3T@xewHWEU'{; ݛ"2,0|\JƘEd(p61LG"6wM{Ƙ"TYvm'ѝJCwNΛ7/ &Э[7VXYbbbHHHCuYDGG*{ڵkqݤ2zh+T5:xKpb͚5̘1n 2bĈB͚5/Uo~wMSf@ < _lo^^V"2 ~G`&hc~7L:~wÆ LZ|EШQ#f͚E~~G&LItt4Nx^~e\oʨ;n8¿r _=O?tݽ{7裏.m{KaՌ;c~@/bY]T9 t5)î4gHOc̦{{c~@z=HH=&Lm|xؿ?*ƍ~ 99,.2>O<ѣq\{|\q5j(OLL ˖-?u~ԨQ|GL0|=T/e֘ 1~PB˸Ƙ?B1?cn^UבͮoRS~zѪU}ݛuYdff_h"ow*[.\r cƌ!33=DBngΝnׯ+_~<3Ӈ7ި |'k2{2%~A:zeטKX(0NDi $"E`Ag!p%Pu?@ 8^!.nNޘ˺Ywc?SǛBR^Ob/ > 1X;{9^>ރ}a] 6xWEm"˵N4twaZŋ3|\.C-+Xx1:u/KjY:xz츸8N?tz!:vXg =j#"7cD$|j^ /B^G-9:oƘoՄWq0j#R)ΝʕG/eڵ ᮻN(vst::u*11"i:x,Yu84nܘƍعlh1#"az^5pRj ߇ l7vޏ']FkrPݻÇ6hٲ% O.v /l̙I'Tajuv֭d牡/X߃ˁ]@y;c.NlVW]u?pOˈfܹ۷BUpq1!m oZqJ;hي5j,i("'aUcjWwE㤡;GFInc[o1n8ׯϼyj]oޏY3EfZVӱc# m1S w\-c27Ɣ.B @}48nk<ժ^G@esKRE*kLgCb\`RH3`IDc21oqH'v"[[,*bsZ"2@(O0]  CQVHxZGqZ"RxU V8/-aӞ"e)sו`n4̇un;L ).7k%@;cL VPdygYTIJ&[BHN50ƜѮ,2:^ O-j簾Ÿl9@@X6!jEYT;PO/PvX7Ƽ;Tv.%M)+10."}kc9NIO/{" !ouo?x6pCDڄapŁ@7EX]&]RݔX-gDq'p 1f,|5& m@C]a<>9vNȺM|{X\&8&U1{DdV*"sX-XĦ{kdi5270k(]偺."끡m,v*1;X'bm5D$c̲GcZ0@7V*X_bT1|u5:tr_b]v֘ˀ߀Ƙ1>|`p5p~1&.+d ٿu cM ;Xc7aTUhW~!wg}Ӯq&2Ɯ[2Oas-0}ŏ$E>(B U" ք;[53[Q,Pn?j[Xp\Qҳu=]wO:9bkVC FڈQOZ|v?[3s˼b>{mܬNjZ ؾMiǸD9rPs8bJU3.pfԏ;6[b6Yn([QjED9+5#8~aD`|֏)U$ĹbP39bkg/@8laFHKHZ4elcblߦ4oP֑5O ^VbOazM`pP#1k 0k#zWz/14a˷IS^PZfxhVޭa]=LCXZx@N\,Év~jq 0_S%rp_SP,'KPN$6xhuf,4ҡ"F4:x!ЕмPWBY"R c_.W1ckZbɳ|SJYǰgú5{=(&+),ú6bAwߪ0 gHbp'1_`::!oUou^(*Ti*Ti*TE,x}Xf iiilْ:cĈ4mڔM?|8s%--c #GpiѳgO~wƎK׮]\?'Jkp8:t(cƌॗ^*СC?>}!%%4&MĎ;3f /Cmۖ;zc?ҴiSRRR8y'󵶟~iҘ2eJ}o߾޽{3`2228p 3snZΝ;Y`_|1}ewy̜93 ؽ{7W\qu!!!zN_̡CyGh֬w}7^xap\s cڴi,^Z\s ׯgʔ)$''ӧOVZ… ycٳgEIăߦE>:Tvf„ eСC >7x .[owy۷3iҤ|esssIJJbݺu`;v`\UVM6qҾ}{.]7l]p̟?z ^|E馛'䫯bĉ{:̙3'G6nȸqWE\>{`pe˖7nou|ֵk׎-[ꫯrm[Ojjjct҅˗}|ڮ]|֮] @ #..T/_Nnn.FK. xINNη 3gw}7`piQ*ԥ^-R}ѦMB-ZΝ;ݻwopZ2p\/q8ڶmlTTKE4lذзnό3sw0w\Əwߥo߾>ϑ=j[l/̌32eJsÆEϱ[5yfffm(v{ 11$$$9x`_N:UhaÆѸqcz-.֬Yïɓm+Ue˖lٲ"˼Kg#nݺ䐝\ Cw>K/p8tRDp|]tEs=̙3g*څ*P^>Yf_ }ۗ;v'vEMܱcQQQ(C3|WK.ԩSk|۹\.Ǝ֭[YlYQ~YT͒ɼyhݺue˖ѢE V^oے>m۶ nqJ,cعs'IIIϟ?_~޽{ĉ|$''3|p,YBFFqqq=GXU]`vbȑt֍;z\y@l:߲#GW_ѥKȅtPlذ 6УG_⼼?^z@1gn7_|q;/ dۼysN֯__%}-ZO.ۻw/7p 67n~+}ς 9rdnHvjTRks%!!ŋiӦ|YCzz:/.c̝;7%a4 IDATxc曃>~7LB׮]+4hg.46sՋ_5kWk7#υTz;vle.Dٳgsȑrɓeee1i$^o>Kcƌ]vL: oذ[n8n{Gqy睴oߞ~˗gArr2=POUE ތ >S4iRI8.B1Eٳ'~:'pBphٵ^[:_ҽ{`>,L4?3߾'L@FFAu`uV chr}˗zjN9O:jՊoV Xv-;wfذatԉŋs73bĈRK/DF=z4zbРAӇ۷[oѻwoCb޼y921q|/\.."222h֬YlEJNk׎o?WٳtJ=t 7\>}:=3O7oȑhѢȎm_0{l~'ǘ1c2d;v ?_Z֭1c˗/g͚5׍1޽EU5%,`^']Mjִ,֯oVn~{6-7, xD㌣ 20 y>(|>g> uѢE9Ҧ3QϪ dyUE+ UQEn;~޽{D+MGRRʕ+$%%1p*Ͽl2c޼ysúu8|0Æ cԨQ64hP>p.k42jU}eڵdddгgO{Çg͚5L l[Yq@ԢE '3Ş{»Jn~7еkWIKKTg7 550riVZpbdժUtԩڏFŞ]rK>x??~%!ꀊl((V;WIVZw RSSٴitou,33n߾sui>@xߑdҫZhҶ۽(FB_Umly_[ڀYbEr&Κ5غ.:0k,4M#..Ρ6l^̙bONyQV%}l`{׀hh%7pr]wѢEn9'**S@||Ui4ƙ7ۛիWȹ< mhWƹON{_c~*+MF<п98įs\RD3O]|ܹu]BBBn;i411A>s\"ȋOƆOVu&tJpȊ < oږJo9⁣kҵY w4-TŞ9 nGo-g.sqћ>0KU%G+2s|TVԥmуkUFoV"+j`"z|K]D$dJY2" 5d.-+}j@=7RVԣj`Y!FS'_n?6b' SDjl+UVhq@۝@2𢬨0wِl9@T5 lD[5 B+*QL-z\ඛ!kI~ yЃ타E>A杋q.j/^v0} ]VͲrUSVԻjB!jYQ721<_Kf9@j" 7eEmvckVdE }u50~w<^V&$ꍃJ;~8[%[])cٶv =ѾY>Wv`;ㆷD5אcơ?g@o2m2Jd.oʊ4+9bȊ:'vcu(yN/f߯y4(.0 y HE#5Qg JJվYQbqjGa촙;9^b&QʹYBpOOdE>!#džUɊg ˊz7^`7wq9SxH R/"G~T;Kl6̻K@"?N^O^qsT؆Xy0p`^-#NUi^s/+?Ч%@[.+Cq9gHӓ/޶ry3(RJ\{g]ٹ<5(  AVԭKT0\YQeEo6hqѷ 5 3s&zm;8`V }ˊ*+LYQ}%5}D=s%x{06z9B|$f {th %$k< F_Ph<3jv#Ɋ:XViEoX8d򁷀MF(i2JΕj&Tf2J=iQy'dE]n'>۬ˊȶHZo❬JJp@Æ^zmCS`rbX'›3YPgO '}>!dh2J_m&(šφ{pW5AV(s-vɊS2']B:N;W0]BoɳkT6/f ƥ[z_O^Xq^cÙjYe( 2,l^LF(= :0r4C1ӹ?"ӷS`v d@D.t c|owgX#; O>(WjOvdұ@gRyWg26wfnSoZ7a\CW+)@J?yaJP,tWEG |RQkkn0d&LVԎA]kd00c@(?%᙭y3c@(>_E5Qx+Ԧ^`8_<Gkv4Kн(=g)5mj'm~<<( ?ocX`OU3óa4oR N\H/Z8mLFi(}P& R(=>m:PC;48I ^5yZM|?}d~5 4 qV5&k{v$Q*5-&t7 TCZՎ{um=xgo ALfiܵTH|i>Ι |?rKTUvn gJ}M,u[FGuq\'|)NuŇY7jpE A)B }oϭL>;KVT 2Ԙ\B.7pvbE]+ rN[zDsJv'G |]yr=Bi j{ GIl!9xO~sU{nenCQb]E^xt [岷+;e yp5 bC5tmVBK[ݝHpW"#e/pzyJ^ gLyiQinѯs-^Md/h`N)a[h\po"?R4hxI2ӫAU@~Kõ~">1}~(7f2J/^o>r\ W_P߁. Л w45Pu-((d2QQQ/L~Hvm۶^{_|V>SռOI WEur/;[ȊꁾЍЮr_O fyQ\!ѵY-ms1fϞ`ߧ}?Y-zضmcǎu]Zցw۶mڵk6;wt am|%1ݙ4~bgԼ lBj@aDf\}J=H{@1;ؼy3 %%%lذ˗W<ԩSS\lÆ­͛ '**;vpBBBXPmjE9^Zͽ*8РD,D5PJⅱioYY~!&77͛7XGr!bbbM69Uul+WPQXqQQSiQRRBY@0@^ӡI%:t>Srss%..\;יgaa!XJh:G涁w˖- >XZhƍCFFC%,,;2{l~G'N`ҤIDDDBpp0={d6/**bŊ :6mкukFw}gsޒ~iiݺ5QQQ<l=Chh( 0ݻwWڼy3Æ cܸqHĺu3f f"%%~J.]7oM4FNXX[O>|6iϟ?ҥK0`!!!0ydݤ$//sҾ}{BCC߶I*/2QQQʰa\-G 7qqq:unpѣ1͛[ocgGvu?N߾}ٿ?#GdժUL8|}Y-i=z4+Vߟ+W2c >رcIKK*?Ν;?~<>(ˬX*SO=żypg &Nڵkk3gh:vHXXf߾}6O>MJJ > [)SuV&O̕+FݻEaҤI꫌9'Ohu3h ֬YCDD*cǎeϞ==SNYcҤI<qD,ܹ3۷~۸q#׵g>>>,7vq.~~~V&@>U^^^~~~:t3gZ::vh, ((( 䫺TUܹsx{{?Mo[^Mزe |ۤYd {aLj{3zYYYBii)&L˼9`}GX@ɱ{F۶m$Im۶\psn7ߠ( CW^DDDؼ̙`w8Oڻw/FΝ;Gjj*,^t>XEDDPRRb3t#F`2ڔѭ[7@[y3b>c)7㏹r qqqvO^K6CΜ9CZZ=755s1|pf̘a WΝ;sq ?NݻwҥK|6=L&@*^TsՈ#[Zv***Xt)ٖAFF|'OdـˢE駟ٳ^3^r%/_q;j|r6?--K~mՂ 7L۷o4Łh"#+WӟDII|ގ\XXh{!,YXOx'(++cѢE/ /iZz IX`Uȧ~+bd# .]bǎ2jԨJyzz2uT@o/w}7tܙ~ѣG[b7|IDAT_`DEE1bKdd${o߾Yuʕ+9rQQQ`4Yf wU;BBBXv- :>}ЫW/Hii)oNW:v[nu] fĈ{Y7mڔӧOӭ[7~:w̮]HLLd„ ^3f 999tؑ1cУGbbb|8-[1|<}UV%''; ݦAWܦVL:h ܢ 4EAcǎ"B0cƌ.B A\L^AWDAp1xA\L^AWDAp1xA\L^AWDAp1xA\L^AWDAp1xA\L^AWDAp1xA\L^AWDAp1OGfgg`.WEF͑{//.v \jٳgϑ$PBQcu]Fs u]}LbiUAn8uY INNԙL"  b"  .& +b"  .& +b" .IkdvIENDB`ha-apache.0fea6a13c52b4d4725368f24b045ca84.png000077700000000000000000000000001325274564300506552ha-apache.08f224bb5f50a37525ccf872af8b895e.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationha-apache.png000077700000000000000000000000001325274564300443722ha-apache.08f224bb5f50a37525ccf872af8b895e.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationha-apache.png_documentation_1.9_highavailability.html000066400000000000000000000117021325274564300453500ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:ha-apache.png [LemonLDAP::NG] />

documentation:ha-apache.png

ha-apache.png

ha-apache.png

Date:
2016/07/19 12:15
Filename:
ha-apache.png
Format:
PNG
Size:
25KB
Width:
350
Height:
400


Back to documentation:1.9:highavailability

ha-sessions-configuration.0c83c17dd25e266627b984b679e3a6e2.png000066400000000000000000000451651325274564300452340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR^csBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxwT.JPI"vE!j{NJ&veK@EOhFTDcDD(}FS`wy9.|w;wd2@8be @`B  + &@ (@`B  + ^  @T(E&p+**ꢲ T;k@BTTܨ\  hdeTTeꂃ QZ,oW@KKKD"eeeqU լYSS[<~DE[ ^, ;;8x@PP.CP YpaoW& &@ (@`B  + &@ (@`B  + &@ (@`B $9ս{}6O>{?FWWUbbb1X[[X,| ʇ7oRٳg`nn.?Ee$!xBFF ٳ`mmhhh999ܼy|ӧx:uжm[Wd=ʡCCCC 瀶6ڼzǏ8>|H^^^^^k׎6mPZ5e, ;ws5allRRRؾ}; .ؘcҭ[7%,PI2slll/o˗$''D"ɓ'Ӳe2!/E͛Yd =Aggg>}:~-cTT4^@|r(,,ۛ-[#ɸ~:C !C0p@JU|:*|&''3l0h֬:u*3޽{l޼={0{lZj%JsEFǏi޼9vvveH+ܺupoμyhРAZB6lߟW^1p@ǷwTZ3yd233| ={6:t~Y# ]NvvB *dW*aL?nnn A$ѤIlmmY~=R &ku "??,YBΝQx oߞ,YT*eȑ(E{VJ辭ݦMeʔ)KX111,]+%tJ~~>1jԨrV_{1qDjԨΎf͚Þ={]g֬Y4ne'\ӧO+2WႷx$A-]J nnn!HSv9rlĈ8::Rn]eRL4I٥ /^ӴiF٥WƍQSSc.o8qbiӦ5Uv2mڴ'Nd&$$]{mۖ4=RT GGGttt]_c.BBBQSSzEMM1!!!\g޽{quuU١[jjjvZeR*ŵ7o;IOOeԁFFFSe2'qYd"##aaa3G{K^^&&&.ヌ JvLLLd.aaab d5]dQ"hBXXD2L466.LUmmm]{b];5d2ULQwd2lJ~pА*U(֦Zj*W.xh芪xb RRRR011J*0GkWRÇs]eA*uhhh党K)s2x555iҤ fff.uuu|}}UfʼTmƁDAٽ{7۶m#55U弗.M6UĴSRxӨQ#vJ{~!O&%%E٥ gT^ƍcnnJѡyܺu[n!J^H$455e ---ILL$--ׯ_~b1Qj2<~XܹRʥRRRQ5R8b1ؐ)jhh`iiI5\rOT-%礥q}޼ySjTZ+++,,,PWW[s~WnݺU/f;w#*uAOOy '==TCX]] 133+1|~SJTBڵy9իW䐓CnnGCe133ReK_|ə3g~R^Vd27orm\\\hذBѷ@SSSLMMǤkwρ_GG +tؾM޿ 022Ȩ2LT񁨦&_{J[[[efeeǕ+W(,,,djaaamKm*L&PTʵk׸~:_.5"33w.Bd2񟓓C^^^״TPBC"---TʐxbYR&UE%Tʭ[su֥Ahkk+DOvJߊ\p5]@oPvm8|Y\NDWK{H&Muuu6lH:uT:l [AI||<.\(yE"ϡJec*.,,lL&[Ԥ^zԯ_Ba[hjjK:uJy///xU̕+W|A~_r2ۏM6exzz W@MM OOO @ӦM}B._K3z8iii.L-^ǡCضu9,ȣ'|_&L8 gg2ٷ>-Z~ܸqCD"dF(ZVVah"7A1nUߨpJ_xٽ{;^u+*:M1Q>6paLZflhhHv_?D888R]xENAm!dHuD]I/;2nd\:Q{.۶mcS5NЀvz1 QYP+6lI,#N<)L/ѸqcrUn.2 WN6Ƣ+j:!,X)`_ ct@99u)2JC*Ƙ`;~÷N?kM>_p2̌S^^-[s*ŔuHR9 =5czI҅f̜RD.dǾ%~Je &?,F^ʽ{2JE ^Exٿ?z~N KIgbHMTcfA- SM}w[&:j|5ԞhgAӻ eZӧO_D6"|'?<ӧ*5(x5[6os~a9apLEW珋o Y: nQ3UdZ8y5fԨO.+TD"rssL֮]MŮe\k~ŗ^]m/(~=&MO! g: |@~~ɉltuz=5w|}}0Φg?zDN2ѧH$ꕓJ|H$*/OH}8>ZvGqZx2[j} ˗wm\ESqƏ'SǃI&}2>U>ހf͚իWqss+}mz=J KoۻWnVn#N&=9}),JusTNyW_0d0,7,,R"H,󝪦DL&[CCC?ڵ+۷ueGzUm]Dž ˚_l0dfS 0gu3R,|n2:ԩSJ\;*T𚛛j*̴ihذamٳg̜9_?+6%36mOįOO`E"_v*qPq`™%? e9K&덌mЫW_,-K._l ^*l>;L&/ɆhD}ưaXp!j*m߽{3øy&\عvs7}ar,6He@`ϗa{*ɢw7k^b9lܞ;Jy򬐞=[P -Z`| 2RٮCGCnq 3Y0gdqMfǨ=DL]bۙ/ޜŠ5weʘZoeΫ@,\P Q ۡ H$(o߾TR.1bDkV/g4 *iz瀓>3142?_ʦi_Y2pd2.ћShmE46\ :O[~Otԉd\*wժUc&rf-:A^~!uߖ3矱{muz3_3gm:X0OZb{I)Y:;hײ*yn2Yy>"x@ QUc+((ssszA޽ڵkܽXn]|53KhhUC[}}_|!{MN} j]qKW3Yudг6wIr "Sp5c~͠ADB ^(t>v'Ok׮Ӿ}{sqF Ʉa^]xFlSZa:Ew's g81Wu.hEUmd2C~w7_/s΄ЮA?رcٳgFqz{we9\r铜ǝ\)c[89TbgjjEW^)DOW [k]f}7@TƁ3Xy׌lϒnh/e,Lɳ|^eI2$*|eȈ(vɱcԩQQQܽ{_mG^=}*m[3Ƌa8{6&ߖ~~ns౗hoƝ 1J\{#Zxྶ]mKb|34e͸qctT|c,\UVѽ{w֯_Çնz鏳S bt\#`7EGٴ#5R7&BVD(sfX?[nǵa5/1s-jm{ҘJ\9ٔ[vQՉ9s>%E΄ʙ3g#..H^zEթT7py>x@n;2vΌK5e{cJg_;yRۘ_z;%^]ߟ8 &>>X%^^^xyyɓ'ȑ#̟?Tu_OO]]]rrrx!.3ƍI[ZԲgwwHI}dzVgz,\;뷥~SH/9SSl?Ģ;\Ip_[3- e!EQy!eFS>F4jԈF{ )))dff ^|Iff&^BSSgggj֬OqקxL->[;du=mW[p%FZi! [Utdpw*kmܞʢ0bXmks >]lْ-XJ/--sdeeajj+>#'2h&4aXKW'Ɯn'̅K/pA%urrDoNaI5ؽm-1 X*0aĨ~ә 7`bbџ6mNbPnܸY`]M{}Dƣ\^eп RIit 8kSqqMlRe&:T,VVVkjٳgsf1n X0L w;T ׹%D'Qېڴi^4Ž,[}Ri֬1P~~eN2ФIx^z<&Gö5Ņǹ>sk5)꿽ŒUlw@.&С۷K. KF$}J,i`e"Sf^\>]:w9"8z1{tHa>^߳`͛i;6nWFT*6p 4䳓}¢paO"HĬY‚UVrR<_|VJ >š>UMowOgQd:K6|4K#Ɵ:!xPժUY`ZZZ|"=LǠ;UPƦi,O^.#F#fS7VP.899xbh5j`\|Z73#uKWeT,,l1::|2#Tѣys.Ŀy+R5dfFr.}BWP.M2Gi86dGnK'8sɖupū0bѣGE/[ t '''%W'=&L@XXTZ=zPZ5%WXB*%sQv8;;2oFFO<wwwt^ܹshB%P{o3k.i\m A__+++^۶mzDΝy)̟?Yfaii! ~2TL6L쬬,F̙3s'O$)) uuuy?SYqrr">>^]Rݸq3fk._Ι3gy&ׯ_g޼ye6mڰ2ǧܶxܹC^^͛7ݯR 3f%~:tHD`` ͚5?wymׯ`hjj~ϟk.&NX,m۶qi*UDΝZǏחM6K:u$A&uVN8>-Z @ow tttq}!%%EL&c|#gϞsꯞKMM_~adgg~z.^HժUٳr||={6C莙ݻj*.\@`` ԩSӿ>9s(,,L:jժsټy3P1sLz ;v̛7pj׮)fƍe i۶-dddE뮍7Ns ",, 222hݺ5`ceeEZ={6}X(ZNm۶,_ZjqM6mʉ'o9r$UT!==-Z@HH[nAhhhȩS&r>|PV\Ɍ3Y&AAAL4 mmmX`ׯCB޽9w5j`hhhϱc>N:޽{/iooϤIҥ P4ҥK5ܺu#GOll,AAA5 wwwMP8z(aaaL>'N׮]9r$bXv`㬒 IDATٳg工7ЦMbcciذ!&L@$ѠAykCϽmӦM\zdygL43gEϝ;3R'''N8=Nb„ (QZmBQҥKILLСCtؑhd\xB?ĉ8q"'O$++۷oӮ];>|3Ç'99A|]t ++Ibb"P4Tؐ @Ϟ=Yl>>>QZ5Zn]o%&&w{n|||1cC  ..֯_/?_۷y]tرc1~x />._}0f5kFDDxyyVm>|/ӦMȑ#sΎիSzu;w.ԩSȠgϞ4oޜ>wZ|wnIԐd|;wΝ;s4i·~[fزe-Z`lذ]vɧtӓիWۛsE_pÇs5:t =+W.qwYb4iGϏ-[P^=֬YSzo}%SNٳg%/RR%ÃÇ3|px1FFF;vCѷo_vŋr |m\z;r8::~u|w| &p)"""_IIIaƌ4Ԕ#APPsN^ٳg"==ϽF;v|cG0g ŋnݺR{vk׎e˖ѨQ#XVZaoo&Mbĉ7`gg.+W{@{E[[777Y&7T-ZDp[ŋ0sLTO?pcz͛i޼9=zƆD.]J߾}!(([[[ رcd3ݻˏ"""Q{ >Wl\3|pBaaa} ܹsѣG3ydR)gΜmsa"""eƍT^}W_n۶m̞=piݺ5uʕ8p 0OT\=yƍ#QSS̘1SjUFɰaÀ~ VZѣf6Qi}e |;-;;SdFEEUVv=.88_CCiKOOg…J-m@ *!x@W L^@ P0!x@W L^@ P0!x@W L^@ P0!x@W L^@ P0!x@k͛S ={R!JviiiaffpttV٥4!x$99Gr!mmm455@[[mmm^zǏ'//>|H^^^^^k׎6mPZ5e,oq?3?/^SSS9.?={FzzhjjC`` -ZH/K d2~Gϟϝ;wښ/CC/_Ltt4&OL˖-ˠz+,,d۶m,ZgϞamm  @OOoӧ1co~ePBoAA˗/'**Biٲ%b600wwwd2ׯ_gȐ!0dV) { saӦMM5v֭KAA/_cǎ0zh>s+nTW~EFǏi޼9vvveH+ܺupoμyhР"L+|ÇQWW'((ss2ُ:ӓW2f{qpp(} 7ٳgӡC ׯ_9::2` ڵ+dgg+dA\+틓ݻw/} 8$j*T7??,YBΝQx oߞ,YT*eȑ(Aœ7|ٳ'&&& AOO/g2}t ӧ EY*TİtRڷo}+DFFǨQs߲@!2pm_|},\}}}wzœ̚5ƍL'\ӧO+A9gV^M`` .f͚a``… qㆲQ #Fёu*055eҤI._H$ʮAQ>ZPPرc/Q CyDRBoBB<_٥W۶mIKKѣ.H$dZ(B"H$.سg:::xxx(wѱcGN8AjjQ {Uenڵk]"HjH$#olQH$Da޽+'.E!*ŵ<_H###233]I$+`*0 r|tH$kJ烤R߶knn^aWVL&RJ.ヌJ.bH",!6uޓDCCj:2SJ*!ɔ]BQR Rx/mmm];rssTfffрp_5133(wTV mmme^*P*Lx{{2J 555e"ٳgٸq#Ts\D"_Κ5k8{,.IN,r7*㣲`BʨKӦM˗.mmm5jf@+ \t VVrssŋxyyGllJ hܸʶˊѡyܺu[n)_U$aggJ>R)W^ٳdee)r%;;'Np|||pssSlْ޽~U555prrR .x+3666$&&r}ihh`iiI5\rc!J}WjrH$JsJ"J[EUx}111bmmMll*+B*L&,]̗TOn]iЗ +/_ƍӧާ=¨%322RQ VPPMX.\Ṭ \\]Y~y,18vSB*ہ&fެɴkÿz˖.OΌ=mL3>޽{ŋZj U0CCCǫlo.Gmv> s 5D]]+Wo23g*R!9pϞL37#WQ~k۪)::Z-VpZW;ה͛7S#6,Ư)uT64hkiDEo&..///%U_zUƼYcQ.\ NmvPbb\]]JJՠ,%ט-Ф7)3cR *1vwz2g)Wae%[j̟=ΟL ?ڵgRoM]] 8[6nʔ uYU|H$R[=.y W2vD$bdd"r炐H$i$p62!=pH$$M}X,V0nMNeXȷl=aհ.V~您}]v}nZ%n,1#xz~<3׮"j4a/VE"dBDjH$wd.0+{o%IzĉQ_x׃p5wm_E?  Xu''TL.c1l0.\2w ]`S}YUK5PA$!L0H/x:I$ceע@$SeOpY ŋZK}UAe44PSBQ.2i!bV^}RJBBB SεX4'dkXF"jJj+b[k>}:fff 8E>КuPA,?t] ^ٶmv+/Z$#VtՊ6HD(ȑ#Y`!!!:tH$X 5u-ԵՊZ",P ?˗ѣOVvI2ddҊ{*P vͮ]8p W\QvI Emvvv޽cǎ1yd/_NvhӦ )JV2i!2i҂\ddRd(''N'44cccڵkG-kQCs :4i9B b͛7ܹsűuVC͚5qwwggO>>}q\Ǝ_LVB {AZOaAfI.//Y Dtޝnݺq nJ.]pww'''\\\02dddp .^H|yLL:'zzzLVtK (,-.‚\rټaА% ż"<<'Oϑ#G?>Rkkk%''ׯ_EVV^"==7oI;w..$^_v"dxLZ0iARQHLāCYr+ڄ͡}JxX,Yf4k֌7opQ.^Ȟ={H$QjU*UT&;;s -hHs:uhذ!AAAru|܇7eH"*#I/`ˎX;;{֬]Oƍ:!xߢI˖-iٲ4k05t&۷/7KWW@%''C_|ysss 044rȗr۠AHHH`9RYBur پ8+޾kѨQ#eMޏz Yd Ə._~cȅhh +P qӦpN9Ū QkcUW*kX44&-?bYӑUve 'S|YslKh\b=K{\IˉĬuU**WWW6lܨ2Jh  + ] 999r=۷/={p!077 R? B;v6m(jx%N"66gϞI޽KL_Ygi֬s **zQ~}Ԣ*} <4غu+uڵk7nЯ_?pppرc\zTOӪU+:u/*jx!~~~L0Xd <|TqAƏ%666۷R?w^W۷o+EQonx uO?ÇɓyIIIdD"}濕FVV.]ZkQ;wn AL<;wmǷWطojQj޽{'OYb@555VZСC>}₟k׮Ç #22___Xt|ӧM9wSNe˖-_z5-[ۛUV1bΝ;@pp0G)Qs@@yyydgg;SSL>ٙ 2}t HHH`>*E:s w%**ĐB֭[Gǎ叝?Ν;DV8pkө[.M4aZիWD@@iii 8_~(ZsΜ94j???;o "!7bcc+_'%%ѭ[7֮]'+W`ʹkGGG4iBdd$G!&&+Wo5jI&Mpqq_~:z޽;...tڕRk ϣ;9;;ӣG444$00֮]Kݙ>}V\ɱcǘ5k bڴi?~ һwo ;w....\v֯_ܹsիsaӦMl޼gϊp򺲲8}|ӧO3o<J p]vs۷#Gxbvڅzbll͛7{.gȑjՊM6annN˖-_ \!CY&ӳgO՚gX?5k-' 6;{.:tښ͛7Ӽys|MÇYlZb$''HvgܹǏH$l߾3fM/ׯ8}4/_•ѣlܸaƎ;7nÇg߾}tԉ &Ozh۶K.˙?>z"::7oТE ^z@\\f^xС?Ju5ܹssss+Vܜ%^^=?>:t… ח+Vpu@OO㮠H"""l _C~~>xyypY˖-ԯ_ qss{g[ 6`j׮-6m/QF}R-^GGG>͛7iҤ :::ڶmKffß/711!77&11MkРu899Ņʕ+3vXڴi- ||[n{wԶqK  TP:@h!탢"hcфh4N!ƨQQCDOOJBPL>{:@;λn񴴴|sBQQǏŋ |>0 Sn AAA~}H)5/O-l6uVʘL3m|R}_GݻwqFF޽{y)fm`0mS;XOԾ[q:noNDD8޽I\vq$%%QXX6W50 PҥK̝;-[BKK ?V=P}JII {KJJ"66e˖zѣ %>>+Wۆb!&&FZ-,,oZ[\|UVnj`0|s\_~ <د.555|8<6l؀l&99h1bPtYVw?~dƍlDEEaX|$$$j%&&(233իWdddczƪ V`ȿݧOڧ}t\{_\AAiӦ0L,^޾}Khh(F.] ͘17aXZsEQHLLT )ɓgddЫW/&Mի9u;wDp80<D6oތdR޽{nbPZZ``Ϟ=$&&ҫW/ƍǘ1cӧL:l6dѤS[[K]]K.eӦMN@QLo(d *߿֭[5.D@ @-v2,YңOoΰaXbհpBf=ISS^o׬؃@Ճhʕ,X@=z=z4`W$UUUp B3'--BBBԋsDH~Chh_4!įd2u=L~uj:B$xBcB1 ^!ИBhLW!4&+BI !$xBcB1 ^!ИBhLW!4&+BI !ݻw t /-~̍70`@WWC@mm@@fYYeee,Vtvuzb !8lyw= ,;z?uuez=" [BB!:&kB1 ^!ИBhLW!4&+BI !$xBcB1 ^!P`s!IENDB`ha-sessions-configuration.0fea6a13c52b4d4725368f24b045ca84.png000077700000000000000000000000001325274564300573062ha-sessions-configuration.0c83c17dd25e266627b984b679e3a6e2.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationha-sessions-configuration.png000077700000000000000000000000001325274564300530232ha-sessions-configuration.0c83c17dd25e266627b984b679e3a6e2.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationha-sessions-configuration.png_documentation_1.9_highavailability.html000066400000000000000000000121021325274564300506350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:ha-sessions-configuration.png [LemonLDAP::NG] />

documentation:ha-sessions-configuration.png

ha-sessions-configuration.png

ha-sessions-configuration.png

Date:
2016/07/19 12:14
Filename:
ha-sessions-configuration.png
Format:
PNG
Size:
19KB
Width:
350
Height:
200


Back to documentation:1.9:highavailability

lasso.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000311531325274564300412720ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRB=f22IDATxy]uϩ[ B"@f18،vL3!$*bl#`0/,{ݥۯZHQ^թsOs.-g`aily-E]"Д3Y{ۮؖ 8[Dv==8`7п ޞv?m:kBLD5?aqbD7Ot=9-dZht_a lÉ>"zcC=]ò;Z8@pD55)DdEGАc4^`DwSDQUhٍ7::CFoєATEz" ٔ!NmRJ)0 0 Ra!80 s^݄ 5x"YwO!aA)ia-QcaI)HKt q"ĿGDc{XTQ=2ƢcFȄ i`QCrO7)eAxm۝---B = f EA #]CU }=3MS3g0;vٳg144AݽA <*VI---m1<󃃃B 3?j-8Z"ro/ُ*jŅւ.71M3 Z8g+cCR.1Ƹ༽\嶴h5eȕj eT-ôiMnl0T*AӒ!BfD ˃p!0xU_z8{l-M}~Ch C n"6gv4Ύ($ |>#q@))C .=gլYĮ*=` s8sCb~K#p"Q jZ.s==mm5 QGۆw‚ sBs7J\w㺺iQ3m{q% "j)DFh9  *_s n2UPC0t3PIWUEIPoKmαpB۶9Z\pƘQ)\5~wMP>m4>E °e8mܱ"aPxMZ Do{RnVknDԂ*JX8PjyJU YWUU%-vuܕM*OnW)͆Ʉ@CP-.nޖIiu}ApueU*B{0Kv  r 8)B@JC $!G 1_{+<[s)T*b;WQ 1NP)%ByBJ9tr!ti3Z ݩ/p*Ea(jP.xǹ_lJ)Wiї>pfѫTBPbQLoFW?xu~"O)a8 CV)YJ.lHyW.''0B%e@JE"n:g%Ń XOm\=PITU}o&O }pɑ3BΘ G*O1} ;a-))A^EU*(w{=D&j|!WݰsM%8B$Iqל~тۮRɔHةfϟLN`Çh!nFe7NV*h{ hY°6& " 0 PժWR(R'#@@J"` 1|o"gI"$(ňŅeܲ4 mHZ $Fw"B!@D 1+W^zV**(X(S"%\|mqׇ?ydaB"_-{oRJR$ň!@KuE;88XLj dOՊ*n,0 {Gj 2#~}kճgvEJ*$ P@*㼍s1rT+@0 ߣJY rgڲ 9g?Y 0 @]?n;oY 5FKcQ_l0d@C@׆J41qB[.mIg4 '(8S=y [#dW;i$ %1Xs`bJ>oF$7Joޮ\IYnѱs` 6/"#--.2P$ 5qovfJ_)M b+ߓOlUݺ7)?2DƁy`\y?!""(@E\`x̵]͠u^1>R E2Ta@'3}Ңl0-8*eU-Wa J 򡤓Aې}}rP*T$UWUϻ:r?T Ȁ!UUU>8s\-t^y1BH+28|Uv(Ava^9L㲟9T)˜eqխm ñ:RJC CR^@VA$P9ir\OJ]%2 DIaABRY-\ RDHRdPkgbn62 T\ 28Rz0JDD&BBPRQ0d1}'ZH)!RAҶuMra#>Um = b!=٥1HPa RjM3K9$HJ |WTל0\.ySFNpf¨n@^+%pU)OŇp |W C($ 'ZF .sϵvdaH?{Ǫ'zfލfiJE1/f 0c1!~{x<$'qe,"PDJRa( D@I!UR*F*b;N0'$KX"iHD+of:ásާ@I"kk:AI\jisSM17i1L7m+!_"7#Q$+!$z!L5 #zaチؖTV$ٵŴHf4-a4wm&喥aE)E*ҋ5[v}S޴ߒ0 B$c GJ[2-#E@C.XM1Ljrl1VIߕ) z(, tH m9M7&LKfTMv%Ȑ|fNvq9XG]f:̰0PLhX̲0eNYݻU:ؑ\.C"U뾺#t˜0ii PR,_ˮ)'GV?s>eP0k=7aJ:<繥?-X+d2ȶܵ{c+6 }B 0\ 7r᢫] 0}k ]:X>T=$ti3fy-,:ؘ!( X}iY gLBOa@i`ew! @0D=mpάMRP-b9h̴P9Q4hc YaFE>,UvyÏ1egj5]Wal2&DcD2 BZx nFTY}{DRFRe}u]Sww՟=)ܿ/7,Kxމ͆Fikz9Gaaia_OGQ()Z)e;yog?hXhI7`  0Ba1byMԚ̤$4CR>2,0L2 cd7mJ?e:D& 1 @D];sSvRU3 /'yj!RJay^\E(T9,JP(bR.{vnL4\WAL2Tٱw7:+w|tN';6*fu@8 M.]7{PLђi[4s 凮xi+XV9Hn8if9vD TveebVIcfU)"z]C ?]R`|{rF|#.>yy:̾oѷqעfd[o>΋/lY@6LD^q`Z6: d~d?:0д2W~?|aA4}DLk߹^NyXB4Y2LBUkqGg#%QE795Y冤D-7Ēߞ0Cٳ5.6 s=k Vwvmt4_]1 Y$`J+ۿqxS'-hJ;[^?䢢pĚwu" &Z.ӑKѫ6 {͜tFǒvLq!":N9hH23VͮU߂j1Y!=WRJ"oKv.FH~"0JahYk.^T;^r9ܸvf;+]6eOjDr`m*je-[:sfu.w-a;mQg=#cĄs4-=2vޱJ"Dʚ5 xKL9ɏ|sDbo_?R\ugWN^uc{fNT*j3%w:VoBp$YsSkyX(|ܥA}8F=Kx#|tHXy 9amv~xEyPBz̮v'M8j>:EJ !)My^7GRW9.KygZEMdEM[E23L{HP'gR)Cm5N gPSfo\ش{l%R<Vi6 Y&iӾާz[C&jҼWuretI+HU@-$S- g_ZKtlp2Fxi3+aYeYz_8²q߻M+. _ٷ?uτsҁ{UVۺfM^tܿ¶&y"] 8m EtM!S"RГ2PMp)_U^9qŬfMZ4bi37THjpe;>G6tt-jaFXm8-N2$EO)Rt*H9S?i};i$ޏX9 eY&d2H$j9˜q0eZdrK+8$`YdetItޭ8z6VjY czWk)Wp7ŝ&L|B ]Rj J6'$]6|gNVsh8qߌ ϭ?r`k٬$0 B-aD=kG{ L,gJvi:kݶ[NeYuwDZ8ߗv^yjR eWwr';Wʐ@a`}rnN eX5'e KUWQ52 E @!jwׁI܅Q-XtC.006 &_偯€A#e啍w,{/DgǼݫ(uj DWO6oMqȼSYqM $یBRA798r@ @Z'̕]!_*|!c 4L0-4-y8gM1.u|=1d~UUJZۥVE:0pe88J9z7~GmU̻² <#=#{M;6Al֌z2ln#>z7g8Ze*ZV^Ey"s~绿[k SOfG08s˒/K}:,OZQ^yS?G8 [X>9.M63m_yQc˻o`r*EY.RA`oY nZ]nîiݓ\l cA539gr{6Aim?|hw1 Gl\*[wNHS}j| !@I^s.U>zt>ϙ5kϙ>i'Hf) 5ܔ5-˾;zw>PU I۳JfP#f[D8Y.;nͳ_{Mu.վp7=oLtZ:cAZ _c^M<62!r6Ֆ8@}B12A sǿ "rƄap&]q/0]:y0Zj±^b2 HRT"O (=!idhtkvMh\ԔJ1arΑa  - a5sI09t.n뾶{,3, !k*Zx&B`.zFAϬ$fu<4Mp]^[7q.T@ՃՌ`Yu߱rQwwn mժ=P'Y-eXbt1cx<4(' cdm*|O+k[憉L #+ %W*ҩ4 !8M~ϖٯRI b r]C.%Wv}yp>0)~='fI'1.[0 040S- MӴ,"¶w'fBM a8H|o监[7iZw.\)W!Pp\/<^DªgiR 2w ϼ|CC#.sk\CSQw߱7?>']J&0a 3+4P* &Cu]]cY.7 )P+ot[Vr0um'pFEiWrwW-e3Lũ ϭ{݉4w5"PW㭎n}゛i۶H`"iຐJqǶAsq $w H2س_.\Ӹe S /~Ϣ%P@D#y`AZTF}wWpå^ڤ*2 Ü$ `&[0O''[d\y{}_5˭i榸rӊg!%i[e^sEqŊMO- ,( gY`3:/Ȧ:-Wzv^]Xo8y')$i0G`旎ɁG$ꪫD6"9aW9O/j֔ٙRz}C~dp}NI$2ºw,iGqݕV.nɲ7;E=ga:cRNC6 ĽܜC6 8],H& _ri{z{O}SPS`uY: --u?J}DVGd7zOޗQ?a{{>)]}}LJS'/5Mbs0 謣M~w鷧'YR}r=R駟d2bd51a)I!)IJA`$ =aKKmykG {/~ۃs3b`$YO ZĴiYStO}a}wuWVߺ%øM=?qcc6nO|kB'5׫{2l/zqtAgQMk֬[zwuρnyy?ݶQ~hז׾C0 T{}M~y`7Hl1tVW7l;whOb̴S j &8r,bjQZr\P ĿmἬ /rޟisݡayTTK%.D]~/M>@O_(םzR%>ԓݘNP*8> \@Wjꑽùag>b-`?޹֫eM26M1t,B#mf -/5uvYy|k_hFS­eȫU (W_s\0 sI)貜w-awCPb˖\svcl``1ALQpèaJ0_f3)L2&Z3^*XFLf{s/ZS} .<+[54497U@LɸPs :6qw壹M/D+V\uw$!Ƙaؕ*!Jq& ٙ`Hgu O|!Os.]v>l6LWyJIpr1r_S~ rs_xG>sTw B$g8֧$5=gq@ LmMUJKs~8C  `8a(eni 7GVssN030@.Md"0Lt]lf}1]{vUy̲C8= ,+:Ac@ݷJQ 3R/ hFn"1TcRr\X,=Zw).LpfY~Nd2dq{Jr\>/ ŽPa3HW;E0z+f}1ضm[L04CCz^p 1Z4,ADTHz8] ʺ"d5MhtpSdqr4b_)ziY,rݣ7;O4DC3f$E>C mg48<^њCtz4|BۣzpL̈ 4!:e0CuD &c0āk{NYƍ+ 6_?C}7ECOI< 8eh"ʣ t-^KsSS7N8dXN!kDvY5Ζr=?[Ζ3TZZu>pXIENDB`lasso.862d375d38581a4d63a2f9a727116721.png000077700000000000000000000000001325274564300471162lasso.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationlasso.png000077700000000000000000000000001325274564300431112lasso.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationlasso.png_documentation_1.9_samlservice.html000066400000000000000000000116071325274564300436710ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:lasso.png [LemonLDAP::NG] />

documentation:lasso.png

lasso.png

lasso.png

Date:
2016/07/19 12:15
Filename:
lasso.png
Format:
PNG
Size:
13KB
Width:
233
Height:
66


Back to documentation:1.9:samlservice

lemonldap-ng-docker.png000066400000000000000000000474001325274564300375120ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR nbKGD pHYs|4ktIME  *!b IDATxy|W}>sx%8V0mƴj<--}.BۘC[>ADA$2kbbٽɋv;s3sWIJWzݗYΜ9|w!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!D!B!Xtui@}]Q+-!B%í jWDԌ!B!B!B,Ax'#D!"B!j ݳ_D` !B,QVǪh;(k6U !"B!B!B !B!B!BD!B!$@B! B!B!"B!B!BH!B!B!bҢ!B!Xdl l_[reuBH!B4!]={f7|8Pu">}tJ!B,:ر w͜>G'ݳ=;UD!B4#,s@gWwNYC} 4B}5_P̡^g>}'}ͮsclzlpu~+>b]=;zu.!ε!K5b3|9eg^+O<تuL0նUĖ+0x#dlȣLl0v#@u.JSE^<{h!_=Vp0H=$]=zmJ+5b)t|t|<,`uiZ?; #ܺ4[Z*[X׫?;w䅈!٥UD!Bٲ'eLk2Nwqk>jԅB!Z?n}EOaBl #~cZKH!B1Svƿ ]L2:!_/Z ABD!B kJfd1!ۀ}: !B!K^uS>^k2D{vj$@B!CWo3mܫ~]$W, !B!j&/@/=. 5 B!BBz]I1|ov ""B!D-TNW1tu>tDV !B!usܥB!fL۲imߺ| UfO@!bn_/8}~Guik70џ`K˜:hԱtnAO~3ӟ~,; uIB1tunYpme1%Ț dDgo`by[Uץcu@_oTvj"${U;,.2tibibEWv6Ywܶ$C81WMkt8d!`~ԓ|.B,94BfeײCoE*ݔ}=p=$2A0 LBdes'AN&Dđ\jE_5/-W̸1p̽N0z ,L\|e>.#vCui~C>okQڇJKvѢc7tW\&}l+C*'s8I]j[90~}3Sm˝t,;{'agMs/kXDN HA dp ^xcA<`O!1& 3`a1BL<2πF04xaXn6%1&6C<1 X/]_򒗴c|`c04\d2cya0 `<3ab1\3x1ۡF;iOz 9\&쳗IϪlIUO[ѱ X֠pΖ~ܶsd|UT9zٶĺSwpċqV|~F߹ڍyľ0,.-@X!N_EԺbR|_ ץ):s!؋d/qC7s2]9+o|^Rd&XX$8lygwHdn7: ,.D |`yD""C) 0x&3*^f0aZ h['^0iHD/4D1 0xVTxq[dˌ1^+4x?G|d}#Z2Ƙrw;cL& Ce-Ƙ灻Jk`F^ =`l>EF+: e`?nqR+M@_o` C?zW?e%%tAw]~=+ӀIB"lx kȚXQaLA$00ZSB`V1aEG7Y6k2!_\r9[F(XDvfƧuN{…PhJLͻ-EENvy|!/u2V<7WIh[[anWiY<"Z;2aqJG$G,rw/y^^|8.[f/} It\aƻ{#r{L?ԕB̃ ]}!ΌM($'>yrfʻvX8~ɾZ2%"opEE,RVIX>l;+>cnBd!l&QZx(ubf5N%a!C:CL>x saȳgɅ䛟Nݵ.hNBk\2ʍ(LM:b'K K1 <7N)t$,)w*Z7☎x+Bl ˇkyq,.qbK.VC!Ĝc {/0YB$xj 2c\~o:-} B3ەu: 0|$T 8ݮX6T+>ȧ- tW$'n&8q#4Z,\KJQH*NJtA}\yq$G!ϊDÌgD}x}㶁}K(]4'A`8pA>]U|˭h)+y^޺[*l`:@p2e%DEZP tOT{ƺoũ~3mkD !{;i-ˋӓu=_rc>bg)^ є-84Y0B[pbXD""^^||̇4XaHXbqF^Tٮ\27 o:i/Q7 >U9} R}5!n8z;-MLNU|l: [e7.D1(CD4)]ܣbkD(/TERٮ̴GO-9fyQZ6JU9wDEQfC `jھ]= aշ 찢(rUFɉdZy6|Mk|H"@ (8XvoB V(QA!_ B]`%\c+mĕ&yX| \o~((}IՂ}b^تuږ1ն,e-~870v#ٵȵ(.93r]~K[yD}Vu]󯴮Nbci?;ϭ=RNLhJKfr'eb'agXK{zIJ6.2腅 & nWqj8V$tb>±YBIQYĀ2;6h<]2/]̈́a)G=*5υӬ%$>|HsGWw^; /(f9N2ce섴iۑGe8^~ݩENj'UjԱk9u,Z71VTWXvuL:jH=^j&/i]*!< qtlxga}KȠXT(xs I Ijפ*笠 d ٮ*[аN^ 4bV7/rJI[RҖ'y % >~ G@'YhB, :mdclzl3k.'K;|Xyٺ:=R^qв( t nWNƭpɕiii1\KR;*"\.gԽƍ+)FSEG|yX|08f!> |gB<~/$npQo)@X!XL:3~Lmo&ZlPy[*M~?v8 gXV]EXTp)T)7g*|(MxXƪUϔZ:\kHr]QP:%Sgfjx/-R 紁(~F{.^WhRߨUo,=j_M[4R Ӷ,/4C[Vz;4 b W,B^c>reG:^aR$,%'SSSTﰖtxb]V8O|XqA:L GrAl CnzVס.)|yK 뗯-!s8."^vf"3aljpS풨alQMx'PaqŌy)tإ+-212$qckRZ{ ]L7:GVLo (D!XPcs|SDE='yьRu>HLbD#>2V>B̄&!> `(C4]rE&%@LZr9cA5'oJ|Vf彾]Drcs)>*!v pRo41ߜ>R `xj87_٫, 9 #x6D^H[>qąvEB\%ć1&a$tJu9gʻX UBah<ϋHƵ|]Hƺ?"_$w?*g r3$r[/ \ 9gFAZd}xMI=# h{3#l9IclEv!"̽i:ˑ`!n]/"_!o&_d0.gM"5yˇHrXם*T{GǀĬ~߸kx ੅2.ALA : 1:zj8Ϊ>A˱'f|_)X֏}N= b`LΉ]Kd"UߤR:"ű|xVQZ>J)Z8'%oB,ґwlpćڦ\G^cR 1gAgBL`-*-Ǟdn@Vu[Zzt^9sue_3>40<Zؖy4)GօZ#C!]o2UvqE2#*r #[LL"zNĿR%XH Wz iQsYgM;oRbB o|!޷"~!AӛNL]V࡮ۀWggk(+|LDmh}фLn팼bdOMfΏͲNS2rk'<3z˿Kfrg'$SmJS/GWwnZݣWD, " EʄXK]yHX6ܚ ()[ Z5q7éb+q2Z%rGZ|-$T)P@;}:`Y`+I.[]={}ϑtʜ9w5DN?d1ys:3i= ٮ)xZ@|Ă#vrqAASN/⊊(%>]₩E怟ipҫA,1AxSMZ!rkWwz u:<>hŘ֧3"ƮbmdWohh9dM}*%V}K1qI'nؓ,{x,?p7뙼 &/["H2gc &WvZ>l|H7h\d26mnIQJۥY%DΔ|rr4ql:RBa}9QFA֫A,Imt|!ʸ6D;¥ |}>Aض1ydl [Z!(-ǎ'ϙϖcO&,/3L᯳0y ڴ Lm}EI!9s|b3dΜ %Ovr7$,^RXcOrHM%vJ]e9v5) IDATFP}GEA;)vc {Lh}M;ֿ& 6,"~`?пԊJĩpnrq@v2U8^8/0(UIQ*YH%rAA`g~Ϙ"+e*vܻҖ097u"m0xer\QI|8 =t-JgŚ3 Lβwܲ_ pam~<gRȝ!76>ٯLݽM٩$@ 4dE 0#a|ƺPe[\\ݮ)vGw 0l|]8+hqbGfc0ixmt&em!8 CfLN=3\KՅ||RDЛ0[>T0G yAeY>jN؍7vUg 5EXjqQ1THW|㊎L q2~:w/k; jQJH!Y9Z9P81j@Hndt{`j$zse)T+-N&*Լy+(aD|GWޕl6[Gdj(p!p1%DE%+(x4s} x:4u)wTO D}yG]_D|S11YNjHW B0n 80$b;‚vekWB|x)G-* QgTrw&[ BfddEr˻.&Q޼gϢ`7.*C00/YDdr\ "]|+W$@(op--e+%\y`eShu]~} pȋ ڍO?+˖rmLf:5?x[Zsyo+3<ř좹8$ѱYNoݯGBDr߄=Q 9\;D/&r m")Cc-&_#E3\ό1N+W`CB0 Ð5ˮOCu$(Yy#yM  |M} Q{M~Fo!f@_PWw~fX ǬZ1o}glذws7n^o&gu Y2ޓ}HQ汅x`իțO>UMrN l{Duco,@.ߨNMcyG_yj`ddǎȱK_;# J4߸]tحǠ9[1gAQ,lZueh~l{׌Onl0;NW]Α'( V-=}vNGB,]nCkho. =/UΎ4$6aNL=}6Js<\q B,m 8S&4tf=Bԑ~}40srӧ+ٰaCѺ^qg'<20aSm|zc9;TlP hJ^җnfYY?/7Ƙ\.]ou+Z1cρgQ.c q&KD/, 2/aXX| h9/&~Wp$>$@+ _ߟaFqmx"nkm3<'T\y<0^jMɛh/^+ۏ pP$@Z-Y8IT41ƅ C<Z2!QU0N-3aa] '5*aĻAh,}y_siY3#f\مMԗE@_oWw]غ  8fEȟO/>旝w?ʿ>vyOLOiL !f$@196g0 M,l)Rba k+Uw1L171%_a=C,>(T^EgɅX J&n͏3pYo9&gtj`ۗm DC n~<+nnplr!l~<K8|U|Y>\nstGnl|ΫK|,L"x_ƵTxp#`8Vx[N9QR|8s36/`GԖ~8Ջ>pnes`b-⣙:sxmXo!ꏭ'OcڦDa6K蛥lc\x\奟kl< 3Yps\.juxXNp=X"YqE.S|P)DDSKmdtZLXHB<R$,Lޚȅ+ 9dM]N50j~^B4!`ƧaӏA]"{xrWQΎ6qa0ܬ'ґx\ܥP !fK:UĂV($qčkY)gUacJJyE P^2&g)a,aa +>X :(5 XL v 8gwZAw׾'—n*`+Zpu;k[Jď<7 <|cbprprje<^x3>`ću%kTv81yˇ**RZnR#a 0-9ɝXbݤА1a~IHT$- /FF?fۈՀv/ ǁәY zKE$B{nn8gzGĊ O3\cVrњ^7}_[y TAt4IƕS#+O/mwb !%@ v*g(v_R8F88dw6Fצ5)G½&%>\bkf-?mx A` $&Btut774QG.!: ,ht\1Ň#B ZP^ǀ j*_WsŽX ?l.ّN )Ύ0>1YQӿz#GF#q 9/X+8/n]\v4BDJY>Jp(q-ƭn +nd l٬LXRTf3hR.zsf^OO4?W2E{ŗ89붳c>{C8vr3g8;28SSLehk.b7I±qr#:MpdᘘlJQzN[=*CDFT!lW$#P&<ו8$*Sֆ8ГKL)a(DwOsl`ۿ:{26:{' Ð)F9;23Kl$4rcQ y3 gҵ= zt n,?Lqy%ĺt%&R񖰺 B4`BZlB£T,H7L[ ա،6sȗGfbE]{>|f.[B85U38Dƃktz{Kdb.H(|b32䇻I|$|Pk%>HcGQ*]+!VXLʸft x :41*~[2׻x_|HÏ_UE9:l#,O~"ەI "&C(%>0ʈE)b$-%x?0F6vb΅nԯN<ϝ~ní!mg+.^T"dY8 *Tjz;zΓb^qc>bL nA83(1\.*v,X9 ٬Җ"a׷H|S kpz=1"dN\ß({A)(|k16>H:n%"ļ!n@7S1"^G`t^jXR H.DZ>RWBL-u~ P re=s28ۉ5| !]X~ƃsOüq7BDduz%f`xyR /9X\j3,"|}W{!|ps !W4c;pٷ;a Ѣ!Hy^{A9QR*u㾕wZBO+cLQBX|1EQ6M H8}xM~m"ĜAg7(? OG8l\Yls/jvڽ^`ugKY@D j%$WpP>U Ŗ"&0oHV? >d~( }.1>*Deܻ pk_}+U6sy=z<X\.Y$> hR@X*"e!*&{$k~K LK LCG:.DDX->(e~}7AeS2 춟ňppI׏s5>/z.-pضqzh5h Xu(}+JH]B qݔHX"\Qbɔ|x6FDJbȦ VCT q?<:Y:!BBd[W+8..︘KY1cA6>`#!V=9GOKt Z'*7\n. 9:_݄= @zlRr#/4RM0 ۛHg*-H)WH|L%>|#萿 |('t1oBd Xr|}^x=\~9֮bӪ\f+[k7qފVn86¾U"첉2 V=Kh.G5ԕ Ŵݤ}"9:OD" e,"P> "Kl[*ծێ}2?X? pw}& ˁf12ITKd'p^L%/ʥ?(S?X>rzr _WWH,lF5$-JH.dK RBJW5f(/T!R1}9.|sE0 |}cy?p 8 AsDgY.^KI1-1MiwWwO#5T>WXLx8d!`P!"6o,"x# L*F$*D|Tڶ0Q#]PcvovPD}Q`^*!^ºitu:[3?⟲l q\\kF*+!>lW,XL/R dLWeR # A|VPQB aݛqa?{ !"D5 Bq zwM;lW&刅Gc&Yo~Mv]IQ'D)q"ĢA i@"#XqR$>} >eeWe ?ҙ4DhATK!ɈS櫒"uUTT$?Ko?ՕB!"Dsݛ%EI W+)u)#0ʹ^?I.o/sীB!$@Lj2|y9!U˔iVrQ wYE bP\RwB,K48.g( DH Rbr.`*?@T5}v94MAdu;zhdf*e*%>dhIm ~>.]?F ^'!al[{$@fLϏjʉ3 !ҜE B >\B9"FGApHGvRjCC3#AVb."E" %Z>sܮʉWhH|4}x";G x:=S/A\+! UC ! hv qA/%0fcHǂA*#*w"">o&~wOgD9AnO!.@ڗ5h+uA{qi OiuIDATXZ"M QQI)uYAfxeF܃ 8ہ.wfOˀWPln{k!"oA0B!S`=b+xX*EiX0rQ0 d2ƿ{Xf+D~ ÐxyT{qK)ok`)G9d̽x'iY|h>/aVWXf?u|>LjqG x!bȧ]4 B3~ˁs/j Ҏ.#MY|8Y5NߏT=oXqPٙn-ށiRz5%Q h $>34fI!͊jB4R!X4/b/  !k.^"KQ 6栓(@_y[[gMlo;ꠔA?;8}[Y'u?rR BIVC LvQy}}}C wپopvmvgv6r:c"kOM]=FVۿ^Vqo"B]=DYnU|Xl櫺Om'jMM'{uul@vY4>ZH՞SC.LJRuӽ{RO>Yky }W۬`\H!لG<C\Zp|V+D[E=&Vxo5Gfp-Znmm¨VvH!B̝$kJmYo/:L F$YW:CG P m4Z38s럡HZϹB!I|3}wmaI3rL4`2YOʫYVk'77>韉qDLB!I|:>Lzn6F1 %&Z`WwOg ~XD,/ ծ-5dƵ>l>\EȎi\i.w/]D!->e"6i;LRORWwo+]S\Qw_}[)2m4 q@_*mSx0nV!{'J<Ԟ:WB,9OiOQ;6wI^\(#/4vl]W֥ *|Ou}f㎁]5\{sCӸqc1J>CKB!"S|TT"07ҵXC j*Y[]TvjxM*z{CU-,(ڮxxcF}YH(]!ɞF!+*!oթ22>|~^OQ^*㹵Jr5\  !B'vBWi{#5`&lGj[87d5*Mw6O]G bf#>s,jsQXD!bT|;I3b?}Ge+Mc2ok_Buk!86Z{+JqćB!s1CmإޓˎG ŒŇS̰ZmS3>~{K!y(rWɾilшIo4Dy+ę;wXZ B!X"Xl+QjփUvqTfiM5RZRUmQ=zwI|H!biQibYIP)cw?Q`rI~]ڷ*c >"ҹWWwjK{@_n=~ B!qykjL-kPei l8t>770Ԝ{m VLWVizeB!;ac?`O}QKYWG%n%B\Ttu(c< !B,m*M,]=3 vj@Ti\nRZ@gp{^g1>k]/vKp- eB!'UHgZ.O~3^m YBMG$Ya$杖ʎdݳ~vS%85nELW]=]={oE!bzSDT}m@zз]|R^+vεZ&;a=,to;mᎁ]=eq_Z|:PJ|ɶRmkcOݡ'[D!+@:Dzm"*w] ܚ&e֔D֝v(s8yh~4vIIy-d"4{o|oH ` !bޱb&>lv1m5D|ؾ `اӷY^ZݱUK<`SN7d4)"B!B)Bf+>f!Boi "BR`{-׬*$@BDH+}ϔYo{ t&S!;'wԫbx"ƶv7|ЀJBD!-B-u\FнN tRkȽ};1ɷӞs=DR5PLEȡiomc4la) t!B456 N*(5G1k} r<۾7o(dݚNaۿd*Z M!)"KС9W}R& ٴx$@B!W ƻ\A`p:N)Ő[ IDATxwXTW3*HU kQ$ĠFQݘf&&Xb]SLDKbT1TbX+$P0ܝ0|}sܦk"z.))Q*QFFF TrrСCJT0.Sc@@ G Ƨ_uLLڵkuk/].033{ꩧZj&""ɩK.Z充999nnn<<|K4˧O뛒rSNeee,**9rSNo߾}TrƍWqĉq͝;7))7nXlĉ###"azzzaa/{\>,,,駟N>3dggzNiƍ\| aaaRڵkSSS?ci7puu}i+k=)%Kuɓ'0رcgϞ]XX(~{kn_z%9$xqpp{7bĈ[充nK쒞*]H111ǏJ=:bi׿%;9rܹۏ?>66255_СCY-fY=uz9R9s&''q7n {9n߾yf!oܸQPP숈#G_>88xڴirնmۦO.5j]䪝;w&''5ig۶mT 6,&&FRTOOAIyK.ݻw]! ѝyB9t֭oo*'5ydSSSgg~zz4iSN-\|ɓ'J)SZ7o:u>}3g6nܨjEEEGUծ666)))o>~xƍoɕP՚RFĉF7nc?r#10T˻uv)>?TXr .\SOiVu5?uEY^zvvvzzA;GjժC !6oߏ?HjcffM1!>[hff&̝Mggg̀ ꫯ.ZO?]~(ֶsT NotcǎɓRIid 4J& ݻҥK.]HSSS{衧n̆DbHN:<zJeUNZrɓ')SLu5?uzKY>Jٿ!...R9/_][7o^ǎJmSO:HJ*::ZIȑ#TBF|Z]m޼[5fɒ%+W*.\p1cpH*_W||!$]ӦM3g΀֮]U{r(_={vuv*;;[n3gܶmۥY}իߟ6mŋ+˰ k3gΌo޼rݻKT ! vĉ'N 4H*qww{ȣ5k$''O0AʞܻwoڴiW^]~}Ϙ1~ʕrJ}aDDĪU;SSn$ qٺ-M%ϟ?tBkk믾Jޟʥ{ڻY=|ٳgg͚U@ݡ277/((ؼy̙'@tС]>z(((yҲe˖CjN:599922Rq֭XXXXZZ8p@jJYTT( 6Ν;VVVҤTN:EGG߼y޾Yf666/^ԩS?\~;wʪe˖cǎ=z'|Yzb.7*;wnìd5H*&&fݺu[n'4iK/r鞺R.1TE͛7uU &III|K.K7qekkk4SRՉYYY} @OUE=|0!!*ܘˍBWXqy@Qz ;vrʉ';PmJ=VZegg/;PJJ ޽nݺ]v5 Ǝ0Ԋ+m@@531v@Qjfd `$c')߿E-Z0v,j;p*Q...TJe˖56&^RR !\\;kgV*PQm۶5vuHRR*cǀzCR y;OxӃ[8;sݵkȈ_,--#jO&C;cbdoC *, R܇ 2CmYs'm79iaaaT v@HY7C/5wp6v,F 榑R@E1{: _^w_B+[DZkqF0 T0_}kZ%V}^zs_rT; Kܿ?vOƩG߯T;vB ~J"LVR @a`,|MiѢB~K\wj K߸G^ׁUhSSBafԾ[-ms-;k\*mn_h]3 Y+Nv]ݞ{@{, ҢEeI; k<V !DB>ʽ~"\q_8dij~?Wu)|oQ#wze~'UA^/߽ߴ7tM[{8C սI_{Pxȵca[\;C;u Q'F~QZJ#KCOuo7r/孷2Jl;rտר>6jbЬR=n[-z{w{c?5rb52~mЏ7`(Ry"*r&gp vwNOGW_~0vPo  Ǐ XjmxR*7o(z';iw!D7ɫWߏLo0 T*EEXvW|6mЫ?~s"#--@=5|f[b$fݎnc7\ʫPHs5rB%:"KQ*o֕˿_|Yq䤤@l:tx}fڤ=u?5I^:nϏW JZktB ԬY34`<:詚sTe]@́TJ.ޣzhfV.<(jӖm* @Y*R&ÚJw/˫bj~FBTI#sf#UA~ǹSl [fgrtlB;6I˿(\Oo[+-/}we/Uebxfە T4W-[˚o5nsl۹g+V`%Zk1 e)Ν;ʊou;*|]yT!'eg&<;T>]r测wܳB61UxY*iժUK.MOG}dɒj @aS ?ULLe a5.td5zaVQwlBaaټfM~MdP^+Ν;nnn񶶶eT'v@}|ė+4K>7V^zO*ե=_H"K2YZZ }}qraӖ6OOzCZμv!E;||?ڱNC9P@PNm]s.FMXI&jUѣ?2Ο5rf77@fU,Y+y9 T`tdԪ[.IVeeDa4]UåRrO '~~|nֆ!ez u\[FϏȒW=bYv///jFX*TT62oݾU.:ѦSZ˼ ,-ݞȟ<hgI3=lLJJJ۷o;8832а ܾ}7C/5wp6v,F 榑ݒ\\\ '#K#K#KJi߾}LLPxTsի?s;5@!K 2eJHHȔ)S|}}3226B5K.6lBlذK.G3vh;ZF PRR2mڴܽ{*J!RܻwoNNδiJJJ B,Z(22͛7 7o~E16Ո@S;۷o_jUDDVkHHwΝ(KDGG+|͠AJm0hРoW^T#vX*/11q̘1s?t" ʕ+cƌa`DB9߿?rH//e˖?:tj!6Ոq>*j͚5 411)ɶm۬ƏRj!BՂy뭷^۸qc7iܸ?3ϼۛ7oTv@u~!'*,_UhC;;Çwy5JRWdPݻwٳs׮]gϞT;v@prrJII1੩'Nprr2bcIIIcjܾ}$2DP1{:,,/:gΜC-Z/-0`5;P7j?~ܦMϩ\rٹ]TPy߿rFjzw@/˫WQF}7]v=zூZR4ZFq"K @*OJ%xBn޼yqĘwwBŒ''$'''c T@*33VZeddHzTj:,,,666;;Vbbb6mS1zhyn)Z}|Ç7kLϸ{i*..رcׯ_wuu:th>} @.\HHH8{z٦MN:u֭Mj#G_~e![Ϟ=[jR蘘7o2&4xJQ*|5~myʪԭΟ?߹sg;wNNfaPPVr՘1cnܸѫW/... %%%4o<44T7ooo>Tݽ{O7n\Y<9N\>kwl]xQO·q'O갸xֺ{uq uh//Bb^-&&&11Q^w^ݭ_>x+Wh͟?:++#!!A099矏:thrrfՃƎ{u/uw*߷o|P>! WVp@!ĦM utttzzY֭ \1hРBammmff&WYc.nܸfƍK ۶mGZYYYM>ڴiӌ34aٛo-4h˗/9mڴgϖ_,)TTFFСC0 YzLRڵK^},,,é"""n߾ULLӧծ]IsK>}iӦ0x;wdee;wN3Q%puuqfgg'φcǎ׮]?߿_QQѱc*-WZuW_}u̙|AAAsiN7gΜ'N{ ,8{pqqqc3Ν{I&UaRc{}葼:qDiᥗ^ 53YB͛7H{w of֭|ɫWvvvB 6L.yk СQFsլzApJ>UYW?yBϐ˗O=%S|^|iӦF{ݹsgyک{OnyUy|Ȑ!}0J9#P(`VVVrqqkqrrLi֪Tu `!!!!!!ݺuܹ|||:u^++iӦi֦#FݩVRWw܉WǍ9 ٲeϞ=ۻwoi5//On֤In?zCsUs͚535azZU{k_|咂(y IDATݻw ![yХ}hJԤ#[[[UkkUVeeeI㧖/_>qDi9O?%-KkܸqrdsiSw}嬬+W믎RaØ,۷K^<==NZ#.\ /ȫiii>+W-[W_I?Cu"K ^}ّ Na&iiiBwwwkkR=zTVwҥC.@-b^* y={2SӦMrIfLLL4ۘXXX<|p„ NNN~~~~~~~nk׮mٲȑ#:v5P*...J%|TǧN0jԨ{n!W-nj|voܸ?tuu۬]wߵZxq^RSS_|rSRRTY*#-sO>UJ4}߾}Z塡Ucǎ;_JffWoڴi%YYY+VbDll… s !>쳠 !YXX֭[!!!|-[HqqqEEEBB ,Ϳ~矟5kV޽SRR֭[שS*vPR숈͒/^xQann^5'233stt5kVϞ=ҭ쭷B̟?O>JF}-[VK,1bDII!- if>|URV0-o믿V{Ѷm[==KnnTR1v@5Ȱܵk8fOP !ڶmn䔔TY*uJڶmsssT*...J%@D @]WRRRXXrJ;;+WT*cG~djѣGϷ UGEEI TZ۷ լ:thFFbTY*ZNMM5jTccc0 233͍,Ztvj*KKK!DjjV3PR*_~Q/^>| ))D}d۶m[cGhRRRR8pǏ7羃MyǦC76oSa Y*@iQJݻwϝ;7iҤ~/;8fxj KK1ctӣG?cPjo|| ^|-Z!zPoѣׯ__zT?T.ww̐OOOcAdEҀ)33_~UǷo߮aeeeiiY;,޽{SU_[СC$R%E%xW͛W-]w77TP d4pM47vr;hر&L0vbcǎ5v:r70dV;wnժU&Lpvv611111پ}{-l[ׄ}tmlljhÆ ϯ۶mꚘY-- 0@BOOj@~fSӿ RT;v0"""nݺ%{ň9R@۷:3jԨZ۶^Xp۷?>|z4h3$S433U!İaê7CxG iݻHn*?~ᆱVcVFW^zwFG.]tС{6D+%%%!!A5*͛ϟSNZ[1"((HJ<<<<,,,9V6gΜB <Խ4o]PiiiBwwwkkkޠm?~UCWܹs;vXl=l 3...00Pk쒔' 8}tVr޽{{mڴ;"""ܹckk۲BWP_u͛{њ7T&&&YYY=\ΝFٲe/RY-Jeaa\ri͑8*JTjvږ-[9ϯcǎ>>>YYY;i}LLL&MTj֬ 5jTvا~oѝ;wݻw\\fXXͩՠA ɑ#G۵k,ZVދgf͖.]uzB>|8a'''O?꤬3`ȶ~i-+뛕%m. --m͚5={իʕ+ug*<=\3,,LMZZZDD&\n|IpppQQFLR\\\p*ï!w~Ց'N|7 iVGiӦ}-\PV?^\\.=zTkAV\\-]wU*/޷oߺu\/V>GDDXB.ܺu;:vW_UJV_|Ÿs~wi2dH\\ĉv9}􄄄C^pAUtEGG+ ~ !N8Y%:)UTTT.]/^sΝ;w~M6裏>C3&..nAAA?ܳg7p+V|M4M_^oݺguvv3gΥK y=\xԩŚ_6 BL>\ Bi;i'OB~i+h_ J$%% ! a#zId0T;~Ss ! d̘1BJNAAABr~raTTiӦRw֬Yr^z988ٹɅR:C*33q "::Zn 077 7|sN3[nY[[+'O\xqfff 8.+++҆;vܰ@ ~,4hfԩS͓K7nܿ"B1fJ{sNSTW[633ӌP>}ȅ7o 3[n) tQ2e/"##k6. -((hܸq=䪎;6mڴ 44T;DzE.ڵB( 󫈱T >޽{R@gϞǻ˝,X06m4CmO0!%%eرyyyk׮ٳg%,4LvJOO3ffɓ{&1Bh &T*  QTN'("xGUTTYnիW[ZZʫ3fP(ʥ;vhvkk벮lΞ={?;vptt裏]o͞=I{7QF&M:ٳg111׮]:ujYdx 4n?tI ,4@PwѣG?gSBtԩ]vϟB9rD#m(~aBBk۶m_x5gz4gΜ:tT5jԬYRSS{qʕѣGϜ9rUPG=,ityj*iwr=//O1]E>iܽ{Ο??]tIrCIyRlҤIqqLV:X3=g3g}BYf>}ƍK,5qWI")[$冤}zY ιǏW(TGBWʃh}/88^{tjC˟r}iӦZÂ"]ӧO~*=\XcǎUVڵk׮HR~w}޽;p@|ѣG\ UD `pݻ !vzq![LELL̕+WiiiW^$.&h|oݫWRӧA}}}{٤IGhCZΝ;7eʔ֭[oذ_?^x!ւU)!Cl߾=44444|5wPyO>}&Lx葞T€;oB9sYfBNR~+zWY*?<9ܹ5Ez+Js酵Dy(//M6H0 :TD" HєR&N(5kֺu&LpYݹ <.5o\ѭ[_~ِ#F 9{G/W5jԨwaaa*Jw,ؒ%Kl,991lܾ}[`mm믿뉉;vرcG֭'M4i$=t=\XR BzTiii zdnnׯw+X;* ę3gڵkgeejXZ%!!!BLm=Æ ʥٶlrڵ^zif֝\YK[v3nݺUgC@^^^BiCHSNI_ 80//O?&|Ν;B~KԴwފvhѢ7n>}Blذa۷_`4=!j ܶmۑ#GZYY1BG*4KzO+X;*Rxݽ{gׯ48B뺎X~رcׯ__m;6vX>,[,++K^ mڴ5ICQ>SB!6lBτM߿_s򬬬+VT$qDDD?!Ć w_V 1ydUVN.MMU\\pB!fe3)4"FhѢĊ\s?y8qB *_~ׯsΡCƍ't=77אk |}Zfii׆  /OӤI~hӦM~~~رc/雷u,,,}ٳg%Ν?7޾ԩS>d=޽;R~֭[Bj3fX~u벳}}}U*4qtKFv9|Yfmٲ{Ǐ_xe˪ҳonn={t`6Ώv횕ոqJא!CRSS92l0`%+ $x3҇tf%!DRRPU<s}פ"K_ IDAT_sүt+dnnR0uJl+wpp(7liÇ˽YYYm޼Yq^^BB̛7O?B(ݭ6lؠ)#GZ1O%%%vvvB]H/pu1''B%'uʕ#GJ'ApBƻw꫺=zjSSS~6m;99J-[jYUj^&πϟ=ztƍܶlR\\,R!5}ry Uz5 ˽?3f9scƌV +ͤD,99}o^j[ 榑ݒt_PJu)*ݾ}!==]z011177u͋/z{{+JP=zh۶m5\=(ZmjjڷoJ*Z>qģGZjU_0Q􌌌=\7pݹ`MIq]H,ΥNTDφ㲰1552dH&^ZYj\nW|fOT~ѣj;=~8???&&f„ {mӦ̙3W c4pAAA+W43aۑEy R6lذaB~&^DW,nɒ%70N^͘1QTz\uǨQΜ9SPPЧO#FH_M\ʩ;WPF @wo&,,e˖wVVV(cP#q)={ٳg@@qZWZ2Tݻ8p+VصkMFFFY۴iS x"te.\V(mڴ1vD K "窼U@B K.vsU666Ǝ@ JU]|gU(-Z0vDD#K֡C)WO !J1 :t/ZZZ;xB1 )*0"T0>T0>T0>T0>T0>T0>T0>T0>T0>T0>T0>}v\\ZP*O'O~c;VT~5ѹ`HIQcW5s9r_u"((( mV^wDwYlE"IIIi׮BsYfΜ9B'O0@GvrrJIIšCrrrjss\f0˱"Kxlذa˖-ҲB(..6|3g|g յM6׮]ۿʕ+Ϝ9YHڶmꚘY--he?.о˰ak5f ET:"la 3ı}m4v #s̚ߚ4KE$?ZZ~}%ru}\RWWq)ͭuޯ~|1ch5zxxL81***))Y#FرCJ]^^>x`KKˤ$Zmb򟧝TVVfff !vٳgjW򲳳Z{7nBxyyiӦ^ө؇=ztڵ^˗/_611777>KV333666^^^Z١!222LMM PT*Gytȟ=zt޽Q¨F RF @ !މ8L;999@C=W_cƌBرDFF !&L FGG8p ѣGruqqqK.B[qqqBN:izk~soٲe˴%߂~ ޺{q>cAlll~x؏?LX\\,MϥO]\\/kmm_hsvءMP,X@ Bn7ɍ=R(/ܲgGGGa_~Mw5~׭[ײeK}ذa5..cֽiѩS맹nnn5H}7ya<::СCrҵ!} W_}5}tB1wnڴiϞ=/^LOOҜo/Xfҥnnn֭{W\@cnD)))Ç0aˆ#LMMm6dȐ'N[駟?~<99yʕK,]vuz^zKF @a_dh2O*9\}7^g* BqEqǎBGG:_s 7C[@WUU-YDڧ#~-݈#ǎ^JEIEg:$RN<)rU2RQF=zH+Vx\mk׶jJ~)mِXڏݵkWUUդI#iӦ#FK' לOPPPYYٷ~+7DB[[[rQ:;!,4"fgggeeJRiO7|S^^>lذyVZmذAe~ƍ9W17{3FsB'߸qCnرm۪ƏR^^~zJυQ#WZl{4+c!K>|8rȫW}Gou944T{֤:*J?9U^^.vTUUܱcGA>>ZϠ4ѣ5+€Qrr"88REHxdF wuu_gϞ]z^tWAjҐh,P*AAA'N6l ťJ3P(?v*tݻwۏ?~ժU۷o߹sΝ;O>-P՚=,--k BsOPyÇZ[KPi5mVwN%Zpï_>swqڴiO 5v>|Xh|߿/D%rcAi:MSLбg!Dyy!k֬y饗޽۩Sa0tո)ץҐhԛR=z#G;S}?4hիWGyС>}UzCϗJkH:so/FFFXBzD&cj%ў=2!!㱱'OZ`g}OI@@m:ګW/!/&ґznt>O4IܹsjX=!!č7._шԸ)TOeeѣ>KJJ&L  ۰agRk\O.H]\6_ TxzPTRjZ~7x:y2v??(m\EEEBggg1CBH<+* C|Μ9R/577WʎIsZnz7779W"ҽiѣҶZHݢhp̍Hpz -Z~ӧ_~q:/'d-t//_*DPC* iʚO7nÇ{aaaBYf>}!Dhh5낗\c M81==}͚5jU:_bErr,իWll?c0PZl~?%##CJ\j,!!AyX͢111R1G;VkdOO+VH_~=66QϨ^ZVV֫W/2~xӧO߸q nݺ5rHJm۶cn1ss={ ><,,l֭z*((HKK[t˵:k_y tԩSR">Je h…СC diiYZZ}URTg_dddEEĉ9vEFF!!!fu~EwwEչڵ>waÆ7//oÆ ݻw/((gs#ܷo{;ɓɅݓz=ztҥmڴٷoT=rϟ?p@K߅$UeK&ONN tAc04Tb6( ͞'OBL}gҤI5_~XC9?@H%<==uvʕ>HUFFFTTTqиRmܸq/^={i,,,rss:899 !JeaaƱnTTֺu֭[KTYZZʛ JeLMMܹsΝǝY0 f,Er膅MkK h!މ8L;999@Co.)))tKIIo;@C PS^^^NNNNNNvvvXXXvvvv?hKh"""zm͟\]~z#FhR澪Xc`/I*cG)a/f"??K.`T JRd`|d`|d`|d`|d`|d`|d`|d4`Sc>%%Q ^*Y*Y*Daa!C d4J255UT;!RRRRRRRRh&LMMMMM prr1v G G @3qΝe˖ݹs؁ A @3qΝ>,4Qd`|d`|d`|d`|d`|d`|d`|d`|d4NNN999NNN`ScQ ^*Y*̈́RLMMU*`T!C;!RRRRRRRRRh&SRRT0>T JRd4]t7v CLn:""u`Scu˖-3v G G @3ߥK|c0Y*̈́JUT`T0>T0>T0>Tb IDAT0>T0>Scٶk+cKk K' $}L_Ĕ)MY*+j 9ux|5dTk(upQA.X)͍ xRЃHI1vڶM ggG_K:+F0ԇMG92DGۍ6d`|d4C ),,4v CL(TRi@z:'tjyvl^w> .h5vڵM6n۶S T;wn̙'N}k֭/bDDĜ9sJHHܘ bĈ7oرc# 0*D:tjLQI~sk*..n  a/wҥr3 ]v'Oڣ;v駟8}~YYYr{~~dɒ'9 S^*LQmذɓg޹ss眜w?K.8΀?~5MY*m۶/--իWtOtt;zI!isβeܹc@ K :T޽{u#TDȂ4kN\DҥK嗡uvZ[[[￯W4k !HrAd42++>@~YPPs1͛7̙n:Cnf̘Q,X LOO|t.3gr񯿊mۄb6R@@ gjjTO J!f|'7o<}Z}X MY*ζmB+0"ءPffU K\իWgff>>w8qÞ;wn'O޼ysViӦ !gϞ}FfQ D @ksG!Œ uցAAAݺu9rdII4fW^yE>K/ԷoӧO׾r0%%%_z饠^xRŽnΝ~~~VVV_ӺukSSSOw}עE-Zh>URjnBݻCȁڵ8q[t|֭QFuIZ׿ Թxn)g{g6QgKj>18rE1--mɎFx!3fLnnN:eeemڴi...r=z̚5GBsmٲeٲeeee~\2<<6,,ͭpƍǏ*%%e&L1biRRҶmۆ rĉuݏVGU\\yf{{k׾BYfkvG|xttCϣ>7=%$$TUU#WӧOW(s0`@iiMst+++)꫗/_~^~L} ,Yt[~~u^}ϳ!Cp4IՀ~rrr_XU Bc[-DouDDMOՏ{0ag* BqE;v!k?|„ Bɓ'y:g~ݿ_n/ Y\\ulAA/hffvxuBaff-7޾}[nB Qy]vF9;;kF*֜>{ׅZ=uV!DXXܸ`!Ď;ZlwuuB,]TkݺuӜ}Μ9'OuaԨQQQQ)))!!!p׮]UUU_#7ifɒ%SL[[PP0f̘'Njժ7ntСمko oT̮Bakk>gBcTiiivvɓj]JKKPoˇ Zڰaߖ-[nN\bV*i &xyyɍڵ[d;SӲ_g;H>|8rȫWVDܹsΝЩԉe}wީSg޺um۶III:ڵKʈݻw/;;ťcǎQǏBh>>>Zٺ`Gj "K@D HT8qbذa7x!DYYXXXhzݻwϜ9SL:DVk|iu є)S<&_^^^ Vc۶m'… ?8s̙3g:99 :t ѧ߶mےCCC,tƸÇ !C-rZ1118233*?/ ]&gC(ѣG9r3mR5g6^tBq̙7xe˖QQQeeeJRT~ᇆ 4VKz9wܭѯ_?Ф,--.\]TT5p@gGAv!!Djjj^^~崴4-gՋ>9G챗 [eeѣ>OѣGCBBRW_$&&kzСC^ZP$T\wYhhcZZƍcǎZJĉ׬Y;TN˗//))_&''[[[7NjsvgΜ\ڋ$g#~N'( 9K5tPB!? vܸqև !4k?Nhh5dʕu "'xرc\b뱱u۳g;6::}>yŋ fDӧO߸q nݺ5rHJ%;v}ٳgaaa[nիWAAAZZҥK/_.wܷo{=iR'OLNN.,,Mi133stt|W,XЩSSNIy>L.\믿:tРA۷o]]]mPzBEFFVTTxzz謬<<%%El?i#x'2?_B;L46_j!}}QZ~ ,oC|݄Ke***o^)>>~B[0`uuٳg[liffu֪*!f_~%00PPh%<||_|S-[L+P(LR\\,Ori:-l_xtBIשS'o߾;-Hdd%x<+mԢ5Ynnn.]މ`Yݻ6-m*|}EJCS;gΜBٹ7pjرceeem۶hOFFo?J255UV_ U-ZXXX(J!ĵkΟ?obb_S]QQQVVZ6776777Ν;wuooo TdZڱcF d;3O>kFq֮]+twXZZ|T*?>~Ϟ=]t6c@_**22222R_>wd5jԨ̊ GG~1!L>̬FhBRԃT3kҤIOh/ 49OY*MCV?_|ԩS .5v\A m۶駟,YꫯnAY*Ϯ.]9r_]vȑ#--- K啝]\\kpOY*iÔ?ipOY*{{ROs@ HQ,;4s>yS8MV‰Y"K۲eˆ ‰|}}HTaRhf̘hѢ'}"wwÐYYY988; @RSuӧOj>̒Zݶm[BBBLMMgat\xҞ +))YvmLLիWF y-_ҲϜ9?fdd\~]cǎI&5<7n,_|Νrѣ#ZXX4th𤑥!33sժU ť}W\ٿի333SRRj?<22r֭ QJJJ?~ݻwmll&Lп[['Ndffdee۷Q#KOefϞݮ];1##'55u'N;;;N<966!;wn'O޼y\}ڴiBٳg߿'}GcĉQQQIIIgf̘![AAA۷o}?##CTuƍ,!W6mjڵkgϞUն^^^g=ztڵgѣGk˗/_611777LPUTT$ QTTdjj:`ǭ$///;;[1rHQT9rDY*0&www!Deeec xڵSNlrԨQtO:%X~6ݻ7}饙G}hYfի喖-[.Z(""B[-,,,P'DDD}ZnqssꫯصЈ'//_ONN׬Y3k,&sw}WB7og}O֭[?xÇr3f"..NN'T*KK6mڔݻw-(( ݸqc۶m{ƍSIׯ_x\lذa;w}#Kt!Kc 6ydGGڳTBwwzz̘1:uꔕiӦŋ}RSS{1k֬=z!Ν;e˖e˖} rp[[۰0777?^7ÇWTTL0aĈIII۶m2dȉ'z]jQ7oloovڷ~[gG}B;wJKK7mڴgϞ/[YYiW_|{/gffJׯ_`ҥK׭[ꫯ־z@Ñ9wP(i}=UC***޽-U4iR~M6i&k[B9sg|,}ˋ033KKK8kyxxhWYYoTTT$&&=x㍷~رcOѣG?dHHB IDAT;vXyRLp˖-B/BsefϞV}9KVSSSCG!,));w"L9rիW44\ΝCCCk`RDfjjjeeUUUUW111[pݻPxdR1JҖ1ii !?^ JfΜe͊Q:;t޽SNgϞu"))IZ)B)v޽l;JGIqhcaaQXXfIaZAAAA;iS*AAA'N6lLC,,,>ؽ{̙3̤CÇ=_ є)SLRPr-Q(vvvZRq>\pgΜ9sL''CN>}zvBm۶-99944TRJ 9|[>D"goo5 wAj433) ]䣀'TT)ѣG9r355ٳgw3gμ-[*++T*J?4lǥ^rܹs>FӇLHHHOO_pwQQQTT׿AСCB^z988ٽiii[r'DPYC* iIƍgmm}^n|AXXb֬YO_jmm~+))Yre S*j?\fJSLLL\\ѣGcǎzsŊӧOjV>Vv%$$=_XgH={j? C4heiiGWWWi'T$~~~;+++'Nwڵko̚5֭[ŋ-ZT炷kܹs ַo߼ 6t޽Á Ky_.\PSpiF ڏuϟ?^ck1dȐϯXbǎRٛopž={k={߿ڵk׮]NNN񹹹e>>>?cxxxRRҥKzu֩S._\׿JOO]\\D311,X@jY~3;A>}z˖-[lZ Ŕ)S>Sfy$ G3T__'N( ºSjwݼy͛?3=͙3GRK``Ν;_x} ֢1iҥ;l;{999gi2D __bPhZ ΕSy󦣣cAAAuYgΜrٹ7pjرceeem۶hOFFoJM?*TZmbbҿy'T-ZhaaaT*׮];asEEEYYYj[+O8wMLL (Rjkǎ &POBCCSRRG~\hitk׮:,--u7sΝ;א9A;;èCCݻwnjjbjDt͟J;wÑ#GHQ59 KHHxR<~cbbڷo?{lcGѰ @3cǎիW?x@ņy***2(CREFFFFFj6:;;_V>MY*\DDRDW|h,F̬pttׯ߈#R9Kq@E @3w/2!!^sSFFiժaǢvRͬI&=S|Onp Kիw}wҥ+Wݻ]vܾ}>hҨУG;w;w_Po Y*9WG )d>>g5ߵړ̇={K/={_ZR*;ꨣ:ꨄzgy]w͘1#s̴gswyԩS?Yfq.YW_mllѣSSq+V{ܹsN 6 /bŊH$raZ.]tѢEAp fjll{s< Xb3OѲe.\Tڵk+++Z{ls[j%Ku^d?ѱda7xcaaalh'p! /pǞ}:,Xl3 kf͚A߾}[jg};N޽ogklKee{}ASʣ!vO:V;SN\ra7/^o}[A< -7lؐe)SApe:z]TT4p/<{'V۶nO>k' 0 k61:~jSNs̉.]4ef͚]v%o=ᡏ>(:O>$~ 8∄b:I;|w:u6,Z(>ׯ_֭[o4655w}}n\Ѧw'D~'aÆ:zL>dȐ|0XdcѢE3gΜ9sSO=uM7s1AtA6  `]w7gž^pݺuA#eǎlpGqO}ᇋ-:裏> ~я%AC}{c}v[o ` Ow 7$ܼ)Ø1c?䫯:qRy衇{#8/Nxh/5^sŷf۰a÷?qvH<|O>9~ 83 0gΜe˖=W@k&֮]{ '#GLާO>}dĉ 2sիW? ,)z㪏>(ׁ>\pW_}''-6߿{ꫯZjv1cFÇر}WQQ1jԨ?xѢE}}ݣ{͝;7QF%vQGuҥvٲe[l'2CcFyE͙9sfK'D"]~'ׯ|S}ѻ'OAwq)Gq'&"m… wm͜+5R_9ry=Gyd+`ĈZuCg}:蠄6ӟ.5kA6D~jll?+bܹO>\p6;òlСC3gviє,t=بQ~  O> (;v}ݺu׿b)6l|hݻwOme cͺtj,l:׮]vm=9"bرcǎMէ~l\ بN5kСC}H$6sdƍK4&+rYgo?b7#X'xbܹӧO?s=w]wu]_~-ܒM ~w?SvZeeWg}6z*I69QoBr%p)wI.6JJA_Nz駏8{,-{~ &LpgoNn{:̹ZVv͘?'|2v=o+V$a{eee:w|GZxq z5`h^3lذ>:)S?9o7S'yuE = ;?)ƙ7D'0NeСA̹Z)|5665'4hГO>lnQFvmy3ό5*19<ѻk\s5ovW^y%y%6PQ˩nN:RaÆu馛n /j̘1mO?׿5q՗]vY^xa<ӶnGy$zk?iFw/**7o^}/avi{~gk={vt&y晽{3gΤIf|M: >;S4h 7 OwwO޻wλnUVp r)\qoaÎ8∂?/8`F6:t߾nݺAŮڵ롇:o޼~bvyos5jԅ^8xUV]x6;w?%\r^vexҥKo/_ݻvz]vc=6f̘=z,['޽{Nr׹sĈ]vٔ)Sw˗?^{_߲#ƏSNu]ׯ_s͜9?6{@Jl`]t:a}yG? N8 L|xBg}vnݮ;;|-dy뮋/8G?Ϣ[?{wwfy]tE3gΌ] &$ArsrQG?g̘~SL9쳯Ν;ǷUUUwm+/*+"RJ9g^fN;t衇kbŊ 666vyȐ! 9H6 6?wycǎC ] 3glll0`G{+ / 4"ϝƎ;r!;Sr͟+?n h:vpqSJzJ)co=lfm-2,zs >;SPP(6{:!x'.'xbڵs=SN.|{ :   ~dڴi)?{R*w}۷5\v-@ko~sѢE-?uz}Awq3 hT[no8p%\v9@.hTS?஻3fLm h ,8#9q-^XDDJzw>A?]H_}}~[/]wUTTvQ@T-$ g?1c^{.sҥKuuuH$A}}}mmmN lhhI׬0皚 bqקlDKFaFaFVFݸn'zhTUUAp]pM=ZUUUaIK++k `dH)RPQQYEEE33a„Xm%%%隕ŭҲtJJJb&L`FaFaf|"B9ԔbKKK/z$?%wOUUUlh˃ʠ,#I6'?E R>Rm^wr@+$ڰH$v!l.)І/]!#)R>)R>)R>)RDqqqUUUqqq؅HH$RRRvɵTOJa@>T@;Q[[[^^^[[v!CJ@TOJ@TOJ@TOJ@TOJEEEEEEa@>"a2 ®< Iv!Bȇ h'jjjJKKkjj.|H I I I Ivp„ a@>"a2 'NvIJd6(/Oб?&[;vUQ%]);_2?>mJlM7mAϞl9촍O<18Mre f<<ܢӞ,'/ 퓧=Hd>F)i d(&iL>FfG쐆k,eek-%%iqr* fj_PC1%%)yryyߒY߾ [VUu?7[ T+8yf2|ARtH1C3y%iNcSTbcN#mbr=Ok{ɵ,GZ_ʼn# e煅G~K~Ƙi,(ybɘ=3Oq¥"ѧ kjړ_4r<uړ)*J~4C;_Ξ=8c z3J3L{/2#yyXӞ=6Odp$6C5G̿i :ɝg5&Yl/eAkm.--zG˗q>UUU%9 KW^TVee)>G;ds Z &ȑ |R*' |R*' |R*' |R*((((+++(( QTTTQQvɵTOJ@T@;Q[[[^^^[[v!CJa@>TOJ@TOJ@TOJ@TOJH$] UUUaW@\K@TOJuuu'N !ډ뮻NJFI I I I bW$nߒyz* L1jd#ᐥ;MJ!& ⪪  R*آtsyu@;R~sSSSӬ+îV(D"  R*'kɝvĦt9O_|2V>%ѱw.ɢ2a _&M; A0ihv%ySTI3:6'3N]I6 *C{[;ֶe,%*m~MBJa@>TJUP2nƦ& m|''JFf?sKSvOo9vdYtf*p8;R+'&{Ƙ*z)ְ}GaKkhT/Q>t‡oFl٦k4eSyÑHEoM5cڌ%zqW\tS*I`+Kq1Id'zzbkZn>Ӎƣ]}'J n&b6ȖҔGqL#fUæq&lqLuȆSG6ޚꍷ̘6Z~ܕi3$^:I5 [R*ڢ1դqFb|>!s J,mɝfpq6~sۤq_׭va%3+@6CҕhbeOǒ;rr6%_܏|ɝf/F{68&a4_yєőڸt>nڌ/3ynf夿5 [R*>7$*ehor̦w,Q/;F9')/Ca+rU;9zNŤH6^a4'vrAOS2nƦӒU/f+2 l*O}8# f̘46zX0cƌ WNSR*t&W7'6 oNu*9+g5|\f(fhsU䥗\v·5s&W'MK66a:8tʳUUU%%%[42(+ *Z}iINS1Iql\&W7dº L)? *(( rZ*آZj_hS1>FOYݕlT2@ kƸ6]TtsE-$ACCCMMMqqq$@@;QSSSZZZSSv!CJ@TOJ@TOJ@TOJ@TOJ&L(,,  QXX8qİ O |R*'ډҚ  R*hhhnhh! |R*' |R*' |R*' |R*((((+++(( QTTTQQvIJ^zʕ[jjj dŲFGYaaaaaaU+R*VӦN_tsLG<ȭV Z{cN:3N/)) 6OJ\W_s.(iO>oH.al7_[s\{͹ӟгgϰhäT̙3O=`]Nɔ#;vB T}2|~w}%ޓN:)*ЊL:m{8}}>d {_gY'|)S.9s:߹îMNa?e]z={tE@Tʕ+O=fDTvvkVwY^R]U?VW_S.e\v!fzOwؽ&B)@jkkCwm®,;E)STWW] @#CSԿ|d؅-`!# {]@#SF?hO_vm |AaW<_ 6FJիWfu>.-RlʕA?R*Hh=^˽A<|oT<v[\ >:llPa >{K<0 u>[3f. ~ fkO'); DsMssK_54_v[#WGcu߯uYz(|:yaںUylY6tmYmZj t-LhFpghlm5f5/?}w+ۗºtMvՑ.i_snGWUQWSեh_Pr ;v4yCτ'ZO?Ŀmаg:F#bwNJ ׭_c$\pibeܥѥtNYFQ$:=ݾ|;+Zm{8$1RV_) D%Un*bo_{pH\g51Vn)CcY 744nh-ˆuMK| 7;ƛԱfx{%TIZpޜYaqA|?_zJn=usȈ{>#o+G' ?M ZܫO>(ؾpKG5o;:.VU1S;myݿ_ F\uke0gԧ_,}[}u`K/y2~8/ZȮtW=r>ǎNKѥHQ$n ޞ9zbw k w(.p[ gv;nt[f QNYy Ul O|?} )e?өkOXue깿?}{A<L_SI/͝zfYp w}3noo_Aȕgvڦɿ7c $SB'˫>|m:5驗3/бO0Ϲ2/}QW,{oVUG/pݑ'C^/y/Y~ ѿ_:GOpy?7 (p;K% L9w c0M[E: c~Ym{cל?InjEqt\Aʣ/W;{r xO7 XKɲ,inR)7qmEK_W΂9;祹};&hnƟ¿w63r]>jҟ_Ct׹>y^I;U t/{1 }Vvۯ]ܥ/ aÒda^5ǃw >}tjſ_YAlKs@~\[sw6^Iq/7Wz1HQgyuNsXۯG߳?@ر깿{=HeUFe>,+OWI⅟.6x9<ׁlfݞrR.˔;vY0/k6%+K7\cp3aۯ쒉SԊۮ}˃]yLl+IKWN]@%:vocm2Yvc=N\yn;uѵ?5s]mޣƬY^}׏|bYϾ{Nr%@R:C?G/F]iU;fyu|cOZS6M0A|4stG9Uf{lmvl%XKyIN9c)eeo~sn_9þ؄qyiqܱ5v g9ǻSgNQإ~<^+IKI ťM3%S:ve<þQz7qR}a֯[ʄo_?v}⒬Ǘt0QgddŲ؟׬z׏r2WzѫE֬zK2.KytR gYʷ_O7|>zϿRЂd2>m%;wNE]z~Vi˵G>|NYN8g=N ϠNU|SI|o7]0Y񁇽5o??_Ŷkk бcYo>zwo.>[f{A>=K;h9o_?6S^yAѥHQg(#Q,zguA`җaYNfshZf{]r8zNy3TOJz{>{λkRt ޖt@6s4y=uenxغߨ=vD_:G,,g>xg>W: S<9U \Mre~v{C'kkg\tGn_?λvY~8)zB6}&{[g/ѯ-]~MoаH~ow5+y]u2^# K&>deF~k2.hf9(*O']%'tC]fϺxRLo@6s,4zHwS.tkCǎ_9e_<[_re3΂/{9\{!C?8xfyMMMa@P]]]ZZz'я/}JJJzilIAeePVTT]J",>[{;ݻƕoSi;peWl>OV%wqmM;&4I7 ESc7vgB%L\4̣RG3䣜+P^C <\x'1ҭ3rYVOlf>Vs6K(xOz A9k@kiν?C;>)CτIegm{[s틊s'WFHQg(:t|Wt5$Id]8$tg_IyKhЂ !匥\y .i emO`O(!ٳg.*xv |inn7-| dݺuvn+JN|yUa/ہUot)(RĵT-CIDAT;kռ̽6d٢x`U1R*1fgN?h7~SN 6FJ1cFZꍊ.hҊ}Yg]@#_QQرcgkW] zyWTTv!m UOnXgnv!fM ~w ={|x#FDݮ.NjZM:gϞa 8裧y_\Ѱ>r4nhgiStIa& ;vlqq)ǼB^ӿ|dN^{'Wx'.u9z??暻9p~8A_\ͪ7-zn霧jyw7]@=@ӳgM0aڴiӦ=wYRP~>$r<)@+UTTt_|A^zʕ[ykjj-../N{W%'@T@;P]]v!CJ555555a@>TOJ@TOJ@TOJ@TOJ@T@;QXX8a„°   h'N Z*' |R*)-- !ډꆆ  R*' |R*' |R*' |R*'ډ   hEEEaW@\K@TOJ嵵a@>T@;Q___YYY__v!CJ@TOJ@TOJ@TOJ@T@;DJJJ"H؅/@;Q\\\UUvɵTOJ@T@;QWW7qĺ  R*Tm I I I E.h *+`kog[QSJTV]m Ȏ0{+*hT@Fvlu͇P7jZH]+ h3T@FguVEuUWʹydk|i{:NWVVgsEZ)N֖ֆ]R>)R>)R>)R>)RDQQQEEEQQQ؅H O |R*hhhnhh!ډҚ  R*' |R*' |R*' |R*' |R*(,,0aBaaa؅H‰']yr-R>)NԔԄ]RDCCCuuuCCC؅)R>)R>)R>)RDAAAYYYAAA؅H O |R*'ډڰ  R*! |R*' |R*' |R*' |R*D"%%%H$Bȇo(.. Z*' |R*8qb]]]؅)N]wuR*6JJ@T/v1ui;F9LJE"H:x؅[l/]!CU8h 'LPXXv!CSSS5eOJ@TOJ@TOJ@TOJ@TOJ@TOJ@TOJ@TOJ@TOJ@TOJ@TIENDB`lemonldap-ng-password-expiration-warning.365417efc5b664a6beaba94666405220.png000066400000000000000000005311651325274564300501510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRi) pHYs+ IDATxy?~fdLY$$hJqu|%8hԋUKj)=VEPqY&&܏k>ٮ~f=xs_ud&7a„|ee%Űa0yd u |>\.Wڙ+ vu?so>@mf 5w^i+߻ P^^nJZ:p /F/xF$h6Cϔd|^BKIKm>m\3Y+4?3Zy|wŕ^Ń#i\[9Q8ZxyxtI ,}Ҋb!1p;IkN1}10m2K4wv&_ [}xL3#e:k@1MhEshhh~kiiAKK Z[[;0Ii&a3hnnFKK {&|Nx$kəZ[[Opõwssshnn`Oe2SбsORTC$/_9HrŁk3GcZhKbg],4- e$4II|KN \rz[*L%СK{irI{i1=$Ig{ri11]9hI eagdSuizx{{;ij8xjV/r{~W0Jc7~%,?y2OzkVYx@jx'xO<^v W .cƌxpM73`ܹr8qA׿5~mSOE߾}fz꩘;w.~n@]]~K/ņ s=q%w%Ko߾8spcŊQWW!CओN҂xg1g`8QSSBCC/}Kݻ7jkk +V@n} {6oތ{^x!ƌwyuuu8 2e {9r9W]u>CÊ+ꫯ_2x̝;8pǡuCUU9\xᅨC=9s ˡ3`ڴim0zh{V ;=oo*a΢f% P"]8$ld Kx)q.yHH4${kFM*4|9[gx r6lJhsMnƔ>$K>c bhfeg;W/WgmfAP^1TYߏ%K`˖-.ǑG: %%%ӧ뇯8p yqٳg+@=0qDK_ 6 /ƨQ0a}ӧƏ9s__1j(:,~ӟ믿'OF>}i<b4gLrhyyh8^y==_, u\Yh Azv SxNo7>T~n/񼔑%|;v^-]X/vY^Q\Nm`tL ,sx_h<%8˫س1|-ZVMq~l(_G_H/St6, iדD7c"d &M_'|?0\pnVwD򵲲}A]]<-Zf\yx饗裏0{l466b(//ǦM0sLl۶ C A~P[[fXvfΜwyv.]=zਣBuu5hTWW_",X?Ùg}bرŋ B;An:,[ +V@cc#vm7\|Ÿkпs1]***ЫW/466"h>#V²ePSS|>TWWc @C59K9:1~_eg/'G _Mw+lJJCg\ӓhM+,3F/_ =KAhg!%!-k@A3''6Ϝ9=ϋ jSwE(?NƐ6gsm`|CYM?I&-^5N8yV5ޣ×ƽfOM b+$3{z=F[PZ^I9lQ8I2[|9|̝ުZɿ!liiի#---/?9N8q7?1/#aʔ)8q+q衇/+ /"{oۿNZ;#FtIxw1c z裏nWTT aƍغu+q|[駟GشiS;g3W^)S&{9]zjǧ .Dii)?בгgO]|n+V`ܹX`C +>~gmm-n}ݸKAD믿;K,\.f,ZsŒ%K:ƒc1c: |KG 5̅xp>}+ ?_×ؼ_N,ݤ3~Ý9saSN9'|2fΜzs;8wq7onv\ve8#ƍÐ!Cj*lذ<%6n܈|>={ĉQ^^7N|#Fhg x81g|{7ߌ˗ i&477cر)ݻ7`Ŋw} FaÆ^+\~7n܌ɓ's|sÐ!CЧOL8GfW\I&^y睇ݻ+/۷eB>o O3\,F#FbȐր;[7u< cآo51tBO5{EbiYc̼셴9t#fȲddM6K.ϐ'fQ<Ũu4K84x}^xX8|6]Ɨ[xZzZ6Ƌ|%L –<0`qK/gM6# 1rH|;Aii)f͚z կ~oV<=z4fΜ<\q&M6aիzc=^{-ƌz _W}WkȐ!8묳caرXx1z!Z ֭7N㩧*[ǃ>#F'kx ~m|_Eii)կ;>9|Çc^x!9…01zKK ?\_/OǸqڊɓ'cɬS)K/ō7ވ: [nSO=_WQ__^xo.]N; GnGǩZ.d_-"C׸; p{zv1҉[[9f(ggIZc/@;DQ}=gs IYm-G3Vt3 cJ-K}jxÛ+Ҽ0t 5-V<3VbQő751uH\}KFɎ\^z,[]M_?8V_t<S%^:>?8߯_?y`hllĨQ0tPy̙3MMM߿?oߎ1cƴs˱{ަM \ۏ0Z ŋzj :#GD.âErJ 4#GD=҂yaȑիQRRA`]{R,_ BEEmۆ7o߾XlQYYQP.ZVСC> ?>֮]#F`(++Css3>C,] ¾ 455{'Z[[tRTWWo߾[555ÇǦM V^{텕+W,8{FCCZ[[ѯ_?`s=b 3555ذar1xy,X=nVTUU! @\rZty V~-)i F.[Hch)vhÑ\ڰɮٙKn(w]F-O~Ije>&6yTia+Ѷ7,߬= 5_Zo}bֲ3)żWf7_Ie7INo+n44xel9pu8ZF 9H*_OSi(d)d;L-Z[[qwc?hcdo( i SŰ'4@ng+H s _OWĆGԆ_/Q -VImM1+zgS!U]iGg$KkFVP\V,&7V,1)۸q#K-ZZy:,-}-h[M!)%ŷd>3TNzj$ VQ*Db鐬"*%G¥^qg4\zF?n_ Oñl#{f$&krNղ4 R\Iq9ߪtVX`/sLilP,i2xm9(ɗF:t ̷bj(y6f,Y)WgBT %mBc\¼4c.5Œ3 Mz6J9cX~O+WG>Yl]砳夾ֽrzn )ɐ決 9%Df.t;+ExY=I1I:rv>6(oϞ9A _*GL9N,6֓&1uI!]O94ydA|\fb d^X#u5xl)~idȞl,b9YHkŴU ΄CK9Jf5bX8I3u_+ŒmvVGJyX;CbΘUgԵPhΰK1hu;ޘ*.w פ{g=\sL~Ig:+=SŖ1M+I/p1Ŧ$[Q_z/dm9](ڹX>^Y% ],/|%+u`E0t-ģyѡ1DdeΆ\y!B:Gud TVʟ]ilHzx{,Mh:Z|?k3Spyj5˥=ѳv[K84brF)6Pb;\JޚaYK_KpY9h{Ic&ϼBisު7/J5?S(Ȩ<rB6KEu{xKT'l4&k,3N.rIX `-ʫI)qyӆa-$3%s%/ťݴuIW-/7 Z Ů&U'ꠄoqP.nϒʯ8#=qkrI,ѣj},ŮWn) Y g!ob⏋4 WW=u7#[ $5ҋ^=Yw89kƬ-_Kq^9cy{xrqSג%g6^*mpRц++(4Y<G-f$+8dKQI.5г_VDh'-K{VQx@i;x*c @6976^,zxR,њ%koG^>|<rq~ qkuKS%zbl]h49-\-.<\~DP>ZޞEZ`ŧWpOI׼H6v  $ ->\aiV*PZRqZ } K҃т8%5xJ"4 yhsP[jrq/l13[+J!1& 3_⑉ҧg5i~g4~^ن_r2I|5,!N.'fXuʋã_9^_XZprj<&fbK˘WN*3I&<'FŪG.ntO;c%N֤]M^IpZc{i.bk/%6>=c1y'%"l%5pU&>kg8a%f]I9=Y:$X4%y)MS㚼&{Ҁ!@ץA񵳉ͭ$+Vӳܺ X_PmkŌd,?垥u,/r5e)$]<Iִvzɯ6gzAFii.kmL̅i5/Mkcg)cXgc/\~i9㹤H|(hq彈1=Y/BKd^xA:q, (/K*ٞv 9`ůwֲ1v䣵Aښwht4"Oix xJrqH9.[W_}7l>)޽{~k hXh^<4ѭl#Ѳb罱IiͼsI1`6,|k>;nŰaDyb5Iyt쯭e9k>b{[1 lذ|.,h:M۷b^G}()Q YfWçEޝ%l]Y6]ݦ+'N9g a럞3O<-.iޮ۽&:FAr]h3P/xK2à_t){gvpk{Pu||O+]4~nzdj9ܚsM'>ܙOŗ$A=YDZr0 <9<;q/WdgɎeߺ,["K, 4Y}?LRҞ3]9;)l'$gT9ƎZ_xb3>CF` |.\@.r@@k+QZk{rY[\O#7b9=1;F.zh!|n5,v%C. 9߱޶^_3vN>9»붺oYAl=Hf0c~>3c+YgВYo/1\rrgKŠumq$|.tg[$9-o8PzSD˒ÓݼU}`JʀnCee%c)Yط I/ ,)$6K44ޱ^cmQl M-yTtٶxyTڢ dkYђ+--b: bU[Ժ;Kӫa>yoV̜3P=lo\rZ1g}mKJ+)nz^7b?HHn<,6*{ mbT=4AJJcАJW1W+'7+Y_y[+bY`})gle^cK%ź$p{thKãhVŒw_\AGrȷf|H4t6mˋ+9VJ6rڄCӟr蟱DZ~<45ԡaf,{*J/=6)mϺ{/͘F/mO󇗏Ɨl'u~Xg%y,up]#Bcg#9F׹ZŔwv'e値^a/{@s}M |¿.Dؾi=Ochkߠn{ۥ2C ѯz8//A!Ѱu3f1|sm0161@!6.TI*tpu,=]-(KPohY48{{JvjtƖdPOԴ=qђ@q(<>l/Ѿ&#f{oKl'ɖ0n8q?1|pUjs/xr @d\n%Bk 8V#4~ZJx1v,do MZɹ !/|> {8k1CKO &|hmnB{[(-@}&,xłNmr@ݪ -͍@GE ;|~;6,r9lYKߚ S1v1[67i2&㷷~q%ݭ5兕vIXu>R_9j~zi%z'I>YFZ2yy[Fe qZ\8$/ bR\NV̥WsssG655JJJPVV?Y.YY5fW&.bhf[ < ---hii@MMM?ByyHgykNeKT|g6^gƫos&ƮŶO-Yl+CZ\hgu_7;~7N%}e_eݪ0󧣬[w<4,{u|d|0 #<ݚCܿ}BwƜ~(OLZK͈3 GD#ϣGeo^ql^{9idL>Bb}sf=C/7Y(.5\wo˖-~?zSSOÙSN9\p[>n?ɽqY|<1B{.pzV=k>b[6NM'x7|;@߾}q5`Ȑ! Y p 'mQ|'ʑ@gJ3FOoP ZiI; f&qbwYxrжej|t[j}0k7aћ/aJ0EyUwl۰иm F=a/#?%e(VA==U= u[54O3iϮC :3^Y*+X{)7:{ +g+w*T^zCkkk;[⦛nɓ ?B@yyy)L1vhSq?9ahbAz6 ni| 﨣!wҤI8wQUU%#Y.?~V(#<S+R^pFEЍ^lH?k/, 뭳~E\m70۪bزn ෰nǜݪfl~@S6,z/~{w@|_2 zġI2"͊־y19kO{bV[c!k SzR!6KzQ`OLJ$u?C!N={-2siY9N>TWamӪexB-DKS^1whil@IY9NnTUsƌg-j͗3/bE|me" s\qgh/[? %&3HgޣmhR_p९#NӸ^6f9֬~=ikE6%ԛ&@99I8ile[> 7U|g J*ֺMGiuMoG=礼hxRּ8cjF3F6nݣE+Lgx[kOKg=H4re/ؘJ8_QX.' avϥhھZsC=>7 RT }.&z ڏVw#pKQ^}mVb``ʯ3/ 9#Ӊ|Zq4:&Ādig%y}4#pѢ+u/Xycx'c엵y^Xwv?whx NBsq`⹛&Ul$Y9ޔ>'j-O|h.!_`{.\0sϚ,8^HT_ hR_sqDu}.V<$8V uAr@,Ţu5βqgA1m GYJs֋V.y~WC=4nq9Rt=^APѣN{Ԅ^@YnH~<5ˡz}pwGm({c]=DiY_vm=9zgHZfGYóYr :sn* s糴-k]`u˧|2x6jˍŗ֥7\ҙI4%y.jO<-hK\4qϚ}jk- &y)ЕP,$;k/ƬY1{g1z m bLqc|rWG{{oGOi.c\9|ۿ7qޖG^~YL,))Ş'@s.#|‘Oj03Cu˚%gd[Luo:yFws:3#PfÇgo}4>%kވgvIAgkMm͟=c?~%ܴ~.P:kcli|%$T7M#9 eL4 x -$w-41Qڳƛk Rsz$S\aH66Hteyٛ $ę$7U#`Ŋ-8:1J4Kbr+f=?&:^699\@5ޗ/גE;(Z45zRmh#i.F䈙a#_na%/׷~wC´m-1wO`px,LFyVdQ³\KuYҒVŘcB,06K}5JF&ɾ%iKH%\7II!%6ы*'sr?qa{ŬY?xpZ*8A҅?PK5ِ6")$YxrzR$Rh1m +Wk'{G=wyxyf1,_H`ɢx!Gi:,~pÁ_g\aؗu&-Hh4YZX%cz7ƋCW%\7y&"c_*HcY#7OP*O^%Z~ y$"JPv$f#^y&RH)m^N#'ۋAL\eBס7!ឧp}֊F l@Ԅ$gyP]]k>,Ν<Շ&mH KR$C딎tYZÒE[ &$&Shg͞!mG]O㆓K,Q}䥲Hr\> z8q+|wSI\ v^.?9Q^WOfb-oҀ1GT3BSU$)&81@c p5zLcq/l>& hDe?LmoV\xg,]@1+:*lO}%gw_pVȚ4}7h{fQn_E;B }1`O (j Kw}88 IDAT_=JKK1l0p ַ7<-iétN2#琞Mf7(_9Qz]͎bF.-[ƿ˿,]tE֭}Q Y\Z򷆆,g׾j>M+w _bŎ,6wȣc]|.;.)܎vl#n潌W4P{jq.-O sօ^;oͳ1tZ7ymfK>gW'rgs K'h 4z>IH>Ϝ9oq 7Gdw}quo,\ B˭I-x.^,^ꊆcg_xأq '`ܸqtUW]{SLAkkIʠ=k^\GVqҎ,gc⠘tBf@)&daƎ2$C8nͼ\nu.n/O×\!ÂY&e5m'w\8s0q8ƽíy,]I;W[ uy;]1v쌗[~K Xo&͒^BBґHίXwn 8=sGb…xquס{/xѷ\+&)dm@3YH#`РA8QR}UUUn|ͨƑGibי/>-i ;KO8(eag٦ ͍;wUϮ]WWH#sgXhTwL  ! k z.#Yc]-:|mm-^\y9rz0a㗿%.(A쉃bۊ/j--9@Z iC/ ͛{7Mrb֢YߛΟV8ѥ[n?4yMŀf n;ykSҖv[䌝߲@W;'ӢWtC~ˀ绡-m۶aٲe8\:o߾˖-Ø1cTNu梞ENβMW{h$}7…0`_d vm7߅bElI@-g4/(ibZ4i>i*bQAL- ]GX:g_Y`֭Q2“O>޽{T>eeeΞ(^gbcA33ryƇ96=hc,[%2 b z $ B9SNkZw a%G4=3,zmKh xudM1@jFgG'ȮzjZ5)='kBY$PƝF;45ņ]\RA"K9M-oN+{g,u?)qyvKϤs1gxfAJߛk^ذh: KFϳcϲbX@oxcGF:˓cMυË|^\t=OUDŽᅬF{},X۶mC޽>Ds>}'#T59óކKqYGN[L(O1K;YϳWΌ9K>Z:iךi&zf4Aꅔ++lZ{y:rbCXy1|zg&s\K1irg81e O[,Pp 3gT"5f{#&s1~x<3.͸KqiK|>vnš7]hy|Oy\!-bCq5ba騭ihuos9ǏNj/XXojj 7܀;A3i3 rYZ:1 YcMb䠷>fSsif??Ɯ»A~zZ:>]_a;ϘQR Vymz8>k<,\N_|o kk׮\ڀ~z[[liy"aزe 6oތm۶u$Xorow5#e?{tĔtNQyZ5thj \_re"x 'p[[[ob=z4Y4]=*uVӗx&=8:(_OMY><<[>LZxxjEû%cA]WŠik;l,Ĝ/vjkYw\]bJ%P1:&M-۷g[[[1k,̘1~x?%K`5jZv-P^^cҥXz5|˗/ҥK1zh 8?<QUUUXOhVVVbРAX|9/_CbĈlRYKcD<&)fOeI!#-_k(8۱`lذ4h o֭Xh6oތ={Wپ};>cr9 0MMMxѯ_?5<̡:{0Ms Nl gH,Yy@̠`]Ąl1+WNP4c~YeYמ,1 dPXH=Wb`֚A,VLsHxwC_ ] w='!cѤyzFGyrE.cٲex7]t w8 uQx1i$s18qG੧*0vXL8ĉ1vXL0wq>hq8ydp|KA-FE1!5^>!pðo\igpb\˛3ƏBkk+~#'s9't=P\}hhh466?!?px8qQG/ƢE /ýb%W>k)5m nlѤ1wFk֭Z?įkxӧ<~iw}hii8'Oڵk;i&]7oƊ+0x`̟?-E]2drJ|;Ç#aÆ Xv-6mT3Y[[ bGMM nv6lXJ>3TPm4k8bϺII4m5diڵ4i>wqwE]]S_ӇBrڝ:JL\$_~e 'xj7ߚΜ^GpMt҃aƪ^_qZq,-]_ =o<̘1PWW'|MMMg} /]wr|caڴik֬7ވz _=fOZss3q[7os" )DhrgԿߥ(uNnOq5O~2rPm1P[[[xrEk=܃)SnCyy9֯_Ghhh 'Gy~;bʔ)>}z;---80uT۸k C[H1T͆zi^&?Gʤ56tc\Kd᧝=VQzXH2g5yēٜ-] 7fc"o:܆|d=&4\:lr2Q:ܞ7>%T_Vrqg5{szsrr6HKuJ=!-)WB=!@Y-%ih\r¯*(hcIjm~ДXLuB3gٳg}6zc=#z)455K.=z4rƍ޽{ >>|8xYgo4|ptI(---G]劦E i\cG_kZaВѣoKixhI\zyYy#<t+qGk_jjj˵}{…rOđGn hhh(_ꪫ0rw =ЁsƳkH t=5 }Y*g)d3mz`K< MN-ŠF=\j&im!H)_H%D#ixqx/с%=:xZL 7h9ƘGKtIK&-] Seasd@Tvjs=۷s=hmmņ SNŇ~ pW^())A.Cii) aII *څB# 4<FҤjV1rkx"-H1V:Brc۶m7n֭[ݺuC.C=ƴiӰf,[ >?aڴiѯ_?C9@ Я_J6𬳖8K{vkdɝ'=rI'&GWʡW͝tLW-ʇ{ЦΜ WAʟ//itٗxz:/YrH}{>-.yxTy[#-\InXv{mF.x}*eavlg2&$Ag`2i*F`RXf Lѣ*++ }ڵkb k0a%K`ɒ%?>o?Fmm(E(-ְѦ GaDG=Tk@ĸEے+Eg?YTTT*.R j*wofףSL_˗c=z4{olذ~O>~ {ɒ%:t(ko/4+?vVb|!4rq1{_K'c9(fdTg7pgH咐u- e<-b^Hc/,1G#[w }bNqBp\-5X&k+W]w>~]]?||Gxgq% .o K.~ӧ YT,M+)<m^7$LZc;vÆ bڴi~aܹشizK.eeeXnjࠃBYYzA0{l꫸1vXa̙Xlx Ϸͳ3, w3,rL^1/Jdg a L9 %(xzt)9Y;0tͤ)dُdm@i[~ݕ8 `K$bq%e4;wdLzl/nw!b5qx\󓊻5`x\._hhh1a=҂38>(f͚w}W\q̙G}3fܹsqwbɨIm4@ -C_H={z덓Hv Ő-9O>oSO=@ۏ>m^(wuk/k_q_ċ/޽;N>d_Z<+:{mZR~z[Psضm/^ 0CEeeebԄ:,\ѣFvN62K,)QFprUc1Il,쌳1CegObrY!C @n$ٳѭ[7~.^ihӿY'rzhC=Ujwe,_e1&̑fÅP=+b:<6/M,=ˋCGv w(óis'렘7(li3PKyBʚ~?*F#w<=S/N_=M#&~+6pZ# xr>Տ?~2{`dld]oӈvdˏoMb{j_Yݸq#/^lii): &S[\}KF R\,HH*ŏ'o#Dw%3Grn# ˒lԧ8z z# MKrVk4R-drѪ _J+_<5-/d;j#ɎWoUl"IOYGGkd=+XFĵdhsB҂UkX0]Yt|,񕫮Ep~u0~,}/Nd U+Y7.4Dm pDekNiH΄y&/2?ڂUӧo~բTǸr'MX9w鬄M] k5F;J Kv-j2J>՘x1Fujҋ9R.ptbx|Ý M~bkҒ\c2Y6iXJ>9=55[q]e{ohril&tzjKpcpk1W~@kf& O65.X_co[w,Z }ckh0lqC=' m1.f>H%m?}Z\p4]4w8Z|\N[zPiv|'-n 4<M&}*ŗ-凇gHXq/ܰ^UUUQZm.V77\k=g9Ik_MOOlaBҥYH={k9jWy)^q2.V Tۣ32G ڐ.MIfOipH@MJ鄁KFC-%;*;g_ '_ $ًC[<(-$oI6K2GK%doq ׀GҍsmGc[eM'Bh=k5FEŚ\~$km%CXyXWZWKnzH1ń4 S<ͶXph~ xV}a)JW =SV/DVq!RKqvRiK53sċ'%$4ܶdh-z4G^Oۓb_]JSK%pؚcK , xOst2szQ8ܠ9BXy~04~ o fъE=fVL%<)F<Vs_9hF455uT^(_.f$}xj&gK-9ˍPN [dgV%'}j'v8ݩ $|sV3ZYlҞ=5HqN6Iw3WJ䣎i yZ*ឹxItҐ \. TW,] l>ܾ7X<\qxf N>|gd;z6+Hm@),\fRlh8>phEut{𨪤s'B¾ (,ࠂ2_E}Tf\GwuWQQPQwEFD7@$*@ȞpUu6̼ythu;~˚ϗ!6)q9U2W.W(.+C\C MG:GI%z&}[ô|MBȝi ˺䳜/FJ=QZKr+>_@:PX?HR T ה֊/-DMa88L WTPɵMo^JQxJQ+0VaJ+l^^ھ(Œ\TZtOu5WE[R9)?q95Z| Xw}MmT5Z_;-G@-в}[S=mG|}%5FMVTG#= J>ꛣ#Ο}ɡy˃ěkdryjG/p{|֡a0p8].\,ҨSpiɕ䂤WiM  3F38pEg 5^$g{x$S›*vSiK[FV3n^;-}g@$R<ӬXEeJyR# 9&BNGn]%5L.NI:rɕbC_W?7\J4(./%I*IdtNvi)O*ӍW_> =\9w $KW=OWW_stt 'ɥMq1Dåc9=9Tj뜿iĠ7RAKws}<._K J8\#aʏ 96T(fMkuu|@c9*Cä%rnH{C;GKB?'OgB8.t.%' 19O?.ix-6^6%h4{Odiw59Zs'ti9A% CO}k'ۣ;H'/ trrr\.Ws9\?oA )/錩.)Vk_Lr#Z_+d'N_%Y{5{pjʗΝ/_T\)o( WI?2JdT 6ZHВ&KK^QlgHۗ4]< 7|M?Ւ 'S|I 7%gn9HsM3)JBqE(L|{-1h-\!dw/_< /j Nɒ]=59V](O)i>ęϭk5b%f9]$Z-gkt8ۗ-rRݿ'{K~)|{}~K1l!S~Q}sg-ׇŧ'k5Lm>ʳ;Bn i$y9dkp%bJ͹Vbo\L}|V<9A0IƇ8Yutt]jr8,^d _Ғ3!wh|LgD߫WK\a6iki4Q_584sr:K$ޚhr]~\%=^ҹq5FAOmYbS̾q GKŋW_dI5ã[EV*u܂EQ|.R(˒C|8ZT\|Cʱė/7h%i!HHJpW]:P\K K TlB+O+T\pEbh I:.[jds< JG1s#OJѻIMyP,ZH|EJt]jx]Z`5zU:~i͗d~QxZ Eַ1j6*KWI{tH9] pKGkUjgtTwhnk$\T@Қ˧{mWoődtRC!֜/m5iR }@Zҷ'JƚCZ/YC/Sr_xo(0ww+@rܜTzMi$\`rOJ &N+o. )-oxF'Zrt>r6.keхbGƽr@hAb1 a&YWm]䋬m*͖U7K9(0p5Wv 9G[Xn>$܃X,e˖a,o)5J$ȖpR<|D#U=?bS{)'˚G-tzkgd{>8:99+gkqj{xs䏚5=BrgÝZX,B.xk$T)rF1D\]@@bN&7xHlU߻-QxoTtwOmh˅ 8ps _)w<}|hC5nV]uuu9眃-[,w򪬬Dee%n6<_wqਣ7ߜģhllb׮]hllDmm-jkkM6n5BQ|/C_(-r-R|kv_ h,&ٚ-h96Tw.|e*5Ζ>QZSLXŸe޲ߥs1(|TTz?L \wHb\JTTX,4U)r%=6m'g[w QHAOuSr-la9N\4Z [P6%AϏvPP;|^q\ZGm^,9LO[XXn'oE۶mѮ];VV}X v~i~> _~9b.2s9xn: >_~9rss_QVVaÆ &=% uᆖw<ZK9ܲ2hjOWdW_E9R !'''A7wvYhX!|t>OIgWc-Kך.Kil<ɮ]!E:_\-88RunqkByLWdd$'ay1YHv],X+ɥ ֩=h{h1tT]^iXl-B+K<V9Z|KqkM5~kR|.۷o_9s8rJ|7xwK/SN\of?/80n8瓖E1,Oa\nFk.ZMpq^nZr>Ϣf(hN4 >tf  | *!%i"sŎ{pił+ 5퀦5- -y^ov6?jI[.AKEFK\b#à%hNM5 ŖZL5A 7>.O_btt#w~\cA_oXrTh$,ZbpiOUZloܹ(w}7rssQ^^}lb1\{1b`̘1xq`޼y(**ŸgW^׿b0G?\j͕R{Iu%ʜ/SZd_cE  3g_OzhɏNO /\.T\RNr%{pbr֣PzNb?N4޾XSo_&ozos}Sᾤ*]4>IXRїBLFуI<$~44~>LQ|_+OO.!h6Oqp䫾m[ҤC+Wog R|92a)j>zN, 6LצMԩjkk /+[n񽹹C,Ú5kгgOdee!G :RQAmkY|B9-ZxԠsܺ%,ox*oiW|v}:rgHeXϝCNKizq6ƖV#X;~l,A%]unj#GDxÒ-|D\ə$~B. ByS^6Oqу8=5,@r= XdD t99M$p6h398ݤr)4J{}9]$Ydkl Aox9Є]D}4~aC*VY0e˖hժ:t耣>eee쟋엕2NW+VߙY|+ͤ{ϢHNϜ6>9A`׮]bqYpzq<$]4Ҝvɒ4|.iΧ>FI_<ȴrRWʱ4}]}q3J & mEp.)GJt?w  $= y\Hr]qr|X)!+R"rf 4܃T~yq%HqsC''ӇCèR}ay5:im6˃oQQxQ]]wyV2kllJt+Ç|-[6mڄ>@j1nmO‡#9G)7MHbT=%DXes7g{ Z=R5ODkɻRXhon9;IyE֜ѻrh|Ґb0w]C=AÆ Njqq%~Rxc1 CJM+,5$W tvPz ~׸k gTJqa˞DA<'o Ԇy.ָhsKyX{t&L… waxp饗 .@YY&Ns=={dlܸz*QVVly lȐ!3f Na^sV?jݻYfƯx }j*P]hllpu}OGQԛYyѱ'{מBUY'pYe{o-tt鏄#=jRLQփQo[JT},+'Yٙ'}go$jQ2 6a K1Śޑf͚<(n_Ab3J1aKO)KozOxtlɲǗϩ<6Zeiv[|0̨5'J3^F=#,,qŅ%^|uuu=999hݺ5#8Gƭ݆>Ӽxb ˽Zq_%۹|5$Ӛ %[3j/8LZnBkNJgMbd4oͷ$9|A8RHU3vOONGG] v/ wܵ$; 6 |$dr,Rh$:itW3X$~ m,ҽѧOuR,6cPA+G;{Gi&JӚ95% ZLJ<|!al"ſ\xr9>%3R׾u`)NHZTeӂ/RSK < $]Wu78l|!%Iv:kU>Ob? ON?$-6ך ~]}6}8$\>\prV|a &ۯřv,'9I#ƿfͣҙX,L 6 gq9o>'pwN  .Tu :ub}⫒=W9ZW޻<ӽ(fO_NÇ#Jj~*8,6Jo-X|&ΚV=|9en҂P1'Xr8m/W"9;c4صdYsZY&d[YJxDYE K2mM 4?X5KCF_C A QR|5lڽ/Y[ϗG9+mQT/U]%;r'Հ5Z4{M.,8$R^Od{kluѹ<̚5 y=ښZ 쨣:&OtS>|{b{k-hR5QqpO9h.0Nר8\Dbh}Iìa3j|VQ֐gD .aSh_pE3]5oFM>+;h_M O_%bKàJ:KRpkkRQZD%: ^/͟Og_jCMG RaQQi:kzXu͒׭Eaz @BwyVi5=ܹr\}l`ɩ_+6MFO?b[nٳq1Ǡ@}`2qkOa|78͵Pehnx<)ñ|rTTTC1at`*~f1Q6iڢ;hBDqcPxh{\c-ȾFʊ3J>48[Hu+1hŸ't>Y?Ceq4Z G5\? ,--ń 0x`aڴi,]87uTuQh׮9ԨqrzС0`H5۱e˖8g+WDvPZZ={6Zl{/>tR*(2\[b~53\OB}g'qnѵslݺ;wF?&93ѩS'̛7OֺB۶m1c 6f|# ! q$=jE^*Vk|AKK]'xQ̒^j?F l 3LJ񔊌&Catl a9*K~k6dq>.lJ\_k9}T5hE椡ᴬs9?t&2p1md_.,M%o(tX'#shLuX}(IJ'1;T5,A-jeɟqeSY\Y׺t;ݺuT[qଳ®]p{cǎ8_Fjhhh@cc#40k,̝;7O>,)UTT`Ν t/2v횀;v@uuu|۷oG}}}|UVhӦ >@cc#}t}Q<>SkZB4}[mIIIgu׮] zܹeeehllzxGΩ !͎;PYY`(C79smhh@YY:Iy6;P__Ob1 ,Y|9VX?؈rرCEnj3Ki4pii)#/VTT]l'`!R璐K sᦉ[l7Yh2'? zI6 %йsdrv Jsb4jQ׆^+V `&~C`eSV\}A),9;H4ZlJٽ:s64K. O /JG]Gϝd{Igj[ƤKʲ".V8ZN^3(.z6V&EEE_:=X [lڵkֿKcȐ!Ips?Ol߾C5\AUU233h"?>O۷#==w.BdggO>/A_ưawN:$̘1/X|9f͚\pN9Xnznm۶EUUn,]5jO'|-ZHm[UUUظq#^z%_¤I"~eee ?C u0i$ç~%K`ɒ%GV0p@7[nG}{{,`ƍx_oǨQp뭷GxWPQQX,/&MBQQn& 0o&~ѫW/\pزe pcɒ%@II 6oތcbƍؼy3dqHKKCzz:ƌɓ'㢋.O?_~@>Xt)&L7())>wyǏǘ1cCO>Aشiϟ?W_}SLi[o| SLo뮻ƍ3<9sL1رӦMèQp1Ǡ~W_aҤIhժ&MaÆaؼy3233_?#o>SA9s栲=z4o?L4 nj3|裏͛|r?ѣG#-G >'NĉyftMhllDQQQo2e .]=F̚5 rF}bܹ>Ѿ[b L4 guL^{ A᭷ފ?ǏM7C)_ wB+ ;Oyq {0kT.N7WXm n1?,ᕊH8J|$q|!asHɲYCtnpƃ;Li?|ݿl$}2N׌Iy:8q-̝=#Y,vP~jNů-P<7Hm)*ߧT;\|Yz-{9:MyH%Z>󝇵&5peE)6/p hhhb1?Æ r-뮻pyĉQ__/v֬Y~ ˗/GVVz?С cӦMq~-&Mo1sL4k ~: s9ؼy3J|q[u]w:v숾}>s=>:FGq.b䠶SLe]38ЧO /pqÍ7ވ;v7B`ըÇ#aȑغu+;u5jsθѶm[b1|G(((ߎ" <3g_#F$W,ÕW^?A /ٳ}v 4iRzzKA> </^Cbر8ЬY366l?|w;{G}>}`ƍ8묳?\s ͛-Z+Daa!w[jÇ^Cmm- Я_t m۶ʕ+Q^^aÆ6mڄΝ;g/K,[oM? /GE~~>ƌ Ǐ?< w?hdqxG]w.RuֈGeT ^_PFT'[ePɐ!氹Nl.]'h--8}|k|TYyRjXR=kNg-TהpubV+dIx8V^V ~D$Z?=9\G$?4Tpl߈A [*yV&[Vk5ytm}UA^z%o߾UUUر#:t? ,駟wy ,~!C ;;7oF֭ӧO_Wxꩧ5|0UW]}~-Z@s=zCGN2n:̘1N&;;ݻwG^^.]?#F@8C'`ڵ0` Ѐӧcڴiܹ3ӱrʄ͛M6 vAQQQ<ddd$.;о}L466۷o>`l{8pgCI8#nOxݥK!cÆ ;  ;\r V\>:uBNN ##=z5k0tI?SOE-֗.]oyyyŎ;PUUԶm/>@II 1o<ڵ ۷o|B466m۶MmۆM6ᦛnBnn.lݺuw8g'{ֲ'*O;{ϨIԇ oOһuHq,EQ14{ڀ7W{]i(krciS;8QhQE?~\=~:}D-Զ[Cғt.V݋KoJ^ں:^]v7~O:-,\~zQ^^vhD%++ (((AsbׯGa…;4=rbX j‘\Luuuxꩧ~a˖-Xp!Nl| t>9r$|Mlذ~:؇z( VVV6l؀'|&LI'4r-XvmܞpиJs?'uYgaȐ!7oL~ o[Olڵ D,Kȑ#q뭷b…ݻ7w̛7EEE޽;>쳄ϸ1`cΜ9ےl裏_~8q"5k>ǏСCK/E֭QSS_|͛7woѢfΜK/A`Νq7@VVZn &$6mڤidj)^Wm IDAT^#p._7)Hظ͹Cj|8,p}GTýMs9YV:+֜|]`&/-+'[GƂ͊[Z'|['{,OkNjz@>xqJ1ix1c 7'N{ׯ_ Ŀ 7#'33s y?CFFFvxWuV⟈-XtP]]ƍ>B紡[˖-qaaԩo0}'tveeggꪫooGNN9zo|XvmS֏?vb:uB<8#!CǀÇcҥXjƍ͛cĉxйsR"77xWQVV~]w!773f@Ϟ=qmk׮ŢE0c ,^/Ƨ~Cnn.ڷo(++|틼<,^۷GǎѾ}{!33ScXr+h;4L_2DyGqqh"[}a„ 8aL8=:wGy<@< >83f@YY;$[MvOGmm-֭[9s 77A\Yf@aa!:uN:!>qbضmo?444}w1hР/ 4ik'OFϞ=ѭ[7Xӟ@':ӦMӱrJ{q 7`ܸq9rdM6ᥗ^¾ pA]tAQQbw_j k֬ Э[7p =z4 7K.C8?p{I&sѦM׿Vs7++ z(=?~ǎ1n8h9ƍ!C dggcĈx7ѻwodffbȑ:u*?xb1~k BiiiS69Ca`},E]?Xx1Ѿ}9͙3*z-t<F{h֬!f͚ɓqe_~h۶-?p{-[ߏ+sAnݰftgNT\= @#Ӈ)e%Tp25aO/ZAΝ;qa +Y(V'S:1,&(oz-[7?_)CC}ux{|6qt_8|jO^]Qk ^fA#ZCמ$|XpiC-QmV?KŎ[Q45Qzg6Q^^=7uDصk*++ѺudffbٲeKxԯ[bkA]]&L+WUVի娪B6mjl۶ ڵCFF @mm-{lܸ={D-ݽ͛7M6JFyAcc#֯_bt]vEee% sNףiiiŚް t7&[jm۶!33@yy9rrrߤ~Qg}о}{x{5JX*4sCw"JpE+tԈkNjCniΰˇӑk 螨xJᓫ`ʓAUn˓ZEiAņYtyo֍0*q9LK:hzXh|-Iafhr|gh9i\Xzlh8}\!0Zr%]yIoUuO8!1 ̉5`޼yIohhHCp('eee]v͛yq^')}rrrpkæK={gϞ}Ṷl-[kbǎ5k}EoB{C,ᛘzH^zW^yddd1Ç0|rss8I~鉫cVVVa0!UyXf ̙CbÆ x衇pgǿ(FZLIyCСۇ$<$I{޽ѧO$LOOGNO~1Io׮]ѵk qP7?>@<>3,^կ_{w C-[3<2Z&'&ѻ|@9X5ze.DD%iKel: (' 6?Gkt8H,8}=Zp9>t>5,96`<!tR \C(p79ե{5Z9ܼdƮ\)tGώ6,ɗ4 +t$>{IzJ|#fk.{ĀwJB:F_ѣF>7''==|N%!k.u۵kaÆ%՗-IZliN{A^^9Gzz|:>}$|58< V#J [o֭[c/ ΆoB+@k\1%'-ai2%ZΠF㓭gd;iuf>t-5 Qk5U|O+]j/+ T5>šɶTDKE/mGY8joԜ Χ}:Kr9ߡ<&M)T|\]R^R*́!ߜ{>jkk\}8y #777=ؒ웗_Ni-9R Ç熯^Q\ r$'g/^EEE;:Tg 7\tE W)RtMZlAEYc/~LhoYq^QpgÃ> ~/Mˏ|"%|opZ&;:dy8t5q~ViD NM#뛌QdK2$"w& +Tc0NT-Jw<8|t $iE0[pgQ1EN&jO+/ k^N_txpF|S=>sijŽO|[a?S{c?7½'vŕo_@9Sxj"jh/7$Ytwf*_j@2mfp'^ipQGE -G{zm}xJ>_ORmei!ِ>{J8\ڰ$kފC[,&w%t͗G;#n>-]-_@|9|w|IKܡ vTx)v"nQ|!/a*j"pI9~Rj}TKȇg{6\\CHV;R_NFk"(T@kM9FCJ:xpNJ;.,UAyQ>R^XN&wo';Z}hEt}5#s- ꐶgy&bԩ4hr>B5Kjr>$p8DҍCũߥ3tw6_Jڄ3͟ޘךQ9Z\r}gS-]^9+=^sZ+T+H\W9k HRHTK!*oؐl9wFN R _ґ+%aI6ђ9ܒRs_R $_LKD_I_.qI{4AkFyIﵼ7c D#ɑ'[ܽ|) N߫˟74{[B+3'(ȝ#ך^Q^ܽ3/[C;#F>E= ^gG5N6'l_3i{KN4gr>RN=_]}K>-֬8~C#M6OK__éeM: N&gZvYmFYJkt.C!d4^RC!X XC+9tNr M7͸dkr4 :,voJ1H2)ӅӉk9+_#-y hv}^q8%}uɯ|+I I8kKX}Clqs-}Qח7\~ΎM҉bX\jT-×O8}W/ &7KIϗ'|]jxp8]oy|k̭u)/~K@^{IO\-}ΑT%{J\1 Iu8x6y})H|dՒsܽ/>]:Md_m]t^+L}4R]zMuO|sl|\^Ӥ[OVtY{# 3(c $]_ӜCJN>T KisD嬢&ZiO4YhXhRr׵2%bR9~ufE$g:,9:Xj߼YwOS}r|Hx,5QFl{ȡt ,RnRM1!&8>&J'5RbEIk&ˇ9{հHC:_lJ=AOG5-i)Tmz -:J=tIgΥB]J@?N'%Z"X9],0_p,p*/aDO|kdtxPz$[FyHI?[|嗨eb1ر1k,XzjWCCVX|MM VXM6%Yz5n*At,XO=KIH6Zޒ%|>}qWD)±`L6 PYYbxg0|/.])..wᵹK˂[{(v 8ɴ}LCk91W44OHuk%$yTW$SZ]K\KyF Ntľk_CŊ+Dߨɓ1j(L:w=X؈c瞋cYb=Xr-q<[l1c|8p拾 eO(..fϟKzknHc[\.K{`WC==/#aXz5?0jjjo6.] O8|9㫯RcTK|QʝIvPzҰ .t'ũ\[T坊F.?I?/QôXr4ɕr{}#I}bDqqkRRΐlGˣҼ˟bE?/SJk(iu&*Gs_B*R'is^ ?ЭDڛKV<Õg\[I|$pq<\>lqkL1^_.]`ҥq<6m† 0lذ8rZ %%%I6ׯ_|cc#6n܈71oԨQ뮻rߣ>CCVZrΝ;j*%iii)VZiȿg IDATX~}\իW給kB۶mK ~@EEE|ڵkOn% ѣ1i$w&@˷QAyr=k^|Y.2(r i-i]zmxr峁qܼ@ӷyp|%9>L}}:H$J|rDoN+:>HI2>M-g 7B8>ri۶-ڶm'ᧉ7##x`|off&s碾6mqI~cܹ@~~>.\O?:ѳgO,ZڵC.]0i$̛7ظq#;8tMh޼9|A̛7EEEXnƌ|C|?~|] ~+qꫯЀJw}:t(b.2X999X~=Ǝ?OHOO3<|;vDMM w'|7ߌ|!33SNEuu5N8|G(,,ēO>o* z {/͛W^y&MBQZZ: vqW`Νزe ***0a <^x!JJJмy:_+V{̟?-[Ľދ{ sE߾}/b̙x饗caɒ%:uj?7pjkkPTTo޽;-[;> [⤓Nµ^3<W]uv؁۷6mu]1c`ѢE+xbA>Gy$>S444ॗ^BII Əddd ++ ?8 )B;!!??3f@xG0}tt[n%\?vɓꫯm۶ضm:Eڵ+?||'BMM }YՋv5ԯu7'=  . WoQ湚I}tq{$>9XpAg_I|jl 9j->ْ\NoNR^ŗUg-ŵT6dZpcY9e4|%tpl ]hSEf*=>~{S^z'rRa $9Kc4o3>ޔ+uyy9fΜ#Gy横… #<2i_=/CŲe0vX|Xhz聏?Gy$?<ΪJnU J !$  ,y^@mkVQB76(*C J12E$&!d G彜ߛ;k%g>5ozӛwl ~i]A?k׮]w݅~3gpuww#HI'XnV\ `仈/ƚ5k0o<_vJr)8sWЇvZ|ŲeZ<#馛TK.}݇/CCCҗO~wA= ZO<wS⢋.y睇9s`Xz5&_w\|rL0k֬ԩSq뭷m݆ѣG㗿%^җoľ?8w-[7 }qiaժU[o|;3Ǝ}sK."'O y䑸{쁟'eLSVUV =X|;>.bx[ނs9_/}Kq17ߌ fm݆x_cm} CCC3f >O`]v_\ve>뮻峍F6l?yz8餓i&XGO~\z饸˱oƹ瞋}p? ;3~೟,k/%/%KSOK.ѣ>Ǝ>K[ f.4_m ,5[b*ubJO+d{:&Zgꠔ7#W's9W񶺊h+{w1F[L'7jp:bk1W{'v= Sd*[ޒъ92F4bŽ7[{~nqDKkKp6bϳ^x NFnr f͚iӦ3Πn-vm7W“O>իWo;nvGsvN^v_um3ছnwߍӧ.mw}7~ߠlb=0Wv>Og=??~<ƌӆo``5hioo/Z{6mPK߾M'6m:} Ə^Z +W 'ya-6n܈I&_"~ǡyhS1w\e/y睇UV~8~_ᮻBOOΝs֯_=˗/ĉt6qD,[ Vj=+V`ʔ);Ckoo/;0z뭘?>7v~`ј5kVW_j6X|9-ZO8묳pYgꫯرcq'7 7bҤIm9lGoo/&0y淪6ɓ[^r%,Y/| -_򗿼 Ņ^'t̙ .ׯDze˰;V_Lymlb̘1fmpB#>j})eѳ>&Ife͵=LLWry|<ږq^b83"yUlp~Z0>v.kK73j㒇82ޣYgϟ E%OZ0[&BQ% F[#z 0[QA]ڛQ|3suɣp1y{e9FKQOjbAz/dQgr4,jsō7ވ+{xrQGx&NٳgSNʕ+q7bm {&f̘F[oQb7nz{{OxwElFc;7n܈!tM׾5L4 F7Z{gϞ ׯ=?^`֬Y袋ahJ|xއ8vak0m4E/JGn-L/| 9sfgyN{O`Æ O]xĤIp9`]J{gG.p 8#0yd<-ٞ{9X&Mc=Z_f VZ.j]T'Tmz%Ec5a5[h6QUE=W:aG9l#22fzGzu) rzWɬz>ߒדf>%֟)H\ɫzo=Zǹjh3Laag{9n:,X[-;~K,ƍq饗b޼y{[qcÆ XjULoc=ZW 'p׿ٳgcFfw}XhJw}h̘1s矏g?Yn>7 7?K/SN &`'x^zi}V\n ?8y ```|˗/GOO0jԨ^+!~tA1{lʕ+o~?8nN: hѢor |I|… q衇_%/y .O~&iӦM?˗/ǨQ000>ߎGx'pUWᩧaf׽ux'0o<4M̞=;&9̋,Q_z򟕝zl=Vwm[L%v,Mŋa^xevVoYcQǿF3?3/`/Y|78D>mGƷu31&k-Ru-=+9]WgmKLFSnT?,BK6ae{i#V~{<Pnye=ϰٽ><ĝiyP,$~-[Xhƌ /K>`sϵ188K/EkڴiӃ h=<<#8/nic~;:֙=oxpya~q '`xx0<<ۮMl4a0vX#?bZ̙3~nmŦMߏ~8qgb֬Yxk__׭-½aJ̙3DZvZⳟ,Ǝ<z(>amŌ3p'bܸqXnկgA?֮]~?f\'|rwzzzpqǡlb֬Yo8ꨣh40a\|g>n+WvG10~+C=wG3gv\ 100>z+^~8#hRi)SZZرcqE__pꩧbܸqXf ?Lp(Obul)JK.źu0}t.>;/?)n]v]v̙EavhD5w\[t)V^)S.&-[ íqdPhŋ1nܸ+ߓO>cǶnM zy3V^S:S0}t|câE0a7<2>쳘6jU6֯_SO; ۾SV>'.(~%KnYRxEh7׌VV&VKγ1<9qmآē9GvFwk%֪ə°}3*@,4<el`P|{4P&U(DQ#,Y_Ј읍/ߙb6(<Ӹ0yd~钯Jn'uPȽ{ҤINF7^~c޼y8p;%\Bx0c ڵI&^ >ǫ-^}8LFrhxVXW?fW7no[va?A&5#?8sL_k3K33xQhOT"^㤓Nj]< u)#kz4ԵK{Vrl}'t2|sV9kו ߓڒ({tzv[1_~1|Bgupwureg?&;+ sy;c*lY23{l ĤXL3L'^jU#Pӟ87*E굘|g̈EyAr=?(ZVoPjW~b:.q1=s^~Σaq2\jD叨y|Ls^c*&4' IDATGU ;9eTph}<0l:[!h =}*^Ê3l!Rr3l}yj-CۣYȖ?}j#_:Ǝ: K=S*e,-֌:kVZBo]Ňsy QnUSҭ2~olDP=ɬ.>&k.,]f9#K]e2={eTl\Sz _hv(4UORo\VQѳqxpD,o[4giۺdײ.Vn䭆uvD8M&/[~l`~me+*VW ѧy9<@%ȱjK[ޙA9;w8d?fv徨!+i[l,Q{=U(22g'sֿ=P)Z ,Nm{zlWO]f wbwƮK/=yvaoƃ>/mm``r vi'|_nj~;w.N\s5m ޘ1c|GƼy:|8p]wꫯn[0aa?+V5Go?2ayX~=.b<쳭p x+_믿7|sI&?֮]/}KXzu6޸kqw͘1r y\tExZk}}}xߌW\qx N?t<#7owxы^$7 0o޼~_W㏷リ;w.ϟ|;;1f̘?~<d̟?\s 6nZ=z4>l PtA8qw㪫j[™g1c .3<Ӷ^o;3qF~?ĉqgcݺuҗ58䓱>/K#»߉/=&#UC]4f{_>m{|m}` cpFfA; 1Y-.KVaVeKO*-}oO,O/ELF֣3-Z<`kٹH0^n[K<$ѪUZ2? &hђVnooͿ[s[cSFG77s:>K.Gٳg;b̘1xꩧr6CCC:u*V^{u^" Gźu:N6 < ~鎳3f@OO?uƔ)SbŊ}}}iӃGy6lh[4i&Ne˖aɒ%gg̘M68vaxx/ƲeFw7nģ>`#cppO?t%g``;6l؀{rh40uT OSG;vXXf .\qKoppӧO"=xꩧ24i.]WhѢK6mAe^,\pϋv cu2e dɒb 6&ä]DOȟYW|hI@+.y[.lI^Hw?o}f}KznߚvI]P9䟝^6y{@ɍ|: E$'g'hE iZ̞n3E4 8.>=d^=ztL}9gRbm3Yb5{ul,r-Ճn]iu>l}kA7Cntnu^do;xe|}>U|P5há[m2;[}z}Y&ٵ_[L%f[ lpD1Έ )leG⛝7Sb\PĔi$<ʐ*Er:ǰӢ1~N9QT4"ߨ7e߫󊎇3:^sDҍ, kJvƓx3=+9{+r-0ava^݌tgy&?0b3 oDac8"}dxum]3za?F} lMg9b: U*$Q.z+dce*rՔ(,Q 7IonX[dg>ճEG,j9/8E MΚc\&Yf|.-5n]ol/HƖ<_Tg,__GxE5m%S^>c9ܣgϰgoo]ZP[YU>x\L(Y|ԀdEqfk;/.ԉH*) ?':WU4oYgsp9w8Y %mUK^FWr:ө'\%6ӵU[ԈONR [h+,%wKeGS+ Y:܌_9eʉeuD(0#Q"gAԗJڌ>+PldNfdtdϞ`KaǒJzj56KJCYgXҎ2oni<޼kTŭy?Kۜ״K_RnlhGMӟZ,˓%ey᳣Na~i;{5Z{lrZjv$rW7^-h48+rb^R(S+x6uieȌҮ*.Uf1✭3:x=G/2|[J~Q5OvɬƮY]>6%H!^ҷ 602'5rs2hŸ(-*1 Utf'I8cxXrS~%&OV&O1/gXM.?D} lXL,KV~LWk63*W+=*:k2Gx.?%cF:d]+_TVbWr*1?ʦyJz_;GGE GLN!cy^,yluc-.U= :G9=G28jP=Zc9ƗãjS9D>Y9W,P`#R:Sb ``'Y>{zVA%";%(^qYQv쩂;EVf+;-g{>beiYG~ R)*+ _RUvSsj` wgUrnF7?ECӓ_xjO,"{(ٽUb[?ÐɵJO*e=GuN|rGlbh62\ wVrU&gꈊDWKQb)]e=UnadUx:=g5cG~P'|LJofeꡇ $U@2]cfiirS|n@|,@M@ KYagDXG(={KTҷݙ-3ؔ>-U&O?<)Ƿӵz騴J6K/+.J&&Q,fwjӯґ S2zElOYmdfYǫy s Tԭ\y5bVg~oT>b5kō7(}fJ5aeGUNɜrDubPA;STXU=9WWzͳܫ*WwW7YwUQR|hFˋ)0*>3$Qƣ۶6A[Yٵr]9';&}o^7"g/z7K%zO [zEPM+ۛPp\)Ӭh+^g.{ے}Su5^S|k<~tĚVɤ|=ZV7Y\_||=h+{6 \7W_kG+c?2Qe<[(tź_x3YR^ϧ"9,Z,Vn#Y+bup:eO٢,w :gi+[%]u\b Wzs^/н}ɳGځޜ|#CC!X1ҷ_apxl.GKFQ6/EK70P| JeVPjbf2+ږũ%KvDXSjҽ}Ȯ1d^1np|w+v>+3<]22Vt(pYbbNYbz `wdr("ʩ=c<:%-K901}vXg/V[B=$(>&W"yؔnQdPpz %v-CCk^NR3v]cg#GxSrfqZ[b99/{uFE魜xzd/êK* cG('XUƶiwء Dɓ]F߾bd5T M5dzb|}/{xKuFTW2竡|"cGFՙrZ/8AW:(NX̗U2,弥oh):Q?TU _;<6/FO՞d)9-8JHFXeٞLnF/ ;;) Mt^}S?%gZlQαyh|-2U()y03~V_b&`ev0<;JJu#ұlgeVHw&sCx~a׽zӟgCɭdV,h_Ӎ]lg&fϞbeY윲gad:_3+oy:_WGox9FJKeb;S?(3=Y,dթYQ.P"쓍 {`;k -Z^Y;l&|QK##ׁMe^0zFzf?/9/Q[^eTQX2 ..Cܖ(Qa0U[b6`4[&gfYzE.RWnoNj-okַ<>=,rO]ڪ+T6V؜U>e$ǂ ߏ3gz4lY{fb0okGn.QcKsnnW[\vg9 ûGVۡlUW6;0Y7ËK;Ę+Um?unF\n5z"#0Q`a6i*GfM6)T.J 9٥°ujnN^^QfJv TzBp)`2*M]_yIg/wL/<][ ҃՛#` 5э pϿ IDAT^UP=j7[n,l\GF,Ug'+x43H[b纼3ZE<=$:c1seWգW@ZU uef|:{{v^YߤTt^;뛌nG;W72GS|||$9UQƧ\g5Ub+^%?XzMe 02-f/XOulc>, Gǣ=3}2z%W,.ԺҽŰg6,0l.ogM˳[0|a<؞߷d5dfnGWO6oo=[C:A7=pDcKywknsۖ䇭i,hz[[z7u=ږ_#|ٽ[vl@+r?k% uSZږª.xYgcz2g ۡ=#dmɰ3?&'Xz|g?n%̺ccl9Eť[4bbg,&byEdm屘gV_BwE!;+%ٞM/=TƂc dJd@U١Jʘ*$̠)ʑMd^r*ZoRjNSv*f'U,NV|%vFɡxxafҳҟd(^^`aA2yE,S5 *WZA'+WjDYUUm:]tX>gpESXҽիҁGˏQ zӻ^T4X4z{{#yZNjO$;2?(i:7_<Y_QΰuQ嫬XގdRX[z2% ;C.fH7^VbRYeYXވ(z tl-*8V*13-ag@ rd=V<~\OxIKzt=CJy"-' \(x8drK'h({͎¬t)?e- WolmP3ن& Mg{uf+";d|yhtgrI̪d3{YS@<A`\[+H'';WYDgX^/F=ho&"ޑm:1;uG6T}_{ӦM3:Xr? V&g&sQE9dl}묇aXߧdzO*)٢ض=q.)z~7x3y޳MZ㝏։Am4{,`V ȬC(z$af>Ljh3GU폂}K`e"UF/I\{"pz9,sAP2D8~_t9UM`4NoMɜtY^5- Ы?L^o&ãgg2f닥PkGG!yDғ7Sc`gխҞ /easut冒f6 <֌38Ɍ:6YsZr,P^a.xk}oiuKE5{j5ŝ<"VxJJJ' zpg%Į)h[fr[^YbLlOdCWѪFT#wߛi3^QI--oVhY^ooi0]X5nZ,O濑-au`*zF ci̊,Tް-^˗gmSꠒax>=l=I9߲U[#lF'QRT eu.mlh++y> WO`2}+,O{-nGɞrUo&{cDU9r'-V9lWWSfm_"K%0Hf+J'Q|KZuxNsb|5*%o+j.ؚMB^2d2vXC+wt%;b::5penv6l\1| LM3gg~QzS/S O5)f^ ^=\uly|z+^,GcY\v1ުG)m'b~/7xyy U e3"ltOxdtmxxTV7Yt*o:ǒo&HViT`lT*=,F&**{)[,OwgՈ$VtXa6ay\6?3֧؈ gkL_, 0<|:ӔDy$܁i+ lAPb_blWTdV>aKks-20*vҋ'=cxϧTZW{=jyjEo(xʹ ~*U3bXgf{lkpSO0*S-v^^gG34,*G,k^.Cl-ҝ[f+7,[ %棪fd2V&]X}+%|TRfT"bXu.+/K٢sF>e (_ αlaecx,1lMyϞO0-KÓѓ=Yx(#+t)G- fXZ0F{\%OًLRxcGTYQsLY )0zU]0ZԸDtf_Vcex6QrgUU>SzrWfxb "8cƌTL5ƇJF>UsٽnU1* k3G^%np2lm߲8޺(GWDf `v>;c1Ȝݞ JH WQѰ0QrY쨛sCɦFxXL1zWՂ؇D]g,-t~x?+^ntdN2 *#W=_0Wg=zQ|sd3V;{wXn[LLyCO&+1Fy4sJneb)GaM:,VLQڡg4s2qh9<_5`5VwLFup+fh3o]|G}T3bX{09- O٨ m%ɫʷUk(^ F5JQQgxu=܃ H](UNz(ϱz Ƌ<^L[c9v_=[>|JdtXVyғaurޫ ^Rәb8LL^QȅϘM̑<#{Vphy(Z,Tf ;~tWU>9}<`kKk3'cwHFTÓ{.*=]+*&KS BaV--KOŬʰgBdaQ2Og.D1Ɗ锽:C/WaR~K^3ʷ^)#[IGlv OVrx9 k&&`~l]X~a||gbFg#.[^{8~,؋ w^.b'FkqgxehaA5TL(a[2R孔W7q"VbA|-nEb%o˃a.ZʖimSUfYz؈JL.Ɠ#UC72%W/rx5lef:`r4Ux`x=ыh2:,(>Y[{gB9gkw֞vO3y6P3*FR+gEryQXՙ(2d򃇋ab9?9r캲[g1@aQgLS`všrd|׫YJ,#.lF&=*7YveWg3o1^`1%=&G=b{Un_OPX=+"lO2U1h2{(J^ɘC=Y/bdP3ɦZ/ewTaEaF{<;ghP)aʑ"Dz=d,&>3JLEp*=;fU8,/cPWϊWѬQ‹S6h"P.eiysR,GDQ{T~8KYY۳L6,Ӎʣ)KYuL^f}ʫ_G2fjWn߳ZĞKy;12lK*Y(!Z"n/S+XP0yX3OK?ysN昶{}Շڣ >WUFEh@yTsP`E *LQ|ْMM~s)yyأb۟E_y\4{VC5;jD5FϙfϾz>3S2rW_n7V&6T=ꟇI#D+V|@bN3}e|O=[doI z<P+Q¯*{3Κ+ FkiL[lBVb h퍊:ۺq&#^̮u(673T0z/vVJ+,.'AOb fưAɒs([svPI֞ȒDk/6OIs(K̹h`R:|ؽQ=3WK=G@Gkk&**Gr7ϑZ|mEfq1Znх!;cZ_fh>odyFW66{vV|=`<#Z,~ICz_C`U3Q ^haن˷۰:SG6% q39bRybaט"X&;y(D2Yʼp2_Qtebc(= *۞U)K̆Zhslm?< B/xį23g={:i{޸gEcF@G3j|9y.yu^8mFa2>l]SJ' Xxh6KذҶx2Q\ꤲUf3]&y(4 M9E<ޙ ugykӉ[zJӧ}nuR2C=nyU@o/dIxp{Yʣxz#:NLoϷ=Zdճۋa/YMPU+go}ar/SmAF*_ѷWfG9y8MR ;wQ+#);̅25x_PIl<h/.jFmf4eE5庥,^mR8lQЦ5k|5:z phP01~pymѳt:f.ϳ5(yxU`G_VgŤ쬞d댶W ӟFN5~fS<=zg1zzzbGa:_̎&^ Y_+))\W:N*5˾/),*PEˣTgYmy՛CǞa4.x^s.Fufg\w ^vTH_ueX_ݧU#bnX91پgٚVtы0squ@B9}tٳgE\aW_ѱ˨a}1QlX…hh%d.k XakQ%OhG IDATbξ+J^c_z3'G Тo;95%Mհ{;<:%?۴zMexYSO׊.kb]ɨhé|bRͫg+G[x$ӞQ|Y`>͞.6oX ZxSM5hX:^zUcӃZ:6v,_|)SԹ2(yv[G<ꆢߌ/3l6G6*g  Vp/җ8~ vox讣\HaTgԥ,zeFZvn_+vaVˡM4@H|eC]̕.ݞ}K1d{ OY 4hY+_ ?D3Ac LWuc]+wiӂW[sW^ <{ރ0=G}<3DG=9sJ _bޮ9e5G&+YFYĂ<ʿ"ڥΔYcѮx0^씶Qř]lc7iyqp'*֘mxx!Ojk1h-,el#\λ0&l3xܺall?`=me lSfϥ3SbPMdOO3 @)@l*Z,a.Z( þZ+4Lg H7+{~l6۾zVWNk񪆒EkZ4-FnlX"0ʆ̧lv!&;Tn vOaό6-QÑ oUgu6c:CkH4O3"(,W"Zl]0wۨdyU^r0h/{キ kXz>*y!KW'N,m+GQ20סˀkٳݳMSlr=ӯΞټ0F:tL(كD[W̻ <`2(\`Y9T}J#4@痽:җһ*~f0(j2~ uS Jf ,rJ xvm7|o?:?i$L2FMuզOG~&*Lmٜ⩰e0JmǨ1ܑ?2d.ҭGʠt^%kv>UW36QrD,^ eKɐ<0ul^-nE0bWy|陽zATߙUgg-\FUӳc=;5M_Ɵ=v/o^3*V6ou̞$f{?h4D#bHšJ9-^Ҿ 6+褏):1W =%&ۼZ*FN9*N^2SakuTA6ܑ{Ƀhyg^U2DIl֬VsZ駟s9cƌܹs1n8\p~n ~xoO}Sشi6n܈xꩧ~z7Ѵ%XG*D .Sd,K>{VlBHb#[2tYbEgWEyY܍(YezJ O=^U^1kJ֌<ߩhx5Nգ:Te=ʋJ򵤥0}^V2#Y Lmg2dl|A|AaT105[Թng14Mg%=^1[F>٬n&E1jxl|J=g,x,KȖgTU,PgΫh[xɣG@MAxzby2ZfzuHVfE_ټ÷ոϣh`ܹ3f &F?Xf ~_ߴi֬Y˗c]v٥:tLX,_Ϗ_5dkQU+ϲo1eHb\aES5|c|5}XS<#ky>n34Y4Ta )TO敝&رcMTZ̥LQ=gX̞=ټ˫ly0]*g~yfH.**)x(ӄ2Z|rѣ1~6o=Əe˖æMZ?e˰tR| 0vXiuOV^/cȗLkFv<|Zhz{,Gڹ,/~gSα=P{_0_T{+{|=^tɯ50>|.oyqjGå򅇻ho|c]xzr_(G*>T<"Ňy#S+f8C(1ylx3Y9ŧ^/h1ޖkX+8t,s8+© 3aGsOt~lgյ:nj؞vC%?Y&ׯXk֬0i===8q"-[7bҥU@jh+,<=ެSrM^h(i{K&Y>V8ZT+LV{5xx8J^2z|=^+S{3L.Y&<L^E2с9e,_?~|JYq2"nO_Frt7+?ٺu(NmwF哭mA;SS&q4 mŬPa}h(%vޛSNEQaƈ.9 c=_r=: ѷ<53)^8D~F7⦛n¦MZkҥK1j(^m|vi'\wu+1c <'?uq=Yz+`GV 26tmcŋ1^GcE"u_#QerԹGV%])\J.nE.TL(zJWQ,0_E'yn{|ݞsbQ7Ɵ]MOt3 2( h8qq EM_'(DPx5qJ5NѨ80 LMtt*Nz>0orւ {?{޷jou߇/|stG~̒~kl! T2gg8dd5Rgr=İL=v&WMW(q I]wm%ef:t43ֽtZ~8<@ߎW_}k׮oɓ'=zA3HUV8c_0}g{͚eGUfCo߾uuu|hްKbQ1Xb5ğ],oi7T!zp>WM>Iגb2j2,켤 &۳2rrY}\w$sD}Z2aM>IkП}b:Ӈ8N4jC"%k\VW&K^ ;ٙ%%&"1j?ģaW29V6mK,iN:'? .]Ν;C˖-)2W?θ|OI,%Qa"Úu.fg"4,SC$Ff`m0~l)5Y,Fbz?ycoܹsO>ömдiS 4Æ C{;8&LG}rlڴ ڵۣ?z 7#.,Lc [h k̷4mYdt\vʨegL͐ցAےՇ+e!9+$/0ձ}XxC7v mR f/Ġ%gj-%+!Rkk`9Y޳D 3Vm`<=Rn&&OܣFӝfHSCSܾphYj82o0M]<]m/ˍ6yڜh1%9r#9}KƖSN| d+dZ: Fgld| O_kQWW$ e qdFF੧bSbi>-;2Z'e1a{:klrhR4F_c|"cTUUaX`0`|(..$LbѢE7o֭[ zB~~>  m۶x]9^,٥. 6!G cw]ɓ'2dOwKCuVOF_Lt|EWVo-9&gYOêDy4T7,'θ eα< b]xTC,[h-?؜۬!IאS$m > +F}rf0c#et[_k$L%A*vy5 .n@HLM{7#^-bؤ~50L,tEEE(++K? J^?/\p۶U2Xd|"K$nF=_/ϸ.OYpH4>c \qxwF¤Iҟ˰H&x衇0|p;!5ZmYh[X,Cϳ;F(N{h[Cx҆U:Ð)!*rZaxX\>٥ L1XE{~mTpy{A{q>HK]I74_f:b3^.g݅U5԰$6#k\C^9'F|&G[g(M:hs18ЪU+ /cF߾}B1}L8SNg*秿%56mڄ5k֠gϞhҤIF|Z UUUԩgR[[[K]vhѢE|Xd :wrq0}[tM6Uuի}vtd2+Wb֭3pY6mBEEڴi/^:t)ؘE#//]tA"8+V@~~>:uF]ҥK ]lVYo-o$v-kNeJ=zg ыY)O'[\ֺe%Z.t-Oj[={" mʲ\j%lvy[6tK\HVO5e4udBp.F9rIv3qNM~Ɏ9 s2iMH{ ȥ jb:ׂV \joЦ]{h.i{Et%{ҵlU0Cli~¥C\)qYEEI/L kJ0ӳw|}ƫ[n n:L2FB>}2qΝxG1d~铁{9ssNlڴ ѫW/l۶ CEuu5 d=EN;vŋq 7C~~~~,Y޽{K._|*GEE5*-ߊ+0n8 6 ۶mE] CXnF. V™g&M 7nyfL6 3f@aa!8 \ڶm 0a 1H&?}b̙K.QG믿7|3fΜ=z +Wߐ~猯6/>y~yʋY_]b?]Q⒲02=k9DW򫆋lhM-j'֯k OfyΊږ-q K|VdzaΥioA̧|Eƌ;7,EQCeB&-5.sƈ$Iˇ Y-k9 u>la4B4^-||~|*">>OF+dyWGꤱO?4:u^z!"cYq̟?>DL&QRR3f "==&Mb<(//G^^,X: /PQQ8s=M⩧ɓ}&L_>|8oߎ.(w}^{ [… 1b 4u2Dǎ1~x+Ĕ)SpO=o6&MN: sUsϡCOC/]v"աWw܁˗[nEV\r̞=SNEMM nf~7n1a3o]>x8wyo^ut>?]ܸeBMlh=AjߊjŐߌ|hѢ6) /g _#t3 \Vwl꣭l޲Z+c~Gdeg׻&r҅ڰlY8ށj2 IDATUSbby)$`})b50&(4FPsచLjlidbtlVc5ܾ=n-lr-MG袋|`55>5m we\ڕǽgZv#gopH!#x*EI }2ĴiࡇJ?oҿ)c>?quֈ|0̙S__g}VۑL&lٲМ~h߾=`?~<$.\8q ' eee8묳pm/?xlڴ 6mBE(,,ѷo_;͚5Cfp9`񨭭Eqq1lقӧcݺuؼy3vZh[n̙3qǣcǎh޼9_}|M 8۷Gii)u놂,\555ڵ+g̙3Xv-իW믿W_-Z cwy8PUUر#N9}]wqw}1:(5ʳ%=֛03Vn.-܌C)Vs}uXfݛ,>5|֫O'PLJ|!>jː:3$pZg4?`zha{$^/͐ ?{M~%%*1i|ҲI,KLxשAW nj$uK>GMɛ WIHaJ}erK^MM?jC|1#5oyg>k" ׇwIK_`2i=,IȽ,4۰xe|gɊG} %c_h{͆^+䂙'IL>iӦ᠃ST<6m`*V`קd&c,[ z*~򓟠wިCII dZWcg|So!8͛]: tؑ&%O _֭Q]]F|=z4EyÉ%ec(`zpj%$qd5.$-hU)\>R6W|="VK֙|guoXaWk(4=fXXҴ e]Ѯ|,.}ݧ5_&>᛭=֐X>ZsuOcرxq衇]zqI&8oNRd믣wޘ8q"F38Ybzkٲ%Ҽ_WZv#<~<y?:O3¹|riDӧOo>;8444y? / s L[]tz-̙3%%%{Gu{=̞=Gu/i1BP[NK>VP [q K,,Byֵ{+hUda -gh [ yxKLM6~>YŽ;Э[7`ΝwzzD"?::uCL8ZBCC&Ls=7Q׮?O1rHK8//4} #R㋓g:pHb5j7[k߾}ƻLg~Y G :Zd|nƽ[)O.&vVS嵥/׍J`.>k_6rp1x' aF՚/0_f|f ѵtet|Y >d% 2+a[åj0hf̆ۗ|p'vާk"Vn>#u gFCZ1W\Hu֡sPVVqơYfhllDmmm>Zn'x<n6$ $I 4{,`РA(,,Ln߾=z2d>C7AYYCwިH-))Io[AAnV;sO?(piO>$M(1+H$8CqM7GqF< 6 .Ę1cPVV>())AUU}Q1vX`ƍxFt^z)K.я~(ФIDQO>d?Ҙnz뭸؈޽{Ghٲ%a:cǎ{4mg,`M+,Y%yHd|}rܯad _|?qhl<0p2[-K sO>>)ͷthv0< [^'v%wIcFs֡eK\5n}4嚴3I 5jjjuV΍̰_driر(**Bmm-ƌ<ظq#JJJ%ŋO>֭[Q__֭[gH&дi$Ilڴ dm۶͠АÖ-[z噍7"'pVWW>S!ƥ;닯sA3+NF yyy ɿVN ѧaOi>C xQ9!git}[Ybr&1wFx8^CrYkhDMP;J;=Ch 3K52piH^\}(,3,="C Db mݗXl*䌖Q }:&%/ U8ۤɗk24qu-е$HZ dH>> 5B&MNɟa^.|! f{mʍ{C19,WRH}2"{^3Ħ8 ݻwǢEpqa!yVu۶mz\Zqe|_>fd]0ښ\yUqe? ̕s=ϐjube軸8rѝ`UL*51C '1i8,GetԹ5la}͋t>XPYX4ښ_iC#ߵe3`i6Ŷ24eg5uţ\{㴽~f/V$BV]kf.?k-f_s!_KKKqa3jWyy9;0?&$²8C|%&h[2X_}srw܁k◿%w__F3Sg>V4[[V>;//15|5ʡq_5?5r[b`g4p2hВ'4Bh'}wԩ=r밬k}i4CsR-p\pfm2ZrĊMN5Zү%Ӆ7kݢ?~^q^;2?rh~ C&x?eȦJι>va{Coad|.C{b]h˳_Mfbr؂kM>lc[ӡ:a}s붥W|2˸EɜKkx>^#8sϱ!k5kp]wOƄ zEY,9wl[pi4[HxL][PMbz<vۗR/>_r@/Z^eYw>ECYYx|ҫ͇]veNh) b- [Irn漲a8"n 25|8S#c$^H㲷܆KtBK%r}d}͘jamHE1b=peŝ=25{ +[By%vKgpsk<|5R-4-%f^.ri&, e 9UҲpY&HR6>h<W/L5od՜Kw:(<ؾ}{0.~vN%i5Yu[Z aUgGY-<]_oolňKrΪ7Zf:0+Df-ّɜg9v%XiB,T!rheI5499Ko.3vF Z|"ZeK_ )TnU V k癮c9Ox1s[8XM?d| / WhqhBy2̏}->e_.Gagmh ^.Cxִcɒ%=z4G}6m֭[q衇o߾غu+n݊:0,ndaX{oƇc|B5lp_[{ qg e ?SsIe >[XzŅCҟ +cA%D &Ak @&&& (W%mMn)3*%/ZQag|-灌O |Mu,nܳahgI!-8{L6K#c|Wt믿e]ǣZuTRll؉+{N/K+s:mk=U=4nr fM̖܌'a‡Շ!<Q[>Z=5B-9˧W+Cu|\^'|Me@sbtybX2#ik>\:%zy &B^xki4=r霭k;=;gaQ(**wwww?68FAAA|AAѱcGXG\tpxduUX^ʪ}m-r>˽g}ZmzX>4dZ?na[= Q 8?P}7rZsֹ+j./ˮ!t>-?,\b S+sT ym 5>M~˩\ ֺek\ۀaes5}OrI>hX W|J0L&M~K'qZruMZlk9Agƈt%á-Zk!G֥l1$$SwN(j,ɹZ> it}>-!X,QGH^ϯ5lfKhllć~CPZZ8SOQPTc$ wo zM!3=Cc%f#kKWK5M>\}s]-wX5_pc\H^pW$FߙL$n_hsg5]نɐkƱƝg$YG*(ʒ$S |geiNŗ%5ӭ˓ɟE?u2,|$qhjhCŊmF:o0٘F[Fݣ5Vӆ3ip0Z_+!dXkl]c7,|'7N_}f筼)enΰec9Mi5YOTVVm۶8cqE{H&xtd$.qZo/YZxiu[9T+!U-U}k {!×]|ha`/3Z>iߐ&ϲ} ٚ6|$%XK9yn+s'fN+\|iCO^kt-Y9B}Dhܡ5^ZOoPc~è%9-C+oCVdJ+4n|Fg W-Z }8 z˵> |f2RZlMfwȷ24ZF¡Ł&YHr8K(qHl:Z䫖YpJ}xٚե;򏪐<̌td`ʳע `&K-(K}حn}*!2k Wl^ky#bG@Dl |faex|copzwo& OHbsugfX v(- :w ق~ώV\rCOՋ˝ؔ[rLܹX=Fh.Z[H+jX4 ӅG`4VBt )TKںН mT|N)e I<{ԃ *K?e7ߧY\z)~Lu}/[~صR(2>e׽|Ɋ1m0(н{wt-0ZƵ>s؈d2Oznъ Hk:[uhOAB-N3>V4X(Ian/2tA+} _gg}(wC1'LUdgC l:S \AI{(eg5>.NCזV vF`GNb.Ə) Bڀ闭ivQSMO;%Ikod}͊_}˯D)kEA;Lԛ;ɪ{dpY<\=ahjyZٗghaS+Z` s.%1fI,f?ޥig`x8(Lׯ`qZG IDAT-*mkA"Gæ]{_Eͳt-eyB|׊O0|Ѳ3BĪ>{k(igBΰ&N.&Ӈŏͳi3 òy7Xͳ/׆uR"@ee% v/$3r] KUUK|À0`Q_ڰaV^͛7}ٳ'ׯǜ9s|r4i"y۷/ uxt筼ouMܲV\|w\s?h2L&>2>Ԋ6blL̗|]85L?1g0ks8Z5m6Bg yAӇYKq jbB!>էynzaJY2Z\Yj =z4^}U̞=Çǔ)SАū7x#FO?sɃ9w\uY7n,Xٳg.ɓsNXhQ||RZ .Pjq)|2!/^{/kOYԽ'_ѡOƇmgOI%f]f9$;5ù)t`{e=u ,ZSt k/@Vf' -|}X8>l]6:uςܒGA{piӁ8BZҔzgW@M7LRҳ&Q}6bԪZSl7&6<ɨ5NRw>H~-Ohgaktymś'$i'a09m豠% 7~1OGV>,Ə38߼s,X%©ُ5V# d[-b;gťϟxVw?h9KP?݇3;kjHY=}h%%OӅYx[-Y>^:k[v 0}CY_|Uƒѕ]ᓋpVdݵp2j:MM>{&M0`@^RѾ}{}Y|y\zka0c4orJzܹs1dDQ˗cĉ6l`xWЦM,[  wމ-[ÓO>^zaXd ǣk֬s֢E8q}'zꅍ7bԨQXbZlիWcʔ)8餓a÷~>N8Xk֬hٲ%Ν/hhhq裏Ɩ-[piaǎhٲ%֯_n'x":{ҥ 6n܈N8կ0`̛7xGp饗>[okżyPUUkgFǎl2r-:thW/RoCII gرcѥKա[ng/< 4$-?>4F>j"/GZ[}+b`Cb%#d;o5W ;F%bL)s2k.#;kMfR "&v/qINYGya$V50,]k2`Buߐs8YЄn:jgC7ro=T!:Z{%MP|&R-)dB#[:ůccܐgdi}KQʛL&?:uB=r؈e˖^)c|ݻ7JKKi4|L>eeex0i$ 6 q .%\"lٲ#Gk#F֮]'?+ >ÇG>}0ydt'OFQQ{1444 cܹv7oxOpWKy8CpwbÆ OફBϞ=Ϣ qw܁C9fB~~>^|E4k uuu(,,_|> O=z;v0p@8s =z~A/ =XDQ'|_~%^~eTVV_ߎGhhh@^^fΜB_ƍqI'a˖-8JI{<}?5FGߺh[qfXk=OȽ>2yZw7; H s`Ma!1uҒSB""CR:]b=0ta9%Gғ2g֊)X`r1i} .Rs,)@fdbtYM>֒֘eO,H~uM'˩V~~-Ɠɧ&áfB7C# M6θ8BսjRhhhya9$~}>]ZjGB&9Ѐ>ƎaB|5jTFH]ر#5z~::v͛#ʕ+L&E'ƣ>Z,\0}w8ꨣЬY3r!hhh@UUv؁sڵk-Z`Ĉ_ܴiz-yذal;vŋ[n: g}pꩧb֬Yi]_O<N `ݺuشiڴi+V୷իQPPr4md/2.]Fh8C0{l]k֬W^O>UUU7o 5kN=T 8묳sN_J?PYY͛>C6mpǢ;w~3j_VY5L .h=|Yna׾uICsdrXسb3vV# kTXMl6hsr·ե)GCk嫜*Ԗ3l}8^$W_Y@?Tєkl -,5_2X5Vs\& ϻ]|':I=֐E=el*rhzmY p}Z ykz a+y.E$`9)EqUX^g؈c@g ؈3foǽދ>ck޼9ʨ۶m׫vuWSS+=ZnN:Ew؈F֢Yf齭ZJohh֭[ꫯbԩ:u*xs1(++X4i8ѺukTUU^y5 UUUԩڵk<466裏_{'p:,|իnV|W8pꩧbƌs ̙s"LW_}y桦TUU"EEEXv- //-ZH맺%%%OYVH`zcѮYLhqB 묶'W>V˼˓\. |hL@=pHZ垓N2IhM?6 G.^3il `yN^ks>1,\yj3zVԵ&՜&tCz&ʇƟKKW6&Z g-{[1eLZgCؚU9քt } 0Y<Lywhi6e>Ze F۽x8 򍈷 wy'֭[%O?c'/˗/g}۷oPgXd ^\|Ÿ дiS*< ,Z(`4&Mu1b{1L6 ӦMc=iZ|˗z)\xᅸ馛pbi^Q /ěo[nOYg}K,g)S`Æ d)<ԤH#Մ(vN82PsG4n|FU[M/'Ss )$]K`<{xk4%>-id'CcɘG0̡54L^M|'kM 吼rttӽ?k025Fg$Vkk^ 5N.6j+.}M}$5~.Wy7k_ b\3HY_O{&%;h00҆ZMxCk|8~gBf# e4!b!՘9s&&NG}tƻAַ^~etM9r$v؁>D}Ef0o<o~]bѢEofqoP__[o;w\K.Q?1g߮7o|ۘ;w.ub\pQ^^r<釴-[bĈ8q"ѽ{wTWWc޼y8묳oƍ뮻_ K.q"ck1 Zw} ;w.n݊.]`Νhhh@ͱj*,\ݻwGaa!v؁2I&83gA~~>=XwW\ן[0x`~xкuk 8ad2G}Æ ʕ+1sL-ҺޥpB ߛ6gZb/ ;yX2ϥ1`Ml)_-.Oy%wOsfS3]I>&=4!{, As8+i>K!XpsՁF'6صhМal(.X$oe/%}߾se1acqhʥ_xd%F>ĠVۢs\hBQ%\}}=׿bɘ={vBkBCW^~xOeee[P^^;wb˖-_|Woqc„ /к}vʻ7p:v|yyymڴW\B#UqDDѣGnj3Э[71" m۶E=2p_:t(jjj0m4lC̙3|\pɓH$1:u3~-.$ bʔ)С$: 4i$-Gii)L4 ~)Zl/+W̐լNL!sCÑK yސV=z{MN^N*׬ ̐FQ `=kgZCΚRig}t,썌۶mÙ^~/ Kv1yoqy˺%c"Fҝ=9 ~a`w" 5ڹbO{{>d-583<סO"%wIcNv [w[÷ʓ\ȺVB's||4?r#P6?Ùj=c 藿DnAQ]/B-Xo؃aݺu[? 4O8L&1zh~h޼ypWΝ;qF/DK?8òI2D}}= L&k[no6mڤm߾7oFYY{Hxwpe/͛1Zl7nDfP\\6l@^^^Flڴ H\kjjPUU4 fشiZn7}9O :8r!/W>o||1k'|{|I=o=+0!ƗuuRPP=r|Mŋc̘1oe̚5 SLA֭3lSRR,5w2Gee%֭[gt-M3hҤI=\^YsBLFɒEbd}Fv}(5,ZLfVO†fS߲O/<̜B&(ʀ1 e _2C2~)ds!l rX@ӋKH򼻇5̗f `%9-BjX|KYs.hx|I*rDa|-jhyg?Ϛ{ۇI+ dMi}1Ӈeadg>/[c5ӛao|ħkMOV/MR +[gX-]O|z0kdb|_غu;L?W /_B_,ҥϰ3_+gab|$60=WC}kby̲+-ms磯FKh|tb8|>K\?ӟ/d2c 7ܐa!ڈ(WCiHnrhp}HaG1`!t;?Uo^ΰs>Jljl/O,o00pkqא D0O>-d, Kv]-b IDATZs5>84l-_Iýg+3L |dzk/nO磠H$k9H$H$F hpaM[rۯ];Ƚ=^=\rF+)jVk]2Qsl,7Mf^Ok65!B5 +`LHF9阚Z!wYq9!K>->Šұ03jv>Za%Y+$Vקwvɭ%MxiCYEYiJ{ܹͭč%;o5&Z~$e <+i2kMu_-2J94|n;h{Z/4$,R>_d qKw&,1H|}7ߌ/oJ$,=|}+08 c]|uk:W=άw E!E뷤 C\1÷GVuI )hYn5Zaf 5Ws-8ս$ɠa Ae Vԝ\H-y-qh$}V0} w2|ĿG<&acŬC]vFλ,9nv.w}w|GXd ڶmN;  tO?իѾ}{}ٳg__tG}t\}}b@[z9r$!Cdpϟ?W]u~}LV+v,14h:qWc֬YtO?!8}=>!"V3].-dߥKx㍸0uT).Z.~){-\>^RovV!>WhStߞZ=oxX?$k, 7݇& 閯J,y  g{x&K[:ם,%1\^s7b8m"hH;I2![!_j{Гlڣae;ZA?-|OwƗ&JĻ]^,*X*؅6t'C6lԩSh"t+VaϺW^ɓ'cڵҥ ,XX`˵3<K.q7bȑH&9&Xp!6oޜ۷op04,R8𬩩Icن[-|.<ռOlYmuyY&7AGOq7SNf=ea~B䒵L'ML.igleZjx-id|N˲'Ӈφ-4|V/sA!K.729YmH|V_dOXBIEh)Mx123>}yf,]˖-CVзoGcXp!]_,ݥKDQիW?GCCw^z;wLի1:t(n:uH&iׯڵkزe QPPh׮<@lڴ C}}}x&'Z]] oAee%^ܹs}v۷V\E}AnPPPXhڶme˖aҥׯw|q:ڵk~EH&i_cѢEh֬8j*|rD.]PWWGW8"ej%k%?rXڥaLab0Vrd`4~4t]B}Ȣ5?bֲ>@Cz~C Ym+,ZCbWY[=6tVd ,- )Vgdk@҂ƽh49kMI>Vdzbd`ðh -XM,1J#%[K*;M'>x?E[|=AOrN擐ZAَ!ϧcʽO}T\\}QQ5k޻h"\wuxDz>ٻk׮h޼9֯_}Ļヒ]/@ӦMK.ӟ;D^^yL6 | ?|p(--ŪU0vX|xW? 8sЀW_}%%%+qGK.o+CUUqcx1uT~hҤ >hTVVbXz5z?Xf* 0zh|8p V\38 @mm-*`͚5H&>}:ڵkEa̘1h۶-$̙79r$(¡N: 7oFaa!*<8#1&L3f`РAذaбcG<عs'~<8bΝx饗мys̚5 W\q je ˟/jL}-gYFͥa _`+5x%;wXjѕ8VΔ{$/{V s&Kc-e"҆'{ԒӽךV|X`l q+ }©aPLGBꮩvXJ V-CΰB{vk?oL}'={6xX͚5 .]w`]}ضm444gAMM 81cp#"ܹÆ /=: >w_ڶm;~-6n܈(~za޼y[qF7_~9Fd2k_}媫q7L:7ooMuo&YUeYYS2, LNL(L@j9bشF6[Q!y#OXksĈ{>{=Q1r n6jիq뭷bΜ91s=ñ; ˗/1or vqGx馛8ꨣ0w\LLL`m5\VEk_W_o;-[8[lʲ}݇/wy'rK[gy&*|ƥ^EN8N?tShI鼺͖?ãUbauLUT'i oPXj{f35 y8+ V/gy02~9FӼRN+9yJ@0t*&꣖ҋ=͢޼r0*R5= aɕ{? *#=^Y뼜h5)@]?!Pzm<@]U|X:x5Gk"_5ᨁ^ײ,CaiP,Xo&uOD?FGG~iO?1>>xCYg}/|/"^`=v}w|S¥^׾mݰ馛bmE__?_ľ!7 ~zl뮻0667ڣ>]w.] qnxmo{fΜ,׿[mUS_⬳j? h5ȑr!(f+^ ,]eYbll /p7˱l2,]`w}o[ޘ?>ʲ̙3qagEQ[;h}݇zk׮AԾw 7tS_s!gO=3\Y$Ƣ+ٚش~zmiuen]+M{m|7SJvd&i]Ld=5/(ZL`]R^s~,ʋlccf(SR%lYp2oeZ;[l4֖n}.L:b`MG0wȿVVb_C?WYQb;z-Bqy+At.i8{Ctb|8p'Rwq-Zx;߉V,j044o|#.r `ܹ5k^hȅ W_}u'?Iθ[}W}ј5k~ӟnipc[nk֬L>v{1xhZ؁eT`ppm,k׮spp{}Ok̙֬3]p4O~_*>яbxG/~ͷjaڴiV}``zj'lġƐҏuRqlҤƤyCNKrjW۽^ђhn3־KLToOGևrg+^^-D}/g0:9-oWa|^0>=ˡ(k*F6c0߲Dv)aśzΦsUvQEҳuEssؘf"*9ULyQS0UȘJk2{XV[RAI5oJP:9<;b|ni\h}-MMY: oxpvdxtM];ٸ^c9?Oqo}+K_n;lOaM6~#a]wŒ%KCQ{cccyV#<ˠo=^g>,[ ]vN;~'pN;4\s5c=I?3oO=f̘~S`tM8f\~O4X<Fs<7( qҗs9{0<<x;ށ}^x!.b<3ַEeE}Eѳ֫9^RՀ>#-e'Uhy6X>lkx[ٞtT3匚q~lR,ZM}zb.G!L/Qd~TukQVH,=VtaH)oWܜZTW>:8h)JY1Z._WԵ51笕a?k*6S>>*nJ)"դ1A W;r(}ⷴ8{o,]wqG{~ \`Au᠃x{o`׽֭Jn>/˸ 7@ӦM??`ŊW_=K.?? N>yի^|b-ڟH.\> ^WpꫯŋjpgO/ŋo} 7|3wbܹfm: |k_wr-={6N?tm>gqqc7ƢEp=qcܹm9 ,!?яo~o~a||cccO\z| _>9o~ç?i|_%\]t~w4m"^]< '_:ʷMȵAN|7=T.m*7'CV Z\r'cK듧~####ok _sbaC2 h5չ[c}+ओo8W+-o3]L1ԋZœѥ|{^BZjhRMGax2J?~' L@C\{޻V,9EqN1R8jWJ6FRZOQEqQ?Kˆ\\w&ɲ\ȳwʋGjsF짚O'#7@1~}=}tL> bpp oٳa1cF٢(ɧ:[oov?LqG>)7p=`xx=s?Mq׿lLyDO's Ӆɦ}F| vCJ^,CdbCZ$ϔKNRvξj򽗣uMry*xi맹Pci"~^O3QCT GjWޞCMȰ슰žz%ΰ0Y[74`zI``{gyˆ IDATVRPa)hjE5(A=%s9P(# ?{f|$f4?K-ӛC[t}BhFGR=Ӫ08ܡ /,nfDwߍ~apeaܹ!oـ1sVX\2yϡU5!=*OsgMTqa)D6P{1!}8mGvP:FڳxCg ѽ%Wrm^Ż7$Txx=9Ͱ1:#w!VwYb ꏊ_+R;rf6[-~tnWC[࣊" ndtw$FC.n~'n>l@/Svx0_E{Ab[7],U-.S5//b ]5e?뮻˖-ù瞋 /K]3TtST/ssxkgj g$U's}r8ASkzA/qb@1Vͳ)3,"?]eϫo%KX=19Wzլ?o(zyۣ _lve53]6v^ OV {Tj[o,pHyiQR׊t4^koETrx3[FtP{#?=ӾnxYB>ϼbȉ9ŗ6ɟ_pu{G>ӧOG__ʲltߥ~.\V }}}YƷ>~zLLLгўmZlQ*;11XnXUvܐ>V+^zfy{p{n%\/;?aw]?sޮ38K,^رjp9`wW}zիh"w׾5]788;[m*<gw_zx衇_yPiӦK.Yp饗?wy'.wyhpt#1_W\ 7[l}ctC[olp饗UW]e˖u~{ゥweyxpte]va|}݇~];s6`]g}piqWv裏vp8餓fΜ;o1?.o>}:J㳟,~N8z(z\{[l9 `ѢEo~6tS\zx'x.-\/=[߾_%Nߖky}:}1^_UJWg;{[Qdsә5 7x2sfРSXJQh|zYMf#>=zEub\*&&&JaK(9Kr"YF-zZrek' O%y( W/p"{;{lcʕ]g̘iӦᥗ^:;m4̘1ׯz}ڴiXbE''9s&ưjժ9s&bŊON6Xn_-o YW6+Wĺu:1k֬V\{@I{ͿCCCXvu Rv/񑏞MOãL~q5N-5Fc!ʷ]g:(ѽ܇&gm/"ܞoLaNk'r~60r99Dy=+1 e p/kPVr13=*YbllҨ$UxJɍlNMsQRErsm\pcJ4 0|TLݘV8oy[p]wkܹsqqav@?ΝKԁ?,100F{6Z5YgY1*0\kdd###p]̎)k wk֬YAO6 3cM[U7g~=~5DzIp; m\S[att>?c ɗ/̙3]͝;O~eb}Μ9gj$YO5MC6˲I}_ꟌnEb4c3?R-KٞQ}xϮM}Pө{ 7Þ[Ou @_td*Ѭŗ uFڔOypXr(z/* K u߳Wt%:lv=<*(lUA 99tz,O˧~_S-Z^*=[Sl>3yL'ESqp<`r#_+F5_)-$+f6 !,0{#+ {pl8L;@b􆄝ld!++ҕW7бT@ǧ@p RJnYdOCSD500; o}[`73^,Gyjc2ǓIV|Y[V5w+3iȎ]{:Gt1Y>QW`v1ۧR:%bje=|}X^+77^Y'=cFBFk[Ls3E#4^03b0SfE-Lc95$"=1SavYS{ɯ[|= rJ"e?ū`xe$ٵ[^qaOstΑ.k\Fy1_-2;Efyb(ΕÛBGMI띵{۽rԾS9xë|QX^|Τqy1(7&,7H)|n;9rWmZK~CSw8f56Z<RLhn#Q5ڐzjG?/x+x=KsΑY5{o'5+ܬN199l(ONC J)F ۷R jxVKȹoDz0b:j4i2Eu-OeK{?]+>)G>)_1^QLx:3G/Qy:+܇L6ϣgrSLXT"SS,v@[,,Oag<G4*ݘL4+{y)``1c76(L6U1᰾`x&'S^guV̖''VF7ͩ3\),M;tMWNADYT=!WW=_Ȱ_eͮbŝe zMM-V3^4qElAMfsLo/ٙb,jysqFELoMWUTNje'[帧wɲK5UӬi^MÑ{afX-oՄsc(Ktˡxrre*Z:vڀPzzxs *K 'm\Τ_jNx'0::-]Lg{bqSgbZ]GzCyhWew{rΊd:fr&G5~Ftk1xal|HN^&W&gmår0z[0Q',ShAb`y5x}ń1 Սլ^ˋȾg]OF9UL6,}}?IQilXQ:F4jѥ;oPQ0ZvNWTÙlvV{^u'FH]/Ͼq041ua3xJ/4J[lsP/uk,/\rQe5 . W=-/ϧ NQDԾu!W{W~?67Ö<OEC,cL7d(:X4V7?˾gj^ SgX_ltPl8gyz3 H&KŁ&9rƄ͟g*Gy+wEQ;~;Ôٞa#{b-o[3<XSJ{9sG1xt5ZxX*T{J_ysJt䥼l+JftI|Zno,e88=c+%QܤK4$[r.ը`:@=:@R4By8Yʊ*:]롚yQAiӒs*S!ᰲl\b9gC6'r#ͬL^=8(X, Sl]LuQ::b[ӟ['Q7L˛WV&󏪓F-V,enbC 3s K45ŕbcIxgrΪ=vgnBayxFd,yR^g29&_+`ؤlc ~UEv]e/ƻWq{EұSw[&r9`D }t@+f98/6{64Ծ7S\lV^ԋl?'&a6Q}67^T؞w>b2i-W\by0;~+e}_%vHq+Kk9>8 r!ݤz{O3'_R?emń䧯Y,#Yla8*@3Dz(~jq^?f,OSJ_5%OFx׸`+(L4sJ?E l#5-[C=2aV+PaSZγ kN0ް0xqSNֳX<3ޡl37™;#Դ^^*Mrѫx^ԋ"Ws Vn?t-+Nѫ9`ouͩ X0nj8{o5Ca7;k:y O-o׷91o)R9k $fV|bQNQ:۳9ӳ_L'5;cy ].eFbIJnLX~^6P2T4=-WW+jpJ(]W>Q\0hKlMLL=ѓSgyS,ՊrQ`1xHuS17Q0R|)aN 2)\|<[Ǜa̭[J7v?ʻ/{YqB - GJgy3 "U:Y~On1T2sh.gzcپZ Øڥ^gy[5zMaLx +9<ڏ׳vECjtjkzŊc/0O>Mz<"|!"Wz@?=5ةf:$Z唥Uo~G+G.jvp;FӍ\eϫ~|{ں{j6xtl'M=;0+z6i긲/9r;oeYQ6XGKQxSiT$ $\~9x58IϨf0y QW]bqO럋Fɨ7|^/}LDIȣ:.UMr8S6*xEvg#Dg=ۤ9g]{EF("G~bal呷f6J1Gy|cur.vtj/(Uu̖96Ss(9Ƴ/3#y5ÒSXp8åbV`a4<ۏ"_}_iN^DqzxZ54҂0+8GJ*eX5 3=%&FR&hThb_Kˮ-=KKf~a10[[yx1l؞scw^X{+,<+`in޵x6HyٳVVGh˱;ibe*잮 ڳu7=_Ɵx*1Lwk#).?U~GgպW_,v,FbD1Ɛgg6/^E.կ=/ {5oy[On#Y,ՄWWbA>'L 71šT.vTpXNe7\3 ܘ:(}-OOGT,5tey6jVV5ë% O6E|1+G͎Q\U /&b-wՎ}TZ#ik"ˢ|*GdLjltH׻.gbXR@'+?y®+sKorkYy 'bؾ2^gQp W p$=g@?fx-nU"^39j+(;eD*LvkѾ97Z5W@s5H5)PRIx<lY},ox^sctw^ݳy9BN-W=\9C^ˍ9/~o*9 Gf~{es#{2Ym4Tc4f,~rq({uGQ0,_ޞÊ]̏'+^R~SX\P9VxU|"}{ŨjνS0);x^zɫgLg/V lY5&5Y_;j]O*wYKN)LV_*|̶9ouܰQl̚Ejk`*,'~mY}eS+ɮ(=#9R5e5~ΰLoz8-OgawnZ=Fbr^GzkwobVN-ؼZާ^W\\MFlVlZoyw\W>b^^lHE9l{9ixxkhM (~jRԙ"Tx1ge1_FEV»ƥk͡^^fjSRr=ޞ-\egφ>[biܳ>vDr%W K\U:'Ί1 ud6CRR>9rgvF7QY8nZ(~5/ Ѥ~,{9Rؙ rl`n1^^ʓB6hZp6`:+Fk ܖ>©YpոwcT~=oOVkڀ>Nlhj):ǦVg{!6,YSbt_˧.[-/izʲ=llbrS}WtLW)o+~?MsKc/1ŧōkCQXbū!l9rloe5:75+0ܹ=c(YJ7Tݠ3Q>Y%7mJww^?M_P7*=ϿQ^_[!P57hr奜n{m_UcaI9[e;%J 4F][̑y|z"){?•s󣲡Wh",ט)N%Þ嵥~,=+ZlSr*lj8ţʃhpU49lJZ_esaT$[Q,!ڂG/avjˍ{zγ :#ŧz×S=yVHGO~bΫbr^xϭqj¢juR͍ܺqT|+JNlVL#e9 b8m}S^Ӎa`6S\"sE7z:(^^D#~4{[ܳ:zxsyM3ad Ygv39Ht?^ǮCjf$5MGGG1k,I̮nՓiWn\{Ƌ of?vz>%ڮ8ȹV|mg, @\,6F{L~}i(z1hcjΈtbguPٸCh[)s꽕 8,FySc2*~^m᐀O=G)]b@,fuDS(xOV ? /< vq ߟrٲ̡!`m*%K+;1rJfYvYWazG+}l3`x:Kxk-D10Pa^ϣ(N>ӧW4+W=W٨7m7GW1}i/)3fn:> LLtئ*9c(׮U+zQ`Rs1駁ݱYU,Zg+;:hXӗf?Xl @?X(yXf9skע󟁉/mM7E94T]Asl/^|SN?ʭʽ%KPX(ϯ^YV\z37_<$0>ex+ٳ+?[WѤ<`QN^w Kt0zk //'+5surxAOuʙ5 ESĄ'd+Xųvp__sӦUz&um!mֶe3U MO30o^cbӺuE9:Zz53(RU\ T+V|9eUPKtֈI1wtiUK58gAvmg=,؛̧TV'֯fu:{P̙SYSOungErellV rK`6KTIiO=eT>#ܶXʭ%d 'YjT=9A5},4GG#+@WFӧ~{ Ft޷ =z8YSWcvbޛ""yb*HfCGAҰcgC0@q`IL_Eq! >QO2?q%W~6eG[Tˀol* \xapK(?%Z ,\XM7mWaf K?D.}n 'ͽFomVQ%)VNc&>>p{Qǻ?u /-]jppՁN: xYtO8O яv>l1;T]+}vO|o:tPu~wط8P_nXss_3(^ॗFFP~__]>Չe,'?A1~Qu(WT]wzpY Ew;_\ l˗W>x6(P\ppՍ? x;е`}*޷{n#Sw|+9(~,+[_w]Mo>Gʅ QOSe %T׫V|gON9꫁Z9skv٥vW~rO; 'S3WWT,N=zبP\tp Ս?8d?(jh~+WfUzG?QU95 CXyU&W~{իQ|wt}EUgZjjwc(?./z? \|Wm|;T|nY|{W׷ ֶ];箽vJ:@נ|U;^s(N=cMd8M).>j睇+<0zu|rNoVlOW9ϫ~}H{X@ɰ|('k~skiu-(ϯ(?>%+aG-ofxgS MT{ᝃKOrsCzRxe)X=ukS='opaϪc.Zۃ~0eؽqHUU;w+O~eu6N];NPlQESFʑ꧙eY}q1k_;U|19h)=4ʲqo>.Tqӿ3Q̚5CG7uT9d( : *U֚뿋E###nMhqO~rW];OGY?[ LDm9s>8ЎO0Ib6͖[N>' Exǧ`O˲D(&}iڴO!T&sm{iέb,jrڟCC(>xemY;:z .i:V=CI$ M}Sᇣa·yO˲>! իQ_=8X$S:͛AӖ6S:U?" OG&ܲD>(>N@nۡw&QL~zWM5zp~kh6߼zĽd\owr.[Vמ{G:S6lyg<$WLV=4`l3OkV d.rF$PLڦ( [n |#]}xnmlyz#e;@QNYgU.%X~/pGVr?'\x*_N We|p_/c"WeYGٶg{miS'(*03-|_U ؖm,曣8tN3fTxk9{%P}Z9Y犢@~>Cbre*gd)Kv]5ml3QN>mJɇv,甿w~X˛+(أ;>|^?mZ~ʣj '[t6oeW"'8J3gJ ŪUݔ3>Hf`ghocq( :p2=}bbbTէM ސJW4Ì,{/G? T΃úrJ3Wb`xux{0qEIÜSzC=؊z IDATrOuåe*Kï.=7md^lAk\8"|M.I=rKS{ox~9}k&M̵KVkHzobttsyʛ]3[+G'f+uV٘'7ߔmmS?CkC'eqX}/φ9P_8$Fy2<ݮǰڳM{幾QCEJϦ!=4lߣLTv-vU%3'R^養c*S]f>(^,eZ+_4OY[ʶ üO@e~HWnMxE>ՕƋ髪όWN\y-/%ŨrlylZx+QoE1 >gTX[Fl]kS[<[k?O W7Erՙd }4T[JW'|mQ}S=-baZ>{ȉ[>b6͖#\j>`筼(Vm]HG*~^YNZ޹ie+W7]50 k֊Oѓ.3zMc=x)bQf6QE#1=%ԯ,A\I<*{^Qp>WCQJk+,V7p{9QcS50xÉM3iA"+J1&FN3H<ⰆgٳnVfG頁̊s=vƣaŇ&bb}lq3,K׿=W$LnNLW:xFC]Mu{f ,Grs G\53DU(={2JV_Tcj JGݛ"< jӛ)/院( T#Œc6o`IzE{ʉ9 10}}{Vٔ+N`X*UxM)s(۩ŭ@|f\j,5[5ʭ^j;joL7[h_57&PGjO:3^NZ[ֻ.{1h|l$G5_nFyj-rby衇0::=؃ʳ-pE3ѓrͰy^E}.'xy8XR6W'[b(;xM9\ҴeRFM2=3=gZ:UӍh#<)H헫m Y!{g=JGk%'˲lYmihMm|:1}f[V 잒kwkWoWm'|PY49[\,WpقRa3lXmrZ>%#=+ZAWŝX;*5龪SJ5^)c:{:k;8}+gTSb4-f(_x5˳q}Mx0QZzu<ݳ"!zв\L7%9VF *+,Ճ%{ZexXrGki4<#(zJx*V]+}"?4J&bIpxI {%Adդ 0bfqXS^J'?#Px.(Nr\^N v6۴ab&Zy`8U墇!qק= W,AfvKDuPanϙ_rp{QDˋ~Ā͋{loǻGP f+QN}eS:\9b!WɖzdJ=3@^_vGQ/R`b5}p/|rt=FCٳgcƌY=Fzy:ers|yol9u-Mdī'zES wz;Gײ}Q6(ace qbEC={X{EH1ߤ,gոRUO {ft^/X.ln1e>&Czw`j햡~''6u/l6~+ rx\njS|sn50EM^5fZt)-[jKNf"6o)>v_e\{sso}K/g+W_ۓ=XL'Zr=="41T#G'T1LMpfAA%ЪEct}fS'k5$MBa;$;&oQy&6}[9^A;o}^Ll+gcַH?+/F{澪Mm.\\{)7 9Z:o(msQ1YLw<]ӓa~^@x=82yӳ%ߓx3,{QC?LG3+b ;;mʏ^agԾ'^<(/rko'Ɨn 73zq5Kv1:V77܁^ZAxb,YV^=)͋??:B Z%ރ{UU}s̝dHHHE@z R  (*"EH Io3s9Yk}/[ùgϹ3Om_>==M;LUExu%cλܒ^%6=7}[s-2=&d>S-advfi _`7FOصR~UfvхFKvXB-1ƍ}ùEA~ChhIh?w븃a^Z*4OKTS^LfMVHx K_67wֳ28 us'&-jKn ѯ@jb|\5}=!xBK72/&ODmno#ݻxH>i)-Nx7X@ \%}CHek>[x^9DF>ͤ=_~N-_]u/:9.6yw~Ї~c|g=:xY.{~>N-b8?p|wg.Y2Y}rk>7|O|i.wD`eϾOkm\3 >% A:|ww%!)—`^ia ,ͭ)0ETF5 P+Udsyɏ+U9Eɢ!riBɢOTQ_"ˣˆB4pr2*LPE\A&G!TQ,dePH`JYޥA,*c蹊`%C>bbTR@dQUݧd e|]AL.\6QR܉2oQVAJB!UT*JEܟPsv3iN1OϮ:Dz`pHcj~3f̈:xJ|n8:>ZFlz6_ ^}&DELu2uޑ확P2 b#F폛[_)LpW\w+x> k}'MW*S/] =m"FMoAGY|95 :qObmVwhESgc8jOù?u6ocaѢE>\R6r+)~_ܾ&ᬅy/%cكg|L>tkWmaVJſIҩ .&-Nܽğk$s5tAˡŝJ1 cQ\8A8oy`3I(&ޤkDr|9Vr2dPs6'ٹ>:}/%h 'æw7&5\bG[|Q擀5?Nn׆,-ѱ=رYc_Ͽ ȷ?I82ގco5[Lj;߸& ~׀_ŲKqxZPc7~߻G_mGpb|aգ'.?ho_Ǯ|ex⡻.ŧ/?x]x_?Ǥ/;L?>cq܇~8CW=^׾5c՚v<`te8{K^~eQj\}8xZ|ʿ+K܊Kľp՟U7>/qp /.FRz|cXܿ0+a IDAT@ Xqwܷl$\-]&oXN7Kuĵ߻ *<+XZX ~VLXx!L90XxG1_^-?`M{;مƒ>w\x;^]k?|~h1^XB9SC?m[óZs\>|)/#[n[l2X"5^M$Y8^7%|͕p/P^Mmsm(>X.uVK47r|qml9]'wJ>9KX:E%GsH}gZrUn̖|sTѷ&>γIx%]p:p\8MtnJ!RasXp# $_p:1*.ml\hRnNzmxTQ*P@t=pY'ۮ3y/{XkؾJxw@wq!Iݧe#_{n=L=?_]xpgN0 lΏKq>|X;|p {>8b_,Et&55GN/os)dr47Q„Q$vݾ~֭o]R`$X `wB#z]Ɲ+wJ{Q_Eh߂H{y}6Ѝc틿؊5/ g?&f|1X{5tv\b1J vB]fpNIS0oKb}Q߂dSOnzfuW >_O|q__[ۨXJYOn +§N5bˆp'N< '< `>'wǘ\Ә8܋rvo|Xb\{>EY-y>\1sMҞoK\IiqrH})-I7>s8<IG.?0pIqc)|ZN~=!߲bʵ7:הɅLjR\ג7%1p Jdt\>ř #M^V.pLJr)6 98]؜SJ tt6;mNT'k -X㹀I~jK.p2Ik8^җ_.T.I9[n0p4l+ä]cӒ>47e+X2c&4kp#.l_y~{0j:}{rsê1SwEUd3W!z4uaN(͛N)946C :6mDg[F5aNsOE[}6mWu>rP\> B6+)+Æ}=U)߁ls?w?O͏ZO0- Lv%@gG'JL#igc ^+;}6ƍ;PBy&lh*`Ogr'w[|@&S =wW^tz⍷Qk#ώǎq3/WPUpߍE|\I⨋5??~x*>cGVo9>SwtJ\:b Cc`ƥ,C<ќߎ9'=봳*l4?_c pL,9XG(|7GG.<_;qgێǞoL>چj-?::^z?w4CU`N}ÿrpXA<ن/1ۮ7'/ 0?gcشq4lO.W^-/pohI| "&D[Y|l͕j/_r$ ͟lVWN7C%@pT&/l!VkI!68&]:^+fp IRBQa[cS+q<)]88#h;4淌r4lEo""m@6a:W z=|Rk|%vIsx9_R6|%p-mjtoYi]j CeUY^$:nm=m<s{jwo<0noǪUjѣ]qzWc-?';O B:KWv/~?DLnC>GWaպ8>N3p#O⬩!Upc??iZط[p;aA pȞTz$?x~~lX 붥Ʊ7޳׬Bnynip~S'9L;d~yWZ17?N~瞨C9 ?5:e1uq>Q?r6/pЈVuOy? z]kBY׌<W]v,hOad_;;7K\{(m@g|Ǘ̓ގ\|Ӱm*Ё]k\Waڍ8< {/]8AS!ه{(d\\իV0(:?jU~lmכ'xQ>q#8\,s{_ߵs5t(͸3oFy n6=Gr :*SMIaIX+=fB |sm+S着y]fc-jdν4Fsm,KW-ٰx_g6ofq.Fps\Ҕ6" <' -$|&qdzֵ>8) Nw+0aGe(~tAG%RafIBH?k$:Kfn_EIviPI1ņǜOx_{LJrs%\|J2Ln?4V|RP~<&m.$tݻn8VÎ\cM G ZϦ7C1#0 Cפ.o>@2Y[0rHz-_Y$66%^msi[ؚ-8ܒ9j-ktsӔvɆoԗm{Fl!b%Kl WY0=e1Ϥ?K48!m]&9 /4IMZ 0]{qc|j_r/9MR㰛fKJ1If_CJ_qMޜݹ67">zvSK~i-տ-%>pђGVɿ4Zekt/gxQ -lvu5iРx\4m[k'^[pI6yV5F[x6\]Chm jDnL8>bxeL/[Ҹ.ŘgPKr(c$¤~mL6Yjglq!Ѽ3.'4'KpndxMOsPWSPM[k98;N'v}ql-]fs(mf ᤴ(iZapz7%;r>-іem|h؂dk[;MȜC.[blIHUE389>Z*͵]hĵOJ"$Oچ[&unɟujk>ŏh1ubӤ˧9PӵT>m=&mlCW-݆[P{NF[a]6ru-b{I>[?.x=r]{Ϙ~\|쳇HޅcXR'\S䲍Af'>}uNP"D']AkW.ذpI/<)?Z9HXF-%+ah 6sLbrjӗ=ՙsc$;|1өy?.q|>O1i=L|pxĀK>@Q(צ5ɟ]9Oڿ> Wk$HGj>4%[P\cΣkޥ沛J:u:n-s9kզ.ž[t!61)-=׶Hmk&71Wi'-Vms9;A $o|줔 ܐ9\cFRoBq-yqz x$Qq6L5ҽsm.jil! %7Im3x~W^l<8?r*6(%I=3R J䲛 s˸q^}eڂ 1\.|Ɯa5$l}.;Jqal~ t+8<ǭ͵Oڧ\qq=ss8^\]$lJqrC]-֖6}Q-T5$2&$]K8bKΜq?^oCyS>:n~6en8 mq)i+a;~}<ŴM 6BvLFm'ٍڊڙ : *z>xK6 յ+qs${ٸ5rbS?xs4>j3A1LpzdMjS6'SZ͗Lq:җS豉'bر.Iclt>GkRMb>zs)lrr$+]oLk/kl k-)IŋK4l09[P} '\qFSz)aJEQ\%Ke򑄵9kr% ӹk>+ niJ <6R%G{cI`K49g'/%]6y|[:ppWmkΥU6 iЖ]EU-lqɦkcQU/Kp7hhX\߼L\)O<\ʓژrֽZZ]}jოk雛kssW/Ǔk6‡m=[KC9\[Js}keGTKe|6n*t%uzIǶ 65AAз#DlE7~էEJW҉ցWSDNicZ|F6&j+sQQõ-)VR@w;況ȣ G66]U%5Tn-/wqqֆVٚ8#9[lc/pRZ.vO\ѽ\7tolVZ׋t/%#uNWoI9R lcE6NZkӁZrb_'p3wa]|Z|˧/c6?Ms%Q*4iL "Jf?Ҥ|AuSg\i- VQMq6}9=S]WavmTԡb0'?.Gllk9bWög2lya\bm.Z.J}Z MF_ڶ=ĆÆM?G'V|e֒_mцCƓo4F$?J>CpR"Ѳ5.7Jq{vO[xdoWP~N̫mqnۿ(m>nˤ8l*r4+[|stEҢ1h\goD,Lp0A*sIhJ:$WpREbTIטso>{2K:fѠB} 1Kl8[I&E t 仜MMN$v`/$N—hpybvb nOY.+7k^cxs$\>2q}Wpy~r^KpnߵbͦoWtíu85k ZVGp`x8}ޖS$|ɏ)ZpQY%8.$^\l.K$޾c&$d*m~nkoԐ+prpS]l)e):729_TͅdW@K )s:TA U <.x,R7 ʊᒷ6<6pSQǦpѦs9 .>$KprqH|a*EјJ>H\"V6nái'kWKi5_JZW+.hKsR6ZpdZpIM?NMYcИ#7>?.Fl2rV\.?qa%G~'ynNoF߱ZӫM%2?O^q6?dqů47AB +1 qKE!%-zFiqLs-n9 |L|n:b;t$"FǗlc|6? !O\|1r8\2K]T*/iv\Bz> IDATڰoEc㋶>NFWķ+Hx\sk?xr]%6y~zNmT>_:N%K{oK> N2_6sn-\|c^|h`W.89tGyc'RZJ(F߹~%gRq1)<\Zu3\>ZtcytIspi &ӽ#/ݛ2r<9|à;ַ%K)H$=p<|۽&EWVQ1IM>㲁ďMu­QJr?ӃR_?} ʃ訩xjo[ضmۛB/ϣy.2=hQ-täU\TsHu/]Jr5W{]~SHJ<陇'~q;P(ޏ*ܭZ־TKT^o[l `'' G}4r\jZxxa+moOVK i MT?җO$a&[<_eYH'3VK5&S;jҹZs~Us|Z\Rwod` psCY}hJFiIs(F3Y !6@lv4?- %lLte׷rJ\˱a"v>SX6zuH{joR/:7;q=޷#GZp}N_Chn\ūleE_cӉ6l(A}4o/SΆVZI'g8`g3]M:P\*Q 6`1i /J0pҵM^N&)/gOaP_*J,66;s̶b1?s9}H@_7l؀~sYpL馠91&>(@}y|H( k њ 2tn @1an1#'WlA]M"CӇOϠ ءx&nDZ6[$vN$>3wgs LPs{ʖAhCP=8Ss50H ޒΑI!'Lڈ)LRc{Пa <(&iY~"xR?~1HV^&*gHWCrrliGB?:DZ3/{qWz9ͭh &|߻ MMMbmd6iY1tOhkY+}Wգ/_o[Ie;m5,;[hs9WQNw(mnM]$@qZB+8MM\!a~9FtM~]_pt{jWI>k6[&+7Ƨ3SNA 0#Jo\I2kO^Q1eUXl !Q+}πs"؁.9(?Zɨ}ި436Hq b-"MLd0*c.eu2;H+c6CmZt\ S@9.UrH025䐯q& GC />3fDc&ZgnîSvB'> Hlפ~Mc$S4 K˨ ıOo+(hDW˟8-Rk6Vք6J6O|6Yỏ~ɿMFhZC>E0GvOxiOMˤ@0's㷥:[[z\4mO!<6tM'M.# ƭyJ;E 9:0?s˧$3uE9ZOu-mn*;Ѣvp1*f NJFJ|9nMDyI8Ø!m>)?G%4k ?'  z@ EvIWѥظ~S6q{G1b}PhhJ4T꪿caA Hpf`L^ vŽo"*seJDR xL7G2'Pе>c|&D2 = )bn-W"BH?hSqk^ qo1/^t"9o,d8.Ud7m\`hћ,4.;R]_R+uNd{hN0pҁkh#N*)cS$Qr(3Sx 9Q͜V)CAi$LuDu/y 9.DXUWh5 EH1pأeD'5aDF2e2zZHe$GLٽ+Fj>l׮C}jť$_^5]%MҸy/œ.I1׾zFN,hbٴ=SMR1>F&S?yR.-с2TcC>5* Wi%2K(}oD楽;b0k&L^2F}ӰiӦDFa/hD?K}\q m̅ˇ/]6ym#N]t}9fM$9V/Ԓ.z^-4_t_+ZZrUgNRN?ʟ;یjsI$/GC#͓dIx3_?J˅Γ9CݸtV+&da 8+5q`HO%}Ұ.jE_B-Յ_wIr`x1WAJZX/V52A#@yZA{o5l<԰I^)PlzEl_}۷`(*@)]mjhk-^RqPy2.$ KJU1ʢ9AKc g'2")RZA@G+kA !x- %fNM?GjF䟱h+A 2ϣ7Jbе{"_snpakz×m阫CW-|)mWÌ7|Nzupc>iNCI=$:s z2)+(o).NYMn;TRs뀤@ `lX9`͡lJyq m\Av_]q4yC҆)YCh )ܢ".'ɳDeV0Ŏ!sLJAA`4y¯;֯#>z;a.BoVtmZ TsjTJPjJ9gߎ\AX&>`T+Uױ%Ƨ`s;lUVAUTѵq-s| sj{#+U<{غT#[c+6b]“ݾ]l}5`|ZFvPJ)Tcj{UE5U m*p@ZA϶M `}[cAKP6EP r)6r9R ]ևq8b MP`?zmBy]>T)й~ J=QPF^3<0oXCdҕ;9ž0? dC#ZY0su0ߎ>U<<A:\нVs\TwHGjKI -֩=K5.[86P~j>JϦWu] =ɷlm5W]x뉀^o18{"ci6ۓInI/I[2GXTgCpEO^]'}><$ʥ?JKz*$-ѥMƆE#JwS}~SyJIˢhqq$>B" Kױq)[]pް e1xѷcV<fy"2,^{<2N]> O\#4CǺU8ϢyXzP܊%8o'O-w=C}_z/y3}ʃu8C\BڕtfNm[6c*TK%i:pd.N)JԟvI-7"sc& (!Ԣ(iTk 3yCuB>MMv215m\; rk(*u\dZ >>/W3~%ߗpꃭrdhl\nF:Kpqn>5ؔ.'+llr0[5Mӕ|tD}N.%}N&][rt4&!6_e& U)4WɽIJx1@? ƓQ1kx/#k"@&C؀\]=_Q܊Y ٸ|1Y(ϨJ(Q2{zL@bCxU!0l6cRA&L6jL6BC#B;V롪p}@?!R u-oQN?wvk TDժVC^Ll>*bbL.͡Z.R.C]Ss\Ȯ[ -~g\JUc}_ \kj2_B\m3`yc%:2*L6+^߇3vEJU 孔5,8fPL6-]@*7z IDATGHb+4 qd uꑫiT%䋍knFĩÔ@6G+e?Z`ԙ M8_@<}۷_}P &l|yL?`V<~FF& A&˞Ѽ3ٔ`r|Z0y1cTjlB+˅9" \>ȕRU麖xA)8CٷPV`_OD0cy$/D@tqeG$mH`XՎ>ГCKnFRpqX!Os%:\=ki}Bjwm>|9qzM_^8}pZN.qIu$4WSF\0IdM6NN@תl]y-/=$z668\֯G;#IG3 |?tWVNR$H#@2@"z*hjTS1&h^D>)y<TH (HC'@0׋wOcly}y Xd!{0Ͽ 3) %?!V á~bzfL0E9J!Π:g9~vZ|J}q t4!Ђ ~\\u˗ƭŷֆVwJ)r[Qi 9]gsZ &aӷwo>/ qtmXރK/ԩ7^nJjpJbya=~+ciL't-o|XQ0oh"ST50&Z?n5yh}@fU"[D"·\s& A4L.jm(?,;¢:_T0݉ ¯)؎ꊨV[*zP-P:A܎bGq 25R0d1}¯_,vlCPL.l kg[G^Է@6gURD6zفl&8ۃl>|P@Է@*J}Q(6" ݾuM-[HFL&J$"};،|}1zJgL&m/<؏r 1ri z lG!ױ u1Vj[Q<"zK\㪔o7D*l.Ϫ}6[߱uM-#Vx1j,M#Ʌ0݅L.lLaL.nBQVW6@{Q:2ĤTRRU¸:J](44¿wed#A>(UJVnԷD&O~.P8~cć5 և3}KUtx0$oE/z) xMx&uYtVפO}t.|qIV.pjvHdkGpC-X]ClI_/p:0Q.sr) GELag<M$HmjפrKbN%iEZǍK4ki†IMV.MWӑRf~$Qo>c,Z >t_D7,dTP)]DϽ71Q* eTG׌*9DECՙ|zQ6..(3GL5FkhroqP4f|\|I..V[#Y1WCcr\ ᯔu -@6:sP2Krr28} 1#p%phÈl\/3$3]|zVS:7G# Wr9ۉ$|>]sm8%9zIB& MlWI.t$?GˆӼ7$, 7"Dy#$cʔؾbi|D~7蛿\&.I91]X:Ř$_#e~iD%N<>,ųjj5IR\J#1s-CQceOhl)#2ߐO US_^c\+> *ӎ&nL( W-M_IhHf=*M80to25wMA"of>BCհo`<6Ir?"*IE˄1Ub`Շ\AiqvH ӞiD> OvN3LݢL82u -i;H ߎ}/HR͌ڴ!5;vl%psp M4P&MVS& Iꁣŝ%9$ )2<98Qu^JXܒ$P%;q&FTo6}]@҄Zl隃:;=p?])EQH \ckhaS(e+.N"1T)Է0W~dgZK7q/":G'xAEq2$Ǥ ]+a2J"dkbZO P^͠=]hٌYf T[mVf'7cvD׬(nɋ 7TG5 aP[|sV=۵^6GCMx6{~zGC;)!J,00Z] M U\ګ@.$P(X$JLAiC@u$K߽zyƘkov9SZ;c<}{(px7p\T* Fg2 “v68+Ňp+zAzAK8_2'vu`oRLuϮi?Ca_ʧ_Ye;k^0ѦO8P֡c^:UGAPxO{dphy1ϧzF+{] pәǽx p Lj`Χz(%.^jOzա2..e/Ezk~RJ1֣xb).=~>O~ÃSэ%VtQҷ! й(vh{|9k>^k}< %_Y.{"Qd+uYz:w~"~ .~hZG/}۹yLnTʣXC=;':<0[oO|WLI4[x;zOg ZdO3duٽ8:+u.hr/HjND-(;[w^ [1oTAPKUej@T<_2ʙTLEaΈ6&jG~'=5)Rt: K>ٲbȩI'||&](?\] OSl4`1ax 8)a;~KSN\uZk=8xCS~ΧN2^&TNO/#:}c?} u6jJkI_ி٣{oHxrџKx\[Vv;?ɖGh^{{<ؿ?|rC}\;t9t:җGLǰzqбuu}|:t;^lyz9_Ǹ7}\s].?0{һD:Z7׿7zqrp;KT]nO ݭށ-tNjd/zaA)f"tԗ[Ƿvd;}'p3{}Jlz&\./n4'}JjgA Z}p^ڔp_蹻F{;@\ {-=A0VI,F9:'W霽z'LN>'?_[g~O>G[{k?翿Z'޷~я~|Gy7w2ջ{wZM[+_Z{_e'ﮟ[{%88^h„S't&R,n~Yl8?&^g-=|&|sv8)g3ar^cu/Ǡ "ߝ]gz0,(:k*@(iQaswr4q/* S#pw:ǙM)/\b];rd~X ~~ao~cw}_\?X?[o\zo[?/7\wwwCz*իWhXt;݋"k2bCr,=l^ n.З4{R|R3O/NTakN.Γ*URX^ v^HQ7^⡲NJƔ%LI+%M>Be5/U;~uw;i44ҬR\w(A)-fNىCN?^l}#Y?|;o^}__oFw';Żij~=bӅʾb^N6zS]m}W6yW7Od§G&Rs:kǗW^lI8:{W͔b~M#DNzLrwIbJyݔ]ٔ7 *dSa%@sɢ'}wtJArz/#).qu \4k7&]ŢdcuIZ{\)I^: Q^ots~W%= cj<]Ś?d'̎xRv{Sk?ʞ0ѽԗRUٮ5)?h ڜ0坞z9W4>NK9*%]|t8WWחH?]w ֔6:w5b3}'nb3GwKNՎD;H<ՂIoV^>siNKZCH*gIJPq.QU}Hd]ngyRx GhĠ?Mb#%OqRs'O*w28⹖mO+ T=u//Vm3YлG:=nb6բn;ud:nܺIN=xyHw|ga!!4տ8>5V\ފ{a>ܛ_'gŏf)nNr+݉{\L>Gn;:腩 穩䡡w ZCz G<(MI<wt sE]윚xbjcW z2{Ksb(It'ē6LV 7srOp.iZ)6|\iŸ=w;uX辋ʿk:tS_t6xEC^[3a@O.%yݧ!xww Cɼgw;#}6 >d 8,vq}'}nٹbR;LMxS=Jv3W]n걄Yc55gTRs c[MǻÖxL&`&}H8zKe_vvkd;ǽtZu +I9mr"zO:Nn 4uvduwv|6ҜwOn74'ϑ;zID5avkq*qVtRwM-|ϭ~WAsMcMѬ#t=>Sӽޝzf]qw%_MwNyUNw8Nwb%\ S'4X @x p.T,iPRaidqtA|ѽtʧ٭斗GkKil$0@5eb+' ~Ƹ/l|uKCjDݾ(Oc"{SC9NWWoXwMtI/]|uT69OSwdw+n퍄^w5؝No}k}F\^:류E:NڡgNi.<dMjwE{z7_RNR:}S)X_OsTii}8̍U''UMNN]V(|U9b;;+8 NATtMDK DP4`.xht ٸqI!|S; #NZ:Sb٥\Mx&Ѿ23# `(6[-{dϩη40w/d]KvUY$xQV;wzEޗ}-r:V|w'1HӬH~R{o.w^Zh9lk\M꠫*T+R#)!p^nO㻤V_Q]BП@Pw5 ɮ +15T7H;I{p:huz 8L :tH/Cz1T.;±wU;#67ܤϝ"LrmOOs;'P[Mn3ŊAR8{8qm.M Uұ.gg䵋Iڼ+urqЭ{a{JC_[m3 S7V{J_"ڝ|nHv+ߒIriLK647WLq ,ǻ;NQj:C. 'sW]ĝQAN:U?\;vQRP(>;R_IɏjwZU5^rVA{jtdz֐#*9VP?pNQ& 7ث\c|BȪ4#@LmՏz"ɭwugIZW'[|.X(7yjNh~$=o7y}vM:>-Liإ;S??3#j &?iHq^]uqC\@,GjS`$nj$0;'BJ:cQYEJ| )& .&C+.G@N}w'Ţ'd}j҉hR[/5P`v1:"[؈jˁJ[t:L58Kτ9řjҤXR&{ד/Oil˺gĮ~pk3 ooskTʣT#>ɮ=>J|0l3O^& ~Z=xt/5}u}w@UҞ+ ǧ4JWēxg -iN|O]竟\p8yH"}%Kj]AU6,x [dWi#[EvL?'v>+$:9PM+pt7p'K5f-v&<^o:|}<ݽd~ڳ&ğS^VI=ɤr:b~gvw$ ir*Ѥ5M4TK9f'P_^O8^ Nl'վg>'v듾Mc;h SK2;)Gã:T^nU\!vðkߧnNtIAYi_Iz'@#]HhL1{.mtlbt@JKnc_>N9~ss7j=GFKļ[i}[qi/1#Hko'vqtg)ݬՖΟnvr;5{C{_;6>=uJ5J|oni ģkn2p0@+ݡUv 4G^3!f'q8tvJ͆0dI/ UN'N|ncu%'I$'zbLbK9?O>KXt Sw>u$55#{ҥys=tNtӡOs<}n8cOG^et:J;ٚjCr*jZkʯv#=&O6;_LUUOggG11ξEHwT/AܬCmJ?]_ҹ[)O\ +I}S]aW&{BX\rTTD@S]rAQ1)VHl?w\ _+t/E>S,aQwzFL/ZHLr-k/t^7TOetI91t?.w8RMg:TWEw :t..GId]>]Q[]{t|rpRoIłMKmhts4~t߉6i=eΆ4sC3Ww Y׋Y>]U P6kRp{$@8`p슪[ׯ*ײ&5ɚ6OqPWĵi^ sRPnлNy$OsiEIC atÙhɆ)nj*?$|xn>w'y_٠>;' u}UywEsA;h6!iL`7d#wݽ8W5f:.4v\ޒnDSOKyQ`ۉźnnNkVk9N/&ɘ}W]q;ORI6]*Or=42II =CJT[j&)!tvx:gl|9vtNG9L]a^NS\1՝PsWMx-5'4|I$.:;Xj g.wŭ>!,[$+O< %Nn9::Yi[cA;98-lDMnbɡc;j.K;Y~tuߝ$Y)_Fc99|tKPW!$C`L]sʪz+̮).r8%>ʛ0yïKƛZ.P|Ӑ1ReObn)4+It9a:_q- N}-߷ \wJK=Xq-p9,ܞe}GIDGwK/:K.'͍^P\!'l-sQWm^܋XT޴(ɇDl)@sv"aT\iM-_%IcqE:kOts;&4ԥr^rbK9~%/;Y#?tO;p9d]N0w2{Ӟ~v}ӟudbǎ]_ɫ ͡['=a|@{1^&yz[$i'qO/uբeԽFQ^tƸ28>Uk }>^OnX\DM4O⑞ɞdt^T,Ty?>z?5GZ!v1X5&F;^vM%dMY?KuuvM:wd$[ϧ4/ӎ[e(sPi')~vg?>Xz.[ukm{N}xI?Meu{{iۧ[Di>7/~9xLwpLi]}҈yzw="GAS) ^]* KNֺC!r~&٤3!LW_4].4fϩx$W?/.?1fd}oJ9rc,MpG~4 Mʉ:ޓHVv7w~z鮽.O<6@~GvnQQw$!uYCJ7{{wznkq/Uc+ΊHwn-qE2N/NK I`- Z*nk.1k.(L⑒2^ɭKm}k+nIM5|trBHvwz⯔7]\NqNsW^$|UuSL郞9uq1DzB:*俊VUT/E>s.nwMi\lՉl|T7lӽۤq'>,D[yܐ'uua|z&h,K?9֝APjq8\'8gh`*pzOiDWjzXsOIտfLvOfRT^|ܭ5(I = N -LΗpotJz:y]q!뾣Stj5 >TYTOJH6?j+;vU9 ZRq<3<UWgb"ydsb CUU.^J4TwW:>.ܧ"9'MjPK+dRv\LQ=,ٻ suʤX&| 0PJTﺺ$v+=,6W.U:35 7h$rE!_2/U(\S4:}Ne<"i$V0mnhq2:?o+5˴OZ# 2IKOwbbgdLuqvJ8<&koSEQ';9\uvq^PO#=S.u98a}U7tRi}/&s13{*w3izx0UCQ>) 7Fz]9R8M~ н̺r'^7_j3O ѽ ӏNtd|[()b:9A~ssvU~<_?"Z#O>>+s 's[~ܥ|ԡ%;x\3:NJ2򝳹M=SЧڝbGT1_6 K#r.~륕-6pxk}rj>?> zTR)گչAUc BaNzv|e{x=Y/W?$IY吽.a8'rwҌWDd i^n'M+>UYwWTij@.w BxvEQ;=?hv5](nT;ݝ<Ε%5}vzr%,]1],)LJ&EL1kS*ŗʝT吞\!wgIնs‹N?ؤ~#ăwsz9.NH6'^n %ŸRQ,G|+^dg Y_nV˱Iw|A_ޥE|@-q^\L֤.ܢKW\.OΗE}N1ddS+Z08Pqr͋^e%k[]^@t 煎k?DyTztH!uMO}>pki(IQؒ(6+z?K\ IDATbtt^L ؝T^rvu9wp@|:')ߊuMpϮhNzΪ[kZR-#QEXv|t:z'/ǣkjN]λ>p|ηN/ ëX&LyYi\.$]줞N1bDVw}O5آpN1N;4t5wT3':Z!Fy2rJHjT$S%aFW}ibs<^gȮ:Z@w>%YӚL< j D~R޴߮iuTqQs6'PSnQNܙvu>RZwdzԌy#s:˿D˝rXH– :կ.&H'7Zjt`i]4H[Ozr'IFNV3!EA:`z9,/OՋbj@Z)fS}x%ţ+T~4MM[jl] % H2EK4+r]T|@XtXO=e>ggN_MyV)h,ݡ芢bRTHInnPuO |^uZkwtS?uIeǻNtD?w\$c|_X=Ak]\Lcݢ*րLMt:=g$Dyz M9LX TSwƍfE:)'u3U]]ݬ]KD<ȟN;dWW'&909=NkkW(;~g~fkO껋ߝSZwfv;I'JByz@9t:p9t=d9vZdWj莽I7)IiDt]0:t|݇gs|ܞ= xjhk+ޤ\R{7.=~ߕ{ost^ۣgg rfםDw$فx&n}M;ʟpeӞ;j Jg>KV\ڭ"o(펟,3ҕVwp%LM|Y_\iS-W^I']]NkMݛ% t=GwWg*;/ ȅ^*lH6!S ځsqC9Usm"Gy%z a}bru_K9?h@r[9zb8*cR'M;zs9-:M(nxu2:*S1;$F$/ٜVݣ>+o~(WLz$7]䦘 9('}OTqSS]Mτ)^եx ;I53[ʞyTST'Y~CXtM@fB5E]O :g\ 6m/@O[ut”t۵>Or@wOwy 'X>uVjnX Sǟ>Lr׆/i|欼\|ZHN$'[d$:sU8螉}RI/xo>L%{T=I[ugW|oŢ_b'ٍtI;*Mt^];3I7݉{+Ii~6щ/0&T灥~V WF>;t70yv:uw(' pC7MjZ+ǿ{Qq;d';L~&Rq\OU=O_ZɼEs{.&5ٛ\Gv>VQ7TOuGvjvZ]ݵG^'jRO-p2JK?U?`xiPU75d{\lb;^I;Gv#="՘蠫ՇAw'R.CQҘqkXwysB'tŋk.wpvwweڑwЕl|Β wFqt6лWRop1jox%LN~l@)~:_u]:N7 do+;u0ej\x8I|5I/&$[˿eI/u[SrLiW4`H(>]a٠E K>WlNOڈwGSljJ6]_yPAs r4GZ^d.rsHŶb՝m=7.InwȯxL̈́'Rs<\?Ltt4'믿nqV4Q{Tw./Iw͘չ$٣-n.ͱħ<C1}T0f/Q g/hy+U`uCkԀI5?W49;y Ѥ5I@o "i%*Q}SQ_ 󑋯]\PGp$&ΎI9[|>{Wwb[GW:=huQuk`nܑ1QīX$.ݝ]-zէ Vww$g'o>V'|{ ?=5g=U׻c.;KpMj.ċZS t=V:S?uGA δ}zp)H Td(kr0wg[ku^_鮸w =OX-hQ$q@/S5NRl+IVEսޡVbkNZ<2žAOKyYyYt$bG8]ר:6C{WN'KcKw~"GtEX$;t=OdHݧ"ptfMpފ#ѤOryvS3YynZ뫉t݉+8:7\izM[i.$^dk 'ޜi8pC ݩ$8\3ƤncI/6m֋_"oKb}w7ō~wzSP%L.>+ ..š⮋SlȖ9zKrYZ@najndkgKpUg#|$;%\s_tU|GgwjXiɮ9y*kWʥ4t8~yoƕCu؍b/bSlxu^x%ݻ]^' #5TIn zjRwrڸ(2 KgnHrk2 X1+kNAr^KEX\S&{QLn~B㚐IdWwɫUP(/bO#L?I/,St9$[~djҊ_qZ5Giƪ(^J׻N%?T}~u16qazOmrxdW?\otX'\NF™j? \]Q ߯zp n.t&YN~~ j q6Ur5WNdT#7Ȥuܧ.8ƾīϤK՝hk:kct3xRӂ:tQ0لǁKJ3BS\RwztlJFu_vtM,իTd8IrݝINw(^/5P\] *?5뎇~WYq׏uu5dNٯ .={DzQrOjb_ixO:O+NV7H 5.4CLB38lNoQҤEk]9ImNzu9+t+c #.j044Sw '_8)FpJx]]%h#&nS:=LP*-N8|H1]|^k0xb%#t=o-4 D6 Kq2t/:w1:A/b;qIeӬƤ ]Sӷ:\Nۑ- [#뚿v1aعɛ2Qc-aHJ<7).1b3JN=Oɣlp龳[šHδ@'NoQJ3IvHñŨwIwjOX c&ZC\v:;]Ukw.Τ; w;lۊV=7Jp{f=|3AInLp5y~"ZlafSZbع{.ߝIt&~U uwREn=Kvc*X/Uwǟ{Lr48^•0OGz>; U'gw$UICšhv5VH\o'.7퓽ç {b"xи{ihM\tMݣg.=EZt^1%۸bvvv}Nie>`%O{I7WOoo~]͟@wvb^v7Vg3DxM|{>6oinI4vqMRw ZW5CP d">AK|uH<'Nq<^5Oꯘh?+cN|Շ+]p15[ ٽ,Ia&\hϺ<=Gr'IW7K$y7}qHOqFZ}FxT_U.S%NW)WRI$}mZN:^ڔTg79ŎJ*54iwCMM%A<.3xM=A2;2,AL󴐫bAބ W;L +mgd+Wˠ|tZVu%wН;:'dr8lJ6I& މ,?8Bzlqk:߹vi&4o7;=dM%n8߭twM0i;/^j?yQtp !pz߉Q9OQ3ceOzIC>:'9܉6 ΄]'vhL1v+c _r!{O$tM}ZyUz1bս3V^(ݵC ٪K:8>]tN{f^Z˟ڟ6jW'w#+ww/Ϣ :`Vw:^HXWSQ,ihqA5ɃFq+J~'ni:?=Oў$}\ ;;8{)DNlbIus>ld:;t:ּvI\࿴rovxKU-5,Ck..?6i.Tބ]ψMMnO TSROt7=o>S.v/gㄹ~w3ҦlCt\kⷉ  :|S~-w0g;o-fz~ '^ :ګVgPҋiUZzM6Pd7 ?=G6P*m T7!Pq.)y+l|G&Z/)8?xgCNKkJ}V%r1!,՘_..]κ鰻gw kҍV+ltg$+1C~pRwM{%TO*=rNϣT/WH7-AϟCrٮOkqb!jdILlrꣲn;aۦ[qW{)ud8YUjt ?[UE(WjV& 1%ݝιBKmtۡ間9Y$y*ďVD\c%]N CGwSp>%\_.nFF>w{i#uÃÒ0>+; ɩ~)_]ޮ.Wiu\P^~V/Ok. g4LM0PlF ''|1I;z޷x+YWѪ5x%8*OQ+Ӥ_5.vd]<8\]j*|¥k ):ڔ)ڨtǝ+F]әSJ rR9 W+IDAT*x~x]QZSUn='|D[pO#Z(dL؎u/}ftx S ~ӏAns9{O=w1pu;^ivti)N|J)眬 ׎HnNj0+GEKF.]i}}_K9dO柮x ;fVq6u}gq> aN=r3D:Ygbω_hs5߻x3J ѪϵDtNd$w|K~OXv$Ȓ ;ow~k띷^LxIGBv$ۯ*0ʯ{!$lSnHv/z1Z:|ZXyG^^׮:\$ԕQtuy0:*)?2*Kt;.wcuj׉ÈzEr\~q/(OOCpNiOS)gԃh:WYNG&'q؉ݔiT)_S>mwr] )_gڧ& %Oh.oiqw'n:=Rs vR{s/X&7K*kCO6;ɗS;y=w={ZOOa]WH5['r]OvUH/gWT]ݩ_]t $s\[o]vTy5ɝ .[얺 ='%-kȳ[汝|U#E |Eg/KOLCzE:9۩:}QFt ]&.a>՛|A9q^e'#=+?hJtwyh"Pģ")ΧO:٩:M쬘:Z':Lsgw%/iHB&9'Y8:~V1.'Ӿu>=;H&/JLN^O?㎼TgՔ{]8IJʬg;sD^ߑ1I+x`b 9MTh\pulXj)S*՗t"* DHc&RrNɦY-StڻyKEeL0]=ox"jBwG#Lw5/[j4&s~w2];{Nv|ZN$ROwӚ2aONvMSu8KnjғIٍ$c7tMi&X{; 1{ݧ!=#E2n*ip"T'ZK|* Kgdz#@6O.(DbڳTO_eI_Kvuw/.ﴦ:zjbI:=w/*qxtYt<]~yV? KMuAeePS1U _ɐf>%}:~݋͎}&_*"ī+tNw8f#pNNg. G]/T_OwF ֨[$wWMx).J;} ^*Xd;jر^+AZ]MI?P]?V7:jA_Tʟ~x㞻%$&hp%{JOw ۫&^\#I/:l3yb,&q6VީA~whVs7S9\w9?k]}pE{)ZV.ttHt/ٮv{%3+;>3 =:nžT{tITHC.Btux(v+]S&s:$KWVgLE$Oϒ㻾(O|2y}⣮=8&kZ&/di':N^-UJw=-#eɝWު{dL|AKw?bL]XFKV4tuʧ'/4L9}n%֝ʗΜ337:6zf#I(Ϥ;K6gwQBBIH j:589{tr~#O^wr .Jȩ9:j_G;#;%@~ ѺdMZӸwto24Ѐ좼$ZpTZ"ZSHsSMp5ѝ#ݧbæ4/y''CW?4?iP:P3z&=UI;]]ճ$ݝkw;ͦgպ7ONnSKy>.Vn$YNO} K:h;_ documentation:lemonldap-ng-password-expiration-warning.png [LemonLDAP::NG] /> lemonldap-ng-password-expired.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000002003551325274564300460260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRP$sBITOtEXtSoftwaregnome-screenshot> IDATxw\WCG)b!aQ`-%&Ɩ`&1Dc(5슂l@Yւ"k޽\W/_>1sgnp9DB! ]9_!0111t .!p3=#p3=3>M6B޽׷|7n!LMMMǎ۷.111JiӦ~~~껔t…7o}233(DDDݻEBJ}xRٹsѣG7h@k 255-nݺݻwרAdFDDnZj{GYn݊>x+++SQVlTcZb+++!ѣGw:tK|}}M'Nܱcٳ?3ԩSF߿ҥK:6ݻwԨQO<̟?[l>|ݻNNN/_vwwׯ_^^޴iիu֋/o(UT;::9sF5M*G>| _ŋ}||?sɒ%?!0((9""Bǧ4t lذAwm:NZՙT'hWիW:t;wȍ<8tPvvvQQѣG>dȐ)SH?eee?XvmfN:U8mzٙ3g՞={N2eܸqjTTTÆ 3ftԩ@wʔ)ڵZ۷+,;;y3f}JKK;v|򲲲I&yyy?%F8iVWO1\T*Uppp۶m п''S !<آE4o|ʕt\]]-,,>|(غuӅqqq m[kvI!RLIIYhǥqqqs7Jr ,PO?|č:ggg 9Nt+J^֭[WXqȑ>hƍZ{JZMj .رcXX"++KR=ydԨQ?߿ѣʟ ::]vB͛7;V1z~o߾BkkЁ>>>=zձgϞ'NzѢE[n5mɓsB>}w8:::88dee-*XR}]tZz]RRr#$%%IɔdݺuBOO „ AAA鮭Vl͛7+* Ts !ݻwSRR<<<ݫP(̢۷o[lٻwoiii~~_|yҥ !ƍkJܹ3&&fǎ6mzCBB+ VR&QF !N<ٳgO!'^'hLz_c^^iӦM6 !ԩs!i]j ortt֭>͛7̧Awm:NZɲ%}[oUVPUK@@)S"""/_.7NzCRz# 7oEddd ,Yoٲ>Viz}}}m&= SѦ}8qbcbb 6*ҥKzmyyy)S6lP\\=iҤ!C,Z^z֭rrrJMM]d\ӄ>\} !ׯ_W^߿ŋeul{a=cdee 4h۶ms̱_~ݧO_VT̙3Bs6mڜ>}/ ^gϾy\ xKJJ w./Yf=x`.Z50*37p33=#p3=#p3=#p3=#p3=#p3=#p3=#p3=#p3=#p3=#pAܹ3qDG'gz8 2ŋ. @-`DLPCjsCĉi2 Zݾqඞ} ]ڃ ,<%8a^6.qL ]jv8˸woEM3{;| mFĄ0/ZxZ}]gh96~wV.◓zLgvM|{/*wV."-d}+&xtBb ,@BGkҢYڊ JݹAiq^oNdg!]&6YXZX׭аiZ-zrµ򪉉iGWI%f[7ֳAKʜ@E\EFj΍W^yVny"6#鈴ܶ ݁zL-\}mͽ+|,Trǰ[ͣ[8ѽSy_5{GITWwT5}wAAA/wJ,H:uJPReEy]#mB\<{|ߎO@%[mB?!3;{ eJ,*R)K7SA޿J{rMGg/i91eQ'f\U.Ϸjoi 96ͻ}ߓGX={ZlimƯf]oJOݹ{ش;^ؿU^6#&eҋ1Q]F}f'--)>xڥhi<. xJ[p{.2gM:4CWn@Et5QYZɍ'~ +bsy]Zc7]YYXyNzK?NTnBp={SP߽ckkkNmO=Uꏷ3#(49I~Km:Tu^ZRUz%.PsU*ӏDwkݳRաs/R_-? tS_Uܻz)vWIci٦~]}^K<(5  ݞεBRe)שomˤ@u@hs{Rv}\Vܻk>Vڙ7gMsl,/-^)MY\ja]W^p G73 K?7v}Xh.B⢢"/++9n@5v,F}K^NQ*M-~;-Zۿ"w|tOEY>2տ'B211uj!xxxˆBl2+ڴt%K<Bm*UԦ/[Zv%/ߞʒh?$N7'iƯ_Um`]OE!XpG}uS~~;wlllv* UR|yoPmW/-J;@!蛌l^Byh-SQ'NՄ}ƶ>raVy+ ʏn[ZR>˫v5*;j}ӧQ: !,mlf2ґ? "]To~{WZM;| @MO='nMrS%#ݗWV~/Sv}ʫ՛@ ,fVfZ:ڨu](ӓntzv}=S;ݾ Ǥ7BW(n0 &&& >6t5N _- Rg.zFg.xW_}̙3.@- t~~uCnZR(!!!ǏߴiCBB P q@ôNR 4H~SSSUCWn0.L BoYh߸qcSS۷gee͝;ХUV27t6mڴe˖d;;;]t4iPkp@mEMIII~LLLV46jjmڴ5Hyj 6xCo+׿uСL#Yp@F' 8qn&Mbς j=f)¿UifPmmFYb"<Ә(D7PmmЋ>3t t !ĦMϟ\~J_ޥKUV1Jn0v&&&.%Y wݵkWL#rĉTڇ ՜XĄ_5D5cA]t)JKKeoooЌX||޽{USSիWW9DS㾾OuQQ|@_tIZ6mk׮}=<<^h}jRph#GYZZٳiӦ:N//[[[ߺuĉJe˖=z9rDP6h@P!]P 6h\MsΕ}IE.X`ŊOL3g^z&''z{{-w]h͛KJJ{voܹz]ʕ+sΕR` xKhh#,,,մ .%::zĈr"(--]v[oU3.^O?!ٳg1f9mB̘1ȑ#U&!Xr+-R~ƍq45ŋ-B;wܽ{WgxxF"8uT=_щ6n8k,)mQR"p1>Jrȑ#탃喭[jC!Yhh1c7oo߾ʜ_SoѦM1==}ҤIBN:i_T_~BZGPc}wrz?ܹ322rڴi;vTY\\r=i}֭0aRRR2j(? QQQB:u̞ǵ`('N4t t"cǎǏKˍ7P9:ٲenj4m>|}}={<SOڸq3gδhBTzxxo ~gB5k̚5KjLJJƥYYY%$$!ٳgK[~~m5 ߥ SƎ+rJ?%-ҭ`666Ҵӏ?^z5kkƌ+VpppP*z.G}P)X *sN\\.Ǐ $-B[[ۥKVӧOoѢܼk׮r-ѣGϘ1C4==믿СQo,Rbd"##咒qiVRR?O6MZuppyzO)JClO+QTM6s玴eϸq-[vm=$$gϞ 4+((x!CBB8 :u}%%%4L9|pxxxfZlz혘հaC{{{!Ĕ)S֬Y#=^{-44[R?>55UQ~DjC@q{ɫ#G,444モV###z֭̚5KUѧOg-ibÆ Zcy?`jj۫W~MQPP>/ &JPѩS'wwwBCC嬬$i9 ѣV++o РA_u͚5dffֿgN8QcEviϞ='O[ϝ;tR''ԩӼyOƤ5JIIu떥e߾}Ns֭BSS ...zF'++%ЅOV ]]\@\@\@\K C@=#p30>&&&...zFg.zFg.Rx^vر$  rrrRRRΜ9۹siӦU8SNU_555ԩS``z{aaa>}F}!!!VVVCP۷O^ϯv}ƌk֬1WFfFw}'~4;;(**ڹsg&M֮]yfmQQі-[t%Ѕ'\ILL~lii?iҤǫoh{yyyj5^qqqrrr[0C@pURRRݛS}5jђooo?qDiocܚ7oڮZJc©S֭[ϯ~>>>7nܨFI)ZeC ILLƾȸ PZV* "%%%$$$88ϟcFsΝ;w:88[TV06RBU*Ujjŋ'L;ٳ V;v>|p%..n߾}GZBCC icc|xj놮@\j 6X[[?gII\E]t |]t!D7n,H.]T}%K8pPIYYY..RPX[[ښ?joԨј1cVX{G777OObyիfff>>>}*}7SSSoo_:u-[ݸqܼ|u>k iiӧ8M6U**J]|ZuU!DeHҡ]vꍗ.]*))y0.JBBƍ322mۤ#G !VZ޸rg9&ƅYFDDD\\BSNIц >ׯlݺ?^ZVT׮];sbڵHAڵkذa&LBDGGשSY !p0QQQjvvvvvaÆrRy%%%Ҳmhhٳ5fݻw?>:::::ZѮ]Gvُ @ҲeKC@2C׀dZ7yc@_[NNNZZZf<== ] /Oyqqqqqq1t C@+911@\F>|͛7 ]= pbΝZzC@ T*׬YӤIe˖D5KYYYAA˝#"" ] pHRSNuqq5t9jқ7ow!55,sC.EEEST]tQTrF[ je˖ j9mYxqa^6/j ~GNky^ Ο?ix0.U`nJMM9s;\\\.OG5c.]ϝ;O2W^1tQjWWWiBׯ/^x̘1|qn8p'=z?Ƌ?P>|[n"pUSCB6!p3=#p3ZBP<[YYYZZ> 8syљ3g4iQC!pZ=ᒟ\\\\%'''%%̙3;w6mQ-[߹swsssww3f}7n͛D"C!pZ9z;yDG7 QZz!Cuٵkbȑ>|ݻ嗞`xPkݾ}[ZgϞ=tPppرcpͣ .,]ѣҜM~7<==oBUVi&99AqZϘ[o._|pY\F)""bÆ :HOT~6.]iC7NNN[֮]+ S?<(͜~z!Ċ+䴥"w0`s6m4qJ @0JIF裂[\ү_*wB̟?Pc2m4!ĢE) ?s̟Wd/|[[yƈAizSN9Rakkaۜ9sۻL:U}ɩs45}~foooeeu}Covvv#Fؾ}s:EMdHWg~~s &۷O^ϯLm۶1c!ԩ흖iӦ T򍮮vss H[4klԨQ}'|RMv̙'O$%%ݺuvvv .LNNFvvveeeeee%%% y޽ѣjڵk.\iXZZkTgdAXyyyrKJJ޽{srrz(KK=z!~ϯM6_42>W_k֬YݺuGYJW^4hSRկ_~rw!*%K۷k׮K.#GTov\\\V5k֭[D!DaaԩS֭W~}7nTÉhժ_z^ya5bիWKhvAmvԩaÆ?^'N|}zƍܹ{V\P( z9OY.``e6Bgk?6 jC !FQݳllllllܹ#71B{_6ԩSB337oJ-EEE{wVZZ*/ a+.]lrƍ˫B.]f͚%Xhzq_[nU?o``zcHHaÆ? !ԯ"==̬wrIǎ5orpΝ;vvv BjQ(?]Ne%NXJ?^vP(4JTnܸqƍ~3glڴRciӦR_hBhL,C޽1]v4hPffJԕ+Wݻ'uؼybɒ%C,^짟~R?EvF"tR%Kh`ڵkaaaꟆ;V\\,kiii3f̘8qbvv7|q4''ɓ'˫SLQ(QC˩LUվdJa[ϟ_p7Y[[W SLW۴i;̙3Gn]fӧSSSpSBĶmێ1"00k׮rXSǎ4o<!DbbرcCB˗/ !OѨQ#b9}ԩ^zGLtE!DFFFffz{^^^iiɓ'JxǏKm1w)4___m'OVTC˩LUվdR}%B{ڵk?oqĉ?s$лwD **&&SNRg)-ٝ;wTB]ӦMJeii+,xI6r$㏥m'MuK-UŶ2UUGB)ʡC:thܸqk֬yUx%))Ikg#Gݻw޼y ĉЫW/!D||/(=1p_@zH"M$O,yɹs眜t.ڵ+--&u]tD^B///!D\\n*jذa>ܶm'|G믿jH%٫% yٲeƍoݺ8lذ3FU7fdd}#G&MT4flРAv6l؄ U3{쯿O{ײeK!Dnn={^L<933sժU#FBXXX,XCZرCnݺmܸQAmcc#+ݻիW}||4^ڻwܹsXyM6U&ؽ{㣣|]vG& mխ[^r+7:99}B[n?AcDDĀ vI]/1c4ӓŧ~:o޼PժeRVVfP,<}0/~l wxѩSʻrJVVۻQFPUǏW*8`NNNZZZf<==zk׮eddzyyI#V^RR߸q6oެR;VTTs=gd2  p탂tqwwwwwUeiiYAOd...T[Ugjj>3zjUr`\=#p9s愅Jڸq˗|MCP(~GC00>O5kV^^^xxxHH0775t.crc/444==5 8}&&&W^7o"ؿ9sbcc}|| ].ʕ+|ٳgW\9qDCW<j:tҤI&&&&8qKVVVE}#WOViժ' CUOVVGJLL,]}~駊ORҥKڇ2}~{ IDATZg@gRVVfP,rȿ//Bj phZڸqczzC'O4tQS ,ZO?С/^Na^6. eV&L(+/QR] ]/ZDDK  @O2t t!pӪU+C@=#p3=#p3=#p3=#poB'!!%Ѕ@\K    7t t!p㓐`Bg.zFƧUV...`| ]]\@\@\@\l%Ѕ ]]\@\ILL4t t!p3=#p3=#p3=#p?{wՑ/Mnqau(HnB 0.Q%%!KU$E F]Q5aƸp \(6rZzNUOӁ'Uu03.'O233/]Z4gՖ16xw7n}GPر#33ݻwݻgư͛7ifÆ 66&A/]^^QQQϺ43\}w˗/?r䈓ŋnڿ+W6'Nt>HMM}著WNNΚ5k|}}{=/(--6{ύ|ʕ 7oNMM}QYܳ9hnH0mڴɓ'jۇV( ,8w\CRoVaaeJJJСCoYf:tY߬۷z֭ꎎ;vxCZ:ƐpA7nܸofrIXXXll L&MTQQhѢ˧򗿤lrڵlNg󂂂$(--Uչۖꂂo`ǎ~ahhhfff]jZZCS YVyyyRwΝ&>ZQy]ʲt!T߾}=KӍ# 4a„Z+k:nJ' m۶={VTĜ}H7cǎv*ɓ'76l%XbR|WT*{>Fn~ݻwwww8p:Ɵ[mGu4OlTNNw\TW2E{n!pDEE޽>KMMMII?T*:Ndgg'''O2%00޽{k֬?رcAAAqqqf6m󫯾*P(>|qF9^}ժU={,++:|꧟~p;CT*cbbm۶h"ݻ744T3111$$Du;w.99yڴi rʚ5kF6z)S,Z_~Yzq|}}{-h4]5[/\pРA3gtpp(++>~"8nܸtҥgaVQj6BO+;88888ܽ{W.B$%%i,8x`B<}T.5kb…S ;x"44T.h4BW0(QTַnݒG!DϞ=8t^3f}nݺjBoooӯ`DqqҥK:w,m 2uT!4B? !nJoڵkףG&L '&ܭ[;vJ%ׯ_ oomm]=z !|}} DF71,,ӧz裏n߾m}J}u1Ru4ON1bDFFFll+C޽^*'6߭9[n/^j)gUtΝ[#FpuuڵkNN!m۶m% H=b/&y󦍍MAI7ykn6mڴi_U]j>VSE]L2 @3A/ )r3f7={>|xԨQkvڵH۲ݻLߜW\YXXߏ3F.LNN0@S6..D)vҥ* HyyٳgϞ]PPp]vݻ˗=<<ڕ)Ϣ.cI^:nԨQ8q㭭Vt:G/}O?looBZVqѦ_NOdƌk8p?7n!|||*++@ɮ\Ӧ,xxxDGGϛ7Oќ>},>*U׿eC/w}wǏ6(Չ'lْk7yYYo}׮]{7M .\J_߿oP.m7͛7HN.\FdS:IJ|jvÑ^'$:zj+W笤ĠDz˲41"1Yէ?*iÒ"~K.ݳgBh~͛SRR vr5jժԞ={FGG߽{ȑ#vBaaa}͑#G+WW/A?~|ܸqˆ&Owŋ;99-]G.\۾kӧOߴiS?3777V{ܹ7F:7ĉyyynnn5#;v̙3wرjժݻw;RSS333۵kנQ?kaaa/ʕ+˖-srr65LyuiƏ 惄 ZH?.o߾vڄiZBPL6?ϝ;wƍRIhh֭[o bll={ʄV^%/#6n)S[[y iF]vrr ߻wC{T!ϱc Q?Svҟ2p-[Oǔ"3Yէ?*1yDKGNVՖ͑Ղk=FR\\u֧OZ_jO:/fTRR?yuUUUNN΃:thfQPPpEww~=k5ڵkfy:&>.t $'NtՂk= 1 #.h+WX"55޽{uUc z)cz Z.h ;w.YdŊ_;vxݚlZ,YdKGNlѣGbb˗윝-XtC` 4i pyv@F椟vt,X vE <+"`f$\̌ <'Nt!-OppC` 3#-϶m,cH@3ydK.fFH 3#`f$\̌ p'11!0 <&Mt!`f$\ʲt!-СC-cH fGeff^tjD54.H4ɓ'jĪ19Zpq0~233 oݺeoo1a„Ft5uTCgggaÆ5RB,Z5M$BT?xҁYЬX:Kzw ???~HMMMHHh۶mJHHYꚖhpBRRRBCC_~e鰠`ȑNZv_~ EIIBXbΝGyf6 XXQQѠ& jոE'V/c!ǒ%KΝkJ666{ޱc׭[?^k{j)GcƦ7VPPSUUՠV,--Uչ5{6Sq7n矍gj۷kcl#TUU]v-;;[ysWjjÇf|\Ԉt BpҥK,oaWW׫W2۷TGєMt:FNNN2eJ``{֬Y3|OOϱcl7k,??iӦIjEFF+℄nܸ;YpaEE>|ںA7 ٳ[l7oހ߿rӧiF)C_pAfΜPVV-O>2rȑ#UUU3gHN/'bۧh ${n??>.555%%%''RGwܹq?xK.R۽{JcLLL tF^(/tj#Xpq`FA31eʔ'?a͚5gIII;B( M6 !\]]+++ .!T*UEE\xekk됐P! NrŋuYRW~0Jemm}֭v.lkkYTTdkGxBB\x]'''BhLBppA5??I!.Y+wE0##CSp֬YB nΝוOV!ԩSw ̰@|wIII;w 4mhמԥ}gZZβ6lB.O>'OjF6l|طo.]4qIwWO6z5C-r2pgg_>ڵ|h>ܠ!TUU=zH1r6$$D!O ˓֚]v*Jn}v!ŋ-Zz۶mW7n~VNHthYXR u1Zsy뭷~O?^Ç3f0x'Kyyy=}.^j яׯ[[[!扄 nԨQܹscB};wF=gGG?Ps !VzxF`sN z#Fv5''g֭ JՎ5*//oΝY ]v%RǦa޼yڵ[~M^z||=ۦM7xȑ#%%%YYYC vvv>tЄ nsF̗nufpY͛6665w(PZ]]m(ԉ rrr{'Oo[Ϟ=Ν;fyeKQQѓ'OQV\YXX˳EɍH?˖-3X##6 9ScfϞ={삂ӧOڵk޽._Qِfff>}V*m{!___'''Z]v-**OH޽+jKssst钼GMsh.2onXF/ !=HZ㣟B4bҢERSS'MhѢg2%999BoQ>ouWlll׮]=zXrjs1Ee}ll={uRpyV^%/#BI]>cS'N.  /g̘!rvkBCCwڥ?e[l2,=:88H;JBBB_TsPzzܹs7nܘ!uV>뒖{復J)cǎIfB+VZZh(ZVVVV ?To4ZԩSZVP\ünܸۦM???)cA%%%?Mppp1jJ5q۷WUUJ 3j=׺2BQ( 2… ?;W~4,)BcY: ]{ǺN9:y cpp۷srrfΜk.Z-.t5 ?K @pfKz!ѿE?Ɔ/n@ÿ\>}믿 #\ Çh(4 hڶmk(GKB-@kBH 3#`f$\̌ p03.fFHV0cvvvfxqpV"..nҥf0888## $\X`nJKK]\\Z- H@+agggHuu^@l IN}8K.G}dЏN>}zv򗿘yza0+&VVVV ?Toyh4JR8::GP(**##m۶F:1b޽{èd(Jt >ЄTP4f:"6lc?to=rȴ4Z^PS\)%%ĉ׿~SN]xCww?_}՟곣P(>}z=ҡ5FſԩBI EEm^{53y!hcǾ򡇇ǒ%KΝ3p}̙3; ƍ?3ZVV?|Д666{ޱc׭[?^ν{jyyyjFFQo'Maj-l0`؛F]mZRfNZIdcc C 9{R9pG^|}};u_ut:K/)TUUuQT۷O.LNNV*v˧Nھ}{JձcǠ7oX011{*C֯b R+T*wwGGGl۷ !vޭUU>~ȑYYYFt:Fs#FL8qϞ=|MOyf#G9#gΜyFٿվ}4͠AèC~SHHN3%ri/B!,]tʔ)6lسgO\\͛7 r%y .~׿u޽)))3gμ~~; Bի*՚5t/,Rh_UUU[f{<xbBB/>?(|ԩ7xCqqkk배0/ǏB=zt޽ǎΎ3&222//x0_]]]N~%KTcǎItF1|~}߅ﷷ?qℼgرu֬Y^^^򭐹|ĉ۷o7^co!B~tp4w}WQQ!ҷo_tۑY?еk>`͚53fղeˎ?.%\###=B>|vƌKK~rss>z̙3C իWTTThhh``~eS:1r㍜mz8 8 ٍ5W_;wn.k׮]`?۴io9r$++rȐ!·0aSy󦍍Mi۷yuBqm;w=zΜ9~Tx !DLLLͶk׮7mڴi&!믿?`;1B[M6 ޱ0'l /gڿݻw4I]Ssu t=zTɝ;w]tiu-.BrJ>}cʄ m%999ߗ 2DyPiVExxxQQ͛ʤFJٕ+W>}Zo0...F^'8z)cNOO7oF9}t#:i„ G4Nͱ !Ϟ=[-H҃ou'.ʕ5o߾W^5cd7n|$dǎ[lĉ[lͭf~~]Çnݺ^"wpp8| BR_7ѯ-XzUV\ib|򉃃úu6̈64Ht8qBq_9 ǏիA!!!.\Я֩S'!ıc g̘QWN:uIIIFZZkZZZ``G<ߴI]+K^Z'N__O:vZ M?>yDV߾} bΝkD ӧ/_~FѨ1rիǏ/,,=ztUUUqttرcdzhոΟE'Y8::Z:@cǎ}C%K!Mƍ7 0@. B4qM>}:utU0JKKjunnB^^Nڵk:x۷o;wNVU>lܥmllzc//[n?~jS%%%5O9} '']2ުj<Ć^.5V5[n߾}=K ڕk&_BRzT*Ko+VP*Jrwwo߾}tt)t:K/)׬رRܷo\T*w%tŋ+JmFGGorYRsݻ80++KQ^^>u۫T;ݼywI+(..6(OLL޽JСC峥Jr5>y򤷷wnzZm߾U{njv.L{jvҽ{wembbbL0{,11Q1|6BwKSRRLم׀"## EqqqBBB\\Ç7n(_k… 9sCYYYvv#lll|}}9RUU%{iD̾}4͠Aèݻw}gvvv)))999?JBi4s%&&7.88]tݻ744tڴiBĐNgoo%B( ¥K.Y䷿ \]]^?dȐQJ !4MyyD 8;;;99yʔ)[f===ǎ[UPPP\\ܬY!^}՚k2d…8pakkkGmޛ0j6Bfo< SL8q㽼֬Y޽+FEE !zH~![ V*JBppAJg? !222%KX[[^^^r.]ȇBOf͚%Xptx)wܩQmhh~aDDSNߣ_ռW6mB… BJ?˗/[[[HF%W>x\xEhkYRWaѠU;uꔭgQQnGşN`Ihm;wsLTZZ:|Ǐoذk׮rybbF1XS+Ne˖-[/y*9sTkMVYYZJAF*!1YYYyyyҒk׮ݿ_RIo.XxګEY[[o۶M>>>ƍ/J{/^\ۍEyyy:w<}tOOϴ4nذA~IVkaÆɇ}ҥKB6`C4]nnQ:t]ڇȔQ7)FZj80gΜzu@Zv.\X`{ァȋ }aϞ=?O?T.)..^~ٳgsrrE%$!DXXXVVV^BCCd)lkk{IgZ=o޼`!DVVք 233T!իWܹs>}.^j+_nmm_/1ӧϟBhڇΘ1З.]B?z̙3RJV5_ݯ_?ZmJlM7|S;vK*4eMg hFꫯz{{ϝ;הN1bDFFFll+}iBQXX(a0Ν;/,,|뭷Fڵkל[u͛׮]oڴIZe˯R2^M6oƑ#GJJJ*++ |С &Hy iK)ݺu3HԬsM7Bۛxn߾-ߙѣGϙ3?ܸqCޝJjfk׮{|)BBB>>KdW\yVuqqqh49/???!ѣG3ҠhĢERSS'Mh"Sڼ7`^$\@+Q\\\pƍ?;vزeE߿.:qĖ-[rssߘ͛IIIuJJJ ZI/`'2[.\/Bi\)c4`ʕ++++ ]$"zj['888[Rh/fcFƫ땘|oYSFm0#V"===...44_w333۵kW_>Ӣcǎɻ.]tϞ=BNgțn޼9%%%))GM3""b޽/vrr[ti=K<==#""¤e5W\Yl%bذaח !\\\z%o5|͌>ȑ#_}_|a|8w~w'M$HMMn{/|k׮ZJnݺgBٳɓ'̮)L IDATj܅jeggpqIK&OܫW/j\-#++z*ρ s N՞:uJ*@XPII?`cc\2׮]BwܹJ{:\xxznܸۦM???)cAӸ8Kep9UUUmٲի24fsYf=z(!!!""`1pN073f˗ɶ^p$\$Vݻ͛l_sÒ"4޾}>S'' K@sAqڵO>+W|;9]5So>wN/,--UչUUU/ZPPSWT(RꌳtnXXvҥK{4h`Bٳׯ_tiN Tkذuww/<777<<< E^'SNBH3D4\]]uNtssӤ'?ޥK3f۷Ν;^KNNNI_ڵue!DVVɓҳgϚ$H\]&׮ТEݻ߾}[rs'M믿&%%I%[n-..&fSVsb€U*UrrIJBBB"7οL&.dm3a„^z͝;WOz+Z.))>Bܻwww}w{6mڴj*--m˖-:5 y޼y5Zn݆ 6l ߿TTԎL!Ĵi?ٛ:6jHDO !w]v%%%ҩc-+ !>㯾jƍBDGG[YYM42& ,,,|رҁ:::9rdReРARk7oZZZ?7ph##@zS9ai9C-{ݻG)ܹs ͞={999gΜٵkׁz+W8;;4jݺǏ={V9/Rt[jc/'J$տe]vU({}QvvvzzzHHlu:ed`>'+>|8!!̙3VVVi3#G;88HMUg۶m{eEEEl T)zeݺuvHoB*7nݺJԸz @ ԞH :'JN:y欬DZ͛7W&UJ~~Nb]"$?|5?c!WMk/7nڵKseiF?ʕ+O,---wccm&e~a˖-7mڴm۶^zU]p GMOO/Biܨ(;TgͪW^scIM6[:80|K:88dggwIzw\\\5k&zet+>kkkoo'OK+G&Ni|M9s]7ޘ={"77~xّ#GLg.]&NɓwZJÇ2$>>> `ƌǎ?٦M/p&e+VFOoSVsb̃4t={?^Z$hݺu.]mn_,Uq1b޽{nj"سgOBBF6 #WɓW^$/w.׮]yfki޽{Μ9?Caab&\ ԌԩSdd5*>},\p̙ zVX1o: ; Hj\.o߾m@ZEPvݡCٳg%$\5NR[M6EEEq$\pٲe[1w8@"=j:us||jrJKKo߾=tnݺ;+s.bsG~SW\Bl;ټcWsG@}o;pAUDDD; /K6SsL2sǀA/a[\\\RRR!ry喖,,,̙37n  W qG۷j @ *,,lذjjH #OYYC` #`b$\4hC` CH #`b$\L p01.&F~!0 ?...!$\L ?'N4w !֭[CH #^~**!!&i-)))99$MI/???!!ի&:P5&A/?~{„ ۶m3Ձ1dHcǎwrS%\-9suLrWPP M!a$\@ϯr]~G}$+..xx {k=5wѣGXBqqrKSSNNNZZZ.ӒdQ&d81v>>M6-(((w0TQ !V\ioo+ ''ƍ;֘ Uxƪ9UA@-8uꔹC`K@S˗/ʲ߿ȑ#uDEEƖJ )Q*EEECJT*SSSw9iҤ>}͛͛ӧ[XXh" ڻwܹsO?Iω&`__GlR:}߾}#F幹yyy|v:ti׮]QQQq„ vvv0EO>-,,LMM=yB*T8W!0 (bŧcm*VhЩ$lB++)4iL& Q*B Mӧ`>)BKڇ@!D d!D||t&Ɇ ֱcGM-[j/1>r`zzBPk \"tNYYڵk2,..NS(^|P(`6[t}III%%%¾}c* u PV\/}tvv^r+JKK#""5lή  !r_JKK-Z7nOtuu)qss$~L]S&nvVVVIIIBJ+MvHHHBHspƻ|"++kzIiiٳg55;u}]Pah/H\]&ɎhѢ{onMr-Z:uBctkkI&ҝBlݺXbNU@@ϻt2cƌ}ݹsGA*UgnB6i!YXXxzznԨQxxxnn>y󦥥~ƍWSSS9RZZ:p@WWWGG#G?4ȘU6leff !M6?^go\QF:%RjӧBi:|vډMߔf{YXX{sYjU=?Bh4iR5;eLTꫯ7oaÆi_ W3VqH&YkذoiiH۶mU*x7}??҄D+++ifBBZ>r䈻Cc3֭[ !={VT*thDjժ:/Ȓh{ݻ9w߅Ϙ1Ød]*{>z(%%%===88XGvȨfϞy֭_3gz-uPg:\xpիBmU~!D(IuVZZ]xϟWx 'Of >Ǜ6m*,, B?~F?HC*իc:Ƴg.]XaE2fڅƇ?|۶mҦƬ'uRQ9;;;vS*gΜ1Bsu~UF =EEEҒ#Gj:ujYYY&;mmmאր^Z022Ҙs===mmm=.}1BH;FEE 4tH_>|XmPZP3| &pƍ]vilbcc#?ʕ+O,---w\RBM{ޛ7oއ~زeM6m۶W^n+1)ciSzfN*Ug@߿bŊ:33s_|]sӦMqqq:裏>V333/\ X~}Æ #|}};f̘!Ğ={(СC#xu]t ~>kkkoo'OKB&NY徔۠Ϝ9s֮]o̞=E?<{L t2qĜ77'O߽{wժU͚5B >|Ȑ!3f̰r`YY륷i8::~ҧ^Q xȑ:B 6Ls9766V.k+VhUXX8k,WߺuKr`͹VVVK,~ ^VVv-'OC;UaTGٙo߾W\ѴPaR<==[hQ ;w׷ ={&Mڼys1Q]~ƍB Uq~~ 6i,)5 :Q,WWWWWךkk{{+XncGJ1)cڵk׮]S3ίBr)s.xݼyxb\\ܐ!CP9ueg Wӧ'L 0f1P `Ǯ jmJ***λjE{>v ] ƌ_le][JP7ճ-P_XZZĄ^-w /ݻwwaٿ'NT CT*պuڴi^TTdpT [ -[ֺu똘s*H@]VS:;;Ǜ;C۷o:gϞiii h?P; LV_r[7yǮu"|3g7n@:ѣ}5w 64wLl 2!`b$\L p01.&FH 4w()))..mllk}eB^5׾o||<9$\%`>/((hݺuII $\%amm]sِjx)i.Up՗Z#0?HDEEO]Ѷw^!رcl3###00pҤI}y5kϟ=&<<<,,GׯoӦ͵kעx…={Juϟzj''UVuܹ011ŋ/ɓl2eʔPQpϗ,Y"9::z֬Y#jҖ;v4ŋ>e˖-^}Eƒ"Ʉ8q֭[ ,P*FtRQ*g\|YTfoooMEѳgϖ-[hJ֯_/n{ÇOJJ*))BlڴI|rMEpذaC E?c!󋊊t>Ҕ !-Z_Gٳ/\[o=zRaxEDD,_ܘD@߿Ŋ߿uffW/BSO?}'H3D:;wNzw}'ٴiS\\\lllN jժ={tyر~~~666?vؑ#GZhQN̙3gڵoٳ]\\?ógώ9"x7OaÆ޽{ϝ;m۶%%%)))>oۜEEEu)22rBGGǿoo}'M$XYY͛7Emڎ3o߾gΜqvvlT;E 4XpiEx0~P)F#F?ܚUVT+J;;;5RSSU*Ufͼ*| PMKKSTڵ޽{5ʲhժ~Zѣ&MҒY.=ze˖^NSyΝ^^^W^]fͨQL4iŷnZzupp{׮]5'4T*mll鎏ODDĬY<<Hcǎz+//V; ׋n7oB3VtdSKKKwwcǎj !ٳgY'$b>^SvZ!L&FGG !Νݩ3g4(˛7ondwAAAy!DΝÇ5BMN$eee B&ݾ}RB_)ies %::ZSx}\T*7%K0L6lذ;j*lRs/~¬Y-)=eee:Cz:%EBBBN8!}uւ JQ.]d|#ӧOׯ_ߪU+MΒjRګ!{t}III%%%nݺyž}}F-~Ç TAz!ҥK--oZŋe2֭[u>´ .]*%EV\ʕ+7nhѢݻwBII|`iFÆ Mm@@@bbb.]Ӷm[ͧ/_Bdee$>EOJXT2ܑ{ݻ{צMVZmٲR!8p"!!ɓM>|Ǐ7mTXX(0Hoyg.]Xa¥[nR5^s'L \")#G.߾}ܹ͛)3f̡Cƍ'mYSNm޼9++@ C-,,./))Yz.~B-O߿څ˗/שf;җVx9=-N7E@۩ܼy366 MM2Ek)))uU~ݸqCښWeccx *=zhzz|BHJ'H,,--5ffii'B O{ٿ+ܿkk۷..._|vO?'NfKUT:/!O?oڴ)...66VgYϜ9dɒݻ7n~K```@@@f̈́W^]lgΜ9k׮}7fϞ"={vSQ}5*$$DVٳ'55Ugݱ>ydpp2eĉy7OaÆ޽{ϝ;m۶%%%)))>o900Ç_tCvvvxxxN7W̙3ׯ_6h LvVZIǴU~ueĉ999nnnO< {U4;𧒡CٳG"!D֭t"qrÇ2$>>> `ƌǎ?٦Mr1w޽cƌ Bٳ'!!A/@0{QG !\|Z<6L6щ'tvH2eÇuj6o\q p̙/#Gj BV/\.VXX]m:۩ʕ+u֯_/HH/>=}b„ ƌOwhwS"6lPBoo[n͛ߝ7n9R:..@VnV.Zpmll4 ,мjɒ%qqqwv$k׮ҥ9sJeFzSҧ!66VZbN cxyؤ|vӅ׉p֬YuV_;ݺuyPeeeӠAW[o+///55UR5k˫^SӧO899E\~ƍBOO-Z['333++Cگ,0:vصk1;FRiii=jҤI>}}UaΝܻwM6oO_ b„ ۶mSIII o»#rJUsrr222ܪ ̎: q64T*Ncƌ6logL;RN` 3g|A``Ó'Obcc===t_H1i͛/Xܱ.ooe˖8p@:lԨѬY͸` *#juLL̂ x L|9}t PhѢ~}''O4SKKKwwcǎjƝ={Vxr!M"J_~aPP޽{=<<ΝkmmgϞ~^ӻ`__GlR:RcbbT*PBBBʀ!\|Z<6L6MfB,X@mkk+Ʉz{ZPP"66@SBÇWxӧOKӥ]9##C]X\B!n߾m|S?ngܸqB͛kJӅ BW\d~~~%::ZSx}\T*NRc>-++[d">>^: dÆ رN˖-5Booϟk*HâEtzk_W:Wf ):XR^JRz֭=$$$##c̘1j111J˗/ !СC5={lٲ4FCRe>V̛7O 괼~z!DDDEw>|𤤤}qtt#FΖւ]~Ç B 1jҥ7{2l֭W֭[ppvtnXXvҥK`nҒ7uKBnee{֭[ !bbbRRRΜ9%hذaMhS斜]nݺϧ ![}mVӠO{Uu7~|sOP^q9YL%MSLkJD$x! !ɎB9Ȱ̇$()c\9s]ߙ~q_###iG2,<<<++K!CB !4+2d{AA\.Wgƍջk׮jz{{KӣӭP$\^1e)ۢ6eʔb<EEE zkh͛7Ǐ_UU /̝;&??ZsVDr֑K2`)[q=9`={ٳG1uԄK43fȨdJrڴi...ҿFҴnpisuccîy4<.<==VVVZRA:-BE4]_UUuرO>$&&fZ32P(ިTF)x-:J*Qgj˺uJJJnܸ_ə>}p(wJ2++K&HeeeTqƩ666m֭[aÆ k=Bx .q1uTjM555B=-\СCmH+h'-Q/ɑh9҇RTon"U*%^^^B !h FꋣcXXXZZZddd}}}NNwM&:w\PP4%$$Ν;kjjVIƎ+Zmx?N)JjW^} vvvAAA?f&&&>Bo6))TGU#FذaCSSٳo޼ux̙jHsGׯt!˅7nTϿ7n {76mj=Mּ݉w8pL=EiU+%Mtߕx{{9sʕ+RE!풛 sim۶iT*mM>yǎZ~ p;w˛7o;vluuull{onmm-۷ȑ#))):ھ}{YYYjjѣnݺ>dȐ5,44ɓ!!!VVVeee111صkg@@ѹslll,,,֯_G=s֭9r/lllLOO1c,]ӳ6&&Jsȑ#CCC^kee%eItU5kVjj"!ݘ1cmng̘ 9sffffppի322viooyfC:gǏϟ?ɒ%BԬ{ x~7K.Js5ktcǎHvn瘚v'cbaa1?G$F_ MIId~zcǎ8qbժUO=ȭ[>F N)W|||._|ykk׮={lWNx4 Ebb}LLLXGO 4/ ---.]:X&?8z#J믿뎎tǑpKTϳfz5\ӭTBɓ'۷ͭ[$\Z\\\LLLO^AR主KZ{! =Hz 6t+x,,,,7666mZSSӕ+Wݻx`'aq^st @fjjjjjӭg 4߿2??͚5Guttl3!Oaa?__˗/ꩧzQ: "NNNҁD׿}݅ -!!!999uuu{ &] ș3g&MӭU$\Whjjz'zaO7 d[ @7#Ht3.݌ @7#͌{!˛*333SSn x-u<5wܓ'O/po.\XUUedd䔟> z @/űcǂׯϺtpp{خ!;;^hnn}뭷(ÇR9E$\Ԕ6c u#FA4@R-Y9%%%<<\󖓓?Om>XQQ/xyype;;;''S\.𰲲ݗ꒒qYZZRRRr}ήuRJJJ뽼u%%*++gooouCt`+.\̶t 1iiieee'OʶM2E...Ç?~AvءrJ;;; ??ѣG[ZZnٲER.^hiirJUՅ8駟-++6lبQޘ''GϻӦMBdee;w.((HrΝ}555I+$cǎBH^[[[ 777R)Qիbwȑgggmؾ}{YYYjjѣnݺ>dȐ(44ɓ!!!VVVeee111صkg@@ѹslllR]ׯ裏{u֍9RQSS_666xŘ1c.]ZQQY[[SUU9eȑSW^$͚5+55Uvhn̘163fPG̜9333388x;w߼y!3gϟd!DjjjVVVlz.ПŽ{ݾۅ7|tҨ(d;w\f9qƍ7 ;v~/ZXXXbÆ jٲeeee;vX`de$&&:;;ݻ799:,,,66vСRVE-!!9>>~ժUB[[۷~[w'NܲeKDDt䐉￯y>} :z洗I&%%%Iw5+IMM577&900ڵkZ 6nܸ{LЪM'NXxqjjqss;{t6SeЍ N'Qy+˜o݅O:C?:e2ٟm [__oiiY__?hРvTRRRZZjmm=a„vO+Çw%uuu u͛7͛_+dgg-Z*~~~mףO_˅ޭ9r… uVTT888H6rpΝ;s̹x#x#xn֚ X:288( {'xX ;73ҧ/98IؽpCP_~Ϟ=ZgQ MIId~xLp~"%%e͚5Ҿ:-GG?|||bccO<)]0`ڵ111=  '/^Oe1Z;B˖-[lBato}}}7n._$'^m۶T++ꇅ;EajjJ¥uLO}煅/ma@".Я8;;'''H]p~ гH@E)$\Lt[ x,Hi}(u$\1¡A].++իդ1E=QYYw˗/;999;;/YS#yyyN*,,,))qpp6lի;_]v„ !!!׿oݾ}5\râE<؍ A]@_Dϯ7779}\.OLL|#ZpbۼysGօNNNgΜqqqhm{K?K/4`3g444={wyyyXXWXqҥ3gXXXDEEv------<(,,={vYYܹs;'зp}^DDDSSg}y#LW_}?0a$88xBÇw='Np@??~zNrr3|РAZ1ϟwqq>| cvSxUՅkvlذaFjQF=Ǐ ʘp}4dԩ5557551cƼy:am~~bرꒄ#Gw6)qcbb"]8qbΜ9555{ݻzw&Ofss󦦦si6aƍPTRǿ;iӧՉSNO`ӦM#Gӟiw<>|XGUϝ;7{ŋ !:4}wk6)&&f֭>]틊Mv%)Oו1C-@[Q66躵k !,bرUUU֭[ !RRRt?+4p|B##Y*innzO3L+LT;/"33SܺuٳCU_fff !|||>>fR_zxx :Tk7}ƳݪUlٲE]v !4_ry\.0&~%Eo &&&ǎB 4(999777'''//Os[܇/+Wu%jO<-ijjZ|roƛo.ILLxb~~~ssB}V !e2٘1c,X4qDuFRّBL%\!AAA2d{AA\.W'ƍk׮jz{{KӋt?!ċ/;Yڞٚ%펧>Ui}I~G!DiiiYYfymmR:=&~ SL-jSL)***..n7P(Ν~m۶u%UUUҟMMM_ܼysUUU/ܹsmll80`@bb={#:ujBB  g̘QWW'ɔJi\\\lmmån7BiKy"ÇJ~q]r{EivgQ*+++''D$m>n8XܹsG3"uh+ɢiذa?z^ vĈmv999IgEkvDR566jvΝ;:7@.o:umŇ!t(^z髯ZhQbbHBkcXXXZZZddd}}}NNӦMBdee;w.((H?$$Ν;kjjVIØi/j?RuRzt<%xg_vuh}z…vmoJKKNi݊ ׯ_OIIьzJ:zI=coooss3g\rEJ!]r;;HjW*Z[&"cgu?%K~w߿n>mw6nܨn\.߸qVo!شiSfK:=&~%E۹s/ycVWW޻wo7|Ν;gϞ6V(ZM0A}9ܕFsW^yl z'.y׮]455FmcI8uԛoieeAЫ˗.]j``-ǒ"@6{̗^z^300dtRk'fԨQmƌ5J)//btRd2uvc>Ӈ|Raܺuk1 5CPPP !nz=nz#ڼeη Ϳaaa[lqrrz-W/@_ @6jԨB!DzezQ@;H.[H .+H.4mc/.F?m-uy晨ߡu:'+_ԁø@I]z<:ꔊ|j,)f$\ nFpf$\ {۞n]H@M nFO?@.,]@.݌ @7#Ht3.݌ @7#H@ߓM {,YM nFLM {z t!Ht3.݌ @7#Ht3.݌ ==pg֭=pf$\d=p' @nz#w]s mՌ{kU IDAThi@pJˑflIENDB`lemonldap-ng-password-expired.c38d49d69a1f3ab10150bc0534a28554.png000066400000000000000000004471271325274564300457540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRA pHYs+ IDATx}yTՕUUw;6;(@ ((JQ 1D cȸ%7FMGt\&$d>ĘEE"K@AٛM6nW}9WU@C@wY{j磌ʚ5k0c ?}\WI#h% &]mq'Ŵc ^۶ip\] Cp\Ǖ8‰ڮ1. Åqe+أ>*̸ :BCZ\%[SLpI|H9~ZJsrt>5'y; g&Lcz0tT|yrs\se& ?pv3EP7Ǖtv3G%-P3osX9`SuXL+\0gr0Pnlcmϝ7llW+]*5^L ηoߎӧ㡇ȑ#xW܌N8TUU!&[d >!bҤIXt)=z`GUU>lڴ yyy9r$|G<ǀR?]vESS^}U477<{챨¼yIL 4RE"ƗbsR~\^ͦ⣢1.NCJϕr}sc>RR+Ǖqu:,R,p9]66x>Ǖ*U:޹s'VX}477cƍ믿]vjllSO=yf̘1xca<gƇ~?zBKK |=܃A3Dcc#vލ'|9s&x O>+]v?z3<fK/1|,^{Q]]{駟F.]҂ov oVX2"`۶mx'm6@~~>< /͛7?ꫯbطo~icx衇҂੧y(++Caa!lقgy娭]w݅Ei'!t#STM7AhC|ܦMPa&\4$Iˁart\cbQl~91lvFmqr\u|tg*nVl1`q%cv4q ={w}8PVVn wun݊CTs9hjjM7݄kbΝG̛7FcccWѸ:A3?}<\ަ5渲qe._pZr4vX+Ǖ$tΚ5kڊ;'t֯_~lق3gNjjjp7rJC OS\q袋P__G}q'now?~cUUUX|9/_/gƀ ܹsF1tP|)X:!??? tRaԨQ(((uuu~nݺaȑիWog[nA~~>8`(..F<GIII A>CCCC?l2;6٦R 1Nn6:{IGmF7@F*K66%yHWq/ۈǰ׶WWW >G=/S\1;W1.R~p(**¢EӦMƍ^{ W\q91X^^zB,[ EEE/~h"̞=OQQQEaĉhii>c=vB޽ѭ[737|3.R?2axW#Gx7}/`-//_Gy7|3ك_~p7"///IL<… F1vX!Lx9\&{ضF?qeۙx#u;݋!COFuu5/_aÆa˖-83Э[ddAAoߎZ <P__5k֠W^Xx1كc(((… yfNC~P__Kbȑ(--ŢEPQQJ={pBa8S>6mڄH$c=cѢESt=͛ѫW/XÇG4ڵkQTTݻ'OS ŋŨB>}Rf…xq' Ei`Mt lapsMLqr~^Ǖ,df+ KGבtt| KGwaSwmۖr\9/)"Qj#WWA)Ŗt8_Np܌;W^y%Fr%ntOc)tҭU @.I::6 Sn|8W#+SR[&6!H}=437lٳEC'G8|XJ09$;l&iCdg1 3qKtS%+}W_+tW+ՑǕl9_&0r6-6Ǖ*L죝X(N:wq86/ӦI6m|6"0:f%Goʜ0in&tm{g:.U=`W+*CU&6. jvh.lpqvU?qOj\ys9Ց  Q6VFi1hlW\|.x3v.?+0y$ئk&*3ylvĘ8qe/+{4?|H{&bZMw\9W  T nUU}88KN>$|j^._ '˄SAʛO-qL㙸R۸\.'Dm`p>#+Ӎ*3'y>LccJϖIk۵q帢mϕgЯ;Hkzޣ?Vtcan3́qr\mwꢬp[H$*&oi,ntu6R~[?r%őbK>u\x$ڙ`߹L'WN8qҙ%욜mݹ4٘ڲ!L88dUD=9y^ETV C!J%-IvAŢ# ͑M9x)8\tL~8m4:G'Ŷmmbqma5Ć[g2?L\&ʬr\;ҭaDum s 8WA|UB"E(s00M 2hڸ⇋`a@^Fas+m÷_z tcn:،+g+Rl]DW劻W1qboI2;W&L:ݰ\mKpP 2Ŗmm++{q\UJKRq MOΞ;Q=.NsL)W)_#jgI٘rʼn.M6Kƶ}m -..I:W: 6j\-Llf Ǖ}ʾq[ibpkOKqD28Wav"NwRß]6+bi|U2;xV&9̺~5OFCQ>Ƀ)<t.r1͹0⸲08)M ŐM8plq\9lqe/rEt {9er`7qr\1~Bˆszm_%tt~uab`L6' mR [1 at-wXu0gCp1xiҘp\Rra n:|W)&*WOSqxLIϩ.w[Ɏ[i+ǕM%yG$;Ǖ㪳p;ǕǕǕLI6X[&a8[ ?9W+ƇS Q9=ɆmtTeՏ 3cjrTr5>X1- :DGؔ+Imʑm IⷉvO&y;Rۀ Ȱ19immᅬ_Yl%1M<WeڵkhQrUudq\ً^56r:O=vt_>yu8#r*Tqlfz T7tT,IsgCCl5gLMbȐ!2J$lZkp]w"n]¾יqe/+&\̦-S.6&+^<>{#hklG¦cK|&4Hx=O愫\D:ʼr\%^#sRej: T&KXJ}6QiAKu9P|>KvKu S/D z8 ]^˕Jʓ@::2)9]GtLüAlM9鷵\qu#W>kqt?H ]+0K.{ڏ/,aPqEmp}+=`JzR\_NIύIP7dm,|Ksh+@C@K{~OEip Ooq%Gm 6WWkzhJ 3ZWR9'Էmlݲi#Ls)kcRrl ItG m\Īy/Ï?7}O{ _җ\`Ù6s`sEǎƢJ:3W-g))Ο*~=,>|?]Qc ٙCݱmžmkjl_w|Kx?aA&|ػy]vG ,@֦F};Eh;VK_ ^J49ŠC.27XR hJHW-9_R#rl$FazBrx^ <V4g ~Z#o#*\RL>(Ŷlr̕BHIKE|=Cm|=ZfNPrEmmW+[dO.|&t)-x~RK/>p(ȫ<[W,ƻ=W.ŢO1ҫ|'5tW5crvJKaTO7aMXؾr)6m>NxqB2#M=W`Va|ȚW m0 PjT: }+ިlXtԆ~KS;nIbTk ~tq{Zީ_+AIʑr9}s. #iQͽi6׎ #69Q܎^v>7;l IDATe~-Ȅ+ /5Ɵqe;e?'WX%=eTwҋ1E LSHǠ[ d%=bϡaߞoQ4~c ܉H^>>ۊ $0h~䗔k*njOn^y)$j^ur":`\|P2)Qآ-u4=O`)-L6\!GQ xqп?"U6:?+gW+?ysM­ӓ [iMŲ(ڿ`~PDצ":>cػu#>CS}-V}cЉ|E8nYq|<h ~Z)6/^}[ =}q9/4L~;r\%$6r lhqETx \ ]tXɷIO@gcG}.^= ?ڊ]nsux\媝 cX߶:*W&f+Ǖm0&W6l&NØAش?qc0K5LS K4"U˰}R@Մ)(Z6}>>[?Y'/%Q[[T_ /EՄoɿg+O&r+[6rr;\f:<VoR\N}?冶555aΝhmm*wF~~>6mڔFѣG7Qh|ʯdcAs6InI66"`1Kv\睍_NjkkwޔcO+,,DݭDkogqeqe`peJmm*f U,A(V^QjR=Qnv|7 %e[VÚԈXB4ӹ/SFRF WX oT1ç\¢v|)TeU6*!Z0mPv SۤLIT[3G(Cbc3P7;wSO=v]r%(VZ^QQӧwl[|:t+]4N&b7bK (LlNq9W&1-Y/RJ[<NJ+߈F)}#GĴiӬi\s}ڞL;W*\\ٜtuoJM~P%,Tg~Bb{عv%Pڳ/cś@Of[PڳOJ܅1_*AC=\nly帊q`.Z1Q~rpI~M9l:N{UO>;=(((%\b2qiN(preoS X6 W@[XL靦06A F _ƍb҂]vH_jE $&6+[=ǕqeC~MH`q$n/}6Jis4nR~,xh:,ĶK܄0[7ox(*;`;"L3C0*!ɂP n"FT+4.Z9Jp6&1٨IyH$"ګ 5SNa 0K6E+S?t1(/]{VD"}p-6ޔw\䊫@ϴ8W1mP+~踠k)W~#pp߶M})mZ T ];ޯH"CF[D Ҍ_GK~v7(]Vb'o&xsE[W_G&S$6WD2t!]SWGۨ^d YqJ9lU)61ŖlhRlVzxInK.m3m84R<W9˃bViFtkڦbl$ީ.ɕIq]︲?Ҹb(..Oaʃ@&aτ6H,#2 w{ʙaė϶9 }= s@1F싊QM=qW}7Fqh:PpׯCKcz=C{*i\dJUL0%Cu%K*`Huã,g F91ŦMx9u}Mme!b8[B-?1vݶ 0[@b]0kNUp*>ul?G K \`#̫U[Bh+ L>ՏQƶx_bL~%ݽ__ml:^IOz86IOC06qCǹq"ub˙n}L8WTW+U+[6}Tz)9#MmOnҠ_=+'hW|-OUx/ F)SblM~L*jV}מӧB ] -URK7ZX-Q Xp!}444X8R<3S4'/]qfsbk񣶅yT1ԏ)*3tKè]|90gqKP~6m·U^;xq\T:WL6&\:^F2MSF1𚨷=~_iS=&=$?NUڕ&7N̸ntyչEW }Su%b8t-_(R*f/i6550rHp xGܜGE71lFGW R=+\ad3?OgwPRPq\eǕ  zq%-۶mÓO>뮻sŋYTnmrjC?LJJCu\!I\+96풟"m̵_^J;=|l~P}J|ئ1~(Ԅ5'*qTs SF\&T\DH 5[U\*Ֆxry}s;#ҥ f̘ٳgwHޤƱ'd 3yu#&6f#7Ħs% w3q%ICC~_SOŹ p '`xo>+Ңo;ab-\و^Wr4seKZ[ޥ^ێsHEI_%mVȩřamu^/2$>?X&d{PRz^DԮ>)Ns7U:[RZLqתTh*^j;r$XԖ~}ڵkGaaa? ֭K t8.$Tέ0aoC+=?W3-mxL9|EZ.Y'.k+/^䏇zɺkaζvL"mApDx{+VR (tQ sSckZl i1JToA3ӴAv'g> ۗ%g&rq_4~6iQtW|=AU)o)_7=`ֵ ⣏>5\h4/ɢ1c^am%͕nn9qe/G2WyǴߪse*r3z<k+V+,W#Yc[m{l)^*hHޢ^)pJ&\c6g0Wmx@I:bBq:ƯO_QZMM[[[xs&O2{l,XwqJKKcqrkÖ{8sƔ_6οn>D+{Q˖-~3?FEE}477ȑ#qeY?l|dUWz+{#+.6/n2mS;I8~ezRr$-M |&tPeVΑ{OXޛ犓L08\ŨA0hF\@+vՎq9u6:;.WOPiڤ?3;>;تɓ';wDiiI9P9,:GM|q6H~Uy9^Uar%-+.ONG]Ƕn݊o1>:;,n)w)>ǟdo˛q>89 t(֍)i/(?V\Ν;KKsrH@k؆IGW\[g*BuQRӅ09armc[MyJm6qtLygt81͑)لYW~lg&y|k&_Ug&r\KaxԝwF]:Ρ=clQlW6 qBm ꛊ[PWկ _]\N\l)WFkk+ओNBaaaJ,s{҂38]t;Ym~w^q;]tQJ,jc7gCnjr\sCv(H:?\UxTOpaذaɾlݺ@uKJJH1\T%aqnZ6+{[Ǖƕ 09זKL؛84^WrsEzRZ@%"7(T($j*UO=|Aw}={66.{Ü9sC9J{/IKω'ͤ)Mm7W+)q7bԩ[R~&+V66 z6 m\يJ_r\I52 _owt\^R[Tq\yus +0b!]u,q>$ԯF;6fڵkp'^t0tcW_}58ƍ'Nx5MmŖޡ$w:HclWu\lu>3Tv؁#?GMM L}r9dU6[wuLs0W8Ǖ-spͺֆL\:.**ѣYʅ`quA~HL.R1skX=9n۶ g 8k׮_,[ rJ_FMM )SŭK/>|8mۆ磤\r  `ܹ(..ƤI0f̘$3<2{Xr%pꩧʕ+ѣGL:d )W҆ₛKF,˶.Oq!ل)SƼyP]]xC|hjjB^p`ذa[oaǎի9s栶ӧOG~L1mˋ?+^jw\9tґv-3}Cú~ W]vŴvUfWWxW_9lF>Η.QcMCT= .կ~{?S~~~FLѫ9?h/++կhk@Oی;ٳF}SO=WG~,Ux<&oF}߿[}~ii?qDxǿ袋-:.%L6&Ll0ݏIJKK?|p)$xա6U/G}S;zmOOMcz۷?<ħySLqڊgy&?w}x?_O~ 'Ww܁믿X -/K\}blذ5__$>XxqZҘH\QθwM1PcssƑ.7dS.L8rǕUytR^ヒw}7zV?s=X,~oOk;5kR|^xw}7zTTTX1Ppe#Zo5quqEKk7wNţ{"ã ='ك3gZ\Ḳq(I_ \ā(R|iu~kWd,Y|ݻwW{XlNx>N?tu]b1bΝ+ݝM7݄KCMM =\s=hllTWWcwEEz!L0ƍ477cժU2e [.>Sj#s64.67?6.]M\e W&gGͫ#WHSM +E[ (?+šmM]]?%mofPyWq 7GѣqƤ[pDH'4iQuM*i\&h:$dM*dLB{*7xc#FGQD"qMҍn]Sc W:aO>ŨBss36l؀\mJ76a\Ӌ IDATpdcZKW•-v[0m1f['bXʟ r\v\H\eZqtڹ-x ;Te⨩ *8 ?O㣏>ƍ1sLL2^{-Νj\uU(//qǎIgk37o:1uNezH#!]s8Wv\qM뮻?~z\uU())A=plذZZZpYgsAee#W5$5a0sS&ܵGƕm\z_^^SݺuCee%F/?яpYg%uKKKѻwotѣƍ;ӦMC,Cnկ~GQQJJJЯ_?|_w];X 6l>l=Zm W0нIqR1 \er-I axԏG:%&Ns%*Z瓊KDw2UThKRa\N\/ @jUtoWIMLcS='rWs!//O5Sqxt9peMﹱ7nW5E.GǕWbJJJ:G\⸲Usڊ}u<]HtEGҁKRnV=8rŠtV8\\H~8Yf%^)S`MRaKm{]~ϒnNq󐳡moH)/abrt\LÓO>^PPk6P #1quDL@1WGĉ%@>S@+FΖ40҂T'\!EO" YXglʿ[J:"S$B"?nNyƵ9U.?sҞG^x7=al%Wa99M\nVi4)-Lr\9~d?u@ @ӿ!4ХhNjS1>upHXhRl^#G'۶X|\ĩ[sU u?=]|)Ɨq+/i 3IqEOU::l.0Ltr\9u)/W}ÉIcrr\yurQ:P^kX5a"K#Ʊ9y~^Ed~2J9Mx_>Ҙw渦89=[_o)ɿ*KGf0;ݵ仢W\qEJJՑU/z Q}59_AJZ 6+ݔM,>u)Wi!lq 3֦k]`Sī:tPQ}E$R\ʣ::M׎+ynS;S]Pj{vi Ә^ Bps񠋥qsJ5)aa0ba*((@eeeJJM}9dWG*pus:tP4i{^w İOgˇ8Wse#zC׵ @'ˁb3aVr͕q)q帢z+[d+غ~O\Wn^M\h@xrENm&-*.FbIE8>BDŽI/N<߇yԩ6RMN#7"~Yƞ)ƛMpu q״g"&~4^abfo;a%FLqtsd W{/qUʟ΁TedUnᓊ!Уj"ATp9pXh\7CT⊶2d&>7>:ߺW~NϦ_[Pߺg'L$œ:g{8?Tg"㸒T8N\q%mhGHWXxt,Rȕ-Guژ dgStqeg<2Foz\t#8\>W⸲U&kg6zonTAvU4Eyyyh?&G#WI6+{\pb[L -<Jh[V}~TԆ9 1h'a nr$*s8;?i3\x^fFwfU?a Lm+L_xVګb[xd=\eqeqeH*̚2ٌU&{[wϤqգG\}8+7L$"aƉdg**i `TTHEJȦ RuhRq)IB_3TK[ՉFzlIO7L!qgkMvaR?a$|F+:va:>W>W>&BSLa:l&]ӗp&lM7arәp\ɾWG6W1@%\@@K:=WH) wHIq_SO佅$6S6~txym'#=Z7w)6i.:R%;ݔs.Jç\xܧ#)g5`ؼy3\wuFA7pPQQV޽7po >Q7oތ71cF(;])p+[=cmp1t;L|v bwvx؎UL5nF ) (]3n)I CU* /6'o\;Ô$(}O4.';:[ۇyCnA-`m?,0i;6vG9pq Uuuuq饗bx'>郯}kI?xWGʼf* 7^` ***p饗U\p` ? PZ9]*aQbIM5ëۤ8ߔSv^6q6TN_́r%=06\ƗѨӷo_ 2 سg~5 gu`رػw/q={6z +E&Bܼʽ¶aï8؈vǕ8#p#`fTldLHzqEw Hʍ  m$PGi\Ծ06RT0pE$CgKmcpsB0An7 L *Sߎ+{ߺ=&jD",}=19\ԗ(>mGrVWpa>pd҂H$}q%)((qիW']6#>;жl$lu\Wfߎ+{䟝ҫHZ$pET-L;]|KڹX$Gts:FBxf$bqa)Ḓ޵uoyG[wlr6\W6\tϲ4toBs~wjT&6h4&)6[;2fr0L޷}Jh8tFi pqEJTjX|9 0w\Æ Cqq1ҰSLΝ;1gDQTVV{ű 1dt0rH?飰_|1 p?)|Awqϟߡ:M:94J9K83:t}IW.x<W U >7ҫz&;jaѵq\M8=S >qٷƐ'^׏0uTs)lvb?[\5s\+y3lق+{g_x?=ilϖUGז L ᪵uuuҥKIUx,:I1̶EW⸲#naRAj{^DjhXV IDAT|P UUWﹹI  `02@AXqlV'"((4_y@_ՊXg>PQNXQP"09Gr.BzZz:Ix |tWPt(_PAnx7W

8%{8'&.mpJC'plW][nF_^ԦmL8{W-$iӆ-9Ù+.(mB$xtl۸>}mc`^ٷ5UB4?iR7*N O3moh2a -Zq7S6ZgG*&Yg~8_i/+m}JX8okJ\ Gi+ &cKlINoBWTTŋ[c0ҶU0B2 Kg۳ǵdVq|P1AARߺkZЅ[6&-Tpbè[,:Lb-aIl\t>m6x@ƸQoK;X 6W\\َi\و)q4a0A~[;6{wllق1c+eP^^ƴn\~\s*+(=ϳplf_7^J%xzr?I7Տp{e>bL!i*.tG7lMymGludӚ}܆l#=ԯp>k4qi(U9\iH +{illEɆ;)..?38_[\p<B\AGSP٥r`E&WѢ[lq&-ƺ?@m''w91x_C|yrBM7wT\6K#'Wí=Pܾ!A]'NinLI|m\ ToJtM{71.OvII z)?ō7ވ 6DdP ii\*=@6guWP\A84(lI>8r8]M+&}t@JH}׺"뷉Iw_"5:/9^^Gk|7+i^0v@把 l߾]\H ҸRPPVXX| .D޽ᤴX@U~<,dgg;'tm)ٖq9.Oա*5Ph;u捣+%tɩ.0o.1R\\r"%ֺO? umG]LtJqS|.,u63:F+3Nn{{:ĘpLAAy` $@ Ȏ;;b۶m(,.ǘ_D~?O=G9y-=͵I9DU*%e40.WO]pΎ'h\bա+#Z"Hx| n#6I4l !>:;6&+{5Ik΁Fnp뭷&=@ $UVa޼yTO>޽;Ə۞x{b{%"flb9Wz9\E~BK)K1Ut*}xNxlqJ7"&] /h:ܒ-+ILō驍n)߹,&m}p%sQIWu:c~]QGlE<~H6>G}gIlxlT;:{W>r8pR.xcxm}S?ù瞋cbȐ!LuOg_K*WWaձn3X\4QZqppPRm3Mŧ)%:p e3r6ONGgǴhLs L'?ɉ7:;8xs._=TLR6󢱹R4|iڹ6I-Q .DΝ[~q`^Ks*I͙KǩmTJI䏳+v$TCMlj;`pX(?gi+[vhm3>ɞͼWK•ncΏ1=3յ 8ߦqҘÉ+=WJXIOpsY§Ws% n;5)am6IީMt+SR{}{}{sJG%W/0$sf/^:@cƍF& BőGi&iLQQQtg^UTTiii-z^55T`\B@U]K%~o)vu}:LNؘ_/peܹ^mt%}ȯ_jOc|!R6zow^#ܹ˖-ôiӢڥo߾}ϰcz+Wcǎ5jkjj> %%%0`8 deeaŊ?x))) 6^|Ek󪩯q<@\RaUO٥E-}Y&:ä& #OƧFcp$\C&..tsm~oMuqGtZg!7VΧqbJP=tM WUk:9l~}ÕI}0˕PoK/E}{gxՆ b˖-?>rrrDwq-Zb8E\={`صkFmW^ߏ?;v@~~>jjjt^mذ;v9tjk0L7Ju1USX < \X]vMGRyM 7Jm}/aKbJEҵIt6ul2m A[]]ϞY|skOtaT\qh?xGU6/1įm'}t=ɏ줦[n$8j*xWp7Gy?b…ѻwH{ZZN9޽{1m4j k׮"++ ۷o7ߌoݻwի1qDL8?O 8GqGظq#Mb-n^qMa |^sJő`%+ձׯۀuT@::N$|#t04vWx)na+ @w/<:8㍁iS\w3ֳW;pI9HqbӭoɞNʌ%K=•d7&>n9ygkӖ+N+NRSSѶm[ܳg~ 0@UAA^u{ñj*r)xDZ~HJJ–-[pYg͛cѢEQ'0e\y啸}DUS_au. S]7T@\Fq~9"hJ0LrE,}/Q{.NKQqxL{qaTc0udAρpeƳǷxlk;d6X\%@#*ÅD`?im\ ~@csΆ#|gϞٳ'Bڵk:cjUUkcȑ]ϧ'+Vĉqg?Yx+I q->ۍJMX=dtzJ\.l\;.\'> pJ\ip%]=[b;WDQClxWnM+N$+{i\ĆrD[>8?p_5+M6ԩ6o,b"B! 555ѻwouQqUYY%+?:.pMqV0t/]㷍ŭAab [9X+Zș|ĠƆii\cL8v_K~r7a+DOlWW&[2W~cIh!ٷ9lp4<9q0⤤wFNNNTq())AQQc}8s0g 2$7Xz5 `'? \W_P(=[g'tf͚m۶SN1C )S۷oUW]UO[[?S=|sxM$ʞC"0?OL4\K4u<|\k(1&Ƿj[r͛{nގ+'v̙3q=`˖-?/n Xt)&N;vu];`ժU>}z+.1uTl߾=p ~;d̙3UUU(++OsW6mj F}:H- UIjPB՗ ^* Mit:V}/I%\L:;:N$CI;v^pB%Cc`g+[&<Wsc՘x?xĦLw&{&t֍4W&?\pJsʯ$'S?g_l~ruq񹮋2,\wVZH W~m۶+xrssѯ_O>'|2O>}ЧO-K/Ÿqnk\E^ЫW(q>}:jjj ".:ukB9S"777W\ WٿI0М199YDϏUKZOn޵bCàIXdW¢LKJptqX$a#Ivr%cCG"9<=5Um~t+>N':ξnpM#a8T\p\I4g\ i ߒ=۹PxoofUFEE]oHSJAu^0HRmJ8s@tEjC*BLvmXiM7+ fRu$[&lb0&@5-ZI.:t|Cp+ )!mh٧h]s6:oΡJJ%lW~xi\)9?ب$ʕ [B/Fqq1kϷ) aر *Q<ӼJ*򙮒# r\U?qG˽01~*am=~rbq:?{Kir{FǛMb٥\\%ƕkÁo lҞ^[l +++?6~C+>(>c+صkW'oN0o$%%tj>ؚ-D x.קrGǩoj_8iRDA@Q:.]8N]1h!1긲qE&nI棍pzLW ÕNWttm@}- 4h>ldffZazG#֩S'1999B͂+N{1f̘-qM7ahӦM¶.p 袋e[T[H xt'jUV߿?̙/GqѶ<͊+=W8[lUHiTԩivтMzbXڔ.4"HS2+I]?@0Uqa})'&{ӓdw<\ًC+`r(j(W/ ̞=3gDUUkɒ%xQYYty睘:u*on§~~?6Ԅ`̫իWc֭ 8cu?*WƏ?զ? IDAT5wo6Gv"vB!TuaÆE~Jxxr̫Dmr TRѢ+$&oI^sE4FgWɎdLu8|Jŝ4dWfNܩ~m6M%^;~J'J/:]p\Vh|xRÆ _cO=~b ?r 1aC,'L1Ϝ ݤ ]J}Iɷ ^7ߤpe`]`o̭Uff&222"łNҐp8bsqp {Ν;c׮]#<:t3<]+q= ??C޽{شik׮x7pu֘9s&}]`HJJ믿4in#//| Yfsŭފ]v᪫BvСC`_UVᤓNku3`ٚ5k,t?0pcٲe={6z-{Ŷm0{lܹcƌA߾}ѣGbΜ9x'ѳgObĉx7K.AqGbѣn6lٲ}c=s=SNEJJ O*q9$l6齍??FҜm?\x{B8Z2Wj9\iB ѱޫb Q{FjbgO TZ|hӦ ⭷”)S;RX⬳Ç^K/AEUP\\^{ )))XjjjjP^^T޽ѫW/,^xbdggcݺuK0j(梤w?>C,[ w5k`=m݆>SNE(»ヒ<\tE3f V^ɓ'cذax{!++ ;v1c \~娪ɓ#`̙[LuQXX~v\Ń>;w⭷ނuذa8{n,_:uիWGbW~h&ѭ?Ohr,T'1M%i\EK0)qV π.rz{^/K]sqrqٶ6)~I&IPz&Q.q%ᡱs CJ%a&K”t('6 ݴ|HbG1HpOk1_Uzmz5h:uu}p%/3WpGee%nȇ׿O>ۣ W\q&LXb{9l޼~@aa!0N9L:gy&N9 8m۶EϞ=Ǐs 2ڵ+:v숕+W"99<|rTVVbB~~>F:l|EFF0`tu#?*//'|ݻc> fϞ=ؼy3u놛ogy&`ܹ8#"SN2d\Err28 ̙3'򉦪tՒ%KЪU_\n6n܈c=;w1ݼ2tK~6i&͏O]џCU0U_G6z@I'ƚVT)>EwLM1JuΏ:FJw?M6qIys# ՏTXm+L%LW6@ݞ!8pȕᡳ-a2&ucseoPȕ][[3g7}Yt%jGRRnv1"e؎`ӦM6m'+r jkk|>SL4 FC=~ҥKxbw}0fxXl***pYgQQQ{2! En555w9r$%%E>5 8jkkQZZ;]vv{\;jTUU!###wWq디TTT*999~SN?0c p Xnp$椤3=ɶ$Yɶ)Fxbh9\pw6a"7FQxiC6 ]4vW[W :>ƨJ{cM|:Nx)f_⅛#X.F? >ʎ+#-$>| ]*nH87pʄ%rF>$%%UUUӟUVa޼yڵkNaa!nQWIII1ETaa!pUWUVؾ};֯_kӦ .ȯZa֭޽;~᪫ߏE /駟 & ##]w2331{lTUUap'tz)ٳڵCAA6mjȐ!xQXX: 7oƖ-[ҪU+ 6 )))?~|E(Bqq1ZyE^^|Mdgg ~z555ǠAЪU+֟*r ֭[q :mۆllڴf^];:ٿ{qӯi|Hg GCwpepe?PqV;MHrgS M u8?WIqp68FNOl_!qOW)fC]I͍ёP縭o}b+{,)ੰc -q/?}9>s(,,Ķm۰d̜9SHcN8Xf zq={6OڏݻwG+*Xs$5_7 ێ+/BԞp̫U_Hq^=ܹ\@r6t6{ÜKB9+~)U.DַՍ۳D>d@EqHqtOuqH_dz.&uJ򡛃RL]WsewOڊmH{/]_ ol8:q~o1ydoZBqq1֭[{Wƌdci# G8qqq~=OGQQ֮]#GkGP(N:ā8HNNȑ#g|w/s9dO^zǶm۰fiv:w?ݺuCrr2NpDoY1F_ 6> Uq$$]S'j+pbzl-ƌp%7K%\xqۍg m`vL"6 H~W8&W&ٺu+1#:_G|x56&[ ?3`l`m*o>,X[_~bؐvs08 L<=2t=*Wֺ͉MʤZզjCq{Z 1ꫭo:mɗGr:.n l}gڕ0bt'jaSXp@:NKT}.ƀ+^?8 6x%s޴i~i|xqj51DU;aR׿>Pc~率l_~0InI1ѵĤ#{~"_%$p̫UN,E!%}&0ޫq~Mqs vDXAq4;\tŷ ğ>.MQ 6b&6R8቗/_ Xh|M8Y$>_Pp%IbIbq:w^{ pIr}4>әb{NH%gD0DKu~+3ƦUH7ɽ7FmWmHEקn+(Fm85TIjm/5V:qD3qKx6:V:tϓ{ppѱCxH~W(>uVSSwyӦMCAA}Q=P`9{)pܞ͵x+q"GoΞv0rm>ϼh6Ut\9pOm\j\F]Q4-|8=KU_mԏ7ikjKŗNtP]lJmc4ّڸ krcf\ϕڦ_}p?}^|Erss#ߧ+ΟN"雰\\H&, ͕mXM=731KM{|14k\IWanåMOn+8PH6FٓusPPwzC(ֽJ1Ik Wtn>!(- 7)Ҹ%\$o̕.*6Ii3Q GnW_ѣGǼy."p X`/_קO\yXzu^vp5נuָ뮻H_8رc1h XbEA| F:ӧc޽x駣>.2 0ڵkN9/Gu_~9jkk1{쨯Ø0avذaC#p?Ŀ#//w՗/={#<͛7Gg>iӦoQVVףm۶xDZu֨: gu|M|Q}o~۷cܹQgddફBVVG .'|2^uGa<Q}8q"rrr0cƌX0~_aذaxW駟F0&LW_}_|Qs1w\E7C >UVE 4cƌ5ksEuQQQz*+&+1W_}5fn6 _|1>cQ}٘8q"0n;?o6Ny睇>-Bmmm#M7݄B<(..ҝ2e tYf oԨQ~ovT_.];wO?CM4<1x=\5 nc9&L@AAΝ՗. :uœ9s}v… tҨ>o1o?ǩI=IOOGzz:k;99m۶1zI9ׯT.*z?^b6mڈ}:^̉8F_g^|zE'pXkWK%-- iii"^=iP*nή %%89J׏]ULsPǑĽڹЇAHv]׍+Lx/f׌^K\Q5#'G=)ח7tޚQmVUx[O+D3 #s9~[SHc)pV sD)A/+ 16xW^.pԥ;NK _Wu GǕ=[\&m&W:<~uuT tIIIѣL[⥗^¢EpgsKSOH뺡Ԇtmj7 peoPsezep tl_ݵNflD'?_68l5X}<_ڑ9oBSu],$ !''ѭ[E%Ui4␆pո6< | LGm;^&;6kjjo߾+ 2l\pF{> ۢMj)e\QjJ4u *W<<39+p IDAT[ʤOcNzM~Dtɷ#opWR?76|/rX[kU:Q9\\ُ V{?m:߀|u:&ߺD> 8ʌ7Wx2W!p$5S@ZqŊj L'IvEW BL)s;!$ʝ+ʹ#qٕm1p6Pmd+i\Ŋ(VoI)sשׁXU;:$|\WFQŤWxuI Twu tt9 :|7>(ހ+\Q} ݃$?=80fsvÃ>, p\IϦbQǏi͙%UVУG+s_}_S*:sDIDVE*.b+H8{}O%}6WJnVZ,n?qE2Ŧ [)o'tHsSAcɎ<ƨmp׏{%_r%6怫X9om1p}E.|˄3mۆsZ8ܹ|j\EP` T${UB PF#OՓUO{c)Q:4Y`.٤-"~g4n$ $@9=}x-ih&?Nuu5vյs0l44Wx\k;~"%X0-b{:/&T_WJHqrEBw|BTLqS6O&BCRu9#NfCJb*^~i!;.$•to)H\/mOPަ-^x$`^5BK8UQQmtE!WJɭcQ-t:.NuFpPɗo _>9&->DObCm%\x dĭwy_b_Cce;wubpJGM !'ˤk(WX۾É`^WQ2 h9n}&WЩO Ťm*68q]?ύreSKQ>']=hptJgy.IXA& y.n5uVcseG&{W2^˓+˓ƕ>d_ :6{ppL:W:)ζ6YnSS}>m?+y%M^DbԵ۷~Ý+?v46WQ_;&q)8 ODuc )HM#G*&cA~OսX__79=w>NOv{l4g%6mڸxL88Uqq .W! ۬$i# W&W٬.a-HQW޳m&NtXlv.Pt SW%W%͝+?ijilm悤/\qzW͓ W& R!|>"J K u~+ } 裇ū\:P;TèáĦQwM)IvxHx#Wsn]]imIsʯm+\\I}&޹/.X[:?Wfx1\cjN\8'q(yIO 9SK7;.Sm6$^\sŸB%DŭbHxU6mDwJvݜttx8$*Vmo] &'\)qrȸ)Wpe/͍+mZ?EDFLI4Kl9^\$͉!qEIM#%bUmS_i;#:oMWGV٧.aIM}FbGGo@vQ\㊎8g4P{?ިW1&Ε~lUUuc;W/];l ].Ɏ>.?\$Urr2:(GW + Bj-i/W"+ i:MCog:V:tlHŭ&tgu\~!-!ŧ+TlnPg?vRB_LuUt;mq%}&yt#_7q fݺpqmH)W_WWW2~i/ԯ[liK;rm#ĤI+-'g3iqTP8da*ZtdyfNjO"ӑ4n8@'ʰ>8(WN\\Qg.ᡜH};Nts$W Õ Pw8W'š^S@ۧ$WWR|\J~:WlmmkS踪͛c|\$͕jDTr"S76o,MRpz4P d$abI\Q&Ur "mۤ))IBV1r:4&'~Sn(\sEL\r]LՄKPmgIѹHU%peUt~ɞ-$ Ćݻw_U;;WҸ+CS*D ɜp qэO;-ٞo?ɷă_t+_Q #')5jr7N<|HvTo.0-VՎN$\Wl6VʏćU@#?^KǛ2FcxWWO?w$<è6g׎ 敭4BmNR©Kt5 LnBfrX%\ԧ߄b7^m\:d5qeҕS>m%{LuM:&ΏͦbJtcu:+WI绚y:4i]wmV.&oq}Q>W\KLOhr)Ǒ o:;U'+{ UCeh@ )):g LG -P~AZ}FUTqzЭ xu-^+NW-0P]im9:/WHvS|}yOe?_TWgW7FJ\%2&~LE̵q6[[WxTBt3c`@Aq#Ub4m+ :>.v]Nj ɉJC{E*Əp͵ '$$-V1`OiUcsw\Ŷ\k\l9t9fC;~:I%^;W!:ޫT6:VIń>JҡDl] -G@MgJ0o߾_}V\M6: UjjjwaŊXr%~G֊JM tf UWWcӦMضmya۶mFL ,@AAoMM ^xٳ'>vMoR7zx& ŕ́p%cd,~KDqIGCBUj3a*;;SLmHr8rkIU*+t-KPbI]HKB P|u 8~RrG7١oƍ_kcƌs=Zcq EEE0vXa7nϟ/H6<\|9~vh[mm-ϟE~׍7ⷿ-D:g{ظq7VSSG}4ө<۷x Ւ%K#l7bɷg[,YKbطo_^ii)3(?[l%Kb|b׮]ؿkjjsNTVVFVXk׊***;`ƍ1QZZíKQQ***"mGqqq_rr2y >JJJ"HԴJuɩȤJUD>2c|JkSi< J糩se.ɝdm1qy۴SL&Qqm߾=Ý+_p/kLª#UGqi4so]Wr9l:LclbƱc*;lߘv튮]F ##۶mCgy&BP5k`ɒ%x衇Я_H~u]ٳfB~~>A޽q7s裏0g <| vڅ. &L?_}Ǝ]⦛n—_~{ eeeȑ#,TTT7HW#-- /F֭gOW_(n*++`L0}.\u]'km3Έ'x9ڶm/1cKصkJ׿뺸+quaРA׿_|'|ݻw#."K.> z聥K⡇}ɓcOcǎ1}t_FUU1n8\zزe |Ilݺ_|1zn;~zN@^^`,\]t+pફbX5Z(=Fs>z 8+ҥ}q@5 ^]+OqW\\tL8%~Hr ԯW&^᪺w aUvs{Uԩ<Նls>η&+aڔ%:+qpᒏ1&~b?555۷/\ſ/lܸvZ/222п)))p]O<.]3gUV;qw޽{o`{~zz8ѫW/\xᅨĝwމTiGu򐓓;vছnB=0~x|駘6mf͚c=HMMQGO>Gy$&NLX\ŗ_~bzh۶-ꫯF6mG3999Xf w}7N;4;(,,DNNՖ-[Ì3pWcǎhݺ5f̘va֭~aС8󑟟;ZBJJ nl޼97p̙;;w… qm@6mQ8x\Y۴-r}9(Ľ7FKc0iLl\\\xD|{ac_:߉౵x5gyegh\Ez\B@nꦪn* JjWm.U0PL qh& n"I}*+Wߎ[n999o< RRRbpر""/M:Æ qӧct&MB~0zhalݺFZZnݺ!O>HMME~~>Ky䑑>>3f ~_gϞ4hrssmۢCӧ:(B(Nkkkꫯbӧ:,`ҥ_|_~9N:$ӟ˰~\uU߿?.2TWW0@=0i$cƌAiii_*(**B.]ӟT N=T\zE1tPcҥXv-ڷo+VuFff&z]?cРA8㑗^x!rw)S8裣pJr{܎V maՍʻ 'WWW<~Nt~7i^(D!O+{?-ȧr"+HTl"5}:\lR?4/87@Ń)J7y~Ʋe0eL>/"IIIHNN7F 'et\AA>mۢu֨dffFalӦ jkkYܵ3gn|ؽ{7"zKB((Ҥ XY]i RvAPT], *MײKQκU*+DPP$ HG8swfMܙ>guϞ=yyyx饗0|P `Ճ+W{4)))x]\9$%%!** m49n*T yN,<ӧ̙F*Usss裏b̙ذa="[e pСejժAe IDATU9UB Su5`.ADmb_Ɗ:T1+^偕.<\[uvJOsc+.&\ذPkJ^njZ8c zfQI7li*v_,#Z4_.nj}8c6l<޽{cby6mp[u ij֬}m۶m'ODnn.C{ 0{ltmG Crr2233Y۶j*>}1115ҥKqqԨQYYYAcǎZ-4T۷믿?ĬYp׆#mx_Zjo*U`U/),, Ia+T1r{Z6tAŗq>pH8s|obD7um aen^ib.EzU*)))u{Xy몴l_X| ȍl uL@NV+':r*_&QqpTOSPQ={oF.]Ю];|嗈BfPJơCpw}nݺzyyyhݺuZz5f͚Cb̙HKKCll,~i'5j$$$ 33˗/GQ^=$$$`͚5HKK͛K.[o#GDѴiS8q~ 6D n:\իWG aܹ>|8&O?z(wy ?뮻*U«05-X͚5CQfMaհa9*vX|wh޼9֮]e˖KLLΝ;|rTXC~O`ԩ3f[n b$(U&dz(xCC܊j?d%҈EuOae3VnZ7ɼEN)[,tDtՈr 9oYcQ,rXHQs&.RS x(ۢmQFCd`Ke^ktpqn6m ~ɴi8q":wLdffk;D*U3SR%y睰, FBNNx|>G nؘٶO]vիMubڴi0af̘HMMt ?8a̘1hԨI&aʔ)޽;|`Szo\`Ŋѯ_?lݺíފ?ϨPw֭[\rիu6665 נA$$$,#!!PR%`۰aCò&O>9swߍT$&&`5e$''#>>>3%%=z)$%%~ۂo޽;VZiӦI&|*U 6`nСC|!͋Dw$̡cǎѣQ┺)[ǎéSPfMn6:˲PJ?7n)33n'̒'^3!^TZXV>xXp1b% uMY*>xL꫘>kFbbb8(jժJ*anXQ2IIIHKK#A+VZj)2իWGj=)ω9^ꚳk 3tZorn=G|5͞sCVaeN#VnM Q'OU^Ď>o %++|r\;\)s))^Y?eb1*Q ,O5?*1i(;͑? 0cǎůH1P|nVb 5A\ e}uAe*T$XɤKZVVо),M VJM&")=S۷oǽދ^zaٲeb4Y8V%#ǠP:?uM Ι`aU:Xq|kmK05*|-ӹʤ TR(-S=:s﹐1ae\)V_HPIK\ä+e^ѮolW-qxs2\̔}aԩ?ÇSTTdy"`|g>GHY}/rMn;t|F죇GÊ#x1`ֆIAHy*`#sᏬ'&&u a孫Ht^ XeæN͡<,)}E[:d}-S89Q7/p|=;/"##yyy$'OFJ=#sŶQ}7m- |&HrI>|bs1xXyXyX)P--DGSJ 4厕VNp!9qQM5/'L~6.*s+whȏuze_U!_]qԩSxױa>}:DfҤIh޼9#Q|5<޲;3/PjU >o6O-[Oe@> _c2(#:d= l[~aV*HA;qU{M]ŭ-QWi+4y<<PRXյJTn6G~:TM+۶Cw[W:V> t9ǹ"3=WUr2= vd>mYV*n:fSRR0zh|xg?.cRaE┌'ՔʇSHs͸#7M]PSM8aU2u),̖t^ EZlʹЭ.He=e=e/*w+#Թӣjҩsɍ:Q†5ܦtrTkJVXTuvJW7_XĔ?VjRMHI%+n͒`eYIyS\Rɛ9+ x"MLrJ<]Da|.'PrC*YS0P٠t+Q1r>:/1+nQsmxT);\K#'ٮN_.w≲[r913>E9pz.q.{XyXQ1^XuͲI=VZ$X9r+7VeOLT"&!qUI_fGL#IŬ*(>jyLȍ]dX6 g?DG]s<.*ǪtuX3!:uCV$>WhqEGuW;KcM%V,G=<VjHrJV/WUzw^n+:{V+ 7)"#$Hɍ{rLK+Ԫ2Uhj), ERSu1U{ZsR\c:8{2q*VnyXc>?u-ˆ?:/ V +*Wpq&.N+{XyXyXXqg2eCUntd);\BDpvDT:UXvtNOK뉋OE.V~7X>Pks\,ჽ'iLa1OpW=TҭY?QLVt<V<]XyŪs-9v>3%'2 |U 'u P.uŭ|PȲ>0qPv8\̜/mYWqiCWUěgG+^skF 6#T&c*=nlÊNJ[ǔ7)C;E"QUrc/.ӢZe[WP֭hRte,Kɘ^IqaaE_NX0@,忢vUxE *U~qrnږ)LCt+u];qshRP$Ub07aejܴ(|nrTFS*B͸3w>JHH[o֮]#F`Æ ޽{-N3F~ѣG9rFo#&&pa/8vӑ(ԩS~; DZk.ԭ[F-P^=@VV[rʨR nvT^e8tۇ4ԩSʕ ?** cƌAjpa9r$$$&&"99@[vލvڨ[.0BAA`˖-^:ׯ8@NNߏ{bŊW*V˲Lddd|UPPP}a툏GZPNdffbŊHLLD~~>vލB͚5={`ΝHNNF:u"8pqqq8z(vލΝ;aϞ=ؽ{7וۜlBNToQ:JC$VjŏjdF@Ӂ*sbЅ-iQ'˫8]*7*E6`Ya?P/K~xTdȩ' Tz7ѣ#w㛇UdXQ9*2̚ەm7)m܎҅CRRRp^Caa!Ǝ_JԮ]~&oǎʕ+_~Ip7b՘0aVX\s=(,,_ӧOG޽h"|Gxdlڴ OƪUЫW/C J9n8"!!?3nfL:5 o6oތܿfio60i$[iiiغu+F#F53g_|ի#!!? ~o>TV vBrr2f̘7hyyy8~8bѢE#<={¶?B_D޽i ( IDATM{aРA߿?PTT륗^BLL |>mۆ`ԨQYfX m}E^_`֭׿o>,^111سg~ꫯF B!++ sE:uqF}E 0|p >~[pB=رc1l0ض|wȑ#1f̘xTT:ue˖aСXl:v/ +РATXk׮o^x:t={0tP,X7x# PB@c̙3>,z-5&]XIe^\*U`Ȑ!)\r"+ss_ITb¢,UBek:&BvMG%N|rSr[\#E.X]_Xbr=d"-lMv7zpHY+)iu 2>$~GJ+SH]Xvm8v-Zŋ7 ի+LV xbh׮~²,:u ˗/G&MGۇC_ҥKTZЬY3a~GEEߖ4O[nm۶ػw/:1i$/A.]`6>sԩS-PDb۶ma !n 6ԯ_M67|t?~ƍYf;w.бc+=͚5üy;;w_~駟pw୷ݻ?;eYXv-j֬|+ozо}{|o~˲УG [nUW]hcǎ!2J+Ը 7>0}NGIl6EF$<ݻ7FSXRe;/yr&XQsUzLHɸ[SXD+/N$ 5qlBcm+"V;wƍ7ވӧcϞ=/K,~{>@ @ @vv6{n޽{@|^4)n݊?(WnVqHMM%?'Ӟ={`۷/`Yş JS0n8<ظq#FɓHIIALL .]Ç_iӦ8qQQQm|TRH˲9` a8q 8z(2220~xx!_;*ggKf =| +N7' +wYnP&kGgݔ@RP:D>@w(e &oC0|`K5kCvPTT>IIIܹ3,??zg}1 1c`̙8y$6mCaݘ0aB\ o6݋:cǎAڵk#;;/ׯ?8qD=iܹذan,\N:aĈ{O]vǺu0f̘78pO>$v튏>5kD˖-QTT b̙ٳ'V^ 4ocqƨU֯_@ d0E~ 5$q3pdTXV%J55Мn7۶=V<u&IHx5V*߹ m۶CFF =-`6u,}2O++J+۶qtOKo, ~԰ GTTTM8_ ,),,  QXXUi`hvOԼK |'~1/ML)J~Gk-Z}݇TR`޼y7neUIʜ.|ΠHez ĶϾU+\k*HGG\U6YTlԼQ1X|wDaB=-XQcyQ|:=:OVTPP5k஻W_Aʕ+cJ Vyʜ.|brQ9 \nظp uvT9n7Oje_B=InߪGՌ&dU WDddq] 2*܆V_+j{O-Onyhh>bupM}P6XQ<VVc=3fC\JX9ck]}ι ] \ɘ}i,Y_=+V raV޺|f#Qe(]brd)&~Ŀmx*2 b޽{'6=ȣsCƑ_2}?>O^yGyѮ] `,]}u]} tqqqauDW<_PYC.y=<"Gذ*=#FB~|?#}QſYYbE4lڵÈ#p俣e?,Xſ.tRvڕSɈj|8tn*\7{Re2jeS"'6]5de]"%JFQQQbDMJ=(tqq:T'Cd`a<哼U{"^ݡߦt>7++YzLr.SvJtU>$#%%iii1hРoRZuв, :Vn}ו[/$TR9$⒫|%qY3e'&.8o]]XeEc8͛7?@gsy3兕<ﭫK+X;cQ*I"?`e/&ɤ*Je=_?8DUX-L0Ŋ8JFe[^o>nNƅOJ1ŊŽJ-*ֶ$"kTK{)q.6Ys.aEaubq՘49T^&D-n}tu낲X6lK+j[W +HfpਢHUv8?9TJԸ<5Exe(! *U-rdUX6UUuoUT_m*r.G_۟U</{<~;aۡ+ 5O,eϤwK}sKj3i /[W:{V>1AQ8''3Y;y"(_UR~:GjyM*'z8~Y'Gmg՘lZPNP\nrvFJO̧…×[4C:]n7qx=u\>P%c08Oτꌤ0a{* ")*-+SE<:KJn a+~QMae\GYcQUW,_QnLɍUGqxdwk`B*uE.WlsvS,-J+cn}Q>p$7nPkڍr|uu14Sg Vμ6XseffرcаaCDEE]2XyOr*E\0o BT_Gt<*9jPe[6Niϭ)6ɨ|ǨZ4$*ukĶj3ׄ3Vq>E6s/RⰠtq5RJ+Ni{X{X_X9c\6#S*r*ن ~&q%+FVVF&۶_cܹVoĊC)py+>QCY`rqƋr\UEm;3mKpzd\8(<)<֙u@a%`%gÊ"n>R5y8tI"7&2Vs)˾qp-W /Xe JWSr 5f1Ǎ8`u]z)}(`ܸq(**q1a匝ə GL6*7/_|ԼJ?'eCY:(2N,"eWLTxd2m u7xe_LM P.U([8E]WBlůJ,e|Rad_T:TzD@{Ѷ}*fT n b4URŠs:{3{P\46n^w]VXzXyK+8wȸC.f}us?9_MEm `*-O<˲pM7aoиq0;]tEm6 4謷@ Ç#)) qqq!lݻqcժUHJJµ^*U `ҥԩ/`۶mر#g:tW\qrJlٲM4AAAAH\>G~PPP &୷BϞ=Q|y`ժUشiRSSѡCԪU e!##}PvmtcǎoAFFХKTT k׮Evv6k;v ZBZZ}pv\s5kO?4V\'+GaHOOG^^z;v$'G͚5#<&  IDATA4(/l]cCe=/Fj 9_({&uqɱ`Vw/Ab85ZЦ?G5)+Լaeȯkuزϰ,;+MȸQVXq:=h2]t1Hp CԙՄ%rA ȥUtt4N8/Ezz:'::ш ѣq}k׮a| fϞVZQFXz5/^ӧ#11{/x ibҥ ^}UEx1|k׮ڵkѠAͨ(DGG 裏bӦMС+̛7?#<رcrrri&ueU2T3!pc\cqs(Fb;VP&HITS&z[}H}[l|'xꫯb |HMMӧ1tP,\iiiᅬؐ8?SlٲPR%oƊ+п<9r$u2drssI o&<:ٶm,\իWǪU{,Owߗ.]:899|Iރ!-^6'W\r̓jrM MIowSTM n/5IKNH{ɭS_upLnm@syX&| yBi3-J+ő<ʜ.tJҰrnrQ$0V/%ʂ}36)V&cر/0k,t_}>g 'c@-U;oAϞ=ѭ[7,^ÇGNN  {AuVCDt 6m ~zdggcǎHIIUW]˲_JǏ?nݺrݻ7n\yh߾=_f vOb, }lllv<aʕO?Ν;iӦ}誫ٳ/ع`p(pb眔yF)bQs2n\*XDťڇ&zBֶy)cK^QƤh4FηHaaa|MgT&1Lge>h\֭[1sL8y+*,,DTTTő#GPn]X{iii7S:>"##aOslnذD5p)^_mۘ={6|A$''vxq"** iiiڵkѮ];b(WW50qD 2 @nݰh"i&/N=u[n?%K[oEnK/_E劊PXXbacծ][yuҸqc9O?4Dl+VQ@ ŋm۶!TхJW%[tQFu"prS7AJ VyLgkSZUũӭc>6gN/w(/=T~4Uaiv+<HT͛n|`ő9yXӥXunܪ_"glc*~%K'7|"־_۰a8qÇGǎQ^=l.*Wn O#)) 'NݨQ#}Xt)7tiii, C A&Mкuko9ѤIl޼ݻwǰaÂoʹ, fB˖-:׿{ӱ|r[m㦛nBQTT~a|xѣGDEERJxl2lܸqqqׯaYM'N~F0_W}AΝCo8999s?~g8 5 W\qEPf!x8TP!̏@ `SԵçjd>NFrz)~8O|K0498oO?T }%}6㡰qc7ME'S#[$^a}3ё!^@CVfc{E|LrL\$X=<<:H"9[yê렑>RFwi^NχSN6Ə'F ^xq-u+sرfDwscU'9$;LͫvxD,W;5n XtRA BS%T/ٮ(K٥MOc"vSz+N.Wts7:,ۆil T.cW*>+*;:=V7V&8UMȍO}Q3O7] ]wݥ^UҰ+w`T5*>#yPє?M:g̴IcQH\oOpZ+C!˪gR"y62u{F$2&6\ XEM} @~4C<囎,$++SK+_Ir!%}( s-f¦MbΝ8ydW_UhY0wׯ)N[Wv例WrD'kQ<|]K"8/R A-cC(!e+v{ِe=r:TWn(9=qqpxr1R{êdX$E7vuO8QUlܼN1ay Ra++SHJKFզgQIGWR %'';w}{^{-voV\*Gbr9y@yO᜕mP(y9R;EyYuaö4V>q^/c.u"nSp 7OD=*=Ծ㪄HsAZgQŢqIIuVB*??{ŦMyfJǏǎ;qFٳ68;wbΝ ?~G,_}ԩ`v¡C}m=~Gm_}oNZh"dee| ~W#{Եh뫯O?*/K:/725f3wt,e61b[xѩS'/_>̷ +o]]XLd=%>VQϖqS2TB59e^9JW$8;[;{ԴTLSXV_\5:%}\t]XrGYIίV4\{ʕ+C3ۇѣGW^6lz^x_x㍸ѫW/3{e/4(Zp!^|EO?n€0w\iڶwy-2}Wb Mĉq!M7aʔ)رc0m4ь[/.\*K:.u.=`^6b˙|QF?d_~XyR'OPGndl~5PvtEJ؄Ql:1JEP͋:X,}P)QᰔRytm1vN^n~uOPvTajGE6U119׺U˖-ga͚5X~=j?Ǐ' ?z)|7aǎCnnn3<~87??Ǐ_z+wm~ѳg,N,9fNO3K)'_Ji&dGD|rC震9V:l?JC'*K#:Tw?H?/L|m/TA-Jrr2[jtܹW^y%n݊;vGSƜcΜ98r6oތW_}ǏGQQnu]HHH͛SO!+++K/۶'W_mۈB~?^~e$&&bΜ9Xr%|IԬY3]waڵ1c1p@ 2yyyx'PBڵ ;v̙30m4?~uEaag˲믿5kDFFz۶k.̚5 , W]u?!../ʗ/aÆx衇бcG|8|0Zn)S &&_Ѯ];tӧOq#11<̙[n;vÇ1a e[xO^qY'{K/\ 2F5\'TM~Z*FtyXOHp1$^js$6Vpļ=[$t(l6V999>wq1vq3{ni@g|Mm6rTTT;v ::ʕCŊq]w!55_`xgYf <8p'NēO>M8}4eر5۷c֭Y&,X#F ;;ƍÕW^{ ={14m5?;vGѣٻ8}]P{]@AłXb,%h,(H{KbDD$5v Q$H ,maeY2?79sν.;q{|}3;%)) cƌA-`3f 8 WUVaΝ...ɓ'|4i5jÇcڵ_>~)8'bϞ=8q"8 0+V@ӦMÇW^QФI4n?#/]t:uR 4i%K`„ xЮ];|7nf͚du]0`|uKuL61mGzlڄbjm)+]lj*Wa_y5\:54ԧf05vFjLu]*F71Q$NMg ;5ǯߤcJZ\ `lxuB4Q.ꏻ&oSs" ^4.67wl~&W^pQ9\߿_~eJOOuqݺu8q"F6m:\uUHLL KgΜ9_qFlذO?4ԩ.]য়~?iӦo0tP hٲ%:tR^^8Ν;#66ի>4j袋xblEEEԩ͛2L8hժkx_~9J8sbƍ6mЪU+Ν;aa!9۷+W 5q`}VZ#!! Ko+ ;n ݺuq0`nll,5kxiZx+lJJ zꅟ~ {A-GaΝSq['yt~;l8>}`ʔ)w^4n7tq 7wߍȍpTlkՉZi ]O;aU,UXC(ع&E MC$zӖ6F>WcHM;G0mvh\17-^](GύWXǒ L>%%%O0zh\֭[1j(uY5jTO|||Ws\vC All,4hmۢ/"}]j (((ul̘1x'0f$&&K/-܂,BΝѤIs91cZjuujbccѪU+ϡfffٳeee!8dffb׮]*駟Fii)|M䠰رc6n܈'x"M4AIIIoڵkׯ 0~xi&䣬۷cW8^TӋ3qhڴ)4h3g /Pˊ ؼy3>S|2dH^1ԩS h1~xL6 yyyիO`ݺuزe rrrO?\H\\8Ea Xbb"6n܈7l$qF|رc֯_"\}՘={6 bӦM7oq`Æ 3glق7|3]lDMu:DUUL5A*\9*AW\]6&M .mL-נP,&-mJxؤQ#J67Gr4F*?́Gw͙q׊ml/UոRև~=ya/Ԙ*ηd[c>F\4m69_ŋ1k,š_Wb1h իCm=qL8]v6mڄ ke6l0n~kƄ ?gFVV.#}㏈Eyy9v!//=8?>qDԭ[@]t t 8sfdgg<8x F3<DVVRSSCX1yd;w.Я_?ԪU7!!͛7ax뭷0|\yxGcȑA0XHKKC:uTI&XP^Ovddd~8<66Ǐ7CÆ C>|8ƍo8PVV@ƍQV-8FVZ(//Q^^ &03g}6^<#G @zz:~a4owy'^~ehժzlDésݛ鎽4&Ǫ ;}<ƕ1+o"SuJuT4àK>m$rH:{ooD;;`k4qpeK4-W:N8UllL&۪sU ͛7㪇#`GGcag[ W4q?\8p7oFJJ =_Kj"??͚5 eӕ"l۶ ŔQFӍ7"===7CII oߎM+WU$SYYrssj8pyyyHLLDFFFX{bϞ=hԨQpE #Qst̡7zhnOC-| :>uG,5(:f"Ԉ0c*v'-Om8R6q8:nM $*7^)))_Eaȑ[.7_+*R'uk1جU8>ts>WzLuf% y|{כ+6כMHr ѪUcUjjj_TINNԠAOqh111uMV+W^s*Sll,ZhajժQLII 5:\iZw\͕|{l^O^J͛.iNx5LG~Gӱ˻9o[^^UVGW_}m$Լ9& ΏΆ)OiLVmnLumc&?yHs+,1iK8*Ţ[U9\وϕ\ˉ 'UT˫G7VxrD}?bS賺 (㪭qk9:9=ákt>|t9s|1c X\s zm_)=UݤU7Mp2pEWq)uLgm҄`?\\\\5X 5Ŧ̋6psxL'*WĎq E gGׇ7W9]W=vI'{4݀8~T(fQ͠o>ƽދ&M駟F>}ts9;W$Kc1ӄJ?6vzJ:6#E:͠ ^)ו#˕\\\鹲mU7ܽ߶m:ԁZ}uMlU:@%ΟΖ6^gkc#sns]ð-Z`„ #<6n ڵɓCߨ9tPoV߶mbĉX|9^|E?3#<;w7A~ꫯb޼yas]tM7݄իWcԩa?\NL4 0ewCϞ=1k,|asz*FEaڴiasۑI&}%yll,yx/o[|7?0~y'ݻlǎΝ;g+>l >s̙3沲0abʔ)~'O bڴiXtiE]+_~eߖ-[bطoᄚڵk[nA֭? k׮exw7o{ׯ3<@ 'NM͛l W¬Yg͵mcǎʕ+#J>ࡇBZ0elذ!C߾}o-..FXT`&,|O?,M7݄45 xîo>}0c |asٸ;Sկ_&MBbb"~߇}u~ll,z   7tShLrr2}Q޽?ylܸ1lK/ŠA:֫[⩧ >%%ǏG:u0eʔk7 =\^uٲe6qơiӦ;vlX.n:30c ̝;7֭K,îExz{f`Ϟ=1bխW_kfرh߾=o ;31b|aÆ7n0~ZjG=֫?gĴ4<ų>0ɓ'e˖lq>-Z1cm6L2%wURR^|nk1j(l޼O>d\jj*n64j=X\}Ƹql2L: %&&GJJ |Al߾=֭\ڵ+n`aڵk{EEEN-[z֭o ;q 7`ҥ5oj@RZf7H /{1!Z9^dPgVE}3p\,B2ʁscHP]]L]-5u9kpM ԝ`^iވ㞃>EI{PPPUV3W^c#kGU"+36$6]&?X"u+[{s/o fИėڊcUHr7EdևtƤy[]˽IWg -5}->WGB?LO'-*p8KN>$|j^._ '˄SAʛ-W^|s9qoA-Z!nhsϕ4:Y=mEw.lmcz\):\}sR(1jQjC'sz:EI8ta#ɡ9'q1b{&M!CЯ_?ʼnx=/k[8Gt>uҺxGt&|d N*暰)Z.MS\U>W}4:^|ǎ /ճFhJ|m(-- ε[+se/5X!-WE7v_$q 4LuU]!6҉Ս,Dk0(Ѽys5 /pűpqMx)5&rθ1 mW:&&qR^"oWM7h{18VsU&L>WjWc#ҽ^7lt{GA,͛7cڴixD=$9Qו\TLMMUm$UM=P RrXM/1lW-Wܱɋ|:mm99I_Wʕ;f;>W|q\R}m29|\|%^$~~PEk:'\:ll__%i\Tܦy&Ac^6G$'W: 6 4G@h wUd \YҋϕH\Tw\ qu)H4q9ބk.b*ܖsu|rBZ$)HyӆR6*54tWv㋫DqJr6H6r -Nц+!8PmiR3F7t,a6\\t;4/] pEcySQr[*>6p[ĕV? XsKs=ϴ6xta˕~\\qX8+nLwti,ZŮ^u6]^+]\'IXQ%E+tbiN:VHz\lɆ9@a5q'q$mhV׬p _ZƯk@$?*nL]ۜ)oZLñlRp3^|\cҜ5&reseserS^t>V[T:|VWPyy~s8Zq|sEXW9:]1V+N*XԸFUz@Ūk#K tN(^tN8lRi}lt7Jz#0PNw/\|qIZt౨c9:n%;^&W&>W>WԏdJͽAGqFGXǭ\77lxAs寫 ''WgmHKlۼt\DucR|3P׍׋AڊmAɏi-G+2؜+hc (ϥ[trRG+Ssuϕ޿ W3.9+N]ǼlL8t\~vB bEmt 85~]W>WNJ+Tek ^}>8^O6%&&SN׫ \~vkC@-r:`*&WW1trŞKIsWuX~iqfМE%爻qb".ڨnIFˍ\x1+/j]4"; 6cE=cQ}XSks_^6\Uz. >WR }Pj80]m>H5I_TcJ$:ͣGJ:P:4ő)W&6t^xiR1Kk} >?s>V\ `?ߋ,͕i~~DJϒĴy)t8l'ь+))ʕ+},1LƊ$nSۤ>nT7%Cb1y׆p>9N/Ot^ggO˕Jʓre«1:;6m/8)/6'#WRᎆS oO&9n9\mו7s|x?\M닁qm|I{;)&7FGcI{RI\|̚5+,HuNt\$~*ox"K(I6i:,ܱ.9Fa m8ԟtp!ڀZp5^Ύ@szj,.W[ N9wq.Lus-Ԧ /Hu6[.;R\Q}]++IN6msjl:_^pн2ͤ^0\cs>}j>Wa!M 8?DZiLӡ>L6ˍHz^] t(OSl[NG=oH6:}}is6͍Q).! ׋+T9\>VIVʁ)6ʬO1|xX>Wfn+ e^Um򓎹1^;65:\J; @i1>)aWWw8KKԝp9sbSq@;(NοNGSwmnܹa8׆H[/\׎t#mNuܡ?B(5`ÙZ9\9\q7:S-0\EbHr"5SEr%P}xcI1+-ԁZdb8M -\P &Q*4qUMWc1aqNJADDkrnҹ7mM\шtkĹ&'踲)Ĝ۶lOL:zՉ+bQ+9cQ+Ds߰',:}Dkl9"===b^|&p0 F7d\>qV]9׵~8KphêbSΏʡY`0rHSW7Ǔ#V=E54'N˅W-=tcsElrwPU2к k ӱJ7++hl%WT}[UTVIlˆن4\uU<=Nc㋫נp9LJmGcs=vN+66ts5u: A=\ٞh\\MJw?P%\mmLgusex*@MqE3u]'e!th垛J%txdMz͉єFj:iMyI|q[ZW6UMTM;ou"mmqR|$h%1n]\\>WbCj,nnT15&}uɉU!T7^\A^U]ZtOa*ⶱ1޿S5y&M7omh$Z?>WbrisT^7Ћ?تyUp\\\ybuq%['tx:~%SLbbb>W: Rf"۝1ֽiqԆ+ŗNSs>阪M/ A w帢&o.ܔMkδfu׊SQ0x9u <Ax1p#v~x㊊ a}~;gb4o^z#}uEmiު㉫з$Q; Gr'RSc1qPTW'BKm_jC1q~B`l_+zsMbItNG.]..6HM}Εs^sIx-aWu^]Ŗn8ǂ+~:s\x9tr6OnNwHSѡC++/>W5Y6[Qm&FLIM4TM3g#I}q7I@WHMIsc)'/}w$?&ꗋezxֺĉ*f P{y4sMToϕȕ-nhllйk ^:7|Jxmcӹh:R\U:Wxmc{{#yhӦMf-[駟@ nݺZttnѯ_?ya?4Lcϑ&/%W?ɓ1tPuYl1s1z_Z֦F&ao/9I4~uq||O]t^'vdv;&>W>W[hT*j=VF-5&wPqW~9).6OcsR~IR< *F޼(^ OQQƏc⫯ ˕iݘ?>ϟ9s`ȑ7oϟ3rEM ;4$&&C o!ɹsO>ָL46U]WqϟIХ'ǭΖ;w1m+^|^NdlW>W+W&҆_u?A78ta4 N:iՉUTq͆}(gG}sǔ@AqM$p:67ίá8A1*A^^fΜGy>`X>|1  bΝ꫑4nXx1}Y4k [l׿XhF.]VZXr%:t{ "77cƌ_??0ڷoZjaɒ%6l 6 -- 3gDBB. >XjJKK//ưaByڵ ~!?0a< ۷oG&M/`ܸqꪫwnCN>ի˗c۶m(--|]\r tcƍS^x4h3gēO>N; ׯGZZ^y$$$ĺu됐ԯ_-[4,[ ]tO?TN9Xf z!==6mɓb ̝;?g 4z*^ulݺCŜ9scʕhժ~u]ke 4[F|||q{b tؾ}{uB״Nغ@\ͱiVsJuCgcM+>BrqlF۴Q|M:bz~2dHUNbU#WsNFz 7'u66걩c爥~`>?l З˸X޽{1qD9;vD6Ӣ+((^x@g /"\xᅈA0ă>_~O=ʿzѣ q9`عs'bbb0gš"7n{gMbɒ%8׿?8oߎ GQVV \ve5kCFߨQ#tMXf ^~Pη~;233ꫯ"`ɒ%kp饗" b͘9s&N=T,[ ^x!~cƌؽ{7j|͛/2Mz=E\8LVmQHuZA+z>W>W>Wf][M1.m _1y媸+Wĩ*\D**1WY㜩 I K("\Ý()GuL#xR?>@II  0,p]wOJJ zi0ݻ o>lڴ M4Ayy9EʷĠ^zDaa!Zj;v;~+tM4 ah۶-5k@ ;"##z*bbbРAb޽y\t>D1c @AAnݺP[FLL ZnLviAZZ222gϞFqF׮]QQQs9111[.ŋлw=+**n:̜9k֬Ayy9yPnݺކ8ڷo&M૯B۶m[oᬳ 5nt5k,kڵѯ_?̟?cǎŋ{bڵ [lA푝o]tz6m &&m۶ڵk1qD\p8SШQ#dgg V uŊXt).\: `8p 8z lݺ[Fzz:N?  ,@߾}Q~}8>} ---tsY9oܘ aoltE]WMt#pT+c=\\\yUa΋)>itm޽ov!ϕ=cU@uB:n\9"bGaS98FBǨ:@ ./{ƤI|rx z7 >>>L硇¼ypW`ܸq\^zaԩh޼9N?LJccc@ Ndݨ޽{w^L8YYYa9X܆ҽ U, a gbb"ۇ !]]8//v0bqԩSyڵk8_ضm+\y"b]r%Ə 9GFQQQtK8_`РA2e зo_L2xg1p@,X ܹ3[̛7_|1w> sYg(** T$Elll(P8vU Tҧ]*6~f[t(V]Wr"qA$"5ik1-ҜO/SN͂n>:C&ӍnÀо}{iIIIBÆ T~K~~3%\=zJ={ĉ^x!y*arc̓fO>SFocƌѣѠA+Wv6+9Ε >&VubŮ}U7>Wse/;W\5\t\SU0*Q/K}Q"Ţ9q.r@FƎՖ+q0xnAA>SwyׯދݻcԨQZ(7@^@QQV\Ey/U{=ҥ 8%K`СF; Pƶm^ڵkѧO\pa:iii())}݇ \}5j>tXr%?Xg}[l h"<3ҥ z{#F˱h"묟tdeeF_?++ s~a@rrrؼ_||<|I\~Xz5VZѣG#!!ß'n~a-ȹx'pg#..>ڴiT̞=֭Cǎ~/v۷o駟u놾Yfh߾=СC1bPsUĢ ;FbUU,K׶*>`H`se>^U1aHLoy./)-W\D*PqGu|16BLr +\ު5T-HUoAZ_qo\Ω:v׮]]v m۶EӦME233ѻw0dt !ݞ={bϞ=X`ڵk뮻-[DLt-6xtXf {lݺC !CBolܸ1vb򭃝;wFFFCǧv8&Me˖ak~ٳ'򐐐.] ;;ݻwʕ+b8C9dee吐ݻ~!..hРoߎ>Ǐw};6˾}򕄄)U:v:7޽;/^ksѱcGdee!..ڵC֭A }?ޢJM6᫯¸q`c„ իЦM,Z[n_s9r ԩ]vѥKyHMMELL VX۶mõ^ 66IIIhРv!&& 6矏Ν;233ѷo_,Y˖-C>}p]w!%%N:ѣB999Xz5 ݻN UTX`0{͹м١qA!n:OuQwн%጖+F*se/>Wr4իki]]ǝSf͚'' WaseosrJBҤ&sJ)8-:R3[]6Xt{#ڍ{?o8˴L\E#:\0DYYYglĵs'mϑWTT,9KzO?O> }ًw `}9,X[n9իr .ᕤa4XK6oތ+r/N ?t4[;:Q|^Glf:&$#ϑ@iii}|jW\P:ftDmw|bS#ۼӆIʉ{.qcʑ;W*7\#p\Iؤʟ8쳑S\ǣYf8CM(ūWN-6Wքt1\+uLw1SNl}˄!L>S5+]\8`\6s`9ȍqQn&s_ŭ3QK\1|͉.6WTWwkL$xy$ūc'KJJWw" V~ŋ1|p׿o-CPځ"rכthRy9&WLB>Wfϕٯ{|p/nn|ژ0rt}|$񹊜RZ$4O T$9 J6rDɓ6į-O͏kָ=^z1qrqLU飄S7\E>7]R_{2dBt}H&m|bW'W^M6>W>W8' WMxlpml]p8N7R\]uF&hi5|K5tm:ݼl8q4. KJJm6-_|%:)-- {^TT{ѱ;/-z&Z^]]M>zSUI$?i̕m^&]L?&ܦ| WСCżl,\Ise/5X۠n+*[8pԧzLqI1i #tut70s*W`…1}ŗƍ#j׮TZ+|}~;?߃ˇZ[ͧXNGgGut7hKNwk||hN6% '"W^qڈ\h6^JHH@vb\ysehsK x7.|AD bqi.::_ŝG1QwOǠA|dlٲ %%hѢ:uow= jI4u:$86uӋ_+?+{'Wؾ$ 'yu~#]vO>UW]s寫ϑ*k8Ԣ ʟӍ@s.9:5o\f^]rcx\I另{kI{Ν{z2}X]ַ#5+.8>WՑqM"==ƯkG_DL|- ;dKusud5`Ŋ5XZ${Hstj4R (-mYtu`:rEWGq6寄7Ajl #\?dz[S:l Rlʋغ}\|Cp%`РA0`RRR7o>(+H6JML\5P3qDL@U1/'}˨W6j6tRq9 P<*bjm=WDWĈW#FBKIOjtcUW{uEZZo8/ʋh >\;⎥yoN/8ضآϕϕU|@u F~ɉ*fI@UP.Qu9uDqHS}P]ǝ0S ß! ǯkqMr91{ߴM9Ul|;ٹR!ssqiWrr%K\|6'&=i_erՉU-?Q*RMD͇GmMs4舥'Ԩ.v~/F)Gnq&/_n渦89=[_o)ɿϕwl6i+9Ћ4c+Ժ6ƑJ֢ɗ=uWqw\p~8:lsE8_6Nw,NJJB=}d: PC]ut9;&s1HIK>UV:7nF&V@0Kg˝'/tklxU*/DKy4]?TGǽJ^Ԏut|+]]ګDHk8tTsJ6|J>utL:ض͸W֭\ɾ/+7US ObuNՓ6#=mNW8c^n WcfNtظ5Rt*iI-)n8"]TGաuBґmC\\cSmDi6{/baUQQQo;\Uϕ=U@ݼKwm6UЄME3=Y9m:U?FzચTS kֆⶽXu\J6%\~666yS^JsouXM VpC#$Rt^pEm&W;/$񹊌#UdI'l}TmVLL,h6:<#\T>W1oM*MTj _W-?'1buoB:t ^?q3:MsbK9Ħv5lڵ nbSh|ΟF7Gq\U+ka%\ MURWO9>W>WTd/u6)nsseRbi@ZtX؅ V6NrTumrps,'9Stg>cNtr]3ˉƷmiC~JjuXlp͡Ns3O]C%5:xST#Z+.wf@lj"G[6\IՋkseNߵi\E$~Lq8OI˜ οϕϕ:~s5y-.nGՍԴԦMdIyͯԌnKI6\q)Q?Ybm/hoƺ86v6bjm ϕ쇎kpmbhHTLGsΥ:->R\q>|Jfiьy?ŎAݺu=9*>Ϳ kDnSiâ|:Omp%69qs"WN 4qUL\Xܚ)vqmD4:s}L^CײmA[FC^x47 +<7c'{U$+;9Q2aOWMٶ&{̄M W9rϕo]ٸv5Wt5ZnL-ڒzs:ݘ*~{nZ8nh8̓:(^NT4fs;OܘMnm8[+yLXqi͡t]svam4s@#ŕ)'ӜIJwpEs%?? is-T\&n*\!??_Wc'*W!t 0nVƑ͂+Էda1Em8|sXBR[|x6]Ta{a^ tƶS?^csk͖/+5Eң>x\]G"MċJSB⊋i[M\q>ĩրɦ*\c֬Y}uurQP9L>R=&I 59j|XڴK(ma Y>8|ϝo]NTtPߺkK$œ\q,w]q~ xn8,R<+)hD.wXx>WXx>WXx5+[Lu$ l)Ο-rj}ʉUuϕT7WʨE6]Թ[8F6útS0I.p\\hR,p ԟ3]򡛗n1nHU֨-FUzQrMn&O)Bpr0܇6׺w2q\T矑K"''ׯIW^O?aΝ!.yyyQq5{l,]͗ˏ+//k\1yT[_W5+/AALG`ڞKs-]6M>W^U|: } -R%%I;Q&9ȧmՄ5U& !VGwH7L/\IR'ҍ}p$Lys>},W:&Spq2|I[鹄hsMS;wİap 7oč7ވS1Jn\}ո馛Æ ÛorTTTW_ŶmcCڵKJJ~`;k.̛7OGRRjժ%;w. 1WXX`0:u갘vڅ ̮|Ԯ] a㥥GFFkii) KCJJ  avڅ뎏߻$!$!$$@[j@x,HS“AbbH "O}">R.ABI ${̝ {>J9{w9{wjf͚N:G͚5`;33^:w]yyyl`???@vv6|}}- ]ͺI,n>Sq<+y+yʝ.NGd;/FI+0YxePF5%,;o_V*ȳbKE< `:z"c'|թlɕ|T^9QړBec\٬d\Ǖ8{,իk}/_OvچFvl2 o-Z/СCB||<www,_+W/Znl4۶m… w}1bq!44}зo_ԪU 'O\xcǎűcǐ &GWqq1}$''c„  رcQ^=L<ϟ4lo&{ĉƉ'`0uTر+2220zh 8Xb~'")) yyy=z4z 777|X|9<<>>>}:Zj\L6 , ƏΝ;ѣx7Q\\ EQ燕+WbĉE߾}qisO<bܹx`0m4P5o(Xt)6mÇG!C0sL߿\\2W/W|v \ (;+ I+hxVB8rLaB!ea[򘎗gG{?g,,"7&_tXMq9G$Wz"uM*UeDƿ %GN6ij4[iW F1h ޽[%Kŋmۆyɓѽ{wԫWO+<W^Ŗ-[вeK,X*f͚ :u gѣd#%%W^ɓ'QXX_~iiiHNNfƍUUqyXVbРA[.̙_5k$&&ĢE0`L>]]<<<0dDFF">>sA&M0}t`Xp!`Ll۶ ݻwŋѪU+sѣpBSi&i-¸q0i$={'Nɓ1|p,[ }Ō32?~ԩS; ((?~<.]  77 (//j?W֭[͛7bvNزe :tŋ'Ə x{{_%Ke˖xwPTT_{ܹs 4,[ QQQXt)-Zxxxx $$۶mCyy9~Wb˖-PU{Aqq1Wyܹsڹz*fΜ ͛yaXb,\gFrr2JKKו@',V^*keOVG$^^^W!_+s^̕sI,8{+Bx U$z,|,8dBIz~T^]VL1=nW5p=,.h?tL4t,YI1z棬^Lae ^!`rU9dEΉ9Prz!o`/[@jxd\î]uViF™3gM4]кuk8;;)))HMM'Q\\ۤI 4 4O>oEѣGDFF"..gPzum6mB^^rss#`8{,ѬY3OOOԪU ^^^Fdd$<<<m۶E>}P~}Za`oX ///#::غu+r 66;v@^^}ի4hgyňCÆ 1x`!++K{uţ>l޼ׯGNгgODFF^v HC6m ///7o͛k&$$ÇGӦM!C`F~~>>S\Gf0x`ԭ[={DݺuuVxyyAػw/f̙3Ȁ󑘘EQЪU+ԩS > +V+vލ#FɓHOOǞ={C_5ƍ-["&&G7|#иqcc1bZh&M >>^Xoܱƺ!˶o,6/++ V2cĕ9 \qe6Jf/i{tGa&؏YE 7F@,VQ)KycX'O IDATQ=UȲ%8x7xI^aoMri;FtxvczϏ^_`oWȗ3]PxEo^a2,<\ pʕڷoOOOb„ r NOq( z{>vZl޼_zu|*>OOO ___{]4Wԩߏ_ 6Dll,vڅ~ 5kDtt.)777TVMCFF/^Yfa֬Yشi5j׿*|||fX@ڵc777#==/^DPP==i?B~GnO>q㐜 h,//|]׸qcn_5?cǎ駟GbuwwG`` q 8CQQ|||*ѹsg /0p@ 2Ǐ3ј:u*|IZ[.JJJpA={Cii)=#66@XX_`` PVVR.]=ڠ y7J,kLjm#ɷ;8uaߕaɕ<[ŕ;X Љ?].E #}јxE%./n6V?6C*nqNl‹g/scrU\ኢ^uQ8U _e~wOʼnbHqEbApp0|||ԑDqGDD~Cqq1(h׮ϟիW{Avv6~'hZQ&ZǾVZxw\4g"۬s闗#33;v>XxVp+0`TU… 3<ܹÏб]xQYYY_k.ܰ}v^^^1b␔~ 3g ++ {1cЫW/4o͚5ʕ+YfظqV777Ԯ]ϟG&MTܹ.))ъ<\!==];|V/aag%vXvX +FVKns\+ $bBsE⊥CaZ9痥Ad)̢`r匑'zy '[Pzea=<\gcǎa8~8~77hٲ% 55`??#M}t*___jJ+PEA֭yЪU+b(++ܹsQn]X,|GX|9 TV 3gDtt4ѢE G^('b֬Yx饗… X,GVyxx{ؿ?ڵk=p=ɓXnEQV- 8رc]jwz*/^ EQРA >HHH@YY<== 111.]Gnݴsݻk5jJJJkAQh*f͚h޼CCEFF&N5jK.|hXȬ?]/mzgIMy%fsl6f*d~DcEEM'qs/y#}L-yF S=yqЋmћ 4yo(j_̕HC#|=aഅx_*@*2e^ [ķ:<(D~d}*++CFFݥE(CaaVjdիWZji߃4Շ~m۶aȀGBUUEqq1PV-bٶc...˗TtJKKѧO /"SXbͯ;}NϚ,++ ($j"++ 5jpRՊl6i7o^,x{{_F\Xv\gԷNNNۇݻW v<;\6]/ώ+L*ĝko'gx# Epү$, e改PTE 6Vi5wL`#;4d%DBúnDW"]Y<~`u/Ͳk'<k]Ͳɲ憰0oWwxq,D5j8}ߎ#&Dž2z{{#""B (ظq#/^MSMC.d}buwweA}?u})BӬ"WƑc²ͲCKyy9ܸbl+rgvrN%t%k>'pWt1q<_<,wE]$)~KVWKLjkr%WVXxc~Qy ~1x#sU &Wn&W~fpղeK?uu(Xzqqq%e0yg2XvxI\tdgrLrr2Ə'UV]`R}9\i!v2 PUُ@_f5'qѯ1$&orGuh,;8y\΁Ox|WtX<Ġcխ>Pzonб^If"""U*<<:uҾO*ƍW;Exy~FYW69cZ=qIrrra'ݿW+y .Ca@H-z0||E&CsA*xvx1&9#/nQL"^o.H7e3d+OBQAi$qG]PFzdheӋG&kUo~\\+s|/Wg%aɱdggcٲehݺ5^{5矆_+#MmN\(oA%^[Y>Dr,9óKm2y̋#m @f2h|bWK+|p0iAfQ?zbreLxcO*@|}P٦b)z?.V֯_aÆaݰy!#2yPx5 +,Dxxx`KycύOU1#˕(I ;Vq/*4vVtQKDU4#+}zcVS̚YDƠ'$[x7R\)dExY:z\]ыEt-^ʲΜ9ŋ#??g)bM+W8l6$%%^C:xfZۤ@ s[cd΍#맲L۹:hwE$}Umĕ>f%$Pt@e#*hz <+!%:jB9ӻwD1xRx!9bq-Sί(fo:nElrW2k/X>kw ^{hSL1Sn$&&bڱbAHHj֬&Me+\ gvD*DX*[oɕx*Wيz^Cv^QKXwX ]*y<؏yоXE0Ͽ,>kP'[V+=Xh>b+E󈇁FŤfTL󢇙ɗk D8'y]yxm"j[Kd022Ȯi[$ 鉀ԩS111xgKSM˱/qUũa6r|eխ]oa; ^dE.+C배ⱿbXY:46.gMYD\6W]F7DRG7ؕFyƲgr+OW6ώhCm",?<|n{3b\\|m\u#D h኎Dٕ". ?^zk׮pww'ٵ?Ady%vr}.x I KD4k!fDX^aB:44oVz'_tWyn<<==vç|d+s^uȤ~:>m=V;C|cXvYqvi{x1,+*zvy8D٥5v¥uyɧFlC=ȊծmO䓷Q^"=6΍JQ=++1w*W2aDcE9-#vdnW1GEE?D&M7UZ\j\ƙJ~ܝ=I >i5KdlrI()7t'7 Wߩ;7F7IBt~DsO\ʴ=m:&W|kc-25ydnrUKUqOʍʨNUɕ\JƿLF^(_ِa@.1v?Wxd0ƚ\\q_Ӡ6|]z#`%ST0;#Y-X+MXY{,X皅 N5wH\2]Kw%F\ɋwhKbQՊ1 Q/}~hz&-e/(Oocq;&~dtL䅾X8X\\׿K!:%¬c:]">nxU8z1kE8ƉƳ0щ3y̳ōӆ㊶KO4n-W<Wx]Ity+ktE8 iQjykMalr%|s_YY|vdGUUŒBUkUV!99eۤcNRnWo[$7+w{N6@HQ:DvhߢYoBYxYEL,vGO^-8SPf|zeqEm:g|tDv.YHǙ?oWΚ83d7u>d抜+drererχQvVO<]TF*P///2{/-6 nnn~ՊZjqAxx'ÕHOOGff&xxx̙38pzy*ك2#Xʰj*$$$j ҥ fΜ`x' ֭[ժye@n5tPOhR1,t *Bi>EvE<g#ϯݑ+{Q(jc J"eT6md<#ze1&Wۯ r-qwn UYML*nV;W=}Q.f'-<۲q턑QQQQ1bҥK͚5k[oax憔΂@j՜+--Enn.5v 6 uJ!Fb#gp 'ND䊒Oq!eIۏil4~.:1 &/.F .w^E]osΛ VFUx],]Ů\ X=>Dֻl"/"{7+xy&W&Ww:WuTT _>OO/ oɌ7ٞc ʕl6jժjժ9v]vڵ gΜqONNƑ#G#!L$$$`Z1*"##1i$m\ii)}ٳW\?AVV_ UU^{ s`Xh >}wE˘6mNW5N#:^DE"vE}"L,W6ɠ˛$dMD#ڸh26e6CхB 9^Bّ͛ &WU/ ^b&((Λ6+cFcLMtXetyQ=~oTWDCWgZѫW/͛cوtgСC裏жm[ Kzz:N>Ν;kyyyi{ɓ'QZ5;HLLɓѡC4m6leˎ IDAT; <<7oƘ1c"''/^ytVeeeJKK1~xw߅];nkk: 1; X-}.K^Ѕ*=gD"px"9&ۢm}1mW,~{l\2̜zmKE>MqZD6E\缦}χ+!o=h[p\\rsE L7?O\JOX\*͕ݖ*(W7Ɩ-[pAA\\\µݧOlݺuq+((@NN|}}\@:u0b੧pTUŲeУGAUUo8s V^vaذahԨZj{W"88-Bxx_c^QokН4Jd<`49W!bzvi]Yy8E'BC:&yj #Õ(n65#?OWdAr!]f.m&W85AVWϮݞ|Duq]Ɗ[o5bc\nʨ#~:_#ɍ@bO8jdܪbH5kBQDFFbڴihܸ1,], `W T^ݡ? @ܹs(((#GT|5$$HIIAhh]?2ÇӧOw{Iz jwɅ*fϫtg-غu+>3faʔ)\rǎCII 3h",[ 7nУG(իAz(ٳ'rss1sL(wwwDGG}Ƈ~%K`ժUX,ӧZhڵk`Yh"-λq^vͦ҆XxD8 Ym P~M(#]baa}DWcwlĀ: zM+q.kCO6H 9^+ɍM*Wla:) `댗grƕsj\tۍHl&W&W&WUfwE+sNq~q2idY*v%W… :u*~gkX$6 'N/(&\p~~~\l6dff jՂ>j"##pWȀWYp YqrKlM=*G}J^Lzm,\ɴJVqe/l 2D._^Pb*Rhtl }Y1 >Ҧ+f#,;,Uv“607t~9?d1~dY<&W7XckkdSWۭlq sxk\Us燶9={?>s|aa!VX>}k׮ի[0 1m4|w6SSSSO!//5kٰsNquy1`̘1oDxX1ܮ\V]SNł t|ȎcƳNeT4}իW? 6O>All,|}}Uc+y?7+w o!uD> R+,ik1Mte \HGkV.;oignIUd^\x\i 8}W}mKbcrbq7/~/22i?''III8p """p!=x衇\a޽wYPP(--m6tl6䡬 媬 bh6E\ѯzl={6 9槴VU?hˡS*&h^i_qJJJsRZZlba76h=5'zyeTU z\]whDZxW5j@ѹsgXlT "VZgEϟ/m .]Vk5:F&32YxBgS=-]JD_y=]?2pfW=SAc Ì36m֭[C!///_FDD<=="gΜg}#88ݻw7]vaÆ r :uꄾ}ǎþ}wزe 6l~nnnxCVVzݻjo=܃f͚>GbȐ!_}~̙3HJJڵk 66;wv:XjRRRCv<,[ @6m0p@ 55֭É'兮]j?] 6yPTTCPՊ;`W_}ooo<_0x`:ohӦ  S^^7BQdeeaΝQfM#55AAAxgѼys(K./Dbb"жm[YYYXj= ???iC ѭ[7ԫW)))PSO=ooo];wxnnn(,,ĺuаaC믿R|8}4~abegOv *Q4ް0aСU%3W" <1rJqdf_ǰv>6 ˳K+RY1ƴm|1qT`v+tpe$W<<}?Y M[Q"J7/NZLqed᤯2ɶcc᭛"^mWsbf0l0 <-•+W*,,DJJ 5jؽ{7>2ɛW,{999>|8зo_lΝ0qDo֯_S=^{ }ڴiŽdL4 9~\zCŔ)SP~}c̘1HNNjʕ+g|rӿoԭ[}EHH.^P#22͚5~Ԃ<ǠA`ZON:86/Dnn.v튕+WbPUiii?̞=k֬8w^u,]Z?ƌ'N@Q޽/"4h{so .~g5 xG{n7NرcqI<عs'^~eCPvmCbb"TUū#GO>h۶-RSS*|Mڵ zBǎ5\7nĖ-[?̙ 6֯_b޼yФI5 ׯ(,,Ē%K0fSN(**ˆ#pQ֭[%KZz}36Z*.*Wcru>KA\cyH}(dj燴zH$*bI4.Ѽm׾LCّ=H%yd{=^=wъbu7/e]&W=VN`~[Sc^d\aqUF Cff&.]D̚5 \EEEXx1Э[7@˖-;͵gΜ̙3ooo(",\C > EQ8L>ZѿAAAL~x^BۦZQ"-Jh?Z,v,߼[/F^mzhm<+r8%~mrMG&VQ<[6~-`"X띫X\lD7+]/22ӧObccѵkW}*͛~~|0TСC1x`Ġcǎ4hWMcTPP,9sРA G5*j׮5k~TV 4ZZj [nh߾=bccO[(طoF;|lPĬY\zu~?,,Lئ´X9aÆi>Zhw^\t ǎPQ_rNBڵ6mڠZjp9r5?Ebb"v ԩS駟cǎxvᩧB֭ѡC$$$ 332e ^}U?~G3ӧO#66Vضm[]V>7oq~ !** $$ 4=ﮊ(U[ :0PA,5{r'hd0Y.:y /`٦m*1,?,[kҶIJP!/W,>Y~Ex:Yx٠ ޹⵱bxKF ՍGƆh}mЕ5YĤ(B^eqUW<]^z(,,DQQ[II ϟX`qQ(ʕ+1\t AAApssgő#G'`޽7o|||0rHlnڵqyMa[RR_kƢEpl۶ ˖-1az3f@^?Cl<ݠ ZTCڥVZcZ2ZjZѶpB4i>|'|{ 4ݻwDZzjO?(bQڴi___ڵ eeeDrr2 sZj(//Gii)<==aٜ>kD\Ie%FêsSʪ)9NB:;FRxI0J}(yd6Mn'x7f’ 3Kg`J<[?F%\Ev_"վʱ͕ETWOt }|<jbݺu3HOOǦMPPPr\| x 4jZ2}%l߾N~Ѿ}{l߾)))())AAA}Byy9 { /2._bt[lAPPPV-T^]fQ_jgjl۶Mn s=^z!99Y[srrPXXPt٥[nXv-rssQRR"4aa|2Pzu_~E:~k֬իW5k8<;lق@!44wIٻwv?4mEtt4aZsNM/77ܹ3ƌŕ@|8yvwee~m V~!GϏ+:qJ䭮6sGqeɕ<͕;bU$W0*f#yh2k,kWTu#*Y=KXp5)#wbX\v%@W5ZUU­[|73gZl ;wSN?}vXV;z2dƍSLW_}JZJ^VqV Q%ՄD!*"Yav%o=<2x IDATZHޱ(ӧKɕX ş|'kF62fHZZN[OxRqQn2]~܍$%%M4AxxV,#33Sڅ |ըQ!!!¸wrssqy$''vڈFHHEAyy9RSSqiqƨWݼ+W8<PiߩS;:ub@UUVZt 8qΞ=z!,, S 5M4A:u憒\x%%% Bpp0$!""M6닀 svv6ԩbp?/^DѠA-(( Pn]xzzjq@DxzzybAyy9.^5jhお388uv1V||?/]&ٙ>; 1WQ+D );dxKֿ0h z8e+c^@1`QEQ57#⑵ɛyfM bI[d7^W1rwU"v$ؓ !^YY,ï8GX!Ld(<&W/W.]š5kмys!!!ٳ#͕ ŴiӪ [9r7=Y}VU3"FmՃGI^{O&W漺'/ipNItPS1oXH}=gE.Id+DqN\ѱၞoz>χQzDzsF%^Za0r+nX<=g.k%mڴ o<5'-ʱ]f^3Vƣ>{O3*˕bAnбcǻ+ּaF{$w,l"zJrM7s^ĕh# &=VH =Y $%d%FڤtH d?iG y\бzMO6e,d`acfm"Vҷv&״ }F/8k⁩E,'+n=>VĘO(N:dJ^X,kqc'sΝ;1{l_BΝ1edff: :;v<4h֬ƌÇ?v kL0z6lڵkٳA]v?;v/Dyy3f ;;GVV#ЦM,XGuڵ+ݻwO?u苊/r̘1ZFcɒ%HLLt}1h OBBB[o'NtK/ƍc̙w6OgϞشi֮]SXx\?L2AAA3gΟ?ףGׯǏ?ײeK x"}]1vX`8}ХK|WNkԨƏD|}իWǘ1cxXg}۷;i#GpE!''gFvvC?:`8pC_۶msСCXlC_hh(^}UcܹNL||<4i+V`޽}<{9N Ø1cၱc:yyyaذa0g8q¡;~ilٲ5kG!''AwҤI_>L4G}} :իWGƥK`kbg㣬w27EQRXQEֶ"nEZqCA DT"Պ\p),%'9΄=̼ss.Lf~>}z+_|1-Zbk׮[ɓ'`ܸqhݺ5}Qvz-|A}ݻw=\oW@rr2z!ڵ+HGSOѣw_ᄄL8xcǎ ݱcǢW^5kꫠ޽{oĺuB_7Hh*hO4t%y h ^o,o(WNPARA@dP8]:\!3+qõ9?|$1n$섎O+.n.F@D7.D§[n5^t=g'mQtҭX X|9/^$=m۶5F}8ݞ )Wn Wn3p{ :gmQYP̕7qU\rUb*d|UCp m!cdlr/joΆvO`rNpSߺSM^yɄWsM|'7U S̥_LE] .+W?͛ꫯF׮]" ^Cqҿj+\ܷq!.t"漑9;B7ms#!9O74X[W޼r#GW~UQ[T#\gGC0P9vtqoU'XTaõxtEyvd{ ͅ:x 'efjۡ9XleITOi !| 5pb̗.2VqmXSjNq>?\ᬲGFsr%skK^De!&&{FVH*|&XL^r~67\Ji?RaM9aڸ}:ym*//ǦMBly\ͫCrrs*.@C\!!qv‰+&e 2ECZWɅdlx> *)*uRsAu)]n>Se܎5}"3YAOl蠻Ba>7\pc=qu|pK6ucuyA86UL&hD[XXWWM+WQ ,Mmfr'>6Dm lCވ儓ñ%] NG@'?Nj(={tpMur H5uI+q+U,$@7A-QǾ; ^cntX<̱x\c9pRLuLUo.0+:q&\ٶr׾U69G;WRKCsȨPɛ <^.PW9ϝ\iYvQw׺1\W,JT!OqŦZ@[$CbW=WnRd ^;BAp|p\PSr$R2qeXʭXgK8~tI١=<+GBJ YGlUE͕ =y g$v^8^g'n}LaT:ߔ UِZ@wsx cj4D`:8?W _4GJ@KumzxED+_+sW~%taݹcr.]>NDr=y7%|aի`TqRŎ'r#)?Rt*.*d?rud\|A[eՂ^ѧ+.b8bTT {qC+67<"Jǭ +r^>P+ݵ]7ܾǕ#͕E/LNs]ʦH҉Ǖ7(?ʕOtL(19"<8rI%b*:JGmE.ظx|;u!q)Os.Fq% w=D[%b6x7~tvE(U'4n,Lpqe>|\crNѬ˭(ۺ乡%R/ؔjժƍq+s;GW>1#JY. JU8[p,7$4U S鸹v?wLpkpbõǕ-1߈֏j 4!h\!Wqe.G#W"Or]zAYgwuvLlGEE!%%7>|zHLܜ?اΫb)\QFDSRţɫ&e@b|Q\L_Ѝڨ,'jDZs9.~ȸrQj=wW]BבJeW5|Ǖ+7/g7U6o*oU$\ٳǕ^o^m\MN$ecb@?Ѹ 9Xȅ&q8*BL c:!8;W29eKreC?٦#Ss!+#%" 7\X~Oy= h %W>*x.h,Lx qqʼnL9&1ĄSF]wUUUvqͫc+.jS ?&n*I`r:\֑:ܡ%B+ b G/|L_y>p1Q|ЋsUaUR&ۓyC^Ŕ+.5Uژ\\o@y(Q2jN~Ӆ!c1 8} u@qհ\*> ʗXyMxi,Me痲us%I1mӝ GnWx\yJc:U LS*:}nǕ{(,pLZ@(~Aɔ+oxW{4Lyt봻)B)cGLrSGrr29眐vC+s9Gˀ1sg.Op\q<ϤO"x=3G싄{!W>+ĕOt<0, W\ UP8?Q_)۔_.u[+u.++]lsbqQ"/Unn>☳c:ϩ1fAx\u!R^rŵ4f++P19oM1P}E\&g$\߿~+Ǘ Uo9uERbR(T'`Dѫ8rir0%crPR)=ʮܛ,0Ŭq}S7q{\pIk4v$O<9B&u5秡mDzvJJJ_cUmn:? ͕j-?W~Fwd+ teї''Pc~UI=tvk1qŸ.nٮ+T!Nu9)͆p (WDU6u"bh  WYƾ) WqO8MQ G}$"VjsxTI|S:m!8*,dkU\*pmT/M枛䀺*NGly\+ 7&myo*^C#i Jl\R_80gڔ94(^Q#*66;wf9#WqEPmU 7"7I,TKٕSdrEO,d ]lE[M-ɇOh5;, msdWaL,|8vGϹ-P7oQmc8W>ym8;`,ذ1UW1ٯ+U"򸂶1DrʟOny\Lg'RΕJT ɞ.UU*ѧ]9QO]2Mvts\\s;\e?|<^ԓG 8? Ώƭqb +UܾaP_Zu9CtXLJ;qqEy\:'qm">^HbTUWWcϞ=Fvw2\섘ƹ$N@mr(nT)qA&I OFҫ{^?&?ǿjs1Rz*|&J_:Mqh Nb +*)utwuɧPjlTqqeah NS_\IAkbU%nڽ{7f͚@ IDATqEQ{\5]|&Yq˅'T Drxr8)۔}YtɫNlj㕳U?K#%J?W+8 @w܈.u|M<܉g5 E_az98Qa1ӹ 44WYyrUSS25qKUɇW繪.U~B#ۑu(*(JLqlPYwَ< ͛JJJp7oʕ+QPPYf.k.|'bcSԸtԼ*)) )܎7knq%r^b)pRʛW:9ʃ~(D)Z,G䱜oXUXxPx{FT IBN@Ǖʦ8T~ ǎOŬtQ{\5 WTii XnFnUqrqU[[3gfBff&QQQ诪BLLL̚5 =y[\\1}tdffbϞ=+q 'Fk̙3q/Ĕ)Scǎ%xꩧ '@ǎQ]].oqm)))HIIa#eL yi?gSS7d7+qpբE \lǕ75T DOK(>|ʷK8ȄR8u;x O%c{*V]LtqFW7sN p)<**jܱȕl)&a hn9ܵjǚpƍ="\qS2WǛod~4iTTTPqq1rssѳgO%W;wĈ#Я_?K/ŪUp{X`PSS_~SLK/۶A+Dqq1j|'۷/{9 6,ȍ7;}Q}(--]w݅3f; )//~;n&[n/'Ex,X>,:,|W;#XTVV"&&K,={#..UUU, {Ʋepuaٲe_~%I&!''?|>&M/FMM 6n܈3g{w܁cʔ), O>$ȼѦ.o |݊jh|HII q+\$*GYC%"SMm.c|M"U:mCHIaT@`B?'nSʮOtt ʿǕtÅ)W\"W4ECwn*~Yr8r3:W555(--EFF}YôiпW^|!kjjPYY$7ڵk#++ ZBQQ}~Xlg ЪU+ HHH@͛78p `Æ m+WDEE Gbcc刏e˖8~.bkիtR6r߾}Į]0yd\r%֭ЪU+cҤIЭ[74oЯ_?L>Xv-Fc֭XjjTTT`Ŋx ۶qeaفo<ޙ]|9~i$%% DEE#)) m۶ŶmXn &&>/`#666?sL<ݻ7:\~A\DGG NmۨDmmm"ĺun:nC *n񈎎'%%׿{]t5\C"66m_~x&Lg3>8<袋dض6m 66K.EAA n:5558p@˲@Abm*k0Vpxt9Qw`ԟ)Jl߾]9ʛWr4p/\eq>UX}Q>k֬A^PVV5kut\s5ܹs n۶ECAAmۆ.]m|w8餓 aY~mTVVlCII &L{ @>}лwo`ҤI?>.B4k v^xniiiӧ^}Ui͚5CnW_SN~C.\N>dYguuzq}^j6=8;E 'n+s2+H9 7Xp7K0Ln \/q Q TD-^(*~ίjAm:?6j!NnlS:jIS**6jn|)Wz U$c Wqqq馛p=m۶HKK3<.Z_^x=ڶmd#&&wy'J=CEZZ֮]xX:ww}]t;|<.]ǟ' <p)m'x"?cԨQ?c$WӦMCUUv܉?=q'_ć~*+U裏/gϞ?K.f䠪 pYg,3ļy裏²,7?9, ]w^xDGG=b6m^zHA]Tx1qeUԃ>(5}J.dr%D.DXBleC, Cмp>2j(ܺ=R,tME&bU;+ MJ( TAK' j.q ?#xpMB /ȡ2^dzp{êR{nCܹ3>c[CŘ1c2TTT ''d 2UUUϰzj$''cΆmHHH@޽lUVVSN 'N; ?ODGGc̘1hٲ%rrr}|WUUsر#l߾v/~^x!*++bժUy[>"|[7oƸqp饗²,tIm~!ߏ1c ;;;믿ҥKa0|pʕ+tR袋0jԨOqj qqq:t(ӑf͚K/EVV,BNNRSSG᧟~µ^nQQQAqgݑ?wƨQs9']ƚW5HaǺ98T*3yظ6۶Q[[,zj{+o^ۏ,[L.drBǍq9lnpt:2ᎡƺͲ,~Htc!pu!C1Q-b W"W.W^^^y3A|l 4!RBc "..u w}wdjkkQ^^ؠɖ-[P]]$lݺz+׿&|K*kƍomm(++CLLL֢ࣳ>/Hiw).MTԍ@TT[6D:M 7&5gjK{l(?#Ǝ &O>w1%%%ѷo㚫p: hy+dۦO|nvdFq /L٧&+?('XFpGaUvLJt H7qfx\鱛bsVwЏb g[[lp$9r7\Y/?>Ì3B\|8p`h8[:e[>/[Km&P"gu1LEbdS9Us([T~G]naIJe˰zj\r%{p)޽{+m2WUUh\{S/&u UX} J.d͢6Wn M&mCMU?w#g|1ak1$b\Ik uq5m2L5盛M+Oy\hߡե1uLqQ8"*rLWd]w:#*L;vĪUi(#5tå«79nR6w[xwq7o)˲~X\rݛWWo;E6΍۹s0Pej,¦A7$&n9Ea7k!a4&0SܢT=Q-@jqmTK7eoWM[Wڀ!lT<OKR98Rqe5I&NiN$Eܦõy\t Rnc?~ƍODˇ'x'#EEE!mmc۶mx'…q)ݜmkk1yhѢE@GwQg9eb['&= :r\\8pI!ɺ=STĄ+@ex?2&UR+sWU>ʟ,/+ +.>] '0qm&Iq{uʜ+S8l)#sN:ᩧb׸'x'/ׯ||@zz:zUy5{xIe˖M7/rbrnN1-[crwPEU8Qq Q87MTQ8\ǑkF)c/tôp|~Tﶘ UN3&#ݤ<̰PU5M61p׿ED9W?B X10c4%HHMMEvv6N=T\uUo~KF?&V$SNy;dѵGb[ &(rRl\G%8NEeq#Egr1um7$ޑ4K75?LR2G*&&/bJGR(8 I/_A7ړl6$jǕ?+s ɕ!'w4^&Zn\|Ÿѷo_~!ر{po+sM!tEr!%;<*HJL@U"I9NR'? G%/v*^+9{r¢ґq !hpP~D;WN8N[* { >W,ܽ4779@Q;.ٟǕ;LUv-j1 ӄ5Ķ! W<\Fr烪T"HU\?Õ96+sl͕O䍐t@){r.s`jlH1Z*IEAcp# VC3 *671Pƭ@*,DwO)/cuʷUpE~5PCcsX$~C&M|{\y\|\ '$6*ďssJ\URŀ*(?&=KDDy-Z/$\!@QqcYl ~[>T6TEǕ8drsPsJҡpu n*2?Z'X]7 jNnO.&JJ7<<<}tu>"1Qs8ʡ9Z]]|~~uLx\1d r_#oԆ,or|*IPt)fEKHqQjAq_ŽNLQ]&Kt*񸊜+U7l$ IDAT A#;2pmS='\q{oGqۑ1 \+$׍!O m֡Λ77n vMM fϞT64W޼<ˎMAš>E,S"bE_:<=XU@qz*n^J5-jMqt9*,&}fq>M[/|{\5 W:#^;bV1d?DJ+_+++ZLT:ǹ=n}76}ttذavmcƌAmm-;@uu5㑒4XQQRXT%2Wm ŨAll,RSSqAZ gqlěWvjjjl2J9]]J1 쨊 ٖIX}lsnaaR7\Q4WR*R*;*bMLmnʎg1Q_;+*ݻ߰|rDEE!++ O=:ubo3?m۶x׌um3~@LL `ېQF?F]p教+ķ~Ν;91EpU9GbJWKKP7I*_s BC/BGYCٿȕWgG݂cton5(%W|REqnaI?POuvMCΞȑ?s$;q,Wf>+Y S<:1;cLvϕE5FCDƽj:xTIe}JEm.nCܧUS:xʏηJG[tx\ϕjlaÆe[kSB?_M|nbo,܌2qe>hLƙֵči>YOok(rss駟?F-ХK:{.=\XlY}a8Ӑ[UU3fW^{˲~駟P[[%K>þ}0d\(,,?\TUU[nӟ͛cϞ=xѧO,^{gcϞ=xG믿"++ }/zk.Xz51h \{ qϞ=xǐ?8s0n8Ġ?6mڄ2twqڷo*̟? ,@ee%5k1c3… 1ow׿'Nݻqw ##@ݻda6&L}aĉHJJUW]ocݺuxGpw}HII#YYY_|ӟobժUxꩧ裏m۶Clll֤Iо}{#..UWH/X>,Y-Z\o_}&LW^y3g#}`ݻ[o5P<__:>Ò%KЮ];ضaÆaҥիWӑ \qx0da޽{ömܹsW\T :7oݻ3g"///!8еkW|>|7ARRZlh;o&ۇ˗Gnn. ߢW^ٳ1n8 4p}0iҤ'N '0a²,$%%a…Aנ[bW^y%nvl=z]w֭݅[SN(--ƍvZw}4i6l؀/'Nlڴ 'tR`eff":::Pj*wmc>|x ;”4U9}AQ+>'UPEa|PvI[#ړ۹\_7XGJT1\q1)ԣQ~C-*|XM+q\gGuM2Vv<4崙ئ㴱2y\y\y\U>U粬j_W*;bULM}Fƕ mؽ{7yaw |Y{̜9K, 8_2æM۷oӱk׮߱;v,[L6 ((( c۶m,VǷGQQvލ-[7yǑvڡwxG~z\oVc^мys<رcكM6c9p͛;w"77o=\4k ~%%%(((@~~>Ν(v܉u֡ݺuCϞ=Q^^]ܹ3rrrP^^ZDGGwXp!t˲п8ѲeK__~+v܉W^y}%Ѳ, <sAnn.v܉3fhywy+.)0ԵـT<VUP);M+]sX9x\)Sr$r|GN['\[`VݾN_UU ?VX7k 'OƄ 0g߿^z).Ros9^x!/=܃\s zy!//f€ЪU ?e_~xG0uT̟?(..F߾}R$$$}i\\\] FĠ{HKK ]G׉;&&HII> #F@vp9_~Pq|'NĒ%KR5*[7pJJJ[n~믿뮻֭[?~xG{)))m4iReN?tDGG;")) } x-{Űah<䓈ATTTOp 6 k֬aЬY3|h֬ԼjduMz%kE{`Q)?tTqPƫ+~?|$u鋏qu!CFAS=1*9d1HYxn r;pteJOeð=c&¯~:V|=W݈ɺ2ט\SeǍqex\We?>yapW݄s}/O;G~zmڴmۨ K6ᵲmڴ C<iTlѹsg"-^K.E۶mO?axBeee7==!cǎd<~:tgYu_Ӿ} NE |NSX~fS m!(}d6gnsv)_2^7DPuX~7wM].nK-4Ӎ$PS~܎yHqq<-Wb;P6ˆe[p>m>n6-7W=6+s,W*1I6y/ws6ʷ.i6Gl {zݽ=z4~R[[#GjkkQ^^wlre`/''\wMmy & ;;7'M#*%UH.\Ӧ*d=:B8"U/cse_*k6"4=txv9E&ǥjӉ_Wt'\ntvqpmk۠1=DP{>qqe* WӶ.6cSSCG~re;n L:K,;#|TVV>}:k} XFff&暰| k0[F$O~  Qr!Whȅ|pT"60DE *Z?+ٞ+ lOŗI//}7uuTW7H&tqp-i(T:W:W:W I?@=w֎ amwu{ .ѣѥK߮Y8"...S \{sTU8b,7Ka YUqKEIeKU<ɘTYR|Žp[`7:ThzFaܮ[rWqLlRrPx\p%c6g'q!X^1I}JǻZlSJ7g9|mY 57YT&m*;nrkLqsc 5M ($YMB2MJ9\\2W555(-- cUEE***OǍ 'd/*.Rs^NZ?68ѷ{%W]o^)|G6UL""WԽ{٧sQ7>>9ׇ}q8u{T?XWl>n :r;ˑJ6W555xpcŊ|gYv܉o>݋ yT\"9sĿo8p ~ᣆ+yҥKƘ̫cݺu!ڼZr%~! K]%- .cͽ0+:BΕ'zߔ\[qո\0c@LKG+?R LpqĈznSOOVXL9\|o.6nק&I%n,X y^_|;'x"^xm۶SO=/ň#ТE ;ٰ/:VWW'ヒnݺӧ^upmرc׳gŋѢE ";;SLAff&x Сl1tP/wߡ[nիqb())ItRt ~;@ll,LNCii)~t5y GD8i(;:м{=xʛWfr4pB+^J@u j|Q}bQ$Q\;uD mꕦhp :;TMٷ($nq`SM4+7T,~9mnap44Wn?R\YmoDm6h>,N8֎mϱn:@O"C(*Z>PHE0A"jD*<@@f?sgݵ@7U}k}^{;vkUVvɓ~?%%%i#ҥKگVZ!//%%%شizVZ!''{N5~m4ok׮~rn]vEzХKtu+eE^NpIC:)%[gH+U$R@Ac6^->v(`T&>KKr%mz/Fo"?i485ldk"):V^-[`ܹ4hϟnG5kPPP!C`صkN8oJJJиq4V8 ???cii)Ξ=F5j$6'N@~~~,NpSNaܸqhժ4k /r7lT7 C={Oak.GݻcÆ MY3m:M~:xIƧw'twߘv#hm|UPdj }ĉ8~80W_}pYݻ7)|y<7n\s?0e|h͛#F7ߌGN̙37"\s53f tRl۶ {I8̂ ЧO|k_Cl2tCQQ7o-[ GQQrrr2jܸ1ڴi[cھ}{Q3ݻ7oߎcǎH&SNaΝرc aO>Lyyy0aLɓ'cիoСC8pСCR߃K#]6]󱍪v /> y͆jV~_]X?e4iI:hN֖Lzj ׆;kޱٖ|w$1p>Ezӧ_\wu1c <8z(?W&͚5èQƙ3goduӧcذa/Q^=1" C/Þ={п ^JCŢE0i$t~!N>s}a͚51cna;vm݆ygň#a_b׬&A8tNdWU9ٖ0 DJŶ9籪X̜9s$8`iB:\lR~  ^Kp|Ԟ9oAcDf7  ppߟ[AoVlxЂí3;8x9?z`.p{I3pO L׀cU99tKaSNᵍM*/w#gLk-J]TXaػw/}]4h3gķ-@II Я_?At999Êb-[DǎqyGUR۷Oo3=zɓXn 裏e˖aӦM8pz) 8T ֭[yfaڴihٲ% ^͛cȑCaa!ZlaÆꫯFNNnc8z(Nj߾=:uꔎ_~8w6n܈r;]w铱o{;#/{NcѡCL Я_?غu+}t#GDжm[@˖-ѵk״T*}iӦ:t(N>3g֭[1h \(,,Đ!CGwg}Ν;W^(((ފ_owеkWi&ѾhkaXYi=dfǵN2Fۆ[Ql1dc]ʕ6mx+w XF&ETVK-H[khi64ݚ~i-! ^z!sAy4x8|8^ζtS$)^Z TX!&cʦtP_.Rg(( a,a%٩Yx_j#VT ׷*KR8h+ؓ`e[[XEZ}U^^e˖ᣏ>B*™3g$OnM^{5bB#^s 7(gc;\ί$tqCar J}ҰʍV4p]EkQh#bDT1us ao1jOOmDHI1(1Sw70-f Z+屒Pҽ$< @l>$v.7HVH@F("(s8ٓ@M 7'C99sN 8zUF+4nN&f͓DgmmWkXEΆ' YF!c?iķVIǓʝLIjKJUU=H˃|&~0)++իn:#AUi/ V1y=V:yL޺}8md5*n.|R +>!Ʈ<-^*b*5\\ 19<浩tNR>6M Qm}X\1|^cR!ؚM3=d\0X󮶓A}{X`ua;vkk< oAۡ߮t)7mc屢:4li|6;&6ِnۼI B'2xt+wjV\R5Ed+mĨ%Lh9~5]d ՆckdmZT˺i|ҼtZxlIZnٯ6d*7l؀)S`ĉ6lΝÇd|AtMXb6mҥ &L;v86״iSL6 6Ĕ)Sp\nn.~aK,-[b}c=7bʕ(++K5ngƱc/ȑ#1'|ݻw… k׮ܠA7oo~\֭eee={6\z0n8m{ߎxk׮k+**qiL:56cǢSN3g>\;1|p[jLE5k>c,ZgΜϜ9M6ܹs;6w]wax[ov{ ?þI&8q"*,\0GbժUxwbs;vɓ{n̛7/6WPPǣUVxc?! ظqcL{7noߎ%K$6K/ĉxpر#<㥗^¶mbs}ѣc,^86׼ysL4 %%%X`A=OsX|9׿ ѣGOV\kѢƏzaĉF^0w\|'CbȑX~=~ߣ<=WXX_طoϟ'Ndym3gľ}bswqF5k`ձ6m੧p=J"??-{z;wqa߾}xbs;v,cΜ936 VcsQڽ{7q\Æ 1c 4if¡Cb> nV6_u ?8v܉%K4=רQ#g?Cyy9O>=z`ٲeؼysl_~3f vܙ_?)N9¯] `IN,wdy: ZU*=%-,5l"- 9I%VxRlUVÕ|QtB {55iJ@"&iH8\.%++N# #[i7gzV¦MpM7a(**2oyr1~sⱲ|XuF$V&c;{\quQj}c0pŠĦ⾜*)4FצԁhȘF.jCok qsˆ0MDp26%Šw1>I6řን䟴&٦ӧّ(.~NF^gqŊNJМ?]mo Oz!̞= 4Y+(//)֒q)63N=V+.g< Q3@#r!lIc4on n}NX"9 V~_]>X>ـ&ghAdH{Ԏ䧤 >ҡ58"-^gS>\0CVW!Hd5MfVG"]槤UVJ-g_T ͛7~L>eee:u*VXseJ#5ݖkUy~GTr yN gzHc="<)떔t i=}f.`B 3YqRrJJ\Zfkbl~J\Sx%\m;(b ï$z8lkm^C GIūKEZ2%ҘA4 CQQFQFơCкuk6ɮtb9*XXʝj3V.Q=?)gY3cvh1NkjB.=(7nv[?ҙTO8l+m۸l`x&| qѥkjWV4{P)F0^]OB[Ly161q24>n/mI#% oζ*9Vi})fӼg  1IDATIӖW+ANJuqu"5WP9}\]_|W8IUW]1}c2_PiӸo'%d>l64L?漩˔]*ClK|-?KtSe\qtCd>iJy8$thmR4p)m[&5fqQ(\'m{TVTXU7Uv~qr Xq(S9'&^MKdbլY33CusW"VӜXP\M`JظqΘ,%E̴K}||%j +.UOp y+z858%n\ ц&cKBq7f){ZntyoJԭ56R^uiqUgӸڑd)ȩ+R>|XrX_*b_;a+O*N+ F#6iUsspPH+T1&鷩eF hnb1kT<3϶A\s1sAc冕tq2N:B ?kr4/ZXzy=Vu+[Q5R~dlvbgw]Snc4^JZXkLdhjk8\L6HzM|5Hs4rApA6џRS'$Sl4}:(.ThNbOkXce`͞0 [BΎĦM&}QXE:ͻ]c.SXIIgFNemڶWZXIs$,%+D@F?fМ7_s<jD^}`7]bt5 L56i1m GUe;z8JC D"]q&zlR}ՉU6>;yɆNlgL\l'cr2~C I~ȥ9o$9y"hqvoic_Uc+ycN4|=īxlksejڴ)^NJڊUJ5WIEx$O,i.57mS36fKWYqmiӫa%dG.5RL4>ng.HI:8%UO$tH8*>鏕Ic~Kc屪K:"WyC<:& \δ$uqAm]X%汲SuaM H2J殝9oK ⤇TJ<F&?mQdžbMǯeGk\v9VeFWVƣB &B O0b\ WmU;Vd|hhJ5eIk9>vǏ7dx:V)7"ЖlCPk~&trD7L\6t aӻl6g *ي^tհ1*g[bUF2汊Rϒ^[AAC> L9.Q<] fpj+ޞ|֗V-ƒܰ&MrIѽDf_ۘ4_WQIta}/Ֆlm6k+icXzM/Ilυ$ruRUG.=V]bz#o{=.LĴzf&t?i5ZT* p5ڡE=u[ uR9= hU\mW倕-^?9f oֱ]0浇u%*A ڵcu%aeltg~RlԴn K.NvtX۩iدF^afs5$. ԼFzRjLY1*Crz82\kvlqnJe]1ipKzAG$zmSY.~ kE> D0=ڼ?VO1d_h$R!s$%SFҼJk A ZbQ{]YvxyxI=Vu+^ו7\}5M͞+gΜΝ;U=V~_^rT.=2s.I'KJz4mp1P>U#WPkG0?Dz4ݜ~ξրKbn֞ʍ|dž58uhl')km documentation:lemonldap-ng-password-expired.png [LemonLDAP::NG] />

documentation:lemonldap-ng-password-expired.png

lemonldap-ng-password-expired.png

lemonldap-ng-password-expired.png

Date:
2016/07/19 12:15
Filename:
lemonldap-ng-password-expired.png
Format:
PNG
Size:
64KB
Width:
1488
Height:
848


Back to documentation:1.9:authldap

liferay_1.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000005615261325274564300420400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{IDATx]|G= I !ťP@NS(^(nE E'$Av^rP/,s:Ĥs,7J!aGU1c%d2Ch4j4N痧2>fefY.}#zFI ' @ڻ`Af(f1 *i6#ID98)/q a2 T|]P- 5R\\.b>)ԥM@x 5qNXew6^㔗)WfEj @{>)(, Hޥ$TCXF#Q{iPTFKޥRbLIF\R:D$EC24RP#a \MAFb/&eK@@nLB=<=>ѕ)N((; 3$)J4sbߔ,Q( b(YTEwd8*c%s_ODJ2URO7b23lZa@.4w!f2>.o" H@K)b%QjHshED].x@Q](~(`ƷG1pۏX]")l@1|vu5Zh|YrL4g(UPiӯG %]`Vl<:Zz3ѣT?_|EDDxyy K(/$+ WHK"uaE4(#,kdPT Ӈ5KREK! v!^ l <, P*[ :45*ɠӋE/zZI. c䮨n5&sP @Ud*deg{Zm0wIx<$$|Ŋ))ɱOc^7$X@׭$=-N R2Q7hK' C)) eG"1(+q -V $OJDTIԝB{R{ h@@\M1X]A(KPμ(+`6E3 4ZU(;\Iy G#).p9CЀ/`A|ca PL̊ѮjΪj 5BԀLS) ?jd { C" *9BA=@k *ZOA7TFlK 77P` Nh>!QhЉE2VSyKIlf}32Kƒ*2Ke t|tNdMm(aE$UMg…ݿ֭[Ŋ3 *5! @! }4JAQ`+Y"z?U!犉AwF: F c`V1*|qkϖdBk2)#;,`$̙V]a&Ն$V`y³Mcf4/%p<  (ʈeav<($ T9)dÍ`01di4hB#vx$94%ԜALV8ErXM` C[ of63I(5J V0W^x8e +daqxw-jb]] ̖;p zȔ[߹:g¼g^E#"*is7j'P!byYxDD޼!8G"Eccc#""/_^.@5?~\.% 4?/qUGSe?-f:@= %F8uĮ!ɠN5%Qhe"2+WԭcƄw1nڡeDz|s mge,uDQؖ?fcÎVOxJ8z(ГFWh*H\QJu~!Q ;ll5'ohjÊJ*lB!\݆ G#K UR\ 쌭Y$ *wN)(o>ܪeh3`}FB]%|hEZRl&<#|(Ij@6x 'ԙ) 1KPͶ$fwdPNL0^p (CrLǃ8+ Sx 8他#UwnAn$STώ;ڵ`}/F',_)tyzҍG'-LK=en0 @\Ue痙! 㝜 HsU>teB/^h6@ 5jteV@:4P ǏUapWCXX`LKo4mLfYG-`@V[G. 24;>,%BpM$] L)%LA"iE$*R >gi|Z*,C# +WYA^"yg6G#%zTR"/J cXC,̫?H"9tЛG;di yQ2~@A+ h^p_K<$TpԈgFJ6㓈z+]0sIs`İd v54킼;2\8ͪ)X룫u9`Bqtp9V4|'Dh[B#5 jI/1hO@dV̔b2ʓT,=H d\a̱l}U\IM&́~ȥ˿. @D+ooQ x?#tG毖LmghWZ?s味]p! CIPɤ PBJkZlVbU*z]={&$8D`/_b2͛۱ٳؠ:\oޭ۶i47|CBBڵmUϞ1t'M.R$9) shWH0FPl[[6o;o.мN#FHHH⋖>O~af .|AWyYvꔩ #$Ҽ*ˊ+RgA_T9BvQQ#?rʏ?hذya):- b0*~VϬ7 IQpsҙ>Nx`;Z‘﹘6)UhX2+lߢT#B).N?%YXILEu1N@9e2{+YIqǀP\LJԔYY`'&x"*6e]:.#DӮf.]y_k3GyPg?>ܸiӰ0ӧOIf }FZϜ={]9r}Y׵k%C~y» 9yQQ,5UJ@+nڍ?ʕ+&NXv8Q1pvՊ(ZG]RU+Η.l7?UG w(є`|#xR̄oX r=)0^$h$9ꂬUO'JV buAv ѱmɄҐID7.hWF&v"'%mNsga)a.ݢчޥ{ъOb S2bэ'vk48@l,u(Y;1)]+@DA]AW" }:do/wTA ̟a̟fH#SnQXhNJL\,\Qc˧R%/}KhC2tf<{^}[h>y~._SϢ*6~a|@l*…W.x޲_+3Z4[)R$ 01=_|Yf-@T]-J6}ܹʕ+wl!6oQcFVRwk]Fc7mP矃lp-|zKEN  oP5h׮ڵk֭[<omۢ,ԩyQQ,\ȥr%+% tSn6]@@IҾ˖-{ݧ%8#dq Z^X=UD-|+*{"7 PweE{V/#`F\^RL}-0'`L#iA>{}nIfG8A8 L8[Pȧg%hq@R QVl]PܘtQP#B)|2F)uAp2XILȒPvE!©42<ˡE/Xbi[ 4Ќ9P |%CIWF RY^+l0"Z>y>38ꫤPTA7vv:~} *T^J$ǯY&l!r維l QCX#~{Ռ}QW,U M~j=Ⱥu1aw 0'g-kiZ-R0`~ŭ]S/j>V V\ԩSVt}Vl`8pm֬LTa\:ˏF&$$Pc(@y|,Z RWt鲛7o())\VVLp;w/Ugdduk׀p>}Gyϝ;7|:gڵj\$T)XO?`>:ub \(%|' u?&*W LaAA"A%#AjZυf`h*@=^*T:N!3`Ѫ eɚI*(VAND9(VO2:!쥃.H&Sxvވ# ҴEj"1L'nvl鹋[,@>w<5T [@ֺؕ-cP*F%>#d'4ld~"srPuiJ8(((rmM f # ˏ<*J US ⻒-.A ݼq}jJ۵:@D= bx]F zA& {??_v9?(88((XFJ,4ӤM T8:S犇31܏O\*)3PJ_Fa+a-bgN1WR/^Ԩ^jժ]x=ʿ(_'O>)Vi`$HK]^zbГNh¥BFLx|bV*t8w5*jÆo@nOs܏;uA 22`ދ. 6b6O{Y"8 t@T???Zb+;Y۱QVVm E^۹S9s,^p1@߯^ݲu˪Uܺv2kL:v͚QQhn͛6 4hƍ@MZPY{P,_쬬1  e{(T8As˯;d0,GE(T*Po^<6mڵ+̾^5oiA 10a)5~J\\L.'.u3l]8ʹPq1y?~)ԙaL 7p?R'%`Cvw>Q MD1΄2^2ʱ-'-20)5/J6;1 Ӣ_J y 5kd*7@s6 M>iy职[jQz+ FJ[=uF4L T%szxz${yy)GFfөt Z5k1kמ=gKCN;=wSXRROu n֠a<ZܴySHHz쩠E;KGv]*t6Y=zZv4w\5j@mP0ovv6rϑ =ԛWvfF-[,88xfttA…=HreRbAJ5TFsiFxM!_BBRRX(m#3 }S;B_FsZ͠EdńlʅhTMer0FA . O)CJb ے$4Ĥln"Xc+΃ܒ,ZYxB1O[F]Ne ;($OFxic'~h~QPcQ:-,*@cM<=,Ł,s=Q2%υ ʃ E$J&|dv2`Tze=~F',pO{:w|a?/ȓ e4gI ckU-%j 4[,7n  gϞ%zUDɀ%xHx|FtFi[-υRx+uA&rq7-J qR OaNL߀b(],2QQ[o+m e k{zA_'UF'v|JH"o21$nD8M#rVNLBJ& 2-R&wu@iURIe$Xdof.]ך ƒ*2FeXd叾l~Y؀Gto!QlSWJmr` uO?&Ej5EUT73+j7l6S)'WVrp^{)f1٣Yµj]!W~LP- AיW/fԎ8RϺJR;t6+x7'9 P|'J=&2x{ bܪ8֗'$@]vw6JT .0@:OE}லxPF{z,<A#hhRH ί>#Oaͦ AG7vÆpA ZAŽ{xac$*t!fi5B '^Gao;'wnAor2`3BnUG}]RAy KG%J8 _??ZYY컕FF5M'1t:m-^#ht+EpT |"rCL`2=IS[pJazxNjr<i8|GaQԉrP پui:mEw$B+҆eΐ89Lell:Q8H\J=o89ێZ k8Hn ވǹw0ȣE;[)dÊ0C@<%Z4T%)(ŭ@qoJAP ` 45yS:o"ؗL8$(D@-<"SRB\DI𗕕 ///vUe}*iZh4z{yiu:ٜAa<wh)!ɠ?u`|AHZ-1DBudv}`Нr~:~P6%(w4#T >VCaZ9:jr4JɄۃ#plV[ +auNwG}i ]a!a:[ cQ%A?owBr7 |P!bڡ+#{)_-߈sZyv ?2RŢŖwT ,ZRSp}W U kTR0RxGe -"#4ThRp*ϊ >7X[sձO#x7 ,*'% [wnAor2h:J#9Y𾻔z螀do\Rj;gHs.2\fϦ2x OAܡ="4#*I⎧PBU[.pb1x-#L3\Wq:2KjSg`uH7%@@h-^2`EИ\B+Ŧm0\)@`6 #42#Gp+Lж3,tzDad9s^cRrovFׯ/_|9K;{ *fٳ{O[hM2 gDψX\0q‚˖->蒃=teʔyVxx8Op-TӧO+6'r^l^lGK=;w &$$-ZlI%KBSY,+W۷?##N:'Lb=|- W._αz&SvY'NwӟAeخ]vvܹs8 45*`0ƎW%:t:x0@ a8~ \l2[U:m@5s̓? ҹsK)[z…%K>s[t1c[̽n+Vzա!&OU8Zb`  'MbaÆSS۶mQQ#U4l%SzQg2LW^pmY2%5 iXקj=<=K,) ,T5ëVʗ7/>Ə=v~:,,_Φ2xp7sFA~$9U*s;vL8**o~ )W)NttRlAtl$t,0ls׭]ZxgϝeZD=Ob0_<*144t℉2 6[jnǩS@@…-9c&G eY:S@rJ j?ԔdW3KFfrK/" xwYYYia(:YB!x; E0 ݄v_~IjV+]^+ꅅaI=a—/^[UsC5oޜ 79dk׮/[͛8, 0HgJECv"L`޼y֮nȑkת-D}&vKMXd)H=h@4^R5>g/8ӹ3i>&'' 9)7?KFfrW/" xPdl)(ec*U*:I,ɍAAO<)XK7nؐ׾Zԋ-O>U0O<|'Oq>b/.\0ѱojAdd1c|=l흞^N6Ylx^PʹHE.{ũӦ?&\\fffI'&&niճ@?D,Ƙ?4n e\ jO˗?w|XPr"7*rf0a-n*XuXO QPr2nx.̝;i$aEڶi;#:zTTThXؽ{Y3g^15lpɓ'2 5 <($Ն )'5j}9ڸIfӦO;zN"uhx0Td7"cǍݫgl;Hܶ-Z^K,i]$''/Xƻ%Q_иQce\ ͧݺuVbk׮Ŭ?or'DnTR3`o'_wʕ+ Viݻ%믿*W ,O>K,ر#{2Uݾ׿BBBBz{M$ 0tttfͽvr,U]:w) 0p9-Z ? :|(jH4yfs-PB]2,\5jTHHH.]? 2Yf #/|-"Ƨukzq…sU5 N~ݺywvLM,fԮXBv_fddԯ_~*$O=*T(?~>|`&洛ȍJj\3B@@@@@@@je233K?)PUT.R_4Z*vaNa<GiA9֐`jk|Ϟ_?q>FqIO Wޱ};Sr۷aYI]2u];`|ـ߿}l\B' ?L kW-5%K@Rm۶m^GM)R&uHwus ujxZ Oj^D@@@@@@@N jjZM&+WXO7]ꭑr  խ+ԢPx;{UUutIO=mo 4CzJnN9X(DĵȽ)cn:++v:wKoC ޳ nS]rҥJ) ('B 9Dvm-^QsBJ@@@@@@@@@@@@@X(ao9J@@@@@@@@@@@@@X(a% xA"D?EX(a0 cXL&a^ji lW ,HNN{ Ffd0++++=WOOE=ٜm  (%&&=5 : N5*,1 %pRSSP@[i$%%=xH>>>hbIMM+[lJIIQw/__V& Wȍ,I,ip YuJrZ m)iuFc]v)Z<+ `63zZn?c F 2%Kzyzh oQgUA?S.TCzz:>}ʀy7))*6 {& /yz];p F$Xvx|J|Jwpzy ~bvKɛ?N7)<8XbU*Tt˄w߉\׹e?2yo2ץLJJ{jӦrelPG3HSE*Vh4nc?|V$aaaI(;϶ea5Zi?)ʮA\S$ P^ŋhjMNN.[|jjKqFFFpp[7}*3_J|esSH,aP{~~ܼV~YzⱑŸrlsvmo?ݼn0b:(<UTGڭJ[+~&.(hbbG^pn;w߱…:`kfFD0QGw^Im#;x y^^^I4AӺR3,b,[<,-8dWlbco8;@BF|x,؁XjʴfRR JoK, ;y՗(F@$55*Ոa+ڹ zeOxzʏ[k pz:ha~O:=gv#~ZԬE]xʮr=ܵ\2cnjaM:k֬KukתYB%S3֩Z$GE^>ۚ==lß1W{Oͯ޹D2x ÆA| v5t^^^9TN N~oB'͟q~C{/Cxl ׯ_ڵG?(RiYLݧ#^׳#{c<Ч/2fnYQ^e3+71xk Zo_߶_VM7)U(xĬ-Ӈ;{&^uԞ% dɦ;4vLٴhlOOTuw߁k/$۷ku_~4_6xzx4k}2!':rf򍭌u/;wg}|O<( w c~w_m2e={jz~CyPɓl1Qg.6*b\ ~vTCdLYe2lجY3ϝ3x eJ1j c(ƨe a;H3 ̴Rl] XMHczt2~kPͻw=_5j̙3XbO6mӦoyu>aήSDo?Ӕh2@l6YdIV@@۷ҴZzl [n\LnR)'ӻC%HAQʥgK@yI㦫|* 逅z{yuMPիW5<BO__}nR2(j4ZK_});;+fA,xDOnGZ;o:4(넾 #GZ3sHk ZlSvΎwUo튘 f^}d~Ioӓ{Rr*yk`3b&'Ry1krvn˖>|E6v1bĵ=;OrС]~3*Ni+Zsz;~]W=c ~2˖,=v쯿^޵go֭g~|&me-^L?t)`h:5H"scǎ5jH~ѳgϦM_ XӫWӧO0?|… ܽ{-[t%k3ѻny Ϟ=$}- ZVNA< sYhqr%;a׭ŷB^r8VիU~vOK7f,qiYf?OwW>>Ϟ,spe9pJxѩY:Uk A~jqPpЌWI*9sÑ-f~W-o80Kś (-xhPF 6ܩo/eѫQCG \/Y$4_h =}OMZY em"3==RS_>z f5>f7aöe*@;=w/KA7oy2uJձ/Y\s4`v~Ν*1v8.( &DEܴuȼ}6j2G@4xǰ? J ;;\ZJnܸaɒ%`H*Uzee˖ [ #d0{#I6CAg4.ʽgK@_j: $$g^{@YzKmk_c'eEMcJ ++;f0Pz ktzZڳeTfURZlZͪ@٫j7G~MGdGuiC:5\'-npg_E-ӻi%:,R?oՊ&N,ѴK:Zez?{ڰn.(Mmڼɶ4m_)i)`@O2RAW/}X6Lk4F-L)&P9\pȑ|'Nd'G/|4hЬY)|瀨|X":::6`VE ~97n8}'ooy 9{Ő=gl@WZ" 5j0<@D?6jhɒ}^aÆlk23TN ͽgK@AAԧfZR3Ӳ֌lK![ez#]1Tܜicz@D)Pʘ̌431,t-k`ǔ80WVj]?d.uG121Mg7-II3;i[Ξ9[@ҥKO: ;=j[^۹l  4_ D__/zb_1qqπL2wBxfeyyg{y=<-Ft:ˣiVa-uz#L\{V:L䥻'hy- 5))ɓL]0CBB0|&3 JOuegwFU#M>߷g%1Ev(mRPێ_۶ P?;w,~7aUriX3p]qeHP??]~_[81r͌-۶%^AU`r`U˲nkmb6g)vۜ*nKgt$Yi6US`Agn6l$C$$$46 #v0ҥKӦM~:n= YR6nvs93Q 4}y8`= (% uUf͚cƌ2W\YzMժU222^܍䓏)"UcA-"#" a*/ԘA| MLL ׵3~E{2 kpw_gx Y/7LfQިĎS=[5PŽ:u7L-Ӡ55_&$hTdM*VYNd4w^w2-M`Z8V e0h4/O{h˷k nhI-^m<5wYOxS=o uQ}s4`H2tȹsjժ' +2EPP*S8@{׹idw[z٫2G&|CY*IHAv"2r[" h߃՟Ps/< ȩ'MnUBBiE>z{pt׌& /k!`T> a߃q SOp4M?ÇEG9k2Ξ=;o|, G={~5mz["U)WJJn ?2dJ/۷oy.]?U_v |́z{[ . ."ڒeNOXFo0g4A\Kvm6{ɛe4h&Aߎܹsx)˖.?~|6mHp? YʇQjjܸq˖-qƶnx]ƌdRPPnff Ƈ̘1C}EJݻȑ#mc_C-YMYRJ-^o~ r>pϜaFv6:2S1իWY@=O,X0v8V=kp7"[-Zv:u8qEYah2e,_|РAǏre1t 0[VYpPv"gz̽`_|Fko/E*-*Wc=[5PdٶkX|^g6[>ݺmӳgJ,^tEK~=tDԵͮ@?9~9f˱2Uj.m7TD2 ꋖڮmʟ~- =գ'wch qrٜC]>j/=m2ZtfOk7w5>ɟw?]OI5'Zi*MeڼQ-[B.\ݣg[$fp`W:`]: 5tV ?3`bm/T{Ʀ69 u>ܪWygl{-c{tK_g0x1сիYZZKð~XO>1-[;v,c`Q?aQUVaɸqQSLS6PxĈl$ 8u(,, (w^+i&ݺu> N+W5իWtRVknݺ߿= ;w΁|L%n,[@e^By@Or)m_O_Nw)Ԣj=:h>ƢeRZtZZj\ߦ|{tO>aΟcDKWn*_+| ZQPe+m4fզA} q5AcLpSF5Rm МȥUD c63?Pь[_Mh4t ?-]3aڹf(vx#C\nǏ_~8ʀo,- `tLF6~ݻ7\EJiժ5"jy,Yr߾LwI3QѢEaeʔEee\ie![lf˕+)=R.K>XhvvK(WI…-䔅BWDB u!؏+78{sڠ5ΟXƕ1y!7 ڵ![|Iٲe?1VUgiFMV~oϷ ZtSE$&-xfk4)|-PC*_쿼?ҋ ZNDtlvB1%ә΢Mb~Q,o1B:Y9{<Ϩ嗯b 'KD0>N͛Utڄ/*u^~lU͞fƊ.eA@1OA:m6iVLg{v6כA*kN.] H5H GĉQVO@@@@@ ///hgxx8aaaqqqz>7[pvk][kSZYٶ(7+ 4%f9 Kse3{YܸYKmhQi0 &0nh&A!CsWιǪV^k^e /-e2|lO 5dh i{ޙaK,1NDYڵ3gIsWaC^~~HIvviʂ(Ŏg/]QAOĂ{ł( ,4, ]`w{IMe[o)|<: ھo PjB~v{JEiR2}ox`_u>:>N6_7eEژ22jfJEEVYT& Փ sk-{>}*ru $H@CUUx[lA[E W'nA@S ^ze=_f\BSW+s?z]UToҶjCqgj8I#<"{&Z+/! i$ aj*܇p[[l܂+ A $؈5ke8kpM Zifo3yq{M\RNFXe 7ˁXM(V?ϕ#AϨa[F̯˗/x%KLضm[qµk)( x3 5S~fT~u{[+vV AgR8"7KH ln׿d-Z; G\h"1V>c%BHa ׃mڴ?>z芊 AjXǍU&H >h.Vo9z-D'K~( L"!& Ac>duvg=Ը'>>` ޚp5#7Z-%9l34EUX#1!L` 1O ˗~}#rͧD;3ʧ^uOA)S|/CTf6 B0یnjm9<Osy/4E=EOSxUX{[ OZ7w.~;m"O~mS}r;/3>w K+]oҥǛާ}L'',p}n)ny21j?C8׍k{&}N =V}}|;PnH>[ӥK u.]>أVÉ/dkdoLb 䩏|ұ;|h3Cj>bZG)0C!uwEw4dQ)$T|IJ;bN ҇Q [jLVQtU[[<]S1>*]"eC]d;T4ߨ쫑@7HJfQ˧w<7YŚ] fK\3ql X~׳fN:SNk֬ڵ 'ګΜUCQVVu7zX4;gkl;G/~=r|,Ɛ4 cR9.ElxG؝nQGu/e8o*TISKR !OՂfAlA~ zTOEf!\|pK"X&4N"tR(<\h0Yw9>4wDqRluZ$~دU@Q ?~}X)FCX{t24҄xw8 \&}dK. TUUv骦S]pg=ܫׇb$1>2O=:wCc=`oQ<%. l_+0bUI |҆VB2L F))`(I"c04.k]d^02}ȒQe%gJdzTzetGNH/O~V7k{ǬǸ_>}Ԟ%lCNJ(AmS]V zw4mHA!֬YN;lڵ$ 5RaZKqHۗGԺ%?D=abF(s\L&%e埄$!̑Of4E7| _;$|w5εuDmhnVF .PP_M+øqo%_ʔS_<^hP\հD!͊Pqo =dX>gFbk)agY%ڱi!²[Q{ү-O>5(־ <91f~eʧ+zF7wb'3#q}!AT8j`օc ct_P3n=uP2)(W8۴g$O{FSs(![`lP 1jۀYYtt#AϧbvEVb)l9F1POD]|caנL҃GFMghSIBmlh *W@n3,da [KA[}ΡQzy҃…L706ÜШD5b@ VccLW`,_^2`۵ku7tSf dJuE?i[ 96pQkƣkK_8N[[ߨb;@-oIXVg܈|W 3- PXx9T"$[zQ[!ةappH(}|ynl\Z٢`ijDThD ݊ΧϊW33VF|_+/4n!gD.ǭy@0LaSisw"]/ J46.NH/Ӧ}r8enZ9][tjSj]h<\,R^^g )\ g輓2(hUή05yTw~teمE_e V %f{;h`-Y70v* rՈ3[Ȉϲ·QZp6sp[,yJX9RlW1,xj^0١0:4 m lPI46H7; .Nٽ#82L}ݚއRV^'c`WSQ^N;6yrMMKf\F 5Vtj ՙKM K3Uss,=PSn G&5!0PBh@baQY-eJU5lFjɤiɖ5WKYYZZ"(֧RKn30:{!I[˓W=_a!zŻX(y0@_H+m{W!Mڰ^P7]ztq;&u|t .ǜz~ l(bQ'\3<4566NtJ2Ԩd3ס Lx_-6ԯ8a<.7CAxѩ2|%'RtŰxa'D3dF~6,VLt YQ;T\fj֫@ݘYvrRJe$!8l:ǩ sL))~J̢`/V7pI?e$ ~g(H4j+?,;XF0tBLCX/Q~ӅH[G3!7.wYr<7~>~ۭ lեӟKk?p=mWW(AҼSI|b?9mp6kk:Uy͓+aWE ^7TC^ry;!#~DEMT[XCYe.]{zv(={&Z/X| L,|45c˺[Tk2$b(|ׁِΒ1-{k[J5rli67_w7WQˡu x*aA`o󌺝ĭ ZM!X7Urta[)2B klpVdϧ,}z> {y\1Ғ3A&)>c+`\}m7\N;ǒGOs*+^6^Bɣ~h}Cì?Ξm֮[omu鹦~?-T\L% *ׯP-e_q%7-r:u/><Ÿ ]7k!L9 * Uۈ;QѢ⃈(19Q,AH޼eXUMUarRUR2" [3cfU 3;d-WqķwD`? Ns&o6%0{B{@%JksGB?NtHS. igA|>K 6 0S8h}iםv W]~ё}օf2x/ ^3=vy%W<|7\5λK᷂o-7^ 9KsKnGxC/\u0:_4J>,[֡!7?bw?_{7\\V1ya\(3$79҅BX7q.s1,dbsM1{pQIW7g$0gV#K\wJ@ieA)V?K9 C+iiߝgiRA-gb5"nJ! ~^;>BSti a`ѯ)MB򁯹ٰ߭~ݨ+G^-ws@k4nj *=#7U~lYSfٷ9&B ތiOD7pW'a8J(tߡ! qMNSq{'rDV J7L`3YaE+P2}05'|9WEk#VurFe0r Pb1<: }Lsڛgd3;u|r QO?s\8tCCG aWI͞(؞BͶg`CI@>l]wVLv9;Ïsgg|u3{t{?͞{ı'ѧ'׷@GT<ݮyfO/ 2Z`f)&+?X{#YX]QVZz?{>h!  4F]iiymTg2 nVBx7n[P5n2T6kh}b/g֯][ݢń>:W\~q&|4󊫯\\^^M2|FUx=~ uG3oϽQĵ8<3L'|sz/mjɗ 4|\|F.9jyhܱ.ED\mXyts{q sG?);v4!1>'ĶgJ х˴XjsՄG" ƆD]_F{dpk8LMam+V xXhN\\ 1ũ u$.>`*ǞdϔRf6bAVu$ L俯bdwnI_N p)g,"97!r8 .gSf2.Q8)ׅXEK9TmIZ8,R"L+g^LojԾb% (Wc|\jZo|4m .s.AQ(+/+drр[ۮnחȘ3g>OO{gߋW=;W"wـ9XX,+ۖtN[w0XuQЗV j1 m_:iܛ{}9B@[zO`Sֿ_XDz]wopϾVvWK=ė&FH)Hpd9& 5S'?RcQ^hWЙgeL*nGb9Yna@Lm%-SO6 .A(tEO~h_h,&vfEʯn4646p(>Cmisj܎a)YQbM`Gqj&p3m楑k+E>v3,WܣG]:b^{]}=;  jhHK 6D 1g((T0:GJVfdxSt-IT2`XMkEl&[KZ #հ Nيr ̘)BԑB}¼0$;2rxj n)u(dht(<߯ڥ3ә̘ j>پ>{9ut:?؇Q\kc{@3fJ`c0bvG IzO-g2x nnyGM@W_ov ~Eyy[Q~=w‡M?ݫݿ'^C/-vPLB! WZ椅n/ai 1(ؼ#  w-RXCSS?#3>ؗOB}hCyAhbaG7w֔K6wOo'qxU{Fn  ^Bűh g-S-, [GMVVZ:^MOUŎTr0^t$.78ݿrz}Bݫ'`(@zPǿ?᪡UUUiG2YbM=!)r^ yTúoTo{'Mu3E~R Q{Â_G%'VJx+2s.ad,*5C2^Dk<}rQ$TboRS,yPXV-p@ m3l`P,4M$@ *vnTVR*ýDEj9NY0+RYVB_/sݺuCnYOqd9b/}gĵk"zGç{ܣf@,O[po?cw3g16Lʧ+@)(g2 zIS̳$3-SO]ޗ+aa,X\_eCe(:7@W/~-'z)X1nl mQ bBa<;Q<5&'@ 1 ʺ&Cǩ>*C'prs9V\XhEy9vTWAukśC,p{Ï1Ӻ=U,wκhj-;^zf`H%CQ/^P=C!ҴŠ|IoU OBú5?PXr^uG3yz򐌋a y1TܔSR0s$n>ߣn{K$Vwu;ZNX|`H сUT!Ɖ|֌ܘq慧>s xF/O]6<9KR%CAo wb؝eMsy`HVw?-s}e _;SϞzN9 $p>Xn݃>!pb:iO+z)Wy 0ccC_G`&R䑢(.3(ٮC3RXMX87?S1Gq?<G[vm[t1zqzic~nʕ1lEa2@aĘ̒!f'ܒ$}׍MO(ElvblD+ɰOMMMM 85/~y ^fv)N{9}Y:>6.ڧ K>}F޲{e'x2C4iVɸ Y^&B a2?3rO)(b33vl]&Bk\64 F_cFYLm^V2SWaꨎ=tj_nT>)xB?/n9]p.c}=a$ŚŪk{_ _n"@愗5'  n>mWHmgZ~j. z1-dNwYT4 SmVl\6{Dʍq1qm3fyX"wAjNCqN(5.m yөN%ǯ|f+54n/.(S3 /4&$wLԽmڣQ>aϻ((cf m@ w\)Q}ܢxY{<޷xy쉧O;9 Gdus΄7|t, bL5yRv)L= Tj$X>UJ@SGӠf!avW¼VU]T%eEcأ9AOSVw#8)=Ӥz4w%~爫bWF +½da/>PSX&Eo|Y hlفSĞ[Y^L[۞f㶻]3M3f[|Os^{HAn[o=k7Do%Z͙7ڌExZ$5Cczu`.DTs!q@I* ם | 1K33(f)eA܌QaD䊇v=wz|+u3i"7jrT &KHf0JJ!+koCjݐZ:xwO!9?͙0͂glAdq[5i06ow_jVc7i IH ,6e2%t`<=yS=q Cԗ2+6g9i^.ÙaN kj*񞢈eM#èT \ʸO>O129f;)Al* SPt6mF ]`Dzʠ5FӉnl0OQ JѼP7,a m5f#dLHy 4zE^v]h誫jPjJV2T1eBYGb]wk`i-9 k3Ca//Ҵk(׺'"gT%WNGeF%Z6k!=imf-'e }ČNzp*}iWА\ o|LHAqzȴ?h>[t_>e|olhhh!/?3mJѷ^t0:pVI{}w_ʧ>w}LB8xV@/O7\~/w聁!ObIJ 2'dJBԥMk$GF/\hy+=`MיDz Tl/|=2˗;҅4a&ᚢ6 K&dKSsVnUܔc!l80sV[UF,r<n^d,{ /rolWIMq[ע bW-B Tl2AWO&'ݎUL嫡xSo_Y(̄g3 rg{0F*`6E؀]0הvn藼]~dʕ Ѷm;{t5Æ%)gw\h_[{N6]> ]b+9@NgK.ܡ/ >3Q/!$ `#?!@7-轀"@Mlĕnn/ ǂ' yGy|%K;_S]?+\a l0;(IjHvm/IŢ] %*v! c) 9gY(#N!T9vfQV%/-|5d\"6`V4()*P'Y;q$gFvgk͙ضӰΌ\BsD^x#%I|JY 1h&R}l.L3*6I0hJڒ:٣WGsb75 x.A7)QE/%ZFj :9ؑE}]@Q1k߁QR.q9?aX?FE4g8<*Mddy 3ssoVu j>2C'`^QOUf` #~2nfb<\Y  pڰN ZU*7 IN7]w%3j ˗=mƗQE{9Ï>L7A,0X@D_{k' hQƎ V# Eu(< bAABN"aL,bS:iK.NZͦp۫f)݅3xm5-6\txpHrZX3.O>"':C̦4׌_\\7p]8{5Zl<Ou$6?BBywh0GM   &Dи8 q/p((.x#M9D(E\߸@^ze8lx1zsgFk=ŤO>[޳"wx;›P6# 6L" 5 wBo3hl&P {<ϖ tT^R/H`pR5usaTU; +kU¹$ǰz7YrxFx8WOUI/3sR"47P(z`w|31TX=ϤY/NW85U*7,N?/^WG=j&&y"-j#Y̆f͇YnW0FK8 C:fiv{B rmhtz7T2LV1J=ɦҐVT  5 s+&x)1JξG1l5|ܜ9mgV*Uhb)6ilZ5qFdX.]炇 rׅB9{^YQ ,4F;qhá't2:1G:&Tw/pw=#4o$!OaTl.Gb/hLZl"AX>4s4,O)nKKB t xv{TH[֠* Hmx`-dx Gr; 7#y S\%P1"LI=2˸@(Jt8lG [ p{ ݱdd'HIBcX U)huEj]w7  t]yM6C}mO>rҿ]r%UpωGb" X+bgF`kMح``HUdߑ؅(t9=!2jҊ6sMvnu޲c9 QxN{E~w3y\ߣ}Qxχ( LHz:h/D= ~%-FYEu:׍ Q)b0"k:R8qמˉ˼ы '6̏rTYtΓGGpxrOj./-&⛂̴̧4ҳݿ̛Q_xpjjL=D̒0)hȧ_ X &8VW_K^a\Zxgvg]6N/:G׀" X<qwoREy)/Xg`~1b( p)h0B B!Q6>էJOHdRvkOB\FAe s)}pt:O%---eZ l_ќ` bv3sLKaCo$ʿ)!9_u. Fo'P2Jte+%n_$X\6IwxN`l@gѳs5 YS.EVsAitlm2߽|ia cee5v>bkz8t{6g';o7#'nݺ+V}C,|D|eN`iE +!~dF AN! Iyml,ɫ^n"`C<"!)IYA78-I-pˏo25Qg|خC8gG>ƋOô}BIߗann6W^wwg~T/9gA {{W<,@tܬb|W^V)<ᇎ펆Fyè|5%۷`UUURJkF\*0@@{c4:ŊR^|+\-.ܾHꩲv5EX`EA<#N 6uN(ŧL3%RTw$PpslLbߣo$C| ,x`Id0bN.nNan\/DF#S<$3rH"3_YȸQX3-^+F:c4ѡU1lrȰq~w{ nח'ܘq(eyyCwqz'*'(8*eXSC&6i]:31mt먑ѝ 䜳2qҧ25ď mݪ >ҫsO4Gm6n!Z:@^P/O;<ѫMSlߦ!*z Sh \< *X|'[ªƣ!0CV, P /Ċ/lHCw8t3?魬BtUu~IT>uT$t xS}Y*|UyLP82@%'#ioX`S23r]#1[YsAmpN9 GCoDŚھ}-߱Ck<7ĤtΦn"ko?Afu}_Θ9 s |=*Ѱi)С Jq„o{^SSY=6+gr^bis%eO-@r -H˒>-\2Ռ$ ?tR+Wib]j"Z0m"_ʾc 14T|`}$idY\yhW CϤtr{Jue⒙4Cp}f& }"r<`ՈrN@.ET6Ṷ[2%ctdFL F]GO%#&Ȓ&4p90ظNO%>7h|f<}64L,~" & VɲCQ : lйf.8U=$_*k`zB]2 6@Zݐh=ڈm4l;'Fw|l]Lb \Ot7\ɼde[C_f=~=i[?P,ק.Dxg,# 7J AC̟IS[LLB+St.$"e 09R)E@eYֹ$: Iߦ|zBAauҗڼʒȌ"|D mX"liuTmJ&<j2Dg\XLp!Fjԑ_TPpOs?Β\GΧNX $ظJHRI= )h})(6 b4`"bNLӦLͳ=# <Q(q VBQ3I#`BMޡyXh#%)S7t=MG)LcCZaQD*$sĔ]_{E4ƙ*{K%"8{knPНwhʤ{rO>ڧ~<$@:y^{-5ՂS:` x.]9kH2P o>Xw0+酐ôy6'>_i SQ!ĐsaDT;źoX(9EU :<%6A#~&@,Si蚌@_`Do)rH XEDמ<6מPh3`ZFvLHAx^'Q0 ꙗ>nI۝wqϿ&֮]3wЌ/z?ugcC}w༳*Gu/`h^~Y]KFEyyı'%ʤO&O9 u%Zw6m6(S|G|z邂+j?p9+V<⟇^qpeۭ:ߧ}(k>As Ƒ@t_h:xs b^%N>I_IwY~{BSɱռw'l޻xW)-81\XS$YxA8WiWe% \\*z]"@ )+< A2&2!%fRP E%iЋ(*JXCfxF\}Ͻ}jݳK '9%bvC/hQwFw9gv΅}|ťîr0+9]vQ[G}{8{w=//<j:zwڥ3⨻\Fw٠g]x~Ǐ94@\zݶ$Qa~w=v:cou FWL~uWt|)K>ؗ;o/,1{/?_3 \2K=>Uiib[vkmy!W^۾p Yn۶MN_x^/zvCGw*tբBxWQTcmo9|/CcwbV-:A [|];VnjUڪk/g} h]}#ĵEU?nK.J1WyƼ5}P <@,;>rI^o/e=xtbXZ6TS+7՚Јݵ/um=u`72pJ玩 -r 9PS s:-05k3H_h y򱠶ޅkʥauq&d WXϤM$E ,VhJN t? @W665XM ZG*YUeeRر.N* m&nQpNw;֬_kMEkR[rN a,+Ɗat?,Ofw2<4k2ݰOc`'0_ 0n*)::x7' ոP"d)AxȊW^X"n)-xTcoqا _|_~YҚ=g^V-v򗅋=veO1l=kt%%V~`ի(tkHHw' 8'Mt)Kys<l# N$AW^AMMq. JѱO(Ϙ(0#t`儀x[n e=N`U2 /䝙!p@ȿ BÊʺNfFx35it∋t`Zo R Kuydʏ?βqd1KAP̅t]%S>J%( (f\2<Ą$?Trw"6Gnj'vӾfPD۶۴mF\蝷ޟ8w!wwaЅoU?43׶{w9gyGf<Ϧ|<2o.[v)'uh_ cM7i_[{I7d}Ŋ .i/:P7iۖC:`鲿֮[wO_^]v~p9iNl3o~cO^|'.WO.X)%yjB9K[]^RF%*xаz4 d{U^֊Jr]kYu°#X_D(laJlIe2fxG)#{rIx}H,Pه ;,T݉j^O'Ow ]LY1[<C>oZߛ%~K]>3kgW03y!@{镕~x89{rͰ!.>܋unI H|~,XtCna nQU5}W?>nmCcO> RlVZxAQn1yxЋ̝cA~ߟgM @ {񕬮?Rm#Rn󞛯z5}ŀxȀ< ?4bؐUUU3O=,>l嘅 CgYjkӜNoN׳~YhLLcO}cK`B@YKKKW+81v:!όhQ"(ir`DKv O/-ϟ$#kWJVw+uUKyMR|oV' :YP`Ƣs'fY1Vμi2^G )ℸ-֟f2I T DB3svobU`|s7U8^~h HJX Y [~%0k3?v#QǞx |͚o gS>̭O<>؏ufP\#5"iRXbh_PD XS(! 0,9YE@`D8@3-}Aw+`Q"l'`  2cwt\.֢ :9U*V 7M\̛IOu q.jS5emdZ+G2>(ʌKDk wV^͗cFӗ{*d q?L_hG4k.(  >>OQXliqnLi-1KVؕ8;CT-^'-_{O_mi&խ\le{ 55//Xnu۷]~Dikk-//_-;m2.HCچu@jkۭ[DEn-U .H- IoyGFIee%ຝؼ)ҥ@ንuccr.o6A U& (ަt %DC&f>f 16ty  L#ޯ)YQ2_{$C_wfv#-eo+JǞN{i%/ꤨqzNi AΪٔMq$^E#tUU7 \1<.b JdQ!ĘKwz2ffAMX$89A(x螠|xBOLHA& s0Ijjڶn HTEE=Vj@&^vB/a}ojQL뵷6bfsUL/;(hҭ/kL~kۤJ|ˆP,xpiIɒ?7ݴ!IF8*`G%%%MsUW7t$p?65MY ^}}}֭F֭\/lM&z='Ϲpݺ|Ƿu+[{Jƚ5kKe9$PYYQiH& TL^Z֡}{EU寿T_k"s"R}4m69ʜ\ blYSYUi% O;vڌ"VY55հ^@p7M7u̷rs-XXyr = QL:6)M Y>"¶| u( o~#ֶy M5}:$|1s2bYβO͚ 袍]7|dnTU&HaJ_^z=qI~05]E1X=|8i LLA% AGMZZ-F:h,^[l4zeRpq5?gPu#k-zivsҥa-X%`7LdtqA\;!Lmj}41k[Kad@tSz_ &sݩj6Mr@*+?Y=Uw] +WKJRȁ+WXn5u}6_+W3W\QUYgZX744EA*@&a ?@*dɟp?ۦFcyz*Ph |]] =*Yg3=P;mZ4BS,4wg(t: mZm"Si٪j9?W?{(GQ-{@*E:*HH )Hh(" TiJ A$$ D^ݹٽݻ{-1>v|7ey{ dيb,!ˆ}9s^f*ePːV]$4&kèDK,MiN֔s윹s[[[Iј֊j#I91lPpkY7ffU-aa_yrX t7%sSEM1]MGKt)3b?e.RU 53؟s }c J#N`1b<_¾0&l#7쥰$\Dy12H䭮ْ=uy/UӖH' AuR]IѶS*5bzV`FwO*TEʟtlgB7݆܆W>jLebU *}cLW-R|qm3|-R6٧4(ūYԪϳz G(P/-ɘ^MY; QITho-Փ tMBkz}dn]V}HLJ+bmh*f[eX~-&/65 Ɉu(^eKP,ÆrK ըg~IJw!-=Ӳ>|A.c{Butt4,V Fq͛A{ !X,ޏ 5AK$$C9,rPLXdhoyԄ!y5xFɫI|MU}x6E4bo/^ VI%t|̦ phS)?o_@lwG9LU %a|:'Vf(s|XE%%Hgw_?[,%_zDEA2 cBvŨ&UP+&,US6MkjN2]ՀzW-ٖKf&TLbTX"?f} &bY]]u0c/x|.v(_$5?.y'MGX7dhXK)] ACUMMIUXWR^~IugĥqBu$+7u*L|Z!Q]3i:0k jöm&TSfs3/Hk >Mz5={iD+TƉE J-bNߖ%iWkPv"Z&B3mS|O2Aš+u,ݑFʘY14&Ryڎ96"rg2Nށ;7?b3d`%0++PQC7q4%mśwl*@j/9E5T%tٌ# >UzCEQ!YEҷC R-@>Jn*-^-: +BlVQq#\b/qRH*B9Ddc3 *4J1yTk7>yt -!r}u,DѶL!2-j&:KuVKbP*fe7c\9 iTg>|13#G^"m):-]FHT5WEC,]Z[Z4PYO]J+%Rfj/[" yaZ)dt{{GT(Yho~XHsƆOCb2 ${nb JI7!hSGהS}1QC#j:i MU?+8_5ϋ^jZ{] 6%>YT>Nۆk4O0tJ$ndbr7bV} {] J8@Yރ:AH[h[Y_f  ~de\ild.((9:6l.j ]&ۢZ+#"g*+lp"F~҈FZ|dži 22$0L"R^&̨ 6 3J%Wl1Gl+ɭP>< 'lVKCi #KR|' 2=6]>QkЭhAB0 / =J`J4$P s\ԌN ~ms)y B"4C޻ꚹ+je*TOE w q Ja:r}_IdAD5\T q^X)'k&ž+K*̒tlǃ)Qى-ۍ IB?SPZGŽ%/|FTK Z]  Zsٖy ꅬǤN_.#-Vv>%IȂ&NEiKd'ëjN:}gy>D' rG)3uL(>/JR]B\6[tBdXZb$R`m/Kgw7١:+R9E(Ϡ7-E_Ė%u/TY. @5hmz5cT{YN'U?#4X{Lӗ;γe"S^)QjDNy얰 ֙"&[OMA2l< &^Ij}/xz)dxjʰ*;qg [S(Շ\Dg ;H~.}tB݊v}W5| UKX WK:씦:#i SQ٦dlحӼZrxt&8)("sy1^UڭY[lJ<˵֞-A R ,>29>|RŮ'7#f3RԐRu g@|)j \V%sMZgkJIj [D]ܪ`D F{6LWئ|Knn’]j\u߂A!G2j`!kCӛ + #'mcpH~y3* G! [NK~5\6Ө6K9[!F `t.(Bb l*h1-w'xO>Ŷ% ^Ԩ'82ԍE=B@uB-CtmrM͚Kآ;ri"Emgq#{Kh2' mE=lzBRM>.d=`A& H,xʕpd*D,2YNsMVaږuPlqrTdJiƳ4[KtR4$XI7|[yUI~Q5') $2]|]Ou?6R#vXsRjuMw3JƫTM{J8("=)bU͗ MVbiK} _қ E/fzce tс]ڧv4g+uaUݨ)v SخTmFpO(9%xboGK*Vp˗rCUiNBzш$ jǯW.-w$ (YחU}om;Gp|oUD1.S_M9%ڟqD+.- &wF͔̔|bWz\KXy:K/IaL"a>s}lTF*4(\\ W2ř5]/'HnÕUIcɳٗ`LկEy'H-ۮP%FLaXachUe6 `lwo؞(R QۨhB+K iޓl_aB szOh4% 7B8>4(RpNy $`rX L'X#wg"}_3ß)ML(ĺ$=L*Aa{֍K;v07XTu^Ozw>[Dfic%1 ޫ!Qr5gMXY0qgOW ~"_[4Y3')&y)iLl]rǜ*6)\LK[DޒsUm jZA1]P铣r[(j j>t,5fF@&НZ *qᝍ;?%5RA` 95Fd4W|:tLOt[AI~sp< LyII低]U|elF#N1%H: _D?_/L͸"Ghzzx`m:{GzDHG0T G5ѿdX7Q, IpdL0Ұ}b՟ B_h GǗaw !}~4A:KI3L4fk(v5:yɒj-!+Ͳi]6HdҎ=웢,07"0żkT&3-ᲬАw۰vӂ QW!T*eC6 ب}B7Vhw7Mz0zuՠ_Lm0u^JOM("{z$&ؒGK%فQSZz}Bѧ`Uc*{ꈊdm`U^0k  T)on:%^iy* Bt3Tef~Sl8Fօz Ɇ򺪑G2V"6H ]ꆹ4<}in:ywh$pvҹ]mEyQIuĽBMl>֜fdA>=ZHl_U5Mztd]sCEQ]dT>x0"g#'5ػUؗTUJ_F<1>Iڬx%2; Yv_8ё3NUEve`Uv0LU6>̖TIS86#]!Xia7Ktzj: D#LT/EosKuw'Ve8bQ&dz6ɓl*RՍ4B*Ul- {oi܈cINYYv<"r~ܭU٬%UUg$((;bqК-]skh.&MF;4d]k_&SwA|쥧{UHQ38913_ UXՐM`ިKth-f,ʼ!ԩdkfTz8vQJUuva?ݡ[>k$4>= .rib."T~/|gn7YtI$Sk7-52I&SvJ6hOtI2Цrk1 Q旾 VM3Uٖ i*)+(hTy6ڪݴ A wÒ[%s,w 7C|.nAu?4ͩnT+E7]ufinMդY_g"%SR%yg.wS4A!=qCݺ1?Fekm92d)gtKh<"q}?O\$Xv Q}LlM"l4/x:mJv3Y-FdGeR,&oIX)7ψߐԳ%lob;?7ɲAŧVm~oeuRӭgBq?50vTH OmqDu" ԴTF8mXhdõy1٬dHۊ8N26b;ʧjXG;0 x󧸺vk fZWw1Ͱ>}L`}R7RṖiN1;x U* JJQr{Ic>($ ?ؤEZ2a%Il2it 6؏&#亽]3I)q%{#J ;8>,ƾiDmYжYb7_ڐ XTxz _#WZ}HՄ9EgKo 3;;#)F'U.O?#l]GYWjy"Ҝ*jˈ+(μ60_[y|:eGzN" }_zXi kX2jJMKOgg`3 }e+V=K!vdwj~+mmWX̡_<)?vʾّ`W0 uŤoPKD1iy|Ih$=Jm# I;>nz^6#DJjBT_DVXy@͢f|a4%^>HT34w%0Ik=;v^ FMHm!|Wp1|Ut*fIPKxckWoWyRW Ys3[aAAiԽ}oޫf"(@>a4 Kamt'ʨ k {   ` lDngAAA/ Jp AAAN[NPAAA>¦ְ"   HbB5h5rAAA$bU(   t귅D   HRbꓜh EAAA#lP[/A/>{1N8y>?Ǎm>/gZsVAAA%j d~wzc;Kot/!sx'cwB<\"   · EIadvoomSG?~uW\}-H[n~ 6 AAAd@AWB3a:?_|#FR7Ù|L}<\z-A2džapnմ3~gbAJ9{@~m5KiBDp2,LAAAZ' yԖ<3ǎ]߳?_Ə;쇇Θ9~j[tϗi3ei$(w&   ȀB8 A+Kz7[.J~<|8;u!=;ovmαGywpQonDzDAAPy5^a j#=ֶ\>7f1__#G EAAA3z\.wvvTnkkkii< EAAAjkkKU(   ~'E eXeP`]E   BAAA#eGl&    ᓜh EAAAT   H߁*AAA;V૥AAXLXeTeف'IKQtF-q?M Y %ըϨ/:5א=%!d\.?,˲::l1$ zV  [r(c4ɝ$3A$ATe,˶`rP"  j#kLMznJgDl_"+ޒ18$h ] J5ifd EA $ҟMzbok̕O6&A0ץ?#VŰ-0t:RĎ5Mkiic.KMO`P4 #  X*FYM ")+l> M҈_ƒe-ӲS)T*J==% bsnbTʶMpG8͡l4R3/p ]ѼQrh EADhmC/6Ө 14Scq :@-YR UhRTZY& g!6UU}E2UMYYpS,wpP(T'ɂ ,=P{pS*AAd@"307Ƙ!deI6AXj4u]gViLFV*JAaYM R<2!*F!twq]"PAAleڰνk5 IkkAChl$'k@ѨDZ IK.̘9~8xw}WvZ_ŋ}k `Ԩ3?]3؉foI/'{GyB,|뮺whoomkl)E  C劑eٰ2@zRfdDn*0M Դ$Ryf%-rHMp t:Tj?zQ]m/_,qAdOu-Ieei:EKdMFj=r.4b/uv:f3&[ud.#u ۶(h$hʥ;wpBWuꄆ :y2S3Îpʋ/򫯼*(jTvfJB[?f̘b?AA7MS4J 0*tZ!v==%O[ZpD̖fDG,]7\)&Al Bv&bsJmhjaeP}G!T"*Jfz睷`5aQ+W^y_}fɒ% v}:{Ԩa-//QL4N{+n'lǍN9 ח_K|T:_?+.d3 ~A= 2l1SѩJ8"WqoncN3u 7 F=|OAsژ19_`T*oaX|tƙR)7håKdٷgkQeAALyڠ- m آDlZ kIP|ִbRB-g#g2ip]#d:ZhZ0,\,1;*_U#Mk*Y&?mϚp& IO4͆چi)ݿQTpl%y Y[b}@Kr ]w*Pk7γ:Oxr\~ɒu?z=_G4~{_'}]n˾{ќ9*7K/=|^ gwaǿ͙֛oo;n^?:R$~E4_p9R)#z{ScD)|lTՁ4i>_KYtuwϬY{촃B c ^Bcm0 2`]@e3C((r`k*T@DtO038֙^ ΐqw]"֭f t]JJ!19li5,s$Al6S9B!ohQ&;!B> ,.J ё=}kz)}AKs&s_X\BW3vl۰a+/_hѫti^[oSOҳlR) b{GH1hl+B&DC:;{,7# HL&öv}٧( WW}_7o;kߵ^\p/'*dw?k*3Wv0HT5i^}5ğG :pǂ }upt4= ҿ@)u:XT')1\2Ihh&(R:S24ic`.BSc4dJt$kP@ߵt" X mT`:]ZINBg)\2/P MSl"y߱ Iv3̡% 7Z,PA 4i2Swq;;Ͷm'Z8X!u3:.4u]T"NOED;Maj\hiuQ8s=w]^Əb)'W]uU>@AAV@jkiKjdQ" T:U5jM+R> J&"6UpGrl*)X"-rt pOidR2~jId%T:ŕ%m{d]R|'6E+/:C$gP> G^ez)ژ1-z\s5t;q΅O>Ϝvg5\6o޼I'ϝ 4Az#UOs*US>ӭw-Gue~g_'paAA'D!f5W*U 3CYWg&ԩrTnm%m.e m acAz֘Fdgf|cejGGN ٹWjtH5A-q|W!C0TTo PP\l<`^Ź[&ۊ~s`;뼹nkoyvg٪tǝwaW7p5>L%Ma&-ɸ/.沣F;U., NJ3ۖ=~R.3k wwzBuaG'= 2 k uvv]X 9h@Rd䲪!Z8tF-{P[,Ɋ̤ɐc٥`\@tL\VAvtt@.V*UjU3鴙 ʧSL;`2HUqM?T( |{3n%ih2mVs"eyKPePY cnQ 'SOrNˮZʼnN{,^igN6'?gNn6eʉZwpכp0evmHUs}?A/[Ν 5nw {7  Jt?TN ͳ'ϲp}-lRT1 劑1|.cRu}]sSLM[8"2*dp.͒Ւ|e,/Vu%{Ҕ--f|[(nvmXсal2.;BU|0aĖ.s4M e&㶶7\SO-\ m6g|cCi#O=~_&SovuԨOy'_嗠8軧~F6;SISx_s>93Ͳݨ{#UM3ˮ+=t:w<'!︓9 =\AصĀgLmQ*SK:JRēpmEn<\)tAZ%L2@붴ABt]!,U㻳t3[bUr,H\vBP 2K6#u@d2l-bɖ3v嬖B2] 9 JB+^K? ~k6t'A߆aQR38gScM7}gfH'-Umذ2]wҤ6: 2ۆalfu2(%4Y'&I@BfTV рK(.btmV be ]=&]p݄`Z 2sNV9j@fB;jOOHQ^f*rLC SD{庖zqdam-pfن,-ȬeuHeRe@BDCY**Yß_xyGozAD Qn6K *']AK-g:ْE>MjU*RYN 3zg],W*L䤥)!pdtttA,rQtQK:RJ{g()a[gOQ:_VH> Q,\j4,|`pn>3bG'OY *5׾C~L 4cUhKP\ie3d.%_Ϗ'Ħp]bk8w~ ʏ+T܆jV{GJC085$"3E!mºGtuQM0bcY*J&n ],٪ 2Թ'JM/%0(ɆO%TȠgU/_6l .y.! HpBJYd,*HZ 3E 8u7:-=CX*6%T=׷DP@%l}lo+Ġ E=N 4v;.pE`hS=WAƕըbj8(MZZ boT\>kY!3JS E\M@2je!3|g5Ŷ2*AAd!O{>Ui$FҘ@@˕R꺖fRTggwŇj4f#&BAA$Qq#_p®y'WͦC-ap>?Q2^P7C`B?}kT}MKP"  `xO`蔇l$Lw Negؾ2jHf 7XHLQS?DgA  2;1ic}ڞO< #~!IrcQF"9{~y`T  )Y+T1idvhHa6z?kCU(  pԻ]B4Fs̡~'<Mg~)BAAUb~IDm&*AAdaZgJh{Q;ipښZpo#ˆ+JB'2   8A`1"#ꗈ$+ EAAADa  2iٺZJi؊kJ0өeYNV1J PT͙ 5X4*MVUղ4m]Wm* 膟q!@Dԗ"[Fp"VU' N**): \\ZP$)(7$;GM v,2 $PAAAN3zgRȥU=eTB!_*WrLggw[[KTije1UJ>!>Ց euKN2M[li]wN29' RS=B<"*d͌'@S$]WxRIRX"+t:PJ6T*LZ/D3iRJÂ``il0t= ÀTZ*PbcCR3:YA  2T[A@VwwWK02 7N ˍVʕt6 AAPre3MCdZHMo)OЍdq`UUTRNiXRUB+$hLem,Sb[ nՎ]R +5)bŁ  4\6ZТvٲJ%N\TXP}vĊEUd 8uMRMfFM:b#59o WK,y JHЙ3gP"   N>xM/PAA)TS H// :U(  B֖N HGG;~#rAAAG"  ,JyH#~8%@*W fE OVKRUYצq P0+vUΎ{/<7ܦpm ]I8zqWfRh$۪X< /tJW2JCcTʖi0F]I:kN(ipöCssJc6MC!{Ѧ"0-հ{kDl?lٲ;쥗^JR/˱cFI7N>.r{M8hJi- 91=`{Ѩ4i+-(MeUFm^_u!`N*u6Q4@Zs ȉ76%#A}`nhUMW٦2L"Iieo%LlJj: ΁$5-RU @ܯUe2Hy"hlQl!UЈ\^AOo2ҕR7{߫AUhd4LW~YA`}P C9O?Op 뭷<s/҈JF.]%:|;(!*P\fF"⋅ uG#;aqGUJƾ{#TA VժNڪׯFBUV?kZU:rL*ΙM>)ƎfRH-|.g)흝=$&5T~&U{/{ [DEsUw둝KMW\)jшlT0eLU,B?P i(CaD.{:z*~|͕Jc^{}wuy7̙s駿p.4i|NΜ9sȑH 7~_m8|ny;cz衉'2eBGA{wuWXz_,[o /_Ͽꪫ:;;AwyC=ki;A.Yeo+;:ς^3a^Xi%HfJ:'2VVAgQͼEQ<oD9 J񧯄:<wn|Pܽ4A|UDw~N:GfW_}.$z|mƩ3Piܔ`v۞x /p- 8V>߈8aFİHtGpQFEd|| -X^}gV[m5v܄`'@) ٔL {AU4:-1jՈdGѐH=Vg|;n>cp;N68ŒCfŒNqR J.)_| Els7*5X]s>fon٢Tej ,ZfMELmlXv\4˥39bAE$jw~u5Md 2W,1 2@vAU'{, U,@vՓ䯥x$BkI%vXMGgW۽q!*!vhǎ H\~n38]{AZtBͧЕ:}?~ţG~gwm74aX@tQGA"EWqCи>kS2n81w}9O5] "p|GB n d9^u(hw>lhh褅e&PnС׾uz0f,H+IDxXaYDKXDe꫋YKTuCG㏃{:{i~S3qHt!I N-Y䠃xHk3NH0jnٳ;nkk%}q erIQD{}y z T(K8le^ uǼX/!f1gJ B/KAS)pxaGX6l$xr]]] - iwwkdŞJThT6 |mF.W @J]g;#)9 P3R(.J_. iHB]#v׹sfwG;nxGyt}~x;{~gϵ^W93raэ)1J\HO:@ 0-ȿw*)a--d!-:tt/<Z[AYxf?]s ?9iiN%"Iy8Ԗ&~ A$4͡aPe qwya6i$@Bc>hOx⭷.%@|7d2ߣo^y#{P #F8C%\nZ^Nv/Dnd&ތ/ "E!'aJK/"̚YV!V]iiH* "'N^"([iY74u>o$}o7^"awO<⚶8eڌYgbVK'/{Gyņ.>vq erIQD{}ǛJDHP(+pG~]NgٰT*P{=N1U'AE8N2\g#G/Yxĵ,ˮP*W_Zt4vŲ4=ծy.^c/TjS_d9 |UE:lAV% E&vR(*]ȇJ[Nښ9;44*CT?MZ_g3g ϴi>6;<[mUXD.> gOJ(|̛7w>Y9rEW_1-cƌ j/"M_zM7E!'aJK/"̚YV!V]iiH*1J#([i+snhI|a{x{ ˳fZeU)f:ZI[X'rˁ[vq erIQD{}ǛJo 6:(ldo*{#A9Or Ct5z5+/(6^+liKN B =EAmt=Ԫ|kE:ShٮSc,DtF]d Y¶iܥqAi-[ }U:^=%6|sQqlBϝ3{') j;Э;FuKŞRwAֺ*d3am]}Nvw(k)2bЊ $,MVq̤;Ô)SN<$O ΢,a hKj.&JW# -UEնRP ~C/[)+zJu˳Nu}`\ %i"CTBu/JL&xIllNhH8vmܸq+,88㮿zhinN8!"{̙^^ve7pρ4 fctG+"M)/^Qq"¬y;jV%a'J7xgkf$c@i]Z>1obtUge&bVGDwgk!"'ʯHsl&=G)DVgx$-Œϸ+ˢ`oo0QqgŅ%ۯɂޘliO8veЇp[?wޞxᇙ !-J4Ͳ>bKNp4Ne L.ٱ´k gy(lS*H|hXsV˪A@P02zUq.(htjuٰzr'rb`a6\Kb ±aZF6fx\[uM=w?cfAjD|62/B-uueHY!ʨx[kטg FXȷz g\wH+!!P.k@1j-^116qoכ6w>ͮn](Q)ڄٳlYmׇ?ȣJK}?`UmE4Cҋ/̝3kѣWYkwV7^pIB\NTt HM2osozqEj^4VoR0sf1N[T?B7|NYwLږo||Μ퀤:n}ex\2J|6JQ.{znii*K,{`(7b}u#+pb&M[YE,5-]gNno1c;19j董V)+Uz4ܹoe ,Ovq[H'eY wfRlp]4u CTuQ{'t>lĈ"LiŬQR$,̙s')3q4#Ik#)qI#YaĬ%̉n('Ί'yoM5xaLtO:餻΃8nbxjH7;H<;CF 3&Fa{#oEy| B-`Bߙmڶ,j]V_}+.ÎHc oְwE9L!;:lس>R {\ve$[n[̥2*M>c{Gq9t),\@ g^m"//_x;݃ɧkLSLOIjmk?hӓ=nοf/^;BtuuwuO-K~ܳ /oFwG{:W 7_-~{]w*S\[Xˮ8`}`5~;HПw/f?:{z::Λ{wBO5xl,qmM~SGJu4`5ضcT[#vT*4X¾m@w篵Z]]]w:} '@kGodȄ]wuGzlIDQp.??!I,s9&L\뮏>htJDNfys=asYg1_q 0N֚BҢ0FIV:sWuL'HZ1'N\a7+N2';+&;n#,5|e3]>î5XCi_1 + I$(<RO?#{?ttt a ,S6N0x!bjK.5Q #ffeItCyawVLX|˯W/,I9v|w _qʤk3fǩWq^~堳 oSDz#ʰfqxošOAރ|֝4ySlF\_5KF5a*4"Lҋ/~'k^|Amm#>?_pͧ&=[-E' 9ֺM M%ƻn{n 1'NꪣGuvub;P=~s)5u䈑:;N{'k/((A!%;8y>l:w"/;8Y.8|EFBN)+ /;GβU|ٖ3_U gpz񑚬<*/)ra AA^!`dejU -b UEz?ɠ`|sjya>ˇPi@d?GQ౫. /\`L{,\x-7󥕘ٳLZwizDcǍg[_ڶ:x~/~`= P˧w)FŶJvMoeWq@σلlim6۲}V9|Ri[O=wޙ_r8svP{'8Zo]sU|7sO8xD_wHQdw; Kڅ(Qx"D(|x3*((gO?9D0T$1'vꩩ43˒tWx~^/w:Q-_p#\:sD֮Y\h;ܼ|&}Qi=95.+(]h(RI %SUA,~iUT)ɢOeI%#^bă'_|g)>6JH- 2P{,O?n~ :tDm =~V׳wAD6%ΰ??c[1AYO#L6mܡ}{Hf:$qwl߶SZx͛%Z;1*BfB֦}#8V8H' @u>QePaœ|B!_$njTIO5 z<0Ed9q.Uz -x7z E-]lO<񟧟5eA8`lWzjISݮCΝ"VlW#ќ}}{ҤipUGƛ<ŠTf<)MDҭ{w[tHףg4 oneGx`2xz<J ȶ?h,t$`8A0qʊ 8yill7n\޽p k۶fB֦qHPR ux/RRGƆG d7HwRn`  Hɶ3hN@AE YsŶh`P5΍pA2%Ir _*-hX}>Ex`ҁ64y:- % m'g|ae/,=՝;pDAѧQ-[u|O=+P8L ψNrrDRвe+*RQ 04N$(8%-BQPc.3,f,Y7Bqr )XJvnP>G:)Y ʬ󅅅?hU\;mɶ(4-iXݤh% | J#=`<]k\boN$ct{f(41 =lWVuq\_ =~emb/.yӇ7.)~N$5bo;v,^7i#Ļ?45*@ʇr,Xp(z]Q a5Z\VeCi0{=ٹD++mwwI{/O?z~=izP,ЍӧO筭0ѹ8>&pi PhUW]E 9s&7{aq6lĉt>w߲e˸lz^z*?szy{ޕNU ksGqS"4ÈX5E)vy̹'ؾ}.)D** OVL%jR囍_}d.=y}ݧ=Wq[FU9_ȣ4k=YUSS-̛E6$1D $EDԀOkݻo/zFٟ>\PXtҐYGs)S4sǎt++WS Dœp(͎W0~Yg?~>ќa0 T'ܲySN0[F[>bk='Ld:'r 5?2i 6`~ "ŵQ)(aMUX߯% ߿ ^]`;wڱ 2xaIOO<wŖ»ܹsȑTh^D_;w5\S/7\ goS$=;AK./--}W B?3f)(nVYjQ ߐ7&i]@j7n! BjI~ oiӆ,>HNc l<9\YY if͚5Ms)ye[~Eˡ^x;lK!uިQKVZ;G&d8.mcꫯh0gN6 9dppaQ^^+^zZW\ l01 @}MTtإ+@']4٧Unߚq̲8W -ZUV^KVA2ɥ+ETosGOB̹,DxR/ .濫|" $,(ԇdFC@Bϝ=1tV`X E/Y VC?d–{A:v:h. z2TnjP($ fE4`O7mԏ>mc,09eaKIh؆i!.bҢɟhO2Bh8,v@z+8p@1~8;v}G3^wuli`e@k|t=/Gy뭷cƌرc=\ gHwB2H@ԩSnBam9ϯY.LqqqUU(0 \XSII t2! `UW3 `<!4Vj [8=&Eŋf>[e^ٖ`闭7g; \ aaR꠷aƀUu~[Q7d}ufS[=ざr)Сȅ#}W'NO8 ӧO'0e?v9P_~/=9vi=mS>6úAa Jc9eX¹cġ.,++7qQ'RP+1k1bYM8dmw??qK&L$ 嚦dC%`X(!yK>;xh $5i89ܱzATPGmUe2VeN5+"撒޹Ҍfݺu˝ Ԕy&+[ tcaa%-ɱDRI*UP(: yPx@.\8zh8ی3`֭"$)5joьz 64ibkUacܩ \ 75_jՖ-[>I&+KxF{, Xl߾=8ms,,`QJpo]G̬pv@жm[kb1cC%k(p `zwB=2.ʶ}z~N*VZc$82!kն \bO?_ sV>B< Z/?pX A}>_=Liܟ8Sc3M ꫵnl2G"xi>vED JO./6Cz3[y g^-/oYV^fEYWO?v "[gmTUoB؞z?dٓ Z‰I;󬳁DDSx<* q.'$ES;9I!pqH-C;t2cA'A]B-M4.rEK'o8;lYMW/?ᕕ+L;a4o`0HcN6mU۶myaC9gdf.:aښv 5qyYŋx駗,Y6dN:r 'TE >Zخj" Y rʫaJQQ4h ,K. >#Hڵ9Yt}RWa "MtK >(M!Ck׎$4i=yM~8wNBhӂG^82!&pi-@3g„Yf Qƍ믿~]w{Ċkxgu]gMs~/=Ki;^FjtzZͼ~p瓏VWUm޼^RVޒDmq#pNNBIpvǟt_pE(Gf>0~Dook&\S.`" .Zm歛6vܹMav0 rFD^UtM[VBBzh}VQCNrC%I!YиbRM}.{Ec.?mܰam4=(kt.oHsM>[07`@˲2 #1jj6mݷD${և(+Cwͷ2$$Qׅ˦M0~ݺO7mFAՇv/)cm[ټu;6u>¡ $+a(D4OBg&jp"ȅ-pRrdx|n0@X( r߾}̙9;6B0q+**7n ǻw,zvk.ns=/d }"§Ou֦M_M ԩٳ_} ll[n wތٟ07o޻wo)Ƚp'8F}5$+8Һe7}U`⼐A q ;98e|RXXضm3<:9@a ׫W/XW$$LzzXqqXz 'ئqy״OE[(^}{fbO{Q\%LrH ?ļE2dBb2⸌T7U85cʃY"(R0t!ؾCΝ;THyزG:a>, R3_-Jդ&$&eEj @IEMDBD_}AQZ`M7o ӢbM,+oQVּiLdm={h(  z L%ve|EAOc t Dfp; `o -ԉi jM]\1zAj#'yA,Wcx4VKpVpicS`Ap, n8xWϛ7\;vŋI$8h iӦ=c]v Yg.Ud;~'|oo믿^Σ𒒒UVuY6l geeeCUWQQ5QV\9|˗Ϝ9΃ҥK8-[PV%,͛aӨQ|#f K?8iLYՐ2޹s=s9-Szv.˼Nłǩ|4ȷ=WToeN*(#V…jS DQQ1QPG5N9>ƣPuEa`t@*kjjj Du˚4e)(Af0&i[gGAzՌĄIYL(bĎW$$A"ɩ(/u enmarWAPRFwTP /*8N$?Tj_ lL  )5tTу_ 0 Hg J;rƌ-[iwI'?.3f̜9sȧoqYȚd5k֡Z[[;h x,Nr[bѢE>7h \@/z~mٳg_x>d?@0ؾ}{`5p &=''޽{CH$pL[r[8)zU:aФIX+;Szv.s.OQ<{ "s,A*RLk :?Vc&N(M$~+`h I  ÊX(<\֨_!8‚S6:{͚(ϱSc՞T2ȧnᥙkIp g 6oMI'@*٤$~YdIF_lTt(V~sgeUYBApq8*$^ OR"2jPJIێR-(K>-spP t"y줄{)i?g`iӦe"k^v N&ʹ|T^C0LBtrĈZW߾}7l`JO *m=αp|zb;؟G:?"Vgnv.;L?mUNMp.6=+SN?ir/gm`II*U8NUgMa@L HzX%Zev>ۤ^x*HE|ߞ.W}/REL\-SmMn׮]!5A0@XX 5Jt '٘ϝ:Vlޔ),,~KjJ$L9#޲9]lR:vTP6Lq*8Ijp-#Ib@FN VEa,hdo;~jEҟ~-I0FEիk"BmQ$Ʒi[-.KAi0ԢqEtOp, fnZ5U%MWTEՀkuJRJJ>}*2OfR{$:I) XRB0d-i`em4| "!E&x;1S-AaX,6|ԃ@I&s9r$< K[Nh@0< @hOh1w҄ _Y_J5c(({w> .rٷI,rC;B;ɀ^:b܇ѫO{.10jF< :y"gB("NAb5(O}, фٮ}NPAF`\6QzuK%_gl[aQNv饗rX j1XTcxfV'$ēxBÆȒ,i x*!()>Ʉ0,ۑUSMO$I1dCUP! PDSRDTnN7QM_ш±7Q*+JE*'"5bl}L Ş c/8  uQ+V߂xOqh3<3f7n\IIy7gΜ-Qo9f"`rb`ysW -arZ A:H;ۮTb!yY1:sMf3:sy1#ң+9 a::҂b(ʫ>&UUE8wŔKغ~.đ `纺Tc/%l)īGiZZ)ISPߎ%BA}>9}Rl8,eBD4`|~' )Kݓq) J97FuS-qIa JUb1) JMB;r^H o FY ]UԠ_vNlkk5]-՛P{4DŪ% i% CkI=uh<hQyjQOΎ&bxeI &im˴!v PC\􇙼;o(u9Th<4IMܕd&KzmMg_,IS)HϬ R'@a)' .%U+B3P/K AED/jeY]KN*}R4).!;Bu=sۧnZ zzg;|gGqއecw ;A6Cj6?G,w5\n/qGaE~ч},\H矿b ~2<&^3qɒ4m6i{8OMBn۶uu3UWkiZ ~ Lqf@}M>r?3r$|w&V]g_?*EI$?:9 S֬E$:s"xE .sAo/ҿ 7G}ԩ)m04/|~W8Rww6B' 9y%SFiW=KMS5.aoaoJQ" ᢱ& ﷆo1dS:ʿW$*=ts9k׮֭wlgceȱbZ¾yn͖5q V`iTBLsbĉp-]*˾ }re) bKNAZ 5Q%RkGGV}WAJVXҸzo]Aack蝛6ml7ilFvs 'R<̴fGH_|G0>AJZ{ÃҊ7t~g*+~SoƂr Eœ]%aY .zY ڮ]mۘc8E WJB-e*?9"5QPEyթj34q?D$N#O#f 7<ZGUgr/}=,Z~VX ?Ç߿Dcw o{{pH !&1s.T\&+mxe ժP@UʝּUC᪪oٓ>? D,5xLWx-RC=õ; [K/gRsQe`E" +)PP0裳fF}w&Skx?Ƞj>.`Mڷ?m;%cw^"oQVn-,,o5eҦMJ7G3juH^ K7]jT-?|ڵlV7eN׀!( Y_4&[2yI6_*Y+%8dhx4;v +//#?abJI׆B4MpI@0 l̐O>Ջod{Z-\XQa0SN3kbd uYۏ$QHDmcYwȳ%QtY[oxp8r#fx0(,(oI@A&FIUP&p l׉{)b0?qdS6gΝ#Gt^x@]˖-7n,@W^y*JKKK!/ 3 ݼEz;o_4xbY%D")u'ނP C&˂P[RzJ\6'5ƨJ 3j62[cd&_ b\Pǎ{}OA*-b^Na x׏u f͚E7%r Vp &L8C݋KlzDQa-0߆^" #oư.))-y7l!yU:uԻロa>-,5 b]ر#]3!kO:- VḵlG9Ѵ-.6l0v@as&J&v["ϯYAϛGu ]z5ܳ@*N=T{Wt,y$_laIfr.%s|nz8X8;CXK*{]vp0eBRVfN:?p iTʳzQhKS̜Ea`k ̊+]׿?B7md'U;>˺|՜RӔ&;&0r5G<~( B:vbա5zRUXOZ<+XIB*/}fNbO2,.=zd3;URjө/, D _|_vDZ #9{=|hs@הH6abK i<>t DvEaƇw9 e ?\u4NȰ?Xf@Z̮9y)o"n> Э[M"Ǎ5%T"ꫯZjE{e4*mժU[lqh۶{՟;Ge-*/m녵i+fxs-mȆ 4iBMo%͛7ߓJA+/֭{1cJ-Y[qF3!F/?~GEU'j<+VGq;񆿢"+ w5DfE5jJhO}.X-Ae}XKݾĚB|_.E_ @>SOZIk,e 9}U|*D22"rr?s,ܩ.|vk>TL琶RVfN=1:ûwI+kԤU ]mc&9Z\ݪTRV9{"`-pV󢔌EbHuU%!b w--2[6d☐H&Qa:oeH "DdkM!B Ts,|Ժ0 6E ?M˖.ͱp ͚Ӈٲy769Ӊ_eb@Dkk0$I e=蓣;H$QgEr6nE tcI4|,! J>(`['}tCxpX(,***7F^v-;|ڵ;Rӷnڴ)zswGuG]rE7͛rͷ!!v"Y-y!JaߩSٳgЁuTŔ)S8kʫ}vw! t嬣T#*~Æ K:M:E.u@v, ڷo9s_yէ&.7lгgO8?ˑ/wd>s|(Y aѬ'7~yHMoֶS:L:<VEBt֏Ʊlt MUE"2iZ"4 VYSeXPka*vSBZar?kmKڪ*!I<UQi)/Sݒ]&8> 1Zrx_(gCDV'6)=q)1WNrrY˖ҘN-[ٖI(?T34R & Ҧw>lsarؓ!Cِsg7ra8H-c3W0E쫩2,oećQ.j+5OBG|>Ol]t6 Wʒo 5d q_jFacǎ]x%Xp@6X1ystMW_}5=_RRjժ: VǏϱRxOOfO:͛7}(6E.]y%߆L6`I kz[[iYYСC#G,**&p>4x'Eģo:cA']0q|߿չ,]3زeԩS̫݅\cNu4ʗ$ X7.N7ھy8ȷp.[ڨN2:쳟x *W*NcH0MXiT,(dY(pŠ^ ).y?նG[6oo[ 8*556oydH\ڄ5-]RЂpAQq JŮ]T% ̶0,YB@A_Ph2 B:((OIBB5!&J"bxr┪theˀ.:ϥ?8?ɡq߽+ضLSAJ5U$agf޽'4oT"r ~ti[鈳E( I=In!M?/rQƵ:Qх p⪚HIymA_B;rCrm4P'-bFMOEbB U>_kBa܅a:on!M?x`֙;m:}笒8O S99MwÆ JT `9fe-tסC0\8um~B1\ZГ6p29ζ(V~#Fd+Smkt:&ȱǜFa4nڴiufLZ)0 v'P(V856gH.'#}/Rb]9ILPr*qt훇s|aYr5lmu>FU*;ߩ4`Vf9tb% /v$%&JJ"5f×M[O3]]{ǩYf >DYUUV~5f:X "`<- gwضm+%d±˅pŮg4)Ć xG6a 5exZk:Ucuʹ)N疻﷞g6X^2yJϞ=7kH&kj#6oxyr2V#^DnBv fͻo[Dnmzw,Ri UU ,#GzK&7-+OtGWJi2 o[Xe'RQmdȔ7Uc"Z(@@ R@bI%bj˨񑲗QlrFOڢѤIs92 a:cƌ߂xѬJ9ChݫsNP8cgؤs@+b"hQ߄9abyM]P0Ԯ]{6bNO6IUS 3Q%axP @/Yf{/r¤K&^2˙~Z`^j v> e׍MQUt!r q/;Jrm&7D@}ם[ g}3eiu{vjժ6z 6 t86͉CJ",|a4LGBƣDrKH4 t > ɽt!P5Nq?]*w%GIy Z2֔ļe/B~3f7Λ3g诈ң:jŊ[2^7'=xhpuVLojZR^/ \d9#F\1Z8(,*J21sn6qy9cI]nO;<4{uRFHQӮK.I(,/6R}ji/6%}'M~U&JDxeҞshVj֦ {4_*LO>?@6B>:i&9E_@|H0.m22\u}OZ‰k3τĵqժI: 5b8OR5 ͹ޭs*[nS.?4PPHR@N$%iA&zac0 D2 [VgQbǹJ'8Bc`ȫP ( CA\,rE<DzQ샷JCR36ʢa" SEtqaӞrX_`r,ԃ&GQ$3cZRӔ$t ( 4AE'N_n\\Ԣiڣ/~naKХS&ÿolݶeˎA]vmt"zKJ򍷁dEY `\"|[߼ Rد_AVm:=Ǔբ ~SwEN:/ß%#ϓz'b `pQueERQ#1h, J$WUƐ'幭asДaZA8 c`gɘ*Ye&|]mX,p;4 }g.DEAK%98_2>t}4oQfkT^G $,'OJ0I 'x iB].D*l3-E#j8>VD{  +i5o|PN+(֨";ښU+3/8d(AQa/ Ve"-P(R]l 1NjA(PȲlis5q8\ҢkyEUihVDfbzLh8,nqE7coSPGAJK*H@Iwj b"F`9ʡpH Q XQќ?x-vja8<5ki_5%3,lhu#&z2b eG%$KpJ(< !HzJG tZZڈkspo@ ͔1 `pߌ #FhZb8XpX(\`?ώ1D⋗,YҪUz#n wʔ)/;s0{x@믿~ՃA0$f> Bi@@%j&.np(7D/,WV VD.jT8Ʀ̟)Pأ IEA; %dj DԚ2ΪS`LC L 1, WQҫ`v@m@HbW5CӈZ}"H@ 夨" Á,Ox!0ƣ:6ydC[˪ Ss}S(Z dP"9+H$,.tLcߓe(4p8D>pZ_\]L0Y`a*ͳBQ2_tJ}wVxwDBV~31<|q,*Flz1$` D{&Jb@M$TM%e^ܨc.$eB݊qL긔.QQDE'aт0NF, 3x;iICI䡛P~4c6$GFUSICPUU%I~dWh+2IwcEX[W2v OxPNM <K(qO3jRQx{3JLc\Y(۱,@)']!QI*0;Ŗ̼H3bFZr×/_>so*ҥK8-[L:6mc=veUTT\uUW7ystMW_}u5Ã(˄դttsL1KRNIPIPS0lEK38"?8;2O  $iXGKeÈt hJE}+JT h*P,"%60EX)@5Lf'(j#.zBJɴZl0Wedwx; aTZ$ObϹ&$O.H Xw%7T5-,!Y"JQ>IȞVe)EEŻ{gsf=wf׋zz9{~l΅ O~!%kѐDs,4g 6lX.]:vx̙3vv޼ywqi>d޹sѣG3#V\bs=z+W _c@  ֦s/mU F,2H~qJ>׵&;պrI^yQĂpʥ>Zo/>zc?noRfT1 |P*5g'ofy|&{Z|M~B~"<*lTWiąS~GŢ˒fI1+JJ_uɉ.9 ,qj4*2IPʮNҌ#j>UV-@8Lㄐ6au"%Oa@U3.&DOA_ 'LNlc%X2 :k?3K+@ 2*8Y^$KDHAvvNMQ)7=- ,4 њ=&PrlmZ6 *b Uwx(38}X5=--NVM6,ŠG)}ݗbs'(cƌq͏@ Q(,ϫId9)tR bo&_2f}󃳉%Ȋ[wy{ng2;7f6au0oǁișV/ PO}iq <+G6+,V,n';k zort;e+յۋStxL Ub\kpRQirSe '/5T%։ q1'@t$CUSE4*~0q̢V"p# ()PR թfu( F:A[a~%K vⱉMJ#%%B]0-hHP ,pxqcMbZv)G"p6Ω)(2cTKt25ٲ#D>,lٲ'N󁂦@ †hL+TP2W]\z@.[?L%;FêaG(%PP8ޱ?KX hX|/Lzi*JD 0kv~KvyŁmj@ Ͼtgb ГZV,o1T7bu=)ԳluFɤL`|3v&ST!qpƮEr8:$PD!vdFbDۘakdCl=fv\gvfQ `\LIQUu_t8װڠax,x;v&g2Vf4f/`4:#{'@[Uz^~AH0ԐߗGiaP}tI~2">vPPf d' s}Gw^d:=@ S& $lO#І#ÙP8wwE@q<{%*|UՂܜ`ZzUj|^,zv RG <);7 [jE"p$׶=j&VUvc&@Dr+iP5A$v|#?UV#iIby9@PQđFȠA\ K0C-RKpҜƼPQ`*3bEOejpC堆䐩FcVJ8φc⒈2L}RvBMĔꉦ_7DY'prE\).#G__fAI>1TVE2셀!k yX)JNЂaZ̆'3E %z'JD;F;Q:jГ)^ 8-S' ^NT)Tҁ.iQ_B,Q}H^D LC!-R l ?h$ga@O| OQ."PP\~H6/s$[U Krhv)z79pFW5ixvdVP:UU0_O e096Ybԍ|$O-#֛-ȇQAYEN+( ِ ҨNbw !~vTR:<">¡g:!>Kj[ 5^&iu11&w: 5 T#!] 0!u_)(>]X4FT&"()JܽV`V喦)$r$YaJzs=W|ϟReʳMTXDÈK{&4uFA"d:}*kQM3kruF!YUflL%c,GMdН! ~`[ ]M|r]\)gy{iPpQ:m 4 E 85[J@GyEuH/sp7hFTs&"geP(??a-έhsPX3(P\C4 \~θ?AO6 M0 ]*7݄U IMKKs0sn}*F%P`\#K*:TVɲT5+⺏.jgj)*(jha0+#k葼 +UEfHQHOo9bWYp0Mf?2̏@>tv 2uK+ҫ-.1 E?-](?vQ'Du_dwi4,VMwEÈ&f!ꦺOUt5\ogaLS1= +J|D4@A%K\խ.N5)ߔM-eIeˌ2HL \?ZLRaDsYu:"RL13,}JJK ŵ3B@ N16*ӎ$jvT 8rpw0D _3dscױ]H=TZsm(e*biӿWTmr'smeou>շnTjCb pzj9?f\rs^~n΍:#M*U5;\vn$ eR~=Z{7hVr@:K`_#yj;"mRU&D ID_(h$_"?W*3==[aB?;*'d*Q%+I:!͖[)2%Cؔv[bL=;NH,5JijD*]".ezQJ,3Exĸł"#1`+3#1>@g@|XtO&.Q5d@ 6 :  X32JDfPO<~ |%2!+Pib,bYt8`*J4&҄D8nP5͵xr鼫pƈvg1v:|woX׷7=$>b_A1,XD0gz7?:CO$_0LI-/I™@b K)NX/V>_0*?;/'hGXQOde3dׅԥP\M O8aF  FTf80F~PNmLh Z0*̇|&qEJ[[(cuJf$O"u<ʲi)7EvlؚƸ,S-Nq4|*jg beh˧b*1gxj7t9ů# hYy ȼ;bÅڀ,@ Òyal} %EtnY8 g3@D2JCJ`g-l}7; ʄ3oaAܔ;P-w?ԔRjkĎv.~9dT@ĸ,fUb Bd&G rs"%^=yVpJT4s3KKNVed";&gS|,I Q =d!ad"lixAP=4xYꤺ i(EgD":1$j:u Qq&gji: Ikn_QXN:.l*JąA7r+PU3uRS+ѺvaHaJZ]S\{3/% 6U2%5uy-(4>ᢪDU@vI2i䆲 {B︾P0w4"9pMoaBA!! E (Qn Wd_9 Vi,lEʖ 3T2^UY0 ] \H%:L@jVZŬxFţ4*b°.94ڨٌc6 ?fj$+YH&N4sk~R GFoLvǚYbX<9ASE$MXNLK'!Μsӭ),L44jw#\QNYQ<eV21<AG5#XLTH+EAy8yc8za*ffd^/ ,HGфJ"@$q2GgL䙭3pR0o&"S\J2>&T銣<&;K,b[FtJ` ۧ&쬁Z3a0iupy24˻BBG=IY%>bJ9kPJjXb4I%TcX7_L{Q̓ U# rh$̘V~PEu=,~UI4tՄߒ%ww墬i E 8ȰL>E!FqICGm<碆)$Ę"Tʞ~jOݔv"L2& CZUl\Ls̏eCy #R>SaYRx%\gcI2cˠ94N8T1ɛ *b!%\iJɢRPU4-偡SNlHu&2Dy*-R]wΆEG7)B$ZpT4U!?;T}^d){,'Q% E5@Ja τ@ Q {!،{f_\Pq]qbfiDTJH4PX"ʖ]hLjI$K:8l ]"Tj SP&'NeHF!½2h=s&Peza{,P3b(*ΐlSGcY*5疸ra3V!.4&I>ْۚ0`.Jo|\YҪK!rPjND*4;dzhe4&)H-!+J,i23 =zM6p!4M#+GM|AVc3[3`(y/{3 E 8A"=8i1R(MHdND%,LM"LtQFL\tp*⺍ٰJ5B4Tv6$`7(t]8Ӗ {$j-J] 9Б:te~nLLXj^i'eα(hWYv5,'<7Ea >\ Y$o%1MkzA+љjͬZ!,"DW_MQP8bURebAU dŜ/)cOW-+pU^rëZ.&9ΪYXݜFd/crTwu3B@ ;g5l1P @OP^LdhJ.ĥm==ӖLW,(&O&N ML!knTdq3M`;OJ̻$%ylP\ B (2g6Qb$֙(֌0b@kZL"*0$1UpT9F1} hT:3$qK[^C ^uXjRMjLX$PQƢ:SÍ[2\ʙWf2Sà#5x-Ye kxIL=zħi/)] ]8=Y%]@ 66o(~ŧ 8)E31ʲGPa(͋.G&P~DHCiD.?PhdHAkaTDa]*F丣T)-^3化APU l%E Fxא;48W-MZ`X,0miYYi20" EJ~)n1Rp/җU$E8$av'kC!~"S@_23$-ը]")nkrI/?P'0٨n25'Ud^i7'x,(G-xI7>*LSfE"dij D"h-HB@ NKRr"jFvBxS4G)4r(sZ'Te}^UB`g _yf"KUH2c}H‘)#˺Eլ5c ,cBxBY\ʚFnE}F"тhLn%2J Bbf<حM944̭]]Ȇ/a KG|>,6M3k.$hLjcDJg0M_f)I Z@䴨FJKF RiބQPvE__J\}8QDcdqbE4Z_j8+EEV9O䢹G}|j @* P@ S Ɉ(O7Z1m>(Gbވ̯v晰_7)[3$omήKVN)iz|u 'St$x:FLk0|Q(ʻlHq *nƨ*eBTssK:?˔,w6Ip=T*E5jOQ*Vh榁h8HcdBMG "GNi'Yiꫳ+g*HLqi()UrF#sYu9' }"E FB?uB=PNn>pQ-@ TT+zH"P@ #%XrP&IU3"ʔ<"HUf>Ib&2Y>sDYe);$+a$@KS\DlQ2×Ȇ)l|xD].딈r G%emtL"DgzbH2'aVˡ,D,?/8-URQ@,Q3QhapJVRP6,^mUɤ"TbQIIrrS*];/&Yb}B^O%H-0DY+֫&5mF8W̻$EU>U.s&`s D4-ȏDs#0!hRbP@ S] 墌j:q"*}TRAlr3EcX qc@&Ք}֞^X !qK=?-a##E0M@\FBYp\.X #JP9{NX41'@vMl3`ՌZ,@ ̂U[q$w!Ucb!I Ik%v%z%Be (RrJT4G3- &6,D([qAeYE\W\.馏VjT8Q]dVN: h-ԕAP5XYW)/b,glaOWFjK\8Q_$h(0FOqNIiBF٬i&U&ވ,FjXW`Z:+L8l^8%r43z@Bn+]ZbklaL,$0 !$K s^ c1)j2@A|&P@ S -56$n]Hbkq'B2r`̪ìI|T5ⴎJ> .DDTJ.A MaB̢}0%WIw-b\Z.ܔxM^-UgTSG:>aΌ3;DM|WMY&" }NA bŶa>TňDbz$We/z\/Q$F(YTnӎYHdVy06G禝jO 0$F%Y c[r c"F 1-`HVY&NC.PSCFk~c OPy2W E ({_SQ 䫒@JD>И@QMQ+jN>rtu\Qr"xYf;ِzY(@ Dq0荏%TQ)yJWdD䥊L\ͽJ9_0?J֜Y!iDA)DȄc.$f\4rY0͸)xMXKő1Ҹ#ry +0&14fItqUXmIT ̓)vI-Be2 "Ⱥ-[O/%4EzcX^~AT7.}=C⩤eD3Z'ᴁq ݴMH 4KF45 E 8$nt?$9.%86mGR^yP5U5 ͇~S!K"JJ vry*^y$UxĴ1KW,(#e1#nlnY1QW6PjRM٦e'#Ir3&@U eKtɾ@D#p(.ULvM HD>f!kJАߗ[Ѣy Bm+G<[]wUrx\Ѳ 5'\i"TӝlՒFU%+=,.tE<Ȑ:co؁HAuڴ+%rP#ߟ B@ N%xPP'D4`p"%4i&\:uq1hXbeNDeGM>U*QOLw8,B mװU \ȈA\Z22i7X,ش4L߰$Q&,)ueL6KD,ĘI1K=;!Q%H K23L\8 !( %ꆩ+y'ʷL,) u%IV1z_RD1]*#+!~kEIKA"P:F_(*5,@ TF")QJ6DDᏩ%kP#lJR+c16hHH sdsm*Y~^%uAae_k7gGhNf/ʅB/<*q/32Z*YJ&$e&SN~*>NP$4NqVALhh,bZ](j UNi =oR:jUPH^> ԀEHi |ʆvr"j'P+W $2lveTQ?u=S2J^E*QHH ql <>N2-\ Q&BxY(@ ĩ j3K݊2;@QϖSP4$^0$K%DMrD(&ceWC k:9-׵g;ŠeɹԈsPPhh^A`PgӂX@2x~X :yLdvFʉ(P5bQo'fqhd:mI'^$1A>333Ti ˹yqT"31wE&r9&RU$Vǀ3tvl>+_Xǟ,JOO8-]#Goq? Y*-u`AAA˯u6]úK쳯lb׮?y5999kiZŊV/]g7s&:e뷏 Ͽx 3N&kѣZvy ~~luO>~e˔.=]sMɒ%'M78 {O '<tp[3)>G-֟ {^[J*;oʺm_~*.! PNBY8KAI)dg̳a# QK5[.F,ᒸÏ;.b$XZDbrȖjDCtXxl U"j#CAi`NK$V6}>5!,\q? 3.ʅxN%k"SJKu$ }>Ps|EsL.6=؆ (‚MYHO&dD-L۠m$SMuhpAh$+ 9&S.94Mi]LjHg( u⡾SS<ϖ5kr']xM\7M4m"g-kcz77\wmZί3ؐ]\"_7}Y ϲ&zq>sm{ VлΔTE Nx,rg5W965{ƴw|n9pOo1wvo޾]C_=zۚ~NvO21gy+}[Mdžs0i|($#gR MFD-X5<$ʐMuZRDLj(ޔS>*LJT*HtJ8ϊڒIZɰ`E)w@S&5NH؏ʦ]LTMO3R>2xG#HLnRDz)):‰(lTs̡a/<으I˖ f`GsG!Z 9Q+!I钱*:fk BKe<E ׵KUʹ(Eu5S#RP}@L˯"/5K>S8E NѵBek]`Ǘׯ)999kP2վ'|8jf4BqE '<|=˔.$p)Ǐӫȧ뼪U~3_} ܛ|=D,5~8ʆt OBI~1Spu/܋Z:^"2lBAÔ'cˑiR#DT7mMRS!N㻔 ʱxOb( d`iF\LFG OS8$蟬k T&4KVTLH6Q6dҽU0Q-89qNaOJNW\p᜜H+H4 s$H?G򶀮:7EN $rlkz`t=!dH&u- T75j!'r 5߂X¼|rR5ovH=>ՍRbP֪l7v~̞S-B=wKDYn-0SI(XMnkXϾ[>5iu[Yw>[RAў?"RyZAKٴo׶Ko6uK<*3/]728qJő %L5Lџ"0Se Xz`USv%Q,<'vlIM]YZ{ 2ML jFT\Adixj +]1SI2cEbS斈SY&eaD r5N9,Y)AfITePIRfWN~j"s0$'j"qj&Ti"=.ԯd"()rcE ظmEO1DDQg3jQC0"*0$3mvPm ʔ: %bّEY'v[^tbs[P1[LbQk D:b5Xl"6 h{k1q +&Ӌ~nHM["#y*xp|i&gDuEcFw\:Z5 %./բyzLMp첥3K{ DVkHU+ < VTF"qT vspPsqwϻ{ 5k\|yz9wc|zX)}x StmPm񧋫_tSK*?<.׿E894 ](&]:gAӞtKEkͶR-qc6~B }Oڭ?G5kx#?}:u|rO!%I`Ϲ*@MEVCuÒC*q",E$KF2"ݦF٣,Œ=16$qڙB#e$!+0t|nLw:}K|Jkx=&&:̕8===j(a{T yRs,A-ª:p%QY@Aa$GrJfe2¹y pŃ|ڴL]]ȾHFJ&܎ .KAs.9&ѣ"2,pn$JA WIMG:KQ P\ z.+;.x$,l^ze֣b}6m[4oH~^pBEsX? .^6rĵ4|'kP|iz.Ym_v~=$))bؼZEf8I"T>r΂ S]URl={&9gK*(GD"Q3T&”#G<ǟ曀4z@x#.%&_Nal$RP5p+[VCICˠNdiLNJIAUd"3 jsj1.2>|cTL4Xf[rPB/MQY&9++:̄kX"6nIJ/PY1Uf͍EpJCBPYH2$/vќH8 ^ْ)_^^^(d2BxRP:չ:?ĸT9&h47%|B4: iQ@VV RhVL3_陊oKcʆ+yHBE̞3]K,iKӥ7lܴ~MVVG /r`y}vK%d yMt^(߆/{tʜR~=?@LL}DŽT-Q<\٥c7xkI )ӥ@hP.JVmϖTP珈Eޣf}+&L:y'PfPnms[&?Oęj+r!R+KMϕB+,7lޑt`I0mŵGd=:iU"]Mei)33\Yjv-I*QUv:q\VTUf3P0༬U!~Jlk_)]Kb*yA4]8cdxYh9/2-xvZ%^R>b1&Lyq:Ky{{?;EgCk. (GgSމӺ% ltrrL'Zxc?Kolz ?|~ton@[~1t#~}˛o啧lIE{HY=j/2{N:,1+ 1* LF0idR3յ*<|I'KOu‰ޡnQ,43yW%J?d#O6a}-n%[.]S)^3 ؜=z4##15/;/??=1/??Lц g'ZTǂ߳wm; yt _׮b+;>ysb/6|)JԞ-)<R/{"^:l=zb}4jي@ 3{,8''V+4-wx4Z km`Kl~C+cRZ-q{w}o g:R$ǻl"sԶj'N ԕSq+]@ '5k\OwB@ Y(@ @ > E @ d@ ,@ a\eM5.Svi7x:"d@ 5/&qiQPYv4vGENxY(@ D1BͦSJ(%p˲YE*]ߵ-ut)m7PP@ N E (^`Ҥ#IO*ԭ#IݚIB)(8,@ 8”)IUK­B>$fQ4 E (""PR~j甓$"S B@ 揸>ڍ0>vL @ bYG[O/t@ B@ uW".qI=~X[@ N! E (FஆjTΒjvh+4eǷ%)@Z8=YM.  TA ?j6m7.|V?}%d E S ' E 8 Pg=_|%]7[IU\Q9т@ NE E (kˤkv#JGܺI U),W0 >myl)J3UG[^`wN'8+L}L=*9yxw۷o?nܸ{キ[W"4}Ub+Vӧ?pΘ1aÆ'B@ 提[ e쓶oe@AE)m‡r-7*,@ Xk׎?8ԄswD\fÇ<833sȐ!N^a֭u9so+բEgycǎ7tK F9k֬3f^:Y7ns=cƌꪫR C*Uʣ٢s #;vlΝvZh=&yclHZ8yLw4iҾ}TZ)#u]:tKajժo6߲e˕W^)7jԨacBkש_TeK\`ѴiwݠaS&gm{vY{*UFW7iWXv߷_xBǏ߇FPҥK޷_vL#r-ź ff7]ivHĢh#-~G;UVvvPZk׺ +d  @xfgo :üMnu㣏<+3L:!mnesÕ˗z-׻ö=Z<pQBbu>]WL_7&٭TTTq]I}h*Vï /N`!5*X[*kW]1u$v^摇|tƴ͞m xŶT\g[nu.翙mO@&'c׬#*7xҵkݻF,ԣ]ۧ#;~O>{q#){^l#9s֜9si:fefq;͖G1cǧ>pR6ԩS{uQW^b.3 5 ܎xlW\)K1?/L{yߩsA6gZצ=O}%KzAW+W TwYp;\N&LzӦdž ! ˣgx4lV`[Ye֝M^%&/ys7|Xf8lD {dZ ~kq3`޽ܣ upo6oQk9crל+VFkyLJ^k,\Dtëumc0B ˖-߷7lV֮[C۶جYSږad(X9qp5-Z^Y!ѳ޽F 'zJE1)&[kXsx[sNF|ro|@[kɮr] ?2HJr.BӠhϽ}dI竒u~^:ţx+##<ݳDV : H՜c֮@ ' AUM3a3n՜|䑇6U0~AzfZ{)Z̙;nrA]}[NZZכ6ۦo)_5.ԮgKM{ S^rC[~/uj˲.ٻ>wM32OKto>dOP~ G)`9c,EysƒU0۰2R0R 5^պ61B;,YhWfΊD"={g͜ )]ֶ<\ypӻGmpMÇa_`›o'zJǏdOdKu\4M*lpb+vnղS|{~V mAL?gk\iPI}8ۣE!=]teeeR%Kq *RŚSyu@ `J>p@NnnzZZuwYmժek^O?[𣧞z~XW.KOO?F]փ/H<46gybօS,&W7n ],6sM.hU4]0}]lRfM1g#G1>;΁*U.UjʕJ ĝ3 9J8tѣ9Qˀ޻QL탳VkӅ{+U$&^0бÌ["2ҊemH?K,PBx(1|;fÆ̜a×ѽoG7\̱-zbF lۮ/=蠼oC-.kOu,<|4ƺvv?d޷,A}֜cW@ Ǐ36kd}7;wDFS[~;cK={?HKP-[^?g܅>~]Uﲺ/Tԓ۶n1YC5o\́ ƍq'b1hn3_J ԳǝV~eQ_t!êU;db&md8 99I}RγjZlYZDsk&W_{g̙1]qH=Ɩ7̚꧟}z~j]kģiמ>*g$iР~Z8\NիHVmyǻՍ{K.[resז-[,9y +W9wӦMW/>#SkCwWq.6]j{Yfo޼٧w.Yk?yIW_WH޽{MNJR#?v])ŵW Zx/B69?p:uE!=]d?R {W'wڮ@ ) =e}]ոVXqcԯxGL:uʔ֩]kС8'B[ )SzsYW@ NNSzb.ߎ# 򤄀[ q-tSf{.+j IBM%8 @ ĉBqLj@ N4@ qbpP@ @G E @ d@ ,@ @ Ju/kbÆ/M/Ygg .ٽ Zp=?V!Nq8 Qd.l(}qӨegO?qU332kשURD5lqz8ܨqS~JΩ8NNxTlV(,۰a#VZ5}^W0ITQγS_xI&$p2ndXF9@ ' b7mo|̜S r-7?7n~lgk f_n0:^zڴ2dUW]Cwǎҫ_tQ$FFy___vZ͚65Cr\GJ/.ZQVxGήMo?래nn#%:tсv[n0,'x<qUV7vLҥ=fo~uNŊnj~L2|\x6dB^ @[,4*Tܸ+8Xv|~jltrcŊxǞ'._csD? :Cΰy'M߹s'zfl97oҷoߞz?x^ϐ:w8uꔟ~c.?G8u޼`<336xٯ6wA,+3dֺu,Y98~¤M6=6t?/N{ypwmȐ!On4p=={}ϠC\/Q`[{쎓n-y7VXѹsWB͚{oVyk̚ E^e֩S_Xx첺,.syU)>1mK7\=O)p.aڹsWwtyP'N&`xÖ[wQ'M11IOzr܄Ol=\J@ `+TX|ټ933cݺ/:?6mԻ`8cγzH7{ʂ{:sv-#ܻG=͙yɓ- .ȷi G}ķN>!ɰ5 6v>կW'YAvĵ\ݤ[Yu-6˵W99G!qÏ+@[xܞs\]EXʕBW\xb~ھ};׹ٙ7ofgymɒ%Æ RNɇzhÇ/ď\Y>|=SoܴIV7ߪk7m 1ޞOU*(T<ޘU(ٶmeԩ=m~r 'g|.3 #HegO||FQƬi4ԀLQ mUj„&)m˗4i͛qꆁÚfTg<<\U*WR*nz۷==<yk綽{ߴyKBBBd_֭e)s۶ܰ^385OOVi*Ç}ӆ"'T* 6 hR[LOO{lLuO=t]DOuڵݻ[A|ꎛ (,4}=qo{ 7y}l ڵsElll [J07wwH$쓓"OU*;~Zj_x9|y فgx#=$I59yrE:Q㯙 {c+Wݻ08# G=A9O0?ZSwjѢׯ^uӦ׮UZ_O=9Aedde?lvQb =ii"NlLzhS$߾}+V_>-ŋ=zܽ{wÆ (Z[&GlWnFop{ Shf&A沦hv+Wܹsիwԩ >~N:o޼ۘ|fӽWð/zDŽQR\ (LJlmm30̴ 66YQݻϓEf۶8|h`@@VnjK[h't/ѱc{vΦR@:u<Vы/9h|t} P!x_9ݻ/XY;p@0tZtq%~}00>"~zB/P;7jL94ht 6MDlU.0bTDX_1-[-՝9'gk&+ڵj۴e˺u뫭-6hP?[7O9oZ&իb2ɔuk9s6&gOcqӧOoܸqQ0jP ǎ;|O>MfRbŽ{9rR8\N0ÜbBaTT*  Fy~͞=]vonҤ QFjj„ sAYxJh Po-3|-Z?~|t` gׂ%M؏9_YpVRs9<-@'3˙ؚ1zA'O޿?prgϞ=zH6̞ot: K(,l4yVV O/OΧɓ'Ι3w츉k̖ñYMV{Nn je %al7,%&7NN];vvDɥO?-3n5a`%8.px\K zӧҗp"p >>@D񜣌s<ČiӦ5 g՝9KNLV̯˖-1䜠ڷ4:ľ}_S+yr5g; v?t-[ͫ<"prX\}JŹSHKKL"A9ZC9d2ʎ6̷nzРA.Wa=Xl٬Y6mڴaÆӏ6lhѢ'=ztԩ>H$nݺ}X咓ѣ?f͚fݫ\ر4iZիO>VU6mcpPXaڵFv-,d [ ճۇ.?XvsYS4;:w `ԨQiiXR_ִPKC1ex+FhggGJVhذznETܹQ޽#""n 1w+vΜ9Ǐ047n陓Ì^Tv-[y5mT$Cؾ};ĬXK.ׯ_t˗) 0{ PVaʕ mϞ=RZ ԁ] K-Ym~~<`ׅn.kfb@;;uWCCC-)/kZ( P L-pRQԲɌi߾=[eС3i>GzGDRtsv,۷[nK,6lrm"x4/VV)zkڵFdo֬ćgB?Y*:T(i>">L=jcze= pݻwg)E٭gxy/ٳ'gN8аaVZAdb(lYKEO<>}ѣEDGG^t)$$d۶miI -@ 88xŊGBв!J ȯ\RZ;veUXqϞ= 0!!rϟ?Yb %K6mt-ZDcbb YWnC~,l/Or*Lw!K}PtFX:#e !իWe 1ӧA<Q\Y(gQFjj„ sA1C);v< L?~ea6mzĉٳgCȑ#tY@-1Kر#7L\p,z͂ FyANQ(R ڵkԴI&,fhKrVN0|-Z?~|-yifCY 3 QQQGzV ,ȬO޸qήYl ]߻w/'t`fag$}Sa YÖS,FAK.e 1^75,}vB1y䈈9y$},M"0 2dȈ#p ZyҞڲyB 4 zq?v )0]``bCH ŋz޼ys!TRΝc9q >:MҥA⢢%Ҏsى|W(78ZRG[>׍QX(,@SVn>#D"XjB!qQ0@֓iչjD,BL&wp`fߠw ٸq]\˖-ץUXEc`|YU^LIJuh]-T@v &DZdI'gg9N`07 f_GfQ*6*[PSj!WVZjN$arrw2er9; 끶 EGMa7oG,3Eqe| XYAjz2:33l_%Jz6Vz{{* DJ-ɷQ'##f<-S,g<0ӊ!5fձ3 8'g'NK-`3PwL junZjꛘיi>uWSTZ-5!Q _P?s;Wuuse(+ -:>;+KCԹ9Y6+;+>>>'GP&۪մ[y5VMլ'@K|_.j}5-dWKRin.T^ygw&$&֪Y:0Zc/Y5kֹt[]._&.\KEFRB{lTVti___`M6Q >z"R/UdjjW>| Hʔ H$f/Q_?NB,y!P &R:L*TRR`HKM qYYY`o5} 8 #v^+')Ly4 t@W'3/b`^ &Bbse˖n ͛BΝ3Z6Ef%z"+-P1O (324|ҳڵK05}["i+#(  œ3 $'llT`=~73K.hOT-~q;i iTȠoJfB5 \Nd љyy ?!&BVK†Ͻo^RX8.sW+x{{$30=z΍[A?p$+G`aO,qy^1-CB9'iӦ,]|_Yfp&=EC<,nQQg8sV8zejx hPz>{Ќ xM+V,Z*gϞ B2yQm_ Dž;vϟ;o͚sfPP=]xgO/YN={nuc^(,qQ2#ͽp۷o[0aW _`ҥf%I aV6[7k_'=F"N2͘ޢy)S'GFFK߻7vBfeZVi-f) Ӓ?egeݾybprV[D3>}F7 T* oJ"Ph!9@2Y$/1I`ݸ y?_" ѷ_Z i'9լYSH9l9g6X8e"V^1PAy`IfYAXz?0ca@AQ5(2$O?_m3ҥK8l( A1eVl(0%(7 s޽{R ٛ7o3''8ճg p 2;;(@Z'Ǘ  ;`=Z(¯_'boܸ }Ν;oBjmדR~]v.)eUرQQPl6l]ru欙7zis>(|fRܼy9޳gmr V'%kc>t),ie Ç ŝ;wڳg7gf4sQq5uZ̸uZ:Цii%Kk4ڬL\h2rr26J;Z dR^vre2:1[D uzW׏K. aJs$fMN2 XS&O&G簥+G`ᔉx ^=#ሎ[X$bfַOoK fО-[shEO`r%%%?B; ,' O]FoOhwXT @>_xʂիW,555̓T48OY:hyS28Z*p))ӷ__ZM7iV@]A ZuAKٲeIÅZ.h)GTD_޻W!8 p!GGG:˸mMH7W.]s?J.$Z.ұbF"[slx[F*9ʕvΝ'O Ѿ\`6飬?&++6 0C BH$JrΖ 2yV2Ue'lUJrTډŒϞ&''uTzA[Bp-`ra3zzzBǾx"pakcM22M|.~}tB>#1S8&),vyt6}@C4˗e2ȹ}6`<1+ -,HDRVK,b34kR?AXtm۶E,FHH]CbVPJ?.zZU!^y5o:zؑ#GG<2;5g̲1LjM=/^nZY"GėBaT*V L (XH@#3#NE ,@؜ E3#R.dsJE- c Il+W?4JchA_7z)f &M>sֶ5{ :tĈQP>}zuĀQڪM3OY*eܸqӦѣGݻhI3gڴio3aM HV'1]#@ -ZԢ;wi /_,hԩsΝ1# $C̊, '>} >pw V/U66jj)@Fd2))cՓ;r2LY. {3k_"Vb [*{AZ$ĜD_?ӻ[կ&NQ_?-_ѢEsY-p#=^1˞[2]5d(:|t#s6v'9:ϙWkL 3:tc~0aմ&mOQܥݺw+332 tZGJ,}ꟙ} %}P.Obر#t]#|T>C_Z5hHĉ ޹k31hvl]\]VYŌ!>>V Ai3cԨ!Ja7Кm #JՊrr"n_.^S&HfJ!VJ"X  =B[?j o//(Y<Q_QM"O[Š,lt=^ΉXkclt<4 Пf9@xo;d4'ɗ}u+V_~ttthhKBBBm),YiӦ[nӳKayEϞ=\RZ;v:0ڲeKDDijgvݾ}{&+VܳgO```BBBʕ?NdÆ s玳3۷/Sm(f${q 60ۍ?WLL̀"## FtFk`7Na5 -˗٥ԯ_œ&MZpaݺua%''s^oéI=FQ0K|޽ʕ+;JQ$kҤCCC۵kgUٹ-Z4qģGB;<|{A*DBA^zŰ;;ͲKgםGy+X*_. eud%zT)sWکT5kTtH,rtpJ$octwsU5``QVGFʢ||Ssr|<Y^{*)9k\2uz\Hћ))4+EZB5P׫VjH#cm 6|'H(137.hD2ߗ+`D )`\@=1h8/-5^Ar`n7aKKOW;gws'Q?c`=ڷoOz:t(|֮]4J:w.`ưb'(4̯4Kx"J'N`lMQQQ@fh'4FR@1cƘ .ۛvTş ԋ䬦5Esb"@waho,]: v[R<[7ve'S)藃BA&N#\}ǎS*Rtڵ;v^np%=ҩ7iiig_SFfv֣'A޼{<=܀: wr.NNeK6ǮR֫jrycc<R 511u=vu뼍}H,V>SRSKzz_|ʵ*]`ٲg.\*$OhutfTO*^`"yYJ4# h7oޙRJ{6[wt+۷5*ڢC晙oh YmʀťdQ_tWIr.kR%&%edd HDiNlN2B!IddgKE\"69B*lr1;:EoE O=/XNՊYG@^վy"shL`e}+,01^Pk/UڿdI!U / vvvEVV˖-8Yq{>x qƏUXWjj@$'@ ȕ(}Nniڤ1LU+gf¥4 BpPZFW <'m5Ș$KʹF*0*artՖ6SDX>u Fw&^db)iy{@WPP`z}P@.5չ@ɤQvVVZJ\,tqry􅃃#j֌r횻FF{u$ b2djf ju\R,IErN.6d L" rX" X$0HAB` XeRׯ*$[oR`j:xGumZP*ˆQ#D6 4G o]$U(|| !H5͛WdeYY[l*]EqeA``'H &Sl c{ĉwPOOOO!mX /u &5%^ڣNSi/ =pȈ!7߆_7XL 5_+?Sv]tιp;rӧ^й÷Zrpp*W }7r*{~ɂ9I{~fLqU x*y WG<ã'3zCzB(&09'GB2[8e˓3`Xr?9~Euj;lai}U:6׮/[ Ę 99iiqțAըsjV,puq KqWߕ >y{{T3(YJ|R0 %R\ptqtrV`C" 5zX1dBLnHz`9ȴ@R(RLFP#To#w)D2P.VomzY|pf ZqN>Lg^!+V;%5?+Ye'#\eP#$ytvvv5zQrrۓ ,O(1ؔZyB(5%5--]*0"!$JKOml\<|kT1vbT\T04<{b:;MrYYo޿pæ*G"99]%Wx*W 33#ݻI) )\LJfefi4jtCU[tӋXY":M>AMI۩cDw`FjZ4z5qi)Hi0}vU6Y'6X hS덫Wʕ.Sd =u9'Aq?1S4>!!CG^J%b{\h~i3jP'*_urvD~E޶mkk[nݓOO_E.nnQ^qFql&ej!T!)KHrF&+Ai"G+zF$ t:f$_R)ygtޕ IaT@mTRd9Cf12uGw߼BXf) fC.a\h,@޽{`<|"}||OS FIFf%eRiT\]V~uptS*33K.ǿHZQk?R)_ (( `mZ4oѼ˗/թwn[Wqq9%ACO/{5_-ٴQ~`#E7{D"b |[IRnHSԓwRHcLk:E+ȷNo!%iQaw1"XLp7GD̿qㆇ{X:u?odh(|:3@%K7fpwog;APr3<<= ˔)/}v 0 }8<|ff //o2rÇlٺ޽/~J[?p@bRٳ@6gSNAd|3fļׯezYZ%4G>e˔ eޫgRRSSA=@ ׯ^)]F1n uI,2QB\㣽˺*k35"L*ЪYd!ɖ:`Ɂ* d0}ݠ'OT0"\H(0.SeEGK2As޴; M>_25®N,FaK{GWPDϩS Ӌt$r' 䓑~ͪBʔ)c<6eeeA @TS;yf+ܚagg@URӧ]+UJEhUDFʕ+&M3asȳb6كjD>5Y[Bl bB/:u6j?d*UzHiM?0kcbSSR굿tA2];uxwXH|2T"e/\rqv~ŊUkH/OvmZtc'iӺmݾ>{tR~vmZ%&%%$&kw֦eg۶Qʆ[ʔ&Wbss7_LjT3H&:' UIq ƃ.$SE5 aN&5fd559dK`B5X!Q&#eeʔ1⅋s#mq #,|ff&Ot鈹!FEkTiŲn\p%!?1caÀy)'^am-Zd"qss[Z̝;wР׫ӑF!^ݻ.]D-^V͚˗/ۺܝYe˗թ];4Ǧ8oV~M?Zhq-_zZ3[7vf͚?~,N|ŁZ71͛6YtRQjn,:'&f&&jӅɠJuFBNLwwst49d"R*~[CTECE"tN (Yl :}``ͥsI˔ky?17QɑdJ;(lde-Ś2vz]NGR9+W콼}`"r_rT s.=-ѽ g>IO J]/Fs0 jrqqAwwqus+K/mۖ,YhяD0<{q*fCsW=?pQ(,ʥPCHhR)(Ha֨QbK, 2Τe}EJSk[[ۇQ#,F~^qlrlGB{g/_D ?x$JbцM[TvJ(+Tv))>ŦϜk_zuŜXncssvUq09u% XvI]8΃ }s)UL[jѫ#'a`dd)uh΅"OJk4j1tؐ -wq]4nݜ1G" /E~ݸu浙z=GVmУJR\8vDTTTFzz#<߰uYsf|ܴYJ݌p贡{m޲L KUC~]$sޤiqÓ&M @ujjc֕#=QrwRrP$rr DR%U&sĹoi.73E $:]岨Tɒ]&>fWN/WMщ"V#OI?!+H=KDQZ 3#{P\Q-f yy^;%9 :'A-HƲ ;Nuĉr?dee9;:bt/%$Q*oy׼})+vWsfg3ӳ]JiraЊ*:3'9Fα  RDiIḂ}RŊOEyxhu:b\!Gtـ?l԰4ڝբT0tssMEB << t1 - L\豞>{qnnn.SZ:c:Wstv ?CnnLH8K 4Fc0F 7ޫXj.J0(5C` U U8' ~5Y BI^q9|80MDJ`: 'bơRb8v wzCyQ}@  Tπ=B"otD)*IKY*%"ѕ'=I=1g@&hEH"Ŝ\tk@^IM8!O=2e%h(wܡ1`ƱD||BvVPT\i玝.^\f];< 3f4=߫OoRn:0At;]H];vs_Uhe&N7~ÇVbe~iժW۾ek'N_ܹ}GgϚ˙]5QŐ{v6ແG9pڵkغqfo֬鲥:wjMS$%&8) aa>[|}e';ꔽ̿FРf%+lJNIN z]f3 D Y99ٙR{s@[[[uxB rtT`ͮ4ץLD)555--˗1oJ) Om%='Q Z|Q _`S:Ho< ֍K|d j~ MT*c<4\.wqq!3RT k  *_Dtf̱o_ LD*Lfj~U1vпaFxܿW,DYօ+_4 hiih%陯&Y#`D$WxK+cw36%K& Lawc2Q:DuB@.IrH/O=\A^j AJPS.M ]BX2*@Wjzp|ibccђTB'L={[|gM9g0}R4+@̔ɓ#  BLժUڶT?Ǐ yAAaӭ,tѓ&Nrpp5{٣ 5jH(էױqf2lF?'O 3f80r2Ϛ9g{&~3gsƙF_/^yӦVF1@ff>RHXZ2J<t(qOW#S\GCbWJ§N E:^Sg{w:ȕkzZ\&I`0$kHiF3C" Q'$$djwO1T$*y^HOJO[*baP~&y'KLP 7LPT*Pz:+<}B@$ jj5](tFGzP~=*|Ua rN`%&؃ȚYŚL v*F;b~1mJy9>y4rő?zV!moô& -BѳEk]100xP\Yh'CkҸQƍ-E"sc/2;7̋d :f0QJZi5u-`2PHWQ"|0{@#XԦVo'Yp- .{~i [O qqmxKOiYiRe}~1*:"m9{hQo3@踴, 0 &g 5-1bB &䪚GP`o/FH/^e]Vi!0dK֜M9<l\5e'LpHh1YsΝ?SLiӦM3ETVs5Ƅ"I%$|<ܣu-}ϫ4Q۴Ҫkfdݥ*rbt\M6УY^,A2XB$LKK/]SO_,@ er9dQ(~e kͽ\`YWGܓSRo߾t]GGGtV?N^>_hr?_e~󶟩Y{]O#[.qMߍ9rwXO iWAICdi;w85=kY^ O|>k)&<9wV sl~`+OHͲ2 AAmK8uE"o. BO\{֢F> WF2dSo9vss?hIZ| ~Wp膥6d 5-_%œZAB?-y7!􁑷#`,-=#ohG 9!]iђ.YZE{V6d.̌-L'щ SKDQfSp(ӲlX,Tm_Ƈ~ 3/yˁW֣W:)gj9ȶn7 ZpGgNB!(_g T^ˀأ~OY[F@! MD+ kSyJ*Th,D }$llM\o[x"w& mI؃T( "/]pN\壏w|(bM-I_o$/-!Ƀla`cx(k>[& }G|b[%< #KBxok+Xr6P6 B=i7ĥn_Tⷣ:ns_qٽC>!ɺIIɋ7le r4M򇢁i(RKIIɶ:w1TZ+>|w僩Dků"[\69RQ^'+ؓ^Y^p%f5۷?55H\`bbbzMӡ ʇB_%)GH _c f},|m@òw~_?LcgOHw-XXf;Ƭ0|2+)xe.|Ǽ<_OH|~rYe$: &M*]a|<0VPgf 5_r"AXj)O:R-Nq[m_2 F[ $1]gb!JqTHYdB ,ܨ-BSQP{O{J2T xZ㴨P&::f@fF#0*9] Q,Sz9-ԓ.ɛZYrGb&1#P,@gqlۏRDF6[-=3 ym9)`R1;ZT(9UŘ _mNJxW }&Kaaauu59bѳ t`T8-(O~ǝS.;z%AJ)s=6)y-ͻ #>)ָMZus>7QǾc07Z ZT笄j"|ՠ~9)#>+·N/[+|1tW~ H[_a{?|ghjc0J Z=BşyN˵Xh٢YK`Xv9r5ŭ((AC[0tvf#~=,cU)?`| e<JҍzlK%n}<B 퇃r}Q?eVc ̪٘ 0u,رk+'-?1bbbjVYRzaHƺ5X9FcNfZ˥ cXPVVV][ץ{GT!";;7oǞPVΊHE 〥-d߹Pr{0*6C^p`ii2F#fPK[VԴF % z|{%;q!gwۖr~-E7_Zj9ySy⭕PFA мK}㫷G%g@>XR1#vTj¹Kּ(!<<_b˨^Ȣxt}~֌={;+)z#q#xc̺TV5mobX5@/sAuU_0LC*T8YzɚPĎ(7$SoI+o"I9Ika}|3HId@8QU_ 9k+/&3z"Rcc}ΐ_D&L۠r E *+P{l* E:QP!M/JtKIyH ?_UUzw dD>~(I\ xL((JITy"- Bf ̩8 _WǏ=xPB*s!fF`t ID)>&-qH4mT2չ5n7P tȆvhƧ:OuX,6e2w!z?q:J;Eg$ %DQʿ(k |?q"3;&R3;ӯVęx e]_0\Vcں$}2:ek^,IyoXe ʔH(#p$rͯ&JgUʂ8gZ䔫J5A8^f)12(nw9daz UyBImN@<.[osD:Jy 2i1da'(ZnzVo`&ʐ%z=MGw7ܜj)5sY *] U%tl*PNK:]4T^':\z h8G%VQ dXi aF͂\JwCE\!@A+6}elH9hZo&*a朜xDŎW`F<%3T?qm1,H C[CEʩ8[KE[~jٰ.NH qZ38@ʹ>[`3vTTld $3fuֲ4v+ByFg2 82^eXm1d"H||Çt끋*3K>1-9ڪPxؕR(Tza 0iƶ𘊏 \@POU}xi4%WBB綕PwEE4vZ,@9ь?y.D@YڇmG%Lǖ̖quSzS|1`߷1z _ҁߚrWJcpՖFy] 5U[mBjC2b yY [[Wcmm /Hu4 ^34ߙ0p]WCEկ?;p:ߒ$rߨ4TK<KSGEQ0B§KIEaEEGN$ e%ߩE;₴;rhJů\.%|UoI{q\Q߾k7ntADL gQOhpzv$fstBlZ.fK`h5JiSas 7VUuI%iϤ?&`Oi CLF^k׮=YeeeCC EVVNS&\^^_+BZ={%' @ӌ8 *T{Oz](^m9D(Fk+?tDǥS:TmYq4RrsKF>R:$Y9&6ȃҹP($,蒲^f4&ڠ23H*㢘JQAւFT',c%yARHR>%;ٕvz=k29AH64BI ظH[{` ӑy^KNNAT,` M"`9P9Գ_ߟj FVē{B! .6"']@>|bgSSFVlf ͆mej4].ךfPB t}(0?.̸CuƄT6yֲٝјOYvZ VтI pkSB hK )k.\UNuQ^\kLHCmdU|Ƅ ZhþhhQ]jH"4Z_ג}aZ(}qR|_lI' 2g4^Eg+)BE>yԂAHm,( HJ9֣<6*? :@*T8v˯ڳ炾}u:d60E>%мSV(׻v.>^gX|/³ iv&$jՈC:ao@M񌍍GbB1b@B 0p (( F n~nzkZiQ*TPOyB1E\PcڸxC[mU$]LGU1qBN.9I9M16$Zz^qzۊ7RG<IEaSj\i9)4דVvX"nHrԔP yMyG7G~|L~/]\*䪯DUghLx]9pfC|ϪA%?I9TAǚ Ey*=L.f )K"8n<(O'yV"|!RedSݢ0LX;\K}*+*X,r!ZІAy tNh+9>须IVSԳCtqk.Nۣ+0۶wYhP0sVB *T8.Sm93j=v]ĞɽG#)jIM(n(~iR/I"Di!1˔ ޑ!$?#(]y6BͬaPN'O>E(PP"h[+jRD(EByx"^OԹ磠~\tMS&EŒU %[Su*PEщD^}OK^9|vS&NOM2$` /`ͺu'N+]&xi6 M#b;[Vjcjk*jjz2` JKQPB /+ H*=Z IaVF{Mcc fm8袳;5HX% <0s׃!Cp)nm.;Wےt|_tV@ Z S٬z'm,9](dZY}~}VaY$7kR"4]؄N}D3#g/wi|N-(BKF5jsҢC|1:RCF98,x(Az"ZK=$pTq:.h2RQQД FNGso Ƶ{ʼnpjժ9>5aCkuu=wg)>dе(S֭]7{~Ϣ/n}lg{Ir4R((,#h5 :GgK׮xyA1CyN {]7+V; e`L&cYɉ.;C^ e )TJy;^7 jmlj*++aXh-baFXIJ*OFz1_{FgXaTPBكvB#3ɕ X``tD J}j( bxh&n1$&;r`ttWk)-/s u1η<~!)ZzH佞ڪ~>QPh%Sm$vL~YM }b٪k U_ɹJG$:<)&vP_zb/$j-#;Z 95Z~$6H$*|åkSҐ "C7%DF!M&AX4(|M$0^g3G&EEE{ML@,tU/zC&ɛha֬94x3Nwk׭9rF^p3$ ;bhJ[bxo*/xEtB<s:*MY` GA'$8o݁C lll\ Iz=lr:0L4EM蘎],Ғ''9#??cz[tZ9՚{FGW*TPq Eo~|Qp ^{yۼi+? MŻbz9@뱖֙ CSEӜph{ Lε4s4]o~|^2#CIVoۻYu>snF'Um[eNϳٵY_ZrZ^LZb|b'1bao|pi  NRJ( Ij(RKKf$: u 4k@ =-:uqt8895*"ͲH!w*84i\ZD "ͱ{Ŋm(Tpu׆Rԫ={ p:ۿw&Mru:HO͛oNgJJN2xwp%֭߼~n')AOtڵk=w+/ٳANs>12llju-~шa#N754)sEG#w >5{vyi)7+P& 9DR/vxBF(/ %}"yTyڟA)\rȰ!C& 5lG]}I3]z[HA,k!X? iW_}zuWF^j֓1|իZvO[/Θ133=}٩!l\]]ܧݗ;ə'HZU6EoufρW:d?=wWJuy2 f= } ^e!H@323;nS,UI[8PByb(5xΘiOe FGM)t xY#&{lY}L<=GeګKaWަRg >diǧcwxMkЭvφ4 {ǦBq;f֚׍aiHH>۞}%^oJ͡5:cb!1 2pn.Bn)& B*4&L[/Q9 9`- /^JH ":&*Ď ʐءVO?>ߞ`ȃwft3aT k@#ǁ`35Zh45}pqzطMlڸqĈ7nϿЧOE/.\Β'dNHL|_O?4|v'8E׭]?Ƭ 7$&%mXPèѣ)(`/oᢅK_S WNyW 6o)/]t/aGzʧ<}뭷 4#>h!j^y她sy]@AqN_+{j/?Ͼ 'DZfɓ֮Y3Zё^~Na»K)e#`N-8IUeZS$'N %++^FSޠ7s~R -2.qQuaƍXzu||ʉ€}w;K^|%y?|ҋ/?3!lVW腟7ܳ)ZU= =G׭[|H+W^2?r0 FC+0Du), *T퓅F#W)Q21Vor5NI>/:yP1JSS/_2OӔ_:1_urbr;x%5'&3|믂mY2Ck"l )JH)IqQI+t`"GGc %oj/O,]b<⇪4ORd[r%2Eס$%'755s)SB]w7dPH;w ь0a옱h4|oS #ׯ.>bԈ_pÍ7l.~e [G=R۶͞7GM0LgQ6˿[ȴGp7L s0ˋ_/V\yAՆ >eْӒ_+[䓰6|HrAY3gM< #>ҪY4Z-oK#,ܠ~KB>Muvzr\^tbaC(d2lk鶈¬CQ*--S^s@h^v3gZ,\ : \yu[S#^ ȼ߀6[ŸWSZ 3n?X 'ǫKF( )R>\ ^"iK@CJB `<.C>YhgMyjϻHgktrRp\>Z+9>+A["H4%¶v Z_ #[}SaI]h!mO.%rEY,MmU\<##o UmނUȧ%$%'-+WnTrأ]yztOHIy Aaqʓ!_6C0VLv!x'M@ȄՔnՀ}fklS-s<'H!Xi>rQ/s_Ni;n\ff֞ݻgΜ9~+j+(׮DhHB1R-p]-n# *T?8pkfwpdG;mW"p.}|)%xc-;ad;r;B,%nJB#d+E%)_ %)PX*z; 1VCy!?%jT [l}oɁ232cbֳWgƉ˖}ح{Sn 42daÇϛ;k#G,X`| OtEgL\N5j44kcǎ'%&.^j[yQ?0a_~yo{a2|葏>ܭٻO>Xv%/ǂӴZf=逫hCIO;ZG"y%,yϜd^0k7z/۷ٜ >~[n^o_Sε0)-z7Mi)7N7z:czݻYDD cR֥m n:y칑HB B}'Zfmw;h8=רu|sʀ ۤIٳ.źlD+B%["%&᷌ERADPϨ8v #Rȕ.<^U ծzX# ̕'DqW8H/~Jq͟z}Ϟ=)**MF)]|ѿ^7rKzj޼[Zع'g!%%߇d!1k$O#Gx?5z\dJs|`ޜyiYwq'N6lȋ]4z4n㎇x'?#?x23gΚ>sʇ."e|}w!U41+V3b%x9Y4+Cx U uE7zKffԛ.|aἧAA&OԹk'f>AD6gCmGIlř&Zϵ0)-z3Λ;sd>#:&j$v %)KY CZ·={A[nzRiUPCQk rX5%) RAaA19=ǒ1h,k0Um[iJo<ՔA)fg@"Y+bgD?H67eHE(0Ev@bj:ACCG&<9pʚ?9^#.?-uUnjWB|+_V6%%% ;%%%4*_7pu];BU/(rq㐔9nﳳB]hr`#Q:3Rq7eNI󄙳ש))X)<׈)-z]t~%w.%DhHWB.B1x)S#x] 2m_0.g^^Oięng yV72ކ"|a֮d}sxZ֥PBFccCff&|1Lή >Y=FA 7s˞p٤P cB>"+'=.H] YBt8Q% 4asN'܉ N:xWhH瑡Q"$!=H_*rtx2C{~4w'H1cI{7"sxVзvG "i &bY:}1Wr/=ouJrw ]hjjmMͽzz^;#mZa+* C_^Qnap85`Ymv/-χ mvf)h2^YѢP^o^n|&UWWϰfsfQȐVRZv!UUW775:D BNJ$'%WUW 2l9...>..A I@A9/"%R:R&O0.aC.~^ж2E=.`wZV${rk.'JJ\.WjJW\|j-o됝uq2 U՝:vҲ2` UܹxibRBnNncYB!d4?~rQQE!>lD.V󚅶^tz-`||eMRzZ*/-I͍F5ixNg#@(zcbbMFFVi8 QJ"4 tQQQ@YiݵK( tSAGHY6>!N   /;Nץs!I}!Q@IB!@+E;"R2 l!\s"t"RʊNp{ TL 9 R: -q"ɹŒɓO#Pqf6g$ ^4iq\,f4"(d' ާ ) t܍MMQƲ,Q4iқoywkÇK؍7޸e˖>}|YYY?ȕr-k֬|/s=v튋[`)SȜ9sz뭪* Q[!… y;vO {w;vPԩSe3˭D"R?䓂ڢGG3 2H;vrCfk;~8Ç?<d(~.$0?C P"XW^ye%;`&=K@o]Bg}֩S 6 4觟~:tC P^^.ywAlR* iifd4e6Xk-1 F#:PLޭ%YfFL6'23z2RmaX 4*RH dEiQnK$pIiJW=L z8BɒTˠN Et! rzy"E`nhWxnW՚LCknx<kaAmnn^:ēN÷wAN> xdEAHK?On\atV xT|+᫯m=N;GbŊz .%>Sv7Erʝ;w5 =/_tah#i&[?zڵk*WDg[Dn?={6ИqxhK~%Z-?s@HP͛!{B)wYFSPv6L6emG}t)7o0F'Jl 8p YVz0%O3L/b0݀B'O>=e?rȶmۂߺHF2' GY=ӳL}PO1n$&2[dKHgSYC. qYQOQ^r(p$ړ RГk9eGgMx<^Xoh/tVragcIIIǎ%?ې(ViP5dte\A[zAiu:M"BZS[SZ^S ^yŋggg~֭->, ^RSS_r%lM&ӵ 믿l2@766lPT8f̘@qпիWg>x!+,#&L X%Kx㍀n%\s ,:.rX0:F#Ԁ)e>`x.]A D8J1cF[eO*L@>9sT}΄,h+=<@o&x_k or .XHk8~}tWuXp퓅F/4\?|85%V(|( ["jF%EEE`ۨӑy_.\@$Z%6)1RšVJQd< b3hРN:PmWfF[r$$̧$Cll~t:{ݖHP'@K0+X;e-8. Ԕ&"%4Eۓ0lEel]:nٺU7pw mm UUTVi5hС0Ҋrr꽜W$< UZj2 f4;+;11wi$DI^gڦ$btѱOȫeHGp =D!v t i%*}p!U)R<֛FL Cb׬xZOg>%~w?mI%`A, TŦncc#,"ב5{ĉa\$J~_AP$_,G%ld!%+gknsN~{$ߡ>C?> 3$>ӦM[|3߿gϞzmWh޼yoqa"744gPn~ ';̀+:RawaT")lF4iM7݄S":/}@r9%G.s.]fϞ O<񄒤Ew&‚a r A^_3¯eM~m}C9 \YYԼG6mZJ*7Ϟ3'++{OSD^^O(JUW nUֿ~ݺ9seggw璉Xh'3f͞8r[̛9ne}XS,ؿ_ANO>93)9I V^ǀgmu]azSSSM&Ϫ_"`a0+H2h/A(_Dd?{K֭ȷWeq_^Wu2͕)2j6Fi5 ~F]vҤ׮Y 4ühUc ?ϗ^yY|+d+JsOcQܳ:m1` ӅiXMVvNN,xvݿiNlO _qTE1>Os%}#IZ-<Dҧij_Ω0G$֭|EQ.IN T՗Nbg_*)>>n!._wSZVK!gMBB_Uf !_r•ÿ\JK/8o;(heAܝ*TA\zj)N'&O7=zQtn7Y5_ԡCq\٧n#۷O me{$SXFKstcWN㩩CQCyHq1|5 Ȉ"zYr\mmmRb" z^"v pq$3#'$@N ;3A/:@$ 6v @V&{(Tr tiZi`ѣȖ8ȩ2gG#Xa͵9G_/W\}A͆nxx4a b̆a?}/]1rYot㺵kzaH۶M}́*  ي;o_w̘c](Ƅ&hZYMz7zN{]=VY ?&O|ynܹc9FrkNȡP^ǐ/1DxӤ$Z28km=(l; ))OD =4,IHOH"]R2z?qC֒ LIJ.-+s8/WRZ{߾ݻ&9hgFz۶\IhE[jazv?vxYyŠ8e2E!) 2UG` ;׺z؈def'EnmJI(Nm9SIMK~a7f4XFj?M+uӍqb/70X$''kX͛./?ߗGGGguVƌwO΂W^ToJ,f˪UXБ ZܑRv{V@[} 9'B;/ :xQQ(:\^Y!)-v{ss WUSv/luNXHHY(zR6ZljKJ3[?IJjWaL|6VNEqS],S"(5SR~/"hc<B3(* Q3dг *T8ot}[nob;e(CE.5` 䦈dX娮j].wج_BX[Wg1DI#tCQzY9[bc\n7òcռbg|\~cbA^o6zq 5uqnM!=&&Esh0hZV Gޱ0PjCyȔ3PZ{ Dv@=Mjcǎ'&&<΅{F+P9jc>vw0 ?)SoX.|+t+һjm|)74e7zͷbC7-?oBT(yEWBN0I_|B;=чhzW"$ ?5?y'04t谡Qz`g.̙̌f* j蚱cG}9`w>{άm4>?e.]Lbz@%C ~ŗFxOcK% +9 zͷ޲uVe c 3z:hӾY"+bkYDv^yЧ3jaC}GZ5Xbk~z򪚚w?xP~E=C=?\c12X>5,KK\$6-?IDʯD^Ǥov+&ȖRdHm >uT|__3>BKaKu&i@𬑒 !_~N[., r֕PB 핅F0dyN[۲@cE!$ZG4&Vm٠pHפ"2$DoAǎ kU?_~IAN^rnJj+C  |~8s.,y'dm2/e_zJJ :uJJiv2hW h+/7KoJ dgfM{ڱtؼԔdX- /+ݻh^=S^[] ;z Y7Oܿ?م>h O4} J~VS)9$FJgR ^NJPѿgI${^~|PnϚE#x.|fPJ;rbEeZpTPB3󚅶E`xҩȆ8xo NS>$Tb;QPHO!FaXiZ#qq P?d2=v8t厎j5(ZAxn 5?Q)IPʪɉh4CR$}i]]V̉9h-@<ǁ,:@g*\iM|_- Jw$?L/$77K}qW]$syss**lv;EHQ1щáPc<\;抋;de>HFHi+aE4ɗxD}ZQ Hr؄LNbTPB 퓅FhN׎;;!ljn6w `!hdNo,î`wi@ tX&WYUYYe0l6hJKOklllhl P3E'NuFQt^K!@H(TRDޤwD ^U)(( RRIHvns].g8gf޼{1$Q#"9%UFqssZ, oܺT\\4^W#zJjZ^~~:uvz¥F \(Hx\.h 恩*KJa/' EDevБ_kXeD/)DI޽y_xԎ7ogdԍuA%Lr~T*V*Tg/\iӢ).\+raL2y2?=#cۮ&&IrGNN"yڳ!gML0MӧE״|r,f ?h+$h)}v ^!t^2#& uV䂵$z 99prrp啓Pyxx89IzTj4 I.2V6RB %99i0\HX-0 #3K J|_o^+Tdd*!L KFr=O3Zaa2 mbNjٰ@ΖWca`j(߾G>>ӡa<+ ]8gx'70>`<+Ieu"]dNSt|OaQ_K*iM+j>E>M.Fvҳi:9vlե/!58/HBAI޽ٻOK{v}f ЗQQ@)AuQdxX(| yb5dFgp7<`$ |Ic`tLLL=a(`k?Gnn` fe= ErF AoPk:NRJ:;9Eqy%%|>0xXi9ވ+ +*OOLOX?#OWj*AVN@!8*=i0hM;iHɩ%8RYvS`_n^Noؼ;/O"0|4-%y( QA34O5rq'IM^vMgzR8\jRkgo[/*5=Ԧw W :D<~ٳ+JLl+WvoybSʴJ53JV/TOyo؂yaϞ=sν~DD{5zAQVVl2h?"lٺ=kך:>"WH0-)hiqqJtNTPPtTVL&322 U*.2缼|m?RĢjJ >>.eNR'o0 @K^=諛Rt ;$rssvij>m_*KpLC_1^7C0D9MDOB!C8M3{6Ҵ4idkP>]Y~/45ϊ!Ffr߹9$as&ʡ+f0T*5E\ FE+QJ> m۶-Z(99y޽ݻwOOO߿sbbbvQfMˌGn rϞ=ᩩ~ bccwi-[\~.5*11ݲe?M6wm޼yff&SI$6##lӓ'O`3S:X_6ɬNݺu(آE PѣGXƧQ^v֊qΜ97nζjf>6^ 깸ܹX~,V@]w `ەtf_|ٲЏ>VRw8S޽{3g;v,~ W#_uV7ղe֭[aUV-YX(q(ZriEEE'NX|yvN;݁N4VW: Sֶ E0/<ԝvH$2 6*atmkӀj58r{^O?]0ӿ5F"*Z3ʩ̈Tb$RVd V(߿ʕGݬh>믿>|xذaN{13`.1bD~Cȑ#ZjgϞJt) A.]oߞBO:@c@ `*r=W?2aV2!?Ӂ"VƯ`ݹs8̞em fϟ_tѣAC˯X4@?_2ef*Բ+u33fLN&M`)?`@G53+DaƱk9{LhS'OժUkyv((P>.Vᔖ0"KdKzP@BB0#F V-,((.) %_Uycɦ'0`bFB"L6A O;e$#X4# +8]t+QZH v?hݜ8q"K0?Cx~O<7޽,"A&?ڵkǍ7l$cggtǛY} XpW،$lUfWXGH 降C0z֖4>>>V_k[iݻ ٝݬZvnJL9Pӝ;wBAk,ZU 9s9>d=-'rڬʣdqqy'L,))dI ÃBs,٥; MII~NN-^z*Ho @ذYeg[Rw8S2`%'$>{l=~'̂bcc:K 6d+7Jc>vkժB!pkBYei i\]\vP\\(AlIIJ wE[I^OÌ iʂ "eGDTv-Q,d޹VZZ&>{ =t.ݺU+ߙ۷{5Ӧ/\RY0^MgfNRi0+Rx2@R`&(++#iRE0bEv kF(Ez5JxI%gg0e eC_b~-ڴi7yG~o߶ `ku;@kK;čo:Z@ 駟?:M.]֮] V#߾}/PDª^;)T00֮]{`?CPvĈ۷ooܸG VRwū jC$kBYQfz"#;W,R%dqLr:֭|}L8hO\Gݻw/dlǏGiV\ ɼV_8WCM׍V,nJL9_5q˗{$z :t(2$ ~e˖ACF`;wfZW` jך8C$rw+/$|5O$KO@U\?uzA1hZ\ P.1m9)(Jf"jF$VlB &X~k:jղ|H4:KƑ%4ͦT rrr/\t+zӦQ-ZvoA왳^Z3///~iܵk|||͘Y ų8hÇ ǂYЏ|<{jAA#>c.GMxAsQ’K^ 1xcEXryM2)lZ}S  -UTK/d% )#v\%sk֨^SBZlvkOՈŀ@aЊS\39 Ko(`ypxxӧmeFi).s (S:$aQ800ҁ}X+bBfΔ ֭[Yu떕dfzP̑!a( oslpdDx Q yb@,OQ1_,9<.KtF:n|Z) gO1:IRe2gDP*"13KJd25ѸqiiIqIT"uYj)rj8###,4tqRJ`;;9Ce ƨZ9եRm B;!xBAjb V+ɻYug6ZK׮K.=\+Wvo 2zʰYsf?w>nI܆()xoٶ gz7O~bA'~;ѪukxbCXR$ҝ {4 O$ʜ%Bg0~8J 7kurF eAWʥzb ڤjڂ~b) Ǘߖ{tFzB˛= Tj}`:$cbb6ok\ZTP$'0 V1JQ,{ɜ"N[ZJzry)V(\VF.vseefXR(r?g!l4*15^F?HFB~zKΠoh׬1:Ҭ?/(`FEM8ڷ| 3mƶ=rNI؇:L]x`T ,/;.qu_x(|0*TjCRZ=$qNN6ZGW#b栲ZMdd^!ti"**  (c =xY`H)xE$V^/usy}¶΅b````````````BIz=" [<桵Od]< UApox3,d=-))Ė^g00000000:Zr⥐`B뢨ȍKH/.q߸86A_a%3nydEv,1wzϧU(\c x!8b9f ip0܀`ǩSRJ~h/ K€'X3Z}.XVTH`aܾuQXsJi~vq ?xy{Ԭ̄21?taç: >M%hDGѠOFA`֟yT atYPP!(jNT=7@H^"[mK0=}tӦfϚ<ʁƋ#WrWn: : JՖ0x<(CFBr9}.cÆ )/_b1HvDԩ3xGO_"3JtrrsX6JF<̵C$mwOj/%\ݽccc+ H~%EQ+,,l_&uDRO׿M6ɓwZn}NNN\ܲsΩTjԪUks=m3;vTZZڒ%_^z@LBPMjժpz5zh} öm[]&H7o1qDwwwB=ldQ~BskX&4V'LJw\1 [CCiB7H;tȐ#Yx#R{VqVt1*`Ǐ}ea^aU*TsG_R‘0 .WPKKBq]\ QVGo+;ww.[rT*Fe_=گ NmU,g„ UU"3N 0*P uxM;1j${a_~m>?YCdT(bQ̘&s :K\akFtϖ-[Ҧa&$lԧ:wJ͛JRz~f3[>K6t]t 1UjJשU~App1lي5pxBRm+z<\Ch0LHO0}76i142}kj*{%s&##99ym|>oJe/6oޜ^}95kF)2Kiպy/gnWu}3E>"=EvOę%\?c2) jN,&T~9hIDagu:{M={µkɉk֬Di7? (h.]믿BcǷ'O fOn]W\QN9r;55u1xFj!|V@CEQF{yyC}ӭB(D"3fl۶mQӧO\"-- reeQAF՞jKV?q) 86ެR_pQ1W^1cz|Wk`i@UBĕR%xFT9L_d"#:tbOVznz$ؘ1jZQoܵkKƏpG3gf9ׯ)z.& ǺEn޼ݻwd2xZr4ӳ7wE/(,,8zcǦO[_ݻR A>. \`|/j]_Ӕ~6^+E_S*ns@/oB4ĉ`_r!Օ$f_П CG6tfM7mJ:u*z}~X*=Jf͚܃&f\vqc[hMhhȩS'ѣŋZli'i[޲e PqP lVZ߯];)ܹsEg/Zn_B :HCNr3$` Oy'B0P{Y+e˦ѣ?kҤ1: by?ܻw<]tK.!`ԈBhȶm[mۮT*~#z @ h4p,L!Yf+_C㏇}+ͼy V$99Cm)+L + Fww"P#&F#jM03j% )mb4֨NQ&~>FpハB92~Q%EEB2=/;2wwU~g/!V{(\EM \>p][H7jiϚ5s4+ 6t͑ޤ#ugw0@@xVVTH [sz ]mbAMoO 2w6621:k{s =to7n2]њ v%:蘒U3_6HFm۲}*+>I[}[F_X~:E~hT6n^0yr^_~ϣG~4AN&GG=b']vlzS 6@~P-[L:uӦfςχpFK.S( ,֭Rq9֒#mٝ;wCر( LSNRRPٳg-Y4((/[o;99O89.v:cϜ[N۬:>+URR*Xڍ%L{ҥ?ظ1Bq_.]Pqƻ33Z[I8(=5S#'N*|2RZ 1 L41cHzСu#O`U1acOUR^(Q$a E)0 ̘1# F>/HL\_|1nih`q-`p/2VQsTrdkRDs&osſXyi>8.R#kk[v#tL&E+PƠ2w E| 3>:N7t@'8y:p!W_ZR4Ud0bBC!ac6H^^^ F2kK03`A/ sm]\<㤗}$|7)׆㩗T3:xee_:jd9kWCK8Ɖ"s)aa$)&"(BjժџVpww9rD6mL 2Kff\BX, ..Z{ LO;+\jJ"tذa[4^zRҭƍɥo'%-_Vk׎^b943ΝZ73N}ħ?=0֭5YhJZ)hFb vN]fuQQc<{3Pog籩V=Lk 4,(( @@:A~~>\Z lȴ<jKD e_UR~ٲ8 +֭[&9vBbKn&$l8"GV2N }I2JRN׹ ~}'6vy[ FizRj"]F QQPSzґ#}r1n4tu 乹FOWZʧ:x3 X͂m1ufapK"&CQdϞ,X0i$M6-Z2Uz;a[K)*,ns6ҭ=e}Vd/NM4s]3i5X,fĞL:@R -VR b3KHٳRU(IƎ;p:uꂵ{5kZ.;v[6{lZQ|׭[.6mqcȑ#\8^x7mܷo_3""%ܹڵk\Pd; 1-~aÆyc>$* ZçhBp-Y~Ghr"֭w_@u%cbꡔ<-3&)k,-[תt k͖S#Y233[LO+tfpYҠҎZ|ttW_}Eb83o<`IEuP;hРKKK:R)G^f*!e*Xp8[|-Nښ9Kg){kŇ[](>xvըTF'w3EQOPTj}bHj5j <^r2K@&F(9)(JϬ\(d3eɃ"?h֭[a2t0K͘gSƋ `NXXZNN1z5 n~]mC7TDLJcz(˧/T"1FеoNX~ۻwoYwMPаaŋY.FF׵kWwOG-ZK\b%TCϒ$seV/\/; [ФyϞ222C{v ůXooZ_-Jڵk3i$`8!! ,UX-UPM(bgDIE1:>ZT1x`P# ~׮][v 4/JYfw 8$>>n]}bL6BKNנzm8yD?jj$a@fϞwyJ~ǎ!Yo6.]:kl/|H!Vdb(?m<,((( t]ޤ#ÀD">@@r~~0'WiʍoZţFI,|6[ůzʠxk~EԶEySk?cMH @fw\Jeldg ʒ ڒVwZqP[` 9ԑuw" g堟q?`+5 8XƋ #!rLBҠ܎Z4uj>zDz/IK...l> Qd[Gee=Re%f\\Mk.[$-[b=N&, V 45jdz^}ؒfz'c(8(cǷ"* z_&,PiQd<.0r111ӧ 1cPk̙3ΝgA;oҤ1?%1|j5^z+ۏ9[䇹KrS?|+W4˰aYaK>BDjKvL8b?YCxȐ!Zy%ܬYG`\Plq42w߲v PKW}aS˷tGTn԰LvX :NѪrp3CDa2E^L.}avQ9P։uu!}g.A`þQotm٤.k6hG068"߯Y#rE߿HE));SQ Ú/ѓ`,nxJTAjE7Øt7B>AOψOKin j֌ܹs߳g/Z&xp*WS'ZC;p@4P8U7~#Us[P/m)ߡCGtp{> K:ٙU1kwSC 4`"V̻_k^ukOer}BaTO9Hsdz\hǺweUke" FP$/jģ,2X^xv#ؒ䬋eK: z{KGzVׯ_x>/n޼QV&"ڢE󗈅:8 ޽՜'!,$L`r}w*Y!˶o\B1000000* [V73E,Ni+#LVa J͇Y*Rb"(1qd0*#rdN`xʱ]1dVgm!׫hZfc #4}TqN1e=ˣY+vzWe_fppHԖ6.p$c]H$αJGhh b<{JaF"00000000000000^Ly+CT---_M)iƫD>rV[_Ih|E**"۸L^s ҅"H|}^Тb^YiZZq oT5yJ~Hp$랷/+RSS/rIx6P 44*Oh4o?~)X_tIENDB`liferay_1.94a72585e62a429cc5c188c3d9a9c85c.png000066400000000000000000004752761325274564300421060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRX] pHYs+ IDATxwx\y-X$2U)Kd*lU[͖dr'Inn|7)(E׊5[-[$R"M{ @sfٳ PV씷3gY.R)$!PEJ PR"!>g[ ծhR4,4ekB<^AX?o +LaxM\ MyI~An3Kf,3h|FlgJ[c MSXx5*^\|M .t8>,}}rxxg NF)%"JQ]]MQQQfeNATAGvMhO0pfJ5#iBd&;aT=p)9fM|h3eec.[%0b0,SMNoX#,U$UweLlc'G $T&V'G'\Ywr:3;gƵ&XcJpLH_پJ&td"%,N:'БIX&9H)立"Ft;$ Ƶ$)%%%%TTTF3yaQkwʻhwwۇi$N[ ɴ޾>Ϙ q2d@ mc5>Ǭ.Oԟ?D*IKR#AA$twBnX5ȫ4t:MOo/T~Ea <8COOOc0qFFFֆm&t`>d5[r&P5~Ct5٠iY`Nδ& :08: l_>gևs<|BQ2 օȎ`{X}lnR; aHr,l([]d?Ÿ٘ ԩ¨7a1ѰA5 ;ha~2.j̏*K)XÄ1'Jq1 .;41tN4 PS[Kww7CCCg;T :::(((`…qx' j핣##Z"czo+t,?狧E(:`"@bDiItV'dΝܹUV}??_%KX+{a1fܮ i۹}2|!Od<#t}㎯qgS߅qΤ%ݠmxx7p#_ѣ~qnfz뗹'X,ElіdW65rJV2ЯT+Xz"PiSnՀ!N[%-44SÓe{w7 ޜ0"'ffDyOii N:i,cph"%dppOaa!D>CCmdYq`p`.N:$GqI  "ݿzuᶱ1ڵk)**gl۶ !%ĢQnFYDcQJ1V~MF^}zzzaϞ=pu;o{?>]{-< Ü{l9\zzzK/4&qy|_G&s-/ŋ9xGve˖e]m[2kl>/K^{m D1,YJ__ηWLKK MMM̜9͛73w\FGGys%o^ΝG[[W^y%jp'O8 )Y]_hY%|&3;@:IF8l_DfQ,L! B.̤+mp2]e/a0!UL >٢% )-WSCؘ`/c3Tbo A ruYlCmp_u&/ hZ@ Kΰ M2u,̦lFl.r'!oSS-[S9:({vJ 4yyqwvvK-pa&Io Nph4tO>D"A2$%X:1'e+.'NѣG~ʧ?i^[7| uHRYr Lt`1&75Ӳm+9NɥDqOʳ$[[9rO]t%fμyN>vݻ/77ٟ9/n?tիWs_8NSXX3஻e˖1kLHq_W_neÆ<rqwӟ|绔rM7~:vŝ_'̣>x/|4uj$Bzv~wS"ݤʛA\ToΎal4yTd'19XUv\E ɁcMƊ/<?,}-E$ȷ)#EۜE@$2)FJ:U0nP`fdvL;: 0m=5Zْ+MLz8Xu 2HIm2 f:D7W#1'ONiJ^[d'+f[fu k .FGG/l~/ FyчYd1[6ϴi8̳|&Q>-ʑN^K/^Y]g !928A|ԡӺ9hm9cWSӖ砎 "R__8' 3xm]w o,^xW\q%}}}$I+ ~'  twuN_ 8yD"o bz5' g[z]>%FdfXYաuXNE&|Y>gEY 2OnǚY ,-ڂ߭Z2Vf x!If0QP!T ! ,~d7h' b:8m J2\deNX0MLDրfYjm* 3f$@3ɚ1bؐPaXy\Z0 Xffmuf1Sl%C:%Af1nשψSif̨禛kç.8t:M, N!8uٜ|tu8iIcXwizz:%\JgG;کtgdRgukL$:tu;(~.Mo._$app<(<ѱ11ΡCm PWW%\Qokժ+Acc#W_}5' j2{Xr>>ǘ;t,elEch)XH-7dpH -o`֥-I{2.uXi1?^GIA">rRz>c&Y2hs }Bʽ aKBDM",q{IM$all )%UU,X}k=F"YF)))xpg>DXzHQǨ9SN_NUU5hx^B{$=^ q$<#EEy34пEaQ%'ISZZJyy^|xYjQK_v^5xs%uC+FlāMj_..|\p~( `rM\|(ɀE.AY~z {Ɣ1ͯv{]B\ͶTvˊ~.j s6b59&tL:0&~WV'gi= X Uar^HٖMf[kv8  ,'cj?LhbO WF#߭5$7c2hS-dF[Ʒo`7vvv011nZxOicj#ǏQ]]] DKkM#ir s_̺CGG'T X,Fm4шE928ar'~WR~deR{\ɜP ;#1aM%sf)r%7:"nM:fY^1AՠeuYDlZ#˜[m|獂&1x$*)ىM8n 6Lm)C;% K.K&l86J\d,lL31>ћmwa6fiu΅.h14H|Ԯhg4ab"D2ID )Q'Nzt8N"z{zp(C1* R}ϒ;o?{\ IG7C`UdŐ,AYr7&7=6fV@r w?cқE}nƖ0dL\u(9# ivD!8/bS6M{͎(AװT:L!>m f=Fo&wS`i*EO(,yܦ{4|Y|HJޞ{nZg5 !tHt@F"Y5L仓mq ׳+7ߖ/- z[:8dDzuˮe B\~gkg˰#[u Jj|I0~N)hǾcۋ)L؊MkmF8_ws_|C?Ҕ c\9>-v@IbIi^O/4(܇2 jߛ J^ o`a D>WO ?Ĉ|0mKa~xqP-` VN#ģ,[eeka:3{W{ &Dނq-d &~>XE !3 wK}k<,;Z PHZ٘tG[ɤTvdgbEhƩЌ|oŴ͙ݲ^d TA= &4Mc(L]¶iqeJa&.Y`BAx:1u0"%__VNE'WL8TmҖ+3?a6T:դ^ImJJ[y0X4ak4m|T@%Y%I͇ZgJ6l\U~83QsvGkq)a$fҖ2+ald'<Cg^rc(LO2|tkfC' ɰMeɛՈpZ>maK> s[@U!?y _fxV2fÏ18FLW W) e;Ƶm/TcGI>]W˜?@%"һ޲u'`q|h%cTUUL<~\wTJVo ]ZCUX4 ;w7/8!-- yP!wv]o$>9pд|!X0Y?. /z?C>rpկ0h O9ҡoĭe`ԟ -[4t=< ۶;Aj67e+%l9뾆d'xI0*|?6{)ϳW%Z ` _қXM(["a0zD _*yUtx*A(΍էq¿ҥO1b@Q<Vͧқi&M-+C`CVA?6{~T6hɬ_d6?Ţ&)m }*8z>oמn_apfWCϠ3hCRo}}rQFGF8~@D2j]~iJ(7h}T1@,1vǻVtI>.mljCZ㝹BUtxf!>Ezl4.cwTH<2мi<ru 4m*q2!Eb>Dɗ/q6\9l7~Le?,"_p[w{5y)dҀehYX}I(nOZvfGω hll ?$mEWYUō7@$Ge̙<ԓq 7yf"(|K_b$I~"Bp̝;}sMx<΍7@^^oѣGȦ͛b߾}կif*++ygBp3OSRRB?W_}5sCSooPZZ>NyGsK wiB|)(v%%%lٲ޾>X~ymܷl߾!7x#%%%qFFFK6mLYYNyy9'|2?7RWW3> \s=KQqs%m8t ~;]Wz).^|EGFH$r͞l2OŮ]ټy3}\~e)..rpQ."Xp!< ]vO?4pŪU444|)*,$Jr 7sh;##|ͼtwa=ޛ(,*K/?)555\|̚5C |;ikk7h{;_Whmmalf*E7o杵k)).[n!ye%},YCmhii!H޺u|K_cl|Rz Kћ3-)gsƵ֩(?Kq&kaa:eMMSwsϮa,F=%NJA*|t?[.g /l&PEV¶?!F?cdښ;'a}~/GXL k/eF6Yc=_ulSp-˕ y5= ɼ#lBPSS 6mԸOz"d;^`ݺul۶K/oD~e'mJ*oX:D}}=_򗩬]>̞{wr%˖-kqߔ ;|[n ~KQq1z+]~9JVJsIMM4lR9j.8|VZVWKZ`Ϟ=̜9xSRR|}1g"#G|+vKb1JK(,*CuJJJdRUUũg?YKCEEqf̘὞3AJu6P|+_ai֭_ODmn}z3tЇgp  ?F1`_k*DCq`UN҆a)g6&~:M?& e!"CڶUBpi 0dVgX6&B9?L!:& 5V_m ÕAԛ 'a5z4fǀ|0Լ"yF~h`IUu57|MΝ;I?"i,X@uM ETWWif]`xD2oYqy|ǎQWWYذąAOo/H0\cIp!ҩo&NСC"vE~~>Lek^jjk-[knϛGSS .D:ȶ< *9z(|.{=```Yfqip +%ܹ;3kL&&ٿ?;wK/eҥYl'#J͕ :g&//N;vƛo2w\MBbEEE{.)..aFFF8)++d*Eؒ"줪 q8v3gE:pW&k$bQeŌ222̙3)--j)--eٲejkcttsR^Q^~(^g9rr,^Lss3sa޼yҴ}2k,+*8S8r0|N6F[`:qIڏy1{l)?>UURUUEYY-ښꨨ S\Ri޽{q,YݻqCsΡ̚5MDjİ~B0::J^^B}}= 384駝Fѣq>lJ|F˩fZm-;v`鲥L>rNgҥϨi466cNN?tgڵ'LF^(B` :lb2Xqä;dd6.2l!n_WLԐV/25e/LJR }|XW[cf .b%`zI'J̱w>g2ha~c>Ǡ\M{6e&>>Р^Qg6 H$)(׻[H-ž92L̤Ԭ7m-ٸ-*{nWەOKPzW->q kh,ȄƣoG$ ׯ .0^nm3mS1S0ksA1rUSh,,{~ 5KCo*tu GPF,3Ir2M J,Yl0ls >z00`^]I1e%CCVTY' 8_g|3 ` 3 Jn3owؾ`‡&mǧGg~-[CӺPh)xd^ d_bL>[f pY_v%niufN2] CҧQܜ(uulװ3*yCB oixdsF:#8?\|}zn~°7FNNۿ(-.v,|Lٻ2@]}2Ʃd۩hlIzvE)]ޣʸ 5T*)HeTۉ6B6'*C<,)8n4hКriAI%w# &$PP*Ġ𵟘ŤѐMϾ>TѾP+06΢[Oj 1>աkE WZ|x8Y+nCðb'?36|uҢU}vmz +>P}d^+foJ%/Kx>mj-u_Ӯ\3 lDڡUh];E%hWz:1lڙK?ۚ* q҈JAN#rYɕ ΰM!l>M12h[)3VEZ0GLYY'}>Lh /6疘۷h2m'*Rdl Q['D~Vصo [ŷ W ՗?>"ni.5~3 f\ՁVc+Eif!M|t%yRqa`% O"d"ma*9Iل?VEK?8XI2QrW(\XY8 G 9wFiuR*)AQ'Ya$ɆSHw+m*@fmM_z}4b6h}Jsʐe{Yj=؈!\-IKXc,Z雺MðӞM XŸ:f- C~|at\&#kaH,9fvwsqi.l@*fAcyf/n]6aڳI+-Ȑv\Xg!gC,zM k-XOLC: -SvC}3-B3fxck=s~Xpic ~̮ TIR BE~\>.忪qJJJ0f>d HSR)""e]y={ ztt?ZBP\\L^^N*d:e #V\t4QիC'hF2wYc02P־""BuI5 kQϚJF ߭0NMqQ1* 3:?svCOO/D1;?VdxhZ>.W `Z]R!}}$D#Q[dCgS.RB*|d%NKEE$^}GG: QNtzLۭpF*y37;W6q(#~t;On}Uo/(QDqA7/>~Du5|ƇITV2wbS}~~aSOw>1;aڴZ#c)x S^^LqABbܹsCq|\>.[FFFbJhmmܹBNa? nH$PUvǑ̞=i+x%+Ieʨ;<\&&&h;|T2I] t[GG7+cK}$0ƓiVol'% fhx 2w!UTm,[B&L2,: ;"iik率qvI4eƌL%;Faѩwx`/LMc#HIz|(NsrKyH~~S|sWg~S\y*y*9 /Iy_SN_MG$3JJ2wCKGZ?l^%Ts>~|lj56(0T{.~Hʘ /a<~Yg_=XwEVtSLLLh4J^^BA?6m H;i'mQgPc(xu{'/ŋoupo|rGw1N2`%{zzxW/O&䜭_.&/D@rTlń10Ϯ={8>VL2-heִJ̙ /KrN)BCWD ,b'^x=.ollѧc&ּD"Bvvv6AJ931JFFG8xGJZ ud߾Bo~"o<DFIS'8{ҙ3I;--߳?+~=&W>7|-^>y̞S)%da(~apA>Ԙ>FFFhmm"{q׿rYO'/`(hʝ<8饣3@X?l>bn: e6;2iΖg3$/C}S0ExypG ݛ*q??ɤt.'1t-sF.\zߒ"BZD))VӤ?Ĭ9ؿ {o~}[O(~/cqwn6_9BODZ)ͼ51>;(/jlJf̠j%5 ?UR IDAT43.=Ao|F (((`ҥeْ%OosLJ`w8e̚5Ң|kH;#})R$qAOo3gF5k`˖-77D"֬y_N;^{7|D"oK԰n:}{1fΜI{{;?8㏳uV-ZĻI'Ě5kؾ};TTTk[o8=(G&K/2::š5k8ګ,X|n|O~|~s5sg_N͛ǻﮥv[l!裏Fx׿~:v믿Ngg'b Y~=d,Y.yaFFFٰa=K,~l:|Et=,8{"8Ö&PTN>[[|d3d=kU>*|X&M}!w{0//)%CCC{#"&&&wD()-OOh:ٳg#dt}ł6ORQ<%?&Kgjog?;w젩A4aɒ%H)I%o>N9)KSo~(mDhʖ,NljҬ|Б뎲hn%zLjţT@I1FGM!yyy^Þ~FF{Xt'9=nv=d% t32:xqFA&ÓMSMFcX0UYd#*f 'JZ(oʹ+%J/rˬrmG:<~6l= CSRR߲yf;<ێd͗&l6Y f[M5߳dS%E5'#);SӦġߍG2y-  #͛^sDgX8;ш@8xJ=c^BT ҥKillD:nd~e1V̍&B^"D"a9)%`zi= 0c}=Gq!#ȧ'ӧ^}s7Ce-;۷xb8DSN9~>|zrwn:<ɤ}"( MŋY~wYbmmmcccTWWSSSx/%%qkt;wX̙3q/q6mW]u5.{ߥ~&۷ocɒ%Ϡ{>45-W?V $}Ayy%%lܸQL\Swmd۝ 6>8.W`̶kb,pf/Hox g6Q, &aɀ9dK@s%&`zl4炙.gPVAؓlrLR!epph4j=)NV˹+VZG CSSnO󣧚uc3...SХ!YLddcuV7Gqfdo\8#^X]q.AbCͷ_g&&"oOw‘RzrPNy;HuYLr)l!/^xHIQaA ` zq 20ʊd%EE|/&1qx{XSo%n&ذ9|%n2-wRIu6|kh,q I%RU]M ގN8׹%[aV]IKw]/’=3g.g޼yKBU1IvECCxhݽ{7#Hm6عs_WX-\4~G_. T&0fG# Y9Q>Lxd5Yrk% \rd2J>>Շ#_?RL*bdd9x BtFƨ$Lힿ按 *++$iq]CӬbN!̳\:B}MuuҗU|ss6 iah=͉^yu5wl~L.t]Dn""Baq1{oQ݋sU$KlK;qǦ16665B{HH!!KyJ(!B cq [pW+S̚YS>\]نZk̬5gЇ>v5OTȈp!pȊl}rN;p4'N<}<@vh4D ?+]-'9~paˣ$\>nCA/OocXqȡضsz##hjB|)8"r 䓮lxjZ;߄޷ 7?Dsl5}lۺGyt1ʲR@Yu VUKZXjN;vN?@,]<;f! .]+Vx/_<ϱb a3eqF @8+V 󾀌p˗:OMMy睇Q)9APf)-٦*SP#GFtJPwurSUvXhWYúxJZʒ#Um1Sh_&0Ya5Z  EQ`s/_*;wDQ3(2BF 5҇N`A@߈X뫺e>@-нnz( |cURm!'kǜuKclBYcM+_Tt5/; Ax=/?` gz6[)yW/ɃQvi2Rg;m䆄= ]>RuO/yt3h6PuۗƧ]#_ɮL>U} 4yQ6T+V@eyo@Q/ZB'gQw?U*V~]Du+RՐeY&'Q*)},C ̈ǂ%g*a'a }& 2J;x"N;$:* %QP{¿gye":Gӣ=ޣ ˫56?:+;P\rIld!Xፓ'-gUXXV5'j~ț <[2>Kf X6YS|맒eQ{rH9kWԥ*ɶWlh[t2" '/X`^,>"΀ž?g!QaʟLIӏJ*rd)2>Y9;p Z-@caQ 򺯮NSX+F!]nd#=]z]#OAAQ3`9(UJ~eCDacRW^R?p9sϓ鋙rlv‰k"(MM;C qS Nn<&}-끹BҺĶ1md]<9{ȱa׼&'a eGa9R\/.(^6X>dD#yoyh|hLR7$yU}k&#"1&W|GI{嵩06C'jwo~%u=<]U)S[ƀ`&{?yG}i_ڗ}i_ڗ?XJ11 k(3|_/[ZRҾ4ltgφ_LuORK?i`{GL-}.}} .C̾D(_JD8A<Iv3 nxlty|d2FZMvNa[L\_uBUeSDZU }1CuZF7x!KAzMW +Sg2+u׷=܃{7܀?+e"X80U'6zϲgi%*$tˆA nNY*q`n$;ͫkdux՟uEL)21~˽`?CLj=5o4HVBt!׍J=MQM|8T>\kt%7׼ډNљkQ#Kb6LV۞]\=ha8CHT|;O3?Sibv,YlLMƽw4 91Xv ֭[۷cݺugi7L~膉L~4Mm`TN )9ĢAyЇ5feH~' DSmyYe OIKՈU!C,K eϲ,?UNߪ,umܯ_?fI =կ||OSオOlк{jCΠ Z~^kW^p|W~wޅwƍ[Aa'/>L ;pdW)qs7lHQaҠO ;S7uƣvIO=QԟlH=sPY<@Gh o{ÁEI¥5@A. fJG }AИQ7:RwT(h8mV(g6^6 Y#jOU1'np IDATJ/?? yP RcR~} YvҽCӳ(򓚘q"!}mOǍ`p0bZb9؁ `zzchw:4Mx|jyl)vON[ #1s'waƻos9mi/ԤaSeY"lذN>dz:(;HK=[ݼzFOQRYF@MY`]w@G W\1hM'RT/oi7UFxxOD+ lpЏ`In,qx}Fdj)(]%@>85Cj,t*Uf*vG0Sac2֫.mY151 AyFz S~t511ZD9___%Ebsn77~8` l$V۷O2%0951B=;YVf^t ܹ;܌O|xx#>酘ƽљz`-BgRqkӕ v<-{V gG񀜏d01Olu"P2B4K{L1ֺTH \JQG&MQ~%7};3N^ѢeMxK|)N,= ڜ )%N9P2ȳ g I9 DU4W>qnur5XƠ4ei2 rutʲP:J"Be3 ~Vt(e9Y^rYRYOhB*:\z~d{[-J=qt{6@?m\'qs$? `˰ifw9,p!k\{? gk஻ J)LNN=;U+>(;wbӦ13=WqlEş‰g/ALk2핥$lWu5h+kk)Cw"=?]LEl߿v/I=<֭Dktu+5}-X"w v w">?ė?/Ocҥeh;ݏ|ox|.;}w,!;05=[o^v9>؅$ɽw-2|胿O;OPqktyX| SbDShKLTJQӄpe\ +]ӑu;n^Rϴ 5K֙;rRq)AWumT-PqeF3W! ݬHXwNj&{3i X)TԯGr'Bَ9Do[GQ?z(- H0rձ?Ȳ=gp=z]t:I|_woi8Xַ/8"L晝%_l}8f2^Kv冢0it?iC^z ο\w|+`b:` gg!_|9:4M (=sl-[Gu567g:kV+l611 ~ß]q%+=FmOn+ l)}x_׿B?gmhXgX}As^9|/PuKtid^g#^b0ƎHvubD{)TI-߲䄰zjPcESJ:j2iE/R:J"ծs̙A}roYr^yYub*z]tfgNK-7݃ܮWܛ0}M nT66~A+HuM;Ll&tySo Z Jnu7/G5K92M] _^t?(~7r[v'VDé':?(݌ ލYL 497ᡇW]S]ﭴWDh p`* >D,`'/%{"Pq3^e.ˋ4gk'!7?R),'eM$n .$ J)e {dSTd ߠ~*cnC)8μSmr2HAeۙ ff0==)۳n$d -g<ăI3/B&"ІTJOzzvC^EQx%SA2 Hψ3*?<_ nH{X l8{.sޖ%},ϑd1u兯 - zE>_F R|_eJ!LE]ҫ}.߅G_br+X(6habٮ7 ,[_i5f )gas׸We))i8"hi+0559F^3sn;x[߄w<=PuϽ&_O?엞:;p 矊_߱'za+wr)WN}ͺ&"}Ve0B7s`2)'A"N~]b:JX [1%9_ttMDl3A'W#=nYF8<낼q# FY4\r[R #6 %)VSnm rώo3뺜TnK_G1j#o!,+jyBy K۱_!Յ/ ؗLW$5D+YK)啿/Y-_֓:TEgzW>op!3O`U m`WNEUq'K-'m1W߼~t-$rwpmv=S`ˮ7pl*y1BtPPGq8op鷾<4җ3;wy眍q:~ߏ,_Ÿ W8Z"߹fI*"/.,`,;uo@SE83DfYc"A !eȣixLpYyÏFEr:ںq\_ˡrBԅTP FVW/ E6v]X%cjP@msy.)0VE3׋k!w0 ]ڦRoo)I:GD$.jݽi|A'BdZʱjq<ޔJD\Œ9gRieS>2|?7g<-^׾Uao|]k.™/z.]<>OczfJTAs8Oλ﵃} 7b8Cqs5믳\}1|x;ފKp]w"U"}~݅ї؉[Ul:Ǟ S=C t(}T*Z޳ ΃ŏWSw>&Qb T) Ebb梁B+{\yiey|TR?"frjPa\w=8"N1:rꗻK?U3оb&nE;r ]d8>SUx,O( VU@*-WX7Vtt˺ LpiL d~ c.I i R}l`WۃU0 S纰H7D5*^==fHeYwxy'v2|TW υ Ʊtawp펑C,z yo0Mƛ\.8])>_ě/%΁wj{Y/͗é%W%.|SiUxg܎]C,n48\h1co `JM҂#їdg^e R::"x+'k!`IeZP,樈e `;xj '|96OJj\̪T(Ol'0)*jwS{5G雞;&SVj09F=eev4^]!^u9Hpog{&T̒zhLū1AE?`~x%)#}Fp␉,$] Y \RL R54ʁ'5{=stG|R+2>C|;@b_rw'4"X0&ܓFD Bl'o4pf{pۧ{8x|5w_e/y1>㘞vnZ`^3W˗g/>KŅo\v9z\pgr]ӈN$H|YC@ :F@GWdDt `;qcG*cB)W$ iyDH@M[S-;o@vKiOYF_.;ZzrpIhogI~'Q4$$PD4oG+qGrDru4 TmŐ,œ(Eal3%m `iqi ہ4IQMr,W{$O2bUWr4$2lAxA4g (5y$[ѕ=6KMgyߓQcľ!C?!37f=o/l)NKlݝ.q/0vNFA(p$nddwݍGxk.S۷/Sv{N:,^kᡭ IDAT?@$? ~[qiC?b{YEZ3Ȳa ʝ+*);ab [ ꚙַYc]I+1'[;J7*Iu*{fd'o1rZ󑝊=s0^%@as.DK(aak31oPxt eiWѷq/7ɩT ʾJ.;蚰"wB^gid/x JQ3%hKNWʲ$A]d;в\2/&b{Bwͷ5uc.^3Bo>xb^"y#O o啝dlOKR ̦ѩyu- dz%fffw z[x ]p.n6<ĦP[ {G9@57j||X1߼U-g El <rؖ +TRQ7' /ud2G+%ol0e>JYhHS#\=@+S;<$?<`jj ӘFݶ+U_TQ˰m/ݸ*::&)pyD7w_#l2V QLՓ#^&U/ >2BD\g!S{h?!KiYٳi?ɱoTX" ýufDM$" {C#R 8Ccq!+CW-@zN>y5 n܈]L݉ UD|`ذ!I^8Kml)~~b۶'q?;5=\zwp'g?1:n"_x:V\<05hOk, Eop?qY;3FF} EӋr2Dմm] G$]hG5Yr@xaUA/tG@- ׈F&Ѫgf>eDSTY@AtV6IN+t=<$s8ŏĉrTd=\7pp 'dDOj*&{-(6O ^O 57-MgP9[U Z>[0/Uc׮܊[vKm4M)Oۇcm;Bv^n8)bw"rl߱pkވ,Q׬GkYT:&s)XIyȎ\WM^޴t* j+8!S$GE;_:0X.'|ϢMJ;3Ez9:XK?*Unϒ3h?8? \i-{@X9V^]ďddCE .xb`m2 2Xyn>H =)z*vuY@H%vYT(k2ѨidGl[4S *[_L YdOJIy|rkw'Ay|+~窗M9{i0 Ibvr;TqJ(!3x-ƀOYUC^۲elV)\~ۺmnʫ05=6$*@I|Ï08k&'0;#\!4H{g+ߡwuFr0d' aJ]Q5xϘ.@ED\#İ 7 @ HY#໬1Hrɐ<=&Ldh>g%יtDTECQ5rUnЌ$hZYa LYOGxcpU퓤|T ]EU FUč7݂7<(6mތs_~-[7>?7r+nX|(- ;Gq{}+Ɵk̻ Cxq"iMFؙ%ʇ]2j"5=Y^y9'ݘF44ag6]D<Ćq0D!) \q}=k__V U:4B(ˀg@үhR9&r!rʲR=p+#2K}M7>/\ן!ȕzY m#Q\?@\`?hZ75`e@S +˅|܏lY"/Xf|qxxcRFҼ&`˽Xaj1 ]L׍\Lc#9N](*78I-D04w"ݻ#b+qwcxjv;q)'a˖Rt=}}Xu{?9/{)vڅy gÉZꈿe1Fڧ gFr*Ndd}J!"B"ǟN T-ȻPi1l2bڟIU^)B1xPjY MV%;Mg.U UR(t~ѽZ#Q2 0XvR +80G zOT"Ij۾g?-ou2+c ,Y&Ӡˉf`G>J>V ~9#&*Pt+ "7:{5{:<`ԡe$:Il98/7ʝrmeNMMELNM|<#صk7m{'SW㦛o7h(K{QX`U8n}/Zf7r+y38[n^{>lxAY|m|ÏY^'{_pi8uk 7vI8gGEFoAFi{ǰuSxbmon +V.Gi Ze]M[bǎ #n_|:l9(y:&"οNl4 jT90vwn3X%oÃբ.Lt9WUOIH|-%F_Dp%9!P߄up'wUl#_V׊"{ C(7OcL2,J^^G uy! ^dǍ2SDJ=Z^kU1oB0$@OLaAJ}O47W.Vwffauy3_iP^{41{Tm۶U޽ ¿[߽}ꍙR(g i2C1L|X?ffgQs^R%BE,F[-WOTGˢ(TYկ:WvFE=KjavvFM\^y(KYYYRv k20ncPfs{Ȱk$`%h;dz, 4 g۠033[h)tk8nIMbp.gLS ;-x5u5/;Ղ sLan=W0@ k}Ws eWuåxYI+#<$rd@2yM,9)P)j2 %K_neZ@<U@MJUSt]&ِjK.6 Vk{ nHJ]׉*)#,Q/'>~Y!BfGjǏz)0 ,@@]M&}wFdd de+n2yܘ`i}*UiBEp7y̎E-i byddMU#kOgm׈ufh3b쿁) E=TGc~s3a:5D=$h4LyF;XifTvaR &% 7 mގvgW% ZPv윴ooY`ZFr;P:ff]=۶hmkg@+l43`ΟCD8b}29=$m^ݝe<Շlc-m?`G$ HF%$v4RztTꙗ͵bs=h %#J,R,ˠE.( ֽs["ʩ}b".2,TD';-)]Ց'"spc@p{`ω^JE%CF#?DnoҮa2 I{r_72Ai繼$U35`WȰ=1w פ(f@!%A U*Jm%P({_ˡz=G>)^vN3-4EQ zfm.Zfi,@efy,-4MnLh6v:zh F&&<`eQSv_e9-Z vONwt((ܹ  Hv+!vC^!^-v~/a FdgT#HDkHH.4$) tNB/8`!*, [șxt^g|2 v-}ƈW׫S GiN<09ٔ/v %fG !PX=bJxhM0œDW+aI!\J(#I@̛,T[x,xةElo}5qe)AHYjpK!_wTIZ*:̻ !r$gѩT %{}4P~ Z6Rh.X0>fKug@w힜"6ɭڤk ,A:Rvaẏ&@5Bev;vxcO]wp;!cc-dav^f8{, Ƨ"#\v yz;d @xD)~LAsTIĉfa#-O;dNnHzrm9NXd8_RnZT[T A}f6am7\?׊d-,8eqcHb[e =U4]9 [e=,HOkbuoiv5p)m[ 2ӌӻ]5WUv&5/` _/ѫ,!-g1t_bũ(Kt{] 2ӹˏ^DAPP(Kf]L;6Z2Rdmf:$f^J(z.J[n(mUv;vc8.zEPʢU,^y m;)Jqwffyn `h@ݴn^uecMvo@\Mj3oHC=&VvJ2-}ik>ni77Q2/IP9S;~va]Cƃos9jH*WaD'Q)nO֕Խ#mP -p]+W>r%STY 8a{H 0[ UdC鉠fYBV8B ´6^BgHn_sBFB^>dnDsecxHp-uHAҲkn_Jxې]@4)RyApZzk"Or䒋06{ [go%@pJUN" j9 IDAT2x~1u Q+ _auX +J}ԿtR悀T(W* bDm#ɢ~=j6}) S=xp,H@=!64u|eGeuiٚ3bDU-۱]rQ%O"\ҀkuiP`U*#b  x}}xL, 3 ~u\ P1mݣi23Hkc[F'g2ZpN5fk Pc\*s%!>'v\,#(1uLlV?P zl - -gK%8HiR3 7528[p>ވ|c3tcV ɔփ'u͌eqF@qcxP"\zU!vPyN{4 }NIūSYRHOLX/aUVl̝\LD{}̶-a #r[G{ߍ8Jf(v-ax3զ>( Iv uMEJȭܓ(8 o'jNg>a2F} ?}=r JPHm !R=7X6ͪl% ,yk@\trFXmʒ+Jo.7'+~=XvX дθ٣v^h0x(y揌P\.KwRTЁ/QF.5Ԃoh@8:c[ 4P^Yu opJMl^E8ߦ=v*/ ~[^Io!R¯'95iY`Jy{D~΄7*F%Š U 9+oBUߏ^9EJ~slH==)Jk;AhR/@rf68m+p>=7Á@+op?)y#BB*(`!D$Vu}6~Sz'觃֣_]OHՉ .|SҎW&/I|.!@ɟ({zr7_ȮؽX&=tJNe$MmAAY4#_pO<EÌzA]!w${&H7ՃOl3)sC^{R{msQU6^lBR4 RnIZz&hO+[(WMRuxKC.edS?ʓMd[0}9Q?vՌv! %I~G]:%ykRh"W{)ǶϩKQ?Y[",K1W ɻJ4LVPOG-\wٔ=?| 5 Ѷ'tR~ÍIBAӗxp: 9R@8, Y?U>QҮ@29ԣﰾ2˛~*3h=8oԾ39,N${eOq om=XlJuT#EGlY[ujP zGUT{2!*ؼ!y-$GJaGHxCJ>%Bz+dd;j#FRcEO^dm5M@9@l!m[T,O 3 y8oc^彛IGʧ|Vn,zeoCFH_5S(U}%~HkTJ֣,,^Dd4P%h$u nV7OW=*z;D{̻zVggp89 ""`$(8A#v/unMzH:Y#EE$ "<8cֻ^@~災ZUTZ%>x u'SM;cB43=@K-/Q43X5SM+&^j{ X)ΕO 07{CUU+'k'}=R <Jп=$*+*пBD5WWŒ bڇ: @]4! H>X玐`sOrKw 66[sV]]v$m@ a6 =փQV*B\tCͦG9}]~Eye_Ѓ2Aub8EyʃAl͈pPJq9zx8j=IavWu)Z)i70% LD)]*{+ ĠCo#; @&aⲱPWW|z<}Ѐwc%7l3q :r㮸d+ص{-M9Oh#@O@D^'gZ<fޜAq:yMU#6^?΃ҲЃN.= qxѱ;z/˴:5c8["6^h_ _/WLAs zl'G = |=T!-l#YBدJ =LJ6h!hr$}%Q[INEWe#twMk\w`IKIH\ƉN. qOH'{#]z0K&MA| gmz`d_X&uhjض_I.%lzLJ,W%&K!DzI),YAVVTdiz]ƨ#vN*,FoH_Av8(}t5ڱ?mѶmgqoF;vǿ%5} o&ޢ/cK"!w1\21bd "sQ?uBk`ж _/=oGIz^0״ p\zF|H^D'D-5=_e#J;ms-~ FQ=`у21 %QGيsxxQ=Bd5> 9I[f4p!'Tj7AzHXbǨضG@[7' foR*Jz  A] ~,PQd=ְ5=>r`Pk`%enۖa_@b1+`y nEJNayAMıVii6=\4Fu l-0ϻIOO#(-Wo1͹g3K|C"zkQ˶#da؊ƚ>C?D6MY"41/-ŷ`b6?݄M1 =nW[7?/-tke>IuRI])L;-qfAXaKG.d"!L-0-(:6 o0s K.]K-I0;4zCyRd}g<)M*6%_[A e0Jע)-Cr-x| pc1/&\u9ؗ ي ^tR[YhP`}\oZ]6{lesr!V_wͣ!}L#~e ~WfAmIr4A b- ?t~alg%c91j҇ɧ#紮.QN|Pƃ">ic?,ɇe 8؜Ehy~&8a+wU `<o6ZNVFsNNA߿fMZdch?ެNIC6<1;ƆlE8rM.G\zTqçiX{XOГ#'ˎv njFÀe3سjBriv7eWlů{U>\Q&_3_Q\qm5}rxn~&KFW9l]\8לwT V\&4hD>C.É'!0fy74?|`!58m021N\mr35.?pd8ssK&(sEŢzh3J-JƣSm\pIg@'neU\Q2Ex6d4m.rFB|O:qt,l}?Q鸨|Nx\?H[q&66%\Qtldr]-*G=OBhrVq ^qF9#me8> 1DWo Tҏw8V[gCcͥ蹸 ;ƨFTrhly\]F_+˭_&/?MV,1\j.gFrnq)V^QwR!9u%ܳ"qg+rgerel}/\tlhQϖl8ΜGrcTJ(Sʖ\( :b7n~#up5[=cyt\x)\Af:DBxj,ur)IABJ3 x{vDO\g#G_K\(56?_8_N쫭l<ΥR}\5&$jʎ&\eO~''WB%( E%؊يGf5ߕ%c9/G=>j6MlzWN.=Цm&pc|سEؾq5&µz=jV\cyp#G\?dP.`7PM> D+;lleU8sLl\ݨ`+"rwJ8 lӗBIϕk9?W\Iץo]IF|D5\GzřӸiK[_ږll`QIv \&CQԿ IDATl*Qt(=cɥ;=.eR2AO,JK|c[Ns_Q:GW*_EŨElY*>l`^WǗ'N;Q,L%1lEhjjԛY Ɯ#˨жcWs%Z2D5[iG\:沔+K8H-ul#ĵg_TsXZIVN|/u?vo+ @r;ٮݕ`mcK NQrdhhi6v!.mdf8t & 9< ܄L&}P(@=؞+^Nq[g).4oZ.g\Efonre-r}&}*BKٳ.Q˵U\T.8(OqÃC(M~~߸zC)EʥC)Q*Q_wR|ҍsFۿTT%w\=\%3oGP@[iTVV<2 .|$ޕ%׼;Pb9TAuuMj)uM`'EGMJ_k!J4_RzϖtQEҷ.>g3.~+]~[6JZO)<囶~6]lRpggl<؊rOQd2řsW<|f8>ZS8%u綘qρ+&lrxpYKP\G%!xɎrn*(K I}>iVLոj|>ÿv6t VD#~G6 $ +0WP(Hkq| /µs[b̏KE)qL` ^~E{=6FRͣXrrD _9\_Dv? ;z猿} RRw qRk-祔QSjMd𰂱Hһ\-Co%+h ?*M݋diնjMŢ |!rJ@BW'{-$$SAn1z<^O~j52ˣdJ56F`< bk+rc* 9jWv)cq ԒĔTB?FdQzzf`+$2Fh¶ T#sB5dgԼyl{Uΰ-lQ\VJXh8lDz۰V!Yx?x2 䥷>LZme]1]dDSـE _DkXE OJ'x)040)Ys ,< UdFS\bıPy#uW3>Ss"7,ͻQڴwIQhCj`̩H~s QDH <&p2A=΃VrbѤ1_(===H$\ΈR-+z-:_[biT@yl$:@ZL 0H)M6v!,MB!+$hP+L: 2D^HH$&G0y4HLQ2b)!踀z0P@$/,]KޱM iB0[@V/B,)'{hWkTU6 "Byl0JvUXI\PfPSH1χP)I}Q YdR`6P9SB@ #A T{[v`@BTRi_zpr ` Ñ6;Oy1c>6e"ET'])7*oUuP(J!lB jH$B2遳\.l65Jl Iz)g^}pX~>G["`&f.k@Ͼ C 6M+HY9= BRYfTǓg+!k8 kDLyK[Μ ê2xlvqC88AYhJF7؄gp=a"rDcRI﹘T*\.! bd$l2끭o-B@iQ켊J-4z6їy09sAJSjy=YW.p390`sA!Q&>BsMfL:dxiMEOd^TVJ%Q(d@2!))s)|osvzJ}hzUw;P3$KR=ϛa]Y&D9\M8X, Z-2[禣lmYJ_c z8؀SЦr:̉)5ɴc"$.hMR1!!k hF2z8죾{?0H:TB<͂g~U509 eCSRjUH&H 2b+ny;A L:B@* tksU qv 9W@SžHkQy,@|J&q@ c5}öI_%oGɚ'\fDVdsdIJym`j-~x6w %c 4bϞݸnpZNĬo4{ckdtQؿ\w~ KL㚢"qCF%-—Mzy|u|W/p[q)|qWI"'CЖE_ s^ :B5\Zc u/=&}(X~rOh8pRv|0o!؝jQa!@\FiH%L n0z%u9%.= athnТnMyK87l6 l͋>1'lAN$R$**+ @P@P@>WMM%Wl/ U)$ zYfHEQ+:)ץ4Sϟ.֓p̴nfܮ D" ۰fuU4a"s5;jjX ?iA 0d8{(XNQfΝ x-=:!T*}طg9mm8Dv˗cgo_Նl6|>߾݃Of\7ǏK3ouuu>ֲ7w{FÝw߃ .ůs@ !1WCRP%hy;P3+HP #Xgilx Ԉ2G:1tqf5}FʨC_q"DWJ@$$z{3FI )Z/ob,< wȴB*$޳Ahۙ()vs]I4QͼF:+OcBإec$hh92sYq_p d0vJ!qwQ\a 4 !CkY \; `عc.u)ۇ;o6loˠ_Oᗰn{'6<ǟ~Of*~gqt)~ӟ}wϚ ~ oرcx7' XAX( D&KGƚU{<_cޭ-]7rtuvчfcEG|\o/ڊ,o݊'6nx/O)%~`'q;+? /XM[% cKQ .@H>QEKA .?4W؇ Ac-Xw.= n<ȣشqz{{H$q}ckamxkx`l8f6  d:9/ wswA60oX]}"D}P1 tzS%OKJ!2Z|{'ek9 0JF<| %'փ2%LPavӆR}X|CyQHcZOɨq-_xSZhMhtu0v+z`s>@*ߋP"HU^T:FRԯ<W]UPooLtFkL$z"{z/JL6ݞM-F:Aowޣ7'-&As"NjEzN䢜C݆Bd*\ HIUUB J/m'I!؈iKH$[5$ 2, **+C66-HB$h0LO!UWz"?6㮻pi tIPKIBW$LhB ڠ Me20 Lw.$Vdg("#ޡЁf&*ES&i\.2)AO90DCK^Gw$чC`m|j>8)"F7`\ .iR,dTDp;O ︢zܻ$_-Z)Mt.Ho ͢uըLJ_*漂< f0nxrs%w9X3Fc?D{&a4/D4`҄@&JKVq\R;G*Ք[)~H,dDQ$EXѠ-X]:z r>.6 O@*5ʥpСx:8vA¾{GO>x9tw?w/Ǘ|Æo#2vī1᪉X47ؾm. Cpi;Bk)|q |sa{^0~h;?{V{2\wGX"l޴)dD"|._E[k+.1i,xU:k74}4]'smBVj) V *6'ռ (]Pa b+8vV$QK[3zɈ _oT 21BՑ~C_ `EX,O'V[a>ī <@ƫ?GPsb\d-ҼL9ڮ*FPD"y]P߿$ ŽFr Ί$ I"ӢhI%!_(x)_@uujQUU|T*|> }*%QJgv<**R y,H!_2ʢDOo=iTs{'D27aJN$ܪ| >l;jjiR VVkW.\WV|x{& < 7mi >xeB_ :d2|+ayaѣ4y246/W_3 'uS`Ȑpcɐ} 3T*Ɓ‹?{kV#HoD2*.}y%8hLKܱc/ c.Lolk5k0᪉0 翇1cǢC_^{WAJio@MM  nj|ϣyϣ{`uao^o͕ĉU WRA&2F2jE$ua̿t16 -it?HQ%:!}_ӅJ㘏ն&`Z`EIs>@K?$]J )j{vO=!*D\<7 zF+x:<zFX [5BKhZt@!Gљ/ %y@<|>j,`IF=+8|vW T*\V@t&_y}<*PW[ A{ yt*AQ(C`-7ɢ \ޛCᝫAoo i^<<@=WP<3EG#_-`164Ҋ="D? Dy\8~8sMCCÀ ;e|8 rةڈaxp=~wsk4C~ 'դS7 l d~.p}˿&^} ;Ϣ#3;9%ԫ%"\w@KU*sb]|(dR|$ ?5yG8 G BnD"Ly1(!DBd2ߊ,79CE'K:6^w3_/dݭ\}d^|m+ar_<(oVJv tq Rړ+\Z<(8TRc(@=x8Bm D} "'i %ȮdkaXZb' fy R+" iYH7E 9Htg@ Ђ~}/2ys-2PVA2x8[pi@A% 0Ȳ%3Ԇ4d<1ra;E +0 PFEnJOJ sf+]L, ]+Di!tlJ6A"酒:(AVȦHЅ*>#a4'0$ `%v7wLl߷VAe:0 #((2Q}0! [AV<` "1|&s GO|Bj6VDfuUx\M_h ;Қ/Ǵ ̤\BIt<]1lwvj3xcLDd5HI/󫟝TԃǝT6łmk*`P@he -I>|s+y5O!_M(ԝ-\ bH+8~iyC.%5]wSZR(Ur{+0R:斯FgAbPg  JJd4cBeԏIk H}3) ˰/gGRԄsNv/L{֟Pt$}} rR6:ΘYaAm }Ao/9.j7q=N:%zxc /4+/߁ HLA PĥoG⾩[I;:_4C>F)z![[͠y$<+y O/~#+̼a?GƜS[7ƻhS.XcՁ<;؛Z}BSflR\6Ppf I yUKWj13b-It-y uL 0Wr`q\h!h_zO'ICI)C6\}x/D#EF S":;Us@\{!{ـ\GREm(y3Xь8׵ha&|g6|2U>>1J9M紣t*u%)N+g˥s6~GQF^sѲ*۱mL)So9-G)ܖ.}F_z5쬩]Qg _g ߝ3w =1֟% {*EdS_raΐj_r=E Y 2^Ud+*>`|-Em|_,à#t,EsVrxK2c(0JiDu>nit!4X*xqTǥ d@#\ksM|?%SxiqT]ikQer|fwi%|w:xGѠǶ8GضQy\䈒+V6:۾ N"з tk;Q4$7.H(ġJʕƳ/"\\Mŧ~qǸ'JR4؊x-KlTJ@"%{9\RDP]J-"8RRb RH`|AP$S/4d1)dRjS,]x xo!ZzJf |z]'P, Gξ9 WǦ MK][AN0"'o0rQ}سqL^|-=m–oh81Z*Xo@B)H ]jKQ}\EEV`9(EǙJf/D~.ZQǕ۵29[){k]̥TJ'-[%C([Eяa'pZ6vE-\4xƖ Q4]yJLܞhWpTonw7֐*ח (|y(wRt(Txy@1*>oX@X^nBn`I62(L[GLJ}Ǔ6s|=.Opp gQuX,zM=؈J$KqJ-*q1qxڎ9_N,5rѱ5f_\q//ʙ:8[я#G\}]QR-;gQ}'qt,fVUVVұw.ʭ!4E(QһX'-!j}#RzR[ob !LgT$ޏb[t+7 Y9 ɢXQk:FJ/Gu!Pj蹣WH@o(kZ?َ BJiY}B9˖-su… 67ccw򀭠EzS`(_,"_,"W("/x"ҹoBBQ(Sw` RP||\G~|?D5G\qQ\@]]b: B`͚5qWW k֬ފŋ={6>C`:3? X|9f̘d2.?1{x lذW\qNXҥKqI̞='OīK.v^x9s&LM0g]0`.YtR- /e˖k_^yH)CRJ޽ .DUU>c͚5=z4jjj!Q,ȑ#y8~8~_s~󟣣555xǰg,Z3gW\[;X~=f͚!VZL&|ܹsс9s7D[[=y++W̙3qWb8p~alٲ3f@Xo[W]uۇCᡇBGGΝqn@"᭒v؁ b̙>|8~ӟǏǶm//}v߿g֭[q \xxӃo_|1,XT*Fttt0`L3ge]w}ׯmqqM_~Xj.bvmذanH)n:,[ gp5`ɒ%عs'~|1b%Kf_Fcc#>KhooGEE{1TUUaŊxPQQsbŊq뭷bؽ{7nF̝;t_1p@c?ֆGyB`ɒ%[w^E߿6lwމѣGc޼y8s ̙)%~R:@WWvڅ+Vرc[1i$H)oȑ#[aʕ+f|E]]֯_\y啘7o&M믿?<ϟw}W^y%nf,X{3P]]M6ø{u< B`ҤIXx1ݻ;vヒG}˗/GKKe˖_*~a裏O***ۋ9s`…xG!@6ů~+$ ٳӧO/KTUUֆoǚ5k0c ,XwߍCSһwF&Q{n| _@>+1c`ƌxс:3=sNi<4hP|O8ͮkqG>#GqF\2 .A}VT6JGd A!t5=1Ѻ4K7#? wf)v24 )hjjFSs3.8pPۯRBt) 4^0mJh1ttu"{/_TW5!׾Ţ#d^r\( Y)=@[hj&ZUҋ(,B}/fH$п WǸJ_YvX~=M|+hmmEOO:̛7~! &>(,YL&ua8zvZ477cСz)DUU̙Ei !Nt:%KqZ s/|oj*L4 |!կ~K,ȑ#1b,^XՅWmmƍXj~iKEGAP޽{b ̙3---ܹsqwbGɓ'cɒ%8x p=`ܹx뭷0p@L0sźu0a~Xt%KছnB.Ú5kpࡇ¼yqF ylڴ k֬IJݍ3f*nFl۶ 'N2+Zl۶ < VXӧ<ى)S`޼y裏܌'bxꩧh"tuua֭~zڵ \cƌA&y~+W;|˗/O< ȑ#hii?ŋxꩧ0c ̚5 /|K. ŋ1c H)su_5ׯ1sL1;w… 1{lTUUaXbfϞBJiӦaƌ2K< V^t:/wΝiӦ'Dcc#`rx뭷0sL9sƐIcǎȑ#Xt)>"Nchhhĉ1w\,\f ׯ_#G Ng;~Bl޼cǎņ 0}thmmž}O_ѣGىn g>[B`1ct< VZt:#G{żygTUUg H&{LǏǰaP[[tvvbРAzgQ5!*++ىl6jա8p qQy?#Gرc܌z +W_T*sڵkqyƒ%KrJi,\G}n6lW_UVW_ŴipA|8FӃT*pӧq!i444`„ ػw/ƍÇ>2g̛7FBEE.2477M?{˫1bرD\r ؾ};ѯ_?k}?#-7\y啨EmmmmmŶmېfq>1cPYY uuuz/RTUUƍ;v^%\m۶aРA2d*++~'ND}}= zzzL& )^ʿw܉}EG}/aÆ_~B|7nжDwGbx4G6ECmU%=id{Pӯ@xرTVVa Ed!jkkQ__ wx/-p:::فndd2 TU$فN ؈Vd|Ɋ Nлc^+, Ţ$:h~M`m* vlyX@Sl ]9]+ IDAT l޼tSmȑhooǠA؟v½ދN 8ؽ{7yߡCpc˖-EMMޭ뮻٩eijjΝ;1o\}ظq#f͚Ç BӦM֤wFlXf_JSK.xomm 'OD.CCClHJpԨQx뭷p)L<wVXv_?PQQFOO{=4556l+pk^vmmmf\LbxpIL:8ؾ};?AoSO=oUԩS1d;vj!ֆQFa֭ ;D-Ԉ(\V 8O?4 ͛pС8uk_ѣ ӦMSd bk cƌ =N3KuSPUUyl޼#FDmM-zs2 : _W8 dߴ}A.Cc@3Rb֏-ޅ]twCx֛o+?"6lz;vlG.ESSnV<2R)ds9TVWIhy>l%m2@?FO~MӨ55F0̣ 477@.Ô)S0|p477w|ǕJBK*t ncۉuzӳn묳Mb^ġ*!QH # Q{/>ϽW{λ9s̙3g~Cdd$ rss@ףT*bppxPȍ:<}:Nrr2fʕ+jTVVe2e 2einn&&&(e+^Gףj FE,Y"ː`0v'wʸv###sE[{{{IOOcFv6:jΞ=^'//R?׮q5 ή.76RzWZ-tttP*q$OM/ 54088J˗/T(˛ɱ#4^ƔPX W._ǯBs-fϙKtt,r25%UZoZpV+yy Rc~~x{0Ϝ\zGjyɧ X!l6bJt:N}5jk=g6^߷HsK j DsSUU̞;6^{ۙ3w.Fm ۗ_2˗SQqVDQ[~ӟp摖ƹs稨85:6&-*-1^52`Idg8q(v.i_QaarQ^]Y= چR v)SBt-SxoIɁdM.)fμaED993gAYEP*3=>nX5 R*PUϚBYdBҿ4j%j1[pchjg&'+3<:Iw5WRt&[+uϏW^yg΃+] RvSO=(yxHwdJ;9W #_ӶMVO鎇yvNIwoJ}r& -C_48n>Y9=wm+'Up{waI&d6gܕݻ}t{C=;*W3gNt^e9k1@Ih8E22GQXJZ[[hogpp6o&%%|8v ː"v\Z`Tj  `0%+=<=Pit\:DoOcc؋*"A \t8WS?CCC 3::.UAAFfJpY y3dqNUHIKߏ:ɟS'lKDQWZFlYu*ExD}}}47#hg2e +V$"<,[,+`-j3L\rK/qF\f$Gbl壵%{/Mԩ70 /B@P)hTJT** ˝9۽cwmDZn:gnF7Ypdr(d"n tb6%frYNA!DEdDFFiO\U٫eWi8)_ J-ǮJXwʁcpd֭[$$$ѣɜ9sqȹP!;;{3;CZ[[vi=.\3fpcWy:N>xR$>>^c<{{]g#?6m.V|}}e^㻫Gw@<<<8}4)))xyyQ__/o{|c_FE ׮]7Eoi4III˺:*>><&RN.yӵ IE `{n%5hۈmyKſ-j% 0Vb dXjϝEPCTV9[}FVhrgDaE nIjt¸uNl6sUx"}}}qevEww7͜>}ϳm6DQ9y$۶mիreilldpp֭[ikkݻws-O{{0Ay׸hp[YYIqq1CCC۷bn޼);"wtt#رc2ŋ/bX(--СC[oq1qFg6{;tȹs8v۷"SCC۶mٳqqoΥKG/̎#.]m۶vA1ϳk.da4)++cǎ\pvJ?~̶m۸yNۡC(++cF:DUUY,**"11Jŗ_~ H\GAEjkk)//gϞ=2>>Nqq1ϟzz3gɎ;޾}eQO:ŭ[uuu2X_mm-㭷lٲ1RPP@EE'NPPP ׭ݹܾMpp0zsNFFF0 PSSHvIGG XlA9}k_(Jʇ* v`IWNv%s_UUP(PkrII](3ϐhmY], V֖f<@BR2^>>BE =#~g Z0 I  ѨmJ{l`1[Ȝ6@8>NDD$DGG8P)DtR X`y=dc4(EYY9"?țoFd4JC]9󬧧G۱}}n:1<2,ׅjE@QGF 06c(: `S%2nOBGW=ɥ2R)n&;'j~j}N8!رCw'Nj(sY2e /J4 IJJҩn*FEnܸ g>l&..ZBBBhooB@¤o1bp5f3HAA߿A"""HNNf߾}DGG{~֭[)--b_x?Μ9Í7hoogxxXӧIOO?d``?dlBss3.]jJ"Hv6oތ/.\jbbbdE`׿e3<<̟gNNKK uuu-ogg' PPPQ*\v @.]~N>MEE|PZZW_} V:Ĕ)S~:DDD֭[OSN4Q),,d|| DDD.othhhŋTWWݻ㭷"))/A~:Ν#22sʺ}?}4Oۊ`9rZ͑#GwO?>"""e%--222Q:::O>ʕ+;Ν;18qB H %%%x{{SPPիWe^d%t2Zgg'ᔔȍ7p~`Ϟ=P( -u>}Q]8q2_c2d;pB .!!BxIHH/h4R\\LHHW^]rRΜ9#(loog׮]W_a4HLL?͕+Wdde8{b>|^O{{;|GqyΞ= ᘮ; dReɎ+B6ckLܶi  c6 lxt=֯~ʒXx}#5/ eKE޻_׌ɢE i3y~_d4IFF -[xyfu$&%+ky+p!i|b1#VYEQf[l[ '=PJh22:>we@rg r M2wfɈmWB}}=\ry!999Ĝ9s$Hg}_F#ׯ_шgΜ)hncŀ߸qCލa^^^޽{~733$4 {\ZZZ8{,F0`^zŋt8000P***x饗SMMM<={Q222QSSh4XV~aͥJf>=zT̟?,C{{;!!!,\'OsxYf/zfϞMdd$ ,`׮]>>283@HFF[nt:, ,];FKK t:f͚+W*N:^d21m4ILL$00P:u(?o<222qDQB $!!@ eѢE?~ybzz:!!!xyyHii)ZRWW'jC~"f{_SV,X@__ZV.s={6F'Oru:::䙑v IDATlFFӧOСCrt:VX[8y$---G?~G^jT*%%%\[RQQ}g}Ʒ-V+Mcc# dee`9]OOO"""8{,XRSS9x 2jkkYn~~~wCղrJ&X!002f̘AFFSLd2aX!33sB̟?"##pRSSe',bu ATݻ9w>>>fسg֭ŋϴi\.}3LUb#l_1MWwC\c``RINn xRxvF'婧$9FE̝^D|B*W3e: 2*Tn^od>K,%??Xq9{,>gAӑp!Ҥrh͚k v>^m7lK%""`}]Kmm-}}}jOAS`4140?6w aUDtXuPhRb[:Tr(kY{˅D(E˃&Wb@tt4p%BCCꢯIrzsܥDkk+( BCC"==F<<<0LKMM sa޼ylڴolN;w.?7 appׯ3{lҥKX,ϟOKK _5Vqz=tvv@LMM%??!ϱ16lioo'66,?/BII 0qϖM{~444 "kj*֬YÕ+WƦMNZZjrEyߏ``ƌ455˒,ZHFȶN'y{{A~~ zԩ tdee* Yh4ʖ5;#oɎ q;8K/$/9eZcxxR҄ 22@jcnDEEa5͘L;v8YB!/˖WvFkk+gΜaѢE2y$''?~ yLjV'dwxxxNHm7dݺu|ߦPhnsjNH߹O,**^-fT*<<<6{yya0dq}/[#☮x_O?eҥ$&&E__KUhk$ _v՛NwI dMFִiL& jچ$Fyzz̦|RJu~L%KXd%ZE֮[G9‹EhcE#"/$mW(lEjZM<ȂCxbV"*I() RDexJJB`|t%VcF L& *= YSn+QHJ#kb6Q%Bdd$/~{ +"6]Dbr2SSC-V+ ?l\QL6LGidյ׿Zfxt,al FaL1b"8PHP Ynm2se{U_W7:%Bw;*Kw)fAAA3tttpayff%KpIKKcƌz"""ضmL6x>s"##ĉDGGtR^^NYYQQQE\\lٲE ٹs'aaa̩SxG% H}}}GFFUUUzj4 ,]oj%>>L\\ww僚\P*˼ydi׬Y@Rl&;;vΜ9CVV֭[t$&&_|ܹs1cٳӧ?~~~PTTĆ d%l6B ** illӧ#q'vBENʲedt.\Ȯ]%''G.ߥKd 6Fii)Νc… Kj5k;vd2 ̙3ٲe Vtg1m4@Rg~zK|||HLL$22x<<<8|0YYYaZ  $$"233BPb!>>gRWWڵk`Ϟ=ő_l8^ @~~>{exxXmjlD˗eDBBBjaXjE\\(h5kvbdd$5 AϏSxNƲct:K,bB\\_~%dggʞNCד#!114 ŋ)--ҥK|6߿vYVY)W )1&O-jd1S8)I|Z,VUj%ZZ{/JZfF7 ʼjB!Ȣ偗Nhڵ%sjo^t G,8pD`92؉wNhŹ<'/:p/dJt [gIɀ+Z]h:>>w圦:++M&-w˱pds眇τ ]Ž_'xUqemn@A@D'?$ܾۃ-1l]/ V`ytE2w(3g͖i,ktCHHJ/lʕ¦i)mJ`@GKy:ì~eN gd*[Hپ V\EbB<։Eo;fL.BOBVPENngQ:mlel܈hKAVo~ʑʕjEU3:n86F * - V;ۈnʕ+|ql;pҏK,WɴHwOցcpE˽v )mq޽>:{'e:q~1_Q'&^:5fwʙ;^dRՆy%MsjR),f3JAɧh0( Ŋ+$:* HSS555 D*Q!j ASj13e.]LKk+@Dd$Ixy00OwW'*(t:-*VF$*2 Z2%`VZufGs%#(heS3/wO aΝݴGx6>I? 0>>m(E{bcZ,5ZL&#_qy,_?ٹ+)N«u&Rɦ^䙍z$Ax/I`F߿6,Z{@RT_(ع?jET26>NtT4_ !!DJH??OEh4b1 W&_G_+S䗿-MM߰ARZERJ%aPЪh4!,p[Xؔ+ՊRk)$., :`SqޞIMg:ѻ#=k1ڟc+8r``[SXXhdѢEԄ?+Wd#p~iz{{ٱc/_`0RVVFkk+˖->ȑ#q0UUU򠐒7FFF>}:3gO>A׳ay8x ՄvZ}F#III>Nuu5˗/ܹs$$$P\\Loo/O<ï۷Dzz:W^j2uTKvv6}lذ˗/S\\̲ed>1c(222µk~Ǐ~#(,,d޼ydggGhXh{ //^Ojj*F;v088멨`ʕttt0555YQΜ9í[ظq#ᔖ7###|g9[#88T*1c|Seʔ)|ǜ:u7xCGz'e&vٳj\ppطose֬Y۷ .8# @}e"̙Ν;Yv-555r1/_J:u3g߂ pae͚5s9bccy衇Q*lذ@AA ,]:JJJt3f̐>F#6l@Pm6<==Y~=ݻ͛7SYYIvv6(RQQAyy9͓qɆ9s&YYY|L2 6騨=1w\(..ϏGyQ3,XL&|8O իWc49u!!!ܸqq222Xx11&HL&|6lbsNbbbzF#:^xZ9IKK={Ccc#|gu7uՇ߫2j=m(gC/eW_q{c(w**Z- o_֯GNӛ ~NJ"<"()@ ;Z9ح(1"ɝ=E}Ph< EF&*5JZ QBnܹ׻*MJ1YLp&y {;~&+dϝؽ{7<ٔS]]ͲeEr .dl߾ZfΜɣ>JII ]g}aŊ޽ykQWWd344Doo/ ףP(HMMJc=Ç1ܸqW_}n޼baʕs 444D[[fڵkHXX)..Q^}UN:ESSnݢ OOO;ƫɓ'?>W&== ^}UN8ٳgdڴiܹ+Wө`ɒ% BE[[R8}4j4v)WmmwZZ !Qkkk&44DQd,^ݻw!v!oii!((+Wc),,bp9|IL&k֬ߟWRZZd\9r^^^w^e޽( Μ9ٳٵkmmm( ֮]1TUU;˚5kd NC=F͛9rɓ'BEϟoݻYf w\\\SO=/MMMxyy`ŘL&xG9x 硇BQ__O^^l644p 8@yy9}/}xx'|Rw|ݬ^6 lذC100 CL& 1c7nƍ=zM6M@6LTWWh"F##44TF¿u% L?1jkk~'2.]IIIr̙իͥOoo/WyeWn+++ٳ4440|/_Ndd$UUU̙3E%Kp)x .__57n7o̯,ڨٳgwC{{ޢEصkdҥKiUUUDFJlJxꞫ~z2J{|,6r xTX,үb@,3md^ij5Op F hu&0AJAʴjhm><":Oot޷!nJR҉v[z{yzABB9V[V >*Owu IDAT3yo=0>6?$'qG{ABb1[-Xzz#r{g͒.d޼y:tlAv~/@Rb"o;׻U044HDevߟ%K!"gOSL&rHI ,pv9Q)&3q/fXd˕(Jʕ*y" TJ~J7ʝYvMB&S '^9ʪ NH+"2=;Jj@ (BAZZ|HÇY|9&Re<-`2jxzzj&&&F>Μ9_-ߛ:u*:FFFjDDD'$)),V jjjNk4̙ѣG!++KhCZZdk]BBj7w^OSS&v%//oR!dٲeh4X,ܼy[np݌͛477 H F$((JEaa!mmm̙3Yl| B[[LOBB)))tttPTTDVVQμ ϣlmmȑ#tuud;λlmm3q<6@RSS [ss3m>Ndphh6'ԋ^ɓpQ2337o淿-]]]/;VVVRUU%[|9g``7oNXXlv{{Kww7r;v #}Ax?#88`4'tpvZǸvٱ7oJrr &8 immghz=jZ/fG?nݢ`BCCe:cccy&MMM1>>Nss3mmm188XBAqq<tgrT\M9E(_-8EO7h `?ݝxv ::" !w3488 Gm$ (o2馷N1 -FF{0m2i./\J)Y budthJ%*Za/M @n5|g\z@*2^^Dxt,uWy?+ R_wxhEf͛MM?o_|Q]Th$=BtMPdlfX1[Yv3`= @PJ n{\)R#{pN jwܝ]pg̙;44T~OCC;v쐁|||N\QSS \rA0 F8v올rQW[[& (n 300ݻiii&IF# pqPT2cܤ$P(2W\!22r1AOpq:QepL{{#m<==ϏlllDӃdbM1|2eee1Y 9symEBBB赝$?w\ &gggˊ9998qŋˊ{{ڳfbrZ pBضm mϙ3]v1k,G9rsRTTÇ'N:ByfϞ/===R|εkPT@?0sȠ Ԅ'yyy|/))^O?sBBBx"`hhH[z{{X;w. b6IHyE?>sofa "2.Yt\fQqwTfs0AQ@@Q@6!,$dvtuuusyyܜs[-O}jw}xG#9s&݋+W⮻վhtvvL^x!z{{ݍb {瞫!}}}^:A\T*ۋyiEkooWGdYwyjGy[.].̝;}}}rrXrT3gD{{;P*ՅBӧcC\Ygٳgc…J8)[`188kע sEKK ,Y uYXt)ZZZ~\xᅪ͘1&WZL&uaҥ( 1c^rS\.l6ǏsQJ/ʕ+vZLLL`ӦMXx1qA,X###ذa&''1c uYJaʕӑd` `hkk… AE]3gZf9իq)`ڵD__~ztvvcY>btƍ1m45vz9}t~X`ư`ulGgVbg}6ϟQ,]˖-C$N3ĺu022͛7cڴi(.tuuEڇuSoV6ӃHޒ |rtuua޼yhkkCoo/ى.,\0VO[K\z%h6app@Y‰q߽ⵯ}-~{8tg5_ȝC{~k֭ǁ}/wށrM[zF]r ڸCP!d \.*X>Y"#.iTxrQ3*v%nz[e#Ni nTwR[E| o4*G6EͿ3G?g&h?~[n!o|զ: ӨiNU4P&OR:yٳ'#|mr.s`ɣ=luFH;˩zt&|;8P_uElMѭ^1imˤb 72K%mԙ;M$7Jǖn_^WܷٯQij.iO>htZCjNUar| C bY?zwp/l6L6Q,=|Y{.g?CsK ǎ;ڊcz!׾~GƕW}o2<};>`xxL|Wt2  \D&'>Q|.,19\*3o:82QdsEP8"jFH/pvR6y:%Isu1q5443gFV#tRd*kҳѱ)_ 9!Gs^ҽFf#Hrüg˓&KS_=<mÖiiXrʩG!$jm*Rvrwo5Dvr*vrO{Q^:!ꂥ)iSzJ%OH4sH\Z.qi%:l6$SSg=>-iz3 W*1eM=.=o̒x5kxN*4QLi,̒j\ 2mmNǦ~ȕT/g+MF׿>MIֻo+[~῵-:DgT6Cer gjQkK fϚbs3[0:ڮ.{p]A 'OgCTBSK+&\s:sbBeb1LC&:Rȁ8d ٜZQ-Q,сvZ@lC -Z^~̙hi CfƧʲo udh2,\T i>?C2xbǎؿo/!xf ުd @%<j |CQP.WT?7S1&a$&J- $m>Ζے|K)vrO,< \]<ÇcXbz!CZbޮV~nDZh"Wq,Y|>}:",Yb}/MzxkG=?CV9$gK F깭rLEF\d$t:f]f~>*Ϣ07O "*'>iV,6_?0G6gyBK{\E۴.75TjcE~L&.6 W,!0 §r" "FN Y=fl($?isx$h`'APJP<)c(2pP`pGhkkìٳjN1PC)߂[Q08&FdleT56H_ڬ @prhADJ6m&єtVz'IBu6ĖtL^?m6lٲ۶m޽{qq8~nwߍno8tNlڴ [n3<Ǐu]|')_!ci~&9zM2Op9 S7Klfk'fz2蝠MF2&ѵѫAK?IyitQVi54}5O=R_6*Zv:=&rz|qr\$>FGPVA64E cp\GبJ7<A$QVc1@͂\ureHFE z1{k'o )ѳM|.~cpsy0#d>cb?2)WAPzi#em h*<_v!7vQuިW][ڗk#}SNVg#z4Gm^&$ə& *MoꕔJҭ:]/)Ki4)?x9@3ԣ<}P}#@(r<9;.7Ăf$&Ck{ ;/`rb#h(EҕJJY!L6*2 Wj8pKM(yQ10Eś (dUƎ r5JpQjA 0YQ&+U4.2+UdDu&J?h\)#@I.%S-4یe]2gi"MOv9砹k׮s9Xd ߏ}aŊ8spi?-ZWUظq#n&8ua||3gDXTtN;4u]xgގG}Vlms2#94lJr&SiP*VZ-Ö/Iٲde-&4ci49Lxԫ6QRd\=LU`Dg:s=c5,لb_= o>_]8(%~$Oҿz$O=ǐuZIu${^.V Xh?vtՅptNNVP<׊3v){wǴ S{ !GS^,~f/ԯz![(c FعI}DR[6ی~/MNlt=P3U'a3a&n5LS4*MFhiJrIrN'-]#v`~oI)MFYQNFF# Փ^f_DXEx`A7CZ%@3y K` 0Qx$L`Qw ^86b9t⇿ݏ<UtqI6,jGZ9V% rPˠ)G1,݌||gqPBPiZ (-4RDVA|l 02QC2bY``8fJ>\K4ϐ:pC@O!:W p 30%##.}+WxlB6*g:Il&{i3;K訑/5 L[s6:I"^$u[ H6iȕi'z#^=KvϼbE€v%:`vltmIV'iI<;I6n42=F$tLGsiDF10@Afpr:(5 +N`ql\ց-g߇9mʁRMK:|n{ag-VlZډ3t`iOh by2| u7kMyk|Xؙ-bS-Ca>Z[[P(8f\i"bVC@8_|%CQ(A0ӫiB 18 _D6F)@]J)MR`}jR|;PZ0:EC>!^xY5! H5w1 (5#1!b=e0j4l#i֖_rLiN.M2J2$ym7w1iӘ*IhIalURM'S4` )Cgzr铦g$6y]ҽ$u0iOc>Oiw?O1N&u6yxӣ}| mR^K$ |7e00{Z2]fB?Y7 r`Z^1nò8(`72Cq7!N{$@PgHT\A`s0m3P;nk@XaS/Ɣ C#PBRR0q@G- D}+KPP>Ufr;!cm&䛏DLz(#'S(XoB4EtD pZL3#f\H2Ho-W `F*4Ji=H80cO+EuMFEt<ҍ 5d%~L,*8% "+0DxIŽBpl*GjBCh=EHn$_iJܢeȑOl#GzRCZ uzi (,i.HV)6zV/$ZQe@) ]љy_ŘsiuT/;̈́LR ;dEm`L=+;m)8xg}Dyl%\\_֭]BÇ`}rrX,bU +s 0P!O䀓~ "", gT R0"  ~wCW0}䓄p|JOQ  KSBbqOVrJ?"l)rlЫ`~}By*p% .<P20C&#g\7aB.C1Qab|:' <L$ M5t%ҿ'O YL.f$(l F"b5mŢ !? 'gYHH)MAA6+KNCr aOfpj҅)RNYf=OCSlq+xCR%RYl izeVnMp B]S@7ў3atF4=b7dȐ*P4hD,\nF0 ja+ʑ-00mD+FTiF1 >`'JƙXF#B>5ĤWG.ULC|PRȕ\SDA!a-eAh͓1W*UeL֎6M6u{K9Fd]W ɷ4}g_>! 5BY4PV3x( XlGj7ZF  &UO#_tB _l m$[:;$ԁUZN* W!fcWBwyDKA^*$ɔ#pў)IH:P)W6k"NôAäeݱ9V'm2sa2YnˌӋ64USn%^7͚b/s˴m ^$mԫ(]?[NI4|L ÚN{.E:!- V:V5l4 i&u291td =ʨVg!x`I9,wJ LmjI%8*ED?KI:Ayz PBǑ1҈p>ŕ˸|z Ԗ < w/ṃFߕ{?Q5ur:q`*fi Leuxp9(`j|_*H &HC'T5d2aJW -W | yJfH >6Sr|Z(K *22<#/IKu O)TU &P^ ħ b BOFIrp( E;!|RWZ䙍\D"ON '&#ɰ#9-{Kg=O|:d*>&g E߄'M z1@ ȑ.5zieEih `z`KmwXzTAFxm)N2t@1~D\'_!0v*_)A: 6Qczp@/se7w;D좕^'΀z/C2j+F,SQo<4xzc:}Ih VY>1E8%!`g(o"PѕX$q((/݀D@gA.u.qv/J1.0 Q#G%mHyZp`s\d2+)SL( btl>ϣ=A ԑ9xCW aDYu8Z{}6Ts ׯV2cdim1u`%| \4UuH6f "XS %_G.ިXsycLD0D F)WTցDdN?M:XQŁPDlcc/F4dQTJMd&d GHZ/"DA?1ejl#-ORج~m4/4}bY'J<@4qLC$zhji hD>7 ͡#,pTleKRtLэH@=zاK b+=Q"ajz $^mfD7 j@n!OkSiklվNb*/`j7TZNDn% зoL=w!pU\\FMG%m-SDLl6JPPvqZ+Q;+x(9Auafbhar$G8|BBOud3Ўb }$ЊFq^Wjb 4L#.{ܙ%HCJ`hϮ@nܳ7-owk|?};<|o?Zy^ukV > Au=q1\7x[omxϕWa ^w=/˟,zQx5pɟ:e\u{qu?BZm?ލ:Z—|2l޴ѱ1lްڅ-6P(w?&,^Ol߁s׾Ag^wI}xH`z2E;]$=Qi LF5…2`U+HXӉ.PNE x("렔vR9evPD! (}cӃWQ(+v<n-X40>>jJ)"~[fϚKv|9={@<]ox .=߿Z6o܀▟UWS-?7xkx?ûw{Ď'Χ^:|S|s$G~_ZuP.¾dݍ< tbʶ: #4imF=Cj E(o H˟90Fü!1p=`|)%-R>Cj&6c( 8nED!j% # |b 46,R}T7l6ѲTRW y4RWt;Pati@x#=r 9UBv  وS D/Se?|i#hmnԔò 1B!)f݆݉Ir/6 IM$Al ^.rO9pzQǼ޹ Wf7|@ÏKq=׮fL]Bp|\xp~z"|GNYK`ݚXrRnjR yչ8v(.zA(RL}G?G} ?ǨT*x;߁I|0d@:,as4)M5~^ԑi#bM=k@1H.JN=~ݿ?iwO} 3gb`p,]&چT`E5;<LNL~ 099'O{&?bX4t֮YU+NÏn܆ xދWرc7o2 IDATӻ@)Żp]?/w R !?ͺ㊔[OuCF).XFtZv/Cuz}1EV*b>ؔPuw0U@&\@xԃRPq 4 (/E  S +IP:@ U ` r]T "wP*o6BDD}!]ԂFfT}V"'P.s012BF*"0)@97cr D9Ԫe45ď"hi*)]Ȕ>=Ur0!0Pu@A E`V#Oԟ=%X^a!&.` 1LHw?7u(~}a7mݿ-8u ˕lRxM81w\Ƌpɓ8r(-1c P~q+&''o2OGZX'#jB#֩Q(XB[4P=#,)=4@&ō2mEJSh<"$X!),&tvCfP>q@qd2p\o[ "5eB qd2.sZ)R1FP"\EGZ,d\.uqdI2Hy9([͗ OI}#ZTtAEB 5PT|r+8` T H3!d2(c|3ӖRWW9|2{jeUW'P_)ïTTk`·\s589'Ҭ6}mɚ`ts\ %^enoż]$ Ӌ\)BkޘX#.-e ˅+ ;pz|!-o7%yz/oSi <6q:vރ&e38~^ 0 礜l~NDg$F.ȩ~-bS-峢/,2T+4}-13dBR@>CXo) VK<#Ա x^b4Y!mڒ!©Adsy|ۆ q0| hBlX&izv/6 f 31)0y$8q<ُoUûg?8~&&&0sLx!_)~A'?Źgo69xPNcO`z>ۏr/x/F Cx?Fư㩝ظLuo F(>X4|J}Ͻ3g6;g ӈ`f)3Y/#i9ɜQ @_ǔtt,x&IGiFAF'HOEm=[b(` !K'C, w岸?GZC<]#Gw˗-~C 㟾U\Kqх{{f|e|6y|+={022ƀnšU+>~xOP.Wp1=gw DZ׾q51˿ѣxؽlڸ^:A\Fy*xϻpd?keD^}̩/ *q`(T l<F34 e3[#'CkVLs!<š{X@XykIu1ͪ$Q9S.M&1' qS.\y:5K!f3ilFpPCl;GĆ0}LwXA˩p6'e>r ?F0v(d9CdRZ$@>(֑ /UVo lP"beCxToŗPnKZr!Wҥ@qbJhitVS"44ZV^Sѐ9<eT` rCV ,nlFb^/Ė$8J[[cr'Ϋg,ir|ј,dJ$'rʟ1Cl6y* Vv*aϺ =)mZJ$u6?!( F80aG#'1~ڈ0a8H͉jlT}|e񯦣)64yҳ p $JK>6x`E fy[gh_asYldc6]K Sxy$O"6!2QrY @X6X>Ua(.@*BvN-+Ok_a44!yrC N5&bKb F?pfa$2f=]SuRIO3o#0?+I˛Mx5^m .I˖QS\U*1{9Q IR)1N!t3w:= =C4`hi1OVO|]Έꦧ.<&z1ro<\~' MuDZ = Șh>5PHCT6]2H4 Coa!91x7$Nѐ-f.Jb+K2(GV5֪i:EOw|I$(Si6G2Bc#H} <QES`abeU^$>_1ae߫ocCt|R>UN&0C4tVqIϴߐe4Zm*(*FkuL$مk2#FMvv_e5nDNH<>| /qvdH lDiz/-Fw|_(C:2hH:h|t4hi槮G=^6Z6=l2æ]ϫcdGSF*Izem$a^|hfeI@%TxyX%^)zU*}O-++˺դǯ>Tqt혵z`HG a<׌&;ʴy/Y_ҟ'O^h$ѴJ$ziE5qY&؜=c ۷oGss3.\w^ |ԙ3gF1g8'xB% ?۷oիQTp ̙30u0.> `ٲe1݋ 4PǏgŖ-[b6Xt)|Alڴ 9hy⋸;ۋ?0sLlذ!ҀVț:_lx#gʕ JMM *Th=Z;G 0)d[c"hCӓN1@5ECtbJAӍv옥.LAMH꾬3ڼ 'w$1 H&k8).UmZ7:b#OLV3%<-7D~O(9/+P$/|D*3 i4%,!LOI>Pa:OދjAC}AT-%a"aGHAh1O>:2H6ԈEȋU&Wu抌w©+.ǃ9_~(W|w#^ٳ~ٳE\{`aΜ9K/~=.yr-hkkCGGj~oǖ-[p7u]޽O?4?j5g?ìYP*0::뮻ccc(g?ŋ_<ŋcppB;vСC5k~_`޽ى>|{C\Ƒ#GP.Q,qmرc[100l61c#`hh]w;{7p-[n $mۆGbٸ[0>>#G'vލ6l۶ b8|0C=G7|3^z%| _@oo/N<ޛGq ߞ[$t $!tpc.J8>Yq6}wsَoN8񑀍c6-$!NB[BhfGOzF?[|POW=\TSGWPRRBii)6ӧOS]]Mll,1`08w{!33z233ywYh P]]'|BUUܹ |s%L&DFFk.jR[[Kpp0wf̙X&} |0_Ζ7;/WEIl8El6{P[:UjFҮ늢I5&-f-3kf>~c')WrM?rC9~ݼ:x(mUkcI^o(4Qs4;_CăZz*:l>`~uM#t7Xhta­,Y\RM\D$3Z5f3DDDpu)..pP__O]]QQQ̚5nOqq1s$''STTD__oOIMMe޽o;wٳ)--ի|r֯_O?ӧ)))!00:HMM$::@֬YC~~>ǏV+_Η_~IFFyyynu$$$ٳgYp!>|6l؀dѣ swcz{{g˖-'p8#++*ϊ+زe ǏgΝ ֮]˧~r!}]bcc7oׯ_gӦM O>IOOׯgʔ)dee0n8vEXX)))pVXA?|Gu]|bŊvQ򈎎楗^h4Lss3oyc͸\.)(($g444p]w1ydLz>XGDs|M6^WS7qi7ti6ypx[E=E!7?F~wP.?QGjΫç$ /7v+T0./~Pc_Ҥ{$- FNFPydЖ}_ua:Fc(uꣶVAMUū}׊Mhԇ |g7wLrhaUe贼xZ>@ &IQ ¬F>g\8#:,9dgg3n8DQ$55KW_)p}ݔݍ TUU1i$N' (vO>$>(V4 tbZ}rYSϒhl">>^q°X,i4!<<N'DEE义rߏ(L84Rj3fӧ[oѣa999h䩧CL&VQOʕ+WHHH ((Պ`"0,_~\А4`tp`Zf\.c9-66gyK2n8tȸq8}D;n8qZ?o]YFֳEٜ 7+szOjHY" O=} ^"i*x;n{?JvVa8 Kn#|[{._n^$&&8#C!\NF)9d.iֿu IDATKO̙̓{SG5]. ?.]j +3 Q2j^8p!&O`>Ӆ1d`p+W܌q؇1 8Dslڴ9f# .q1Ѭ\} Qdff2lA9,t81 ۇv80 Fw݁dnraF*{}͛s؁h4* v0NArt``><Ϙ7ofY)3yy1:!8NʕkXf5=44ƍ6o 0Hإr5]ԩS$9~ς&'۲d<ü‹tuurLHO'2"RraAx7ervTxwp ;xߐHtTFaz78R{=HLJ>dWtrя/xXpp:v r(:]tR nxp:poKtmDY˅S}hpАT7DыaS?aM`v0<<,ٙ;tR]]Cee  tGqvD-A n E\Nxuw%NS.RN.Qd߾466c9JGtHRe0tIc˖L 8N.j5A`ꩼD(-9Lô)S-#` QQQKPPraϟOff& L__f%K`6X,\ziӦNVVfѢEʌPff&III$''ݻ{9~8SLaʔ))`,XQG4"""ʢ<.\HSSɘL&0L1|݋dʕ$''c4IKK#--I&Q\\fc޼y$''H?ӦM˗3~xl6$''srfΜIFFB;55˗k.rssAF#ܹsL&/^̉'(((`…\z \.yyyN[ZZdܹ={f̘h$<)S8YXHbBCCHNNfl;γ<,ko~C.Ǐ >>nBqD/r9Ϟ!%%_9/\eKA&Ξ+'44X Oc0IIIOmv>}_OjJ 55DEErU~uX`1,^?{>LsK F=JsWtb*Οfy'mz, wu'_o?{o^Λo͗_㷿ywֽ?(999ꫯaZY{ۭ~,6Ͽ[m?󕌋a=/qIz{{O6ʟINN7w0m~7U+ټl6+wy;寉asaXf5}`ΛKEyUӧO_?WDDDp8t0hBWyG8~$ɉ8p?QH FAt8v/E"""ؾc+VtPSS˃}{``p>pNAbbvp{$?sӲAuM ?~I>8N{arrrx7ẌkinnEʡG //Om'>>Ȉ~|яY[/IzZ%XcOK{'~p\'JJJ2{mYA;=<444mm 'xܶN-]Bo_O$O?砻غuǏ'<"m۶k嫯ĄD\bʔ|f(`-̝;hiim%.6ouR?{+*g&22?& =+`G_mN= Luu YYa9)-ey> x{RS[b˗̘ŋhooG <ìX-7r- lٲU 8$M<#|F`{Wmâ<:(@Ţصk_W3(zXg'_2=eBXb23N߶mmmm|;!$$D#?Y #ŭ֝o'OTf)]u?֗C7M"a#Th3ZzG7Ln@GGNql۾9Zyďk\x©Bi"4,\8N:GXh!A!Ųcg̝3Ȉ|l>l>rsfMUu }XVi]Gȑcܸ1@ff& f3.'Xt C!RRRHOK#0(P+6v"77 &Xdôwc2<#8t0==TUe!))Ʀ&,V+fb>|\^zL̘HHH(+W/h4q:::0L8Nl6y|m=z Jk[egRS[G݅ 4RS[jԩBzA? hlj$,ݘsa|BH|rz;II̘Y -LfDGp1D`yď'*:kVsq֬^M`P "^--y睘LFivbvG~ɓddfĉ [pz U527߼Ś[V*):R]]c11y>rS Y`]HCh2rL)˖.qg}Pw ҨAbyLwQZU&m~-C~hgԿgΜO< ;\q'#|uiO]W9~Μ9sg#}g#z0zp_̧AVaRg'X;N@l\,+VL#G 77koCnN'MMWhmmCRRsfMF 1Kf4 i(..a̛?L6 rY{mǸ.\R>߻}_'##"RR1[^S6N &PP0e˖(l68SNq1s!&fd(hB.pj3lRSS9ti$&$l'IUbtĦMX<NzZ+W˗ 88>2332%[nY(#`6IJJԔ3{g$%%իWT^N Xz% \ѣHJNrc#.a4Z-)-Ut`0k; /<ώ144DXx߳IYHpP}}}Otz*#1 iTLo_;37pKf)FDDPVvAIςӨDDΞDUu5ӧ2DSދ" L(u}}dff2uV^U9w))^99[{׏tDX6?A[[+ Aə3{=inn뤥!F HKn3oMFJ{{%=-g @jj Dic5EE\."##Gܸ#q:+|! /sT+h-0cu=ǗoEM×AAӓZ|9sZӹ_=_Q-X/=>7ږwq^f\D+?EJcB,˽q:/]"554JKe RZZFVnBBB $$nf(vf͚'Yx7n pi, lڼ$~::ڕ~JKXx1=1iR}} 0<<̌ FgϞ|f3gΜ!33ݎn`===$$&0pc .0eJ>0!j&N@] cX򫯘>}:.]Rfۥ!j!=-Zө`0_~ITT4;wSXxHv$Ξ=ǂLiSp̘)ËȩSH~~%,Z΄ )))Qe@pp06ΒMBxDQ AAAl6[Z5s5\v3gpT!ɓ0RVVĉikkc Mffe1s q88qAfF6., iJAA%%%̝3bϞ1ﳣDD,Qttrss301c"eeRUU@Vf&W^3f͙2&NHn8dLի\~A2338<.^HWW7sfjPS[h!ú: pp8DFFԩB'M>l"!!"饽yR^^77o.&1ʹ0u*Οg9\/~+۬Y29s&c2Bl,$OO;""kׯA__NC[^qɓ~:tuuᩧ$;; LɧB9 &ԏ3 0tuwQRrHr1ttvrRRIOOm9?~YfRRRJ~^.UU@VV&L:EEL2gj,X0'ONTTnPSSᠩ%KsTW a4\nl2_1d3.&]n!/ joLGzF7>Wh~2p&:Xqr?:]ՓWǿ_h455*Kz`:aƥPu:_#8DFNV꩎R7K6555tvv2%?[ TվM-^dP#qٗadv9BFOUxIOOW6(z jYAu(mv`וy7]zWt-VggJFz5A @nN&WBDn|sd|Uh`"u@Sm0Ϋ~?}ZcqdEK_!he_龞z|=I˧^'AK`uuGxxĨeJ][(";Xӧi>_O]?D E͏(tUmGVO+cWGF‡T Բx/\>9LzxGyӓM65|<NOocF1ʖNÇ(6-^JWzq*N^2\DQ_Qh8FFK/#Aq8S:]бiuK0ꖇԺT#rxhtd Gj[׶kA%_yFsDf8ZcqNKGlZ9r|Liy'/'Vϗ>|`4{4RO/t_a|ɢ rF:8rcH '齫9xvu\z'Nwc#ȝ@iir슝8qҋ/DPp__ϩS\Daa!n>E"mmm P̷!x?_IggGytFD:KTD:lKOjG]SNze'Ф{);T|xd;|Wr'pws O6wnGONQ=\'N*@554P^^Nmmַ|L)#ˣӔappg9%ȵkשEE^m [+ ^iVLU_erp"-O(+;YP O=rJ=O8)/_ikkիtuuqy/EJ.cyne*EA=i_؄wxOuGDkX~;|j>^~_ΖS׷Xi&j^| WӋ/@G˻c NO?Z襫%ױ8վA6K(=/Q]_Jv"X~._LwO/}ə2?3=,ZΎN"##B{좡AN-5>_4|ɁN>y)EEs.A~{@iY Oƛo1g%3~s-#ߥ*=ӦNt`k=x =l߾D:jU""#)/"#p"%%\nIe IDATrq\p  .^v::; 9ࡇezt'\^=Y8uj&'fVG|ٱ~+=G{,!(_FS}\h;fwFKC/_xO=%UzWKß-m02͹ V+'==^0^ V.B_//ԗ2N(=~:7"##* r}ulN{1lիW}N/FəR'eQVv>;a` $4k׮~0|K72S裍$%%A_?9r(55\kƂWP4 gxW>7/ڵktuu+(o6 Tm۶c2۩p$oXPFJ#oL~A{G. {6AZoD<{42Whyԙyf@ܣv<0¥ZSJD75A+Sdƛ CYyIKKB6o_h|kرC`⋿wq bǍ#v\,QQX,>c%K ᦛQQqx:;;ٱc'ʟ̪U+՜c0xf}z{{eppvIOO/ ncU}7OֽǏyܹٳ8 Oo~l6Q<ӟp18ČPw"uuult;vɓ9W^c>BsK+}ݥ^y/>Na_RQ^A||<7neصk7SN!<}ݻwKWel޼+WPZZ(o>*++/̙3رvömXnׯ/y9Baa!|':t>S,R];}4~!TVVyfi~vŞ={CsEsѣGٳ{lܸ.Μ9Ν;~Rlذ۷se.\M3Kq&|-QKkb$x;YSt_D@4MINb%eQ4#R# UHx Ut8tuuqoXflh>t'R\T$''S~"#Ϛ5ܢ\~2>d+N{gO}Lffdddp%{V^Ō3T"wRVfAD9A[j&LK_^>y6aQh2i79ˇ et8ٴy ^ LMWW7&eICtdefRYUŴ_NC]FdD-v9 ndhp '-WocΜlx3gϞcE18076+|f\HOo/qXV 3i$_F{G3g̠kmm_wֽ^Vq:AEy OKfӛoK}-]]]456IȞEpH]Dm7| xFrgM˗p 箻O>F9ESXe (4֎49}+j~pnAuJJ DFFIbb"o&N"ؽdҥ_ RSSñcǘ:u*}7W^g?\xN&MDKK K,rqzzzhiiaݺu{ddd;PRRBmm-_ҥKTVVRTTIJe˘:u*ɤI,aΜ9Cyy9{aݺuTWWSSS'O̙3/3w\vׯg֬YL!!!(cƍfxeժU|'lfժUҊKI 999oc0۩V4RRRظq#;wt:ihh^xA>^` 裏aL&AAA /aҥTTT/3n8ٻw/ᴶw^ZZZHII̙3ձehoo'++7x'O('Nlt+Err2߸p66|I9rToeM/jߒ4\FcZ55=KDҔ  &IQfV2 #{|bamtG``/lV^z鷈18.c^W ֬YhR؉">7n `Zd2 L&rr1M&f͚ /sEYg+ML&>ٸq̛?NoEo}[ܸqٌjMEyg044> ҂ܔ?;0w64+/SHΦh #Ib4|RE !CmK /nO\З͛pXyEko4ٳg --[A Ȝ9s $f\ @Ey**c(Wl\b4 QRh``A 00ٳg300@``F,Aj2{l,~t_[wu'(b6Yh!!!dff d>7{s\xzIFnFAoŋiSE̬L{׀oZS`Z>}[&Ϗc20LU;|~F#&Ă ٬^!"AAAL>㓫 ]|'TIپHܶ+{jg˃C6YAm{p')32Yׅ!6-M-(uAKc ;;{2ydn6CCC?+WOEtm||.~ǹ~b/Ѭ^Ǐzj D ػw/v{6A:jߏba͚5CHHdddo>\.\xtJlFޏnի̚5bp\deeo@:?9rӧsaV+ \z &OLatᠾ+WpHmm-!!!H7RI+?ߏf#559s(3eAAAҭ ڵkijjիON}}=s3 MIXnk֬aɓ駟2qD>^*1at(ԩSٵk_;JH\=((uO8Rш Hl\.lV6 ŢذbEL&3VmX-V, qܴl:c mcZ<3gZ1$ib#VEu]n;جA0`Z$۳%,fdn&R}3UZ$٩baX7ngeHOKC HdŌbINJp8^K:1djb6܃3& ֡JD0`s_mqr{jz^-/O͓kǢ9򧮗V>o*mむҶF]R].iii8NRSS $&& .0w\L&Ǐg۶mMee%"##@EE HNq㈊L< Z[[g9riӦq}vyꩧؽ{7!O\\3gd2Q]]Mnn.&Lrq7{n&MDBB\|8-[F}}=fɓ'#"3fp뭷uVMFvv6111f"""8pCCC} aɔөdfٻw/ӧO',,(((`׮] @bb"ݻ)SNYYK.eʔ)۷+3S $*}UR"aX&((.jkkYz5)));vUVaXdbiIh4h"V+јL&VZEpp0'Orh$--r&**Js\p,YMM˧ RxmrfMfOr"&6몜L=pzAG>htr%Av|t=x_<&[4qc=zu88ίGrt|lg4ohIrf 牍pPQQݻYd 3g[X}ͫȑ#,\p|@__ʷ|/hroupXxl=]>Orb\\߫r1p_# ;=HW |1'<1Qo4 *<ơCMw,+ a *ӿK8.?:G7P]g,:-8X*}GM=;WQ[n16܂ xPa{ɫc#: wW.O e4«^"v+m#yG!ktn(sMڣ+MEֽ!Vnl4d(4AoflXa3 Z]ei>f/FyߔXtKNu30zN|com2ce,crDPI Wae_[xuCJ-сz7F ^Do޸hjjRT{zz t:vrմzZt2\_~uQGlWRmі~,0Z9\r6]Yzr8vx-^_^>_ǧ?K=`zޜ/#4=EF_ Ǘ/|KO=2W)G gZ+'-Nm6٤h4⋆t=2]K=zr'G_Oz4R4 x6۱ Jss3[n%''˗/+_hn߾k׮-ݦdЀd@:::/S.^y 줣INNFs9*++ijj">>Boo/VU_g455aϙ̷-::pH] IDAT:##"PEo366Ɩ-[g?L{IGG'8u._&''fE/c133ç?ig$SSSݻq:l߾!._ /@<{vbUVooL&wͳ>3<<ڵkq۷o>dNnܸ:2{|+ַ( tvvr9~w~Iҗĺu8{,ӧf-}Qb(rw㏳w^~~-of5ej9,;\TK6kߚZ)R+TCLq%ߑ+z4 p26Bf8l̀!.Tpf`m{OBBKӘyUȂeɆ!dZBrt oLt*kji%[:5gǃ*Rm1b,?w,W@@+W)Zl)Yl@Z5=Lb&0%iU%ߍ`XLt,_o{=r ff![FyPiz,Ff0%,G]ӷֲR^RkKVa+V=J7cT⮻Bصk{a``ݻwҢn:Y\\Gի}xhmmʕ+<#r9ϩTfvܩ˰}v0gΜq#GNSj!'0mmmOp8z*;v젽~w~H$ .#~2 ]]]E6l@X$sM7q5NZՃxP(gmݺmƍٱc,tHcc#ׯ_g˖-H2Y[ SSSefffعs'--- mۦ{Rn7:hzupiJU~WצEքڋcU`Gɒ9J_+5KؽlޭTe ?j׆J6ӳ(ѳaUU/갽b*=o5. W#׌)q*4zXFl$42\}Z%**q+:lІe@Kk*[lf= #Wm"]5[-Vlh,y*yojEÐJsT< 䚷ԩSJ%2 eumGG|[loo_u@9T*~bַ馛EG}T?~|ߦP:.*L&C8z|MGkk+=^r|rpwfvoN@]:rr0==t255;ںu~|SS~jww77nܠP(zj&&&x饗Xf SSSvɱcXjsX~={GeA&&&''CD?Q_|E:;;!(d rl6_㣔 %/z{{immܜ~oocp8]x ~Ggg'ر|>O6/;;envUDtZ ЎiHmm4aa\e'2\J|.G*3M/DQ_N_'aShX6v)0WVoe*T"^}.TV 9VuokvIٵaxE(e*}[ݣz -\.S,$ !*:~0_у.0J{gǑes73F ,tH-Q&MW* Y?4w=][ʬ!~ZyLu[2( C9X'{~ռ>(_җl˕>bD%xVh_}Uۗ%|ڧfLD٠=N5]R+r:-Uag ;yq>9XU˃ `\d«4*/Q(@T"vs33$IC%g3*m鿣QDn7KWsss vm?J\>jf&F>&c`ef&F4l'^D|~l||n8F7dHR.^).F_[cyyUO/KKyaqdX>vɗ+B~Y($ Y^/@z[SFJ9P(ɐWK0 & m=rKCi@q՚vP{>3׍h5e4&gZ0QqugVG$Iڵtuu.cV£‚O=IVNǯ3)B+oY/G_jҩ˯*~[֢JC-0a֒]PS#}>~VCR $)[I T.v ʽpPP]vSR戮Tri}nyXng.j++ׇ#ʵZ+Щt Iq"CX? ];,~_GN|`}=ssszON6cllD"ԩS,,,E%Ο;4 >x,..rU8?K|v˗G;w }]&'vm?ZOHDDq ׯO1>>%ɐfydHę3gill sE:.\8MM\xO/9)PQ@Qv˺.荃[Xv)OK Fc#GR84D=u' "b1LKPb1h+XPހ,I,,,WKi`hK\.all ϱw7s,.P(r455rihh`zz .ڊ(i>4Iuɓwg˖M8q\.GcRn._455rH9 73HW2?7Ǚ3gd7cH,crTd2I<>ϟ'NS.9u` |2іe݌u#(K!~kQSi_]Nu Pko7ȶKSknGG[iW:xwhaA]jUiUN;[ܚlX ,îL4^c,˵w.4>͚֔JWOYVPa}}A2E_ef |(?3/pEQ P3gQ,ٹs'/gYֶV6lQijj䭷;0[o z|~WGYXXiv؎«ѣrLMMQ*p044Dcc#CC*ДrO144̶mbȻ%Jsyd"=Oq8$SiփYI*]6:c"x*xFi+Kӫu'tNYe\'bQpptur}zK@Ⱥ;T 4tN=#ߧ=v@ ݻ 465r%:Hz˗GN*bjz^zeΞ=?ӧO qʫtvv /_ 7dZ ;GY̐^\#~2݌\ҥ!zx_.P(ī_!^pvZN>f|<~\'N`d 331:L>;.uuuHݻ )ܙqR 2rޒjFzOcjֳZ le>xvɴRߗӵ ЬTjW2˶VXj)RW^oKr"It:M.X,p8fyEvP((SZ[bH.I?d!HP,m9NZ-DQ5Ype|>/H qiH&SefN:b&;::LssrӦ"FG4;nga!AssM\~@鹹9֮gll..o26>NcS8!ez eD:&m&fѣwt07;W(kKvWK㨻5+[+c]NZ" eDNbgANUo]\,ZGav>iyGHёPBh!@0%J(iOڪ?lݺi^$##WD$zo'&fd lڴ'OR__G?Q722LFdY6ՏӧO9{^]d32 =ƕ3&''Iݻi@T2Ŗ͛9y=\.ks㹹y:mwUp",$IPHw^ɕW4CXʲAy$rNd2\vm۶l<N`֭5=RW\Y2o۶M^v.\,.vȲ(}}}KliMcgkZ8uPUVpP Y1TZ4Peoj-Pr1ع섲[wXJ2$I,,̱LJ%m\*SyTRsR)RdRKH$ +TDLHV>ҿT*E" N#:Dr bq8Xx8Mkk =ݴQ."LDd ~B@$,O[bDwwWbaa۷Y_{ x< 27?O}}=l߾OۍCt05=妻V" a(d`}y6flc̦NA,˔%e+i[ASnag%2lxuKd"Td+ݳG.}<ffYTO}^/hkg }GOO xKE^}U6n܈,޳n=FggH<~m=w߅墳|@o_/CClۺMsSolb&C{{;/e``AqcAoo/-(MM~0mDQΝ;GwO7pɉImF,套_᦭7i 1^J"9ڬ.Q>Uk l޼T3 8Nt"Ǐ9s o6W?~P(đ#Gp?err^}"T*Dxx<477O/sYp8$I<yga~+pSOA}YZZZٟ\g\~N.^ȫNx< /4x?N.pQ: 7Áe~Gj*>,;0??I$:ut:͹s瘝Ǐ322±c(J={}k p8cddf}`Zh 3l+όtp‘bo#õhkkS`f:5yHT"on~%ן l%ISׯ H" K 9_ bΝu{,>ͮ;_ Puj «%Ưd˰0G2x}*EA,] k~|+_//y7H(_:P͛7S__Ϟ={׾ƃ>=ܣq|yWؽ{7T]v˿ĉ\zG/|z)DQ`0ؖvk9r_YXX (r9/o~S?}cp!֯_ϡCڵۿ9y$/_fll{C;z+͟ɟ3pa:;;{ԧ>ŷ-nVFu^rEoo/'NS[)<yLMM133$Ib1֬YéSpݔes=ll_2_WIo6O4 !?N\G… k.~aFFFp477믳o>8(s=<3ܹ4sxH$n>077G6;,i&~m^/---dffF)>1??$ryz{{z g> :uuuxMB/| 099ITb~~^[E2~_OӧիWS,YnOXT%J\l6ٳg~8rYԩ+*Ip:Ȳ~%(lذCQWWO3):::zuq WwX 0 l9zv&;z+/

ehH P\uigUidc%3gpFZ΅=I@C&(I38p)ʑs,ϓ,I+;J (צD* q3ךN[IA$[l_V+Y}"ʐ(LOOi&XL?sxx{Dhkkٳk.|I_={}~ZZZxsumF]]LK.~]T__Ooo/x;ofB444punFGGihh)3 JՊ[ůvw)Bdu0,JW}Wl@\2RsgN)}?<>.7;ng-p B"I&ay'S.xJ P p:J%^~ 2 ""ouοp]w>0x>s;jr Qt B4ū4ޚG!/ Jw򮄷ri~:EX,+WL2 u"\ QI> (745) yKSS"M |uBY\tob: xxhd*l2McSJ']IT*07O1kH$/Pu:uo 5H{l=\yTn~ǒjdcçC)m<Z0_:2AWWWUo58}4---rjthk9 V%'Orq֬YD~vkNZKF>-ܢ߭ajrYTMUa[5n5ݪhrr4E/˿AV❪h,^׆$,nܸ,ˈs}$ܴeׯM6sDٳk".!WGGٵc;ҹ{n`kV7x聏petޞnfb1Oݠ#W) HZ' "zcZ%Si 7) 3,|@02L;oeMj:::X\ ISzzO|>N/ 岔J%BB*۶oI Y\\De~?.ϳvm?yuwD$ ~Nevn"zJ^\.2U$gݼ曬^ 6p%@9m-M8ccc/, 382tuvpEZ[_X 2 v^sy lV=.%+J Ad_=2wD Vl)TL(( x'$ 2DQ.\իvo!k׮g?PֶVz{ػo/yuWrwsefggihhԩټy`qd2ƛq_Qy3>8s-[6cN.ǎ刺 ×)1IlܸQ ʫ^R:l('"F2RI>\Iř z_H?GApzqCHRWoP,xdYtv*.\ծ쐓B:Z9lڀQj[otIv#xWǐ])qQ _(06yHS3NbYgf_7$IdYrun(DQ"0c+0S nJ)zD".}w|[)F5Ny"?A ˒dI?Ob:M4/(njjT*q5"tMv@1lbc9eM7-6EcR読J;C 2Y#S xm}\i7hVNՒwZ|;<-+59V:le0~ͪ<Ŏ5[,'rzTU5l^L,_XfgE*-aEj bhsMs2ٳDλxLOre$YReEm0dh;dA (H(z, SMA@E  BP FeL<ؒQ.TX,x~Q'H$D"lܴbuXf5#Wp' ?9\n77yH$¦͛2BCc#SS^?rضu+r۷[oځ e}v7z̥ap8e9s ?gΜ!K/O~bC+禊y4t/`(`(2y$dӋtս;3/1#i\":qʴ!ɥ"M:?OK*v+6 r8N H{aUwMe|G#_l@I "YRi[Z|E圭8v]l6Q,( Y<~"R ɪ._tan"l_+뛷l{qy@t*}7sSS>?A;:'??z=D"O)W}3qlެBY>^e2\ׅ׽tI\*y1Hc♎'24 "~S dDj@z3:өC"13vnJp8 UJ1YITGfff[\yg@ւJ(h*AXPґD+4m^ie5ڿ dBNFp AOr]w1z*,a Tޞ^h?s7gϞL0,S_+XK,CC=sMM~)˼q z= 06:k׮Eds92 Wى($ ZZr!4;wwQ15>35Pm.LJW+1ie\AeםdO#]}9PgJnk"Mz*O0`znp8l(n7MMHRxF 8DѡLoJeRYYL/ h:0͵!+l+H:?z< RIGS9?KWacMixul۾^zF\.GX Jn:~m6O]GrH$$ƦF\Rȋ?}UVQ(H&SD[ ظr| TH;Zd85aF\ɦJ=_VVSЙh;s~oIHY+2< O EaX֖%CYhȬZJ?a4G.o^uRTlF9k?m\SPAV!о;"obp3l9 m8YRDn:EMgXʥ$^Ӊ_GrnE|vɩiŒEQD#z B4p\8ϯVNIH&m;I ݔ&'  3  E|>2i:rɠCp8]4(}3֭[ٺ&=;m('j+8R򶫫? wE[eM7Uk=|dYad;G]|`Id7l6=Z>GKXc1zZB$74E"#Q68\xU9Yt4ιI%dE<.'>YB~R۽ǗO# "Óm[+1y~m #SL Gc#2"g୳ t5q͋4\%R*K@s\2K\P*u;;a5]?Z dWw-YVѳ-ͫUZγfǻO;v|NV+*%ܫ!Z]noY% 2 MS|d5Y.Aj݌1Ӌzn75!}àuѷ1ӾW55i 'LQlnF`Sy?N7 yV&=kkTMuEn%'7u.tQL !>bH&q8]`)/Jr rcfD}:L]?홚 PbvA>5H֖f/qVH5CQ0[WuD`Ϟ=ύC*M"sT^vŮݻ <*ukd73ןiFl0ե*z_T6*4\Ǯms/tpbE\3*loc!<{wrjh ݍ$y!C\2o߿z"NH2)K^<:$A2vHwjSܴ#rMs~mm]n/eM+/Az9{uHe̥$kD 3W ju+SyYR|ja%VXλT cB2-,Rq<Jbsjn3;Wf}Nj 8RQFo-dr TYs)2oAN,:fVJNJI7;9%4l`r޺:G5\%s䮭}z*zxN_t6ȵ9iţ$3y~A~Y"Am3cl"| ۉb2dCo8*E`yFT4@H5apVUMfVqI;9k;S~Հ`-jkjۅ䅅V,vYJ7 bJEM:\*0;*+T`qzTK*q% W tSH)':ArS;.RpCۋ;D.=$TA&fk2R,K8udbB2)<(p3hV%bsEU%1׬+`glQ+ `O_J/2V`ѐOR^Vv|FûߏCdysu Z[zT$?7\B2W@D) 4P&qz397{f>,Z22yc'i*xVɪT[%ܭ s5J-W)1S%FuWb'.WNаv7 WN @nnr[ng~$R)+IF_~bfoC ۉ{O}P 6MmAMQʦhN__c;Nbvw0T,{MhE\pЅ2I'( K 0Tc2^OA/82 bꃍ /A3h~]J˚ፔZ` H?1w&dK` *X OZQJZC2#WhZW=Cn,EOݬP:2KS4J>W|"ʅnK6 ԤP(D P.EE%h'}E`HE ]\~ilR.FWOs4\|v, A"J1VРRK ^tk1\4xrgI4 +im4gVMMdٲ0^b`w%Wxעg^ Z`:) ;W"J49ijѨZ?u9lA8s Uͣe-վ[1wڕ1Ӹ<>r qCz׏VKMhZRnAD'}4tۈց6mGS8݊ n@Pw9sʥUl! e( @%|g@[fėebY­~dWl6٘G26Y=tiUbq6nKC[7h+WiimJ&yhjjbA۶!ŋ]numxwy6lXArU=kϦh$YB # C F`+IRoRe;ꭧyįS16`lA6 $$@.;h{]37S5s~پw4y?^go`zz:u9oSZw2ˎh Á/ɄȤS:Jdv\|^/1JˬrdqȷZj7= [Zl\SBlL+9<-&7y ߜ,b˳23t+lbLV׊bi A6-#GJpˑ s׭W@t=OU] 53B lrL,B= zeIM-YD;\8$FpJILcsypz8j?qGNU6_ApaSS!m H 0ri K.zDdkO5ҥK|333rifgfhlh5&gÆOp_v츙?#J1<<̶m[pL&x<|>*+}4L_ QYl)W^#J111Imm 5>tk&Jt8i^L:Z'Nd]|>N85qwXہ$IrU,]W# !\qIܳ)|-[6k׮eɒ%۸+_aRal vv/^/NNd2؝.jQKq9&2T!D2NHR-pH$clbLVD$`E@I)eU^\.r2V0]bTϳrJ|~Kؾ}\NII CHu:y״3335@OO/==^3gѱ_'< HrH3BA=F}8n#uښ dYnn7G}1֬Y(+VիqYbqĬeK9xm6ot*0G;ʎ;Ƣׇy>yiUVՍ۫)"9za"/wr,*-6f^MDhQ&s+y&,ZhI˜f4+>>*X 4|3bune!+h'}NR=9{#(G˗Ί dYDm%I"LP )+!Pׂ7Tb::D|dUJ) jm ੨&Pۂ,D ܊_ _U=ʚViQuc$(υ2m 8m6!s F98vI#TKTݚRXȹ_:'O;]<~>CNK.)N'x/'pE6m${b ._==tuw3==Mҥx<sz._ڵk9}4= p7Ɓ7p:Si.SSx}>~Gc||+WknFGUVo_am8Nt:ő#GC #.\6ޑwFdYfgghjn^ĺoϝwk ,Z`3<.6 +JdM SA~TڟMp:Up\sț`]ԟ*2YRbsIF"?<\"離*B++&TYIYY"ө/ 4~뭷)++cM{GGqU6ݰ=#8} ֭㭷nN8t0SSSy]a'v6kٸi#o} q6o±Ǹt2err>CClٶ˗/~ƪUx=plH&~}MMM 5ƛ56ooǏ#27GWw7=qȲreQgg'dR頱'N `Q/\+Wr%ZZ[8~:mǭXvz;>߄d2ŔUʒe@ +V PK9~W* h7Q9䘯>9 {Ht$!"u ˸=rCJ8負b8h]fCJ'Ol"wyU Yz[@hFF(<,B+7}npe.{l+,+K34{wLt.$Il6 cx<3SeBMgҔJH$i^/dr4. SSS7ϳsN.]D]KOwK/ZW>@ @MM-/_QeH$GpGOOn[Ҳg~/ᗹr*ccH k;!VI$WI 8NBduj+`cVoQIAzc}k!OYO?/(yɗ!XY sT&+dM C,G{-[ Jڵ{UUUr1 23v{XR5Oj SRRe={z?DBLtCL6sy &0==dҥ .46՛~yy6[JDeU7t#xǏL$%IEV$&&n(N'GFt̬s´oSj>B13_y6W bMf^5[ߏ+bu3˼PX -AVJV@9s#lk1]St8ͳ`y˔ FzdXݮx6ɴzj,Yǟfmmx^*+Ct]l::P^QAIIK[Y*+CTVV 똞a-TWU li+KZx,]p8LoO/\twvӺёQX:(--+Vp9֯_v.eWb BMMx5rqJ=H:V?lM`fY^k~5NK /!\W|<F>I ,W r^Sͩ*Wv<gnnjFJ9rM`͸\v臨[ l@]yFeYfnnDQ$R^^^Pg:\V9M,|hhDB=*H[Lx\\{X,ZHH% ^/ ^υB[<ʞ`mX,GbbU1٬ 62(T*hX0_%(D0, N˜eY OPd`*eSp:,8N*CAzz+' zH$S$ ***cEɲL&C6%Nq{;DQ4`֢B"+cOV&#BYFFrd$7ɬ(dbix*Kyr²b9j_r@ELl10uFɵ7bDN`DͥhB8r|bFm;vN2 pfTs6/=v1 O׽AY 33LN!r:sU>Nsk2^#r{( (h^V zz_^Xlyq& uSeA9i5N: U-!p8O+:4)Ad|> &P'l6;7|2`/455a|a2IY3'/ݻ9teeeF#Lvf~8ׯ_t:8LNNJhjjRU3 R)l6>0Tq|>ַE?:%L E$uvN8IR444(fffC6N'(r)n7k֬A|>6t:Mmm-P& wͭJ,ctt&''x9Pq166F6%supM9x1>>NSSccc~f PQQehhZ(lݎ'ꆉP(D,)lɾ}hhh72::J}}=HH$ %%%LLLup6n܈qތ4X0&ZUyy<p8펼X,$z?ROb*{1ydYVJpMEynAߧLFL^l6VtR x{N7͆$(V bGwx`mHr!rcSIq:Q&],d#MKĒ.SqڋQ ꛮOV OLQXȂa+9y2Fdt?;,B#`63ȩ9o1!%y*NPWބ|] ,3=5ECAŬ~o&m\q$ID" ^r';I3;;CYyyN-$N5A{24}QPkS> ` iq959d dCޜB ?|n+]zQ˒1TPT4& pu8 ?#333?`jjJvM6p8Gydyqſ[ [ȴ^#JQWWǺuF|;ߡ7200@{{;O>$, 8x _/~%K HٳginnRWW,˜:uロ#Gp5$wqo6鱗p8́-/27nGx<طoAW_}>n7^iAիTUUe/RQQAkk+Nׯ?.СCܹs344ĺu8pmmmlٲnGRVVw]p goRVVF"gOZE*p||M@[ 0|-BFpdĚ.y噬O6V;].&&&ET*EVIdYQRfL7d2($ dnKc(~{LV#,I~~?>W_[삌 mSup $D2l$v)!d$R]jafM n.!Jĸ:ehjO7{ʉ͑H&ӊV/W(!? I"S9ii2բ鰈2=je2 ,XT*E,o,#QRRB0WZZJ4FF%Y/Cfԕv,K-P䗥K~fosl$ba[o|@C-xP9ͧߖtVte'Еo# 18H$ԧ>(|>vEwwcdDO;55?ƍ$p8 ֭_"twww^nv***غu+===lܸzDQ$zYz5.\իW%χazz;3۷&ͪjX~=?8CCC$ ko IDAT_-[(//gΝD"VXA}}=gΜaɒ%tvvbصkl ܹY֬YCMM l7ػw/455ގ,رeeAyy9|޽))++_2~(˗/gʕHatwws7gvܩ[eŽ{ͲetK+Wػw/wuCCCmo 0ZC7 l*1;RX!"R^^$xV$ 3>1A'J2SA&(++%ɒfHt*x]lY 8l6$$QBRE YlVBeYfe$QYB۔8nwA̽fxhH$B}C=cd,---  sY֬YÒ%W@E 33Hd6eKl zillDd&&&8t ?/ᗹvUU_gu222$\>ᠦݮƨѹ( iN'$ёQ2 hgflݶU989~;}}455郀1֭Oc伾q^=),mczZN#onֿOMMs@bYد8 "/JU'IN:_JDEDIVO^qAו1(twu^RMMȌ\axxiVZI6!.K@mm-===8N|>d}/ӟ4Dݡ0###J2192w=֯_[,f!}:82?(+sR-O`Ңv8f߾}<زenaXd |r8x ---TWWJ)[ZZr|x<rJJJz[|gnnNƍy'سg/^DVXs=%p8hii@ @KK >|p8M7(---tvvrqZ[[Id׮]_0dbbd2ڵky'x9q{nb vO?Dɉ)nvZZ[x?rj*Rr󖝬[_CCCA<u\8d2I&a;z֥|O1913O?MVl/"KDUV2;3O ๟>G5$q{56 GmM XOl? {QOf 7`S+V`P8d2a,###TWW[̤t,-Q\@$SDXs[Ez{zh"W^WQ4˲`t7r0?-6XŢJ446qZۈ$V:<{  _t@"Pe%X~`Ik+LQXb9׮^k446_jYҲIbLMNk.Vig9,,FW7YD[tld 6+ɭXMdb{KCz)xr,4KS`N3g?YZ"Ytd֓bqVef@S0cFO+[}'r𭷨bmg'ǎ#ͰuV$QDf 7[Ӭ*ŀ+t'ȁ (?0+3>?s(K[[Fҟ$233K۪UOL 6X,z"OQ3==MYyd2NhYq\E(/TipPWW쬥*ӪhtZVtgБ FF<ˊ"(RVVF4e69,ii!H PW[C_СClٺUу 3ܰy38~lll TȒLcS332-[5kxABDQ"UUU,L㭷ǿkX|9~ׯ_dHg2Jpܴ"lTVVӋ FeeW._͛sݠ+Uymr6U-Y[CEvhAo:>q~t+Z%WD2SYADAu)w\cӭ+ C.GA[U'>l6r{4'Ao ) =06heNMMt2IS@[.r{<+*+Wq8diƨ@D$Ic /^$ L,SuV YlhVePe%*goWrUa J,ΊJX~T[NyAor3VʘY2iߏ=33 +k9υ&ws#$mUVNBQ@YGH+nՅdYT.6˅}dYۃ[m$!".K!){DIPll6!oSY˿PظeʘUʊ{ ϧ(--a'6ӋO2KT3*yY~=Z9~8֭c\8A7 aو AI]ε  3;3KsK3hkרo$Ȭ]AwW7>ٳvm}mbjʖYl3g[|C0::nWr7aÆB]=MZ8؎U$eU)&`Ǔn6.m!OPwޥ1Lmus(z@n$ SQ^F:eof@Fdt~M ͎xQ>$&˙3伴0~µ2yT3;;KGr5PWȊ+x{,[LTlZkW*K$>εkffzJKKfytwK&> re-.Gӕlh'X7| 7EN`VƸża*CҘ@_c!Cb[UFeU|+P:^A!EBĬb7L,'Hyl4kWЗ%Ibtt Qb;HgĢ1uO< E4P@$&&&)--E$NxYpx}^$U(»Gem9tU`0kb@ՀQB1~k(+ow!B־P,\ b2/ҥoV2?Lj7X1_"\a[1lfXtKɆrdY9et8e׫܇'J"T!+f0"+V(d[V7KfdtǃC Lt8{Jfx2lc@? !I2-;`#r9d82ʠmt>3@%~_S]rCѕ@(GF2[lH /x!˪2u5L:JZCx͋AV&+VӬk;4 ;Z[.`mN<ܘaH[&GS0jŰ/JPFQ>ryVuD>r1+ɊYݩ?&5' \r_o`HԚ+ 7'_~|2 PE Q ǀr:7|gwLؒː,)Z)Zz=}ڤ&B.+ЙYW& 4 ?svXv}_}7ب9J$f'NA'ĉ_^\pk37 6l0gF9ܹs:/5So00)o_ i'O1>3q5K֪ŀls\:7$Hp]wz]V¬b|,׆u#2>,mmmlذC1>>Χ>t31cVSVimy!]_n\:NzDsyi*PA-o(jkkeեEтr4lI"zev:Amo$XO9T76e˖2(̤'#> =AυeM^Fz|l<$#r2vcğ^dYy1Ehe6I Ll4?oMŤ'Ԗ˞73k€~qfz\>I.n*uF\T)kTχH.I8 Txs4 ~}^2x1rY -ÐC(8u M]*g<:q:<W͛pm6ynV^8'O$r뭷kQZZJUU?x<={oCzjN<Ɋ+xزe .\^… tMD".\{}SO=EGG?%%%ynb9rP+vN^z%z\3gP[[oݻooԧ>믿Ή'z*ܹ.\ƍ pA>Oʕ+9v۷o̙3 Dx399ɺueӦMW;x㍜ :;;O~?<ʸ766Fww7/$yf~#X/~\|D"Ux饗xǸ~:dzZ… 'iwx!3ϐJ蠳_ر'xw}j]v֍ݻwOxf߾}D"y7yxyطo?0=Ln3>>ҥKyg8x LGy^8ud}{կ3 ؿ?'+_ O>$7nD"[x ֭[믿ΛoC8@"_{u|eFFFnN:8?0/_۷˗7fȑ#?~Yr @f\x\_ߏvx8vd/Dr:O55J@}}X_v`m/U8\ب[Y̖R<7Բ7^QUw`SG(/GDlW'& A,e*YYF8yGx&}!׿/Tr I&S5OSS^+g7XYmx^BPe܈왲JCRd~:CMeYɺHI ?"KC+Em$Ibpph4f#SRR(d$k؝.zFqH̊ jLMO!KL&C2TW; !A wq+kV[aMCg< ۏa% i}_xYHaƼhk9ڤ4N\q:r-tttOuּɬ|_WZjصkΝcǎHĝwީDm6hii[n?}>/<n6n@ccc^&=&-܂ r-@[[>( wvW\hwߍ(=z]v-+{_s]H*IDAT oNuu5No1o˵kxԩSڵK<:l8p}s8ӽr-B!t 0<SQQy衇Z߭XVUr9GU;~(dҪ&Β5mƕet*EeB?233\_vr9p\vnċ,CV̒Nep;Z҂rdIpp8N,ӽ슒Be$~T4Hۋ;š lW64ݥR)86쎢XďH׵kTTT(֏iVZťK UݤR)$YRZV0ͦ|a&'&ٕPee-ۅ`S[3ُYi:X!No‘HIxt]bqҢL,~g(ˈ&Rq*7@6Yۦ3i|.h,gr@c.e.ÿ[G_.o?cmSZZJ$QEt:EUUY<ՔrSy1<^j z"]H(ܗhX.gJaS76a|>:n'}]M+XvAʘ'6(1\ɔw0 Ix^{+W; +۰0==3`o+WRFRXVei,RWn~f6VB|yJq2JoEPyobrGc>h.U;3S狑Xe8竃BÊү䘯~?lƲl6;CUiR$tAO6{׿q{}㳒Wbw7^Ƿ^>{00n[aT?Fu ɩS';੧K_GU1:t/|%/ɓ'SwqW\lذ5kְ~FGG\,i8DQԍ%JIlZ)!s1R/fxt2[izzHdz;55M0X'I;v՛oɭުg=]sytӔW,8w|~1#%$/}D"1_ǯ~Ç=UXee879hl>𢳣F쨞^OcYyq&FJJഹZzV_'>{ 碇uBrud]Ч}$ Wc#By"#L>MmzL&(b<%.fN|>R4 wy`ÁG?zp[o'|EMM HDVQd͚5۬@G__,SSS#hE}-a4*s (m;sV.]>ms3+LkE0}ӘP)ak(Vn7-NL539HEi}aˡ+ysXu~U1ʕӧu[3MQir~Y0ފ8rXK'OC6(DKSwyZܼ`ydV \D9l!>8t;WrTT9u4,o~[}sjH$K/[oSZZJ}} ǞoK.ǏC{y;xяd͚5^7SO=Ųeo~ ]wݭ^7&{_M&n66$Ќ3TlN LNNK;ب<? (Vbffet:&8`z>Hɫ%It200ȋ/a@ Vg0/h gG8slq M*K1q Aꂥl,G^'QiN IT,5FBb7_aԮ0SY#8t̃}ql\=U!zm+zE1"!чRPaZ[`G*& ՆWFW|!fo^n6كIzB- trS`bjFl!<^TŸA#G~HGGgϞIy~<8&'0LݻSN1>>·aÆ DSS:~G3p!JJJغu+׮]chha||NWWW\$kFGGYX\>dbϞ?$^ŋr(e;vUVqb|Z-V^v@ OCC#l޼Cb r* A1-`(˽ is ^4nJv }120,;DDps_ PY_ t bP-sdW+92 }hŏJ~K#ndo dp;zEڋ-~GYeXπG`jR5FV $u|K~Ν;2t͉u%#Ӽ32MBzp'|1n;⸈M5*i vJJJɄ$Il6KYY9:^/>Q9}4lݺ2֯_˗7&ٌ ҏ@fnn׋aWBH3F$zjΜ90--lܸ+VmFccc&''bf)_d۶mt:ݻwǵkعs'7ono0:zۙadd ֳkNcݺu~z{{ysId2!MԱE!VsjE@xE_1@_qlǨ]W}?Kϒi[:l r}yS$DY>W3Q] d6]Ҥ^gT(+O;R@V >>z -r~>,.gKxStIJuDQd!F9plz*A1] DKK+2 q/-*ATsNΜ9#%&Lk0(//ٸ]8<Ųw֮]C TWWsAvxAT*U$Z OX)o:VpbfSH  rU^mmm>|z ( 8qC<Ӽ{jSQY梢^'&&OgݺuX>A|%Kزe >,}}}Da}FCkk+'Oi&1H({Β% v~~NKK+;vlƍU'T}?ug>F)IA.O27_YڒG9w.JU?''NYDע=?̆\u$_bm#|g3 = w/ТDY\u`Ӎ-0΍K|Ͻuз{N<=6JA==T P-"SBkƉy|NKR7:jnFj*y]]$~C9H2` nqet_ұUnop8Evߘr騮fbbBفtl6TޱјI">֢Vq8=zKQQsUUU166(F$Ibll FCee%$ ^f ٌ茶͸n, l6#I555j|>]"!^/UWW}{ɹx6%2oLA: ᕆExyO3f#W%uIP/zA oEkp{©PP u9UEE/UUrD r^#z- AnAQ'L##YJ`S'O 4!eof'$stsG6|Udž\6W;3xL[2/gv$M~4f|NYrɥn4{xmw~d2Kv3=eyS6xRiA׳ܸP^#/ԇp&? 䇛ITTs # }DۅBAAٯяFpIHGTzs`0!=yC-\EV4FLYtl<(v\.c6!:^F.\N{q<C! iw|0p[ɠ,lL [&0Hʼn']GY-O]$`!V-3A:.K>V աSd'D<3x2q>x}R>Ƒ^ՓnL`M>Ɯ (m6/sLxi^PW_eU˱rJM:d.YФGc۫\xYy4i蘃MZ{>1OAu:~c f&|&}|WWzWYN8 oQӍ/e>]:2y&T@SeWKGt9H[:dX]}t(S---+A=yuJ$ٳ&rrp8lOϬWeajE܎mQ۠+=8ᱸ2J)Hɔ&*+6 *O-[tRz]gsv3~cqɞseq9^瞴;^VJI"jZ(.*{Rطo5t&0h rssv2J S__OMu5%%% 0-~8 IIS[m{ط/ʈa *%'' URT]m-*h4JMu5TVVRXXZP`d*?x,zç.#OLgP߶ /fH(t,NxwvLv,8 ,/Yᕶ֏4uFp֝iJk!uGJ-_#8h|xB&}mJyrZ[O1=eKNV'^4]nPޯ3WWnޤ`=DP}ky}ummmߟ! PQQiu.ۿ߷@(d_}=uPQQI(,V.RBMM TOnn ԐCEEH$Nk|KJK' )Aw.!bN41}+'Mb@WbJ$֒_OII v/cbRTXD~~~ʑ;VLԻ6N88ݙB:3g:xO;me9~K:'P[Nk13jh."y]/^D2?I@[z@k'+he+0o<֮]˝ws=K]>nV~!rr"|*,'MK/aÆ|ՕW}?O[[+?᝔8lݫwJ|tBVNO٬;evy9 "3 >@ڔNR#ˎJ Tz}R4GKx'GgpHm<]զ~HJ׆)^<،c {-;r&xƧO2\.t8/<km'Sϳ9hҶ&S]^ڻM[[o 8ʧYt|o~gPO YRH^{#v;o`ر"|[͖-[ͨ?O)**r=UlJ[%;Vލ'-|.-=[)g4 *?SS\aHB_N*нP?T $V f-&YPl.*_js鍷v9#zIձP|1~1-N:K~8*^2Ybm ԮV,ո*!j"7HSdz{mA-I-2*TխidCT-]jDT)KWFe`W4ٜx5U9<'cS2k G:*M} )}IKE:r|דnXڔkp/]5y?5^5iv~Ϋ[xe*-^d-ʂnV9\5`5h{RB Ѷ6t1 FQBP9?q$L$477AiY`bZ[hoo'//\ʵ.$8oQUUŌ3(**bͼ[\xх1YA^w,h1Gy;C]gwPNQ̜]S ]7KȤLIXc@J!$ kСطo֭CAcCk|LMM ˖-H$Rmv/[F3΄ 8ʓ@UVm6*++8D",[;wPVVƴiSYYMx뭷TXXH<'Lp3nx||:p'0bH\Glnnх?̳8p o޽*/^5\C,_Fʼn'H `+PSS@qq1\|1;ooA0dL6>&;U|?ۡ|R8#aԽ ,La΍OV,R]*˲9ܲ O3pC!թ=<{[\xSe%;˷uScw0v=pxN-er]";Jxٲ+&nEǠt: fZ:x34<jD8՟IDzL6yK}8MN+OqG.}J;w`[6ܦxe AdJ%j{pvݱ;ξW3L$ACCu 6P(DGG۷ocawԛTD'HDˆo(=|5r4899ttt8fxQJ|gg'PLa^~i  ̚8MMSׯmv-'6UMvԯAȋ52 ϐKM5툎0%? 2)%@:XZFCIij i  Lpx#455qԩ446z/k׮ /"77;sÇ?ߘ4iRJ_xW_{ .Vv'|+s`VZΧ=]v9u\kYZ}]~֬Y~;O?N~w=~#R7,롾Pj{iӎ+$Я_?ƍKuu5w/8w>'3l}=}N< ӟS4{zz;CK?Ҟrd^}7xt7Ώ-V;prލ'Pkum &ꞌ}ۅGRnu&}ZkΜlυ,#{ tlwW?Qc1`]31jTjkIWҨ7<u394).pYzP;dJ7צcel^:gLs>ʞzM44_p@"$`>ZlK.‹.fѓywyW8;T!)! v^[ D",7r;EEE;O*K|RJ[s9>=>[)-M\w = :'Mrg$4I>+hl)dڕDj8)N^E2o$RF쫫cŲI&7xj0.ʀYt)h'+?+?Xo3 /{ƏO0'` 1. @YYGLhs"Z0&L@0dap‰;,^n!CL&yg8S8z\{a:::0atvvҥKۙ??74h'O 0^]z5%%%u#+_ ~\~c:u4(#G9sسgt-w= H{/:1opNoKsСH$<̳O~*>}:۷ogΜ~Ό3ظqO=$W_uwq7-/Y̿˿0~xԶP/)%Ҽ;)%6x(i$2JCJ2&i7^]c =V/{8ps2+~nɬx5swoª2tpɤO ex<,fesιT[o{K.7Ӽ9gCsC q{ܱơ6RzxB[q$m~sK;?)U4& %HEϖL*οJʌ rh1ÓOΦɓ'i&|*++c;~7iIJ)$ݻwLJ2`@. /}{)KDQv:' /4J% '?bԢw')%'tO?D"Ac[0@kK O?4 ,odƌ6K<ªի844wH$L׹O&C dtv&f.݃:W@ `3h>m@qRX0u--MGOn)5; [NwR2:z%֝7x;/K\̅G4lOs{{ӡo}'p]>s.,M#TɯUOd%nx_yշ!x˒!7=#cDX*Z|R-6 x'MG "w|YVb7uH0&Zɻz H2cD׺y x4d8;?-w==u_Y#]ЩF}3o3Wtkg}̺0e}1CzV9۝ipUTHmS&n Z6;φ_QQ%_% _eϘx䑜o>Բ}LSO?SN=p8LQQvL&~SV>tmߡ1cF{.VON0N~Rw9eT3dT~rw/ϣnm!U8&bH0qkT 2p@c^3gN B!-$ce֬Ǚ:m}Q2`@ Gc)(gƌڹM}9,_ӧRN=4֯u?i(++ f_җXC ̔)ShGX.nj<Fb:":`ذL4P(Č3ֻ$wo5۷o[n#a'/8^38Ӗ j˜o,Zq0)1Y"_:T6lopSRR|c=e]iF[[yyyOJAAgu&/w'|@LJ#F0IæNkj|NJIaQ!wo& >3"ٳÙgEmR D2Z[PT[W6l+uN/y<;nu+U/͹z)3ᓉƋΪ˱@3}S,/y\OE8~|zes.ow#KV  &ضegˀAY2I]m %eaW-B_T|uOɐɓCJɴihmmFuCaAwS)++Eey翳`^x8LfΜIuu5Pc!Xf _1sIәK&ELԓdj:(F*> i z\r|mm6¦nf* םGtBuԡds.QW5K[8x8#R*ġO.rgcfhBY?1`|;:$ֶ= 0DkOjWE3b[(R~=СWV*}@Z@mێL`(M9%)pD=g5j Ig&S6d kO57Uj9imuL%2͓gMziㇴǍ_Ȓ[4gj/HJ*+ȼ&v[asNt/k+U[}ֲ.URxQGU`#f@((( 3hPxh{;e7V0@V S #ҙHPgMifhPp8LKK 8cSޯ@ INm utPT\⽾״!lkm[ wPG(tNgCyUG'Lw^rss(++3_j4OrGCw[ٛCh%#x;/^5 ;v?1GLȝwioBAaPx9yf{ߩRKzH(CҹnW)CVOi׳zOj3Ѵ Uz@}qY޼< E̒*Y^pfqBp{edgu R O{cYTDqCJxKnXJ;ړfyOJIT@](yT>H-) Wo&ZWF*ge/ "ƴgǽjz‡uId ~~{4<Bohl ŚinnR)d޽DQ[`R2h`"7n7ʌ#H$`rx/.)QB7X~{ ʼn($9GM@K8>qtvvcvC8#GRRRB,nB q BYQV7xwիFϵz\-~7l4me2bfzV!@=%WwuQi9"֪HHkg}VPx)ۣ[V܋/XQ.zf} \iIϦbUZ5u90_~3p:WkU%ƎZVር+K'/ˎ2hvEVR'5:}pgVo^zCrl,CKB_/ 0Ҍsڜj: v01Lf+GIIy  f暟e0XܒƻO2A)G*> uR܂I7^R j*>JGI@wM_]eU>]e>Ժ *d=I^V5Sڏ\Ycl k{$+48-r*^qEۧWQtut{Ȭ;]Mg\=۴~:.zߥn2a'=bgŁHD;9?zJW:dJW}v0U?D{{, o 12v"[zpe[ncE$}\Q3>3W~2d+t]Mg맀LtW=LȨC誫/=~Co=-݇OMD|SS˛`$4B4Ҫ;۹ʌՠF ȬP7*ORIԠθ;.bo;wZ Rj2;h[!Sk2 6={ٸq"CJ5TUU^D7% ]w!--Rѿ.k,ucLmߟwu]]mmQwtmۖǨ:vtқdmicߛtdm鴺:K:i_̖oߛd9xh4Jg}Q]]DN,sW,2*Dag+>\ٳyblvr*VRZZZxKO*m6=z-f?Ўb%,!LrJl9-^뚗ʟ:7|ܹ/u477 /͝Ν;yᇙ?>;v`ζ|b\sUPշjH|Q#;fjn>(oE9߰j[ƙg <!6!Cxj] !`o2'ٳg/vwNii)vjC]=<97.n߾?`(S2 K>3pFAU=n|<+Vmͷe'#655#PPXgS]]cʥ$WtYY:[3;;<1{6[n1&e;ٌez0?>|}>ݧ}>}ue!M@|yYyyA8p `ܔo~p>LoL3y2cƌO?ev뭼;̛?>%jjk=c***k <3'?a< 4551qD Z[[QPX_f W`G椓Nr۷gb֭ 8xA…C8tn>~hiit窫aؾ}e\wݵ5QU޽{ikk['^>CwP__ϺO?.K.7xE 2qDmw]UU >&99|::8K_ . s>'ѿn_=O̚Ŗ~QFztĬ'OfzV9shll۸h5/<{)*,䦛nb\u5;e ;wb޼y{~z~*cnjatf-kxرc'yyv 6Ԭ!e_ɓȣ+"''^zUVԩS"_d唕ff=>D"ߞ5kD9v1R^~eRrGVֺZb / PVZƜ9sos뭷r|Ň]]vb n6կXd #G"2ؼe3%%\tLzC)p[ZZؾ}7\:ƍ#L?ȣ)**dAH >ͣ)Sp%P\\zC~tpO=Mh4]=t@q]~ç&Y D"ѣ  _5ˈ#hnn|"sep|pORZRik|dDyLF?G}Ĵil͟ϥ^&sϱjjƏGaa!7pCRH VP</6po3`nf>#>߰?~<_W1c' /կ}NE߾chqcRTT'7k996C(<:S@ RqCb&LCg˗kNMMM\~e :!C `9@ ќvҤIu6@ ={8aʔ)jMWl n·oz? ),,dĉ!>l7oFlpOL&y9䓉bǺO1fXaRe = FA(hJF :::D"lڴg}Vnu^K]m-G&//#&Llۺ}%KHtvRlg}/ںfZZ[ZH䢋.b΋su5נ[u< ĸI˂]UUw2uTz7ɶTޣ[lY+[mok0dڴi<s7ɜ9sA~x60a'`JKWill[y줢կ믧'?~<5Ajkj/ISs3UUU|tڢQ**+(++ĔvWUx">C \|1/͝ rqGJKB!ݾX{;pspOJqiƬgQPXUW^Kߴҥ9q"Q·+>dƌvWUUqD8xR{g MCm¥tO*tOj]YIaɡ~#^fY{?r8H$S`]R2axa^Hfa!H #F`1-B غu+C !<ԩӸ;f 555B!b!RVJM: ߟ9*+*ظq#G99Zwݞ!C5L9}  :#Uh Ν;紶…477ϰa\QaPUUEiY"pɲm6>]$ ~ ۷m6Zm!7u*z*D<ԭoUQFQYi--1b8#6lO?wE =Fnn99b֒L$;w-r?g|'F } O>u?m4.rXv+}&;GfҤ).*2?3t1k,;<&O+h IDATyzAÆ hjjVܜvﮢ[ HʩO3g'?u{e͌5m[1dYRGJIaAijjݦ߿?}9&g͚5v'O5\#ػg^!Nwۺ=s+{P P+|m)Dz{,G2dΝTVVO c{).*2_b''f|P.@38'zp(irwp9g39G)))s>P”!Ӡ}饗Xj'|2O>$_}l 8s_ҥK Bt≮>iF;%Z[ZߟcBNN_BAAH=UVqO~~>tI̝;&Lؾ}O?4G;fdzif{2ɀ>O9?5jf??tK$,\{ VOgz|䒋hhhɧRv*;\}Y"08! rg׿XG2.BJ9hqQ46{;>m_zuׇU>{ڃ@*҃M`|}}---׏Zb+'F=  DKslh0U)$:)K6l ''>P#d$G"d2IAAH&cc2$F),($LF<'[( b1 ͠"N$!HPRRBSSEEEtvvB(ȖYJIsK3N"ݻ]vv<'H0aG6m"fHi8n8IN!N:::ϣ#N2$''NZ@J BP B!ڢQ:z4/ KF'CaSQT~SS6m"/?t2n86"Bh6 D"qZ[[BO8&! ''RDqQ1I[[XH$3D'-Hih{Deg;}UUU޽;ۄf FYY---H)!L:玎_AmZvޞpQͷo8yyyV蔭A G"Iss3=8?# 5I''' );'+]uttuVcLabHDssiii b-{gg'vB (`H$oT\\L2D"aTWWGKk+mtvvDcܸqYM0if@>]e}z <}d2I0dΝD;f ɤN(郡۰^-k-555*ꫯP|/b:Hڦf1A4p999F|8 PP(i--)F}Ϩ(Dz BVZ޽{ڽO3#//Oѧyu}Oɗ֥K0h s$77 *RK^^6 :u"Bc{hAA>`9$ ݥ  ]c_bL238C{rd6d5 99>~V!lV*4F: w%??8G8ֳqj8q"H!0F|Nh]y@kk+'nƌ}є0n6f_.t~l3n6䙼 [ >U~ٵk F"xډz8J/^.i@{ឦ {.Vz^:ߛd9`0h0ZZZJQ"aƫ)%fh>!L&$ 'CNEwt%yU=-C+Om8 @II; L$:wc><=B7Khyև&<w@^k6t^uAԉçLv'ޮl.,C^]zOi;(>H:"]to{,=ݛd$KoOI- ~ӇOM"| hRRRZJ@- (!4 0C[ġ%qʧl /ȟʓrjy<ڢ%]̣://ps nr~M6i[ٳ!d$5>(-tW N/ݧ}>}u%,k[i?z"RZ*4KtV Xhy-uZ(u4@ˉO=mS۫iavA:dWdk[[W+Wd6N溜w=LnIY:|wtq Nt8x`8ҕU]u8+/2$K/[A ;.~86w3bQU8Ӡ~u/ԟm~&R~A6eZ3G /[ȨCʋYh>?/xԇ4P}B/MlZ#a|^ „.PS5eL\4 ڑ)Rry٘'մVA oZ3"&DW.jC7"M!g6lP ?1bݐ0Rû^i]w5m,nKwtÁNn]v]|oU3JkGtdck δ^c7ە)Wp>]9]!TVBM':2j*̴ M.3;δ"-=U>Gzs9k>{K bN<*/v=VfK]Y|hFLf+ )!H 3]ޙh!1nȝN&466/H"Bg]&kLJU&<`*-*A*g.VtZ`feUkAK:^ dNj~fttٳgyD"9dAAAAdRJ8{eA)5L2V;ΎVMHHBel@ 6 ^ݵ D0`&I(ŒdM;sCwt}޹w4HӷN:]U]ΩRe麤htRJH$ڙ8qu:}l^Ch^$&5CGPUt̺+|7qҲ̒ݍmLTPҕ:{p]XfOSrgI]cG{=n~u-kATH@q,RƶLґ'>S؄Aj(D*J~T~mɿlR.]*ivw0m4B9㦠( BQP0޹g=)ut]RJt]g~YP@P@XH&ܹ>0Ʃ2c ̉V8>"cـԽi0c4qG㾡EKRUUSY ( DQvA,( ?ҮBhhgS[]'d_J$rC, 9pJLE.lW>v-e!*h[|ߚ{7(.fŔHƒtt➗xw׻TF*Y>.'^,y뒶]hxUBgP !aF={=gU'yfrhkk?-7cG=P5k6u +NXEfPT4񗗷O' ԡ.(R^ZQbAC ( (c)% P]]ʄ H$X)uQT5@<>XCdUU$>L]Ep$(O|9sl)>,#LWE(Bp~<2dF[3kl]KJJ!7 vt mRZZn{@\x{qϿ.=0p=0hH!EB*SVA1ctEeFg2u#ppMyyE̝CiiWӍ%B#AfΞEǾvvvB-=y,f]m#^jZ[! Aml$U&XW/{S6-{:s̥Z[dFJ)iڶ O>ɼU4FPd.+SETQ( 9dB)2SV8*PPE0\cJ* R񢾾UU>}zZX>g+\2~hꫬX"3:qo 8@q*HQU(*wGY B455L& 95& E/TUu s:\ˬ)I_$2xeAnf:Zwy2|޽$ɴwM/G+<>d2&PTڽ:L,) g,IB0'L̒cTLJ]״QQVĢY$Iz}]SVQ*a^C&\t:742]-6%[LRCTUa&l嬓µЩ`֭,?pR zشy3K,8Zg6*+{jgɡK/  f32qlNii)r$vBWV0atZFmme0cMNsoim<@BӸ˘tx[[+dS%IOgw{>'TkN&N>HXSRMۍ3FHʾ辔@ͣ.ɢ R7}:EB9'TZJP 栛ų;﹏An7Y`5UUTq53?SO?&w&RWl{2iz(bjB%Ŕ#Q ₸fTp[:k~![l^֬Y޽{ٽ{7O>$|~zzzصk[ne۶mDQ٦^xEQS[JIcc#6mbΝR4v͛طo`Egƍ;;͛ؾ};xxJYy=\pՑj80%ٻ*]|1r}wttsc>,]8lURL" `kRCGTZHI[{z:::̹֭[yꩧb4r5ײqF~_qˑG_ի6muuuq֭[E]D,c]V[\}5y䑬Ys!} IDAT/\O?Æ Fp饗rQGd{]wimm}ٜvi<3!믧'yJŜtIlbΛ7x<Ό3#Lb /̛7K/_W~hZyQWW̙wnDII  L> 6`n6fjzan{YEeܹŸꪫY`{;@t/0qDlH$s=ǟ=(S? ۿfǎyAʫ8#;ill3eΜ9v}8OSSSuvlθ6xOx߼B!{3N?*:>UVfj^Ea 8ć#o!ߴ$R]y>Ko;$CWy9fs#yeqO4\ӒH Ζ>t)Ԩ-"J2e]G25̗ u!wQN)( _ MDcqz{tvuj܍Fo饭uIPUJ_ =Q*KGF8`4_xk/>-EA֭@uu5կyI&y\uUٻLtZ.u:Gz, n%'qp0IsM6,ЫJA(:HլY P:d2IOWg AQëCYsܷ19seIh>k䩧Hm5vTqiˊB,]PT֖QUn&H$ٽ]O,B&,Y|埉Fp |[7O5\ܹl|AqF;o|_'? /2+Wm۶QYYImm->uuuTUUy>ZN;u?#&M2&+l^/ r7#$R^^(D"';w[oDسg۷os<˖-㥗^gfTVVrW2uT֬YÉ'HKK +W^Flذ!k|GYrr ۶m;#VsUWq1ә={-`Ӂ | '|<2/=|<2/pr:;;)//wh ;::BPQQ:]KMߺOŸ+ PUՌz{w>qJSe5h͝7n6wu'=d~#sL_܏{xe$q&GڇP*KL,EѢTTTf#CPBhH@V,DY~]6nKEU PR")B!E*(!@Ֆ~: tiUU<44vP7P=-]DBL.cO>d)+2y'L렧t^c{ⲲRB bxf79UE$\L@rk2BG8|@Ey9 "jp "o͓%Fxg'3#no)8ٳiIVTuv6萧uL;ן2лخw&_pꪓX䕩ҧHY5P@PP]lݏ.aRu҈Ez8{)..6wf͢bbAt]˜tJ~)>3\~lB-F.\H(bԩܹ'tw088`ZXp!2uT(̞=P(̙3믿ロ~j].w͛~3֯_ϽÙgIYY)===EY裏o˗s/q%سpd2A( )C1g\֯_޽{9ٰa=---v|nGt]3jƌALBwwtwp衇u뇔RQQAYY)pf/w߅K-[7=wiƕW^SόyP@!H2mP XX,fo)55|{'`IyXOiz~ (&Mq/=Tkb:{f7:U 0hz>J^}U:(/_݊,[Pq?㉗O6W_{f>4@IW_P- 200}c8oX͞:1Ai1"X>o2߼b/L<ڍB p;ˏO72n܍5RBW.vl̶RQZB0$(- 0sN?Vp~ B{yl( AMR&$S^\KC[O$Z|-}[=X#o4zlɓpd$Bu|lnbL:Hd! |_8#`Bݡ`N)pd) 먩EJ_ FyԑzbCBKJUKUBs>CD t3S~ߢ 6P\\Kڵ~v͂ݻYx9sxWge,X{~~m燹+;}f1u|My睷:uڏq)'o5kְn:8hEQ(++#8gy|Is0,%%DQl^&vM]];wr&Ooٳ={< .BUU9XZ~nV8pPW~N6mȤIٽ{7TWWSQQI[[lٲ۷PZZʖ-b[tvvxb>{okoXM7ķ-9hq/QP (,,QKYga=)m(N O}S}+nvz/}'ϟ>[{ۉ ^5]"}<:ZR`)Z1w\."-AoE.7(te Xvs¡pM 3RCtlb44gJ=脻ja׽N!*ƚm<:B (/(eV?(9d B(A&T99j# \sbqc1¡ ,[8}Qa9c%e%ScO%?sBE ֽ(Z*x)o2j2,MP=QAPE!*"ypl{o$i~TWw6NY'ʾftTW0+ūԽƦ-@jgBUD@%AB4T+[sMLu%0B'hhi1&m$d!"]xH$B$++ 7+Vp-p '/ŋpUWnq3sWS\\LUU5[~Ȕ)S?m\wݵL0n6\s-z > s1\tEyH)Yh.Z)bCq3¯*n7s 'rJ^7rUW#`Y\qg@0dҥ|S⪫/,?җĬY3ַnફ[os/\G {>ssqǣ( ӦM;ٳg#d*C!꫹Ȅ rc{nVZ1Ýw;Ϟ=K|^*\y~;W_mE0υ^w͗%0^z==|\t8purW~ljv.Sܱ[.?Md; EVRJ$H$dppjJJJBH$<ʡU G\|2yFryWsv"q\-gzM;E=ikFYY)T3[zc>x-Yƞ{hm t:L:;̳Ϝ 9K?+xUts-ʼYłʓ?x:gNsdY¤c:4p'#?R4Xb@H(ħ?y"W8yфAs__?x^@BY$LYQH X}2tDs>_P^K:2b lyJJк:PA2L^[ÜE9Ϛdͼi7Gi9`ߤuvo~gP-*i09zBJI{W/;(J{kku#:tuwi XG"N=H$o:˖.(RDl0F564 = ilھ7ȅgHueǭab%t1z$;V3k "EgZ۝3ˠr@Lik`sIIKO '!=M3qڮ2C\3'=33ORJzشv-k_~C.AKhiɓ9S 0u/n% Q]S˞6}'Aĉp±GAKs3VϜ.3QX &ۃ(lR3+u׋GchNq8@@5֯ϜmʇsbFQQqQ@")%eeef" rY厂r&+)%]]] Fss ?As匿˴(ZUu@h,J֛X#"wQCCBCII;jxt8*L200@$AUUZZZF ?a(XZVQYJ_my4LJ]F p=3qӶaJ ϤLҥdkV|h Z j*b J"pY$3IVbK.s%8_@꒢b{ii݇qn  3ktQYp!/q >A @UU{@/ƙr7FJ$lv`P@P@8$ s,$"ꦘf({ܦ;&1!{\3V蠮n EŅIFRJ5b@B1**-pY+l 0Xgk>Viw*ErC!7s~NpnS&9)yd)t){[*ޟ?+1-#@ ( (FM4=ͮ]eڴCZ=~Qӧs9Jc\*R/!,PPQYeCP,-RޢAOI*5v M12n(Eӻ5Ϛwf^fk_x{*F:]$54[NBlv//,j8WF#)ݧ[ǵ \Ǐ~<2/~<2/^K}YYSNw .Ԃ> R;aSLq(&Ǔ*;.#Hvq Y! z{{dWi!n ; չ\ !͒m C!7/\3?}/%}.&dg#={pbt Y,Ѹ~VUAV $O$gTQ᰹ą帓Ʌ$P'M!/{Bc!5Q/$خiʎuy&1rӧI!6Fh.x6R_f3_$;]p,F!ֽ_ن A=z)|XO8MC= *CUPgxeϺš~/.x8gD5d(rFfN䘵N -{YyhfkC."FeeqC$24<~x>UubHYFVFsǁ~9@~kX!<{޲벅L㉗qn XwxO ( C0p8,Sեq0a[@CԀ߷tޫu+SN+\l0ξ}6D0d8 ( ( (pIA>\Ll*BO):4*[&K;‰lJ8Tfȶ(pU'FN~懂>Z7[?x-㉱[-eR?i惀7qv ̽|Zir(TNFKS.|x bhHCwc*'||ݣЕr#_f qg =mn4,y-PdFONǼm~ukTҧ-$? Fz?ۖI qf$y̧ƿ=ng4RƸbpRyطoIM@VG:$al(!|:~+zF~}hgd0:(g'~A%yO^yf2d!K,<٢Hڜ C6Hջ sޓEs)7їѨ+FiU3gQHJ.Y'O7߳D; }#if*SLa>BYl92>G.F /5߶57mg|&U̠( r,2oyQ,3=dDLu.h=V3򤑭ow) JmƳ>ˢE8Cذa/'r pM[C*VW4 IDAT#;_*J2E D!eGcǘezec9q">*q}?f>BO?V 1-;P̶HL4UQ@QߊV !Ԕg*xL(VRY8ǽNO`(u̳:üqJ4)-OڗR6S4LET mtutB!miR ٥qCMt(^\![3c=KL G.[)K|R@uKX~Qi>#s+K^yuᄒ_MH pN? M3O`(v:Du7 ˨H+cD~{#j[0ؖ6 RN( 2(  %OUPUC2ևqhMhkL/imV '(9*loryZ\E1,N( .u]7R3M7;< z]SWwpēK?w>񀏲5-GcY3>uC:AB t]'헼84ËQ&2 ,fG6|gÂwǨ[SG`<`x0Z L@|,uRe(=a4&Ӈh@P ]/y%񉡚@Zy:ИWRż{W+06hCKybʙ%ONPU[3LwM5-R dV4ksvujPucǎo=uRRB݊#%~Kf^*ҩ,_R)ޤehRoLb-t2ncc(*fؤ Tikʢ@lXJ褚E6;rX9PsUFkݱFeɅGwoN4%dۉi Dc|73pVGd= X=!2()D.Vgug N??G*O[XBጯA(;g~ZλkC!j-kdbdMW72ߑ8A~)Z29kLgC:ӹڠA'*0Kٔ~eyR^N3wEګl[]AjG]s꺹R鯌?c!:\\m{41}X[bk> 1RӯLihYFn E{BÑpEJh-놅P|g s+Hb1=`ul`M 088if PTTZeƢ+>Kw/ ȩ jHY\v#E8 J 0HNBcfn3Ś@97ˢi 67r)bSIkXL*&Hp)1ލqlf,RHSn57`XyBO<8ñk!+G1G +4M#:?dCZZZ9Sb6I((s%|<+wou4t ؃]0d 7k16j1^PcC@XJ _?S!XYu{'&OHIq1!^JRF,nDjX<.M̒6r>sBJ"tM*3/ƒ9j L=q V `d nƕ4" kۯXiֿe5n"AL PThtP5h)3d5nz>q]`:]9sV2 FfxkR?o]qM=tm@iIаYD,p WTcәXݝE ڜbN+J_S j恴Z[Sau?>}$˶Qz&2 xڜq}w*Iz~#t<L 64FoXq{c!]DSJ {r%Cy2]RJdAܸX4.eCҜpm?G$tPrLi4{躂"$tV&BHϹ!yu9Lѩzt"A?7BEy9bxϬ^CӇ ĎBKșH P]%а:ҤXul¬Oi?’+-ӏ9}ګ:˓R &Z`׎p-Zkv#9~n- 3|83FCN<646cdןGJIO;aIt-L$r"A}NbP[Si45( 'v;ؾ};'N```ft]gGC=EEtww|Hc|F2i(^y}qÿ+% go#7o8o &10;d|kSӴpoMt2]F";d2ȧmIs`4uiiO[QZqG-vMP3'WlTZz)Ł %Rrs2yO;﹟r)!_y ɧ/'?_v 3}|{g^x )3N_RLxwV/n 5bV )iljwW^Eyhzg_T5W\pzV'$h%E p7oi 2yb2^x~htMll-riy6zS!ܖHv#Zϖ냚K3<*'n~ҕA,nTGo)@h HkY:#g\[ ◁oSIuMy,~6IN荥WَFj,`a'ɡ#`lܸJKKjBss+~GGvwnK)4;wγue(B(ۘ\BXW[28 ߈ LS->/wiw<˶Frq?y)?v"m&L -a;,g]]v9zzz\[[U}uLM7 [{c}Il޲ CXl=|*|+qg~ke6?ȶKp# z{+ r8ŵ߾.YZ.o"I@"`8=FwokKEGd03Nղ0;'-9t>+@?2X;ˠ_p+gHyƕ[q8 Ovh/]R t{BMmɣ O\7y^S[x9+ӈ[4u7N0$yyvBU\F;I@6ؗy^zݿB):Tij@i:(1^ qXv`\<'3nI$W ($B~|l(u"j5Bkma'MSo-!hJf 1lܸUN1;; v͸;\qUWޏ5/}C8hJ߰goT^ukĉ5❧_ko7'ߏ|~z}xdf c\y8q7G3x=S.w¾W1~#3Y}2;wh(ކtm0X(ЌǨO:mυ0hy엾*?<\wr?DwI}QA7Z2|etUy þS璧PPma= 1F]ݻ-@6~l߮%<wowW@UJ  EX8XCd)ZD$&Boj-8H-&Yv;l]&bE)ET/(^ eOr-}?/8hJ\x9KpLK Qܳ(122(pe䂨hf[n1.8l[{$Z{$R/LMAp]s IDATĞ;שC;X߅QJޒB:/7Oy+̧:54*0ϭ*ցb˦}.ߘV 0JpHġS*9߿m(W0^c+(k IHꖗ} "YAir໌"8> PĘ{Z y!ĖJiw6.: k^F$&q(6N n ":*`GeNkBwRy~ / 7Jkq/8;vU;r0^߸6/^yaW_~\MT\!,]~:~QT Q Rd@*t zf>keEqg~3 I+wį7u_ۆ"Iųy{M!WuK'cgjfqF E5 Tup@?` R[Op(s,X˩/} X5tȮuiJq7~哈+W,ǺGɧ =,\}|睃%cժ8쳰[L6_]eK7W%ZmO)K+/\aγ*k~~wK+fWk Cc/"Rn hsi_8X #0]AgdC1Ď~QҶxNl]b,qԉkwwr?> Wy|"ޣ5[ac9*TH]Vksheuap^we*`?ixTs0L̦l( QGqZf+|V%G7yEgxVw uhVpWSSWpEczG[nCpک'aV;>o_w(}=f^%㎟ރ^~Uʘߌh-2G͗LO'..͵gcw(rYzoPU,U:HT`ffhZhzaOEZ 4 ݐi"IG)e<#+\u]/s\Ro59RI="Nx%ݒLC[9>M0hw3߷<xI xF ,i2*Y r 1>[86/`GcsdAWDMĵ}_L j%QZqI^;|ng90ljCQ|%e[Gi*,r~b5w 6㩟?F.<  SO=:kѿ|]š^^z|1GK{!݋ko$Ҁ5ZcEB5(fHz,n\@T C>m*kL*^W|QMkwzuэ\fWd@FkbLat6ڠDE>A*BH&yyE>r]YwLr1Kꄧ6YU  &.):yBX WF >犼}sAykPE|z uۊ{n:\󣜿S!V)@pKWʏ/5 ,2'CXyI,?}l1@ڂQ~hC\L#=J/ n\(~b~5n_]=>4Zq}`ۺ{ZBHfh%M).8z }5KLjk5딾< ):R4G£Z~~qt۶Zx)8hJ[O?W^]N8˖.瞍g~p7m 7Gq\oyyQ~SXh NE&DzBXͦEÓ'ca#3OCu8 $JDz@%=PxOՉ>/#.O7@ RTnq X1[X9l,@t= [66Rjec9q?wS%X^AQoץF&ߨ*|/yT6ϹNßn:L8iI#?;M7Y du _3O* }ixd=O>Q~ "b 3)aƺ$?{ GD)6_ Yq? Pv^ZQBno~T C?2MϤ;B-JJ(7D-{ fs2U,N$H.QgFGF]~?3O=$;V[?q,8eV21L?n|Wâ1|W~|:t+ɝ?urvsP${%FRX`qʢ}0%x@L>Fs&],W VH9/oץ^XT: F ڌ؃Eh$2k, y]+Pvnx G2 QGet1j@pyBTdIdXY9o_RUx%8\PٴUxG}g ,w̾A_poO6n—kiU+qa˶7#I\}188֬iy/~ q_p7aNnq'kS֓@:V5qQqQ>%/}hfGYG^)2 @YTkJu Dq8_DzgRav C)[׮D XkԖJ5J 65!Mh,l;V-o}}JoU0HZaY(MʂPi9!;jl2ߝGbl{ORFc(}6Y勈ey3dB0T`0F-ZܺaI1H01`0;!)9k˺>E?OOqa}Y !`/Ʈ]_ؙ?pQGa|b8ŋfj\|X{`'+ظic5'38 +/A;I|g~w1jcOhn᫮@E@?(¶7ߌ|+6mْ,8]vE}< )~;pܱǀ?t6l$tb߽Ǭ[>`߿> WKPx3K1Z=RGԗ^.GYkRdUwۂ9yJ;,Wph |մ"$vhق(ʌ |bC0mIl> "60\){IyuR(< )r@ַKŏ{cX@rWCX1yzTr0 $}gNBt]5bF.bf| VʰFK9ʝ:+( Α $H Ý$7o+z3>0>" ;mN .Yxc|wu7^{}fO8Ǭ; ?{qz}O'V\G3y 6m O5DAq׽E BzYx-g⏕]#0/Hxʂf[vBGP_" R'`ʴ|e`_ SUܵ|V(zR**}|;Rme=k^0Qń̇kW= a`)bK-2ꕂ;Wg,VݧY w99OI6n~GKntdw /BM||^*=*s.W&s8/K ԨlZŬ4ow PE+U2aW/[Lj E h&C}ٓG] 0aeŶ9qUu,QʫgZk&[oX̗K"$I˯ -Ī+בҬ 0  ǟ|dJx'+.8otC޳6oQnLO<4RcNB}m64KyW8YzTuPzV4fYS_%@S ynz-1/2¨w/PM}hY;Wa?in#yɺI0=D^Y""s}Lb*D6#,#wNvOkzX#_ݎBJ8DVvx!Y.ߞMm:U;{ɫ}?Y /p{VD@}rIDYzvDiJb.AQ{]#~|·ʄǬIUG@JRBÌ/qLja 񠗗9- J&:$?}?I<) ${7ᘣ#qUWbM.Zv~ax)'^q^lޅeK@hk*rB)վWmy+C/\+~e5!^Ly)̲؍4i])*j^/hM6߱Z q{8"^wf=)tT_ F(¸t3=t][uv_e;dI ٦»&0UH/D.SICDX{t5$tJhP IK'6g _Q/uNxIлZ8͌:2տf%SYék`%Pe9׵|֧`}(NPIb%΀ĜCBeDjF:!^C3 .3I޹Ivоa޴֮"[mb`{53mfBqx{|G%X46q Bd%T-g 0>P٫yD:{_|K{![m|j,֥=]`ymܸ06˖.y瞍~`XH* ]:X&c I@{!ߙϊ@[~Ar?]A!J } ) |uW,O\!fIX !~Dג#ja e^z .@?.H pwHZRBYY" 3ʭK!,R!\6 sX2e3xz;*f48F|h d,!i+jtu(E}#u8J >rVzW@N0g`LP`lb\4*ac}#Bn_0K 4ۨnUws Nu8Zgl1ɜUy@^[x4.Be@V|xσyꄘ̜yG|j ϕ,pR ܙs♯\m/eB=r`&el+x#An1Yx +I'RA)iӿ||"VX?${)= ,E矋i|[e>}?Z#?|~ѯC0)ׅ)P{YɂB0lˑ@}WϬ,CELA;sg @BٸZkN|.E;n< P"iAcW9!.IQ$_WjZ M ZEű&VAn%r2|y&Zk2ޗ_!Q5rߴ^ َu!Rӝ{]/>fLt Ӱae)x^d3YYTr^2a ! ǯ#RΜ]wzFp[ɗ}EỌ3Vvr{k<BT"$J)}|/حZ{pN885=/*%{P #՗TS/}B}DLHH(`i!xĐW2#XEwġm⺗.+oCewBCfn&EOM"dh^㟁I iL71;;vv$\Hey}V&H3 7@vcz3稖 B4 !01D箢S@S=Db`D+\`}2; !!~ƝW9_$[JR[A,FXDb%wvYDs9gNh}"=pD,I+NAG<_8o/8䟐B˴r˓_3it o?wZr=^|olUCӒQO"O7\()H`3\-)fy鼏8,#*(LYwC.O?,KK/qoeŽ⋐$ ZR%In'j5| Xb96o٪'_8ntR>*a=U5g.;0lYuz6=J pa])Gp9{^-jx?h-jj<ֹ^}1\v}쇬%sʖlY\Vu#hgVԄE=/l\$,7/l:ELkp(\꾯:9Xl IDATB<D2!a!!?I+GȀ~&E,jX<\P;Iabuu#Q W( |eLІ!nGEڵqSOa*ᇭN'^|e<3Ϥvc;ǿ,/[+V`hAhAQõt7|ؚnx1yKK VUFN#o0OB3yeJpu غ0zs!2g>h7-/*$Ijh4099FZ-8~J޽7E*8(=6Y#cmKvpW>=OfYe++/^^ˎQ_}ɞlK[ =B9{g?:W<,"[d0X4g e,f YM٦#u01A=B^F>4V@VSƭr1 \ [Hh5(+Ŵv,xQJqˏ~˗o^SO[ۿ={paQؼuJ6lڌg9C8p]?Eď9fN>x$IL7Gj&/TWjMԷx\Zʧk*6@ 2|Yr<,ڒQ2MƴХ-,C z22bh!IZ$xGU#S*FtKTe{8m?/5bXΘ=y.-7d!^*2&fPZy1DEBFyD<0=ىƘ=CL%p`xi< { AJjG "ʨ;p(֭Zjd,ȩKb;jm0|V7ތSNٚ0W B`.$q8-"LLLe}ѐn߹˗-?aW\iӿ^wv*D2ʚuOygCC/=v.Y?Bd" bbr#jT;OPA&ה32c crj@|yӃJT[An G5D2ƐTipM+@DQKbl13 BpmܹR+߭ k V:T$R>^5ުPqy6ͺ/$28\^zecYm7& ثүˍ}Ps_f ЄKp&|y9 FO9ZcwI'Zx~ )e 3-f{Vތ=S3pK_|͛ŲY(022KA:uI{molM!$2<]`^@;MVu"Жyl x `| ,ޓ:D}@Ũb6ce "P:ee=6B*2$W\<f;PR%F)!q(0Ə|F=)"ºխx2D҆ P6 8 -6RW\eMz橢1₎LYX i%KL|PrZ`U>ϼzy Q~ȡ!G?Ư*y2>1TN& .:q=n\FL Ͳ fmGD{aԟ*dcOx"8n3VeCxk3FX4yB.+>O?㧙%{{)u5sTuҭF4<ÜM^U8n5]]Wϧ3A$ NjNڣf!IJfSYXۂ.cY kpWBļU|U!BtϹU'k]売k#\a>BX;⏇`AձYV f23@Ay'9LRk3ݡlX,ff c^w"GnF耾gP_-!S#rdgkթ Y4C XD0~\lE& T_e8W\WYy^~h,˛t-ZS3GRJnݫ;]|}}O]A%۹P`c `ӄDH]FD92bCw`Gp! %?eCh*uJyV`tvUq2sߕy0/Bkɇ#ΖsxN?55~z(534g {k-\9*g䌕 ,.]4?} ;w*f/ǥ=lqak /↛nLlǕ'pxϟ{ǘ/=vE8=gc˖m[8+/7x3R,\gD8+71KKb`럶;o(74n^?jW ՜ kx7\w{ /uen3\G"eT%Y!䔥X"h",XZIKjkeȸZ܀Bք}9O'*@]>0X5߼Z?ez<n@#k!C\BEℳ^925KдT8߻u$˟)&'Pױb2,c 6og3N;9`Z-a#Z`@JSDQaqֿ! !c<IJKtl޼v [6oE"k<7p߇_5u7`Ѣ1!i'x}Fk81lܸ CCCX`Cزu'Å矋Ecc8c V%Kcؾc[s(6oيzccch40<4zakEEذqG|Rܵ ;wj+Ǎ?Rq׭Eذa`a&DQ{N`歘O=5kV߁[cPJqH_ߨ3\e-@SyقP0iY`/S@?Mp{7~ʸʚ-C4){lFl^X^Mw lLݤ\SePEI3uQ@#:IXfY0l\2Vټ-@晿̧`(˧VM(*qB/P*`˭#~zm9JiX*sY&l@s4y pĕ|FX}]B98B@CB9wĮ72 = WFcp0}jY? 0ʺwa@ks=<83.:\L7fs/شe -]~+W.U+x"wW x|c-x}Wy}}}8qCc#qs;199ukCFrV}` X4Y?1G8u5ޏՇ{ VahpϿ/ZuGmol駞n> lظ t"|x'jJ|+yV\p{pͷ?˿;\_t}yw8˗.Ŗmo`z\qv|9@3uG^Wׯǡ5kVcvfgN<?~(6n V# FqĒŋbr\t btt>)! jM܌r0NqHX29d'^߹%:9{%Xa@U %@ 2y[3M7l0]}r2Vk>ʗc E֭U*oGYR89:?X}q%§axXj_OgpVhZHi"Bdj`g1Hy`,a o~hwk1ɹ[ww9eup _8<)!f_S=n`fR酨|ʮ) ]oR3iy$ǖ|9?«s[{h- `QWxQJEQd4UCpKpꥹ~ºB$mҔb|bu}8pk3?(^~5~?B|A ? |>̳G4Hia<#h6 Ӕ28Иi[~;wVr^}5,X###x}Fpe8C11>]v7ߊ|*tAD"j1e; /\>6oي}N8X<̳x1Gcҥv%`޽hZjOk1==sIZ&; @ 0/2|^~U O`1ap\< 4\ ƒ@@ u0ćIr^  X"WKkh7`-rS0div}.oETFX̓#Uߨd12d91~G$e8wkogO3a9ҥ^a[ܺPPMS1ش&ӣ`ߝ++*AfBh; YAJ]副Z8*+4tm~C1>R/ʂˢg0x~D?aʤga#u$ E}CO y'Rhޣ)ВEk"zϾlLy1ظi3|%4Xqʘ&^~ULNMah6hh6zqa׮ݸ0>>{vԓN{l ` ,l`=)z_7K0.LLN^ /4,^]71;O=Yu ^c…4gصk7fffwFvvK344 }1>>ĵFg^4X|VX8޽{$ `(;j)Ŧ[wݍFK.]6mC?{lx%hص{Zj(0:2 i5q<C-[K ƙaXX3Ka-yܲFRp>|2eKDzEZ8?v?M3z[O| T֝ϗfVjitku]4"UtFD[Xf<q1^H2>}2]V+τYI(] /~5p#mѝBcS*&3qhlgE`мaE_Wyio*_<͓$!7np@( /Խ*`_;B^~u=K_矍˖)UYx+JvB]{#kV;v.A#>M[={A{clأn|s~r]xܒǀz-?v5FGqm?14[M$IM[l}^c qwN;\ ?3zIe6o~<4LNM㶟`ٲ'AGasֻjq]wcoјA`mj_kZǟ/7>]ǟc cشy '&$)݋|^|!N;d{غm.|ܹOZ{NkcƷ ѫ'ؾM,ܹ'pibG^k7`ffl4~x8ԓ{p{/׾MZ-  aӝ`5 yT; aBgdR@ }_\;2ցJQzg`Ye%L8hM c{:FEe꼴[8GAUf5t2l|ME M~ܴab&mXUŷ7y"CC5Z-4ffpak)QLO7@0HD0=5sI~4[-PJWchx` $ꘝmbfv}:j(Ofz.b2LOM}}}H3-h̠>;ˁh^`ffiV+ittZ R-uL7l584Fu鐪QA kZe26W Mʳ,{oNl}a=Q[gf"m+J*/7~pYXN7pr_5/OT/k Ȋ5.lt!."Z }DDZ)?؆@¢s v5QkX9ȆK*islIY_X d;L"z"ݘ{Zdʀr`?$Xdt^tS+#+7Lgy4 }M6ٟ?O}ر $I r6n܈-[T/Cem y]2|'!(b3⸎~ }Db %WФ)FB&Q8&fɩ)Ȯ\;vDJ)(¢EchZirr Øn`h6gncɢhL7099ǟxJ\46&,er⑑LLL""5aQ!ISHT-c lp0`({I6@* zZR "IPJj`CChcfv`F1l"c c1zvh5gۨaBߏfKXIw!H˗-o˗.Şq cfvV-DQe˖u/Z7r m^Fll+:&uPذ @o$UDsi_tEaBˤZt![ Y IDAT!xǿVN8F`XL2a+ws_;h E$uҩ>ܦ |x]+L0Ɲ k) 4}4 KYmMaPW{;),tV?Efq~D2\s!m¸<0)HR]LfU~C8Ukj&xi#z!ʟQi\R>TjbxhذqZhZm6IY Vaz#MRiI[#b[ pw3MP_Yajj PJ)fHq=ܺW(ً;~z?h26Gڈk5 j!MSq038FI m'HmLOM!bn!@_f a#&RFh% 54AarzZhk{[- cjz1Z-LMMјA-159ɛ1\WivO6"T'Δ>R4ž)O8KQ@2n%<Pbnl?uWit-)$dZ%n22_R#\))E)PX!iBJUQBf=Wl۠Q(M2~x,2!5 28ܷ%2c9kt 髉"g)Lr 5$l1Zxyie,.mQ1\W DDrl690tZfZ-Yd&]$#(I')+5M{TὪb \ ^a/*\\RVjӴ12A.fxEI" ;cLx|biuD4vZe-"v@wYL~@3<7mO8%ڦ|1R7Lǒqʀ4׊;od_H"z#QQk.`0s?yM<Jp|2|_'IXo1 R1yS$ 4^׸\XAhJ^?F,TV4MJ aBЀ[HCc SS@0<46+A\t1EW2 [~̌0 s 33[8 0(z $"(}!n]0:QԤ:`xx ['&&aT:Qא&)8ta-춻VU/d>aݓGlm->= H!bܴO|Vpj{+@)}]~TǶq|!Ox`ْi YL}@h:Y*1<> `t4ߙ`p[N}*|lQ. ]Z !4„aQ:n<*݂5j6!BK!@MeT P*ݵ@Ti&Bgf }1[<:UZ5T}'Z E;.MSP% 37)0c*;GR)H^ƐLO5Șr7Ai8^GI)EJNR^&N-K:"I$I R$ 4U(^Xٗ',V\@6B4*c%r5Xg`ʇMH8WRP8W,A/rx(oiUẽó|*DJ y^O.PBkzBUfiȒf~2U䀇@o=gM e@-=Ssgxdo*X}|u&ԿLd.Ǡp,磚=ʐeOߍS(3$R֗ s$1cH)CJh7I&?h]G aEmh#fi]1i#,4MD KS~/T4Oҧ#a)"i\q $iżl0zuOq#&u UwARjtH1(HR^ltGk8zfr*q4-' O^aS_֮N<jyۍx-yz?c )䘘[oEh$}Ns?g3r[D LDPNnjP)S0b:L\@A<CMsr5 + p ; F+udOnT]x@XQdh%,<y,|˧30:O&e>~3YOJ_6ҁ5BnX!dq2tGAS""!\OEb|31a=CYWƘ\LڂMJS`B|q}=ƔlJ@Df sf k5j1(w8GC%h%\(ҔnƕJ^ㅮyG~pn;F*xpϻNʨ (PTJA ġ⦳M΃ƾ(_Ige4}!ϼmLş"k@ WF7 -r71@n|B"{^d_>Wg"˰Tv('o'])0DnAD< gY`%bmՕdq X`2)zf}V`s8>?ly{'TWʅۀ}LKlPvuy!rԷXcၺNZk(#ؓGL6/0#zck ' ~UIRuҨyRqIN\8mHC)N sWy]e߆2TԪ@0/.* FWW˓p_~:qw잿}SXJ{cba&٠*`ʧ,1۟v<ϫ .H+yr0%x@)YJu`nP~jly] S"@r,߲L%Bx B.+8Vasțå*<-ܴzę_ #s5w.,mO`"&t/iBLfJd<7mNCXD !Q'<IpI z/єU F3(9cPYUQϏYEzq &]J% t IUھŏRG̈́lj P Y gN^|RK,He؋tJ@ |b}z,B.Qo,]Jr8A`oDpI#Z`d'd֢HqK ,R/-3E='kYF Tn˻J|[Y!hwM߸5MTJZVbS^U<1TzY?]_*KCgYцV`Dm'+633(ØQ*MEB)GV`Ix<YRI}yƓO(5Lb[]שb}Nf> [F.wcMwo%zuRoyVhB'yrF1=aIy2X1@bR]_L@ ' gXw pݕ KJ~@$DZe )zpP(kˢu3e @y¨e#Ǯ1Q2&<-]/eBU_VK]GQVËqKTyܴPQB$A`AUP`m,jb3|/q^RNZ"=NR?{Eq_w9Az`/(QQl D '$o3cԟ (bQ@P)"prםٝs9=sMwggӂ6좘[}aƥB~v"޾81(!2!89kM, !b^2QW%!%Һqў;eq!!C$QXW[΋d:tk'SXR9?&cj%ԾRQEwl\qQaG=ۏ`!\ދ9jZZ::69dsyI1v'1]u,ul+s#9U>؍`ZpeG\`]oxᗎ 㺍.R%ݥ {Kpmptx]s(eVO`0Z@Ζz} /wv[/p7/N'g[f&3<$Mu̸t-tZ@!.j8Vӑ^Fq`6^5la$y -f%V~NJ\ ?Z)Ȱl{V"!4/H,fc:^f:hXhg.DFl/-?bNeZ+=K|5 $Ng(Ehf'j 5'Ra;^q Wy%-5‹Ku$upn#1JD.2-CA i\ݶӕE$f1u. ӿF6ˠKnG mb fv!GL]|]uHЩG$Y]>Z`Yvsg"9?扛,uᛗ۹#W.iQ(([AFEݐb. C(O5 BΔ&ʩZ;qAI~K *((((((((((p!4BH54b(;4҆0 bzh5a~۰[ΨI'ף]:w{#vt_ ~8T *((((((((((:q+78vb9jkk1Job!}+מ{`' h܀?kֈ[GvECtn!솸#ss-ZWDibmnxmbq+W IDATЀşB4ptV{6V={OO> ЫWO{֙8{=Ds$:!b:.>֬]m۷cǟoTƠ® s:thBf=ę&dFJ9zVbXm=: >ͫѸ5@7X8pءx!jnj,Zm ^>t lϘ Lgc0W >]s}d2!YqWbؐ!߿4OQQBǽRk vMm6B!d9 Nqt5N!O &ÏkoE۪h]ƭ[[#ZLϿеkW>fGݱ҂mc0b146nr9$I躽 sKc#f/,[GL9 NP:ku {PB*_ CH<-Ə_jټ۶7߶f&i|ULwذq#oou fL{\;*\5rL6fd2HڑulނiO?3O? vq~:PAAAAAAAAAA!$!%FM@L86*N?閭`ms a&#~GOtt ~ܑv0|l1֮]? ~57#!סsдV]Vs̱9VG^9fDQ*((((((((((T8BA+8;[ʡܖQ7on"Ma"kseʿe?6X ǀBgȮvGdBM-dTs:PAAAAAAAAAAF! m)ChҜa8Epb:PAAAAAAAAAA!$ #Pcp;P=%E93JD,Q% )~Օ9.A,ʈ~>{V6 ɣ ɻrG)0Sɧ#|9s%9$S%o(u>JD,Q$K(ju>JTBva bGF媌uIL@4劢L |,v--w\Ai'tԲuTa*E!|T(3V*M(U:YGIQ%|d:_iY:r+GIr]ttRtu/"OW\vZ9b @e» ʣ ɻrG)0S AQ9rSJ>*2DI(T[->JD,Q$K(u>JڲD,v튺:lܸMMesf$v zBCH^lRƊ®Jױr<'"2є+2)(((((U^|d)'B4tݺu J)mێd2dRB 2Pl:+vd=EQ(DS(ʤ#܊ߵu֎+V"H@4G<rf޽  pC5 tua(UgMbŢU0/_jL7Ÿw4]-7E];{J P 𨶮(UgGIJrn?;aPlݭ@a—a Bݥ'(>HeQzSzWj0Vߙ+X(]Ux(]Guŏ}j* KVy@+z1x {ΨKbEGڪ:(ֵ(DW(BJAAAA-fxLkG Kq;p^|$i)(((((((x!i4͕u Yӱ3saVǓ$Nh e0Ju+n,ަ3Wʝ1CCH)ƭb[k2Tf:S"%!I-RA^e.ymKBlHB:ƻiŗy̔8ǨpYO5ee0ݕ#+3$||ۣoT{aW|Z,磌*uB jk Rl.v@zt _ƒ'bF6S[~RȠVԊBn19jMf\>R[RI>*e&Fה1p/74m3853D兗^Ϧ7b1 uK03H$hhyG4q[T(^\ #ځQ!VO.^X ]]](ޖM JRJ!{lՓ骤<,Q畮Օx%]&\rrĒ^oEaXR-TbOX}_ĎdF`^nOHmNnO4A6j4XLC<ӠiX,x׊bH$MtJўJ#4XWXS4rT$uό/T&<Ϧ⫊;B#n >$媴TC?'luX;*O$!A!6O+%ȖnBbőUatNo].JӢI d+TUPYv 1MDW R rHaLWK"X|^wKTx̘a a)C8<̟E//Àdu^%Nk#S#![v!nD@uD#F͆R_ :ZRH&cq:٬qx=gTD)p`L.0E̠SEM#l\{l7. i7L/AyDDLrzOޚcΦB 5RV( {_W/ĕ{['WHS%/˒խLEp>2ߞ}!pUJƣ:<யJ k3Ӡ:ɸ94$AA>G>qidH$iv,GS3YGDIaq(dre0侍wlQMMNĔ֝$~f9xAYl`}aVK'R@p9 .j7G\21f22ٜ1*$b~Q Xis%Qխ1Fڂ>}{rXf УGOuؾmz޽{q6iٓO]|o*t T{ [kԩ.|9, ΝaM7 \.A[uk#o3ZBj'78qԱx\W/ÉŽZ[Ѳ};~szӄvyqc3O[0qĈM+@@Aov f.?Pԑu[G睃G>oĎ3朳pcGa5i)''Wr A ^ԉݐ;KS,&*n>'#+3$|`A|\#lT(*:PJ BGcfr9ds9P{"#3Fj 텈\.\6'ӟFmMA{*t&\^GNב֞F.?-UIE?S 2L?jjˣt< /YZ>.5eJ`T^n|Sa\z–Xqxb MRJj%1uZ:eDaAgٱRQre+Gq /7opVy8e]uU%YWLWW! LX&ƐkBRcg2@,Cmm4i̍6y;L:?Z3%R5ZC;9?`_/ 7o~K`5شi6m܀N2t(rpO0l@??mmm)w~ 7^mmmxpOpI'^c<zM5N码83٘8n2=/ gr24?aSMkVE8Ӡ9= ď9_e vY'M8\,^c9 \۷oaڵhiَ/xzi8bO .5s&VW}ðpqᕙ/cY1l(я ;!q],]ep6O$+ᝈ_';G*%/sQڼEYj2Wdx}(r#yx |.t:T*l6MP[~1## _"06Ѓ:u5E]]L(@ ,&)g\&q֠&xLzrא_na%L&`i#uuF^fwk݌,y/siIV%e- 1y*I9pKWJro6:FEFwx`ߑ5muC)fj466#Hq@)GF4nقںZ?|ߠ \6N?ʒNӰSO+3gg@1筷?mm[nƢ 7͙?9C o܌:{/{A:ƊsVZ~>Æ xnjL=Pӱ[׮\/U+W~s;-]3>]wWJD,Q+WGaH553jYd29d9d2Yk$Pӌ֌XđfnɟbO ک!iI@4Pc=_6!@]m0Ru Sh6`u^\mm-b1 gC~dd2Z$$,|ϘriG;?~g>0,1k s?Ŵ*˯{ 5)/T#mFҺ| G^ !=[8:C-Xr/Z}x 9-ףݺwG=hkkO=c/vRxe Gmx#ᆛ)̳~ =61ƛp‰'q .7ބOO@޽q-s`y|GXr%'.?l6W_\.mQǡK.N)8,}kw1W ގOL5 J[wrr?OO'M^{/L:s=2vؼi#yM!_\ W^GKO<{~`L]wӷ/;uoڰl|h!F~|QlɚFa/1/«Osx~ IDATWO 㣧lUx>2zFy]u~>#`lKސۙ}bӶK4c:k.G[{t iD2i5NRd2d2hoOZ;jXLC&E:E*A*׍>?Cx4JgNNQH7#J#e֖2Ch+U83Q#[Ћ#[Chm#jb(_ŀpHY1C!BbVx .o{Q#)ndM;#YSÆa%Kazsӷ/F/}mR^bXf `1mVګVr9_0d0n[aеkW 0n:!!C:v>}FAlٲqOFZ[K|晨Ulܸ֭;z˪( KWpbkL]wwb ;af30\sM'pɩ/iӐqŗ H*q~U8ݔ>WꮫNV|1B:DW _j.Ѝ5JytH3;|lKud2Yka|Ť} EsJA>oC|(2xdZ[aۙFg3 VWkM)E2ŎGE AE1ld9DS+T>F\kwsKo HCAݱǞ{b[hհ=饝QSO?O:)~C4tjmhjjBCxe cЯe7mېd]7Ν;m e%g 1!c#0+#A2tC(xtƑ NХKg|,C 55`=3pտG^de>`@38L}Q|E} c[:խLEp>b0>sϣ:<யJޗ`<ǐǑe b1k`6a!3E Àb4}%_#>X<8X ^^= >.˥8姧!ѫwqϟ+i4R7ްa= ǎE]m-a'b!W? l0u+;eu+S<E]1oTJ k#.kysWLW ޞ`~PIeB]Bu]Hԙ7ԎK6}M#"Jo QM"nC@̏vy]G{*vC484bwЭNK c[ #KwaaT=˞ 0e"fوBê] ,W R Ma YeWu ~3άU0ŀ#.2n6MxARp<:G&\<럹8@ ep[s36odm۾CB8y;||8f(tʩFsFqرwwLb*-]=@@1h`?z42 zFFMM-ݦUCȕv*GZWգ,ue:#/KXx %]E_I]9aUVZ٣CCa1LRi!b1cf[Z"#gL^[DL̶@W[[ciKFjMw=eLQ[qv|8;#wڰ~W^7ߠGϞjO#>xl+[AAk [ˑd{~pd2Y+lm܊ :=zt}&ϝt:ݧw_̛u!ϐL&qȑV66EKK 1|ڭ`#FnqNlR_~u|n-"Wwa'GCvA|aS-8ƗN`ʫ/>bVG'^|uUZCUxTFW]:ӥL&x>ñ1%4fnŒ?BtsfLӐHĭ_:A6Y[4F\EF&X,nAσRØL$1idD#:4dhiiCRh&A`ޞuXH4 D,GihC.#nTיk#5MCNhmm2x}w/V! v|k`~r.[wL{-Y۩?^wP:~*st-]?[s6l=8N=lx|e-/xi׋JQU%I!|tU>>JD,Q˗WX'!D2H/RCYOx\?0h`aq7d2bZR fx^+fEjqmtk],.eSTԘ9y]G> rDaT1lL5vF5$Rsz.6Ļ%\!0GSq+- pTs# {9M"YtzY.5ˏr#zߨ܄Q\#!{ze؝rl.SO&uvE &'V6џzak8rX1+oV`…ey*"扰r+?^֩21sO?O%9ҡ3uSe-/w.;VY>ں*o=)J#BDGd: >2~{PSScitfS \A@&ˏzN c;м̝JgG̏;|.[*!'2;#+ʘlڔy"o$QjݲєT٨b2yze4VLwLȌ\ƻ !f ɻ'FߒMx}\o:!D:DW  ׇ⇕\CR3:lu7|mWs`[ 0[G] r8 dUwPT|i!dnvOxOe]]ȥ0 Û,\;( 7ެłR^kTDoR`mh(6(;Yb[DpB6 1$ ؟oie|Yg9(kxgQмGBJ*qwl]wP.JSAvLEp)ܹr+K|yq/}yw]eG$K(EK_hO 2<+BMac=S6l6TyƠ HrJὦoV/(-Ivj(XEnjB}uOPkfQ듈RNnƭS.odT-E/l ψo0w s /nWp6) !i[1ds^o;1<*w]CrGOA<5Nx;\tQմrՁ9WGIW%˲y'啮Br 6 }ӷԃsli~/寲*x(4yCL+?o!a\ܮ%5?.#{Uî^MZU#+oXrzx~FO֦QBk^יn~±<†2,P`y&K!=Ϊ4DB]Ea K@~x\;"<Ult+2k$ds4B®#+3$||ۣYJ=BڋjEGtU-Yѕ;s̰ݞBx,yF/)Rj8ް۩Ń@0Vi\>(=Stlҕ5웄v:Cvo3|B3ouwIJ}.nRjf% Pg"xx(OL#C!bZs~!S|Ey!,,^[R9a6)],J^ b(~_t|uUJ+%#Jgf1#Q4^NMY\:P,C3>S hlxj̏41btD0sDm-/+ס6M8n.N 34MvA:ayq̷0}5 <0,y0bE3w4-7~ASE(*tQX ,w$k+ .?SI@,& YƧ4 \".SSyYdsyhxLLc:*O2aaސqFSrX^~'ޅYCh11 A!VeF|T^ B)ByźEE(])(`#mNQdqF;i91&)t3WnecZOS\oz0Fp$*9Ahx#+@^בuP,QH~\޹ō;)şv18(*1~h\(TxՐ\2t4J )P:PzX3ҩf-ujLQچ';sF:m@:"\ZH.e:i BzSpf~hn#cʨ|̭:ټ|>c|Rm σ } A(w8*_4Q4JJўJ!{$SN,LJD,Q% 3(5 0ÐӑSP"3u3>ͧoֻJhKO)mYum[^̟Ow$0>@X'&!m B+Qv;/C7Ty[f<ރKxpF}q^awuشQegܯ kkoG*6R׵B ʣ/~a Î]Ng|ʩBX݅ȧ|TdQZ|d:%YGEBx<VAA!uy[DT?u!!ҥ :כ!x9^TXQuQ:)J<(M(ŽXMAX*AawuдڬMV| u4nقNl`3F2Pl:+vd=EQ(DS(ʤs#^xD*|iXuJ)@= >EjDZk CJ0 lZ'An,#tiЈi!i1w J)| ʝp]\qG:LFJuc\B]sؿ>ߎLe P9Q֭TO[b1Qwӑd@C,W4V}JJ^D,#;-1X;u6/!4[?: /'tkXf /^ &ࡇ*8Bn: BYaXvgwu6m(.]w .pC9j_!(Eyw1s̒o+'ϟ^O[oʕ+=y27b՞ދ 6yr !nl޼&M oX`'D[[[Q2yOV”)ST*SE"㦛n_]w]Iӧ~fW^u])SJ裏bɸ[cY!qw⦛n­ފ_a⦛n”)Spma?^{-nfu]o|?{rN8MMMsCGjTnm7w#酑%0بGn =hܲM܌|.gMNaİjjLP覑7d02Z8^߲LY٘C?jx=C3yhپ [6o͛ظtFқxt]G.C&F*B[{;7nƭMҴ M۶aV MMڴ[l͛͊gTL=0 u7}C7AX!~Hdy68?xiٳ'vm7{ʨ*~4 +ommŵ^7bs'p嗣W^x0d̚5 'NDKK ~ilڴ z(Fwy_~9K/>Æ s^/Cb„ }~xgyfva8ѽ{w+xsCy睇N:/OcƍC~Ν'|_+WFcc#:,\tExG#%\h"Ag?C߷|>n … 1b?^ ݋/^{ zµ^޽{O ~c˖-xGj*wy8c)O NÑGz ׯw?ٳgcĈ4i8?bxwЀ뮻fxg1k,>Kѻwo_?0V^ѣGNÆ 0o<30m4s1oyf̟?GƱ/Xnƍ:L6 .<@L8q#`ʕN6oތ|DK.G?ߐJW_{żyObwѧO<0`^uL0=e˖ƨQa hooG^P__o믿q3<ӪSNE"%\Ç A){gCbĉҥ t]ߏ bW\Ν;w%K`ؾ};V\;*fϞX,g8餓\8Sq'#HѩS'\zw}8gFnF›o73⫯3fG4i {[oBnF͹{h";\s ֯_K.!K/555x7\GƁӧc֬YX`&O(q}a˖-8q)@3f{#FXrJu]1c0rHK/_{7*$IR)w}oq衇;A0w\W_}ж[?8,!8ꨣ믣k׮0az/SNqYgc~~1?x==>Sy;v}X`Ǝ֭ßg|8p#H`xאqI'Nÿ/`޼yhiiqpG fx'yfa[9s`ʔ)>|8^x,[ V^; xGqFau…ׯlܸ?N;4̚5 ?Oqꩧo3<> Zr!3gΝÇ uԩSilbݻŋcԩ%\aÆW^m]>ٳgCn0~xZ ;8ƚxuuu0j(d2;8qꩧ^|E̚5Kgwy'9s栾_={9ٹ駟Zsܸqݻo}( Essaga5Ǧ!CbA6t(jar0xyD;lLuJn$`& ܕ:{xeK nK}dӀ#?!`IǦ͛j?ݽjk Qj]6nT 1AfF[c͖S_ {Jde#B'A6GL|!޽.h,wb㜥#כd"XLCkvֈ*4MC.g+WX񦛰{ZD6 LWYAAbuBuH%!8H#mVgLCY.ɹ'mpefAE/~i1cڈ ȏs0WŎh" |X`/_n {,}YA駟⪫B߾}SOO 7܀?-ҥKg֬Y "LZnݺ , &ࠃ̙3y<83pO?ܹsٗ/_?W\q_[o.]tfΜ͛7㫯//f͚I&aƌ|r?~<;0… |rx5j)8qmg?^|ESLA>}Q#ӧOtxb̙3'^566b…bڴi޽;nvlذ7@KK n6~A)ŬYvZr-8s?1&Ol<֮]~cǎŅ^_|:YfLO=W_ŦMp-`Ĉ?)܌{ &Mo0Fb9L<K,'|b#O>MMMXr%O. tqr!3f ^x/t4Ï~#K' ,@KK M#F[n+W◿%>l|ϰl2L<w1|L4 ?k?Dss3|I9<.\F̙3}:[+Vի1o<\r%8kU(ç~gy\s (,_^ ;;gϒOFFsaʔ)x{{3~xf͚#<Çc0ꫯd̝;t\p.XlÆ cԩ|㏘L&yҤ_ܹsٿ;F7`0Xd >>>;֭[:t(+Vp=ľ}aϞ=L<ӧ{n(((/`ڴiݛ[ҥKyرcK=3f̠{,_P gѢE;w rss'cմ~ܹs9s&˖-kgLƈ#HHHp^RRB||<* t BBBFJ!*//k׮(JùtP(o.]G( wy_kk+۷ogر >#GPXXȉ'xG;عs':,>qRSSYz=JAA-bС[޽{%Ď;gJBڵkG4f4ϾK1TYY… IKKcL>]Z;|G̙3Xvn\~ 94.V_-Y,233Yh +XjWb𴇸;xwJ[=d2ab, ٌld2a4V&#:QinjZZZFZZLFDQDףh455"zZZ[ѵ`ij0 &LFC;KvXQQΩ'8y8gϠinv95^elLfw5E@&Pe(2D|͜G<~Tˋ0w֣̝(>8u5.۵yO<Ƽ'fܓ}d.`1еhзh1[1LN3BNqq!gO쩓dfb2͍[!Kp?BhPx=(,,ѣkjjbPV^>ˌ2sL>z=:lKbRvWC nX1X-f&Y:\:v;$)˨_ Eo{EǛ:aWe~myIXF;C ھѐ_qk2fU( d۶m?~ZA`ǎ̙3'J^z|8z"88X<ĺu\xt9=*dOÃ`|||HHH ;;V***$cH<3 ?Gݺu;DVӣG G߫?KTT|'Rd̙""lѣGT*EEZH.W)))XV>myH$ yP<#x{{3d]R b޽=W]r9111)nVuL&c߾}lذJ+f@@yyyf233Yj7mĨQP*aX6l*hf3~~~Fعs'GAx0avbcc $!!+WOjj*s0ɓ'ȑ#ݻd2;wdxyycƌ$''ԩSۡ>ݝÇ[oȑ#"77: &%%,jkk;tvP\\7o&;;ٳg?@xx8111DDDrNwrЉ'6l]v%$$>H*={6AAAx{{eR ޽{3qD݉h4B.]ɡzd2^^^wɓ'jLm^zI^xQٿ?f"9ԩSٻw/k׮͛;]PP> ^^^p "͙Ǎ7ވhD&ѧO.]^:EZr>|87|# VK]]&Laaaү_?nv݉d2FTTTVVRWW'SN%11vEQDPзovVI iX0 ҁj0w:`0H2 V >>w}Hۿ?EgG͍3fÐ!CHOOwY;---H{ڍ˵"Rt< ;/ agmH?w,vquw&Ppa=nIX M&#VwwZZ 0M( zAAXV83^GRbSdBouRh[rr9 kCp[whߓf?N5ūe6і,Z˕th4J瑬  yԦ‚ \,FK FĊb!R6ݝRbdУ׵" 덛RI}SC *'94tCC6 Adxxx{_HH]@j)O@޽: mtYG}Xvt]`AԡvI&hP*|x|vhO>d2HNNv)7͘L&rLdOhhh;5773x`6lăk yyy3pQV^͌3}0uTμy0I.zP`q͂ C9wߴiꫯؿ?999̟?fC.]:vjllsѓ39d2*4l6c0=l6K“cm @CC}&񴺺w}\ZzuĀm;.jJ}`0X,֙V+---Ri4wYcv tRU*:@O\\(X9ߌf'c9Ãyq{=F#7tTk*x~Ƌ\F#k׮ABA~ EJFq NLFr9&f¡h g頯ˑ& %&@9 C.W ɱZTn Rئ_.|GW9ڞa&D8BEHgԫjE"FL==RMx4\R `[Gyyp-*˥rRIZ>em?ҵ`2qwSZLpww ^gZ*6hQHVC(L h?+ݙ:u*sė@{a `jJҶweN;~>2AlihG6}V&Y-cB>9DɶpZ\Xײ U022Nss3Rsl"G;v A|TWW3hРN1L6nݺ駟f;v~zV+j?6Rm#==j_[wѣ7vZdd$>>>x"X,zIll,IIITTTtd ҬY5k-E>$$$PSSCn())q9t>}УG(((EV҂dBI9rƍZYbb"xԸ7\?LPP[neQSS/DEbZ[[1 TUUIpGzÃ+WЫW/O>TUUu(ׅ'nnn#ds ݝaÆh(w9?WSSCff&ݻwiFC||<f\xwzdH IDATEQ:Djj*FQQQQQZqAvqTTVQQ\.G?rɓuFLLLy ^/Y)~aؼy@1_ogp!,wnx]<ҽc0'p)bAٳ9p5۾AA3tPz.o=5&FG3S'OknjjKHG3Eξ>&#,>2t(>Jʍ&v܉`@wO&44S'67֫7cƏ[R2 $_PWru=s3gʴY/#n$1%Վ) VdvL {4ULL @yFZsgNoXDڊZIUE9W]돏':VlHO?Kee%2Hl|"A2<+FdOb۶mСC9rPc\̢\NXX 2TT*\pՊ'ÆsRIn?wM4syΞ:AMuJ]2xM 2PVXmn(UNuX Nu^z-O뒻 Cyf).p we/^V%55={/))ŋVn@Pp7OhѢNl(_B]]#Gt)dƲxb<<<\_pŋcX1cQQQDDDka6P[]r/]nF6o,gϞҿ3z7hmmeҤI .<ꫯعs'Fq]^L&c.`QQQl2f3aʕ+K.<;ٴi0~xF> ^Ouu5wy'cǎeŊfA`ر3J]]DEEkV%""v,^=C=, m:@pp0ϟgٲe7uֱpBvWTPPW_}ewrۛuV,YdrQR}z呑TUUpBr$:`KKK9sf\¦MER g}Ç% &_{n3f IIIlܸf*++e)h6y71iӦqF + 'ϗܕR׮]oXp!2_~I1~~~v4"2XN8!)8}$s|BvAwbVff̜\.+WJoJVKr$ c~~>'Oy2hM*XDD*+xocϹtCfqLn5E6XMbRwΜ:isYXKr44 PWSCuE9 ݨ(/sY}EAG.uENN6>,* Ν;k駟x׉cO?ݭ;,]1|Y4n}{`XছoA&q m^xy)=lx2+P0#/.Z(yR<5Y&9;앿6ł7E0ob Gkc˗6+;o61xRϢDPn(c ˹e0,|/;K_y]T] !:NR)U*^]aCg1m1=z$%%LFNN/˯BbRV < +ϞuhnnMrd2TUV"ѳ'iiiTVUq)4b,]L؁FRJ$'#V{BC !Hl~v (\ (2ܔWdZ[[Xx{wԙg<33\Jj+:BUZ! QP O Jkf}}=~~~ו?MVK.iʕDEEI@r{hѣ%4V7oSN%--7{~c?SNѣG^uȭz]p?ď[GPPpE |M<'//wǏ?={\¿,߀gF#!Tװaybj*>\N8jGZьj5mFͳJ!Tؐa22ܞ2q_u)z+9 )cJ ߝ$AHIIrj;#Ghȑ? ^{<4v~vM2%Am2Stmtr.2BB bGf}m#چȩ= P\T];w򃓷P>5k6]tT(r- 6qEh2c2qdװ"`0 vS[GBJ)G)8y~L{(ں+w!Cxmݶתs5u:?[ύuGZV)&뗴l[gkxY/.?/Z566J M5hj:ruI{/wZ~(HtZJ{Mxpsoן֖JKJɿωcؽk_VP;QRRb `0pȑv)BIaR;=?mbOKئ` =)jl]P!&8Q11y2&{bu` fmz釻Fz=5۳[BLꞌB &.@X[S͡vҭ["2 J`p0\@cϿc[-vw˶}~曆09|JNCqy^}u -=+kh;Fm_Hh(Av\>SN*BB& ǽWdXrL& "塴 :\.Qe(rWSnu&3t&X;5v}(h"-[RyG;O?-Y>cʕr&MhѣGmܿ|j[uVvU?Β%K\uޝ+VtO-YVD#]PDtQ;#q>LF#cƍ?gK=Ir-rI[} &$ zzz2klFYYY\h/o/ tHn IJT? @Ɍ9O.%47[mU[?w|mY 2,+f 'K.ϗ7 BPL\BWq_??FG#C@irv*|جtNE*Z%K $'JŴ5jIvv6z{jϞ%M櫬xS~.[7|3t.;z_;,V+r{vJQ,fAd6ـlovsz S9 g>*++ihh???|}}|2$%%P("''z聧'999jHIIAՒZW^r,:Ƴ` =++K {( ˹|2?~+++8<<<Egү_?jjj8wnnnCB ʢP'OGmm-#;;ZIHHpACz222P(Brr2J 9j!* BCC!77WWGJ.]Zܹs|xxx믿Ύ;{ijj";;L IDAT hmm%66xA|}}IMMw^d2wϞ= EFF`9wJEmmtDJ>!bH)"~i)ߠ-=zرcS[[K``QXXlk׮Q^^V@bb"477(ӥK[I~۽{v)۷Bj5B@VVMMMVqssĉ9sz)?(;y-Vsʔ)رΝ+}l O>x"$$$b!772IJJVKvv&Q#H\oW-App9J8(+ϹwwOdžl6(-)uBJJ JAj4cr!ڗ }] EonvA4f;ݫA`A=}JqQ!ϥ?Vt:ՕdIMAm,X̶|Ke! =9=nd?HB,@jZom^S*Ĥ$A{8,c(.*bϞ==bc]V]K f {I LeCI_jz!)#G@&H͛ghhیP[[hqqKvqC !%%o@aao#&:ڶ>Jri@ 6aBƒΜ :^c`2*sa\b|HUz=/f߾}lܸ?~\Nnؿ?J*ә9slB۶m ^ѣG#BBB\z$`z{{3nxV+rrر};)iO/u!#]$.>Bѹ۪BF~!!!4442vX>-[AW^y4rSźI]m-#G+>*e2GP}n8OEj`B9ϑ]QH5#"v;)  mI_Y~R$22+Wpy)Vvv6'OfÆ ,Z`>O?Đ!CaɄ_] O= .`4{0L,]TJH3eBBB/W^AVswQ*L2Ry͘XfϞMpp0K.F OS__g3?N}}=Ol_Q{1,]E toG.c2`ry 6l# `ܹ(J&N(@0`:˗fbcc|c{e޼y-u/L@@7o̙37hf͚EHH˖-)ѵ#F-hKkk+]veΜ9DFFb իfLJyZZZ2dO?4(rm<iiiLl*dݛdvٳgkWsskI5kV}7o䠸8?ʫJmm-&M"'':;#///.1 ,YFYꠐ"""8<7tdРA.Z>}1ˎ@VV=r˗Ku8ıwFCeĈoܹs&>>]vN#== ""W.nmi歷",,ӧ3o<ɺVyh4v"ZpcڝMFg˖QtZ9<6mHXx8z}lf;ܽ͘>ǏsY:w̩S(**͔Fٳ m.JIK.մ;(j}YjkkOyAx8u7nu֚v̬|'7&up 7sϑýSFmm&O@L $55Ѩ?Eyf?3@=#,\&~Mb˖-f}vƎ %ш,TVV}zzzsx/ܹsYr%sK/iP#Y,E\Zn͜9sСӧOoA[kjjp\ڎsXJmŢiCYx17nhb.>_}oo>uƊ+(**$fff AQQ+V>4GO&""Q fLL ~$a7qۯJ"-=\ nÈQ@V(HHH`a?))u['&Rpv tț)3)>SLUeNTT4:w&99Yi߾=|``I)w/%%@\\7A^$1;HHL U8]f\PP11ۏ~*{%%_1cưrJl6'OFKjîBCC5 0j[~)xL4b+s @Xբ]>s2_ws$TUUq> N矧SN$$$l]vlڴGԩSeUVX|9wq,XoQI*IdӦMdggæM$zX˚5kصk۷sMefΜ5_d٨槟~bɒ%q"o[P)::+Wҽ{wV^ qs̡}xnV :vȎ;d~8pO?AiJMUrmݺM61x&׬Y᫯&))(lBqq1tB^^wɓ:tg}Il6SWWG~~>F""";A`M9}n`Ϟ=L&-Os222XhFMcX,{&dWBZΟ?Ϸ~C6GWUia24h EFFvIIIA!"GAz-[ĉZtݖ[naʕ˜1cx3gNu{ƍl߾>} {*Ά \pPWsɾ\.(((`ѢEb6\.W뮻=C]ݻwSVVjLÆ o!55UV1p@i®4x`V^MRRk֬aС+h%ǃdk׮]`6lI~Ѥբy56ٜaCAA7BK N"`IhhO}={c͓l"ٻ! H^7YmOpRQ5%f]vR"JsA%et:DÎVOP@ QaH::+6k=\ib" *d?YFNhh5TKcotHlgAH8=౻푔jw[whh쪀Ȳ( ]o +i@l6+V\n7fР`L^[R~ÊIuzyTP$4#m|J1͵pRm_AS0̚6%[o%o|O}`+f4SÇ@ff&ciӆoɄfK.&**tشiV}ҥKt$%%1bF׮]IJJ7ҫW/ZjE.]l۶6mp7)((VZiW&߿_>}^zGtt֧0Xv-L4XN'iii͛)++c񤦦A@@yyyyzXv-cXHHH`1j(222@E6l@\\'N$$$Ν;4hqqq|wp-h~122/lfժU1yd""" bll,6l… |޽}2d ) TDFF\.;v{dYfĉlذL||<;w֞mP[lm۶j+\.deeغu+qqqՋ.]`0'&&@+3550ydHKKԩSݛt ϷMv ֓:ٷo۷d2i)g.dZjEYYjmn7aaa~/߶DFFjMKKsIHMM%88p- 77CZZDFFjQ7vfU^^YYY}hzvynJ>}߿233a͜:ucҡC$I"""BNخ];mFaa!Ç'11;7 gǎ2h ڷo`vMBB.KHr:dffҪU+ Wfq=hԬ,M[Ώ?Ν;ի_=:NׄjPWڴiCZZ?{wۗ YoׂܹC1p@:wܠ5q%V5uD ɪ`AP \J5o%Oy}}N@T 촹j} 1ZTTTx!2F"WUV)t`4mڔUU|rcn'zx[H:L&&Zl_*b) J^$D/PwjrU[(J{DZS.TUq,vQMX`1٬8A}] N G" @{jAߏ/TǤ1T{-xp{$\n ۃ[6ͬ (: +Iݕ=8n.rK({ TLSq%r1ߏELF/ a!c2Ti@$tu) ":(ڜ7uZ/v{lZ99*** QuNjX  ٳ d~.ZKkMexꩧZI/=z4+l_?L>$x rss[{ҥK=-[yfMk{1cƌp 32?_2ڷoa,k 6'QΖ!YY<;?/7UTiP`,Cѣ0q5/Jj62k[^̙e6#G)w J ~Cz$$}FoUArNT==EܪVЋU@əb;n^'>1 A&sWx(zI 3.Ml':uJњ 994Au\}N76GB0EOQP *WoA.X_ T毀A :v7e ddaΨ`Ykꨵ1-f~*~&&܁& 'Oa9hۦ!Vo<ȱc0 G_'%9ǃDV+>,o&oוoVyKygy 6?ݤ&kѢE /HP[?x3/K\qN@u,frNrQ]>`ޭG$Iq8YX qٶu3 ^Pu=dHLLLnM>PxkiA-jrɣ$CdE3=H)|ٿq">) TAFѩh%r?QPٌͦh 'AI1*Od?h:Hn7ՖѠhF})Zi$:Љz6%F=+qjjQ0T$I ZL؝.v;GV %7wA|IjN^=v-*χ*Ro\< dɒ%f6.Gb6ٮkM$p [&o_p"{`||^Mo )+ʒ 5Q@UsZxzoժ?VT[[ENJ[o2 Z{m D@FlK6XD)۠\frڸ4WF2$!dfec45JO`/Raa=oP 6!G{<n @ɀ(щM 6 |i zYV%NÁl0`4-%/5 #U5Vv+`Q6Y`wxmg}ҘG-[%_߹JsK ͵K?f/c[ե_%{~I/e^ }xWc߾54qlv$Ƀ^`0# BCz<"x^L޽5&˲Xl.`5l!vr9++WKfC_)qwsh~yw4S٥kΣڟ>לG[ 8jb6M_kyn}_T4Srr!o X]MRr2ݺue뉍KfBVݎlfHHf'Ȇ 9Y eرD .YlpЫg7:tbvznJ) `;XktAx<ޜ2zÆhfΝ(=}wH̙"V5 ܵ;ȰfW8$3;ЉFQ = 2 qp;"ǎ2nMTVVj*TgΜG3x 2q:=zm۶A"J~Ԝ֭lϞ=`hGbIs'N ut޽{ٳ{7UUjy IH}n.w$,4αsVjk9mڭE'رc' PGM"u:^\ӅACW4Q_yZAeAj1轀ىI :YFn*Wi/Kk˖l{_>7[7^j*KRTUUŒ%Kx9y${aĉW\߯YZZ:uVǿKo6w̜9WgRYYI}}=mڴ!//)*\۱c.T֮]wWx"v̟?iӦѫW_-_s/\P@o̙3][~y^]ɳo>>q_Yz5wyu֫R9t'Odx뭷ʺsiѢE;k.]mO,UfDnc*+;˼s9iΜ9ÁpSNlpy>?%Nl\ڷ/o ZT/<,.zv:Fޚ5~x\.';v`ǎۗ 7 NM7~:@ X2!{@cV*(?[UPPwc2էq;ib SDA@' H>k.>Y$$$п__N8{WΑÇ8rF4RSپcoE~|N=^݃mYYuI)lذ#t:5z4sY[vѤ/gΜlٲG3GFsv; @ ._hvvƏA>5\ۃ-y0 Z49m_Q#~<P?2 ͂j}Ett4~Z}@zz:.vwܡ p-@hhful6x .\7ߌ,˔sF#Zd2QXXHpp0񻮮@ Fmm-.8L&6l 99\())g***jHDEE 4P4'OQy8qJ fm񄅅! u ֬Y̙3l:u Ir^eQ?TC Ma)ipRSݜ;wJ}}=?[΍#Fr(EܮȲ̑#9[VpH6|8Q1֪ʎ `4q=$z%3LD}kb**IH+ܴ9ݸ=2CROnӤ])7G6!&--VK5NR^"_UUŊ+8~8uuuddd`<Fp8X|9|#<󩩩fqEz)xgٳ'ol2rssscǎpB[e26oެ% 3f k֬aܹ[;wb{Gy~Q__Ouu5 ,\P裏j@ 3tR$I… `}YdY&,,2ڶmˬY8p1:rBBBƬc2fN>Mxx8!!!2m4 Єgf͢yѣGgӦMcΝ<X,JJJx?&$={˗kW^pI>F^h4RZZСC;ٵk7ntn&V+ӦMcС$%%Β%KHHH@E?5jc˖-TVV2`ءCgȐ!ٳ;wbi۶-FwaٷoGaѢEoWZfڴi<Y?8z=߰Z|W`0sΞ=lfܸqlٲn6mDQQdٲeTWWƄ ncҤIPSSëJHHUUU<$x'h/^s> Y9wtn NѣGyx0 8qSN'0bRRROl6nv?$IZ{rEFȑ#0a'OFe>V^%gի#F`׮]ZrRRR6l,^QFNJ+4P6~xZjK/ģ>`ԩ|GϓO>Ɋ+(..d21zh-o|@}}==믿ٺu+F"x衇طo˗/ܹs /GuVmg޼y_PZZdbܸqrA4Kv3gjʘ1cشi[ln>}}Ygoذa|8G{eԨQTWWkM}}=yyy۷M.]9r$}V"''JA=ǎcŊ8\.˖-n233CNLJy'+;Nj>bk=?0;*oVDxD4VI'Cp~hB ;lHBrWV5}z6{3x؍} » 5ڥOӯi1?jl UDDFy+44kD:Ah;DS@ VID=Xt)Giӆo,/qwٹb2p{<$$&xn<"LA!M4$`6p:ţ0iXBŷYf+~ ͆"aZa,vz~{:u,I>Duu5qqtA3!50JV ݉$cjU?Q&6cUshw)yF3&U ,I +6 kIZVC1c xEn:fϞMpp0c=FDD> 555:udy R\\ӧO'==JnV܀0aVI&1k,-[<@RRoCK111<8qwy9si&8!C ς6mvN̘1cǎa4#&&;3ZywˠA0k,~Ν;[o% o8>}:iiiTUUq뭷cR]]Mtt4׿3ffrJKnn./^;ƍCVX̙3YhSN%99",YO?hdԨQK/c4PhVN'gϦ[eƌqyL]w?ƎKPPK,aZ{Ann.s!<,m۶d21{l|Abccٿ?_}=6;wf޽x<ϧ޽{Ҿ}{^jjj7oCOv.9^@Yׯ_(Z;wOb6?5^v;Ǐ';;~ǏȈ#޽; b…tޝ}R\\̳>˘1c믿viJJJ(..'Dx z @۶my=z4:tw<ddd0o<&NHZZ|o`00rHٵkb0o<.^T|bcc9sf~`Ĉ7G"yyymۖ3fh1 4L4N:uVV^̈́ Xz5f"88?}ʩS>/Xsq1ù9x k׮0uT;vI3yYVRUUc5RH|&@pp٭[#KJÆʕ8ߏNo@'ʲ_od-ǀzF 6${S9Aɍre%*I2Y+`0бsw('&i@oRYOo +($XJHOL 4qsee8Q]ƍ4pzl9ۍn6`C>IH!a%$?Ϲe\P铅~ZaBIJNK.l߾C8jA8Xkg#U*&ÎnO!t.DA޻i7lʲ jn ˅NGo2+%^H"hy<>Li($^Ɨ+. ~yϟ?(6QQQTWWkԩ6 :DFF(r 7O?!"w}6l@~hݺf$ˊ=yE?Oٺu+ƍkWTSY,nFv1b~*v|BqqիFѨd!" oX%..HBCCx,**֭[Gxx8Gᡇ򫫸.]hftMw-EQw~拠&q|O>D:uu֤l2HHH --{yf塚( ͫkƻᆱL!C`0PV٨k׮DGG#gϞv̎;Xx1??rJM㝟mm߾}o<<裀WݥnL̈́\T0 !bk ӟ]wѣؘ ߿?:T:v$ŋY`| k׮Zp@о}X~vNhK.$%% v:t7?裏ywyѤ-&]"aaa/^̾}SOQSSÛoի>vmbX0Ljc㏳uV&Nx5R}n w Ad673%Î$禫?穪dZq8HЬIi}'Eϳr:`2{$Iv. Nh67"65HZw\* NH<:Q)ZF ۛPPMc)XӲN`^YCZU]UE `-lۺ Tr;[1XHA0l߶>G.A6r {ɑG8r0ZH9 (bXp-r:$YvDNESh ͘j|hAp=NQaTƹ4DA([ /EkF^=v-*CcCzJE2LJx^Yv-<fee%L0 &-K9fڵzZoC ᦛn"--f n7:N{aTgll,/"ǏgҩS'-[ؽ{7&M"((|Sxx8Nܢҽ\)A_~̚5õ?(sbhv9j.hΝ裏~'OkGRRСC̟?޽{kSKdXxGy9p 2 ht5+jՊ~3|lgaȑWUnKc|}j"`DGGAƌË/Hmm-]ti";жm[&L`Μ93iTRۑf㧟~InɄhD$v;vC8q"&ӧ#"'N_k׮fOK ՞ g3(4IjF#=3f>g(;oUwگ$@Lb bL v= vbC!d> !lƄFY U a ,n瞥{&]I>\]UO=Uݧ~l[rA===\}#3g3=ù{dƌ o7 kx+ |[qFA200xfƍu23F]w֭;vb+իW׵ڍUi< /38okK/6jA8`!}}}uZ4 !( DQZVk8Ν??qF.r;Aa[[F~5R /a{OO̞={XY80005/Y~<3P(mjo#|\{nsg>o9;K-⪫g/WUN"i,eWRJm4PQD{W2( EIJ;sFL~W(Vw7J4r57 h (-8IE!}K%t:GSJhx fΜ/<.> /m\xkoԕ7s{#i9?pz>-Y˗aٰaGqĈutI<#,]nN8}_v-;wc\. |Θ13fpgsw/}+Vn:;8s 0 A^z%;0'l6˳>˫?+07tab 5kOe6ns=ǥ^:F VZk|#<yq,]SN9NX/+$0?8bu,Y g}6{/ʘ9ǩ41-Z 6K/aixz뭼Z 5/׿Oϳ3<{5k8?2X׳yfPŋկ~ŦMؾ};?Ɏ;d2H)~6c:::xXx1~:wk֬f|tu`XYrEN8{9zzzxx׸⋁$իcL6l஻8QE&a׮]6R7촚W\q .dڴi.)ºu駟f۶m|+_sݫz/>O,?x{1,XjʺuRr뭷:?ً)ϳ|rz)y78SlXӟ7xN:j:*=餓xggqe$5kmmm j5n/x}XdӦMtttpgO<~(F99묳d2x7W_ә;w'p?qΝ>1̙CWWׯ'"~qEqGC+__0ntRf3u`\KD3V TE~*ZMj,E| _{y`P%oJ)vMł!ljhL;հxcpkJRɉDh͆"КP )<@8d" ( ON@$M+je3CJ m@F-\n&mb6t `aG_ղp]hhh Vj?8 #1&~&gGIBaΟca+/⑇UjH)੧ٍ'%NSlk$Al#Sb'f|ʕC*łlA˧@ aMmD ;36Eu5@[DHe1dty#!~spp5\g=2Biwٺu+_7o;wdɒ%Gdҥ<g?cdYN>d"7tr)\v2 ˗/L@5kP.7os?8?{/O䦛nbÆ {,X?w}GάrժU,\G<|w`p cŊ,X 6p7s}nQ5w\^}Unv.]ʂ 8ؾ};Gy [?!z*ͫՏooQGL+W: p'rJ:YJ<===vm\r%(ϧ+^.bMƜ9s<.N:$ r1ǰtR.֮]1iF촽foo3Np;TBhq$vϟO\fL>w}yj*7f'|2w\```N-[֭['>ٳٸq#~;c…vi;c=C9r??;For7r8I'pfv/}o4Jyd7L>" /_~kRE]D.}f̘G6ʕ+yǸ馛f|'*Mp IDAT`ƌp |SBu]GTbʕr),[_|Ǭ\s}mرO}S;̙3=9ruZYfqg?YbӧO'6m(O=wuk֬aٜz̝;B˃>-œ9s8*q;=wz(z(o6oݻH[[۶mcѢE|ߧ/| @Yyyݺulذ~,\ ~zNĂ [x袋X|ۘk,]G}nO>sY8_ݻ⋝ ڵk݆駟&Bի6m\s [l3`…,[7xk|l6ˊ+[nGOZ6}_͟?~[oN;SN9n v͂ 5k/W_k\.s%^7Ngg'7x#O?4g}6˖-sc@?k,nVJ\pO>$?RqLev;x… 룧lj7o? Ybň(M6+ Qj*/^w|oOl;0P(xb>яý]w|_iV}̚5"QZQS{6f"0eJd+ J;`yyǿ};\g>g֔W)EbJlݺ}Z3m?ǎ|2ap`G~ٟ$j7|Gy`ٲ,]A3fͦBPI0$2~fwk'=::x7yy&^}e_~W^~m~Mgw7y6nLtϳ:n;K9s8묳D޸aywX|9g2OH*o?O9c3g駟fÆ NV4ua/7mOi_x'{m'pʣ2QC|1* Nk?m4/#!p&Z0zA@XQ C!DTk(x;BZkݼȀI 3fEKlO]i'aw3g@?;@#I_L`qkNze2vZioomΝ˕W^ɡʹ{ZI~{J44S0 Z/H)O<:K_rx3.K__[Ϛ=W`!O>6ߣ(bw ںj=߳DmLY? a&mJі2{Z { UXq2K#GY /M @{aR՘6C,0fΙC8^?p/Zy駟>}@?|];|;MoS46˿ [n~i/&o K>5_ M}4QY:탉>ϏZ޲EK-n yA6k6U' I2LX)Y. 2 uGs7g`hpZrٟ`ochhˋpѡtwOcI:];4#'=O8_>ކ֚r aɐ254Rj+! *7Cdsui/IYf\ I'J;dlh)mUqjͱß_z)W_}5o6}}}<ԺYk0R rh/(V˓ϓ|7Z||Nf͚MIU3@6W̳ϩŶ6N?lϤMq]x~ `?j 2I !\Z1P*q4j|U  R(Y N$<72 j uwlNid85UOs9u&'i{⫱|$ٌ7miOkoo xFv#.ɕWIq>hr(_ךO36Yxkey+]{Ǻd}{u2x ff+LλlriAj/\2|!BJ2|q۾l.+VCj gy&]<̳E[{ZO/l޼_xZB>'}*C%:;;8M&e LdSN=+W>JZA)EGG…xWصs7(~(ed'ĴkU_Q :;92qĊd੧25mhmTQF<>YuQVhQJ۠=64'M>#VJ^,iEAkyO_o驛+RJN>_>/+vAV}ژ3#V`ѡ)V DQd|7gVtϘ͟\zu'ڸ ̚=O GalL]!A`@Y8|E}̆…4 W{m'2AU3ORϺRJ ,}e B~\k5B|cJ q|nFs-͚ BԙN&]ӔOS))EMMFÈ:G&0yvf|6::ؽk'J۴[3g"Dݵw~Ar<,%-{wQ@)E_?2fLgHJ1Kuhh*P@\@]TS+!iӴ EJwY?bHަ\Z~.OZ<8@FheJ񂀶N[D kh? A1aE mj8%E- JԬVMHLPZkfMfnEDQ4<>Kݽ4 ֎$jD56L֤9 |<)LϡjJ5VkN@A67c'f1ɓ < ӜEFn6[SO6ړH1JOqt*e챐F;oxB TҊ@͛ Df7AP4"e?tO AD\/z `.5qLRؼVUPim08-Z|B}^*ʸ֚g}'xbANccVYYNw+W+4ES4E Z:;-A =l4G) BEҀdI{uk ( ) $)<"@BikDr<7BiLY/<ڻҳzR L>mT+0BcK4C}@i2V)[sDM&'!dsYje%V5f Ii(&VqAmCr=]ƘM riV3}ad`}>F?dsuUmMP!Q a@sHjAz*#)_eMYz/8X#pF>B ӾgrUJ*t* )+dنױ|G;)˺5*W=P"U|s6tH`@tHtP!ܲe ttttuua=XQ,Yv-7|3"ڵۿ[`B/^{o-[&_ٶm7|3R_N;??EJڵkXb7o7WU:::&$f㽥ɬo_/H+vMV][QVI{&d5~jeY _`Bz118{y>A&C\AUL& K]]ZkJ}_OutPV(J 4RL!/tFX _ W0-jٽk3O&b]zBh1FKIJ1T+¤ XR}WtaRZ۠)|r٦Mb \#(:]Z\BtiF ;RoR&Bh}sZ:aSC> aj(+COTUICACdj!TlP*W (lHg5j4lghwI Ǽ:|7)Chgj)MT9Qoo/wR/rss%yfW+HElݺo߾3gyf6n??f`r|X'席ӧs/~ 6or 7;`"&grNxoUtxٞ2M9nqw3uoiJV֕HRDZ٠.\dsyCwZsljG\_lj6`o(}P,+dԪFæbߘz>1Ki@a T*ejjIb󉀨VZ <ϔG17"?C Ԫf#mr{k*}߂+:W溆E6Pkƚ6ǔRo0#iqێlMQs9 n +|J)-huUu:RH|-3,|3sDj0~wJO܄$cy1%^T5ٌ 0ňLP_d8^e}Wfl"`\tyߥSz~7~:&cmok֬a͚5AٳZm6rӦM㳟,3;xǬYhoogժUîЇ>ҥK]t_|+V9 HZq\Z'h]Zd5EStQ 49=(~/2<7fv.O&k#?,A^)B"=AFk֌hg7R@!B @5CCC[ĄmES½Z. [*EL?PJ!QTJ)y)6:_,)`gL )54ܓƔؽ:> ,L(vi0cG0Z$Lt2x)JN0Z;m&(XSi 8hi@J㻨)|̽'m@$xO Vnc( Y'nj3˔Uc82x42u`\TB؅'mC˲@Oh-aqP!d2T5n)V;A@îrH)UU׷8OloiO|;A):ijiJVVO=7"5<A(T/R|<%@u*  |A{gIi:7Z)W*dYM; &Ѥ )]DA&KH6sFZ~+sȴu‚ xHAT"6EzŶ"Z[ȧ xJ|Hk+WakD I!< kUg4R(%tQ :!Gvhݿ4@I i # 3RQ M3 O7 a6U}4BZқJ">!A7h+m3cxBXZk")UCj TXd%.S*7i`|pװZEuR8#[F|7۬ީZx.QG?>9,Y%\ fⓟ$-r[x17o_*繨Yc?A:.zzzpOʤijO&=csw_3C+J<{[V/o%^Zx/HA.aA`{j9E>Bnxh '6qfhV N<u5oﮰpfO?]U^yg_@g>_T (d⪟B[wNZD>_TczWyjA'm96p`YN;r6'7x|RzgARNkL!Epy=ge)MB<0%KOy# lLhz_W,F&4Z(cj9DJ)#P|If1 *nX~DmӚvӘÀZ;ѓ0RDBZmRBj 2ZXm4H`0m;xxQ˾oRԨMNv@+o`[RXho&7ÿq}{!C86&_MYbJ-dFd Kd$kU-Rͭ/^e9dFS'%>~|)F#=Oy5r22onҞ6V/w9N^> [{'y9x5>?΢Eh֮!٣{O_Y9ίw#fy^\v|_bX>h1:.p@B#xkҁ { lEcW+eJ dR 42i Us*Eɀb.c}M]CAH IDATdsy2ƀ;: iHY)MZ3-Jd|;)-o-R *ɪ9-)D@H18\g.B![K{[ݝ&eT5 f6HZR5DiI*e^)~VWmcb,Z<޳ 2l^9y'J?6m[nq;osyn"߭&_M\M+cѱF&UCXy(6#l~k>7/X<d(pZ02|eHiwfFG+/gOoU~Oŗ>WA%_ޢg-iN_ !3\t~E1߻ v T&"as۸裋ugZC*& CVNy3 hVD]4vl @+7 G$Zy R0}.ĤI`.nY o_6l&vEBĦFS_J@9~mHTF"ڮE^c621|MR ڣ_>OFDr& |Xŀf,0L Q:Z:)6tH׍DU‰7i'|2K.b'\G{KY߾^8Vފ`vy~hVd޿ɤ)YZYVZaMΉs^ AklX퐔g⬙`yG)FN):2ԫ2H?Z;;[a}\Tsw~2a[|飋}O?/LϡjU{J%B*L(2`<ϤА> A)sgԢ4ғx$s"BT5,g4hh0jEaIko&U BhѸaW pSaDǴ ZiP%<8j*"f=֫F/"#3OV#Ll!ٌOiRK9ib3tPtTTɪɺGhkZKs{qV3uk>Ty'DlEѽeo'VS_íwMi<9D?S_d5~jUYi@n%qVZ"LVCz>b;QBQEo`AFRrX6؇9x[>K!֮2} -Ո&8jA;O߹rTe*ax~Bfts(G.˧s9Y\yR] xtN;' /\@R(*%TC@U:P*2G&%U*4הKDa?Q܍dx. BWڌ `6(LHXP kTJd}L& }?IƵHH0yἲE&>26H`Q "6dZ:6Lb#Ĵ41et|) Ƨ.63[tMh!V A@8X6tRv٨=II'ko:_8@ I~M볟UVQSj]ԊҊ: j~_E-pyshFeznhEJ(|(em~&qJ];AHL+sΪv zuvIjša!} 0^4NJe f+u(d=( <Ϸ>.|[_8MR'?I.ָzAKOlE&0w{>r3n}|lE3X4P5bYsϋ8}<䠽e=`? h/d*f9lnv{*:`z{ dîj|ysx½@)89:Qϓttv\ZLP$ T3RA&gK3Mb#YPGyZYׯV!ju X jMAOY3 ϳfج56ERT&vAhb h>l- ƁB3M&B(#ł.iL{>QlB#Ff$_l#d1MDQ#ZbV(oFci .ʨ:::;h׎Ufx"N=xKc6'ux0Ёh%FVk[V/o%^Zxy}â*UыYiSf?4B$ ӑ0O/HlP43;dVqpm0kvT+ҖpJ̥zLzm钁N:˳JyVdCkZ-EVNRXlR )]09vI<"Q1ΟY< vx1#%dN(TkԢb>g"Fa+2Y6XL e |Kanf"LUEKBBR2RQ<ڀ1B֧LI ZLuy4ЇЊg8ݵbwmZ~JE6we?" b["T_|= ـ;n2H.|(ije`o±7mK[/8{oe7޹/ڙVᡕh%=PK/^JIk" j}Ҳl&θŽ-xSd>*U7iJc6b}ӚphmXZS=OMMMM6 K>( t!5:g||X"XPz }. hu$]L#m1l`<))?5!v]`Z>=Z |YJNjgc$ݵ2|gB|vm,WZ׉,.S+ R`2Qim1.i3N I1:=ja=i`Εv"H@6hOz>[:(Lj7!J1saZs>NyfzR: TyOӸۈhl,OnTacFɰƋؤʛ"q_cbW['R_i"}`D?mNxIͤO/<<ۛr%A+dQCyZV\'cAGfM]#Yni[5nmNV\ck(aF8CcSK & )">W7ҳY $PZ*崘ƤR oD6hLE /:̓yx|3RixL|T\PiHY&ΨI7aGu#RcSԸv21S"'Vk<42m5D]"`ZRK?21IMvsHnR#]>u#A0~_843 ƄN #Gfy>w?N8ϫh]d2W"C6(yo%֓Yj4OweE =dچE|Z%WPW+vA+zq^{mzmͻ.^ `8wƫ$O4 @ovNz;J|y!<E;^ .Bi :IiAV⣗t fHIY|,Rs!Uw,"'oE5FpdbmUh(B{6ߡM{t u=K7ЗurElR2OyZb[(p kn1d1VyxʅYa87ޖOt&}lXfɞ7F˨}}u˅{ᆺ&I,g|w@JV1Uݵ{;!m"׀}u'Rݼ׵[j`޾w@#A~튺:͉f{\IN7sͤ:i`~o $+T$uBVD'2$Ҍ}bIzGa c6zPcon t}MϝgF5Ba:Y+e$iM)jaDX59kLx|ElLO;m`܅ HuscϚ:MҨHh;IZd67|'!! Sѽc>_rǝw6]x' G;Wƾs"xe/9Z[ KCY0.7;ϯ_eԂǭK/~t1g i †&/YĊ,0 EY|N+1ᬰΟ0F'zjai@s:=sD$p$sEm_:iTJX=%dl>* #_zEal`g3m4m&BDF"IU}J_J9͐DB(BaDa[ $S|j҃&OQ܆69/rL@%Z^>R Lf"k{@!S*W  dL6v'ᭉnZGpԙ1*KtLY'&D^x׹ΟN^cHD>w]y7T.܏g ddR ɪ>~;Өz@P j?;w=~$9*X$:^.%DX3FdLBn1"13:׌4VS_kt8CܗFrk@]Ԝv2ñҌ;񍳁HjJR% kxRfjo#_'_׍ P) DIǒHMl/d6VZg}~l:2IOMQKf49he|UK6q7! 6t, b*~ -1_[QCKw/mt¶ߙ(2$>[!"NMJl]z! Ȣ"ja6:CH)h/h 9hI3ލQ̜IU2ڷ$H$N HMw1Z?<& "=όA RZMo)5К H4H=Gl~D̀ysL&`RRQaD6W;a>la6FVVH!Kuh6Cmc\<4lXMɥ zMFXHkӪVfc>2K/T()j-5%\2ZE'9ׄaL׌'R)h&_J jZ8hRL$>@#6IKtGZMnN$RItZi")ve h'MDI "ch OZ]mtwtIcDZO_3J5TT Y <5π?/8IbF^~],>ΨKmJ4NȌd/5H%ҀIcPyc)m02FEݙtZ"mD =?tjA_R*!O܇D!e&b3YMuaQt5uZHoa}4SwTs\t}a}4ZJ×R]{ ΚuowAQCNՕI4=w&Jr2VqΓΣ=-o12wAlhZOkn 2MQh@z3#~o7Q8\[b6vBm8>jF{ ]ێ;ms̳ Pt.9绰~ #Ɖ> y(sy ! <IZHR~,$jVϗKPc7ɳF&bRlBD ֔t*/b Gꓽ !MChr4^ <E(׌i6[Z~`ya\d%~:86tH׍Dm„-bœy*4ÞHg34Osu'`djw@ -ޤ74)* ذmײEem(H;D0=@Hi3<̓Am {݈$+HrtHDF jpZ)8 f]C IDAT3DD1Erêqm+^@ %I1ȱ%կIR$QD-nh5y8F8n!v[Nc;pM.yqwIehl]%46Kb0͖͘Z册캠eN`ьaNfq{˺ʱ]Ejˈ$kLևhvY:f)g8gQѡ3vZ^Uu?Qb6V鎧T BeU5n=nC*Bh۾mh Vljo߅⏞#xiJ.Pqƚ ЅUU\jnK(EBPޥkQॢ@4KҶh"8t h%%x{{^h$;;j WP( 6wRdsΌ(:$:fwtD0S'3aۗ}O;G H25EˊV[ ZGUTG 5QƉ.Pbo/僇(W;_/Z2t] 'U,JmwPs*X%](ofěA{$gO?3jp] (2os.kog1xef_`Ͼ *>~m{DJlu o.~B?L<0څ_PHLl޲?__Vb`^^Glpm (-5`/RE6kIxH]ITPDis %G$"-bD$;+G#8 -I J06nk6m΂]6iln\VV3yچBJ9j!);jy\ `0ۢ^2WQfd6a6:H" ;\ (ΙST[OkAcjKc?G%+(&N| u.Zm4QQU'z5VUquE4AU]Zu.u<-JuBܘxӍݷ}?t[%88sYu'[mO>U:Ә#݃Ço~Ç_~rJ* __BxX(ϜF'SRE.:QYY Qݫ'k^KS*,*o˾2_?Sg|ŗ|ɧDoG`@SI8}ӧҩӧNog] =>ᔢ ;.~XmBC nz0debVW=Kom Ly '*t#'/o//͞Eee%,W?9t(vo^<+96jbF8#۽ܼZmۙ:i"cGߦӦr=w.<Ν#WXs%>eڔt;wacÏ,喩6A̾V>+9BL׮̘6g[٢-ϴʅY@}݉d<-Fu$gK6TG}${I5CX%r8'4h5ۛ=&iashErhU;I.e^} IeJNpv%G2_Ahf2h-͑kȩYNݍ &3: rwdPYYArJ %a}8f냁QK]ٹ]ZVڅh"77ol159z R'oooFw@.۸azd|BeK8uJv{@h!5-Æra""s"lCOhjw~<=9|]:w"5-!"y5DQǟc Xjaabo<=c6$Yg?o^`y񰧌yզͥI%;:Yj~ڸClbnjV FfGںHH͑0[, пImj*bwcU)hdu g331Lv![W8AxbccȹLhm*++@J~=w?&8 V9޾]; mBlΜ;~R^z,Y*7h~9h2lvH9mvӢ̧C uiΠ sAFQ9*ki?sy;>[N@@+ș1JCzB)`pjEﬢUYvA-i!/%Ɋ*b"YyM 7/ZC_ɵXš?^WpECkXuvfĖ)..UF9|(Fl8ڶ-=bs3ص{o(v2:>Ĥd, S'O"5= G$Ͼ^%%'{fˑؾs kg\|les9ωg~ٶdbٌue\u%8[yȞÿ^]Ě?r̛3ߓ8z%%!^t: A/(ZؘÈ;yr**5b손GtㅅEQWX,L8_'Nbp3p0f(NcZ))-E%|}}).)! rL$0;vfxHƟ:+cבp4F֮CG0s-̸e /s趷r!O2A) -${u6GAPLJL:lQ>[Ly= 8Av'zP|" 3v[͑\EN"i*vVV`3pUNRz;䶩 G#oCezA[8Vъ$JHUU%Vj[B8x`rj?ABɂ<(cVhA͚ΌD#=KRguҨJehU}pg'TD >^:顼j#>xےɸn#;FQBd'^^MoN~Q%(`P,5׮k9\AIlu-&cc),*b݆ Yy#ѻW/xp @aQ!W cy׫cW6Ѳ9voG$?@~~%%ΊRU"a } Mf***i7\V}&<2'1\RC؋יUr򴽩ۖ+FCثG,m9xXU+jcnFϛ6ӿ__ؼeko֥3)i 'W @aA!fB$F{ u6ErJ ^o=~Q#_P'0܈konƌ۹f:|BBgZӜ8y_( qs8x0k 7z:>t)t_oE+.5TA&ݍuG[fsꯧaQ:M}e'K򰯎"yغc'>'ٳn9Ѳ=Liig,uBjjSůbh^ M8t{!hzI}/ϗ}_ ח.{wbӕaC:{pKgGuނѐN.ZFHkӉ9&XG>BEe%9ﺓW^|OOO~X獄r߽w##ظy3ii0V]DzA$F*Gg?3[p)jbR;v1caʹyOlӉX,s8> FC~A$> 􋯘2i"B~Al+fb`~N_~ʬ[g2x@~ Ç)))ǧQVrC{:uV>hHl"Iz?z^t::уYcևK1P(*,rsr _?Or&FC[WXHDYQ1GW^gywwwlƎ'nd^'ZJEEfU6AATTTPUÙ7%|!www<==(+-Ϗ2&xzzP_<_K`@ee+FG%d9}qssj#nx{9?JDAa!^^ǐ6*`V>ł<.SRZd sXUU(;&ifGNꗵ ]9ZC`Slg x鑐ZyxxU>ʬDLG _'~:V~^9 J,5MOOO(,*wxLii) -Py z|}}-()-l(ux F**+$ A/EEEXE!eeh4~~MF*+pwwFlP^^dkE+Z };DQ1QlE+Z*9$ lȖ-INܲʶiUJ79--i.Ӯum6?.8p#ةS' ["QV.Vgb^6tV3DEY+ZъKF!X[r[,-ޒdi,-ޒdm1sJ;1in4B5KWŅk%(MoN.~rpJ-˛?ZghE+ZݝUeY$!ZF )+ʡGEf>&wlr"b;mt/=rDsWh],{[,^MCa1'!l;T1Z9mfB . v:k@mvuhQ/$ ߈tL0倵'^$ \H0m)> :~9/,vX~[,j4UZp1ts\WJRlťESٟƠ% {bʔIXկ_}͉V]5HW~~;> 惏VMIv @АWvÇSGМ?z~ӟޒdi,-ޒdm1seU}+oבƢ93{rаPB >IrAIR\sc/S\2^PKF{AwEmlJg%Ek^ \7k(]IHtڵN}ڵ ]5'e魺jjZp,=؀do/fϖ}z%;Ls{zu8q=z\" ԩ GK.TUUѶm:effHQQ Vgٴ4"""6QUUEddS p:4bccEDD`4p*L6mZjII t]tܹNzCu5^]]MBBAAAt$'OIhhhG$3gСCu$ITUU@YYDDDh..Z I~yZm$ũPB. v>}{MVrk_$?MWTHXS7FUԺP zi,V@e,4')mm"!5T[&KI!kCmىlۯ_W:Ϥ y^5+&z#z9dP~ lO۾鼢"xᇉ{ΩLFFVκ7'OdŊţ.ڵk-g͛ٺuke-[FYY֬YT&55y,[3W+O?eTTT(ʾ:1'NtR2333)odǀW6 ܲlNm4]гgOz/O|޽{6mf˖-`m`K.Z*>:hxٲe W_}5˗/'?? ͛Gzz:?gٳ#"K.W^$%%Ѯ];l²eٳ'{%>>kcǎӡCx z@JJ /"%%%?9s}v ,ॗ^"&&sѵkW=ʓO>INN˗/G ŋ9r$,Z0i$&OիY~=~~~aԩSԩO?4'N>#((g}+VqFFܹsoi߾= ,gϞo>n&&M)))aѢE@m'Dߏ??8(g}ڵk2dw}7=}#))+W"?0 <Ν;+K/1dee1k,bbbU>|8cƌAS]]J?3]]h0]P#QLxUr\/v{%@1ʹmRF m\:iꗗ.$IHuRtW•ƌNcҥC֭[Yd } h"ڷo^g…;Iٿ?gfٲet֍#FУGyt:UUUlܸ3fNBBiiik?~6m;0n8/Y`dԩq;v ʢExW߈n'==1ck9j;o>RSSYv-AAA̛7{ш(s=ܹUV(M6裏RYYɦMh׮:z￟{k2l0ȑ#CxQ233yHHH 33oEm6 Ҙ5kgΜۛpv5k۷/&W_}?I?~<ƍcɒ%lذxƌ_M=;`߾}t:ϫwF# G˩8v Fb׮]İzjƎ9|0zB$n݊hd,^ /3W8w?5v_nBIq'hʾT @@)y.$8z]6fEUR lНRCg5Y57tLZ͹aMץ~梻ԕ@[/!-uehFz ]z.hKCWKoIt:{\jzKҕC{wB|B0McǠ׆ȀH"Jnڵ+(C=D\\th|AN8Arr\:^Oqq1%%%fl`0Ю];*@EFFܹs޽;AAAѷo_DQ$++~ѹsgfby{{ϒ޽{޽r9r$ƍsALxx8$h8<(*NMӇmȓGll,ڵStEdd$]tQkGnn.{fРA#FLP_|26l4sL>QF$:t@`h4:wLǎE֭vvD`` f"$$DvZEVN`` eee 4iݻw矧u9,..K.DUUKBee% τ ۷/DGGhjdeeaZ1LۗtvE>}ۛŋ|rf̘MPlua߾}p xxx(;r5ON9H7f+m9Nڿ MHRbrQ;/'99P$HIKduԔJKKI[C IH H6H@ sgB$IJN&%% V(ϗ`Jbe욑u/Z擜ʙ3TUVHnAZ !IETVT(ש]$JKJ(++wDyY9JVV6Id(u#Hr],v]8[,VrrsA|Gqn׵.[,VrszuYu )"-#ܜmq7糲8{,fYU>]5JWJRb"%%.r&#lf$\f+%6vY%Q"1)b$ARhjl&)1:tQZ=s6դ,rsrA\2U TVTX.IPZRByYYtU^V6uqq)IIVy{Ar)).i6y7p:WWUHuUE?o^_[ ILJFc1Ze?3gko}ԩ׫JItI6u׮]h4oߞ '**l<<<8umڴa|W=zTi I{ '55|V\I~ rSšCؼy3cuĉ8qZeڴiCXXᤧ+j4$IСCDFF}o߾9rݻ+())a۷ t$Iq,X@TTX,>ZvzM.v>S/VTT !掏S% 19UuФ 5YJ$Ps*Ǐ̙32m&%22AyR^{mjXufnE>|x()-%88*^vn:^Cε/vf!9r{ɓ)硇EӷOo]4f|mSyI"ǎ>$9%YY 0djُTUV /ItTG$՛. $/`1[QSN/"m[Bxxs)+/g̘1 Oꐹ)ZҼBGW^e7j8UUmyWzݛ|C+۹5%ݾݾ}dR&N]5e[[VȊLHpp- )9pzgعknu-QQg-˔ln 4-={ ?#gYz6eeg1y4z%:*QyGIv5;wfٲtMR>KfL-B=z;5׌MP':sϽl&&&/~} Yӻ ?9tjѝej5Mf:FE}]/7$|}mر%Ng;=jmCB.ys9'Odm^a@^4wjwiڢ>>޽@n6t:&%%޽{ݻ bĉ!I^^^qUWBXXfH"##9rW]u < 0"##1̈́ɓ)..&;;5\CPPo߾0~x9Bpp0999L8(1уBѣwFE^ !77G2n8FAسg;vdԩX,lsxxxرcٿ?yxzzS;uDuu5`ܸqN5j׏DOαcߟK.] ͍={Я_?JٵkfOiSxx8f#Gp7һwoDQ$00+NfJJ ǎ#..jbbb޽;:pt邷7vG5 €>W^SZZʤIdLyf|}}:u*owwwHJJ"..#F(}̮˺|?#=77ZS6U5DQDE<<K:w̠٣DoO\~~w![g~O?o՗_bÆ/odfO"Jk׮#<XwL6v2vhΜ=K6AdfWӡC$_}l=6^~e! LQ>SR d}:|Q#GrAfΜNTǎa ˛o!G,~dHLLbٿpV^Μlbɛ-MI7$==o{16o_6m&S4w̞l4Hqq?`4fڷk+ _#""NQLv ?-[9:s}kλĉ7s`ǎq##3g%fJf~;7baf* JD|W<'0b&OFhh[uJ`` 3gL'/?>EvٷW_sqΝ;Y̿n.V{ѵk7J;~;yͻC;`Xٱc'۶o'{wv#?̓܏AY3{1ݺ_!x̻n:ugd֭+?3gЫW/{:EO?7! l!ڞ 'VΝ߽ā/()-eTUV1t`RRRYwtc2UTWW3udzɃ?))-`S^^7+W¿.!<,/Z=g[h777N+^rJ*/>oOcҋ/P\RʕbXez}$2 IDATsϽ@=(*.&'7ؘO%09tؑ|oUW]߯&5-ٳooVQZVʴSӧ7>(oaÇɧcl֬]V%ߟ'19߬S'ӧwox ޠYIHH0K|oooZ5EEL4Aɧp' 0kƏwkѣL:???^c "#ڵ+f<[#" [oA]k=x%9o.l3fc O?߉ѝnOO>K~;9c_~ ПG2zh;떭{"c;Trk\6+dzO|ho~ƌÁ>m*gf:9ח6mpc[Nҿ^LΝx;~|^۝M6sױw~np#}a?')),]qGTݠpK:޽{y)((`РA!˯,;g2L:H'^"JGvu- ] $H#!{x3IHw={-{y1TTT-3C~'̞5 |\HЀ+ p->T3,}OlGp$FǐO۷s.r9rmm[\\df7rA>`5̝3\N:MjZMMM5N xMa֬>ɦ[عk|ٳ1PعWdggjq1%(jjjں.aZ~ cHHLW_}jGRQ]U=dp!{u v϶H25Qs-ʕPRR ywRƱ棏IKK''>.x@<ߢ;Rw1c##G8tY3HpO?+O@sK3ܷ^`s9š.E2``_,]FvvQ'&W_o"88c##:OOLLM`׮_&== *]L(멪&22b}w)4hK.@sM9+99s[/q7( ZZ[.JJٳo,Y A%ETdEEtKܹ󴷷CHe666xRJXdYDFF@@?_}LLL:%/^JKs Gbe$''ZZsaq&&&rI7r紩dp>6OڏcTؾ^UsK3'N֖xSS&ǡG˓x/9{6+yyDFF?ӧO?]ddf%ee[~$&$qf223ƚR֭_qqql32)\BNn.QgbRR]]ڏSTTDtt >4?v1b^LL$FGG_ЧDAAQQ 5ź"..1WYc?aaalܸ_7| s=//}ϭlW_}%גv=C͸\fq|}}8v8[ȑ#prr"6.N>CUUy=>32 '/jNظ8 7p@  /;wq0?DFEq!)v"܌'OQQQΘ1c!>! pptr"'.6N/<|2..dggԒ :;h-i?/OO.$%* f:::$HK b IIddd`\iiirpA-pQ12EsQ=Jkk+ cǍDبWihhjFM`@er>6,PrpusCRi(uL Ɔ/55˴iwDE!陙TUWst$eeBnn;~ICCܼ\*8}:®HiD__g28} +|9EEE2~xfɧ899K;J*bcpttȈk1c$74PRRB@@#F襟t'`gg'+V.g&]RRꊉ(WK K DtL TT3|ذ/?hmیbrGtѣFaggKSSt(_Νщ#(--#?xmlb bJ gF7~FԒIuu؎ʉZW_’Z[[io_[HJHZz&q Ry //OZ[[immQGcƌȘ}XYZq߂g}Ɔ|;74ks )HVv6NYXXR026f`_ɥ!$$$2h K kia^[4+ظxre7/o/ڤ3DZ~%:EŔ`ee%6SSSBQ* Oд3SF CRameȑ#Q+hhuaa a(hۯU*ƍTׁJ"? d䒗Lj#MhJJP0qxͤobuM cnjR˫ttp[ ݺ}]WweWB'DgO؄PRTDF밲BRĴiS?n,..R=K(zO"<| =999EifJJKy9vFFFx{{ͷQQYI``fo?@~ajjҥ#yoE/} yxL{[;*$===<)))vqXOOfZZ[022B.kҗ~T2Rɐ! f`ooBdUv([ cǏg&Z3OIJJ,,pI~ zձ()hllE4jz( #)$', 466ގ e =JfhmmC-%(JT*Of 9s&[6?P_pI&iG '1%|zzzR300‚w-?mGGLLLPT2}4&N+6oFr9J%3q vd%Z 3uJ8'M;;[yMǞgϞ}4K4EDӗr94UȈvhjj&,4'N0c #GiˮڵkG& jk̜&GsK3&&&.k'4m465Ihoo>ɸqc9z(yyy89;KsILJ%*O&|DiCF>x.__oyYc0xƎexxZ.] F@t5Əettv2\RRRM!%/C.NMmTGhHťG{e(65BQ(x{y!|v899ahhHk[+MM T룧Уʪ.)k~.ׇ1{ <==ŴqWTxz3}Lc$G=^oW}m=mmmsvuˡoqݻ)s=VƱõ8::0l0@OڊJuZX3Z d2 d9(m鉗?ֆP3@lSõu+=|6ָ^j ==:;;ϘqWJ J^(446j.[}}//zQfo[Vused՝+-C}Cnƿzcj_f_U3SYY[=g''6m¡GpvvΎ1oj8v8~VTKi]]=wG{wQQg{048`<_|'$LΖ:D쥡d2F l&M(R<ؗӞ"hkk#xh~ ѣظq }ô;JrämyoakkCI+gD2oLMMzWNZz:&LsIN{z79465u\ON`` FFJuoemMkkK^_~r!q7-|˅ Ir ȸqc:w}/;wTkD $^ȑ#b TVTrꂃ(_ewijl/fÆOSSSGBeNݻwK+AAIIɌ3?uPkihcY=٨hf}J٬K PŽ瞹ddd_1fh7wJ[Fx(ܹ6d89:ahhŋ)P<3cH ii9rL &;'A5kYY쎈`MmBjuNKk+m}t/=?-Α#GHԧ9w}i+[|CCCC>z_{{{Ν;СCcΜYDǰL277Wp!#GWeg$++=|qsx{QP({STT/꒿vCdT$wL*#I'lVVp4w}V#;˯9u .88cfj&4g,jk]w݉`jj闶uA >>s.L~aooG 4P$ɓ'''tT*CRWWG|B"S'WR^QAll,akk/ٙwy;B/`ccСAtv9|;c##HMMCTOkkΟ} ?hmm%==ٙҲ2.&_D__?q F \.'2,B)**А֒|v< ̔'N2r*++))-amYs33 B\\0'ΟER܌9F2iDΝ?Oss3'H[:;;155e \HJRTTDJJ*2HFү?>6449}}$++R_ G %%\x %>qaBnn*uM[[11#Gܣ rDb&N@FFUUU899r1% Brr2xzz$$ovBCC9uȯf^MY hkkc`_"''wA$^H"00ԔT.jQ&xx쬑7dڵ]u^)))x*wC]ez zzdt3 ۡ*-+-"ɸ{ z{vyU^M5557=kǍ߈eomG222100`ܹ\s^U|GAV4_td ޖNJx~淬^.pʺִZ|}K4G}52t}u@#,\u ܾѝ_I6WG3*Vfro'Znw@ pUcO/o'ZnB]mFsLnޠ IDAT/ZfE.U«-h*ڟz n_i/\^it\Yx6d"ӝƾ[L]WFWHY]ۍōRe%~]6nvvNLꎗ-:2;ro'Z7-;vVkw鮖|P7\;C Z\KߒL7СUФ.W7_ݸ$˕y յjyE[*?t7?nvvN/zŸp @wL?N*5]uvJ$Gu I(ȸG(CIw5"zպW2eFӥ2eu-vvߌA' }[ʪx}K!}DR@Wt҇7Wjj5jT6M /U477KꩮCk457]}oZ]]-!|m]-AUUu^kjkD} f*+P jTwBeU5YYB/?<&oe%Bގx;r+֍hV^׿j3ׯHSdO#Ǐ֭%ck?W|oM67Ž,=jMVder8 }Aq'aai`_ԩӬ`UU Rnټk쎈)Z> v(//jGl30Zf Og2xwxyyackøq?o3{e}5LG8̿g%%=Jq ISS#F}9|Vlmm%76*Weujd7X_dr)X'GOLn2e%K4\nL{^FuʨDOOKݸjx!bML1ƮכoJ+\iqdl̾={aieESS˖-'../[[[rl4cƌ&(H|s׭gQcii=QQQ 6 @oIА!7ԣ/>˯{{rrs6liiijf S^QfI&T Ⱦ}133Vs|,Ç'&.$1h ƹs[f͜Iaa?l݊3uuu `\#Ga(**FOO=SXT?l++Kjk눈 -= ϟ yyly?- 2jsqA=k&f'p`y41ZM8>LkK AAA;% RXcC#?mNA~FBV'P3ax.$%1|pzz__bqq:1b8^aÆͰC !--{Ѐ'M >3>Gc\2bb1`xA-FĞ( 9 G''Ξ=ˉ'cǍea] ? f}u447!?Oܖˍ z9돂&I.M}ux}e]z7| ^&NJD: =ɓe.ΞC據ɓ;=$-RVVJy,YG'`bb{˖STTLD+*SO>#!>Jdd܌Ç՗_w}س}0nܮŊ+ի?x뮻g<.&_WhN>XΎ7771|$cǎceeIxdcOIIIe޽7pl4o_~TWt V֭(/d͚F2䃕7ߦ___OOT*յIF2)7W_[WC-k>Tпpw'8(M7q6,Y19}))NX&NO~ydgg믻~Q"^y_DD嫯7Byy9۷,fdd6mfٲ4d l9^^_*011a˖o(uRIi)QQ7;[[V^eP(#1ڼΜ$ k?F8p $'p9Νc߾r)>K}سg/;vBZz:$?gg@\#*2$&^`wp!1/OכP8|0ǎh/`򕘛coo}OA~! L4ccc-[NNv{8} F*#֮]GNv.&,,_֬8*o֭':&cO(1n@BV\BA{{;V!;+ŋ2Pȶm?RRR y3e/ҼVuu5_}}=YYYOtD;YMԖ+6233bSZZzު-WNt׽㛛IOO'//˦/++jQ ~S[Vd|wʫۤ=ϫ\KHoK-{[3v9)%))sagg˙Hh151`ƌAv)?cL{{;&Nщ :՝]h  88;f4 N=vjjk ̙HDFHuUT62b)/3g022 Sq[)Y>Wg=.7kJ[ѣhhhҀS??eeP^^ΝwNKjkkIHH$(89fb2LMM: …$ʱѣ:y3L! :q?q|uNxXex:| W&HD؄u!CRNRIMI`@xzz`nn#{ϧ JKHIM%2*oo/tĘŭ_|L2EŅ_DА!?p%GZzyL:''G;N~AYYYy[Ae`ٻw `kkaCa6 })EKJJA/D2ڊDΟֆHҰ%66c*xcl,3g"9yAAC2755!|DL)++#+;f̸ww-@EE9SR>r8ܿ[vAQaմBhX99TWWx"F_oMJyy9tAV] TTVRYYXZZ[`]1s ,--4}K',}SLo l޼3gyfxꩧ$f_⯷^-gMkY^{5.>#22wyq__u}{uw{ɓ'ɹlKxM(~StV\yݲ#㻃-!2;J{]/g\^)A[ *wm i$_La޼ƒ-m 5#@C u/rQ%KT vWr9 @OO 6oLzz ܃)%' fe2\y{("q՝jԂBO‹iPΝ8z3{M70:@FL&C.q{r9FD'GOO믿ߟ}&V II:).UӋ /lo2^{N_In&nҬ pqzdg+Wĉ\UUGɁSʊ{RZZ3wCPP {eÆO/^DRc.T#h\&C.KBCC|%wj1v8i2ffx{eO%;;/ɼys9EQQ166rdZ \C2L3#wMcǨ"L.\Y2;՝=1oKgGVVVڵ3p'uῳZ\.\̞3[SS-5DF\znN5NV,_Ơn<ϕxif+%fŊxzzb b<QWW?O~N:%W;v|_~r^y ꫯw^}VX~ˇ~Ⱥu먯筷b:td^|E}Yy^}Uv5k$Z7o̶m۸eO<sa۶m70o<֯_OCCլX *O?q9@4hSRRذaӦMW^j|I~aٿ?˖-cR>gϞW^!33PSSSO=Ŋ+HII믿;^?㥗^WVV3fGQ[[K||<<< 444 pajkkYd ӧOgTWWKuvZ )--e…RSSoZ&..~:;NΜd_4i"N^!cc nnQUUa!|VA<ۥ[]E :kw=-Ct+=oYi&gA%2*ȨnFɥf͠tV^CnnH22C## 8q `4`1A2dmQgYrmhѿM7cdd!#G@``}9,Xp/ >nW_x, ###JK8~8A[("Ç c!̙3hko'--U?$''7WW(+/əF\]닅qtrd1 ~Nmm-"/mxyyOQRR¾zzJ*|;ݻ#XRyv!MMRPPȩ{ vE 061#ttM63h |}zP fXZZvX[[3qrrs9z8[Ҳ2VlBBB077F~_}x}!Fhii!<<,]UVall̄ x뭷xUUU|嗼꫌5'NHs(,,gyF>c|g2uTVXAQQDpp0-$..~۷B\\< fffl۶%Kp}1|pٽ{7̞=[vY;wG!99Y2T[3_|Ey'c;vSN1{lCll%Kx衇pww۷ٳټy3MMMK.4{9j5=WHH3gΔp񴴴pBVZEZZ<ŪU_ZMxx8x{|IdÆ l۶ɓ'׿իWsIXz5 lZZZ4iO>$/hͥB#F`֬Y_"JJJg̙~zf͚cر,]TJǥ6;CHHNNN,^ 6)SXlIIIٳ~sss$ݝ;vp)rssٵk{\.7ZBzj&OL[[~)ϟۛ+VbŊ.}OOO{~mLpp0oOBRKULtޮC4Yh!NNN5~~~ҋttvĢE A&cQ!MVb^΀E GâE qvv ĄESO=;ҊC=O創5..ߟj\ߟ!|,rÇT*Y9ll137gѢall?G\l<Ebbj[MY L>M2|~̰F"<dd:~Z񽹗koӥUOZƒhT$zzz,z9駟777iEwҿyE L]}=ߟ^zܜ\,,-puuWW $QPTdgg>P'g'^~%dY`dd /23  i⍱z䩧ܹ}-#( IDATE򱰴` Mg񚃭-?IMIO<VVV2l0<>,\]]QTL:Ut (Ee6 bj_!w+]:п>0~&!kn4iĽ7 chpCEZѿ_zACЃc@7@|{|dKO 8p=%{k8~7WWu'+{{.466E `mm9% K f͚ 0#GԄìp N8p肷bwp`}|}|ESbs"ntt1Ү~Z+r7\6L;ӎh]uvv&M>u gĈ1Bj5)))̜9{//jn)x{yݕ/M>>A]‚aCui}BBKx` ]ʷg9_w&Oԅ`Jb֬hǔju $%'Ⱦ}P;yGԻS xVPT*'@`@ ]wuuAόJ2q/_GG@7gã*2d;7 ;.U@?quU>*ڱ4QAJAzB^3s334@V9ϓyy9s~~g,|YuR[HE n w}'wrrBN8իWQThZCVVi6Euu5 |gkJ իWK8woHL4mW_}EQQٺu@bb" &j*Fi:SktYSԼgϞL6]vлwoK^Jee%={-[8z\tGyGT*9pIIIU09帻K>COpp0W^ӧk.رǏ#zƎ+[߾}=z۱c ,^'N ))={0eʔ&zbFɓ'QT8psr)f͚͛ٺu+#,,P:DϞ=>G :tӧg6n܈B 77Wr-]wL>}0` |hhh`ܸq|dggs f͚ŪU`Μ9V͍T~;ѣ$G J}2f^*ڱ4{YߟX<==ILLl^6x|x0iC(-m щfaӴ} `1y1Y&8`͓(r4bKuGpi?ώeu1YVg[REVI emZ:jHf͞1ch4DV7˄fΘά3i6&SFᡇ$""ꥣtw뙽[/,ƥ@!D ***Xd رc,X@A ___֯_\.g >>zgϞzٳ۷ٳgӣGAO>TWW₷7ׯ_gĉw}ۗrv͘1c {e֭8;;SPP m5j\̙Å IHH`̘1T*ͨQ\.\HUU6m">>#FHѶ}k.i*C=DNN7of׏j&//3n8"##:u*| 111ӯ_?BCC)))t&4qF ƙ3gx '==D F\\Gf۶mQZZs=ɓ'9p4R lܸQYhѬ_777yfƍREEEm:ʧ߃9~8YYY 8#Gjh4<#$$$yfxh4xyylEEE 0Mە=ZLK~_Ç;ִd@Ւw}ǀ1ccӦM_s)I&qqqyf7oΒMၷS>}ӧ <ի7nD.\HYYYhhh@ѯ_?jjjprrb޼y|7裏qu %bA^>us']=TE*kG FzCc1v(4EQViK>ԖuA2,n(}K66lc;EMdqi[ ?T6JhݿY|GҷlneZ&Xmb"ΠhoZӏxNmo;-p)ݙu J>Tjv/2uTFlM6k.jkk4iK,:X`Vͭ<˗/')) N}'Mͼ`mG7?w|w+ɤT* )t:< `0 ov6֕ꈉ(TKJzw܁;Piv,:G5w܁; ZHuu5*J9ˑY be(++MZVVF}}= i] lj$ .4qu!4 ?R&Ҽi !H{QQyK-mh>e_)r6 ͛s[K϶uG15oMvtlJ֛Uc;=phWxj}#uѣXVyؽ-9ڋMgG)l|ߋۭWNtw|w;;ڟ4LH}UVlw5UR^WWW/[oN\y:RuUkH=b+Fw{UԶ GOx%cϱ`6#.Lod!ut'^gxjH`:ƶTX} 6kQ|t~v'5nZΠ i=܁[mqp_|e\zɝN3[ 5˷:]: j{ʑպ.Ѯk7YJ6j[ڥi1}ll^ılچ6vۏw${ǯ%|#ΪM9\'%\;޵4u@O /:5^N:Fuɲjw;."(t^]?u|7vzنֲܶ(|TTT筷ꄖǹs~_7WZZÙ:u*f⥗^"??Mm7/"o&/Rr_߷9߰=Y\yΟ?߮9{,6lpؾa_QQ0d矧Y|kɓ9s&gΜ`0ToVm7ie~ilH VbBsVWo6]Z(H]$Zyŷۑ=J|{h´hg9D{IGvz-wdtbft:ՕXDQ㸸гgOVZ33gTUUѻwoB|nj?X݋``̘1]1#;;ۼcF&LիW ϏǏLRRNNN;BNc߾}h4t:GFEO}}=#G,*++ٰa>>>ꫤ7Æ ###< ]R^u\Bzz:C ΐ;s 2yd^ӧaĈJ|}} ɓj8@mm-,X*1b䐟+׮]cذaHff& 03g0~x***زe 1!!!s%&>>, bȐ!8pWWW_N޽O #JJgL<09ƍĉOHHgΜʕ+ 2dcƌi m۽Ư{!6k ;ZGGwXw%_[9]DeiG׭֕@%܁#R5HMMe͚5Ʋyf??AUUgΜر{w>g}V:6lҥKT*_o___;HKKzcĉg1w\-[c=&_WW_|IgBRR}aL>]*5kĉ;C=DCC><<!$$DjkNvIll,׮]Fx &MDii)}={cDzo>~7Q*駟&,,~CReɒ%Ȟ={8~8aaa,YDҳlK|}}INNFE ֭[Y|9鉻;˖-e˖!8z(|,Z^xI&V5p3k͛Xi2K:bS@s,-hS>Rք΁nUN[O!Bκށ; XN/m;j;tKIIO><(L8p&MDNNq رc dˬZ Ax衇$000gggRRR8w .>`z}Q djXxx8Ϸ9G,^K.~zyquuO> 4{|_~r⚕yרbʕܸqQFqQΜ93<#كO&!!ӧsiYj y wތ7Ç˗9sf`ҤI->;,ZRҥKQ̜9"""HNNСC̟?Q/DXnܸɓ9qf"!!'Ng}Fhh(:0j5K.ɉ3g2c bbb$l߾O>gggjkk)))ʕ+;wCb O?A'33 Ϯ]$9b;L&cҥdddn:'!!OOO͛B`Ĉdee1w\),}ڵk$&&rA/^LDDӬY`%(|ѣGimI~ xGZz O֮}9ښϖ<-dVMFJ-rvC[{de],kYj=i !aw܁Ԗ>}(hȐ"_GfQ^^Nzz: rxJKKYb!EN;׮] -''') QwwwA ;; i-RDTEEEdff%E2]jZ-9:tN><@yyy#nSPUU%M3f۶mI+999\|td2Gfٲe598w׿5 ?2233IKKk|~(=} PYYIII ﷡BÇ),,{%++ 0M,**b޽RZ- \\.g„ իhZ֮]lGN...ܸq///Μ9C^^,Z˗/&gh4?T*YYY(`r`-ZDJJ)JO_WRSS x1b+V੧w… F\\\x';w.?#Z#G'|"=W^/E[v1jΧ_C{X32j6_#h)okkke[sբh}h /Tm|ke;WR]]gܕ9?][?u#hd)**ouuu̙3y'\.'44W^y֭[Q(C`իWtR{9?sJZZӟ?ϒ%K7nV_ _g֭,\G}W_}w̝;צ/˗/GRXXȿ/l  )~Nŋݻ7!!!$j7qDRRRXr%r777 ҥKym"qqq,[}a4=z49_)++򅅅/sIhXhW\q(N>ʕ+)++CR¢E v"""x饗"##Yd ~a"7|S-Y{_WTVVѣGy@PYp!ӟxꩧ̝;z IDATw_J&ѻwo-Zɓ'Sj*6oތ}ټysZ322Xb/^q좣ٳg}6efݺu6?NEEw}7K,aڴi= &xbmۆ(̜9CpYΝ;oMaa!^^^oOXC[|e˵X,0us' 0stv!He]@7=(Fhs Add2=_ܻWn]2L.CR"(Рa0olPVF#z 2 \B.GT"x@Ow3Fz݉B@RI{U TVk(++ŋqA~_ү_?y; 'OO?eʕ7]Gee%<_}U'R:\vzz[?~K(Yq|w+ɤT*A)z޼'! |7-4]'~]-`CCEb0aWۯ= PquZnUpp+pG[)EYa~ױ;yv˨ deڎ+-)'C4J. A&3?1D;6yG@uu5iiiTVV`bcc[.E~Cu899uiӦxmNXOy˪|́-s0кl7 1-嫬C4)&qPE z=eWWRZRJiiWIyy%nnh- l_Rd4]ۘDZcKEd  f/k)oÇճ ,d7//\]F3 b&Q[[L&GuE&Ț7ٙJZM`脴^h4RZͯӛ+&WTTub[[558k]R[[\.GTv,-iSP%Yy 77r]oEVED(9ՔRSU7( iѲBo0 zz=uu3jYIڅϹ355IiOOSqoz'UEg-A[WԠV;F[_W_ &^0}кj*j/L4-DWaUkl'ߝh*|sBkp!C(:EښjkjP;i@Z^GƠ7Pk>Ĵ5'Sj}ߑz yQܱ,(MFxii6l^ϼys80ޡ<"X/|w,^|/r\1mErGfMh++"omMXk׮K[?pqvi6g+˗IMܻeM=8tC q3ܹVˤ_evXW4|.m- ;i[ږ?euqfݳ9tZRR.2asaҨ',s}̙3˗/s 6rsr9rfϚ޽{j?~G}p>xCL7߿۾NXhn%'N$?/g"l3g7|ɌQx83f?y$ lڴaÆ /1i$t:")N'ͱ-))Ajʨ@T@qq1Bwį#4W^Z\5G}[("2\\]kBIi)%%vlF(ͣ^OQaVjVkjkkݻZRSSCPPZW-^^\LHv ""Q*6gBݛ(^ͤ`oH/**&, `͍FBIpp555 777sN WWWbbΦgggF 駟CNNF 1h *9M+ww7quqay\p=z0y$I),,"992(@d2mUoMi; 83D˭Z RYu) 7בG6DWGȢBr RQd1e24Zv,//6;mYƶZxGד ddd4հ9(R^^(*y[A\8I'qvgϞ?VDr;Vʉ'/T*+DEEՙ_0sLvmzf^\]]pOW`/~:gLgٳ'NCTT$ZMFU+W27STT(”)SMx{{#i$Mw&=k/۷? ر;}Lzʨ#%+Zы6-7ZF:jtO?GaŊ̟?fݻQٻw/?U_KرgggƏb 444i&~{9Qݻ۷/AV;vV|~~>k׮%00sJUw'Znm|U3ϼX]] ڴsb;`Yc2v FmmHɐD__A*,YS Q%zsK9΍n箉SIˣ-희o?"a-1u ҔZ=K˟\.CRታi$˳y5A2L'3 >#˙?JJ#O"9ٻcɕ+W8tz"gOA ;񊉍5E"#(/+CV3j(6oނ'E%y׏"֯@ OP ֯@]]= yšrRy5AqAG@?k׭GߠcɦH!ٹk#F g7E?__L 8u4)S OrfL7zb8GTT/^ȑ#dS]]͠AX~.`?^e2drvItT4h < Ɍ)))&))!CӳgO|}}}>ǎe):>]֭ķWEA~rQWl(c4 QYQNYȮ5)φW@S,LFiiC-!!=u~7[{-ҕѣ8(R|nno`玝ܱqcϵk׸( "&%1o\ DPdff1ct<< c!N BPP 6l ++C\>by'SL&++o~GAL8 7n6l)yW<==!&&,/$9vh00b֭[ZՕ^b  <~… 8~G"88q|8IIIhZT*/^ŋrZ'qjjjΦ4ٳr5Ã?NNN˖-h42h d{Zx.++ӓ:N<&''|._`cZZ\v$))Ir8O>M]]7n %%|}}7 22RI]]4a.DKW (..F.L`]ֲDn*+؉|pxD{Dt0(4'")򗓝MFEjk+TVTVx-Vi38aVA4h1#Ȫ }[9 V$nZ0-Z4a~8-ѨQՄGor:Be,@  A0iܷw/iTVTϔm;3mɓ G Zzu/]JcwyKpoR `6ϸpYvr9j3Ap4ҢLEx3 TVTs:QHSEh0yrjkȫS=!/o\Σѣ̤0%")--%F6ᑑmփ[~!!aBjdeeQUUaQLw^Ik֬h4ӿ/_N`` ^^^? B:>9s/LBB7fMX?{eСTTTw}W: '&&cǎ#ݻwJ… dڵ@MM s7oF.a^}UA 115kyG/~SO=Ŵi/t1~xN >ǏGsaȚGy+WƥK2e k֬aʔ)1yd\]]Q* ,Y‡~L&OOO֮]o(=zB\.wߕyqJ2׿7oJg}kƚ5kxxDV3{loߎJB?SZZ䔽 2k,/__|aZ[[˂ X|9 8u#GW_z A`ܹdddh$>>b ˋbbbbʕ+DGG… Y|4`ڴi$$$pe"##INN'uuu<ܹsy7yfʔ)Mk;ic',Y烦N t ^ IDAT"<;wCovqiMe Zͱg~>S ) $"AMSFhĚ= litCJΖy'?pVeci6-<,Z4.Zx̚ m)$]xB$DKXx ] pҍutDQ ..RicSϞ=ypss)^0K͕ǏOgTV])WJŎęy~r0ki}`_c crd;;2:j@.`R2-o v.drCIiR!lY4bos=ٳg}Se5pPrssymt ++իWDyy9񄇇0`r He W^B+xw1 eȑ 6 ZMrr2O<4uΜ9|W\|١N>ロz*w^` Mxx8qqqM갞5&8F|wVnuU9q1 ˦7ݖ֑_ Msէw hl,+5%h^fܘZ4~dpZ;l\2Arf'tkJNb(2[Fh.gEKk-ώ=Wd_w&Tƻyl|3NZS#Q:/<{o??mcKsSZyGxmg+\]pvvnoݴnɪDY!=0d%RYQh4RW[`@TJ(2Nj 2 W'qZ`iDkhh@PLoEڊZ~pZ˚  *d2WN(V2ܻ'`U^&BHd DUk,5f麚@ZSQ*ۗd uuuH$NJ&h2Ba1R#xyyI8\NYYW\t4LƜ9sxz*ӿv7$~, 444PXXYEi}Aڈ \\\hLI7|}}QTKm̴ʺu={#,`*|K?uuwsJjjjQ**kk?NVwRшɉz5""Ra`Л[h@0b0Z6-i\ 1h9F̟1Q4RhXEעTlJLMh7 ;7;coIӵ zfOgg-/Hc>rʞ[蓒f%r-<8ETш@~@VUSh7!d鱮˞0P5oj9 XJK> L@ V()fFIƙ6q[5XO.1כxfڶLlnj5tEr[Cg?1!66oϓ S|2h4^""W*_YYɎ;(((@PH~~>ӦM //7RRRBXXzb<æ@y&t“O>oLJpyE׮]O?%;;ۦFàAӟZfҥpBV\'|B~Xd +vu3fwa˖-_|I/Vb;β:r8{BD!'$!$P[H+#Kޟw:G߮?l:d[-$bI 009uӽ~BC8s߽nUSuΩSu7 .2>OO''$|;;;׿w__\y-2sss<쳋tmChDC1 Uz]KaS] I*"zD"߿n68B!Ll6K p$I ^ @UUK.#z d2o뮻Cu7l6Kcc#BAUU^/iL&P-~\.G:v-atIGr^L&ZL5'4NO"vyO8~B@CC$1??O4%JQ(5698#/}TU0 \|RD"@Acc:Z,,,eP( op\.WWD"Gu2iT K>wر]v!d2n=S}B^7z aC*O !ږh+M_}Ҟd/U %=,a9ٷo;wt?X,Ƨ?iX,qQ;ryc/CLNM<<|ޞnZشq`˦Mx4뮹P(\"6mX]εW_Bb/auk!`~!˯IBvTb[JcQ޳3z R_]XZ,-D5G,pXoU^U/kg9)Lc1BM}@PvbYKA,( Sd@cS[H$B8&J12:F6:f5(%Vҙ_X][LA#pP&э,)>U, 4buRUy: %L)ְi"8XcJ}>[Xid$ EOg)+oh'dr@JMzVF]oD"}UU]P(T:8KB E֮][ekѧBI\ܺ(rUʯs>ρ@ PJ> tV$I455!I[ভ,)K$^|E;ƪU4m'Z^/.[Ym[\]*XYokkjwLLLV]IUPJ\Z+,PxmpaWlBXwލiW^!Hf]EJ$8{nl.E[6s1~}?S33lڰ QA6oH8p֋yϭfC  o@.8@(1С#WذhIlw>Jf2(,= S^kk;逨Kʟ MSn o8ʇ$jk+^kמ:_Tٲ$$ {1t{Yrj8@<uNyrSՅQmc)Z4!~8>:BC(uܕofiQB#AF@Uţ2ʹ^WF"\y6H308*[tEG%P ]S$hC5e j8׃.]G!,74$i lVh^d?1D7[xb6l`ݺ(ȫqb 8[Yf).ziwev)ޝ^7s_Iuꖓםl/:Qۂ[]%slM4-ƍbΝr9ZZZeѓw 'NrW011IgG Bdsyy 2|gwQ,.V$SS4Dp|`l.GS`Y\,0B z:'Fhkebr S؞QmK$KuCIXね̿j*u3KqMOIi]HRͽa J&J%">w=4 xhkkGcۥpfrbh`>|~R EQ{<(l>FGFQTKCCcǎQ*BUTd֬YC:ĉ4McppO|pΌa&Ǐlڼ AWW'---b xb"0M$ӴBёзnp1 C,$fǵwE-m_+zyRXiLOM 0, Y!/`mzQYLICF% Z(Eׯg``Qڻ\GȲ룥'8t0-%L2  T>/FR`JHBO)! MZiE8VX<]HY'lҰA{4.<cCOߺ{`bdv/ЙGczjne||Ɔzz{8z(@###|^hd2xŰc:P(244a]q:;;ܪ L0/UJnay i y^ 0z6oH&E4fggimmꫯ y Ӥ$7p|!.lѣ>r`oǿ= mhlh@&.يa756eb>R)g|֖f)֭olx ld2H4s؀cR0X]sq[ث KX]vet(c+֪_Ǵ'wzz{s O=4_~~{44K/#K?drDa^~y7av6Ƌv:6U PId( -8z6NW XeWVtAcsh^ª, W[LB.ezb֭]zB077G*$I[2oYtںO˿$xF#H +jv7?380'~i9B(ihhoͷ$6czj\>Ǔ;bzzÇ73~` /SӖFY62;}~^{5rϺkyG]/133C<O01>-Κ+~ʒ%[.0;8ǹͫNZ60=%KC_¥'U*uyflXi)M={(JO0:6N6C/kKE$ 0:>W79ɟ bh4B"477J,jM>BPuTUT*q1TU%F|^J##ds9zYłm $$U`.J`C<[sZ$Zbʠ *E;zq=lܸX,-ttvra֯_O~81|Um};ccD"?|7ȱc(v=s 7x[.xkի{+{֋/fmsχy91>:ʃ>R= Ͽ{_o0 v~uvv077K__}}.~dd'|sl-oa '0 >~[ZhkoGeFƛnIrir5Z S[Ӻoucֻ[:s3\icX(%4)`Ȟx|Ȫ$+I||QFvժ؝ooZ^`8jŴ@V~?Ӥ8>|t:M_hWs"щ,oDzi IY j;@QTL$:;(jk!di&FGغy: d6c";g-e|>O2tEFV~=/ q.z\r _טuU+L^7r3@ټy3}}O<#H{vMIofM G`x4 }Le$CU<x^>P4d*Eba0]ذq==ˌOLz$I +2z 'uU+/>ѻz5O=|#fq~婧ey^Gmlal,ZRb*\GtH(++l(}7̪hAFZrw!rvJ& ǰJv,x#LrvTg<}nyKK S3OMU94p&4 lٵ V+&CF Ir4x<>LG2Ȫ ]X{kzf):X,F&AddY檫?~?233C0'r7\z%lٲw>\,jd2Y d-9߮%?6n^u$e<>(>^|E$$nfO #IvL&$P/FLLL3V~Em ~'\!5rCt4PdΖ_a!Pik,+Ipd$\Ysl\A&ik Dށ)TEf[;a?|.^{jvU8Œ8|#sl '3l^zlŹd9_N6rR׷iS5lpz;ǣQ来A*䕟l]{+ʶ-n=tuԛ*@b)v KVgcK+V@Et5<BKM̒u>Q^7C>O'No>y֭CTza~ګ9mmm^N}u]gcll|<.vڵ<" !q4x-[n߹htd=¯Y/_M |cG}6?s=G2zv{Q'$K%+zFuU</~fU@:"(J9[F]͔"/(Ybaq$Y4J8\:U-O1VC󞪪m/uV1J;((*$InT*E,'H-м^Z ۋ6K6kcS~קiXa~>$YUeϮ]N473C6!~`˻&/{ r1b[/ox;dӦx<;x\׹NX#}d*Oy##$ QZW&w??"2]wcu>ʪӘ8#;^玫i)GYpuyz/{Z) yUQ8>ECiv;vQ(Jdܴ'xc`yj/wX l81svW>{+f? JiVğRs[Z#-zB4nm z([\TҶhZN0_/BATʚOmSh [٫տj8)%WPݩlOIf28'` k%]u~?l6k ǵd2\gI.#( |>|>|g Ig2~۩HTt  dIX*dT 4o^\>rx^7㥤KELd!ccc7E o(B:AP(G؜ry <^׊d݁@L:nX1~?<|K,ӨsytX2$M]]]8[2W_<#lzpWJ=ː3@ D) ӹr4EahxYzV8P%8#WT"1=>͛\4b9&&x<~4alL6K*B4>(D4LYM{[[]EP(ecb6" x鈖Xxd2466ڪMy146Nux:v+4MP(4@ @:!bk/d Go +[߽|rx< @S5TMES5Wȍf&lj>ѱQgk׮[n&JW[rߞp*3陀~7>1LS!IJ><WmΫ Gxc`hnz%C'fKx5ϾrW}&Bs8UxKt96/ݶ?s d%[n;=C5>0Ś(?~8nX2{?-G_ESA/^o?:߸>w_yymh_u+o/k6s K>=z߸_Е|ak/[p.2ZY+pFr/W!wTt6 >z*K4xST=rJ{u>ZRPuR4zVooI'UBB6T<5DV%%ٚq8F8WQ*MT w5[yz4e4hpc:t@'> kCX2M .M >?~MWE> WoTx_C,j s/D~E`&aTsC:{9pZ0DF]2S/L #1N-ׁR [c۲J# 41rjziA Ad ~6щiU0rٛhEѵCa%8^@e&:Z7pE[y-+-J>'_(X: UU-P?E<]=]qfZh{>^{`}t(k~jr7[ㆍSTH$g3Y^֭߳[u],+D#oS1%rje? G+He|齬h`}Wr6^:8F._B$r=;ߣrWwaxj?Zxţx4~sILSlmq-eM!}nr^Q{U %OPy 6uɗ{T|J2[P?rv`:f*fl61 ߫umi=@Sϯܹ{5<7\'瑟&_If .l]G96^dVOĹ^V5iBIgt6A⸏ru!˪WdZ9|T Ak%,7Uaš[` M! ö#*ޮ-wՙ:Y -!IT[%\'Jy4V [uTfY:[Rf&I\酺@ xŻn|W+8rgttcq@(H*T*Y>/^hMJHMtk|\c0Kyi4`슢TgHXNt]g||ɹM-BA* ,O:TSY]A]>gNPUPHIcC!djB?^ ~^N^N{HdVioҟ{޽l@0hJ?Oy9KY}d%zVE1 ?xhǶv~{ziR"@ֆӂD^>n0sWخ䵡ǟT_=2%@7LQ)i)zsiC~zWE$86-BC34E|^dIbv4U)iiASrx**<* E{F2SdCw3su4.g ^80o::%Ֆm2_IuYT AknY6Ch=Z}EXL}tUWzms$Sy_ZZ#:v^b"M-)uj86De2wUЀzTT5p)򔠼mUx-q)NvvIK969vԇ ǃ*PT|G$W;ǃRHH~7Lf+3i|)j:"FS+ XnkA\lH5xˎY9 v'FyPƣp饗9zd?w|}rȲewu֛~w6+.+|B, M-|d%Ck4ӯ 22`ϱ)~#3*U"I⑝l};:g_tTTuh}#d %6n%d7|4Ȓ Fx]x) .]Y6(2WoEǮ#[(Gv GDم,L}W0>%)@< ^CkC~b\M!R{iKǹlCs, ;0im|+CXEǯ\(i]H:)KRRtVkDYU`Ȋy*u)B ?TpBQ*x| ERrIB&Q0M})iE\voE$˒+Y2^Q!I1*JUw{dch|_,8oE^u]ilnaxt2R;&:gR BE/QM 4!k;r(^ ;uMM)z-2x>y(eο:Jt 4JlٺlڟwPuU "Dsf<*A$,>}fs_W@]V|2eb)B Gv:LT<ͽLo{[e*l%bM{Hz.Z>`VnKd7Id |}ik 2O/,dXoCcx5\4uҹ"f=="\ólZuXŃch[ef>C4u[S~KXEUd>-zt ܰMQX\BQ]jks`xmw;;p;^ΫYC7ld [ִmkX@,e]G#mMm8R閂5{۩ D:9[p)N5rR<>T, ٜkTba>Nc(DowI8qoT(dQ`sȚk#Ț b hr3#(ÿ||{m#;3'E AQ̃7D)(fix%YRa4 $YAVT0Q=~rS3;7F46V]/199I$|2ah[!- kEAxXn~*yV^mQn3Y,TyvKfޯk%Jy|bI_AP`zrpޞn J(Jw/ͣy !{C((r IrIm=kK 2pbn"~ٚ~ S`J+Z'^ *I% pіeP(d2wX󍰭}T5XVMtid2Z]PQF&M-VM=H{q8ʼ|xo?z'sL$µԪ>gr_TVVu*ϫNe.qm[:j%UB dO>A)C dOK;u$ߠSJ/ ^.fg uCR^r# ;b& p$E%CtV&w=KZO>>OG)@m5!y U=V#m?N \ o BbMP$(R?-]($wˊY6 0(yh.,` OCXmc`0M)0LiZ¶#9mb)W`-f> n;6-]kaEޡtgO@B:OG{/.vg*;9tv#rUl=I+6"&P!/!idG DK?P߲ Dx^LLeyxj?SGueݳgK@M#059I|; uJ*r9FFF0 6E!'N`aazzzpZr8@K[kݹ윏sR7L3#N&]6u$;V7{wث܅v/VgJCu_9)o4!?7wNrI_ @Ѽx"Mn S0J$YOh `$hA 5x-Y D FX(^?6aw H z>K)iU%H2pٙa$IԋxCG&T o #Bϧ5u፶`7t%ˁam<5 ",Z_U7qAV uzeTM!Y$;LN.OUg'*Rʍ.ਘg12UblYmմr{JJ&ԡX$bX"hbfr֭YM$T*; z{{D"GS<^4ڮHRyLB`{J9zNk[;HgY*]S%\㗸w;y<B Pq5W KL}jj\.>Mn(-Hvv!T xd&k^Y*o Թ7~0t\{/ofdg1iPd-א"7;MWpll 㣘(L PL%t9A!1_S;!;= ¤MnAːe\`Z9|MmxvJZ봵*C')ռZVʊ(R:C)|6$E6&*Tn럺mWӾr%>ݳ׫OE7yqxQ,jLE3iNԫޘ!ߊ(xdEQhP(N yR C74K1_$p0 ȲLTBQ:ڙ]Hh;.KS//ɲu4lfb"˳vjL,YN`(X*Ns|pShhn!yKULCaE@u]Vv.IDzփ$˖?k_糨^whXM\@PHĐO(ZfʔP̝zD Q%TO@S@$ ɢUUقuw^ټuR % Y~/|m>QvKtjORH&ZJ!_dp`nB)֬Y]}]7ŧ3<+@bqzzO4ѣlܸaI|-NHE˼?=5CSsX GCCh9OxCX.  K(l+(~caZgJ"n() >a۪\,4]v/'^@Q k)b(,V=Y&.cQrI$;x;j8[ZFFFvQ۶oCSsʫޏ3n7̓?88Dww7ߧRץb ֮Ys^Q˩gYWKS,x%TmK>Y_tJ˅ŸeOo8 }Qm0?C13 I2z!G.6Azr@V=h?OS&ܹ-%=5Hvf$ǎon#1נȚ#F7wRLS$(BAboC4U^+8΍[Jha BV5T}%;d%ȠֶOb ̊a4K3LP0JK3rN ꁢtuT:z-kRE :Ba>?AGn|׻w388ȋ/~\??P(27;=ƍ+j'OO__ 6nذ$~Y>._?|faap8LC4r?%U!藰XJ)L{JơP&2L8 ׇuxƯ7Wgw( ^bծ+gS•,5S %E(R9?! K.FSsʫoկ}uUg ۶ocgLkU˼~5iK:33]6}jaAyƅw8p* s^-n)X|pCxg.EЩ?IT3VNQ0/;22z!yP<>J ^(=d~u־"0KzQ"=[HcnN 8bEJY 1י|qs4mbvO1րޏ0M~6}0w%|5gIMx|;U>:~/UtOAT)$ wMSh8Eי%z(!@.;[uq#/OOOSOF;lO4^r'[ٳux=r>|!^{5rN򕯰aF000 ^tӍ465a&?N$|̭ނ$I|_W|߾ϯk0ٹyy6o w;C|_æ͛ٻw/XV>Ok{s?ET>{ONkk+SSw^>OꫯrUW^å.宻o;C.9?1zz{x084Ė-x~طo?W_=αcp Iy'cǎ'kfU*&&&% +i~P#G}v>яP}Ž#RɄSJHw|յ޴ZV*.rƸ$.#G y BBbbL5-mJ"Y]ʒmC8jt{eιsDQ(4zH:Zjh#&J ȋ VP(HөSfea2tw٩::ՂF +2Z!*}zN *N۩S ,[dߚVy7~999tuY~)'Ndmdeg;PRR̮]1ͼ]jjk(.*beSy//p8߿AK/<>_xM "s7͢W_Ea⤉曬_;!'`q,^ʌ3x=z$'Nd夥q/E򠹹͛0z(~{1+T"ʝl޲)S&JKNeX, <ē :O>ȕW}vb63{l~{1dʥS2{0x io`Y)gʕ|F ho?aɒe|36 pT^w ˗-ǖ7%Kٶm;#F[>'x>^_X̤qFf̘θq2 ï>UXXUę#rz_/&Z|z3|][U菉" t4Dku!l9hMjwA#hjTlfمP|L)ȡ:K":L4E2:EbN VhEgI$ՊdC421$\ ;$mG#Ltƒ /(#Dx-A0jUreE}F(E!lQ- 5nv'+ȁ.۹f a_QpˊyfƨQ#q&' iiiI'C1r-}Oy7:PMss ?t,f3lݺo}k& v;?_z=Zy;`e~z7md6;0A ͆w դgdFe~rOyo/CL>uGYt)]]5ήNdY!7w 7nɓl߾N^6oBgg'f85^Ì3y駹kp).)7d„ Ȋ̉'ٹc''Mde߿v6<˦@\rhnV֮]jeɒ%z7ߢѣG3{C\yՕN3C&?2w FԢjЅL &d//I2#uǩE ;1[,231LDk؜6㏘3g g'`X͡O_}u6zMbw/`42uT;άٳ믽ΔK/ő`Nl|-JwݻXG3w\t:-֮z=?_Zɖ-[ٱ}9(̺>c$6mDCC# + C^G+?;rg%K.ffР<s!۷ ;+@ ]̚5 ^lj'HIM" r͵ҋ/éFOQ]}3.ƞb8Z]y%}O /e\.MM[t:`+̻}uuu{ؽk7'Lȑ#|"zҥRXXyK ka*oRU]ɓ'QdS l8ѣz{WQl^.L2quş >M[}޼.VWmyk0LF6z+Y\Y8`Ht| NW\,)Y]HR6_sKH.$~%N>?{V!:-[^7&G*{va\+S}X׈a1Vn.DA@Ex_FԅZnko (MM^ 2OMQzx`ODף7裞 cAӒHd޽$WJ Elܸ))JI!rz1ͤMKK ]a=%F~AVe˖Œ3ذ~Zn4$IRSS)..fݺdR1cbINNFAAEQ`0~zEVV635%F#D#j"K3Z@ww7.IN' HsK3, ;/=5†?lH9ɘ̦ƶyN p'.OGŊWHFV6@ :iZ MUcjƨ㛾v ///ī Vݵ\BDH0%'15-ԴTZphMs3Ir:IH^l 39CIKk UU$$ڱlȊBZZ/+V '7Q! qrssINNkfҲ1r\). D&#HPZ^Fqddf7d`@N'&`0@cS#\<u>IJҩSNk fƌCzFI$\.V ׃˥)))x=h]"eu*###Cep8I}頻 d 1Ъf^vqՓoB^aʵHRۅ!}, $_rƸ~}Y`HpbHpYs-= :CHQ?A s/ I/lю;$3BIBjXAԨdo~WUi_A``6I_3EE?NGff&GdW2;wd˖-ԟ:ЊjmCNn.555TT Ia6x2h F v;̙; 7 + I$@zM&Vk%W";'qkl 5-ZDA*ъp\n=~Z z}zz:| w0} rr<=YWWjpqw7(080O=w\))ڵիVfZnMTUUqsٳg/)?zhT֮YCCc߿?LsO[4bY1z>{Z8cws^yp6⿼t# {܋ Uqp~h3w6eXd?; m۸kYn9tyͷr1B! ՆSߘ;o/M[[+lqI1 " "BQWWGii C[o͢E뮻0 X,>ްCbfWnΪV{nR[ #--Q:d(=7pCٌJ("Gt(2_eegcvdY&$– P\\b{.MƖ[ٺucQȲLCC)BAa!kW!ᠶPgؾ-[pIuֳ{:Mii1ɮ / zm"gV{LKjU瘱/FA`0b 7b3gl2!)ZPHFGObYр=!!>oE]1:R:Ey5ZOׅu@VFzov󬶜`0H{[;(b4Dޡ*DH@{#LINnF 2,b.N_A/hT)2n\O$I|~ @ɤv#JZ*vdv;b/>DjEEƎF1 KADLj#().& SդOw7i BYYdd{nӘ|| ={`XTS0Ea`n.z@K>Dv{F1& !u6_up3ې~-Z^5Z dD";|S#9E`FE wx (4JZYeL6^#DP$Nu+!PB $ L_%%%}:wMKg?:x3gvKϊ,Cs7PPXKu>VW믽Acc#9\uո\>t'Lrr~ljI0zC1O?EaEQפo }|侪p߽SO^\s>ŋx<\ q?  ?u[lez2tW_sM'xns*Oѐ`DF}/3oZrs!<5AEAdWjjX!lPc-줥e>ҊG^űIaX?(! jUBHX,LT`P-SQ -F1 ȊViÇ6 I,q %,)x>,!id|D?Dg D`RB (H$B?7sR5(uxgt<}LG r?BM/˅<+VKLdbh'UsG{0d)Ch5uz&#ZNBl;}cxiW:ՂϤvDE:;;9tjht$&&~٤۶mcĈ;EQR\\N:E(b>b ǀ륥~gLmC$L&uuuIp~\tWmdYv؈V kjj l6HWS h4x<^F#Iq%'s @zj*MMBA\B8r b0$)) Ahoof8qd'` N0PM-IIdeR0DQ   zJ A t!0J$Y! M !YC@<$_H&(% ($tK8(-ݣ]toRp&%lӪ._ t^}J*ş컝{_X줻.PNN=-zK=!Hݠ[>~Ebt%أAg },hzw>_koo_;fpB<+.]4Oerd;`0,w}7]]]{h?ρ(**?/">(r Ν;^//| > &~puQPPq:<裀-^W_}"}QVXUW]uv z!:;;;Q%K`۹뮻x8t(rw~~mf3wu )))lڴ/ln7?8~3ϟOUUcƌa8p3grJ&O 7@II =6 ͯ~+yꩧHKK?1'NT߯ K.eҤI̛7oaÆqWPTT(߿n~?>\}Ռ7z!C8t|r8q"ׯk1cPQQ#<ԩSygp:_{J yyylٲ( xG8v&MnT&>#z|>|A~ߡxgHII᭷ޢYfɓO>ɪU/?&33SN_N;]x7z!UU $$$ą,HA=i.l6ؼ3D;7E9=u_yf\PLj"((8n( hPӢ @RD@'/ hE@(2^D@R;b B si#Dz?>E)qύW>=^bhW´?u|lx^~!I`ByxIRrDl6^/h|Ovl里Fct^? 2nYC>yLB!}y؅/ 2_G|'NdѼ[ Xh۶mرc|~)SqFV\W_ 6m ?' SOO}vٸq#ՔG囦&՚{復:@5СC?W_%11֯_磼lx HKK cƌa<x^(/[ZZx'yxxG#ww7}ݬXYy{蠺oJ.\ȑ#G={67|37twu,X6/_NAA{a;~_|=xW_}\͛?LRR<裼ٳ#F0{l*++ ]8x dz>ˆ 8~8=j?pmrXf wfO o=ߏo+wu_Tv{V\oSximmpp z)v;999 DP Ȫ)*5!pl (T"ҢqUzs+~>Ns߻gǫBJ >kW] _n,o+@Ѽ;Y+ɪRe0L&<:ߏbAe<aZi?.F줡VKRR%%%$On7;+w! :n1_o|_h|,/ lyq?Wg?|#vsiӦ`hlld„ h¾ =v?0`sUUUHıcB8QyBRRRpѣG|$%%1uTʆ hiiAxDfϞͰaâ8NG[[Z6qذa\.233QrVZ7vXHII[#Xhtww( v!Cxg1Lʼy0LdddDw2A!t 0 V^g{8q#Gbۣ8rssZOzz:QYq7~x,K\\n{=:t( x^$YfV% 1h 233gΝ`\{qeF^^X,rssdSN1rHF#<TTTb!JQQP#F`6abBןf*I=sss"???o{C]]'Ons3j(nV^"%%%=z4h4tM^y6on"#YYYɖ-[.3~?=,wa1>^q1 r#tYvsh$LNjlB5 n7@wIIf3 B).၃5 d}=`Z@PHv=ܤFA]Ilt;n7FngRY*gj6DQUdI l!u0v.#) %Y=+h%D@=SDD ՍH;ׇ~7(|~^ÇsG|Ν;9rMM5*}?<ͼa xz|>yin7?춶*_G" Jq @,SSS I:f֯_; f3Z%KiӦ86ƌ?8p` ?$l2MΝ;ٻwocHDizi߾}ͦEVu'?шh<-Hߨ;'NDRSS9~8+V`޽ CEE ,pk.Fʕ+V+64>nX`;vsWV.qر~l67o&;;Ə… 0aƍCQ$2d-"%%*Ep466FleZsY x5 zy-lU_7$JFv"q&#v{ij;Qub0za:Nlj0MXȴZ-V'n' a2X|F ! +"#LF#PoЫ6+&J0b6ŕkAPvtvHJBQYReU8\#?IVP9wA+VTР"DE v/*EЋ :AQ(pvur8?&amg(.CFp͆ ؾm;`(yfThQRSRpwY*|>/[6maHKOCD]:Yz jlٴm[aIHqxHIMaGyÇsN>1]8NZ+W$11-["K2NgUذa=~g%.aՈ$:Qn޴ WP(p\|'Yl&paRSR8~8MM8@ ד YVCٱs>gƆ֬YgϞ=9uױc K,eǎ>(RSSi0nWW;s5Q+`0fW?,7ѹ⃁mmNEeV+z>g0 q׸ zib~{] e Uh48(TNYAVM8žȽYM7cL&,iF#''PdGn:^/MMM_+")S8y$lF9|ukqIIvnAKK 7m? %5F"cv\.R(d''N@oN{{;;wdep\]VtPԱ}.qeYFSɓ'뮣TuVΝˠAF\s5ʑ#G4i%%%vFӇ (ddd I6l`ܸqFRSSYn̚5|V^Mnn.LaÇ1i1AQQ̛7bٳg_~9YYYFma0&EEE̝;NYJJJ[oDYYYddd&Lپ};ƍcРARQQF2tPN'555gڸٻw/23fL˨b᪫j:1  I LnwZ9sʂ\~wؾm#G#uz F#kWOUWzjnF8^oʹXj5ٴ47Ɣ)Sظi#Gƕ5{r|'0c 6}OQRRjGt:Yj~UÌ7"C+LQQ555xӍ8(cƌҨ"5-:&L@Jn֮[K{[;_~9<<Fﻟ9sG1q$ܹsYl. 󑖖ƪVQPXǙw=_}#t|7&(`0)/-MS2 d6s_8lذݻv9 #G0ydU2z(@ o/;p h4+JFEUU߹;5***QUUŸqqx;NG{mmmN5"Zl6{e [֮Y>+uZ~=RNnF!<;l߾^x~8|Uaǎ̟?z***{z9 ~rrrw`4PYYɇ|S2rHA a'PMF]e's@B?x^!IBT3>mmCA8q$ tx1U/흝M&q&%a4x@ 3) ϋF6ԅ IDATh0 tw{x+ɤ:󙛡e{B\&!, DJ5.-/cMlڲݻw3k,ʆ aɀ@G{;~IXnjEEZ#G`IYdj8yRdylݲՊ( #"Mlhu:rD\.Ca}L4!CN 0[3q&9Y_44⣕rDa#FDi=F́q':6|8wX]?mʬٳ5#B N?z@Rd"&eLt j8Guho¦ټy3wf(V٤ɓ{ezKG&ªTL?}ݏ-*|0))%+Vq~lF:-`(fD2QVCGLWW7,zC"!QzƏ8y{&axT;+vq[8-_j?$9a#F&z!0( ]]tttx tZv9D 9IU# !  I8q. tz k[Zx I{G>_ F/ $9|B!zt{ʤN=ىәD;vx dIMӉB'&gRRtLhؑgqgYL8ш{־uڎio|o]WXAn@ DFv(l68ȱ:i0 cɇK>}2nvZ[Zl6fϙ ؾ};'Yv-yy! iii |$adjzVzcƎ믧 gR7];+A :lR$$9z!jN{[;[6o0A?yVÁjN՟F:Ν;Xt 4}{v@Yyg-ȱ O*gFʀTSrŌdjP^Rzh4D[QІ.=2L&'N6ՈlܲnQU\VΙdY*M HTKWNSZc6Sy'~\U\cA-&DATi7!sӦmŞ`G@y^Ѡ ꎱvZL&3d'ߞ5dߏ=2d(n4˖s7cZ9q1|v ;. FL7WH64b9 I"M"_. k ߝW H$$#|O\TZ!z5+t߳ 8$H(IP׃f#ٕdFӒ,KDV5CVKvVZVl& EE IIQTL&F%|^ $YV&}WE5A *ܟ jh8[;Bʒ}8kt:-huڰ^Co00k,zM˰%؈: !=6P?a[lѣ38h?8i"^}7|ѣIMM%//7hftE >V֭YV#=--b & 6ݳqDAAJ ƅ!CDVz˦M埯+Ťɓf'W ^Od/mΜ<=GQͣ_)))!//iӧh"JKK|!., ݽɓ'1j(38\{]l߾:g3~;O'YѹNE&ou!\QębH)Y޻JA~@cS3N^$ItttRRT@Qa>Lr0`@&uǎHq9z}FfoUuX*}*!‡1)Dg"gϣBKT a1?u^)xu᫬Y'b ǞV!C`p`ĥ~ȒĬٳtw(29FvN6,rrs0H@+ 8g{zJסj0\++ %˿s9ްZф`mVF}Q/`Ye~/yYH:"^_/&Z 7bёƸov[kЀ($/ 8Fx~=*J¤D䜆Ws^,K"I!dEAqid c8iA$BaMzzp2V)"z]t.>(2&)Z $ a'_!ti茞!?&\/FP`3bi4755ks3 k5kb9_^ZVkQgw7@>s#撛͢7_=FFz*2)*L=*)/-"5Is[r\N^ǁd!j4;FYImmw`TeӓFB%tbwmײeWֶ*XXEUQ(RB%L2}̤ E9{<9OzpqRS@vv:zMPs\DEQB]b0m@ "T\!iO|+YBY( >( 0vD4}4H" lvE1~EtȾu:9w/Fv/†Lty ]|3/ڶT{­gjwz$ v ȲbFףp^pFР+D@𜉀$zn4ekFVC>(F #h4TVVa0utZ& Á 9׋ >>FC(bINfJB`X|>գ,A9tʲQ YGeYFhb%dtvU$U6}#z}Xz[ Uɿ4ooAm8vvz_(^!2^`;U0Q8-5յ,|%2Fvه?G@9GZW]V޳Y蔖J=M[8Q^ѤcIF*۪I {7dY1YnSK .&stE_+|ffY!I/|ՎWt*p8ȲNjqIJJj:Guu5YYYtbԩСCF8~b prp8PC.2j*rlu>hoy9555FjkkOJ8-pt#UUUffstq [÷;ʐe4L2^] uiOrn,`Ahl?a-*"oym гkͯmqwOw0FT[ѐJjjk_nyiw;)8A dY&9)d$I97777Ż(~,z?im6ШXF:^11DEEzq~bHbB"ʈXh0$%&!2U5Ġoc(dt:-994odR˹ztEv#1d| O&^>GWBk"r > NlVv hT6Xa3OFC W>@ܧ,#i,W)ơޗ! IDGG#)I䪠S&&v3GT''^WByؐn,f$I"''y /DZ2sLf̘Alllo|̘1Ců\;3]?̇~xFVVVr1fΜߎo{a‹:^}U׿r]wbŊ6RRR¸q~t[ !`+Bo>Hp%K馛ZŇ۵k]v%//emܸJnVm. 曜8qAP|`<#@NW./_51C otӃ g[F[vz5t8-|Q#E1A96t N,)ᬷu:뭸= =Xf3@Ӂ444(?ZVfŦ0@tT46=FÂף<7HbКW:$VvU#vVO,-鼀 D.86~HPV/}ci2VLZm rPB4B=Mp:zW%3rh|ܬ+ٌ$KJ`f[Nǽ Wr}ӟ,̞=G??!IO>$~ӧφ سgofDGG#2ׯgժU\.y̙æM5jcǎgҥ$%%ѻwoP\\Ly饗:t(Gcqan7>˗/g„ ?*׿o>ONnn. h"233ѣ/'N{ &P]]&ϟҥK[9r$cժU  p*ϟlfԩ,XkLyy9ӧO'/O1;X,.\-BϞ=Y|9s;v젤IXt)9U>|kƆ 6m'OV}̝;^#QChԕ}^9(I>QY*S2, MOTÜ՜ ㉏oeLrROs ojJJj232Pʤ$'X8mCS 0b]*PzBTHjﺆFPԫĄxbiUz ?C˲,kbw@DΟrP>٠:<ܴqyǐ$Dv;wq/?%%% V_{=@\}|ٳgGQYYfʔ)++_|(V&5\Vfqȑ6y̙3կ~Ebb"Nxz;v͛ٶm˘fFœO>ZO0j(|>Æ .+T^u/"?0`֭tM9s&_~9g̙ܹVZ, 0Ç3bv޽{y嗙5kDf̘>Nii)VÇ#"믿o-[{hx嗙?>YY|9~;GW_pUWK/1m4wΖ-[>}:O?4zRe-[0bNyF_&-- ^֭[ٸq#}e\zO2c ?N7`ĉlݺs=gdYg%11Çso~fc,X &L믿NBB~!<.M61a„(~_ӄg\~^q[z*AY1NЖwn!DRjp\Ţ(/ ::*9T&;+FKUu^K>|$,j0 $cp@jj UǓz:|>.,j5 |>׋ i!˘fL\l1#AXZ@EEp8ENinߒ;y+y:#:4s E`0(m2Xa":8T0$YV76׻C0,[ý$kĒ$)`ՂRhx zxxs+tܙB~ڵ|Ǥ[3111֎?(1zhuFw;C=Dll,L$Ibt҅:oNBB)))dggSPPNcʕt\q'<`00rHA_TB'NpW?>tvM]]+ bbbtQjУGTsN]w݅?~[HQTTTM~QFѣGDQd2aXؼy3~vz饗P]]W\Anݸ{ذa=Z_X,4664ԐHuu5W^y%YYYb٨ĉK~>^%FPSSͥ#FzU|(;Іq`\oqV|3ڍǏ!M`@ >χ?E<^/ hK ^MT_ Y,!KME?:bulhlDo0`4T'A]m_p`2j4xz\.7YYXV V #XVGtt4&ʪ*~d<^/IIBx6}Me!edYZ9< IDAT8cGעjY"233?`= #Ï=z4qjnBbB"G2nU۽!$:J#Dw.|b z3Ȳ,EN',ch4F ˅DhP9^1@Vr,;j"'Y%  VӉN%::^OHlh4Zo$XbWPk*A9JW0t@ /.\x^A2x*}PR]]x oҷo_ gVX!Cؿ?'N$##իWS]]M>}Z騨`˖-@Ϟ=#DEE1p@,X3Z^3gR__.چCFFׯK2n8AhU>zǏrRVVvJ>`߾}XV >Srss={6}!..,  `ٲejZAPwvEn _n3j(,Y>o^>@^Xl*ٳ[/QRz͛X,TVVm6x  7$))=zD嫯"**?zHݹj*?G? GRR'N`Ŋ;Pq$oEnԴퟗ1h bccYbcǎpVKAAW\q;wjm1cƐ@uu5odݺuj,YtѣGdjjjسg&McժU|l6ո2l0^|E1L̙3n9sD$!!Ǝڵk*KZ;.7}z~?X nB h ~L'LBgBKpiL^ϗndDGEkuuz-:^S\ HrQ}~N'0 @mX0ͪ-AԽѠhyl2j{8='5mAŢx8 Hwzvc';3)))HRNOBpkFAe2rĠjx=?/ í(CAeͫ/k.6݃ɿޞKtt4 }E}NUSۮ6Ҽ.dAPCBj<'˕@CCǫcEү(@o0Rw Nߝ-f$p8x<Ȓrvtzd2tٻKtaү ? r&( MJ_ &@^nW_2H5\~qCӸдV~y8S䕺ɡo߾lܸ^HOO3m4^}UDQH׫W/V\СC2d[f~a.] _=-n`ܹl6LBzz:CwXVzE||<( :ܹ3 =b={6cƌ!''#GZx<_]ҵkWDQW^h4rrr GСC)--eʕL8t2e C G,* ,oVƒCIIjI my뭷;w._=YYYׯ3^z]v.roȑ#e#G\~jg!)r EEE7^{I&{ڵkyٹs'=weΜ9\{H[n>CdM&޽{S\\͛yGIOOWi ɚ[nѣ޽{F.]ü>|8;wT[-oС }~/M L_YƩcn*ˑ]^v _H}HJhd7YYټ,t qqzDLDE2iZ(x<^N7V#ȢES-:>Fۍ$9) ˅Vt_j44@U~Z%Ccظ8䠅Lԭ ړoe( oPG _<>:}DG*Oڲ lڸ`I^Wg=?1cߟ#55,H$%%QZZʤkeÆ aӷwy#F8tVQf߾*$::֮׿MټϞyp!Z z= 1! ^.+9B߂|:ge!I555X}}=$IX,233Nө&$]vB>D ~?D.ߝzmh4:zcP}.4m7)))}?p6BM_9R80EQ@@wr8|յtJO?p8B/KדN_>| O<&LK.i|:+vƎ۪>vwؚsex<Lu]n~a"~b00 )҂ aU B5{=;hkE/\Xl-tDk$z2$9/?dw{x>4Fn+א*_V@Y.:Z) H*š YB r : )$VM)Bw l*Lڬv:4 y!+mlЖ\N=zleBEOH~dY`4 D&#("^7@H 7jr_]BJdUyioeYYfh"ҨnIj@-%8 שgid sS'YY5#rԵUۓCs H ato{mnNGpT~(Ζp׆{lvhZ]6n7Z繥LxS*'m =5 0PFq3T $t@t@t@t·77M•,9 :Mk+YC66!#J<m?1"ir"dkm6S҅,ii-JfSNHdTm=l"ؖ4'ӱn~8N.O1O^,I Ѐ^GY`IMMCeL&|{4GoF|Z9\ uW(|@uu 114 󑐐F6f&\ߞ:6mH  `Us#{;ϙ3u]GΝ[K]vi&AKҫW!R+HA%oQVVFzz:ƍjk_jK6q]d0%r^^ӡ3Ⱦ@&/|H,]ɓ'2}<8N^U[<$:] OEIvT/2WaĈ˙> xؿo?F6ɩrۄt20)UJxP%?G`T ̫xPH^T *`Hx$?8BՒ~N'l O9K$^ΣG1Ġ@zX, p L5Κ |"FI u@~#N,22҃tuPDAX|^$),+ANf3\s5Z88Ájrap:z[EY={p8q:cZՉEqar|pMLZgSl5X, !PnUey]۽c9e? u! 1Ad2=*if:,ٸiQ8jO>ف#Wرc;$c69~e˖ٽ{7.!xVaZihh@E`\P"zѣGh_^^Β%Kٳ',\155'۷/?Ca˖-t:Fa2 -Ed2-*y>D'SN=ށQ<4rǍb eR#(6EQ:tQ@U}[C Sس@vvg mF O>L4?oYGBBC?rRSSt?~#G2i$f͚?̚*^(.͡C?(7x3;gstw@t@t@\A5yLj$+fLi62#hcc/111[$IlذQo>uC_dڴi3FM /%Kغu+|M!E"E)hO&k>#vHMC !I<}|9~gFz4ŋ⦛n?O>W5dff瘾;IJJ jٶm;ӉZOzz+qJJJ$rzM.-;wR[[CΝѣE@y রNaLmmwF$zIZZb6ӧw؊rillDE z,۷#GO~(//gm|r {GcM]ѣLDϾ}8qb#ƨ (JNSvh ?k +QgmC1z22z:Cpmep8$$*9F@]D;ZA!wy dݏQ}"gUD@Lx"B02JH ` !WSpkXH.n8JȄ_fcc -m<U t6:7mTxMCDg"2}uuw^ܹsd;⣏>ꫯ&11G}'xh^xy&M'[Dqq1̞=b/^(**oa͚vmmV:::~,<h$<^ܕ^E;QiJU9T&#bj5yZeJnDxitzLOedt>`ZЕdbݺuDEEn'11Yf˘1ٳPMsxUUUv;Я_RRR磶ʰah_e^ ~)dY&??DNVC\\v="bѢw qF&NH~ kQYx~=9Z^餲A~^oKt{^jjX,\y<̊_0~xb1a}媫 ҷo֬YرcIOOg޼w!|rl6&RNm~z6oL^^˖-Cg(--nPSANǞ=%xx^>CSrrr\w… )..͛G,\=zcvlFuA(|2vqq.,*Ndz쩶S 2[1w\GPۙC>:c=< KbŻm"͚A@z} ;rM;?5P3hm_ֽ˯2!Z˲׉&g@P]o+E ݐàثx ͍6J]ͲYQ͜Vб1RvrUyؽ-p]Cğb{޶J Hz=i3ٸnvA,tE̲C lڸGAA3"KЩLdbFCuih$*r9tV {(d Э[wL&]tUGn),^o ++Ӆ,ٷo:Q:t(x{mCҝ 8]ڛ-y͛P1v뀟t |?}`kH}^Dm;R?\OD*"N+xo9L6\,h"w#߇0WYwRkpn>Y d]vRYYGZZR8h:ƌO/O?WZqqpÍ.<_;\.11\ >|E׮]9|0. Zѡ΂WP_ojr-Xl)6t:>#.W^yѣ'+ƦIDATǐWjٲe Ӧ@tt~cDEE!Iiidee AhdwI$j{=4:c\\wB0~Y_'L;NK'#2]L=4vg/Q3Tþ6".Acyopl\?.BƎ% IhHewʟ2"~ _ֽ ~UB&LoFy*TdT" )E!#EKnT_Wr{]ynJ_ t)l1>F<|-<|@pI1pffb /`޳cJ 6rll\-đҖF͛6!Idr*t}fte6uh. ~HKK$$ī142 8؈fkAKkm;p7-g[no~,m:@O.BFCcwg4aV)aO b)IING5 (MBA0+#I>d--6%ђ?c)**$' ۶`ܸQ(IJJB4$%)+2wNvv68 l6HMM .rVh4… իXaÆii6f\.ozh42d#==uaطoLee%}ٝ7*z%0=̞='(?!73-(D\=6@ *> GZ+Z FVkb) Mqn!,l߾3yJJJHLL{̘ʍ!hZ2Vk=6|!K.iFVh4Z(FAz:>+YSF#ݻw'666Mek4111Arğ,˜8q V^#99tƎYv-^;޽{J$ؽ{7@$JiT#MP'G<.A66~ʪ:4ԋ|^>ɃO9hWţ^hk8kM! $8V 239t˞||Y^OgU Kgd"- /hbx^o߀a:Z8m=N۷/onݺq-x6[3?::{ocĉm,@V!˲9.^5nnM"$\2:'` G@֍ndM Ae?~=e~l}+ rgݺ;w. пT|VV;wf̙h4FMLL ]P]]- $66w} _q˗_~ɰaø{q:| >NHLLdƌHrr2%%%L>hMeXhyyyX]t#̛.~?յ%(;"PepQx *"~V}\yL&RSSYz5cǎ/Vн{> ҥ _}nVKqh|M [*ߖ ElݺNGmm-(2m4{oBS3--0LL&8xP7Zz:\^ b2B}O嶌' Fyp-"Mu2!n I}ߵ53LLkGO6d,V`Ȑ!?~zY>}:W]u^z)7nD ٳ>|xԁ@7x͇~Ȏ;ڵ ] @]]sER__u]G~l\veԩS)**`֭,^4M$h֭x<믿w܁ |駬Y.]_l‚?jƌCyy9 .ȑ 0 &"'Kؾ}}qVU`@ $ILQ~'{`|Q/QH0~}ۡ\ C&F2b>Qh:6mdÏ>v )& J2f C1gfcW"˒:vF;![Fe/Q@Vv' I2tJ *U uHTT W JvLLYjGY44Ⱥ|SZIɠ%m-'O䵉wZdNӁ9sy&yr&צ~'|@Xh3qؠW3sωWvj:FT54rt = %j~!R[,>1gk_̛n䵸Eos>\Dq/X9˃ ~j&^)m%$0L[E|~u{,IHD}'ŶO􏋠n}yXVAsʫ ^D̡JqOFbl4Uj/aۃs,mfڴ_ҧOu}lAWOgE;ӆOr\.W0槢kZZ-c(ߨ .J.QQ~;;Ї QǙl@nmH*!ĥ8'9DҪ'T8%REF 10cOvfg6S53׏=i6x3vIf?N&MgmoMe䆧C`Ga^^ ) Z7ˆy/.V.q:vqny`!Ok]Ytso-oXઉO~x/?X\"zNigz~մZf;Zt[zMeM׺MluZ@zx=}Dw?Ϗ.t} 'h7k*[1{⋯ŗ|'<2(s?&g/9'{t:5V+ xF?=7 7qtt|OkSFz EX!cc \T<υ3” cUEl@ZevXl:ycZ*;*/^}YQVq"dJ _~w;.HQf;LWݮH6+bc `>Ozj:^ؿ}aǏ" #<}nat2t6F22;鷃rxy@(QÈY,o rye0 ܚr^7ն"7MjȈgryST,CFe-K*ܨ&6T,-]'[V G(kq'#=5'%/oW:A+A wUS~&_M; |նp?yY1}j|63`:#"&?@ǣ<<<<<<<<<<<<<.'!vhwX;,~ݨ{6#]~VټI gjC/k<2TWO׳UxZ#[{V^T8Sثɖ:lpC8ꇧ׀58bPkda r⧛6@4!6EMSfMua\Nf6m藶AWl^ܕw^g;ɢ|q:٦|1~bZzjU(%._Zk;׊{_ޮ?z4tnI&yޚĪIENDB`liferay_1.png_documentation_1.9_applications_liferay.html000066400000000000000000000117301325274564300463040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:liferay_1.png [LemonLDAP::NG] />

liferay_2.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000006562111325274564300420350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{\PIDATx]|6۷oe/@BžػQ ]n0&æP({Qn ھ/lBFOw>˲<Ηx#ˏG$KO䭶Fdgg3 Ciahfd55Mdp1#;6,M(V%$ayTg;Z5D" -pc +%+ .5(D4)QBR aD$ձ*jsvjJ[N&!Fhq Vkj8rXhrJ,RÐ+ Fɫ%XΰLݬ~Uk%4#B/,3BedL+Qp?a :+>ƞb&i|.&u?rS[7*nX>r1 )Dvssh4:n/Ou½ʶ؞.}=A$P ] e0ݠG 3H4$ۍVDʅ0xUPD.( xhv Gq).[\ .1xR}* 8'X,;+qBqKTP[*Ya[ѬB^@E J!W), j2HԞai:W)aS4hN%IQh[)0R MAFb% &eK@@n⬬,B=?֕+A((; c9 y7-K' s#"UU2Y)4&XIד!Qh>|L!ز @L!-xy5UcY˛#<RlJ}I*lAeDŽ%F6FW :$F;< l.T?`R ʣјfG.pbjSfe~wu5Zh|ZNi8Q1H{ %]`||:Z~3ѣ4_ {zz K(O$+ OHK"u"ZĂ 6aM*.!fyTz((0 xB(6"nGx dۋEn_h:\Q$ncWGJOr# xKV\+j}d{N+WIMM{w%Jjn 1.epߠE.'Z<*}b01[)>*Q)n&Qw[ 3eJq~F7=`mWd]¢,]@u8󢬀AM26jWrQ$93.k倮aC)ۇy@11+F:9": |ӊV Q2M .h1#B*-Rhӊ(c-j=Ѽ%|R<-%dC͂u'x@ B{&&ި [*Si[.,ARrqrFfʀKx[E 7&_|?9&c/|XԿd1_5 ? *yyyQR```Ѣ߿w֭%J N>%(0Lٕ,=J *TQsTS @^#10+PzhùygK2UqaUQFX|1g+0jC+0tIr 5gEi$Ni\69V=ЖW򋙸L'eR>Jͤ<6S,AL^D3/z2 +daQrq60[ؓyI cp[ A6j XA~0n59#VnhRGZ9  L.\h+o\ƒ*2BeP]{rpW{W;#;;ͬ)ҺԮ3i(*_9RRR|RSuWtf&L`6SSSiҤ1Ү]ʔ) vu]s>7oСChhcǕkC #={[d7cnz0rĨSog5DGΆ@OJO3DbG|K:=;\rt@ O0^^U:5D X" tԠ+/FG I  BqQ JbQKXrDCᛙ0@q]NRe$)(%YF({[7|I0J"$M'`*Pg` /+,:B G4Fa AM#c@91xA) OJpc~|ˀ^xd4ȋ.4iҥˬrui| 0p|;Ǐ=yUapUC-X`fKoSk<(bfpjf@ d&\6I~j3㒖u )JI@-SJ1íE ),(HTRu J7)< TX *"G5VT)*ElFJ ,sNN+D^@V+^%aQǖXWIQaErZ71v Qe4V|%zẖx(I-`*Μlz+ݻNav ]'Rؔ&pHڑfKx ȰShs᭜mV OA-Z5S>ۥ;!ҟE$Z88hPK|A#=d&T d^雞4ŠR@F&p+H d\a̱l,*=H%J|I|(s.AokV@ժUd*8p\kB>|9jW<F,4'qNV\8AZZz3џ!M3R_i}MUG{t]㥆3 %BFVj4@ R+:j5&IV X˗vv  ^|g06mڱ}gqAA:u+{{l?wخ}{А~JHHоW{klЕ4^_XdC7aBg}TB5`BK6o4w\P)yz9211/Z{{y?qѣ-Z(Zz횩S0S`IxUyQmUnW[⍾r7\"#G?~|Ʌ ԸqyRtZ`tV=fsdrYI]Vpqґ>x`+Z‘9p6)\UhX2+l_T#B)~.N?%YXILEu1@e2{+YIqǀP\LJԔY`;&x"*6d:.#Dayk3.Oך Q7m<44cGh֢EHH'/0fΜy1 Qɔ kE6viS'4$vRb.^mڪTFS٩'4j?~^$O%U-u^zTܹs>:uh2HZB7|a[go-ZztemYgի[oʕ+V߮[d^={ 2ef-\FQ/pj OrhvZ3C:9 {54cn'C8v!MWF RY^+l0"ZYO]ZylrHqURPJ~FD(({5l$YC;mt"E4hD) 4Ą+cل:EܺpaAjȔ ko߾]Mjן5Pb)/ӼL^ٷ.&.XvrF{'|ut:O@j6*_D{q~Z۳FxŬ|{WVrԩ+Vu>+WPօ*0yB|<8h{*0>J D#)۶RxD7<}D{>UBeW[d͛7222nի(W 8ҝ;w*VȪ#"5Aols΍9b̙3n- W)I18j1mJ)֮~ ~!8֫)S.;^}56QR/g` d*is9ORK|/mW 4CTHJUqPᚶHV^(HLRF O;IrZXq?PȻv# O۝"H2 KS|ezl=q+fK߻dsSQ0Hհ9d٢8-P eBa[b}Á́LR؎XeN>$NpF5v JٹFzpK\[S|éHB!#xBRSb_*dx EyPPPРa7mX{6/ޭg|Y9foф0?^iԸB&'&=tׇG R 2$_A nJ31OHT*9+PFSPFal+c-lc1WV/^ԪY5j\x@'OivSӸ H$>>˗| Ġ;BeѮSFe>\Fup3rt߂u?ҥwm쒂y/^&ӔS4iT ;ԛWv{PSp6mۢ ߍB~ӝrd1_NXURɵ@<]<7ک`Ya7S)&V8ẉH \T_ :UxFCI#=VP HOXG].*_W(SP[K(8UwAaF8E :?`]MtTsP(\D?abb y\[t)~g @z~wǖF^oص}CO/ ٶY3TTE9WU&{vuRdFl8˹*=lFd+I3(a@COG嬁J!Ba;‘Z_wE& x AZ+?W"B3vU QIT;7kk lsZ'ȸ{j~Fk!! ˸Pͬp_|^)x+I_!3by߽Rqq@QwOOO=sx,[,*##N:lLݺugϙAٲPK׮nn۶oi=w7UtzsDHeF 4p8pl9tMһwo0,!ڲTfիZkDz{{׬U+j;ysrr>{T=WWTޡ޼r53:==}ҥAAA3{ b,p׼@P,s{0Tɧ0@LK3oo bB}(4EnMB4Rm-"+&dkW.G2l .C]A,Z@vyM!/rVSߖd%- &est!5 a$u͂I> -ȣr! 2%\GO{i:쯼wYQ X:5_䲥~ΧMnš hbm¿F+͋ODB[(2:|hQv|!I@Baٻ7{37GdB( v$yPhV-er2ʔ Tr>6 (rOU*.vucUm٘ZhR" o]fDi<˛~;;aB XpOM&e}M *wxLM۶ʛ^raUBz)PX?ܷo\˗Oj*V(KҼrraJ敫J8k^(g/Ѣ)<7+%nI x>8pUDhdĝ120SQ,8mQ cZ.ھҬh{ WpfUTNTQEݰ*`P{2#_D aʿ-Z|ac$* pwLK#<: s?O2\ƒ*2Be vtjտ8ڔ o\>*Ua4gee~G5jafh&1+3U{\C]~ւ5FR@^L~|*"1 Cq1+gw([pvJXk1wm%K(;b \gaf_^*dA#Bo[pm wcQ\uDXه=0[x:㨼1%TiƵp{hl-SK@HCyEh(|NI;W"% ˜!qYشpڸB{,p,csgC pvɑ({C@н=)sn&`GVECpm%` byr]K4i"l3JRP?ߔrh&vk?t_E0L8$(x" xV)W)cejt.U4 OOOvUe}*ZSәM̌L CмCK ENmS B/(&' QBwʮ'{b;MlJP thX-FTA|2ԭ+st]h' G9&Lc"؇~0+ *ЅB=uۍ64Ơ KxSHY>/xR(k VЂx}/ȖoDN9-<;}Q bb۫- ))+ ͅ5*))=2va4ThRls*)X}F cmUvPoheY9)p zȔKqcn68@ rA=lҚK `s Hf(ƅQ.*c&4"csl ( J mC+Bs+>ٿ I)x %TU Qv+AI5'?OSݢ90T eq=sh&0l~}VG.pډSB R% V%+*RlJv a0B#8Lq4 ODm 1Hǣ]Z$)fAj{WAQ.mMh!Rw$eDfߍNJ@j,_4VŞsMk%PS;w.Aoʣ2< ~ bEӔpΐKzD$@rTN O'!@\EP6 F%0oJ4{UZpUW-SXC2xuA㠒B9aQI -Z q; ",l9xJ# sEK1Xhw`%co\He[̾(>}/ Ft{5rE}L8pP#E ' oBI=LcXd!J)7,[!+nnXU; 傫?Ċl 7-[# ^¢ +M#].+4#dJٗdT{iH(,xT!~J0æc_.o&n:MPfm:#4^c2= =5*be*BQu3Th#6I,TUt,ZIqkPIXM$b, sQ٪V1 ă5yHV/4& ߂:A3U 7(J,Ou K-ty KTR%'U婲tG w+MfSU9-<;rK@@@@@@@@@@@@@ ~ԹB B  % xsxX e&__[ݣ5EX肅 'FE J@@@@@@@@@@@jV^+W^S Xh:t(f.ZP۷F V'Í+WM<=MF}޼qbaWV- z ycχ4lJSymڴǽ{˜e+ǖ-._*\аj׮,fx0+?.^xjU@G@$VkO;:vNe.'f\k׮kZ~zŊϟ ¿k{nJkkD6nyWN>#zFtJ3~:]r0ൢ\r7o cO" ?}Dis#L%K>tl1ݻk׮߯]bbb%NTt4l^bž}233ի71*ӓ͈-[6'$&^|9A̜5ĉ nݺb+[ Ν{ nܨё?njNСCl0CYjù0Ǐcb\rh2ըQc)@|vv̙3O#0H׮],Y". /Y޽{ q(XhKMxUnԴjjǎkެ9?hb{ICm s[dɸcK֭[+_zueN28vr NK.QW?*IFT@x! x q֭,cQO|5VQti`9^reAx0~1~~~ н[ukb߿?v26հC;v}|; *~y8իؕQڴm2cǎv&ȅϜ)[ [w0gΞm԰NzΝlm-ZdO?fj#F~sY6^ECv.7w/JJ 5QRF}&vIMl8u'A_hQxIIQFy5j@, uUgP| HMK~~if!*9r^D@@@@@@@B{4PMud3B< ˋ(a@ Z,t688XV5| -Ȇ%˗/ _x/)W!:Ԓ-?o NUPL}Kܸy333vABCCp(gJECu"L`޼yk׬${Ԩug`yɾlx,^2x0/gyLW ϳ`\4|}RRYFTbQ%Q\Ջ9)]:''xAb JY٩Z@beL^ |I…Ja׊@^B@ӧ˗OI@|.P_… Q6:DSS;n7_{yyeddԭWŅp\o7".\ MvgprM ž'˻j?kRc0OиUr3i>TTܹ Z}Ξ;wRJʵvf05 d6߼uH¬ d2=~`hq{-?o9gI&/+;Eо]ѣ##CBCݻΚ93ʈѤq1 'O`yAO6nh)'7n}9ڴY fu>z.]"u8h'O._AE.Z20ʹȸ]ɾl5kRKmܴ{n)))1110ޅ5~.qߌl 3A&MUr3H6z3rtdhhhʕ]T Λ… GEMߤȋJj\3B@@@@@@@baJ۾?)PժU-V_0Z*vaNi<<FӫWO簠\k`0L=ޭ;&߻WOۤrA*}6,jj۷mŲ ;eOm:vdmݷkP$0иqaM&^2enݲʷoߞ R*,zMja խr=J% FH{; \Ţj)6ZOfb>UwoFs;b,ۧ% .__Egu( xz0 h47wwN)9`e "xڦMۜ 0%y z3xj::$'Ou! ;ʖ), + %xѹ3x]xGKE&wg x AX(ao9J@@@@@@@@@@@@@X(ao~n  Fao0l6jzV7J@^PW"%%2335 #89#9#9#9sp/^ؓ1L99Ɛr[RRR89T߿P!-- u! Frr+ƛ洴*LT5Yyyy*]jj}Ȓ[BG-ސm^˺+fb(VV8pJc;zX,.c &1ҥK{zh oQgUA?{cD|eB VyZ Xq:eezy Еfݢs̊E" q9[~v- xL@WJ~:a\DSH5s@Au8…sݹ=,A۶X&+)R _) !X,YŀT -ȁRᛱB?`KZg# p '^rY|\rrOHHhժCCBŒ%ܳW9[tIb(װP=VO3fu  wocTSFu:]j-m( nO2S)~/g)G;wbOz7k'}썎ݯi pfƧ|@噈<|xeKe\nZ5ԪXl2L9s$bZ=dMkV,"r[ʢl\ݻwyd2VX\uqsNHUOt}6#Nq{:t^N/\:̺*UZ%wDt;v*Qx޽f3w+VlٶZ*5k̗/ Rv 8/+˘xfzvVh2g-2u 0 `,0Po% ؗcQ59 [f]5u~bڢw.^72fڶy[5zղW>KJ! p کc{yL!-?.\<_N.!f&3؃yޥY ,vL_JBN#@~A8jpetɦu\q?XԀNt) ̜רV+=3fgg|O5@eĄoԪR+<HKjM@իU[}sǟV~޲ΠA?h @RRR|0Ӡ g3PK夰dG-n :bX́2!8*wϖ!^|q{YpTL6g>MʼՎ\p>}9s _O}ϊl۷O]ݔT KcѺoQdj2EF<}xqO%jK,t`Pf7ةY#_61hƅ:yx碪;w_~ I ~۷mta򍿬p4ҭMw舙+60]=3SuO? .܁Ν}.]Z(Tݻcƌ_AVZs-QeӦM۸;`=zFEEvvVzm&z3'4흦$& ҥKo߾O?f o)x=zbRO99]@ вa`j 8U=[k͚6_yK~V9hf76gf31ӦMݱc'#G4h0 Ο?֩S" T/W~ٷo~ӽق~ݭ 88fNZchg_E.۷yQ] +?j3cƎKJJ:u… lmd$9$&%|=R֯ e T2*h!P{,9I-nغEjL~vάGF2kfwa iRЍ'jŋϏ9=!"lg5y" *~!{veE ~97n8}׼y_%b93g쨨+WtXR iܸqbbW=mҤɒ%KU+AB6Zre6\R%p;yh ^p~8 :ǺwwFh׮O}}}Ǎ ʵjyP? .!8ȕt'O*qmf;{N9oURyޑk419TN ͻgK@AЧeӲsLK4hAGT,bؑ )+=- ooOooOO͝4 Cf c4Y2r2223MFǘղI 6;j|)CN_~`L3V٣#xψn}W /d1uƳS_䤙fO4֭[gϜ-TPٲeLnNwYOj5ZP(:IpiGȆMf~ ^7ʶzr\k_ *?{fRЃ_ֿYxȃj/ZeOc\ uss;ɩjg,jժ\Mw,3hAC_8481d^cIJNA,3;wRV2$&fJufΌfW#,AH`z)R4 g̚5d;vݻ` |a<jY*x֪^˱yl  @$Ox^2πL2wBdf{zxz=nn^lZuBȘկNmݳGuA-f=6Fs=ٳ'n<}щ[L^z`/&/=qP+7o^0IMM>uVvcǎ2W\Yju233^\䓏)$EcA-"Ӕ *TX\_ re1{ k'''#~E{2y\ k0w_ed{{/ -/7&6)5I5\{jPb;vo<{V(A<~2Im7V\=w:A7s͏@ړZo_N'j5ٶ `alAxz]C[>1OOzֳX>Mm85oYkGk:uC[uT:ip6|عsԩ'(2E PPʟQ8FA{ ׵yDWKzY2G&|ѲÆ[2IHAW~"" " hC4@{/ 'NnB"ڋL xyt:kF3v旵0v* _Qؿˆ)'o8ꢟbbŬ?6CpƮƳgϲKghH3y;xځS{5m [bEU)WJ*Ulɓ'8R?֭{Peuk׮\ȣ&rww/Quk~~~*UZfuҥ٬q~[m-YD>zhѢ L$ k?/_re9k:wI7LU E^G5PM'vc/݋?agˬ~]Vx=uF???k}|c7PڵB?5uuޝz ˾cgݴ]mnȥΏh'qc7n.K qfpF9ڌ!@\w%ROZ<+k&K >L3K+(s#?AnA~|0p5t_Ghi샡_+{n(CaXQN-tޱM:MQVf=8p;~!>^8S?~6g2R7};u#9'ź~N܋v3h-FwAܹsdɒcK,Ɯ0!]l$|9p0 nE(ڴi3~KڴŮSNcǎYx ^()7++ 3"rwԨQŊ5̒Du,Sŋ+V,;Μ~ʶ'6"K.15hpY@=_~LL̸qK={oBnZp5kZ}Ҥ .bʕ+lٲ!C$%%M0˕t222/gXw=133SMb_D{t׉tο[za ko/-Ū,,_vc=[5T65lиabz8TOnٺٳgJ,S >Y"bW1XjuӺQZG \hEkpо]wsw"{5nz ޸lbŽ9dJvnw_}|2@u:%_m;0z/MO &yФSL)!)JgFe6jKWnT۵?WYǃ]O8x@e+4؏Nm6 \0#`d,fu-Tv^ =Wyok-c K}ݒ2~znp3M?,e;DҠg, N>'|bZn=n8,[FҘ1Wn9L0=ezGFRP޽{UVD͛8 jժѣ#,5jZѣ؝ ܵk\c&jذ! } 8y 훺ܤ#F-e;~#T7ǧN_^hѰacb6e9k:wZۗ3)ۖxʶ_&_| 8Lq.rΝ܏ohç]O7<;->mn-Rk]|!!!5Z~m6j舯GVTY}mרV^q ocv\9OE4(3q[r3iS7Z^-R[טULc*^ZNKkuZkUVC.'yXyɊ]ѐDK)tƮ8SN&h@p{<A89ј`ɤ)Ѻ7X7~L&cRI *t񫻉f.h%k~-ûQX 8<aNr;~uaT<X&3xڷo_,7ȕҦM[.*WM4ҥK۷t)6`Nc&*^8,|y X "X+VdnoڴP"[,zJ,5# xo`999<*Ux%$$GIѢE%萅BWDB u&؏+?~?9aLm`,ZC͟XU1y!7 ??u![lqׯbUkKQ$,x]L \ޔĤ%^?qf.z}>>>#Ѡպو˒crnek)t3kSo^_ajfΞ&3r`e+YQp,|7_Yt:mU`OA8dhwOc.a2ga'hz7iVLg}w6כ@*Kv.] H5HmTԄQ"'1q-K" @hgXXz>/Kph[G[[Z99(7ʕ 2%&?Sse3C{]YJڤ@Ө` Ml?hCsjEߵ}o^Y`RnT)ϘaeL-Lz++tInz7y̬bݺ̙ԤG|Ճah^/DiRDAEKlkL,wGlGc7?&XhT(6۝e[Nņofgyo-[3l{耀UkXז ?^ TCZW]u=('+èÅR#1E,B," J;X rPʌ 5:Zj99M67`0."T.JD_wBώ]k5 ~c#$P6O~sT>pv\[FE=; N]PJrӠMGo{3YR@De: èt )Pkw?:;4^B׿B<j$<.,!! qmoY|hvjՐH&X S,B26Y7RG,q߻ `>#Fcjj\^wrrNbP(Dۮi% 0aeYȻY=T(K=tw?5'y2h쌙?t b!9HTB!/ m2,%%YR;88Hz'C2 `0&0IKKΖ Z};.ҨTwn}-1=`eY0u*бw`0=IQ?%[7o޽wϽX1k@ 0_U(`0 Cb)߿5[y]& `0 `ȭOF.dٸ"WW*G V `0 I ?3#1B1o z1 &ޅ% V `0 y``0 `0V `0 yU-Is^oP`0ToW`0oKѨ y{bbe RDvاD^`0 aIº"1L{ `0P `0 |``0 `0"66ۿ|=c`0 `07'>>~!ϟ?>tE?V `0 yCæL1,,lܹJaXTߗ.pUWZt|8[FNo~΋/U֬iz&,-3˥m`ފM;2[--m咜hⓧN8;T2l??J{̛;A/&, $GHE1`0 ]c4WZwazmNίk 8H,[ U8UkeyA gbq]7i3z۩TԣmI[oIFUa0[Xtf dZ':ujժʗѣ޿?=![(eW X .Q!l3/ ii~~~/f#5nZ(B WAǎl~ s?U\J:>uzU^O8VZ~ Sm߶']u_jB8a\͚5]̙=kӦM^0n\Xw≍}43b֍7 z=<ڑ#ר^G.Xh߾bYfF^&UF7DzÒ%Kc4aCH$d D~MnQccx3?/?ӷZNhPc7͝{'.\W(_~ʔI-6 c eu&c7OTlq֯69s~zۦw^:(HBܦA{y}Պ!!3x#G!*X<|G5 'N֭kdIӠ~ܦ~ݯAnܸlÇC*;o+mtT6vA:V\ݴI%߮Isηzisr֬^2eZx $Y>7"P71|34<|O03,Z{ioG{sW ͛6\qsɒhYs߾9[O.0˟"ƸP'``k_U|03g$}U7;v yqC;fZG۱C'#*50I8n]BOz pD[.Mci#G\BC7͍G D.yןْwpl{gկ_/&O1,oSdy~ }&"P "P }*4**J[,D"GhbzD˖-To~i޸dڵcǎ ,ƌ={O0$q?8nl&ym`= e9Rz[qUTQV_z566ڵkeʔ 6 {;w~ȓiiWme푶.]l]35 wζwV))Z}Ӓ TJ \۫Mv*OJ%PyWOޛ0kUvo߾wD6ta~AC|ЅftU@_HOOUXUh<R(:wuw y~haU{ע,]`y";;|,RaTj4(Z}K"1cRb W"/_U͛7AQgΚ50͓nq%jTϱcs3c̷i@N:io۶/]+= >}z͞-yGwζwVͽtү_x*U8TzzH_](J*d$3%5U">_ĝgƍ#"";v ߶m[% P#ׯ_wuu{ B޽{'O1b׮]i ^dI:uLl/Ν;߸qcڵȅ7لȹ$מwT&[[>B.pk 3*иmݺueK\k5K>uTڵ?zݚ5k>{̖jê̢{ZB=mB0VZTҙ3(ۇa`a{?2D߼yK[Ӧ7omAzR˭ynִ 9b 'xm[skܳwbbcy hdÆMp7 g>oX+Uݻ+ ,gmU xᬰ=Ė۸ԩ * ALMC.0JKJ +m= &@oٸq3o-&,9Y!QgƍVbe>*n:?._½[ f45Qv-[zY=0oa1V߿°[8t{ '( *ܹ{j 7ZXYV0k@ Xz%iBPsqv ~ ݱcGΝeWTfgc©BY݀eγ w@Ӄiܸ3f+~óX-kbmG0bά~aL}˖0P ._"3;FS"_߬_.ߣGnݺr=s:tḤG/_z5onͷaQ8U(  ou ?GVӦMȺK_s/l˼nܨI&XG&~l1?kEA1˺_נmX;n,q[{L6|'׸zd,RH||X/}m4^ D=ntiٲF; vζtV̏-`9vP[ӻWvm➿eD ]tat͚x{9 c,>e4_|𒚚{|SH׼"9  ̷hѢ_~k*]4aޘM.ež9sM6?yD(uz"Nn.h%i--n^߹et^͖hK\rrr@CƏ_i*]T^ 10VyV\\\A Hʕ+KlYP bIhc,[.H*V 8qBa0-6m׮]{iVhhyƎ{ &ܾ}[&ڵ ;vp3))9^Ν;/+Vhܘܜ|J*/^|(I 6u֣G  ,888L81xm7Za]tiժ-6psa$Cpoq#>znp-p[Cp-Is]jA;xÇ',Sǚo)OYߕӀ tJ &TH `y[JpVօf4h#;;L2_~ƍY5j$H@!lٲ\,YҾ}5k,\ܹsөS' faҥێ;5jk7J= 7BnR-lI"PCJyl׮ڥKkFeN >9t:FJGl?BB1 `>eǷpK(qzK-__HK֭U $97Z#GpKAFGI/򉊊! ʖ-+[Fmx򢏍Lͅ|e½ō=h޼p \> 7n%i@ h3J,ysa&kպB ]& 6'V `0wKݛnݺmG.'-oqC~yQXU(^#` ܫ`0 ?ǘUL0a&|h+ B!  `0 *\,YXB1 `0 1U(`0 `Xb0 `0 QHU(…` ܫ`0 Tb0 `0 P%?`0'C˖-~> D"7'd3glӦYصkWǎ,X0x`yeK'!`y1Sofkt:Bؽ{wƍ8)}:6s.h * b߾}f!j:ԭ[7UUo=[&A]1~ ڵkWx[ڵ۳gOhhVJpPf̘3f Aڿ߾}iiPs޽޽0 0QFG>}:\8pN (ư0Pm۶&3gXg̙3lذ{Fj"=eJ5nܸp9v-M&1= 8pСCAFxIJ'VN@۰aêU=3q /!###/_uVZ…Cqߏ1Ǐo߾Ύ%`t7Hj:8o.XIJyf{{(m-Iu`&a^#OWIr#MaB1 `>"F#˗nnn\0P.]3gڴi"\?[Bm۶*U{NMMP(Z7*n-Zׯv^^^n\ryFx@!m@I [̻&7O`0\l-I.:)l@5(4AOڵ MҋyFbK¼=Xb0 |D4lp޼yݺuѣrQEM~kמ>}TARRspppXX؄ &N1~ԩ3qܹjb%׹s#3ЇvQr_~Yb:T8]ڼ7oVTxFZ8YY`ٳtҪU+k%ϵZi㹶R\$$p.xCPƎ{A-o@-Ir뤰uXnD}]n޼(}RH< SHU(^` ܫ`0Œ%Kڷof͚ ;w\@ׁ(UAAA[n xxxdgg>}tI"q6 gٲe"""Ui&Vrm۶k-7=NpDa4ͽp`Y/`QS>>y $y?DEEyÆ e˖%J:uȑ#$߭[x@֒H wV DX_ٗȍ6o9r$+!k鲲@EXrK-ky' %!: npn.~Xp"##A6AsKR :)l@Z3LZn$\{SPN/?xɨWD*zX xp8& w=YYYii99z^ʤ B$;::* G''fp01{{ YnݻNYf wu*49P# =MNL2 h^[讈>L&wquk  a.A ̞<~#Ŋک*^"z=LMI3ENJJ (YJTrz`l-g}oH<;›1k֬:RXUP?,dLMM)樉}Y'/F_ϟ z//JDx,T~{N_[[Ǎ7ZI `YE}!-|c$5|b4詩<ż $>T椦<{$#5Wͪ_h4ښui oDի;ׯtŋ-JiS `XV1+3SG99 I:>3+իWڬ,:^W7n#\fMjQ۳iM$?["sr`P;vnٲ5>!Fj&Mpb,/QF/X0.Wy,iu[z.X_˪录g)#YMDrMmvZ(PxqPHE6 >"Q֒OIIyde23**Rii-Dm>q`0 ! = ʗ/ éxo//ɔ)J_fffrx+X#²,IB~cݢ?gE tNx2t)3j`r\@*t߾޳hnW3gY3_7S#6ġS,po{K)PhJЦ?>gVZ"O@v F vvv!!! ]&S*n3 `0, ^9gu:E4)&I=dg줁aݻw=<>>7nޜ7w{4̘NkF=]o>wNLL9szh_L8ɓ'{ZpQc FΜ1ҥKçO)[æLeʔ>m;+O5O3o @~Y9jٲE"f9ѽ[?˗\8kXPB#!PEJCŊ!tr bL4pb z\JZ)[-14JVCOPMۤՒsԎl13(SGA@jI(o޺uŐULࢧO^v|r^^^XK3#f>~_{mtccc5nW@XG:/m-n?1?Nӛ8q… jŊSC6>N*3}wʔ0SGF0 ~(*&J:2) JVZ 777 0ZzQ&ʕ+S*B@IIIW^mԨ\;;;*U m΅tΝ;R$j|YCmg'ik;O<wXA>7_'XFE:wIOKǴd*Uz{a2ePT*]۶躨[5kVܟm>}:b+Ϙ6~uԹwސâNרQ~~C;v=`š5ڵ˺_bOJ 533GlٲyժU]_:{ΜE "d5O3?&-Z4nG`{ח%V1d&4eJx&MO *}߹r +Qcɒ7nJRd(4DZ-+3ڕ˖)]-EB~jLHr\aSHHBو*R(e x&)O%&+L*˗||]\\!+D4zj֯U-FѸIcǎ?7n$Hy-X7NayYC ‰(oeRL4e AQ6QTd(=o'w>_7o =… x,( HyP0䊏OKKW\\A{ Fޞ={AS=xB * )D#- -u& : ڵ0hD~%#a(+ A jԩTD,VԩS[N-x%hY|:tHtAP= SMѭG§Uj?reA]h5o4\<[#+5 ?GkVYcȐAW$v;vl[,/]4c RI׭%/a,(oFFՋ-`-HMM+VWgffejNS;hJ!W񅋷*U*T`0oRh]vh0qs={p-BihܨA?~|856[ϱ on#Y t}~@̼lU}ZjI%nokן)={tf[nݠҵKPy`0;%9?\G# `#'.=㧌 FTp-A|@p`effB@GGǪUBN*],u {>yc捁oXkin.eES{j fX˵WGmzAKRȁ 5/]:(Ț7xvw >޸y\p0A\&Ӂ ?moB*u+Ҿ}'hyI(8.t$bX}\<[#wwFs𑜜ҥK[ }}7ƍߥK֭%Q"k>~t.]jZݝ\_n%srrEL.7*{{+LdtרR ?Q~-/ZhHr\ *Dwpm{@QQQi< 4^ۏ@k??~4rۤԷȘ;` %_wG2Z)pI ‰-?Urr2HM>R²5::J{Ch{DN ew(LVy!7531(s) ڵkCZ;Y8h 4L&mm[t'7il[gϞ6H"7oT(MHHmۿ}ڳgU>8+ht9h[5k:mt֭J+差]FEYloXt޽³"< 'd,XC#*TYRIԩs>}zVWXɫByۦ 4m:fk~0ft+ J DU$95q5h~c N'}t%&s@{ׯ~3]`T\yOs.\E*pZa7i4bĨaÆ6|[8׳-Wr1nNԖ 211mG;v"w--}&E˖-K|>#}}ٴ% a |P?{0B?@zPJ GabuK5Tt$Wq8’YKԚ38vH .|xFr-t D?wԮS~UFu ~^i&1fo"'zn6Ǹ7f:Q[2$:?BmԨG8cFnFl31,Mx=LY6m a y||<`/y (s&A]mB+nFdYvvv@.y{{3߃]JX_ʅa~;=!Ice5;ɰ"j%i=Usy)`d(LblsYH*RC # 3/on2Z$ϑдYaj:usժ̻ߵo?cNè躘{5kW}1-/Ql_{y{gkk:Eyw۶m#EeʖGUuC7}lۮ53[M("nEXt!>*݂h2 ̬Wq5U hY .HGI`xwߔQ8WIrD!ɥb#Abs$̥gL_?Q@Ee~D9urv>RJ>߂_~"ٳ'lnT2wƍk׮epO'22\UF% nTґ3wɓGvZ3d֭˪Tu73UVA֬Y3rHVO8|y9..e7[ULp|AĤ$H,%isYՐEjeFE?`5  *N',#`CDͪ+-&( ԅ16XY499R;Jn%Q3r(K^LybN J̦w.AΉdrqq6M SYف%&O-WsԈQ&}?C_{Ij3ҕ2 }".6Z=FD& `I H+5DRH%LA'RI B!HQLR=xЂZ`[`ތ.0ѻww8t{yz3gx5jtȑӧOVGq˗/f̘3fX';eyW(ǏرO?R,E,GGGәe` PGO,ZR* NH+Mʐ R<ڞ\f>2ܐG,":mHRO|QwEK2Jh'r';sD(Ak\/zu-~sC h„f*R<%K JQFʄF;Q1= 5ӭC1Y(7\퍞8J+t;fJm#* *w#uj-H5ty ԦФDggJ&۬E!$HPhrAQjKB~2m`g6Db?P)֊CS0oƜ9s8tPӃ˱cu_pɓ'A@ {<==3n8@Znx-ԩh'6lX*϶˗oݺdX207PD۷o 6PGeoo3k%ASPHnٲEFLHbRA˄(i_Z&|dε"sc3ϝ EH:att%ܡghoi cb o2}X/u1a`o]NHII }F^5 ϵH"j59H z )x22EPd.`lJNNR[wnQVSIE*Lk0 23D*6eļkV^̝`Db0¤ K>sL6mFݿh/_t#?FC§9(b~Zm۶,;Zhѯ_ڵk{yyff;7B )db]uܖ;tid͝e'/US)*f($Jhu:/w6+[P}{hKWrр$&M"BTͳKo0PB/H`DSh%RR!jb$wRGI  MD %hI Jg Y\ESE.K::}T A'-ʙ{&s'}YS=Lo}t"ϊ%xT7{wn(`]Z%9]I :3339%^y +xK&@LEJv*}O|/^{$H)"ר5٩rDiWw\i d ڵ+44cǎ%888,,l„ 'N]BnѢE֯]ܕNy*hѢ(FSZճ/Ig''L<%!uju02EQūx3))%+;=!!ܝ%AuhձH*`Hȉe򑒜b˶R$)Tz4J͖"iZ>LM>2qbͳT˜}y|ue zm#rE G\'5TJB$M$@WgOd*Zի999^}:u]+V8QAAA[neO]$gٲe"""Ui&f 4@ckI۶m <Ϛ5QF"(-[-).]2cF6ݻC~Q~lVt,@Ē%KڷoOj…in>Ynܼ o#\۬UUD0©B+/s!ȲҔV01wzz'OT*A1"IPk3';`HТsgϜGOةR4GQ۬wyә={tߨSQgW,]ŋdMHH(Vn-:OhuH, ̯̕X>ҼXd|JOhռ{J *N:&5|܊{*ۣM.^,K퉔̴BZ -ʀXbE{٨+Q!j]AIHLLOOGGJ clctNos( ' ^)JԤ5f *ZFSNƔgM`?Ar #/F^,&qC(LP|;:U4aQ#\CRRΝTo_&U ὥլY3N-J ;BBB֭[y'RXUm  \/ARڵ޽{.../_ 9-H7ohG'0bhHFcUTmNtj&=#NZ7nrqvRA%U*'OvP? .[V.XH$2\*FB0dG?_{4sڤW 5Wzz P.8-#GgΞkղE .v&R05}DLsؚ,h0IN?2:͋ibUdUrQR[:<[O`5΄\LP{Ŧ*0؇Ez]/k\$腷&8nmNH2L<<̔j S՟>}zb=7<~%a4xlTVC #E-'ri++y~YJ]sf$$&cL0zݦmߵmU!LԹ T{Ź“<*2DԼgHPddN?m($СX dD0t,r?9fak~Y Yf`b@rBo% H"geg4 LFNjRwp$좎jeM%O^^^S99~%z' QX&+*"EΞ;עysP: a2MY:B*љ$ N4 F#(lX bZ$M*XGTRL jd>0++%K!2QG4PV@m?zTӣB2Alz907B8P\p0))'9Z|"nnd-`0 5y4!Cܹe˖Dp(1ԔV}b$%9%55͠׫Tlfe%b𔚖󸗯`WZƶi*< {.YAmWL̬/nݺv.k2N"łKbbhsHϸzFbRr|BBRr6'G:-zӋќYaEpa괜A$Y6jD Oz&J4sssIN󩻖27O2c`m , b:kg}(` Zz%9 J?,9;9bLRTO}Vz*Ab{.E =Who[ʲSֱ'RAEG"E}ժVEg0i!CA*)LR'$FpPJt R FzT4ʤvXd0P;0s;(!'<mVhD䉉~~9h y9p/O ~eJAƏxʕ+e&z7nxT)^dذظi͛ϟGf-7}޳gOBb *H4,l&LIS<}W-e-k9".7n*U2 ,,ǧ86nn]{̌㓜ܳG)-3N4d8S嗥SL4x0HPb<)%ގH5^&)dv.&}Xe$kUbmBbELZ2!h*/교U ӡ*,Y*ԙ(rrr͜Ť%K|q+!'VT2fR ?k!ˠ!YRQMҬjr]^ x/9 Ӑzڵ<==!OWj߻^GACs<dH"h^400[ż݊-ѳ͛,X:o"(`h29k>Rnܼ;p玈ŷ#˲2R4j$c[ogea1{:*;+[&ZƱY9 ; jTno0IȗX"6~|%9n颗 M.F~s)\T&% %p,}IXN0by ͒5:4QBIJu6х'Cߠ>Ubk*+m]m۷1\zeF \ޙ|7ȱ^P?~RVMa0fڭkWxtnټ%Uv-\K-?:v |Ҵ r~Oݔp]w۰q kYC\x>]" ްQFݧǏ`o.D;ZJ_5|P]e+{%&%%l83;k B"WjM*Sh܌a2""4'>+դ5d$$ʝ,*'::ի"EJ-' B|=/xʕgϟ}Q ںISI4!'g#Ed&>к_111\-AgZ3-L4[̼qJd:a7WW`3 ZR";3Rc {1$111--Օ2PiVN:* hZ|mkJsojڼI nnk*`0KT-FS0J6i8';dyĈHjrǏ=A#?rvQ0ZhzM+P_ҟ\MPzp-hv\! "jdJJD&Eq֒+a47od0*U N+Bb^hIfA9"9&1yV.:\"ꇲ|`fD^+#3M\y`N sHI RȘV3 r3b&jv޸qc :wodFJ< Etam&}j"Ya7~S!$ͭHYwiYĸY>&KdS k{@6npwwȑ#99Ax-|dff:;IR^JH~V_~lgz/2fe (i@4mFvS6#H"5n2E6;9;G >Bˊ*>Y]o0HtJ*%R>~מCHSB14:=]c|ĜDP>c@.˚<6R/bݸ nѢEѻLiK9CYo4L99 3z//&1jQ:~Mә,Gť|p+7nV\`CeBZܦD0* *wRY9K^$g p(&2'bΞ<~z&d Jΰ lat_u.П{s=^Uw{^ȅ"EׇPB m "9;?6&Z@bFEBš#UX_5vsH! łQ+ Fx@ }/l TOnjE&I䈒th'w*hEZD5e'ݧWy_{֊g'^vS<!ȹtQW\>g?Bf|{_,{/w}'X/l+ _[#u5`?:~>Z!y׶[b,벬~5:k+4hyu\u" C9vaFY3u4Sf3/`wW!`޷gfܿXhux0;#=99Yi}J҂HԹ.Xo^uv]{^4h41+O}}=>MQAJE6^J~mHDg͘ǿˋ%BM%eIɪB Fp!*޶w#eOᕇG)S6/yqƒꦬ䨧 !ujpd%EOH@Ue,_>//&xzUe۶f*TBYs55a5&&Kvx%b7OM3 z0ּO)ЌI)lF~cX_rlL7ՎCh' JDPVOl΍Y"ߐr'~vx)2n@"]G5"N9+uL1%!6;-M)O>9gE>̘>c̝3{F{ٺe+<~挙醻һw5xb̙}i3Ec=Ԕgϙpxhw縟~)dqYΘ>s3fθp[I6m!k#3ٳ.>"+#s!e Y|谡 |ȑya&`GT>9/U{~@鳼Jdl.{f7bDwP۹PesbP1O)5~=kp6+0|$~裫8ms͵5)oB%Fg-2g+@-I$1.RRKۛ[Jexɐk׮_nzHڭ˒%)+"--}ň|kuX +t"oѷ o2ELONN`o$FWVl!FGw#ZF)5QQ>J^oʞonNdMXorח8*c)FG,/8nM"[ګ+W!"I cQpءZC3ZfnF TUUGEG5ZD;Nfs8zdO5CY by8ghr) e++3Ҡ쭗3m9(DziuZtpz=N P99X,ْ f 9)>q,#3ث ^O%T DaV?s/!.>zc⏢6Wg%c)qǜOv<9z='|'?n=?i^{ >8kwapaz-R 8?Yh$q(6A>0-B 2YFSA~E*}r2 Pʭr ְUVlVJ_&J'-ə DefE#GWrX" nNӧ*iOgWT `ase7ƾEJGg4^MLuWcw//XD&>!AT 3dm]}|J oڷk^VZZq U<6SD.WrD#_\$=$%Nȅ M&X3QPoEem`` 8X(tEGGcDa琜Ƀ&wGAmҮ߰\x#OYTD+pjze=FMAJLf1#z7#Oڜ^eq`z:F3 9 Uo4[u٣C 37y7:)gj岾93>d[ӷ zߛ&|ߊC!,[g TAGNTY܉á_߇&\<%*T&OzJ~">?I${TE}^TB7nJE٦튤r7闖u63My1<5Ts~Ƌ RsPq 5eN{rK9h)tr}tqwV:.E|Q_w;X!ׂ!ڲdEVY2r t&rC@Դ:xݝ0 [*-bHpEIyd>|!أ|0h]d$[Jb{33+ʊ˯2)5!%%e@/"(ףlMHHP>,*t/)M8B/P08`bVz6aѻV=OFgKHwǎ)V鵚tMNOaeVR(u|-kvcy>?+\51RF#QX7iRY #z(T *~[!f㧋ll*Q!e //熢rJ6w\ UNEA HR*?)ɀRͬ[.H#_HbiQ(Mtt={̘L&`TrAX& Ak.e92n_Qbg|61+P DgVzHQ: OBqaF TLllEEŮ}tz!a&w'Båt#Ei,F7_556EG[{슟oܰj\7bWUUV=+ʭ@gkkk vJ6AA^g?GnOƽo~o),kZŦS햙_sU i3o~LhVN.9+a߱jVBJvP7ays~j]>g|˄[@}5}o:z 9]m혦_~ڧ?<`yekz(T yNX>Y/(UHaZ^h?$ CghFr R8KZvJjP$ݸ,U.ҖX3 2F!FTCaVTS'Cǝ 8r2#&&cy] e^h\l>n-=XXkh-Ɍ%YQqZ{ k4KKKjjtFs̐DTǓ>w}PNǎ΂bPB$qWɉ\(t;;.7_qhh?x4& P(5+_QjZՅP? @~wN=a<3vVIQs+Q9|T;.ɚ>X şggߜ(|ZxBn?kњe2DGy[_m Yۮ藇o={iZ=})N|1Ƣwϥʪ{+Z`-30yI Z=BBKք"vD^1!b~KZyIFLJ4ztXI8N&E0uSX{Y>6Yc0()% <:6o E^ܿa´ *PPq/)*&rPO`* .4!$ގ@^󕕕,jh ߁o 8٬$ p5L38"+%QySX7+@ j0~]^?Vt(6UxBX,ySbt1 QiC2uu'1-9ZPxؕ2R(T²<,&Sc}  }"І*T8C8?m06]Q f P`NFKPTՎ_:ykטDΫ142FsǛ'ˎڊSaFot˿۷yk郯ptgs,kǥmʐ֒X(|FwM)c 뮯sz 6U1R۷!c?0eWni8qՕYgzˎuחW1E-Ij )OCPH Oq,aٽCSGEQ0B§KIEaEEGN$ e%ߩY;vhJů\.%|UoI{q\A߾k7ntQDL gQO,o`ݺXNx.w{@;I%Km4:-RsZTtM%:uGIә6FIk޽{0hrx>,jVQQQ__C@I655ׄ෾PTkRf@E`e"׋uӌVQ$pnKz\Sׄn8G1kF'GQve,RJiuZΓڃ[њYUX[oSMew[2: \<:JV r,(N2KFb 'DN@ "/R _^JVksi @!;wJPYfG2n~*TDMЄէ-[;trRo߾K9Z`6Ih2 zVCT-B<'g0@6lbuZ`dh 09H;VzYNO̰lJůOƺJ{FGGGEEƷ oaDp6P[[{'PB E,4R`Wx-y>(pFgY+\\~ Qf9>(]T9%GynJGIL("7%e5!UO1C Q%əLGE1zewVpOr/ ;BgCBF$ӡ7cWyS:? 7W6,t$GqQLG%ݨ ykA I#*rDSJ J)$)⒝J;k1] AI $^pBŹl\ $#]{ǎNӑDZ,Uv111T^C%Ó*zR2u:diY<ٶރzvympqD\\Q?!ȑ#V>444-~B`3cܿ?vl,Pv׬Э6ӆ@ *TpA@I5 q)(fMޡM4%–poLN뱕1Lن4jbŘj*>5&t@R[]W TzܞSB"x[A ȠL >}YJPFO CP<:*RD; ?|'H)DN*BN5 g'd9kMM}޽f3хɀ.@NY| _޳xqvNj5qпt|jNõ+!UQ{jZ[[t:!?gll,=j[@A8f0bp H+Vv[I덺PB g 4 SrC9*9+ORm=N]^`Jvrו29Y{]ZK>&U}BbIoxl?,[aG+GnN455%eym(sMYݡߒ Χ"ǹr(FY]m#DVxØ.(uUsh:O[RL^/}\*䮫@Uo4Z`Jixmpfc|ϪA9?I9TQXLT{Q]B%+CQAR ~E(pq"ysQ;;#OMBʲbçE,`8 w֞Tbe|llFzZtTTBB ۍJhBRp- zb&I _LMLJ.rUڵj. \pѐub%&@_n۾kVe5*TPB_GTXO-ջ~dva;lJOk+ 'L}etE+.y:Njv"=W/;s]z5X`Aˊ*[)xra ~ k< ئs1 }^~Z7&fx+ wݡ 5xkYaL>"+hY*Y S7"1DiL;zjxcY]"_%NG gfE%OJx|i>IcZ|F̰Eh{u~s8fY#%T8W (,Z]]]X\=|& ٜ4HVcF@N`2_<ǥ+) Jk?:<_.<9%blۑn]&%% 2TVrxB:P#lu74=eZQ5*TPB_' 9R ]Tb\Y{5:2ĥcIȼ'B2ŪHJuBȡZp^Yފdj%E~(0=m!4Ql'6(h'sΞ82 H Q3S~$vDSg9.>!!:6rph.eY}Mr9]n2 .Ӯ0>U\R6'7$%,;4l ME:n+P&+#qqh x,p{VVkQPA+ *TPq6q~SBM&kKB5LtNWSR>6)0tsO+]4 `1FW]VӭĔ_ESabN*x`!!}ӕ "@})"69SK&σ0&_h%iԋ.GH>5kZiE(PP E$kW$դNJDȏEByy"@Ժ '磠~\tMSKEŒU %Su*2(  N$d FcJj*=-yELXNJ>eDI&_tњuJNi>Xׅlr'>蘘*`199R`-m:>;VSS[^]ԻESPZ 'uW *TpqЈޣ[ [yQLn7 CnZkc,[_ꣳ;5{$t9V"7B\sؚJumk()1㈟x`|-oFbb"\ZjYs2t|z聇dА(S֭]7cG—_ ܹY)UԔ™po)JEF#贂 zGgK׮^xY񢘡szFA[׹vݚ3!gnڸ 7Nm!VTT̙=w;Θ>=)9C~ᣏ+Yn-'O sMwڽ{C>ٳB f.,,zp;Ϟ;+##}Ǐo<_£G wW\=Ԕ^9ߟ6mtzwlߑ4use I+λf͞,BG|/;lإPmeE3JJ_{R>?Ÿ!<@9 [%O]d0Oj$UϠdkj*.::K& Ngm]=Ik pX ւC%.5HS>Gpľ߷ޫOk&RӧM6|aCWeu3fL^y٩3fHMM 9g@Fp檪9ݷ?/iSǎϵPuMM޺Ϙ d~lc=wWJuٳMӧA. ѐt$K}/Z*x]-YN?@S{TP6 $Tj)1Mκr xYfy{ wT1W5~Xe3ħb wx]gn׭f456bSٸj,yuћ`iLH>Ǒ}5 3Z)1ݘ8vl7!@t֣5$tDB&i+4*8"i+;B)4K䧗PHbE?Րء@/>Hg3䡃={jx:IOR * 5VZe2C ]\l\m&y6n>|}n^ZЧOE\K'dNHL|q N_~y Npq؋[~YoHLJڰ~##GSP—__8ry Wb!xOy[6o+wO4hG'Mzva꬙8 ̞3{??E+e8i Қ5kc5kF*:k1d'8%l$#,as@U5 wK %+^NSZh0,VK^R -2.qQuaƍ/s\Z:>>~?HaȣeW^oٲ/oɒ%_9gՠ帵p݂ ~_Tε)-z@2/9rSO[n/,YD)ʕ+}ݥK|ŐkQ6R$@@_,x!BK`iUPBE8.4BM1s%.$|I2%>|zUMuֶ!3\.X!pL Z5b0Plm2J%%0uQ {}nTՊ :fN=f\xW%2ow_gU?^x -Y mcǂkKF( )R>\ ^"iK@CJB *"B#Dg GvR}}D}&&%UUVtÇso/_;uʯ˯ 72Y2fE.e646VypLewu8رc*`XӃ,Vʕ+=\J)'ګ螐m*0㤕'''C-@0VLR)C7N., I)G=UfێՀ?&[bS-s<'J!Xi>rQz9ïBp㍙Y{:uѣGv6 FC2mR$mGiUPBE@, /κ(wُB܆tsJNC5Vz4ʹh( uH0) .MM|W('Bb#5uŎv6cb!?%,T(4$l *}颃ffdŶg/{GSFګ=,7ޣKvإKu-]Cfh%$ Y|agͺzإ͛w!F>|xe#}9qĈ(!$+KJL|7[ˣD˖}>f̘-7-4L搕_:'xTݧK^6yZYx6b'p=pPL槝.OKK]GmD4 >^N}vZ^0k=kΤ櫯CD6gC~Rε0)-zww~8{g~z㏃{0kW(њH} ^V"Ժ-ZYvH9j4"\@Jˊ߯/r9٪:7cL40W9R4iu 4^Ɠ"㐑tb0z>OAAl2F^6RNy] ` 5gO?]߹iSC֐?d!>kC%O5bȐC|Nzl٫ew?N:W.lH{}LnÏOc?>̌ӟ 9dÆ {덷.2eG{t~#x!UpׯX"̈#`T7KMxM"|!<(΅‹5~<{lH4hcu幩ِAkdKG\1} \ Ң7)_?{lf͚G}gcfHa׮Q50Du)|[Я3f޽m붓JB *N󔅆W04%=ǚ1hܾҜpd9(1UZs4΀>CEVΈH,Olj J{(>Q4aNt*5܇z.5#xyM/:D Ar<"a Bѓ{|`ԧm ZII_[պr8%-5Of/rN|}7"8|@"OK]z믿^ٯxM\wШ|}7N喛b\O50VQQ?)`<ÏY!˾eF2tqa{qSqJng8lPïӕ=- Ȳ2[%ӥeFfw1/*;v({ͦ&VBlnha!/9u &):&NhvXEE!## TYUd0 3 a+ "T\YUIk4q1i5E8"A< ((,KHIrKp8|puժUcƌٹsgrr+)᪫ol)Z2 Ǘ_~9z_~j:m7M>. a.hV( N׉g>cvrv]ylv;jvYEǎcett`ԡ$Ա#]/MLJhӾXvV&*H4ǏX.**(đ#G֨CޝҲ2FW cOڵJY,ѱ0^/p@z/jjs"@" ;cZȥVľoI H dՊGjJZhAJGY(Vh=(]DN59,^Gdn?U ٦F㒨 00# >Hi CV7 HA4;Q5JϜ^ThCcǎ:t(%EK2eʢEZw"=z뭟u]wF[ȃ!#3[ˠTqA`0t:ޠ_ybVIJOK%IRQQGGGК4 ^7 sRbbY6&&Ffd餟 y7tXtVF3** (+4v{veǓÝu؁yOFdFA9䉤ҾD~( $AQ)BS9\A:ESNI)eEGxQB*ITNBKHrD9ˆ.*TU3$H5na0L&BE_d?8.1 X!T"|ƍw}oְaFEp>n[gɒ%YYY?ȕN8q͚5}Y߾}7oC޽;..n޼ywu9s|PYY $ y}k8d~?;wO {w;tPe3˭D"Rݿ⋎;G3 2L4 8pOwYŋ˩,sȧ7prCfk;~8#GL6 Eυ<<fgHa*/A$<8¾ygB 8¤?a  AH۩S 6 4_~2dÇ PVV&O@€lR* iifd4e6Xk5&:PLޭ9YfFL6'23z2RL٭aX 4*RH dEiQnK$pIiJW=L r&KB"PwyEDM) I@Y;DN5Sq^e=w͖LCkzka0Amjj ^:ēۂ 't}~ < Fğ'7e:rZPq!HW_]__7'#G\bŜ9s9Z?d0aP 'Pkٵk׈# B:t~ :L0 Jߴi\<(᧟~ww]6rMDpfHDۖ-[6c 17xcWB=z/D nٲ~a-re?egdS裏o{'ΝPrC~!3a | Q/aޙ[{#xX4tꫯ~in@X_|0=zto]$o ' G9=sL}PO1n$&2[dKHgSYK.ACⲢXrV8HFID)ɵ_GsgMx,,h4DvؤcǎmHid}U^( eb2s@2E. mX=xb4hNG:^?$.5%e<$#u֫@odgg~֭->, ^RSS_r%lf otR@744lPTx _2ի3XcgHcƌ,tѢE^@7[_n J`W]u,rN`& jSBlrm0/6 üy" %}g}'&Ϝ9xGJELȂ=҃C~a:;g|zkҤI ,CNxvD" ' 0^h,1,hajJ2PPEԌK1¶Q')cY/[. Wq TRU])xP+MSe( IxLMÚ =(Rz*!>5߹od(i8 U$pXtp!Q@1Efa//<Ɋ)\,'vFKP Xk#It42#8EQ6Ù|H8^/h.vvqu#===m3_5aZ9`*d,42FxvVTT{TX%ŝ())tz<ބ8)ZzK۶ FNBn]-555۷lPUyENE1aP+=>3!:::=-m_W[r4 UZj2 X,$;+;11ᴍд$DI^gڦ$btѱOȫӐ^;:*z%&ʉC 5JUxCRH7/x7TA}L Cbؐa׬Vg~?%N;DkD T bӃ:| ?EҿȚeO8q&E" "˗,G%ld!#+gm57d\,XSO8p !_2d!^2e?8p`޽?I . (ٳ??pƍD ,HvۣN*v 5\tȩDS8l#F7nܝwމS":Ar9%G.s.]f̘=Ew&‚a r A7ߌ_3¯h9M2%d%X1a3gfee<#u3mn*_n̙aأݻwSӏ?~r,sprU _Y8u괌3(L?q왐w/|Yu5T2;u6mjRrRiR-n+?rֺp^BXؘͦ/˦&UD$aWe"*N_Pfÿ.r-[o# ℿ*la+S%e~9%9mqo)BC%5>G(̅>x\pG 'x{?๩υ\iSJo_Tʒa]4iR@mWfٰaCWXZSСC@crk"\O7Xΐ2gp6eL˲E:wLD6O% >^|ݿ}e3a |ah@ـ.?0Ģ5*ᅬ?X0IEH\, - H*%QxL8;WrU܅4MER`СP,ܹsEO;Mryo⎐!b~B)OVhkۼ_xa _7~ ۜ1O$} L< sAk?[*,4 ?yҤO=}x.?~lƬbdC0kSŬ#kZͧDS 2_~1gNkg ʇ/OD 4$7^C"Tӕ|$ OHO85AʣY,c!<$aӨ^oL4O&%LIE cyl\Ilz~Vb"WW^0 _b/Eqg?x/ 2, w9T}Zp~k-"M vյuR IqB<^n /}.ZTd0@BtܹȨ֞E%'ϜI ;yI :qq9*SSA())qeb8k炂’Ҳ>o;wNhh1MRMŃr]ddiZ\^QYz44ךZPDGfHbb"WM5|vm%gKMh;)J洳}Ӱ&?0tHΝ'feC~r:\i87}阑-eC'$>yjw@72tUE{_}ȨHZgxD323xA@7l M.Ŭ#k7[6 ::eC덥U"G) [HOMwϊ=xkϣaS1,nE7F`Bl$:Rgig }d)n)+h?bJbW%p]DVpio.@wޜ?_:uzWO>Ԏ?yY5@)'xG{ nК|v|"&HbTqF@!= FQfm!08@>]ėH%ذU9BJ(Kx'PI[HNIl72^hY\C.n,e +e[q%5 }A+4) Ȱ@i 8ppFYh ihh@232)!(H*pN'&$` Oee(Pۖhuo߮C,GGE*5`]p$6557\t>+ G=GlVYYh@Bm ԔEYEfHZY*x w={.B:g|uPcڹ+sf޿o^| 4ߝ>X^7o}ܛ;o&d^ P C7|cѢsf|w P4ݻדcva~+_8a7l|4ЕI(_3g)䲧Ǐ?|^+YGf zWWhLPKղ. h٧s9_}u}(V U'=u}*e^=v|vޯQ~[m]]uM- e0}7Ubh&(ػF#uF 5yR΃ySzE$6 t+3_O=B++6ԕ^C _ >^}iA:NL8pˏB[ K+Բ7i/jGK/V{=3Kɻ*C?H}{X/_7/N_YM%f)wɲ%8}>]ǚSfft?|K2۵_1Q@^RZ~ϣ];wЇHn]W~qOq3-}G Y잁ws9M O4=xNGCo)' QXQjJzЦ+ ^Rc7ko|͠ f40}A+Ё~Acnjp8ԮȺ0_&urje< l7#A|\,v1Jig_H$&? !-HT*e~A aahǧͮjR A2  r.PQbi)#RTZ-ֿsrbcc|iMMT8q)u\ |M]_ߚ4yk- U@MC~{SSew&%=@2?RRV`6 STh."BQ.jb;̹Æܓx.7WϠdb06 GM]]^PO{(^LSO]uQ@/GFOVZSQIvL.  -SYU B& "$'ݘ,;̉*`=Amxauj5ndL*uD*>wΩҥC~E%e]:e>{VKO:m_Bf>үm9Y]]֭Ksd[8P-"L=;9Xl j&͚zyhazO lE׳|2,F^x7AJu<A>mu 8ppuvSυZ ڒ\!s@++@TbW* ۩T(HcO^.ӨNCH*j@ӒnB&' 8+JbcJJ TRT\,"" ;//bPTb!2ЇUʠ`DX텆U*<)%&''j. T?e-6<ָlܟƁC[C=DD"AeEշ[tb#y0VKJKa@&Ӡ J BqQ8EED~ܱZ(65ht^|sΧMb*JRy+`.} {+ (Jŝ;u)I𛔔8$)1ѽ3066~vc^>6b($֞sCt>%ڏZ^^iA,++Kb@]U#܄aw9]6M BtJTE?y EB$dU5.AbXj ]:rCZʣl_(r=+6`U`0lʊ Hs]ZvWTTjBRRR_SS jըU5"1r"1v PԔzmc$֯_?o޼͛76G=x`.]6lؐp޽> nڴ)%%;|p=>Dlٲu?~.r{O8e=~"u믿n׮ݯڧO}/''o/--e -))7n@T1[XɬNNˬ,Ȱw Xyy9k3Fܪǎb6~u9s\QQ8;t=zh47n8WY $P1cƜSz™9ӧOWY3o!\;jժ}2ŋpnʕ+.\,8Am KDmBO]x R&r7\Vn]Y얝Myt:[&]knf)w&h{MPFx_0EIǎ'm6&VL_ }l`/HchBhXKJkjj@F:u)9 }7 ۼz}\*/. ʪ*PLMInh0v\Jt:]r`0BHaAadTdtT`p8RTӶ%Y1.K$l6[ MR|]"ȂZkkl>R|.WIk|/@󮯯gO<|W8qҥK!?Bb޽{Æ *j8 1[RD˛YѣG?# }G6cb*#Ą1l ia8~Xd߾}7oƙ.l~l \pf>PӍ7]֘7ok@bgΜ /'Dv8-|806Yh q3544Bs,פ; 0`RvL y|ݵlQ(儛8w.4uu@2K333TJ F :M۽g߾7XÉ ;q.GEEB/hͶhD+r|dMȉ"0`j`ɉ. tFzP(_PH%egv j4bH$bh Pu uP;@-ݬH8 [)aTo4f9e"AT/ DBY?a@~SLYQ[?1n /|xK%d4\ !yrPN}7rpopRhEH3YF>a+blgwO9 93eKl \pf>R  |Bs/rzDff:|ƅ Ɨp򊴴cN2ZP \w7_ C|>^bծ55iS͝?UI8< =>3фRH`3Ef2`dЀiR񜼊z1*n:G4=$ڬVrJU0h6z`&N`Hl-5ɇ" z.ps:B]]]HAP/_>k֬u֭Y~:f̘?o߾K.4iŋ}r)Z O0ӛ6mZZoɒ%oۧNz/VVg{/> Ҍ~`mߝ~-Q8C]z5ֿAEK,X裏B gJ7{pf> *! ;t0c hПYY(\}?[nСC/ZV=|qSPefSRQ%I(uNC:YEBc,G"ܐwG}h T:l]L>+OVü@BzދaD5BfJ XZV2323*owbh@!09% zRSJ%umVPƿ0kwfNzsHY#O&"6X4 Z7pZZZ ,&:.**o7·JPRY|}t,0Dz_g2c7"F)ч( z1vGFEe2\&WUV*JxU =kkb喅6DR{5dzcz/PNJ^4~bp* g3j"6 `e^4lB_]իW)p`ٳ'ӁJ?AM ƍYE#CdL/ E ̦\TVV͟;3SMx1}Kc^hɱc"""=%3ggccmm=XȄtw <[4CEF//x_Ќ@ѿSQ/#aZ RNZL/Xm2Ierd4;RhP,Yj0"^~TI4}:;oo; A*H719|k.|㔔_5PSxӧot)P'N8ȊLKKsQ| (7HYV:fflU㳊DWwwV5/ahE?$wp .kEڟY(z™$$$@駟oߞ5C O˙Y(֒.ٶѣG 惠??JNB$BL,T"BtT&%2"P .@wmvlQ)$"Me RjJ!WXT t<ѨVASXTt-LFѤnWYyEVV;L-ЕJJJ@NF]]]BYvt*bEy eVTVerVӪJb0|H$Ry` f3u;i_V.os^> +,\K/yw]{v?~!c\+SRޙ9CK.Ϛq83{>\ji4bb1Z__:!ZVQQ)IY}._FzZmmb)PPcݍ L23ZB gT B\LJɑ:&6pUa lAܫ+*Hox~^ߢVmy|qw^~x>Ӽ|̍}͝5{;k-S͙7ZKY68pAQ]][]UV*'zք"k[PZR`RhD* Q˵ENU#h+drG \aq,V$ jVbhÌ|oCfZZ*!&:VC1-].WNryri͖־};UiiihH<#y>{EMfEFFMEJ3;T5cV;RĪCXhj mɾӀ@8/ұcozOiǶmVZI[*}ЩK-_~9b?Z'k?K>44Tj}{o{zb[•;E:8H5[9wE.pNMEĐ EGp*X$TBci^w9ٳ~E! Io41%1߻rF ݱ'{,W]ϲq `%^WW_"ZfE pJ>Ӆ};JДrs΅ D(O&Q)ZLVjU*N SR!cnr[T& ݄ԠS4;A R ~8]xd?mCjʁ\zE8^H$u\@h*7AVYKBhnQ4'7v''&!Zo_Rz￳ڷxgB$ E~^~vbMHj;""'Ch ccTTTiY\. Z,an(V"I xJo:gol죏qA>/]8elə!U ;s9@1`|@* of"@dN$V*ػ2] y>g֬cGvwޑ$صk7pR٫p<|p dK-Ϛ:xpْV;N|ץl=3.wEO>{ƛ@#yZ?V}OtdfY̲wǟg>w&5^._{n,نw/`~e/HdT~q|ec˖Ν3 V8p $\NL .U\rm轩Y(;HʌPX(PP'RP!e"BTt J:CK*&Ue"LxmZIFFk4]{Y֓Nk4*^_WWWo.;7//2*t55p,+f Y][vz!PYZV贗vrT(tR6!TSChJLL"t:kjjuu5hcxqVj B*jT__PJVjjj2 PeSEH/N䷮n8 t7^*Fp˫K>hz|yRD<7O$qZ +m6[mmmbbbX(1Fа0 D/Ġ#Brrb\!q B?aes!DT``$3 EYY.D'aH 44J4 *wfG:F !Gǻ&x~zrу2Bce[=44q#$Iod#)fY̲(jSܱckǁ]CG5|Dm,)^Lk}nC ZBn^Pq8To,]`NM mB@&ʥL\&K\L"A-lT:H!F\6`*O Իn]8]nPeLOOۨRh2huj55EKPBp8z=<ۥP(vpg4"a\lLĄZR Zm6=?22tМj[o=WRZ J)* HɄ-hA;6w?|ki۸*ZT*P"Ο)7萜s9iY&3C+]#cذӧNϜ1c fߞ2!;tO'2;=5)cMjDhh-BY.  s.dfd$UUUT0 S\__#թSaP*:juԜT$VCV0[,١};TBZ+!"»zk5+;dO;=3~Qt_@HZzz.rˈa5|n-3Cǎ~m+V|q;z\v͚ fEGI|e1u׀zvEg TjUNڼeQ[6 %hB_s.sg TS)x>"gݻeIBt:<D!gϖ "gnp-jٌFh&)JQ·/\@}zӋxbL6DZKh"7&R]7]*Κ3;xÇ:dС/>s/<ꫯ. O 1C&Lp!SFFF^{K̟;w7~7߂5f̞],wyol6պv:ro{y„~ڵ4u_4jάrɧ?Y bG%R1вɹ ~IY|g6Џo m2T*bso~N euudGFU J%\`\Ni#'<[_I|l6[v{XXbN55V02H0 !j-b\H t6K' f/-dV=n"~`8[OjbFIbʇ{ӆZI])`NNJ_Y`Hu)njfMMMl\,p8(DV E@ bJYyŀhHT3B!OrrcccuZ-jkk|%%FDH+MHmJj&}DDF.ya3fHttFβ:x'Ch<8ly;Q,frֲel1ok-&&z{ ݧ϶?C1 x4Z"3-3ge=Ç{Cgnlٲu;vpȑϟ>} /'͛7'3g+**6ok/.>SӕeNYRR2nܸ'$$lذ[na[ӢG}tgdd@qcƌ9y5k~veW֛g繲P՜O /1M_taeVbqTe5 S8~p_Tv,5&Qӛ_~=șƔoСêUK TYǬ23:we˖|_Uѣ'7nLIIa iMlE_7"Mx*hmu.EKrY?at9'R@P %BR(R! I.nY'&ߺ C$ݞT*"O92)(9?: BCB2O<%@FѨQe2YyEŹshQqizz*(|NQpzoҲ]CCBufH ~"hBPV5ц.`fBV':&,\MP4Fm2AV$h0lڷœτε^^ 7t1 z.ەJpc(NUF(IbZ<(+\(9 95ZL Nf1:NHD3X(NÁ6F+qc)oMzzקT~РAv={6oۆ{8p&LpsAC=~1 ہ2i8>K\+* k`#+O3a?)ر?~?K/{;w\`k,tM [ܠNg<2/` WrߒͿ0k~8sx{͞nMxGgQ9rSG): uɡ,}Ufb60M6͘1?& ҡC-Z ![f̐VV]pc[{9@SrJyn ŠQt^r ZPmD`|7"][$Bm2:2 (ۂ:RUU\ڛ`@u#kj C l6+e ǐ6krRRm]]}AFӤUUqqzL&EKnd,.)KIJp MDbv5cx02"awqi25U* u'nݺ$\.hzp`3-5leehƭ7*+t:-0ӋM7ޕ3NL6YS[+HRI^^{Z RYUIlX__YAX O= $&&ƷhrP!Q0JB0v$'2VPXyhPn3a/,iqPVqqIuuMǎUb& uZS/t2d6H{7B,499n B![;4Pew"eDEEB|ߞrvAtY,ѽ  V$'%&7kH*jc5 ac~AaTdDhh(PM]ٳ@ h&p-M`ϊ\X(Râ͓ 8r: l}ܜkb3{ 'MD)k=]}j&N^]͒VYVr a Ah9dEE%MՀ|B@,Ϝ= 3&:e .JFTUWJ"<\1/\(*ѣ tdfd@>~~PcsжAh6}7?}􉍍m׮_|?^˃m0~`z ?Tquڏ9ρ"ʆ>;[;;|sϔ'8pࡇz7{TUmڴ ݻ﷧|2X`Zs[+r_7(Ƚm^ rXX(AaNRD5/ hp R(|!6&EľǸ4`*N;z$Jk'7+| l DDBHMԔ$ݻUS[FmAL<,###`_q<\nbCfR7hnI3UkXqqqP(8s} / EE:v,(^h5550`46Gwâ %݅i@4J"IE&Na.Vj^4EbHD[d"U,%%%blx0 }]tI/^ܻwo\d?OSNeggǿks^r3g!̴ B;w ~loۧNzu>>8ppmNR(c}JH웉[ρcǎݺulҥڵk8W=s2=^ϸ>m$'Y(k넅!"W_o0Jd!t rm"HRFT*}U(TN.+/³ ȷX JpEHѪ"1O%'% ]bZ|,Iy XkKM58}/aھb7QQgU"4e$nǡ_spqO02\CpB[ V7A]i t8MNTJ/.. 8h+fmmmxxNhUjm07@|`{@XT(<^.nVk%ȽժR*1U0\Td^ WBww` _͞?rA\Sr֮4e{6wlg6`AZbN;=F> @7 3B$*eY !|ly &C[`yeE9sJJ<臫?8?>6۶~ʌ*ml%<ƀpE0+'@ˎ;֔IВhb g}Ekʁ&ls-z))~7+Ihj|RT$Nccc}MMMĸN(Ѽr"ˬLLLp:,# *џZr\0v 2ul6kVJ4:b҇!<,,jAA--<33.ҵk|Cq[ꁡC-XpС˖-YZ+Vե?䓽n=/7o;ߵo>iۧo_!p&]jyx8^n-|W̝7\*20Ka-`^x_-YɺuEւ:6}[[e:sA.?o޸g4y2hc?lF7m), zc+oqAx؟}6{!!ZT-tP_j*Ixv)#x^F"zz\${} ydQ\7 Hg&}'c *B$?|20J <# Njۿv15Cw7>ꞷ|d/6~b[ph?ւpG=b$k}+KKnV;g?h5z4kC?vld2SF`mvv/YÏ?&>gpSJ!TU|]1L ؊6=rtҔ)Rt葛(9޻7Ab"PE_>A (ɓry~w_V 4R~"2r穅F BGBKɖL:GZzzlSM/R%|δ|ğ^f֮"A=Ƞʁx\hkq}6M,)&Dh j;QI -\C'MwM4I(=j=8oޫW@[ǷhN[ ٭`w`~TL, L B%D8.{뾻w=̰;;wΝ2mZ5ih7oiSƃ 8}m/Tf̚Kۑ۸cl|!vWn؈aaٳKtj)nݜ3̩~ }fol 󉓧\u&ľ%:ўtrOt!͹K‰bohd7Ox ɧt |%TqϱP-ϟq5;•Ke+A&aAO_ou6{VݷVs)HbC z7m|MZ v./ ]vAF=zꡣ(--͠u!|jU0'l YN׵@U-cxTKp Sy+S{wBP]XY‰EJ{K>ce|%TuBUUUVw,xB9P7p&3EVgff |MM͗^9ܬrss7055%$XY!e0Y$%%~NJvrr|(ڠo߾O755*`hhl%)9PCCٰl )OYY7oc 4YEE؝҄BUUUxEEE*\.C&S~~j_Rʪ=yݭy?rllLzx DSWjz 6U^rmƌ1Um@FBRGUhrLh ,LVvvzOYlV6*&"}i`дiSTёl,ًő`cŒLha$>l^H["Iimmlb!)@6ܬLQUJII d!''gddD m;ZZZ-}-4DXxJ |&;c Re2rAb1D+(pddJW?ΟHNNgTss3lݓ?6m@lvaa!RtCFffx?4n&6V/_dd\nnn.HO"C~DkuVuuuT^,B)\2uᰑ;k XYY~IM ~ʠ޿|$5UՆ ;D؊4POЫвc::*j ]{rv&rkl  jb ۠w;R},& i6*"d!%//odh '/"ATIYY]MJ$,VQQQATPP$}Kg))aC_~zvj()9YWWt?Nx捉 9GF}8Eݡ#QZZZ|i`HOXkq@M 43&]ۡ^q1_ia<:KVw8gIK*C CSS555$ؐDH!񖝕UV*..PQUEV>?|PSUC ŻsrrDώōlmm97b!'''@(##IIHBfff~YUE --M[[;??_ 7$n?|`dddnn^V'h ;0Y,(K$*9*eX Mk Y5r666M)?R\+++0Qx^ݺI͛rrH:6wm; 77'aÆobcu\.9G"63++?/_SS)al%EIY$bJ,$iձ/,6LLh;f=@U] )Z5 2 = yyy H@^zNd mKy9OIɪ** {~,,,mD<噛cfft,YYZwANN/("騨ftnܼ%#IIImY YXPA}u5bǏ***_}CRVCC ?_VVuKhCP@Šv,TuU@5ӭS/dvQ=墦N</%5UA^6 յYAAoH r~((,LOOWSSCZL\\(.? UU _ $v\VH *+)pE"ek~Ahkaa!R?~(++ӆ1yldh5勪*rnaarՅȠ^PV0=SG] /?`۹icN<6N/҅Xtr[UE5k?IX3bf?m++ /]?~v,][72y>[XWe`& *+ HDT;9PMjz-TB-.R$j2<^1K4[D8 */g12pEKS?6^*//x*\TVK#''GU}HӢJd)/'' K "ct)RYYFFHXnp&E.\d!3`Q,\/MXX:"kR~D1aH(P@y|ч-DT(G[F-I5U&A/kvhش㉎Mr^> uӲ==l D.sKDNٴYDt.H"fFĵ BDݹfj1658yׯ_ľycnn;w7o7oIWQQ1jdN:wk#6o KmC11:]qԔ :G zŌ 8KZ䁃oߺܥ{&YR#JF{qtSHzo&Giiio߽Ζ"i,'.>AAȠAdľUUU0^rd%ݖi(z`RCC.fai@EU~h0qNN( |(y%-77 jRс @}ABڻ|~/,VzLvI߲`kl">-IU'r]O$2ވ$iYLH"]׵a?_7ʁĭ,&۵okmeW׻cSȧ #4,QOC=#ȩJu=s{i E1\za\&obFH"jM3}XhP~>CseBjKK)7fJJ7H: a6GOwgܿmf?sb\ݚcW?;GA^UK_VNνOH9O? 7FE}8t 9ɒ!m$W{XXXEjjjbbb^~*RYE Ǐ9zEEYYY_}UVVFϟ8; I=[//E_Z?EöUT7 4YW#Hi}]o hp|@᝜deظGɷbJfb"S3{"Qb \w8jf?񋆵BP{Gr4Aiws3. z6¡e9?=̅XC+;W)+D*^/h[ͫ׫Wիnlђ.^9hijS.>pģ[r4eǠ6R|g%7qT*YYHzi~MMb,-QCb?4bbzX[IVIMҀbd־p09~\#-T;{ΑW%2! JB-t.9,3zj1^ѩO+[VLs_s"LtՊy$<o>Ƴ<mw$;!u5U.?P6iIOte__v/Ӷ@BX3r~3qܸCtw6qHj(jEav^"ˎdtJ2[N>$PH|w3QnUvhbh絁wjjtOna_+;u]\-Qb#@6ƚ2^~uq\bP ~Fn= -UM1sCky wiSCz{444`1jyY,vA"]1fi낂?``h8{i+LuN57iի/].@ccc6$KZo~%Otw5"BBz:9C1 n~EM]]VN@P2k,ʕ6ڢt*A"լY}<܉g/fj)[=|ාo7;wX}f5Y]]حզQ=ޮןc:_41[Ɛ, or;"5c;HcPŻDWX%Ra'vpBٱ3qJVqhbp65.u-[v#sT@53>R$U v]GZW&;|@d "L6۰R|6kc;BB؋^}z.4]D{VbAuN5Z!]rZR%{EJبP̑YfddeWsBIuf'G83 s.+,{ƻط;߿kʴ/_$'L5Sd%wWX>wK]_ٴ5 գ_&Ǟ-Lmr⥌y Y WWc'LDMguu4M,d{0vG,1ڳK $ѤSq嶝5uSknJJPO_T9I&9TrZK--ۇc\ÇC1Djя;u,yP@`͘u{ȹu*J=(yr &7oYe[8Cdέ[3}˜mKѺ]w 0xb̩fԃP#͡b@E$#^/9 Q*`(Tf骋 Imqi"@Ur[rf tYYYruU+VJ\ɱAn 㪪KH|󢨨~-*R޿{IPhDE z%b (Զm ڧP>`|_C25EKgɓcY{Y95f}naszxضk#ItmPg#߈{/J-`<{l9 r -[d-,6ѳm50ڴnjnߙ2}Z Hx9tU j2^ tUVVraϟE]%Zk.4±PRVw\a#o`h@[&Ąsk,,-7p1F:*꼇7_\ǧkRv|.ݺ;8hjjK8618WM-]]YǏ83Af08t8Ī8Z. ~Bk<++KF[FA'&YY8# &|Uc$\]L칣S#TgvJѣ|܇vaTThcGjӮʥ˰M)&>qb0ar˗.PO7~А! TPPܷoZpΏen;?&l^2ѡÉDG?gv@d[N[mdѢ1csH2iv4(B"ThF.xl],YMPwCxiS۽Hn"HR3|pI#A*WedTNb=]vC <ѣG7ok{M2u̸L \pm߹sgHiSB%L=. 360`fݻt={͖NN>q4ՈQ_ L=PdL:m]pСs~ӻqBiKԳU];vm׎ɌzAj<:@0r0y#eU^ p7M2VDJR翺c7:dOo)]H>8bȠ* ٹjϕ 'H &Z1(()m߳44ag.^k~_ɱt6T6a@ޫGb֮:ѳ'z`}zzm]5AA{bHӖکӧpWX=ɌzAj<:@U(KBBAxDJDHI:IГ0HWTܞ֌!&U gqKw)n`4"JS&Pf8!Z ŕ@ŭ((%u~q_В*^ 62F u8'aȑ\Y=26X5)8N$lL)1 >3EcIL\ vwD?.GFU!YT/e?Ŏ.nXhypLB O&`C?NGE|>LLZAرʻޫA$_T=%1m1 u mnw8tp}63r "=QɈJa* u۽W zh4)0K0bT &%Ap8&!\XX(D@ CzBE$o0˫!9LQAE?-@w= yC4+rw1-L& Uğ}aFaojPE>P`B+k&Dl$/R{-"Ώ7X5}).-5/7$)01BSC@$(-0 5*Fo9* "ņB%]HW+ZOkFJr[i[UC'.Tq͙,Qbc&$, Kn (_%j*x~ݒwD<f]Ժh@@->ȕl 6JVʁ]pݻk5~l=*/*RqLef1u{lb{al8dz_gbH=k0 Ig) DʳOsup~i>+.[|YE[Ԏ* s(FVxZiJ _3wyeלN蹘'x|oИ_I@2¤&^Cc|>+N*#mu*̈́|Ҽp.f"%MN)=< s2'*TwU3:4ZTFd䃨{VV={bƥ AgT_ ~.0 M,9hqc]%66vۖx#w8m⤹ fggϘPƔ0 ܎Eg˥&m P-B"@; Z]3)lu' PѣFKH+efEIId o &+<~4tU6suEe݈CnN.iպKr-SIˑF06۫G;xo<9Y>}>p䰯O?W߾ .7~BXNLҫML$f<{;Vɽ]r,g+0{ֆt~Μ tvMZ?5?![Jӝ|>;ŗde>DO Q%cj'vN/K >!<8gi ]=9'Og{xzy_Y<"ݷ/:m%t9 3r0qŝTTT 2k*4MomLw7cST ^sI‚3oc/uINw)kNL2U]>vd,YiaA&MHIGwk"< : _ROO5vW0,vל`1ŒC WwDۡMcJ ):6z6MDp5<t+,9gvfq w^8#v,TrzBfˬe1DUټuw 4KvvvgNak ! |PxjeثŝɷbiFڠ?VnO%y`mvaB7W, u]fded?0aHw2A$n)0 Bj,N;ާoǏ'l6 1G2Wtdooر8z֞~z:|@+W;:9!Qw]fkzL{}MˎNhCgp5:6v9-3woS4Wngg">-IU'ratq={v>imMŅAfy.몮@K%%=Oiy玔؁>Ѯ-y}%[cd,u YZjgG_pPQoeTv!9ȁ+`S0xձ3| o{Vnz*Z=1Wfd4B%qk˭]= 7J}VwhԨ\ݼN ׻sI[dBπ7Gb-iY$ۼOpg$ Uh^~FKO]?K-wvu ngwOjmŧ2+cJRn]HK?]̈P$Wi|TK jOߧvhbLH͜vi6:PJ$(;,RK^ȕk~@pVݽ+]2lemKN5ݲm+mVG+SO+I~0Kj[ 542g|٪%zFT BwHIʰur2S3{" 8oQ3]_47I+ǴUA*Bg{af;9!mMyty$2,o1d8(Jh+G䟶w֌  󏅻|+d-QQ Aۅ ;7Rg.Zѐp}H}e8ewޛHV4l!YEЏo!&Ǩ*Y\6'WIrk Bq^Xx/0HM@\x@h5BP]XHy"B|z :Ybh~C+Ɔ:5Hϵ+hXP?lSaVT{& =FƛO<,'gkEl?@7_Ҷb%JL}IOnA^%g!U)4V>Cdේ+չ Aۅ 5U.?Pt1T_-!5T} ~uVWegJjUOUh)AM3@JBkEǪ;I v,>rduP `zGYH++-q-)qdfR^6̤$bc Y5zČxgNXe&v^^xCKDLjZcwvӖacz$apجO;([N8jk3/7+wdŧ][r A=u;vܞxst&+TQ= # ]\-Ŏ6w1hG'tu~x<@m>ȕF(_Ck ̒gA@jo^}Nr2*LHIy={SbHy|E8϶cSguu"5?=w_xbRpJoϱX]/_0֌0GĞUr2YzQBs=7LH#y>gP;FDǭnZM z.}]&j'6ofcw= 8ki~ЅC,m^>-$[v#s~Ww)]}*qى$M*%LfBk e!~ a@+t-$}4V{99čtTzTۃX3z`z)_;˳!z6z(bl[3}uj:jJC@Ke28a3}ژ5xڅ d!KMlwlE//cR&Pg5vဪjofJfF.BM[ÑpT",ȭ_ۜ*D?`˵5PC KU#I,Ar $']Pez9#W ]LƯTrc`J-wЊ(IRF{oa~c~%Ľ~gV˛1IO>/ѼB!U)?yu٘m_ĥO+elė;Sq]S7E T;Rв/XT+` ZYvE+?"~nf'/.,]~KFo4ͷñXܾ > RP*]1#&[lD@E'}}~~>P)aAz7: S_}5GI^wKKq=cC WnXj,Z~ UoYy>q2geuw7?t=0qwjDHk&*븲D_|ճ紜Ујb1xDWHF6.\5ſo46mW~VM^zڋt7;菨R+7op3,"^/#؋h`BrDQ53oW|8жmDʳVJbHİB*((8:9gP@-`@T5Fޤ~#Թ Bz~<}'zh&cḞo1UbVn!$Ƨd X76Ozo7Q!W"]]Me8l|8ж‘!$~xڐj"^YB؋ `Ww,@?id`ihΕ]+ $vFO=#25WQP$c.TUZ+)$)(*5Z#@Jx&#Pb'u&Sj,^F\uܑw fuw7f"/>q}+jW{zyryAM1˦6:>ֹ!G%oSMt#Oަm;>Cid)P}+qGXw;^LU(ݽljghkGEWW/?]S]H`jT IOG0YnNvW;Ͻ@h\Fš6ο񺮺X/RKE@[TW[1DT^ o?\^ub:Ւz5.*^>d 42meiZ2Ͷo?O:!5FBP3?Akuu*XExg^g2J0oKnv5iԗ\Ĉd-.NlP2Iq;X#/wp1^:[il46LW!m@4Y\ w$Ć`*7Q/S5hã/bb]J =෱fff}gzƾ 1]3&O`nӦmݽ]z ~Tidb2vx;{{R}Y8utPÓ;Dm: Z[K+Y~::[6l|ⅎtYVV.Hp T%gu|F.̜ϕx°amd cRgF8?׬\ gNZiQq7 9~L=3*o mяmܴ*`-ZnZ:xfnn -jۡC6n޸9`7#C QTQRQ[ ]\ݷgQI_\o=OUUXIPe;ubko1eDT̽}fQa%a= c߼ ٻjNgLG/M OBTc 1],諙'b nD8WU(Ck  @u`db)<׶v:ŕݹSr굈+~\KKk]xe6wmݓIw%@6}?*ʾCH@8cj<8rekV E OB"}x-6[L/OKw{=Lf_$-J[\}zvs/2Gocܺ51!1m}{x4(̌L߹s\ݿ 8y4j:5</8u;ntW;_%~2U^SEk? v_xv@;b]@eƕ3 *Bל Ǟ 0̕^qŖmTS?ueAC6k~H Xn}qp@'vltXpج]v}٧X#ؚi|^ة}vjlkG}'zv&w BY|M꧴bMȦ6l0R}pX]\M.k٨:Wn{6bUoBWtҹ J$A6L&<Ӯw"!f-fЄ\EqQ_b%щ,e^FMVDkO?_y1;Dcxg[e RԔvlhoAr |9{/kf6f@[ZbN9ra, )rtH<44Q(N|\KKkϮ]l7>qh6m֬Z)eV>vifrQ*iek?y{}ry 6M* g#*)kpNG폈ác,]<{H[n>|=y'nd[=^ʰM5գ&~tR5WD^|~]> x<._ltsЭx\#jiS&/a`;g_ mRJ`Ő#V,Zbw+& {پ}}zP{^^|`:i됎h뫩uՕ] 5ط6}kjf6},lv>y ߼~pμkRٹ׽jlmj1Wlڀuw޽g4CB\|hH ٺzzL?AvvvYS90Jp?[V>bMB&'x{XDLf-=`׹I9;;>_ɰ7~u凟Й@ #RM O߉J)ʕou۟Mf |V;]!z`vM[չs5^i*a[xyߨztGܽWOӸVV\v-|`rgEE7w~s @xx~ئ {X=?T63r &11ZFݺk3)((TvV)~ pV*Bޢbnb˘Ml6B*eM}|=x0n=;w=;LA_=ow>cGĚ4mLxӸ>33s6Mz#/+ݻϘa&VV/_РȫW'LL~&Tvnj"f7X0~xNdFd3EwS:iq,T?˧͘x7ݧ5:55p??z!NL&C+?SC%K-` ʩg2Z;_qxE} Lbv?֬vhq雑fL13&FGP?oK Zא+GQR]F400grr.M[Z2_,]xfggSr8w%y,oߺYXTy.'~LfxLf6f"ST,h[N Y60T-[]O$Bt%6Kδ5.ZSWB=6P2Er5o-d>yj5G@σ WTT =zڑI=??#H/srrV/_zeTs:ͨ=|ft|rG;085v^ῦ`hˆSS_2eY7Sv-J`,Yzܼa}~0vvgNݷmQT ܽcGw$mvʱ'F^{j,^XlzfQSu`c $1/ƍ/W-[rp8C܍4߶>}Bp5+̈́y~dg;s.+3k~3g{`hJwUܤP)4qcSsh#^+q#z!1[{a"5e]zҭ8Z "18i;BF!\GxHgffeg egP3so)AQA h(C&k0^Y-2 [x<*8P7 + |,j z&&N:UԈ+rȰm3*o?3r \xm<2u!Po*x{ñ^9?}jW̜Ey9K+\rӳ :K.wt{ 9|hG#70 ďb0 X(T[6l4dpV,YT(dXÞ>y.0c;mwvv^bɣǤt Qkh0t!gsFj\9.owFZI_s7Th3Gi^nc8[iȕ&w˄.Y4فk էq:?ɽ͚xW[Z MZWy_!Pi8XŖ@@5 oݺ!77ٻC/_=>s?vpEx)y\:VfJ49*qi9yŨ-'c ː6DW2FuaXiy9RCS;0&He72D"LJUG; S F*8ҳv]:ihh̘4{pdewdB%}IK[+̀q-el +l@MYv?P*@ت@ < *U4sB!@ d8w<8Va8ZnewP@ <= @ @ P@ <=O @ U lU @S7FRV@ jx^U(@ @@ @ @ T@ yz@ @ @ B1{&@ ت@ Tϧ M@ U@ *O @ RXxBY0 EQDR(b+P ES( & ETY>B\ !u.~ 2 00e0gXYAAad>Y[+a2R41j ӧOܹu?8z^ԩӴiӠ <O06IJv*qkR@:8.8(B+]'Z̔hD|OKLqlAxPk֌xS&N]Ou֫__R24 0 -+y88ܖ?KSP D?%n{T{R$!HԵP cǎxak-8p`TT8j׋h+w9s۷oGGGoذQFBȟ~O?uV͚5ΝۧOG3&Nhٵk{ ]r%H8@f,[lի%!!!7on׮fnzƍ y<*@ Z%R,e2gOyƉNhTɄ\(n.YT.^pJ%s环_۶׷VZ2ff'*p ZNM+PԶ,BsV Me˖/6mүgϞ=z3go`իW{]Rl4h RgT_߿8qЖ-[&L {677_~׮]YرPP@  ]ƖlXydݿwo}i))wZMqdE?ͩ.Qc IskYNJ?gşzo=_` ͍eə&EKY`+Q \.*̵E*W-[޾}w}w999$եK([%]@-YM6͝;Q?~lݣG# [n[|| WXcƌYtI6l`7|Ӽys]zu ஐ١V T@ #0+4@Ra3w$o];-"fR݃,.tFy $ * *]''/Y /9%g$셥 oޒa(8 (})OD-ZK V(N[ƀӧOxbOll\. _v+_[߾}3LsHPwww |HII[a_E`ok.A^rl?#Z=uTG:p@`2d0EQeکٞ`k* y@ @ R"c #Xå3֭`\ LKuF7*/Oѹ+1(6X&(@*2hP$%ت-s?ZIg~?"eJgeKz4Y߅u^z_V ]fDwF`+Jm=m333G{_t:k\`B!rJ iRJVTe]lah4r @  "b,k4޺Ӄ|UR k0Y5xXhKV.S:VKqKhebC3@>Ɲh޴~HhJ'B}J?)(pp>DK4BmT$I L[Lfur`=fΜ阈qԩS/_h"SZ]XXrٳv޽{ܹ-Z4j̙3SLYzy*\T̶X)}P@ j #,C/]OM`."Q\j#BYB#( "'0wPF"=9 R`-LS5kQKl\e[[י(7"EJFu.Ȃ(M6=~oKɓ'Gnb[5k?>/// @w…ӦMΝ;@ [x\k~rY@ eU(@ Hb֓t񫯾tɬ)V9 +S(!Rt~x`ooo"-Q 3ͦ`qh*Te-EئeVfFb<0$MJ/19B-E/X&/l4"(Z? 1 EJG|eޱpEq%X`q㞵%>|xʕ H5P@ 7X,9EEǏKIN3hѤ af5@bRhhl2ڴkY%RJ~VBN[Rƌ:3!1Y,Gٚ7g)R)XOİ%#my5 rtEnSNbܡJlڵkrr򳶢7o>k B!@w/*6[A1B!,)Za>I ' 70!՛!AP2(8$ڶBLc@mFS2 'p9B39ʄc+H_(RٵCJC!jBJɓ^ӣ.]~mp( 'U*NJ&I:6`?ewH@HOtJjڼEK<o_G^qðij'jNkؼ5xH\fVV@}iJ*:<÷0GԫAͨ$ -\B.-\Nߺ対r?MˆjZ7o~aQ䑾Pd!~Z[YkwDzv˩*t3_ yS|UTt/7s_#vn߁Gs4ʗKӷǎ=4AjWG/&$vڥIt#Bխ$(?\֩}v[/ 6zy|E`@w J-%ɸwq 3nx-ڱ$ɁGIOЯO>Y#rwʪJ))#r{{7,eQVY̟MJMszpm)U^dw7];S0|̻7o Έ7pHEsz˵A۱_Q"?li8M4v='Ԟ;CAEysscQ WYp9G`r[2۽~Z|mF%,8M%hn6Y#6hZ#\J)SuEM Rkd0+ëЇ=h\>@' @TSڭK ؞;l6q(7n1/q[L"h"/RaaѪ5k_nԈϰ=tWwUG-ײy3B7j(۱/_|jKǎy{.7ZjhG⏕օZ6lڵ+{{x0inWNV˽=mX*xճǯyD4Norc!Oྮ"oLhHh.]nqΠ+wE (nxj"$+yMGdnOWrrpp*ЬrNA,CCH? c Т(c hjyIHyу k,At-H*yR] RTT,&zPV#۳6N`.W˚2y/@2(!tw\w60u]В5A=^b\$FШ(ZC!ȿjBwnny>b, :<>|nPe E]:3m+̦֣[xohB7}1 ؁{:sVx9Ա -˼4ڍ׭zXKcKA&'O)+)G*w ȵ*~{ذNnn^_ m>^^V-ö?|~G@b8ݺ>ڭ=Zyn8R6@ DU۳܆,jUnqn|YI4Norc HYN4] vZJAWZ \{MGI@ xWrܓE"|vסf9-=HϯRX΍RTTy(T^T)!$RL Yo!%R"1R!AY#n*p CY %%le(Ei\n )i|}il5͒-H -1nٲeow :}ttte <Ǎ ><55u.]ڸq 6ƂE͘1$FΚ5 %ڵ HMNW\پ}{k\fc)"322F lѢ(Ϟ=ۼy۷ GlzV/_y愄Ljvy9hذu&''miWYZ(ϝ!P$eHUQMU(zwߘ/NU>KkLgr??ٳX: :oa*Z]\\1n`A @+:s;z-JA#t iJ> lben䈔 QJ.Cݤ*f!2'0@-""ʘp cbPlMYOH1VA!&mڶm/ GoGN>].b#GX{$5)>>~͚5;vʧ_ ͝;w*)S̝;Wַo_ DDٳ'ABV@HLfN-Z4k֬{E8|0*믿(#K?1W_ʊhcz TP@[)Y8sHrIRUTSǞ_7#x"LdUk|&{ÉW6:x[yrِoQͦo ! ܤ<LA @K3f0ܒ(-B)53٠tSyIU LN j J8EQfA#F,#@bTN[()QT%Tfdsƅ#atЇp]Ͳ=hc ޯ3wl'?;5OSgζn٢cG]TPX́ӧLe6~<̹$T  gȵ*r{۰84Xۭ5œi4DU}]ىȠ3`'mݴ-?/?#q2pöh!ܜF<۱*U# P={tOHSbW=5>}{{4qW >;ʅE1 fa~; H:YԄzJYv'e*SBq-AfIiFT84nb@,B`2!58J2zxjh6ae?OCp ,.Zj'°pvR+V̞={7nY7STr:' ]FȔZ!*졑(˝B RF2q8cR9*YX#c],nhE)rٻwoY!u̮;w|]Qֺz6W Un$jT@ )YRDTrɦMyuGԈ ͌.AvOfM<̜Ԕk(!HFr/C0\%ǤĤ%% )6[L&GJ5Kr)u~ S=ʶ)r] #G6"##SSS|͛o߾=,, Y|͛R{iǒϟ?|]:u?~]vGرcrr /p=D"tQΚ5kÆ YYY@;-- 蜛7o~|vAX;EQ3fAǬ/]qFk\O}fdd5*>>>44֢E b1yCiڕB;Zk}D?E5"a7n8po6'NأG)S̝;T /^ t1wСC .8~hFO ȑ#~+ckW J \NͳDs}=رcUkcJ<*4)gmy y@9g0CBpo4iTP jD.U1 BXGhGMfp` a]K{7FհA"GY,X Em~IWLK, &<·v} !`wT%f܇^s@^v/^d2 ¼6V||5kvq…C:f$ͰaÀ ݽ{իccc-[¬_6"bbÇo۶M.Ϸ˥o߾@&''ٓ !c@ܹSTf쀚UTx\kW J ,byIfsfl#ڙs8 $H֠24]b:α< փ|||NC*T@ dYnNe* *)*((7(RKय़e-^B L"U#rT)f_ibEf^fVj.!qRXbޭԻ޾1_R+̤\fuƂ*j\M`$7*CiȉBJ飋Vxkvo~ٲeӦM[dI۶m,=<<._ܤII&/ivVM8)1cx.111K.2db,\pРAz۱cGԩS8_vͱֺ)iYR":g{+V̞={7nE'd W4wTJ}&%׍^j5lYGy{a 5)H$ &06vK ,s=0N}D"nݺmڴٲe ]z56oٲ֭[w9 h4.XK.8m۶G*Q1,o߾{̙ x)V\onذ/8s+wzժUo֏?lݺH\ ڇ FEE qi+x XhQ  EڵwQW(0DB!@5(@qWho (M 70R5twSosjju%=Շԭ]Q(l6IG)EӺ R&]YMFEH~cݭQƩSJ9..NpLܱ2)+uNVfM쌱5Ipt,w}W^=9 :vӌaaaŴUY9B$5Zq46@ddǟN)@ @ Ra HUnjo ~?/HR bB/d*-#! B!y9t(nW_|V .}, _Yj'(7w*n"jRfv^QM{FzDf޾ %1&%MCȲo3wSu{"4Ak7^qrz "kE 8L(p\P cSU(x_tb(ҳ+S&O""t̛;{Ѣ%_X);k}"""~+V|믿3,ӧLqܶmڴjۿ"S#).+/[ĤK .vNэ͞Y{Z6λt 1yr +_)]";'޻1oՌ]5Z,eWٳ`4F7O{ekж kW_}qiSoMaْś~2u`MW^ ;{goN߸i P;(jRgx)pZ4/b{BU?y;G7PNpIc-c!!NW"Pg2iŋ ?8^y9?oڤON8)Otyŗ_]r['z #5`x~A%KbS&{ilVV֠!\xq_^zM*n 0BѼiٳgzzxThhT_LLLBQIƱL8 %<4+O? vaa0lAK68HJ.WFF>q_/7Ta8 2ԆLpzHi Xt:9ȄK T'd)U3\B\ePgs݊@' o~w0k֧$A3F=~{va+‡bg~>̝١}iۏ>Dx[yӦ~>F3f}6׷iv9:5R$/[Sݿߞ333G3eGl}aCׯ[|#?lV׮1JY K})?kOߪkmB֊5pАimQ#=%&e{")qҤ#G/ZpӼ/C!sۥuͱ۷m:h/v=*Z|s=gΝuŌ(V[пYw r꒥Νx,XxF6fp-//%]^qW_8j={|0AP*uOۤYw_1w_h&H_2uzփo7|#'aӷk֬={/=z7o9Аv ^ߟa#ؾ5"" 8jpmv#< EPaYĉ!% ě޽7j/Se T#(eIjd,Zdk-`!u(AgNJ6EvJ0a) raYNB jB?[}}sg ɽݻQ8A_mо6DX67楗&M ]vM'x9]/-[TUv9:5_++/;o~z b?2p`'V(߇` sǶĤK+V|yi ;i%R{ւ;- n]c^;E*p}\n]J/C 5kζ?xy  kFR*F>Jq7)9-xF箬WOltС޽ޞ*wǜmn޼9{/_6x^/g3)TR(T+g-RsaJʶsrsNJ=rw9wi4z`c`FoyjxK/@y@&TŸYx ð]U{ւ;-e,<!5ӚJov7Nj0zL.s?>8-~8-xF׬ۥK:#ngV3OԌD*zwJz:l?4vx+%X@O018z'ѧpK}_ylI]vń>;w+[L&%p\x+2[S[ LVVM7󫽿ر}Yalt}C 8 +6'x Xnj8M\|$+|Oi8Df-~9MCU"RP 2'R{.j!e#k4P z{4[BC݂B|5jFI!JPd|7]7je0ps䢼enPT'Bq(Z2\G[AЖl]_re t>** \|͛;撑1jԨPB-RSS|͛o߾=,, aÆS5c w"vY@`ОܹScnlKJ@q%%%yyyÆ;(/]qFjVcUa*gyZ==ש]{-7s d7x:txfءP'NpC^Gң 8x;yCq.ZeS#A^6~+aX~}߰Q;*j؈Q͛5]ZkC t ߌ=j߾ܴFjF-@! Fɗ/_AK-G:u*Ў|~1v8CRSR!V#PΛU!6l-*j5[ܴisbbx8Y 4/[sqZrqZj ^v%J8?fb_~5.xSIx{c.XxƍY,_bƌX px_7 :}[iz:A:MNaXv={>߬]- *SP7\8D*Wլ)%V aZz{qߗ ?i Gj$AgV ZsV2uXXnj8M\|$+|Oi8Df-~9Mc*1Z""8ð(D;11 ddwngJJ>ܲAMNo: % #h!kbfɋs wGe 8MYP"| waқDDq_:UԇxgȑcypJNira*0(|!HL#ŮetZc djZx8}kH8n{:~e}:csޏpu螞@[LK~~~.rq-8-~8-xFSN;wĉǬzkF߅J+WoO۷SAUU ]LL'O'=]`U px_yӆ^)A&|Ī۷k4|D=izB󯐧 =4m#Go4_ &tJhWQT >J̈́W:vpS.%mͭcF&.>c=tZ'N/QY_xNөآJƀ.dK@g,88v nm(wK @ k`PRܡHKp@gM](̯covvΰNXɕ+G]DL4; K%]^j\どq 4Y O~ы7ZFTL /7'iN13Pn\$0X ?p!C¸iRb`7~u|e~K= ť H9}4R0f̘y~gjժEQA>6۴i# ˣN<e˖>Hy۶m] ft7 5MPQTN8^2u /3F8<=6m? ;^✾ ̲ƬFFB]cnj87Et^Bb Kyr/ٲYRmҸm?B'|$TF<8_bv=q}Ŭt#3&͟٢EsxOQW$ϔYV& yj?jS5']ԲOn߮7chеkᯯǠwakCCs.{%$+uc5&~R$`c`aݚŊ {uNornl|ujmyi>su$F' \~y4CΜqq&+Zt_ۭ5Aoc5knH3hΜ9K -YҸdL7%DZ“آtD x{8?eaT 2I8+U'֬RMH^qlTv?]j 9AQ=y~#{_~LTr)!X=~Q<{6u%K'+ '4P]HL*4Uh(Q P8J:ѣGߟaH///q~!2qB#N0i6?tcOX9-[ׯv+z;0n||BfɮHw_߄ tjԬc4?j>9)p*_SؑZK AԁU3~a/^hoU\i"s@eQ$YPٳjԨexf 4J*Nw{%$uZ2q4g5;G_B ̉ JءaC]$ɍm98AND%$;(" `'1 2l24 sV⍾aN^}]݊zypgi#P^8R SHJ`o~q^<: j/FGZb!|-ر^z:tSʖ-;uԉ'N4 hӧ;wNl2e-`޼ycǎ=x {n:uϟ?nܸs֪U rkYghytѭ[*W\@#GZiԨٳ;w?4ZWa B"]|Ѯ3F{?veDZ+{IRH7b zldK Tsp2%6x!˲%K[k>Ai3ayOSe,FVO/kU n Lô2-4.ag$%DZ“آtD xLOgp?~@߹|hc.9ewP'j /ޝ}+KK%<|ullQ_@R%JtUSj*$ zy )WݻwǎIf.k]vh4%KܲeF`[nf*QĆ ˖-y)>>>|J[n __~q |Wy& IZiFLPx8sfɍWWn^^lB%GQ=@GY;e((M6L? ¸e}8O"EΞ=+Ni:"ǂ -ZBI7[r ȑ#DGg 5o1B|39a.kGk]agÐA*k ~M3 I_0OX޵MĘ?epVGJ>ӳ7g|\bz%H)VɑS֠cN.wnIq/#Y@ 'Fb*(s #'כNe\߹NQ#)iT n]=E$`hFdqgiS.f>!'Un"_!| OY4 !jVX) @ >%6riQPHh&0I&XN=bSyർz;&*48ntRPNœ87%41H$f0><Te %('$'c dRA$ptai 85LgIC,0kNsVbθ+  R0\lIn _.L3"/B3 oֲ@ n5 #8 ͥ)g ʱ I FФR6bc\H)'%qJTRN9S8KEyG%"c04P H5Hޕz!` 5 Kc`$IzV(aF]2㇇][YZ<:.,Cu(T "M cucMPa:={X5/_n6edk׭[l@ԭ NW׬ [x!l׬]egONq)},_W>ի/[wKCȊa9Y @ ' έqZ>bO(5]JbhkYJ6&G*95RP+R&$uRgS5<,EcE>|jh\UjI8K3FEj4j5(9>^kTC茬A Ȳ,NzƠUdܳg/SXEω($,1/aM|E P)ӌFS5NNYZء?3>Jn==kL[cܙSe:HyG0 Ǐvwx>g 鰜uA A'Ciҳ[oxabtj1NQ T 2rF{B9kܔAvbUJ#i҈S RME9{L2hi(BA1I`5U. FNdYS8Td uFh i) RR$ 'oF/w݋*\ IQ,_E |*ԩ ѹ޳tVV-njIQk$$$l޸ɓf޼q2)Tx3g?}lϮ |IWp;WƯ߸4'$*V?erx2͛7߷/'Rfkа141nNzeP禎jשAi&$dhiݲŠAJZu7mzcԫU}]`Ƿa/cE ܵ 4;/fg:ߘ?wNغu^ZJÆ歅_~+W4bВ%Jbnc~{׎?)l3 ~ B|;i4UL>=G{yE>}uZ]P(۫OkGQ#GL6}o fLle!6o6uJ;u:n͛6H=Љ7W;w\3?AT* d@ĉ~ϜSBg1Çwkw 2KlȰݺںy/Pï ¯]uH O kֆ>= 1~W4b44=|c{F|K0TtRze0+q-zLD[-)WiMKDiԉUN*I cҐ˰ ͒Eq SXWbFT*Wc:3p`BJfmQDP6 w&'D=Û݋ԭTaJi\[Wf޳*Px߾ quum[6]q38xs@VN 73CgOwr|̃aܼ}{ҐK\đh լ#x|dl*U\\aᣐN ǎlݻV{9 EpKjAԂV{vp2 ;: uի9-,]v]@6+>^}B 8-{э?ݺ:;;C>+ ܵ>6nOBܱWb>}z8s[ 2g֚'1!އݩ/?h0[Hڔim͍kޛ4*Pp@D}__Wv&M86?!P^ٻ۷cb2d0sPO֥r:ipޅٍs&W&Oim6O ~ZIPDY,99ƭͼn`0ؿ+t2t|q#Ϝ=lrةS&70wN~k] ߮k׆ϟ2+,/gAoyj*U&)E̤*Tv-_R9bh646/\q6[lXZA+W[hr5ǣ޿Rp^d;ғɜYkFd Ύ`~IZ4o&NI2Mrst   ] >.[R FFFrvL5ѬOxb@ٻMNkޛH#)%nsU;;;exN I?{YI<&6E\xK0Et4cnjڽ'T ctߒ_ u \s/ )S1 ,<5Nbɒ%@,-^CǏSͻ*~xX<|ynK9ڣ[=Ŭ+?ֺZ'>L BӀot#sf!sjբQ#߳/9rd3mPI7IVtj͚oLcYԫ^O`'U^]`>ttrj2Gw5f~OϷPXHKh/eϞ Z(rFʸBƎw7o}q, LY($sOׯWs$LǡIt\_,zH([Om_}d[J3 H=M n΁[̅{5LP8pghó7E-6xjrȣr(4:axy.>Cr|Tb{\%8 jJvUf $h ^$$-w4Ő4c 0Y۾ѠWh&j:BoO*@MN3gz@8 Rinƽ*YKռZN~$%%ݽq-!^ݺBϝ;cv5= v{+W<s{΄[hs~ͽ{Μ9s7wKLJHNL1L):q#ߍ8yihO[cicnS1S:eA]sƛhެڰuዷkEqZqoǞMK)yf%NĖ;vrğkE$m$Ix @>0Zs\H*N[hjMԬ}B׬ YTXLRQnk]ڲe5k>zxbv/_%nԮk'wg$2e16Wc͓' Z`W8Ԧ_AM=wN~K@ukw4fAy(X?o5k޽kV| Eԇ_W *>s& 6d0; +xouW͚N0s$LZ]:/%V`>⎕* 7`p;fN nHr m"L `߂ ӝ> 9-J9%X"6'$$ 4E4F%h@Ȩ(օ5LN ZV'rSʈFeC3$+pfXIL HPd0.dX`4RROKqU%vv Zk ;ўL@'mV-m"{_Yg F||C*TY@U/^4YwiyƎ0kذt֩k3WTQ Rq!+`hlٲs5:vob1}vmMOЖ`d5r=|!~[xꌙsgRen]N蚵p 6L}i٤IT\>Zݲe!ݺu풒K*2og 1jͪf$Ϭ$׉ys0h0t&\'N ?X6FҦM$_ޔ7Uh>snON.\\==ɘʛ7/ N>}ekG-\U>Z4o^6.%hՓOAZ 6>Vƽ7Fh0J7.oG cRxwP(t|ǔNϸ?6.3n=&ӥsoӦ-߼uzW=rhzuk8Yz5<T[AJ*ݐdJd80 kfLcDLW]8PDDqoYR(18Jbp{SIr)XFd^ ARL+ %, R`S=ria*%Ln)FZ^Q*Րjt,i`1Nf#X5IAX"8@>MTJU2FZ= Uv81)닚61nMr.~B|ʥ{/Ι3(4*E_PSHU*'LfML~2ܷL6jr{fKgÆaTm{wܱuIC_P1A%Ny /~{Ȑ]xUdD/q; ^>o7ѠA& J1h`xa=,^XJ?}%ׯ^77Zh8֌H^'by Yجa 5̴VDҦM<HĞ=IM`ƍ=.ffDq钅¶t^tx Jh/yxo\oo!؉V޽ J7n8|(霷Mmӻב#VZ]d-[ 6McÖ|fW-V 3}9T9m2jڶ%9A*]RVNΜ9xb0_inxi܅ |y切|t\x5͞N$ba;nqT>s6`3(nAU8MAr*h$H( y%A$FD*4hY1!,fD,Z 8W'F@P61xg 't^%85Jd8=J,TFSM𿁡9FfwڠKY=Pu4i~Ji;x֝dNRʙx)ﺵ3FxNqAP{ژs61 C|Ydª#pN1C?FVj5j!hg`߆|fY6L7C'D[q薠-gO%^G%@۞GiYe$;sMЬfzF!S|"K'[T[>,l=Eʕ64+-B>%!TkՔyDOXfT?sp/%ѣFN0iٳfnظOt;6XѢV̛[ * [m: 2<Čgv_t9s+T(?lPj?= 25[2&T!Yd%ѨP K~pGeLqK87 4:BQCnO3L˘~!抳 |wHHO ih\*'R&!8Y,MZaN7 רVl4c `ߨbiHmFHh[hwЂs,WסVYvX, S2=I] $}ٛϴWv鰖gn>?4.z*;#W5)Fp݄6] rv'9׎Q@3v .,t2BF\_^4AM!ՋuwOAl }usJXa0g֝65fƧ;1ju+"zP^P󶝫UnM+ ցTh&pYk2"Ařj^6z#2BU#bR"/_+/MB>ٲ_H7Ji˾ ;m%C9*WdMd٦,eˏ"N!gV/4RLi8;`3C%3+PӦ YҰ(9 I(w6*d~5M}7-ws}K Hi}M4ɥqr6]S\Hczk I:=2$(V#J< "h̨v @5e \6uHXR^OޭMGI{ hWx{N_o"I"Sמzj/v5^ylɁ)]X94mqaaсoBϠ?q?ۨyjo"6â>I6:7]"" |n—= +CGQS Z:@sbߒN,C+-tOEfn<]xnH-Go {{[3G>:4xD-m\N`Q$q_`i(EZ3s tzrc\Zf[s֐Ђd2(e;/56.|>-m戥pUkHs`FVѨϝM89e ԓ՝G ;fV,5gk4lB:S_s \f*4kW>HۡGv;DZ@7R4²3!9, *!E@ Nr uŲX]v,N&h&vzs[0ElK3]3?l>O5cw܄g t AK/Ҡr 66^b* ErmooSRYe<.+Gxr;5*]F{>pnrD|# i| (>-E[R]v'E@XP6z ͕Ktr877s-QOR[w3{֣W~Ɩg:Yz8̏&v@*4-[|Kq:)(@ iFwi#G؉0l sG Q@dHAXrk18.̪o|e%ׯشyKYꇟ@Tٽ2_gp[ݻ;w?nC3au5jEB@ oK/𹛂@| pv(k2n$"uwZf͈s5Ɯ1v <^:CXq"!@ Usf&T` x}ZmJM+P ܹaia$)R'OϜu-#m,ZȈjԨΫ-[߰ac|fp72LH֭Z kА oIK/|E% MW໾}{M k6m4q'NF };w:tM>lGE, M}yIEݳg>\$ \ݷ4Mo*`'[n{AE ѭdlFV uTE!E>$iҥ!W ]5%#@ 4 dYȦ[MRDN?a kB׮[q$d3gvsuׯe'ޭe?عU˗/8gᒥӧmܰiǎ Ennn]v! Nغu A綵g|a#.\oϮ?{矐$+fݹc}̚u.mĻL=zةSY7n"V!!+fς9Xz҄$-zƪ!g߾'Ou ʖ [cVVBӢcpUcی)R - NQ(5#B'OVO?@oBۻޛ6m, )ڹ ޛ4i̲,?!Bnxŋ_p(Zؿ7s/]Zg 4.޻PYR%gȼt»"֮ cb 99-ʉP؉5X [> MZ;99AXPFA!+6l"JZHjxU)\Ol|ʏ[zȼr. ^RvU^E*ҢEBHS~Ӄ.RݍFy vGXV-njIQs9aֽzo򤆾^~A]ARd Oɛ7Lך=6o\/ "*Ôi3o޸ {*|D޳m\aܹӦ-[_zi㖝jռl8,$5nD=qqo͛[n߽kov?{{z"Bke|EC&F:m̘|-[AJ)(ŤS\\40%#G٫OМyb:z̸ׯ_߻͛763|Cv?W KȴJdq&mՊw7~*r=fͳ$_|p܅ ~P._ɟ?G2Vcnjڽ'XJh7U'yNςQ))k入jZ,El_O*o߹{jŋIb27wv*UժUT{]v?Ț*<GsEEd"2C ?lPOJ4/(>}z8sQ!6GΟA3|CՕJ2DUb$m۲ ϟ |ϝ=ҤQnfy =~~:9蘘".ɨU/^Z5mGn4Nʟx$HM U?~1m{z~ @d _ %E hժE蚰ǎ/VcnʗߺyCW =t0CӘyɫ=PNÛ7os&+ 6)Z6j׬Ix&o|)!Ϗ? r&iO>+Kℳ69Y:9i 3f֭]k=/]dдx#__d3JhX[ѣjJf&ZZAW>>Վ?o|/\Rh!.^Ch4<(vLJ n^޷1/+\ژؖd?f $(!h4V)rd&SظXH|giu(79=r7۟4q3,X7iBej >QNWg~quqO-*!Kf [aμujܩKwZ@5Z"S2m}"_ 54goO!.{y={*[pTdg̘QFc@ɒ%.\RlC='tBQ:vXS -?'dYk~=/!}@>+V `cm۶1 >ܢἨMUHX߾}nݶtrz]l)?аuoբM3>U-Sʵ{?sժPa)?A"EI<(P[ MMKP*b:uV\H"ٕ҇S3XJ0!*:Faz/{ϟqNHMvV-5jxÇٻoZ-KH-h:..A6 ݲ UȴJ$lKq͟7GPG,w٢c7k%|-|]; ݲw#:k}(y8m3hYuͪ_ 5CTM/qB?n,ĉn\7_o4m^ 4{eJ)C~ /9tp~:o:8%NuL0ۺe#HQT.jch˗ai-b6ߨZ m/q9۬ys1Pi-YΌ`U\DЪeB:DSXYliب[.lȲ#uLf1f==?E\@ @7Ib!o}cCՋpn}{Ǿx2G|P&zWkˆ$pҴXmM([ruJW*;gM)4 > ~a出6y#IܚDZmF >;_ t쐃vMíiDT;H բ96 mđE [w\2[ҹٝ" wZ7ޣiU, R|JpZŧ=aGbJ|J{}[UբZt%3:r;s^LQ$qo]4kȱJRK[wNQ>(@ r2_L~DǨsoOhV]E{_)l_^ck-sz8C+pB)qY3;|Ԩ(c]camI(ҤIЅ@ Y|*4}8=IK%i'LHDvQ-Ѣ^R:RTf@ < MAJf)]՟*!GEZeۇE @ >HrXmqJٶO*R&Q\z\"M@ @dHZXIPộ?3RX sڞ7CB4r;-[@ @ 2B%4=.PħL5+l#W6RWbc\묁r@ OķBmE䏻3WfTff$(s|Zz^!*YX*d@ @d1_ Z-(nUYHPi$h5iz4d`?2ҭr>9h!Deѵ([mH{"$A@ Dt Sfij%ASMI$-r~"'.?S[R`vmAVݞiebmh.TE|] @ WB#_A&ﲔ[&I,.m6˲v6?}Rֲ\M96&Z< *&dVFfAO@ @dߴ ў3퓠zII A&Fes?fv,)Ql(%iG`iXDIO//8L+}&r)Deɀ1/nYטk6+ r(RTjuV@ ":UcKqH$a OJ4T63{G ,R!?TEoJN5bPI%8+ r;BbLZ4eDDtk-q$@f'&& ߔ*tX>md䥋K*QX1:k=39njkמ?ի&߼ul2sgɛmϛTHOu葿+W! 9ǎxSyo޼?a7K(8kfB,BsǎhзLq8S_r%O܁ʖlfpQXS|Pddϖm_L=Y8)J/UdNw$mB .LӴ=f8h… ƞ9uT2..n 9OX&ݻQ1/=/h[8M)ucceYZq?"0h7 T⨑J/qψOD`gD*L#i"@|JEAX\[i$ë71Hgq"Ey:dgNS& 1Yei 9]{5L&Qqښ pwړ0 oI{Y>lrBih)wٻ*FHBBBtQDE@ >TE 7}"`yv* K-@{gfv7ے>1^vgg4?/ ,?TqY35o"3܊TBNS*Vp0vĖwY~g|d6DL}`Xrrsɉ E׮\q;PFӆ0, %ur:/\Ws͚pJ&)XuwKGpޜO^}ͬ̌iS'St2n;*eb׀ PjHyg)}0c?h߹*Uٻ{nִ =׮Y٨Is1Ys]p\co9eNa$)Ĝ>IfG^0)La BlZ.u вBP;i-'˰&3cH\"dp z2$@Ut`"Dc/Xkb*UTUIJLN`1DXT*>F , 9'`P*"  ({/rô |@4`R-@U&Dz"V[NKFuah]9--l2o޲Eڵk?Ɣ,Ma܆k)+DN**5O/?'kܸQrr,_^Fo_+!K4LxDZ9z}h Ot}_83,J$*%)U}ǒ*$7λ>qS P8ԭƛo&q}i}B;G*^b)S*cQ- ۋ*h7RT~yNL;.<+wBW]{/_NMM*6j䈾^V-Z4k޴[[b&Ohƿe)ɧ =,Z T9W @}ك BP׮]ߧO7lXPUuM __:Ƣ~^_|6ob0H>ey@a S?(& BZ@m#$4KѬ0$NuĒPbu\tA)MF%Xi"#RZE'C6)#cWQ\i,J® ++ :` .xmk*(#JH=>dF<z2 eZfjժΞ=kk x`g{tBc~/֛9[h>wާ:v$Q?/dɏ?,^7ixz`fumL2XjTIHQ Y yJB3=?@vڴIcF aλ~IǓO:u4:lo]jIy;l*YSN,X qÇ9{ܬIDžάSݮ_u },}~ΟA0.סCxTBeS dxZI:,lҠڞCLeD),(?tP}g(CyeʱJ%S .^WSDY>ѡ5*Pngvq㞴 .f_\,M;(wd#,M澄a}$E&fdt86;a@ M'-V`dKT Wn. T!?jҸ=c<A&G͗e/O>'XܧTU!պ2ghLy5eee mv֋^n Ac "0ΥJ*0)La (\T*F< *,}hW&!EwS0'|R㩲XܬzpVNU"dg(]hQd"EzJw$( K;(Mܹ}OɺVIIOOlA`]Abz{jZJ38_ztU瞡E0֡QTؔC?N8$j9 >ۈ#5=J=bFSE[׵O'~Z+-Aq%PA sr(1L0r l7~{gT EYi&}ЩZ >9ڇ[3h>СЧԻ^1l蒥?ށSZ  5oQڞV̀O"K E$]lA3,,wX/L ? 2Uu0#ʼn XSǰsؐ7c0 tkR@&!o*iblYF~0gR` \2j.` 'MKj0dVtfUQJJSXU2Hډ񪈕6𾤧w[|IQc#Y$#,y=_<ܤE>](cX3;/mP\?X0ݚ0(nqeĎ)F(=@~y-rK_RM?t؈QZܙ9uʤ)C=xȰVO8zk>u[ގZ_y֭B_]~Ӏyw!C =w^&Mjƃ`eM͐FqB(h{Ϝ٩ccƻ4d7 p߀-1fVMWHuW/Dܒ%*&!h&;].Nyy/DiټimPcdC /eE1<+}k\ [Dy7<`a S45QMO}h[d(ZO)bE48Ilge9J]TE/Ze@By==rvH#K4K4}ݧ(k LPDqԎwJ <04#Kŋ7ɷIV{^b7%FѢw>_JZV84L/R]L˒WɌ$0LTsn|O8*5yjh^k%P9EBBc]X"Dv[q˲T$fX_[/ɔS,D$JQx37@I{u2ߟWᠼt^OU[ ;KV]D1<oO)QTؽYM`b+c6"[xBfJ~~~BB >RZ5ߛ>?k𧟦/?Ϙ?%99y.6m۽s9~Q]րرmK{32ҿlA9Vx'39+WسkG 9]{=I-1fX˭DA1&Dk}}@l̄U ǃ>`P6"'cn@Id1{`cedf"a8-RMn``UHD d>@v}dCV<`eꭅFEEnz޼y)hҟ0) 3Qrxyh@Uͳ-bdʳԠNjFz*p%'>:}3NxV+AU=7ZE&΄(jR*Qub) !6\2Eb0D6S!bYR%3LAI۫GB(j8]HYLx|-@s2q KPfeTR<'[ e62\B>sѣ%˻ Ax.MQd$y-9),hvvђ#%ܕ_q)")zհpTGNbU4mqU|]((+YJ&JaZ[9OXJbdQ\^Qv$B%a6bJZ+{052bT|W+xer,8Qh6I*,((*(^z иi`* Gs fH jN:e̙3AnK/9rYfV-رcm%ׯ_ni޼^5k~5kn^?ۿ#GR!IV^ݡC+Vlrƌƍ#+o9ptgΝlz@_|رcϜ90a#.n)E($NL+!YR`*ɰ@(dFy I&pED܁#ՠBʷKjDeU=vjTՇEtL  ơتlYːjƒz0$2oTwY"#2&o<ߚNU_wfץG>;$(h7mg( HFr |b͂$,24mf$ErIEukL؛1=8s8ʒ+bML~M\x}У<5fkXU \jld+^Gpzg*(0}# nHn2BhqRD@nnV=xHRf830>X|->.>qnox;Z/#ݐfuL(2hXi/t 9lo}y"¢"8_a8еk{={-ߏ<5n5/\W_% (ؾ_ƐKI/bbbw"SO=u!r NyE}Y]v]nwa SFe,5 T]gQFFQřB!k~ezAuhʶHӵVT5ē}5W BS.ԭB1Ut+VEC(% r$z5ЪZ~Rhjqh¨7L M {>} Lڇ~}$曳gφBГ'O޽{6l5`gҥ;wQa SF&B !(K&)H(2$ u 'FSێiH) tqE\4:R"0z%p\ mvc6 n,8MA6jab)ͲkϖRtBh EQ{G /`#3?ak"EeEtY$wYV(FDDP. #OX`q@z^/UYLfEjժbe͚na SCP$Ecw: srZ_\J4(ƸR $AB -s,ZWR#Pym?u#,2_\ 1)\t\VL:u:yC RRR(??W_}뭷.\بoʕ+tʨC 9u3cO>J*W>q`[B;?2>07La*(q`]WU[ B)[Ԃ 0tE/ YT[Z-XUreA(q9$S3Ի%$<$a p"P7-/p(cVXjՠT(\.!'W)nh??L)p&7vAo'FZr֗W XٸNU\®os^u14sp(ʫhoW +4>sp;T$dx[>m`аHЙtlbպU(rxKQ<`w{CNuN/HRDLNy2e8f3u"L'E|:{,tiŐ0)L8l$G܂^pw~׹LʰH?LXC~3-( ;QdcD^oCLEV1GDxgtLT4GnxtT惾tb]KF/ŀI˖-gϞ㏏3zLkԦ.7H? 8970a6o F4y)V."ωBE?U90ZR E_TRr9f) Fcы،uUe)Khh6D_W H͘ǖI&+W,&]rrqV^Ԉya _H26&Q+9Ws}ŋ/<Ҁ9E欪菜 Q`Qؘew{m36adgbbݢrhqV*}zfyʕ,wL]^:5MhѢi&55x7|'ut x`WF>[o_Zڎl޼ HS'c͛6ڀ):uZlYY0ӭBoB}'y.?_׮Yg/$FΖ-LTx; {Ud/L"xo4Dz d L!> TUe hMSZ,v!;\r$+Ay S(\nIf p=AR$>-_1b̥p4p`۷oۑ#GsRJM[n}]GVoVX:lP܆73zij~Ѥ ۴/;a8Xf gee<ڹ+ R?,Z8 |򠷇?woN Zsz72+O2)==?ppҤǏ}>իWnjc կWϿmboA].]:lC֫;eԴT֛.ooIx3G3v{hH8Ɣ ]bCnle#YU]5($Y$/2VqEVؘR}Pܹff*e6MJT(@0&sQIq8u떗7vX޵kשSZڂ {{w„ ͚5?~|@K'x?9r$0Ç喝f͚իW}U\yĈ={O8qƕ ۗ^ziÆ Djj٥6駟x<ӦM wΝ˥0Ee`M?gqrK%nɹ…gr%"""%Z!LXnNΊRz:,C]>Hn5Gv|$sh+zeTGnbEє^1s >r)aV9"j~2" \j MIV,W" G^NQ4<+ QQF>KȗS'O-\` )a"@ѓ'OVuyy2=` +C0xӼ9=d믿6iAڱmIĬ?ڴy;t}{v5l~rC@)?.^yVRȔi뮞td\lۚ+-X0yʴd5j+_{;޷_G~d5jԘ>}꯿n=ƿmzV?esN0+VقƖXԿ0DI`)w*Iq0j3~D6$gK$8pzJ30Ӹ)W͢N{lE'v3Myi&/{pX9@y/0$Lm԰D9sfϞ=0 *yr#$fggCPW~*kֿ;s{+ZNH!MA{U+W)S&uhޘWŊɣFΖelMi__[*#ޢ.swt]&i&53MG5^qI1Hsb,͵%%0,wM{~rW=0XaZd+ڿ_L4VTyC3l%ӪLc ˎf v%LK0nZ5^ hi,:*+,-T2'QUsG#y5;mdw(Kd%RN=Y䴙&JRtmb~C*)aW0)L[ )q>Q4BP$`?v"7Kqf2*UmTcKĿb(Jڢrh&BPYz&&xJk"^Ίd)EM O"{e5kN;^:<yYcxK'OsN2KH1# #cH c}۶Ώ<2m8CɆR v-ɤqgw0֮J0xTO QzfSrh2a2(*m1T0ڨq v~B %aÛaV ;€5-Od`$4 ʨeš@ΝLS_Cc a(O,JXMA/HwjZkC(-P&"'>FEmBf.`K%ڹ%Ccǎ8qUV׮]]F/dCFNqqq5kքزe/Ү]kۦMy;.]'xg(G8Heef>DSTuܹ=ZKNNOFPݺuLt53{o`Eˇ?wάM{cC X._8wE XMl;nWza e("Ǡ/Kug7ԬNW?m^x"|KK|Q=l%oW݊XmOd6t9 ԉ4H(q3e4- #(M$Ye|I䦲fyFKiUQxs4XGR.@>r)$S 嗍(Ο?/_̙3 ([n? 0P}+ sojJ?v3dܪ*8&4t؅@0[[%sz nҤIRRҩSrss䳔|*TԩSZZڵkt8~za8yj-?O瓔 &w3zO`k־|5>`xyȽ'LjyGٳ>B:'Μ!$nٺuH}^*N,3`-@CѣF]n?.]T *j~Ncw}q;\V\U%+`!ڷ_v}=ׯߘa&@V 1tW.XE(?))OQS97>zҖ X&I<7399=w4FBtfŤ56Q~Ռh+ LHdlbdsg)9VUЀU Վ5_=(X4sf/Yj@DDõ+=)La?^7Bv pTU$a1(&&fէ^n,o"εeW!zeEbOz*Ŧ_Bxi ahDHURߧ.s) ,::f倫KW-1w/?F :l-[eefN<1`JfMom[2dAoaAo [0>1hJN}Aq6EVg@R Iy:L,m""JGlbغUhfFVzq*UPbG$&c_&!F#2?y(:uq:FS|tuUmDo,O18$s|2k855EQ\㲪UTݵKg駞?ܳȑ}jZ꼹nIeC/ɿF oD[L6Ç_x19$BJRlwԩdddp5 8p >RhY&(}ņ;pD258s%5U;q0FCrFfgg'&&zpM'lON^RM i@yqq1->e+WFxHHJd.6*eˤ'G\Jaoɑ(HΔdJ9%S#N/Mivi<Ǜ {ˈ>b:$r!ڨyc nзaV0?O%f6nrkլ11vsn)"ՊyҀ & 6gΜTѣ,y866tBСC5j =<'''ܹsʕ77hiBVZ+ź=B{٬&Ő4 ͆DY#ojNNQ=q3s {WzuzSǎI ^ޱcxs!PAt}˨Nkel^3f|T^6e^{uTA.͛7R - ona<4?@F|Co 6RZhf,ǿGϓ"T#C k{j+:6,ǚM|RLydfLB"9ZyYQC ch1%O%Κi߸\v 0{A)8{$*&(Ju"La ߆nMz2` 7X/ HiD3AS^đ+f{ϪUR2+^Rgp=xjSըhKcABP۸ @0 VIQM',Sbʼ\(08'˸.5D0n ?BRc.) vBTyYdo`=b܎ܢK{9tKuiĢUo-Y݃=}$T^~5FǏ/k=;=)(?K%`рnчT?ҭF:ujŊIIIXXX_wr>`۶m zur( RbZ3?Ve[5oӊҟ+Fʡ06K_6Mp;\HHL 2f"Տ5 !e$p+Vj!C2|ժ5'{O(s5ɓ'Zڶk7X%P cI:Qto݃]}2cF!C9u$~{{KJ/#BIGw>x7oz_?Գ1ohጏڶmOǎB ^}m`B|)yRaFS'Um*IKMxgi$e(&VE^hKqjrgQ6D^b]#&3VLjbal(9bL]Y&X dD}ڳ5F,}sf?nMʿ}Fʾ{.YBlb:9D#mD}Fj2YeȻz.bv#:RR-V]`XB>yD*[?Q?n '`.QiRN|]U#V<梴(^uciGq*pQ2Fm% I]fBD`'ImDrQJ/#vF2+h]TF" q~K&O?ڵkњa 1*۵k%ԯ_r8P&dSNǎϟ gϞ% i,T!50[,Nڹ`{껜o3T$FlB.T %t 2YD54ҡGе(UՒ6F_l KOO߼ysQQQyij  mm~ꟇsԥK.E`,M޿LRB :ۿ.xKJF³SirFƮšuKQԴԹ~F>enRmcR"E.l䗞botӂJY#aR)B0L <|ܑEePZA_Y@+JQXQQE x] Du8,UK'##ҥ H6o0}J%",[,D-7G,E=V߿, (uT.' (0LA|'ev\*T#F^ Rx^5W-A TNs) +}e) FX`2JH2p0YYdD` 8ANJrع#d"q %?\PDX|D5〤nTp@Eq8:A)^ S&M2n9ZW,nl oB᧟~"7߮Qh֭_ݹsҳ0(Q%?/.JMTIzcJ/sE{bbiiBRf1A>&O!BTj$j(2ЂBR4S˃,z/^xÆ ~͚!k N8ԩSNn۶j%&&BzϞ={o;sO? ܹZԢEAKի}!ݿy( &^fM|=5PBLL_|QjUH3goM5yP.[/3X-0n>;6m_fffB1cpJқjprٳg'N9_'M561` *c 5Iט_bnQZv~]rQ"FJKC$d[Ӫ c7#kKhG!]]Z "W# Yh^JqNp2 k.MTC8+UoJ !@_TIDƗ܅";ɤ4H  |SW"VX,-[$GcsvH~16\dDTRՇ_]7 +Cf7¢PhCAA#%σÀ3| pԸq={ԩS`dBBD܌Dv`ՅB@q90IsF6P k!T5U;39eSx|)":%JDlLR&(rhǛhK΄xMX!TN"+]SRB \eU*+T9-i'ݢDr7WfVkOݍb+V+=1kib"C:LOz5DDEB١}DǬFeX^1@G]HDO݃.? '{zX_#ҀEaBe(u4xVX e^^^ll,?{h&j {n.\I5u ~MV ٵž(-K;Z4^*50 gs [@LbJt"x"=xw `$04$:r F/+*Kg'@ ^5k L$qȐ!pu҅Alذa̙o<?0 x믿E)S3楗^0`0㽡ZnnK] Iya,CeeӦM/PrZZIcHчH>~0м'4` xCvѭBGIJ9|2,>RWՙ'#4BPc WMyvdQ(" k9Ať vQ( JUYKM_-->9(*DW*yPȱyf=b 8Q54Th@+B)IПh(C"s':f]vZ)yV %YT"6|rrr(֗]Ω[G! ȑ#E%&&kOE*9¡)C(|hjjWmiН.'ŚyNGEm $)ZX"%S K&x_}! dDܿY1dn `o81P~&M?^F64La Xjı>’#kW;]>̼Ym0wbi`U9Ҳ{ZNGE0&N$W2nS+9bO +[$YacbEd0"aKVM'Qò,Pk޼yHnc$bSN۪UJ*ծ(2Y(Epu֮][/b0z *_ QQTp h6)! VQB=K~@vYzj6ɿ`tkReF 7UU˥u-.AQǜ>NgitK(Z@QbeI$s #"dЂ \P&[=P13yu]V},A^Qj$[*%SI"beaYL<"фfAQŧ(ʔ%h{U(æSO'b"ݠX X(}K^yL@I Ut P%AqDňVmJB+%ú04p sO<ĕ+W#ꫯ֫W/990[˚ğ&+Lg~FFEҲTCi`XV;%ŢHʸA0MծYٗUHY.5Hz E%FTZDQܴiS-2334&&F(.,,$hU8&E]v )w}O6<rB0)Cj,ARb+E7j5Y郇z$uymy3e=ӹٹqկSwAcQRDd-),C]5bDiN/Z?.5X9_y/^#)s|aÆ9rC5j(##cW۶m;ygyG)E2>zh:::zF^;vsݲe^հ/Zz'Jgw+X vYy A;!v* " UQ);IHHLdw¡?2͛7o~5\?uY6>Sƍ9s橧zЖ;jԨk:51V4KI#oEAc8>Yhsw)T8$m+Ր֟9E_J5*"آ3t*s:65TBݒ8 ydل8R Py`gn-n[[L7*5!Q2%'2J1JH":0$z qMT/PZ*)dE`} Q<ӷVuioMU,GY" YLr^^իGy±=zlٲ䗍PH%STrܞBXRFBabaxSC viΌѫ. PsJ ?p Wp<,ϛj^X$$PM6za׮]yµ@& gQQՔ0MRM|Ge{g "Gfq /r5u/T?D[oեm f :<`F <3]v4O\2msd+٥Π1Yhˢ'T _SuI$0>GrS7K6i=1-%}5EQJ"PKB43Z :F ӳ3k1u4>:&;4\J+*>o}9:pT3|Ev4Jm\E_59Jd)f4*B4VHDU[Ly;&Bvxɒ phCiI|deMUYx^|ǀ1q<BbjVӃxI" lk*r0kO7dżQ^ p:( 2G=6[V-{ZjKRIr'X($"tBq1A c}18/7C2p(aO]ήC ke9g9޽㎟~ו܎‚h,VW=tn=\N]-I`0U褥ΪhOٲCʺ}]޽(ec;+a(8 nZՐ͗fL6ЈZ(68JԼfaYY٢EL %cǎMSRRbIa9hS'| v~֖m.]JSSƌ tXUf6gj%fv/_ZvWVpx̛7 iC [lK&F޺hg2,4i5Xe IT:$JYiZ + hVԨA <je,㑡@E YHTEII񸄔J%YR`Q"84ڰB3 ]n8+Dv5@qe͒e ba|հ_hdM.)Zk:vI ך/.Q A Cp].rt??qC7lڵM-nQlUNm˽c?xoa6/zq>Ғ'NZn}ǎ:+SO:zlj[o~XSSČigyfj?4i1>ӦNC֮['nzL*[73iG bo5S~J}&YZZМUV]tE 67]ĊNZG|pшyyyP.X {rs*+*~\C"'7PVVkѢ$^~ >O߾ݺu_xFvG66-6حms"qI%r/iu@ih%iuAX,2TTTh&/luA`0HqI~Qro , R,Xv9H ؼe3Zn=z瞻:vpH-y~eC2X-[7h COaL)k?=v>'))& efϺN<1 h5W WaFsyq~jN-r_MsسT*k3Z-:&lCaUBt1 ͸1zÃW^s=aMw҅JȎ3QNZ=8*6Qt3:4L)+ , @u Z0,o[{}f\ڥe^3G]`M&mQ-S: Skriʚ#h?q2<ϊ%uF<l&A }5F;l9 ~v-7/\gq,cO:i^|nKs>s3큅^y9pٲ_|`WH{?>.}g,n)s演HSǦܵ?xe7aeMw9/k@| JyG~ڲy-N86w͍I;C,meZ1$BmQIaj n˖-W\qP2܀ QNx<Cw--߷{^?|_cΝ;E={M74\r92-.Qu q HA#Xk׮p N>唯 /76Ze˄PHjͬL"R4!$E~{`uuuPzaD0g=pTl@ G \1ɸ㯿 >?9G`lp°i"0oYNY1)IХ[_`P*"!A5NE6.s<XQhGӮsfHnP.X:>{C{s K f8F6 D' )#WhsUxWu282Ȱt6X;YUV$ǻl\USh?]GIarIc.AOͅxbJ("˪3F;\84,4]%?6xםwHv4Fq V|9^YPPЅja:V Q %I1(ITzoSBz֭[(9¿fM ?;XG?4qf̙3QC؁:MŤQm^;jsU]"?鴍'EzlZ\;zvg99h9ݻ 8|B.%} ?۴qS0J{I;;hF=EU 3v@ךxP[nH$I裏  (XP2^NR<a) 1ܷwO=mvG]g|VN㍆yij֭}8kdYD_pѷ.ڲysvmWZt:nӒҒ~رcw60+TapnOͬ^8 8*EZj߾}yyy@G';ҎiK< ޵kmݺ5 ͖>(a I~?ЍG~(p׹sg Jg`Uԛ Ig({tӟg>T*Yݴi󄉓N״iԞ3߷Oﴶ]Kڡ0]UU䩫&w ?s=%pPFueh¾94֗,6gYaUCSs m6NJ*;PMtFyVXR\6fѬpFxI1,Hg1Zܦ`V-K[J_4i;v~-nPFΔE jBrSjVvݏ=իKJJ`J;v{(GnQ %MXڲ%b1ӑ ڟ~>3׬Y#j6e{EMs ,^rEzŲ<^|ٗKIL01wk׻W/RȚ+-?椳6yA}=`CЃGy~L.&_jZm ڷČK{)-bٻ7m^~U':$IX+'_2ޮ?{JSq8eMehc8-twSME#7 ;يaFdho6ctѩsNK^ȷ\:k% Xp OSX&2EeS]qƮ)KkZo3/xRЌQf) =B.p )]|~JSl::^B:tB)ߞeyESh#(5ѳEU8CYHfb$kɲ@#PUG\"E0AJ0&s,r(H2 Kʐ\C(90O$4jq$4sxgۆ"'.?]nRaHi$ ^pᅇu{6lO>yW}{~׮yֻwo __ƏtS&L?w{O}zz7o?͛;'ԣRmεmil۾sNEO0=qx9j@Fb 4W-(cIiȥq͛7dRdee}||}衇Ziˑ>b۷w>(T5rc &ޭM,*jѧO_o"뮿F7~쒒RGjAihC%]9Uȳ%PZ ?dkCQh\E(crra}5x=͍U2 A#QU$ Q@46&t8^R" #\2Ͱ6OY̿N¥$ͧ.jnu%n][,ohRC9}ƌÇx`}{SNӧ?~s~+R &>pK{1ePa|S.+61=]GS=70LVny67\邅Pҿ! [l3G^4GJhlቒdjK* j]IF}6劊`oii)ˡ pnEEiŲL4qI%rɥ 0>"pMiig9qi. k :C۵mE[t:REk}7PvW\F;w< 5ᰌAb'I%2 GhFGu=#F^ L 6yQ i0W~? >}#F8 .,Z'!Y&m%IRƓ%S9RE/U~_:zԨ#G3]PY!hxQԳm$bߑtԦ28\X(c:G Ǡ/r@iW)7+zX~@q*vwÝ#zuq|nVYJpwP4 TZt= @$TC7(c;9Fd;Bl:UnRavvքINc!QTCu_ڵբTZDN<aVEd,&#E M("Idȳ@A=6NwQE il&)ϸR1ZVⲑI2jN[[:J'EUb6Ee)bCS@`g(&^k& 5G)ϛedSymL:rGBtdwwֵūAw3 i~)|-//,pk׮LqaCƍxΝҔ0cGWyvM$@A=%zx{3*+{Qk4zgNĄprdUY5([$(Ns<嵐]hHOSȦH4 /]QlRi(CG;kGtGNXw7 >( 0a_~cSz뮧f>Ks֮]{9H zNiDP;v4iz_YR{Ѓb-eX6-ܑ9rfN5X2hݐ1M![ՈtkPkJV( >i E%>'9J{v;MkϕTu1b AsxsX0 0+NϬc~u]D#aO>á?rl].1`(*1-9QXM8ω Y"chP$$2+U? AA>ŀ_YY;fH@˨r\焽u֬ھcgUuU$u]YYfgz˗/~'Xd1& iH᪁n޼yǎ7n @E`85>h,ֱC)iT2`4NoPN*DzlMdxS(uj,2ᎱQ-ncU6k)g@:]F r,K掭{ T !HP KSH5b ]:\D@d@HĀJ.o9P LȑH  Ml]4INq=XҞ[n<{;t >}ر?4hPӃ{71!P dX^O:_ c#4|i@Bc.]n&X6NM04#HL㚪3,6 XPCiXp@ND4JhUZթʀ*fuFs| SQnIKҁ "/+iD_"o(Hir9-((;wnuu="W {?~ 2/ȼ`bӸHO5`s50w%.qOr,P5\(W$'[{nku-.捿@axm~v҉ld2H8œPf%=>Yh3&yҍJ tgyy'{` @;ֻ)&EO$t!Sԛ :Ό4, ʏ!0PDE8 kD;lbPKhAaӯβW$ ˚"J@#JT) p Fêo3&#FtTRPT] +T$ARqr"ZiT"]өWUU#[鄧ٝ:u[:r*ɚzuה4Vqf4a{, Š"脙YJ`*װ( ǂp0PU 4EiQ\̱Li.G555OC40Q&^GWΚ/IdYqbk%\z!CY w%J%n#גfP&OVݽgO$bq( 2m)J zBn+PT!nvخMA\~^Wi㦲Ve`Iѣ'N6X !*??ZXu^ӟnN;򴭥1=5Mz?K8eFq}r- [ojLⷧiIn|VZܵk'<Ƭ)~Zn]Ss io+`wu/Rs>[Բ'*CR("aJ b[cHL0M;E)SݧuIaR(XI2,I׮xxauEJ,m٧+}"%j5ts;|7y7GH 0m۵_U 45gdj8~eyS8 ma8n, ߸vù [+4SUet I&6&e Nh2'R ?)R Gc:uѭnaauu Hٿ"O\$ t:YeR"(٫]5?ܹSۻ\.@& m).!b B~[iw2EВֿ"^(W ðȨ`$V hr"GbbJZtnߺT~K;SΨLucon8[ڂqIRΕ}5G"hLȱzJ1IO' N0aƍ|ꩧƍ7sSO=5QC!;;ɓ'+s|ά_O>-[{_gȐ!p FkIIsz֭? ĉ üꪫh! Z_[[ RY_ YSSfXY=oO4wMbg5% hX(ٵmEcoJ0Fe17ODJUPHtf.E8MEJZ$>](lP 1Ɛ4$ .IueW|bz֭nyֿ^8ᄾw?冞ɧ/ʾ}?ؙgQI[cSL.+kuB{[NKu(m^~O̘VVVjGKpYnOlݺ1?98`ڵoߦmۤW*G u5D*IF5q<2A@< x:c9AAH(6lyףg{Sr*I2-[vu޼i36Ѧj(-sμ<_шBM_@ϓLi HfB/c8yVe9s(8\N{v9YHtjC(e1(j\b]tq3YɾlBq6)4)E.JiWbwW_}Xw! .dpdML4M-Om݀^|MHNI֧#5+(9`viqQաWB%I-(@V? ZP=#ўORѺ׼Є./X`eQh96~Vb{MYOѤϊV.4CxsMl,hUQW'=F1%)"k~@m篭 *m b^^]c2:cw[(5w< 2`׮]y /cN||桁:Ỳ%opi ht5\uvo7V̡dkŦA }n5$4T9.‰ qy ]}eꩣ %넹QQ6Dn8 јoXٙL5b4bd@b9wc(}Ї~]oO3I& ];J8 EQU/^Bv <''TYHM7%qFS1ErMEgkJb!GAF[&I5MI],4adC {!JrLBI[4zhXRGԯ~Ѫg%Z j"LC$=SK ^\=IzJoeee$Z i/[Yt1 S~H}¤`B"=Oڽ8hEE@c`qfO{#.ݧwA~ȑ^2h7J$f8'eX͡EL> 9sS,üV ,p8$ Jɋ`D%:Rhe.lC͛^6[n(ڜ> c,ZK., ţAAԿ(N|bjzbA"UK!&.*Ip|qQ~;vdnJ,\RRiahV9]2E)= 55xD.kWR˹\&7׮)m 1%hC}gtdA,{Hz?̙3NN{Ѓr&4U#mD9&a* ;[oaBٔoe$Bw0J"Q~#y9$0ezԤF0^s3MWCS7o)8&N,Zgf4%.GC;4%!K`#.4y1t#fTiBMzJC߬t.!COW\':tÏOmwr!UZntu_ٶuE?\B|WzBaCW\yM?`דS` EbY˖+/3wY5]RR여[56M={vZT\T~پ+߁ p(TZفR4|ef1KpUL>7V8 |BڿsNn R$u(i>LC9dO!+\4^<- ;r8wIBKSttK@]Kc{Lf6Ļݜspp0 Wmڻy׾{sv(I1c(Nbx/#7*;+Q=P-J_e)F*1RE ~SFk~6.-[6mڌ}w8,m+ mshNX\*Ji.|_vŊ$zߐ_51~l"8") 7lZl׵[8D5]MR7IPD#;o_n֭[&<Ӳ$$?Pz?+3z1w9%^,npwy>ǻc|HpF(ܸq#͒ Br s1'iiz"ab`ٻuNH@sf>p +L(M$kSCApd=!+M]U8خܿI[ MhYY+;v?7nO$ƌλkfx5Q~Tֈ47ߌ`܎6m}}z6Ϟ릛n Ak7?nS8㌊ ڶuCN"(jʔipGu -Lz 6teڔEE&<16EUSA/>}j9mԒrF2&: s0͒noI#xJ&d $Q GnEGJ[{1;X]DYlݻژ?`/TWñ6q+R 龽uuhөk^2l6QM1*%ajI5t9!آYJx,/Ju0觋lsUAlwzvPNGd Njw (L @"bdMKh $X% FgϚň#SQ tn܅^07- wϞ=w/Y{۵ɁGirm!AG Ip (/߸i;B\2tQvI 3S' UVVVW*k/2ݳ1ɃoVhâoHKp̯D-?e :N-[@RxNNLFH}cZYޜaq#Ç 8oY4yܛڍQ{ᅢs,MZlɌ'<夓{W^GΕ(`ƌ'۷o?}-]6ycog8s>@p0 ?K\={8O:즾_zIdH@г{goڴd*vw3X'|2a yff_~{]'u5t3K߯~mѣyyƎ2;ZV\5yʣ}ȐJ֬YZ5jj {N;4e7)\s5v𤖛suM m7PPtM1d Uux٭3%x*&*Rvbj[w[U!(9K16[ 8.K0oz4zHG)"HQPl(UHHh r}w;ɲAٙ۹捏e85c,&5?|BB!vZ>A5}#8AHKRyU_"iV`V05S( Ôo3ۭU3bT f2"J{Fo3xY^FQ`{P/ᚥ:CN7g, hͧS D%k.(gLDYE:2''֭[FPAAAD)o"4@1y@c͞={.;; Z$ " F@mK3aq^B}̛j:vTry!4jh~a硼soPwUo}sիjŪb:ux1Fv&M9pιf<%bogr RvBW)oC.%piF?h/9.1Uj)T**B¦qJ6.5RV/(IK8 S< ET0K@{i46>0dJoh%PQ!瀐2N\swK /vTԨ,E#:cS 5޿g(U!&?ɩ0)=wZ?uj1{N]k]7pfbx>|T0D9O( R;:DG.^,i|I \![(Z,Z*Rg#"2GST Nd RJŊ#"tY\ʄ_&Mfݾ t… 999(Dw%r+5/+5k뛖Vʙ*}A%i.IStSΥܼq3!1x6k)nW|קuEP T\tLbb ){H.>"?$ҮGv{Tdhzk9dggÓj*Q?RJPhAۃCX; (v]V a)fh46@u??_a%+W RjV|Y+v0_dY\\܄ّ#F$dիOcJ'Zlj\{@YDi+<)u3&p1 q !IZ00v5ߚuA&3OʓFPDjLzdHVY9de)R(><QG[4f@㣠`534QJϒ@l?C A \طV~卄9ʲom}֖2OhjRgYNᵫ6*:yǎ?NM1Y,>*eDxDI-Ղ^sڳmٿ#Ѝ/n'@;ΤeU*2q=>y"'/7m¡/կu-LT.*UP4]G/fF BP\\9/mS/.4uԛyA]Դ\NF>yюԌqo*Jhx Btכիt'jT_0šA˿\Lh)/^υ(&{Ϻl;5@ۮgw?.QxPoᕭey[Ç]Mw@ŜMNҡ$"'8EV)V qUU-W3 %素-j+W\۶:jzzZZ[Vy;[6%p666qK.EnvNBf>2ˑ{kB32n^x!>1bIW,.#``3 }XB|"\Ti;y򤟯lT2 R s^6үGFDS Gj|AZH&YPoUJ Xd1[h)<&襰S1Lه09OP999p]j5PE bG1t#%bbb/Z?)TDUU?=Χ݅@ su*TO`|E]:*ńL}3?_Bl]Ty?_s`-_ٛL[s?HWRױqű[}"m;X;@o=1qGSR˹]6۝pqqouxqw]Zb>>ג>4 go8BڸETJ?Ԃ`}N2~~I*0< a ss+*|ǟ}6af#Igv# NǚmpqdKճ y #,PxsR.?uBƞSP ˏ_XSWGlO>䯣iɽ[:mY;^31>sb6>*EeiS/.C;S/\> Ō/ը~LaF 6e=V+t]B3@,i7Աf~>߳pp%[RG?#|vC5Ú5]'>2ZjVd6OrI\\T19ʙpJ@bH! TPTj:0P4NfzWnr).T\f4%X >#3#7'h4VX! `߾}6;8:%Byg/#xeffJ.r!}{͛ )5]޹}\c+Pf>8X@Kt:r[n.(WEFNWPd(/P0JFsYaOTЊ<yqEFF[m̌L,:2^FcR(BȲ峜b!g۷n) PP M?vMT gv9Ӏ&P;H$PI%qMpy .eF5xfx *R:lZGUgf'l A+HJϳZVDi5UL M fM&TZ Nfba\U UXń}aRI+P0-DV!D5R ϧZ42J9Er-B&EQxt*ଖ"h+4gtFUcVyW^Z2}[Aj)'>BkfռV7u2ƲC)(!l]~͖-խmOrHBڌ Sֻ]-*`Y2)_l;礠!w8v)#0}tHXm^ze˖#/_|Ǎ;ғBwc(YА. .&qgf & T/CuԙB,yZnh_&\18XT*{^#āu-_tq[]x ϝ֊dᡍ30:?={,{%-jhoko޸axP l6Q*2n4_k*7l1 ^mR8郲[VRM&H3vjl6T*UT#GaB9TqphիW}||kիtir`b(z)&ԕ ]ݮgVFK*yRJVQ*#aד t%YR_@8~=-~Mbb}.TMWPVv6MN%Қ/lʂT)gXij#xEnew&`@Iή(x IW1)K 0(zmYe?\p.R^(.j6:o`J'жA+ԫbZ;'m;PF6r۩:‚t%#JlhDײַ8mT-f'o\qܭ<}_V]w؁BSGlvWJthʀQ==6js;ڼbNM*A/yRG@ x9DFDmn˖-x]OˊO* -C3ׅBouǏ[~%IfODOdK SS"ȷ֭kَ/t|2a&MàJr)姤?{l>}@mP's/_zU.6t;п쟒RP^^^00IhκqFffrĉD RRHo0 @JF ;v*~"h6jP>//Qj (_HaOK0HޠͲ8k$m[AWk!AZm|bFQb``<JW8t"|ߑ|dU-g@)kU %"FٶU4- '>t2jcaA֬Exdh ɪ&*/H;x挠:qrɋ0'xB$q+!?d p$"x໋ V4l5UZx-^|\lȟmIy۾ `w.[ۥ +79ګ%7`q|vJP&0y/oTa#U*l1~Kط2_i4hf"v`o4?^>_j9]p *~1Cu_S. FJ1Sn2^kܭA)f;wnכZ纽xlz.xQ v1k֬YW\tt0SBExH^^d*iWx dX<έ'ߝEnbJF%F r*T^B==MuhG.eܼV%-IȺEzƍ̽{ + *%K>;H_GL;1 06.!AQ߼q#$<,>?x%"o$|Y1pX6am-v"G* 8bZz`@3I͜t_ؒ佛 Bk5A(>>>4``7vZAfosJFXmN-k9;/@H_4RV3@T@"(`7ggZ!(RGC(iŨ4sFb%/boO2ϯ~.SȨJy|:^nA/EeěoZ~mڢy6j:7ܙ Z9KP09(xlLhƺraRCp(B{sן (gZeµR,y~?vm^UO:lS/yQ-EFN;?2o٨.vtbb eb<9/Ǐ{ݻvVZ'xf>GH3k4mްadzĝȲ,h~BF)Ny`!hs95X(2yA-KnBΦu9|tDrk'ZXF)(J(gB$V>/?:lmotׯ Wt|;lrMϋ<5.`*U6LYƍ;s\?Y\\\ϞoN29!!ڵަMrs4ny敫V9{v/vy17n۷Y":_T/^;n Ā%VJr !e%H~Æ)'jUP&q>?rzj'|];wA|'6n:w?8׮]C ~ DFs ]}JZ֩S;ϟK)W.,,x. 羷фG2dWN;gh,崙e-YI jŇ^$}z˖ͽ8mnv߁ɓ,X0}w:cƹ T _x$&Nh"R>լ5}W^K,uwym߾tْ+VN>st]&Mh8|]3YZ\JcJ/ |feݶ-ǜi.!q޷ok΃T( vo ,h%Ε,e7'8 I14Œ4K(W>ES87 E* @!'/8'C%!Mqi(/jaE}ͤ)J!h㱆# MOO{(x;XՒy}{_$$xa(oz1o|ݵ5aJ|]}A%G'oH w[qCgb<'b````````````wB%/5;P,ҍiB&}' x4Ss!`Cu#Yt7&_+(ಢm 3h|@)%g" 9>5z֭U#OC%4,r^\bĈ*U 0a-@ E ygWCPdm2o޷uMJjP̡C>VڴL9\Q֬Y>i;v>~Ч y9=cA999I]>0#dɒM6\qsyq۷IgƏ }O6lmT |nw%)V*+U˦lys Rj"/?vlANLL염yPf͊{ٳUp׮k֬?AVV֌<,fZ}ݢE˒JmI7G]zu/O8i #G'Įu-Iݺ:x`| /?yFiڴٰaCA=eT4zs/8.~r2yF,^* ߕ዗.?y r/^PUf,˝9}&JZvVv}+Vt=hlo 4r}'6:(3޿ /sx1^t?;AQVQj#I:' nVo @;uꔚz JQTLL4̳VMQzW߽mX :~]nꄴKpU锋o~_J(QmBr!C^rC]e>63Z" gU&xR~6ϘAS[j/^4b(Sii("e ZM={Mն.-Yh͚5CBBI&t5@20ϹsT*Bt (aZMm6HoaĈt9_ǃF l#,,,==?Znd .5>}Cc ݯ_ ƍ iH̟m. D5>uV}̙}U(S3j5~,>g~BSJyIN`VRåQ] Ą 'ORN'O&'9+5z@݅LwJ}=_GʘLfa YlFF3Nd Pxo( 쥰;'Xn#y)j%//<9uȐP׸;cdSREoe)buhκe0\.'EK.9>e͚50~ѣ={zAAA0???غu+P~wHϚՏ?(H` fI& a``````Yf$$޽KPݻknGn޼ lre@š@7kѢ*|5tYk׮Ǎ7f̧P=//o…]`dTD,4]ͦ.ojٲ%ШQCH' %~p? : 9C:w~y̙d/9zT|,FFPtw_+L&SGXVI,F @oi,k0^B   6.FrC5~߾}gb+oa۸8 @3ԩm dل3uKb`}d&4orưNHU);F*C08j_Vszա0{t]3-WLqvÅ[f_{_os"M3䗀[nO( o ;o;giڒo3-5;(M99.^L 2!^νΈGE Ky/Pu` |kd |V+}wWcǎ&L/7JO2Y[#G D޽- a``````<@vs]`MJۭGZdwjVW~!Z߰_%J*Kk˔i6AtqO)_V׫v n<˖WP!<~5kNs?.N޿>㏚6my]vݽ{okW^ :{e :o߾xBKDA >F 3f̘;R)sǎN?<@1b7̞߭t9Q\#T 'NLNNN3g>PR%C5jԈHJb3gN^ K*9߹/^rnժ5 yڴig=څRz ~r!|#X~bh10^^z8rȒ%KDʉ'u't xP w}ץKoFSҥ?۷oUVgffΛ-0DH E:;0ch?H\2M@ 3V]eI}F r"@A'^N?H}lpϑs ]J:9E)w,PЖA嗘xGսi\PXXԠ9B=W?ڍv鲳m1"eխfXKͦ\28Wd]t";y* |feeu]T844L^1  < s7t&;TÅ0000000U\7r.v}zҤ d29s&_Mƃw7{Az@QPyqg^1b4@֭[F#0jsRR.^H)j7ΎjNog"K(NoF&1a@]Zd{ Ͱp&YT]N.s87f3P'Ο6p`xQaxy``^Ӵ]gD[/ VPpزu6`DrCQfnN4iQQQiiiK,2eCyiwe^΁:!)E]SfzJ΂׊nܾg|LmU,VSēLB@m6+mE)tHw49Ik/٭ۛ))=?~5km{͚Օ+WvP]ڷo7cƌnQ~ӦMϟ4ihz=&%%9r˗t7x߮XW;uza޼y(v.$D#r~X9b?tLf }K(sчj""p,O}Wr]{ݻԩSiu_7m-Z!_ס= : )7q\9DfR7o.hi(=ܝ@_zJ|Ű * /GSDuR ~z*bv޾HRl!}rw)\بN$th؉2(\\p×g6vYP-Kn͸'<˗/4;;;!!_9\Å0000000MuB#.|}T:dީIn;p^ݛ,|뙷JNGɕVTЌ診uȿJb˕+csssZ`Y|\%u.*^`|c9cG;QGM3{\He;teuqWy87t@"m:pӗ.m6Exa`8*Wԛ/K+(qщ(l@#]Ci焗xTDYa:fM|Dڧlگ7g9(33V93 ΘqG_z;$͛p/T Tq" Je&9G^7p'MrOP?L 6WV i]51P).YDMQ:ԩSg̘1@~@ѩc?|߅5j" @ie1 ɯd2mvgQΝ_&Ys~ס[w-% ݻw^o&4ߦMaÆW &M*#ܷo-Z2 Q:<3nes=ۑ//һ~supsl<]3e\ngEEx~D^ouV3SX72 ]C= \9 j'uDڅ$[Z㗛7ZN yL-#7(*JU`&&ii77NJF897bK9@ V{d>Xo!G>c%(Ksy"U\eժR~nJ6l /|p!_>"<о[e$!Jr4M2؞ [Ӆ B%-,WDDxzW:rs] x!;4-E]pcr{TK$HDaQ?iάykǭLZtn4_6r'  v17YXXw^r)g:22JP={`("ڬYLjzżJYvAZI/P$'cLj/Sf[?s;={bqpE˯q6C"(ܟ__'CzG~Df~q j uPjjb!n+zQ-kX+xJʭ+<)`8M8f|`&@A!>C?CW)aɑXC+PGDƼQYf\\|]SF'r J*]h4~Im^2,?֠<{qofS76 hA<OyI+Pi*e2cc㼡pȹ"nwj+B1000000Cd]<x$|LͽW?۶d'MOOk#OaaM$l=r1000000b ]<x$|LHͽ.+=uT\B"C'f3q_hjש '& ^cNQY-$ ' |"w-[ˤeY#Tf.[w@N:@D WI,\WLa*rOZЂBڠϲYTge|ZsLLE+NժU{حx`,2ROoo/TWWFCv!ϧx<>a\hNR8 PZZJee%h4#v xwWϾ}AŀLci̝;qp'CpAٛAkO$ j|JYE]t?EULGJIoo/I/2f݀kW3hHIwOt^ҩ}+Mx:pn`ݭ Ll@}j*LnƟk(ˇ3$j+ AӦliMtdaЯqu2X0T}τxBңe >9܄vÐ |{ %'YPlß٘ ԩ¨7a1ѰA5 ;ha~2.j̏*K)XÄ1'JqQ ,c:41tN4S[WGww7CCCg;TD"AGG,Xbd||ܢArtdD ]t\cm7ԑ.|E WCHgDD2IqQFI$IuV'dǎر+W}o|455xbken:~q?0ƌu!`;uS&R208>ğٟl۾Gyow>7Ľ[o"セCwTVVw|G}L?eEvmk %lOogD% =b$[zLX#Sj5dQhԁKgDt]W ^j6@7ˎW& ^j$PCJO!)E,?$(ڊRϚJK'ә-2L !Æ&\.RJ5WjKR5mJ->0iweCcjx2lߵ&QӛfPdL U#_0)++Iu]YgMOO}\rɥƤS4>>O?M^^>_y_ŹyikkcѢEs9< pH;ҥK3MX֬Y-[1s&bW|Xx }}}|;F}}=]fZZZhjjblڴٳg3::SO=g}6H{0{ڸ+P#?}:=-_Ѵ)HaBr/7ٙW(: M2anf#"2덊,,`a X\X}pg&]nӗQ'.{ oo b},Ki:2({`Ѧc|KZ@? $g2o3SnҨ3yn@e&NYrhxnca6e3bs;>~P'Hf@lٚʉ'AܳUnNˋsYt H&;wth,8DQ]O{y\rI&ĽK=椢lSi8r0Oԧ>k_ƛn~J=TH`fZn3")H4 @yd_k+ /lߙ3g<3|gy>͛tvt0w<^jl‹/]wE";w=x~V^ۨaϞ=̞=wy={0gzz{x?$ͱcǨJөڵ馛hkk /[V~z~bڏ o!k'?#)Njjjw}xIl߾]v_ooo|Eqd2A:fժUyW-#NӸfҥ̘>]?quW_ne \o(xGJ_VGH'.]%^k /N rLu;0DnN-Z[(cԴ98`444ht b,Z[p]}-&L$I&44LcڴT*E,uwc`|CC *Hӌ/\x"A<'/?D"A$VX,']s~ENI,S`kDflVQ:[4Qd"wsHx[0OlP C|y B-ݢ-ݺl?%cj) "dŘBEbQ'O&|#y 3fڡߨd.CKHXYD1ddNDl hf\Ro! 5fHeÀkfVg3V2t``1h]4 `FL vA:fڴn^[>yzib/!pi3ēO]H;nNb0NM_1.:;8NMmiNVǙ5k6uuu,hj+b&=Lq>|2ctYh!CCC,;t̙޽{H&S4662::JUU%c<1m P___okAcc#W]u' j2ɮ]Yb>mmsSM>/&ꝏ۱c;\p!\xᅔÇ)((ݻwsy1w\vɶm۸k$\r)۶mcɒ%UqȎ㤑^84i'M:I%IRSIR{L̿$JVi/N%q)Lut NynZ)cSidž!՟#m\eX~8ҁH-8@.Lw)7cc%=8>;%0-&I<0cyS;ǧSTz̀mӣht,? &laQϱ-?{ڀ#@̺C9iZ4+ 8fgH68HCRJq,d}"_m9#O]!830{ ]PH"5oB T%߱3n/D"! 9{co-HD0c,(122Bqq /̧^>0:: ӧsҩ˨!˧_r4pɁ<ȃwQY8D޴| F_r> ]ǎD)++R P/>,L\/[;sD}e`l#zAt/r]?/ŃAoG0&c.>d",eM?R==]JcRncWJ=.! f[*[;@eXZ? }9BFL:&Cɫ aӳ`构*pni/l˦w-5;?1R4LJ+#f UHVl{Ίo̱D[f2-[V 70];;;H$MB<6r0Hn}\$n~I*DD"H!QW7h4b9daQ1G Xrin@;oPtBN^AIyvD"D9:6G,I;lDdiϖUL*LVrac$x;fr7 &hXBm*G}.*c/Z:-p63+O}^DxuٷeK̋>_eAdf̞4]'SM:8~:>BX...뗢=G}R$#yj/ͮ-G}V3goth\وf谫qe-ɐ mB٪D/{EȠla4yIqlKXg!پ=(wkH!W^B{H#qaw'AuV\BoOADH d$>e]Ä_PyO;΃B?]J(BGs5Xg2P'KŒ'07գjk?,̴3|~Jӥ 0L?ʇ;C)M 2&‰o )ZڟkTBcҏ},3ʐY٠v ΀Mؑ]3hzX`PßOxɗ ֙ kLQN6`eT88M<ͲU_ _P8#wK`r!@-BQ>`au>*PI6w0[[d!JJGƤ;J&2E%=cu(B.5NŇf+w|(͠lz$ gwi0iRGd2 WM+/[V3qj Ӊs )2p*= oˆdQj0_ўq_͗aߗҩ@@&JjU4W*̃ [0贠iSO)?wt *5*Lj>:SQfwH}ƙŸ[;ZL #1Y ?e;^%;ـT?ڕufNCd ԗӤ[4S:IMmʠ-NޔܯF,PbiCX27)O JJL! 2dz0~_YE4bϿR?Ni(-1o|`X5>@;<2L*鲸 &Xd*-=<[MF(蠦`BNcTPzl ’NyWرs'y78xiimahh'( Az#1:Ňβu-qx7HУԸxO1~A[P_*Im~K&n5,S`%>6oެyȤӨ4Imٺ BEUSvw))[V.aY5D 8Kʄ!WK9|}*k(KzjF٢T {%jTWɫUU Bqn> >8.u<0d/}ڌՎ1O'kO/NڭTZ[.M&|kݡmXϯ_ϲaj>L4iRkY:6h6k 0ô?`cbԗALf4dx6' a,59OiT`So~w?3Ӿ2,4Z~AzcdtÇ0:2±c%Q_U/qF$vidR)0::ʎ;MZ^&8?gaя~oO>IKk;.=ƍK ⾹vӎc 7z (}Nsyq<:vreYRQZ ɫʏ~c=  "re{i'́b H SzpaЛdo$SI[ lgÆ O|XHoF'ԧu]?ͻVo?z?P;Ʈx׊.ϥM8qH u3W_,'3(B ej gS7MW@n6}\ЙfS2p C%N>3dHԇ1=Ά+f YeYar n׻&o8%Ll6"K߰? mI ^5ȀH$qx!i'T-jnz"?ӧO'M6F[na]$I~"Bpu̞=~Mx< _@^^oȑ#GMb޽/q&xgBp5OQZZJ?W]ugCSoɟoPVVƦ>N9CsK vixI(r l޼޾>._~ymܷl۶ !7pqFFFK6nDYy9IeE'x">%6P__<5W_ó>CqI1g&Ɩ[9x~;Y×%|I.b^|EGFnd!|ڤ.vIDn6e] /HII gqH#GpхR__ς xKyꩧpWd޼y ߻nHp lx}ڏ322M7+JOw77ߣߌ_T\̥\OSjkk袋1c2lZZ[wƛoɑv/¦MxUh7m;ﬡ[nX,Ǘmf%Q'cii ou>l8h2ҷ]:Hϟ'3z օW{2eMVJgY:qg̶L[ 2`g$/?lh^&kGӞkΕM?tjΠ_}!#R7c-BjkkAaASLZBTĂ,{ǽ==<][rŗp_O[oYzX,/~TUUٮf=<=<b=|;uߥKrWoJ~G7[oK/mm%+iiiᄦ&ZZYd guW_uw+W`_k+w|\%L^s {nON<'i&lWUgxF4w6ogR]SâE89Y/sۭnZ2gZZZ+_aŧ6"`۶m>-ʸEyhhh 㼿q#/RTT~L: vZPXPgݺu~:M?& e!"CڶUBpi 0dVgX6&B9?L!:& 5V_m ÕAԛ 'a5z4fǀ|0p.yF~h`IuM zLŽ;I?"iϟOMm-EE԰q&]`xDrmƶmX~9|GR__3X$L6^/2(axǒS)|M +ع}JW:ͣnjjjؼy3;wb94551PUYlkN+#G_P{Gww7Df̘Ά7p +%%ر۷1ctqǎ;KXd +>).]ȵRs奂̙3SNaΝ̞=)S~(BbŴeΝtwwSRRBss3Ìp҉'R^^Ny$S)OÖ[h;ƢExmj;\z{{unime=`9h4ʙgÇٽ{7-[u2> BDhnnfƍl֯gعZuu_TħW . /~\zU]e:| k;d߾}ٳH$‰Kb uuWH}O!l/HѓC*`3fEtyFiiڍOrֺ103 R6gLyBFC{Fe2k,Ň-+p|XE &ʆcْs׃);^և&4+PX6und'AYɀ] ̐mӌ~?bn=ik±ȖX_& f% sqE/P[Vj473h3<4Dgg'8ѣL>E%ѿD"Lת$bQcѢE222)++Rjkj(++cҥlkcttٳgSQYI:.P"`PPPm8|0,^ff͚Ŝ9siA{aƌTTVrI'qa 9m2lxtPC㒴=Jcc#sa̙8R2w\j)++r.\HKs3uSYYI<SN>={PUUE,Y]v122Bԩ}Y 3fZ8BFGGˣZZZZhhh`޼y084ĩBv8gy&x2.\޽{I444hbvҥK1cl޼jju`0ooչ疤sϥJ762cL)..&??.Kq.\ҥKG|ձpBinia3g`ǎ rYg8o[lZ>L,SNI `phr֬YC]]ӦMciS2w<74p!)uul߾%K0uT9[0;%K0j8;vpꩧR^^NAAk֬ ?)SȋeBhݨnp|P?dx Ɪ %?0TcE5Dj)^&~/ʹ^@M~E\(|XM>dFatp'Ebl0QWZL+}tLZݖE-ʐ;*eOlIi><8>_ O=փu pkcL5aE$W/$TT 9|LѲ5 !oQUYi&g+1Faa!ݭH$aOO">08o q晔ڈMdsldf֯ 2 K>l+q|M 8]?o{nYfٮɅ&߂ayYR\}5΃޵kVV^Ιga!o3+7 7mHeeuh0vEUu^xnYXygꪫg}+Ry)//s^x,Ce"%tǹ rv\ cWuKss3ŋݤ̀./Co^ S&Ґz&gRꛞ֑5g)4L;Dm} LLd*O|)5Yk{UKt bP&¡xH^RMW] P"̖2yIU!3*cG3G%r+NA3d:gm m|+\s-de{68a4pBl&G6\a7Ϭ~A9{})O>A_k^1Qra'.N5XhMV- %738S"3g$^hΤ$zp#L8~[&n}fRj֛6` el +Xʧ%c=?+SvISָ5U4}dѷ`gl׆ea|xP/6}6L)  VDfo`)H 4MJK@= 7̌Wm#(#$ 9צMk,669S^=ޠS œ^zAG;-c;E(ybꅌȀ)|!Ҝ0|0vaLF}6|0 /îX€!+ *ՓepЇ3CP]?0JCBԅt%7;l_X0|h6ӣ3Gsح?ui]]jr4EF<2nkڅAVSF1h&-3Nao/;__74:3'î!_(XnN:ȺDYk؍J!!vԷ m9# .LC>tE?7?^a؛#lR'^ JE^B;uJ>] |hخEs>Ox`TLrdgKZ.^QVe~Dzm*`ICJA@r`2Dj`!e7h}S4hM4Xd P yfUzCӼk &"Pg3̌Ctd?\5L__Z߆cY0t`ƠΤ҇drce|F(]xPb]Obhg_Kh_S]rv}vgѭ'5k P5ȢфAZ}-><nbaA7šaXݓL>:iѪ>}6=L>2@|7tP}%< [iW KPX6"*"+=h 6wGI?ԥmMQ[8GiD% @Jxg؉&]Bi6 yɭ|"-#,>B~]sK[P6g aZ)jSB2-R"?+hZ Zu-ۄKxOG74WaOe3.@1̕4t3&P~KV`Qk{ )Ҹ0VƄ'V@2i6RX0$~Ol[UP좃]](+ZI\,,#ǜA?S;:Kz}Qߠ_Ө0d)edf`6 3qضL^> qza >AS@Ƈee2=ÇLAf,xTlU.ܖ%VEf@Ck|-MݦQa؈i&|KF^,ONyz!GMR?r?0:a50ac3|;Ȼ4i6 qI |ؼe. 0٤׈d{;c,PUwγPqÐ!@ 5,D'SJٖ);>C[Jx3[x<9?Y, D4^1?fWCb$S)!"?.U8WGF2)R.==::'_V /`DHx2#MT* Dc%mf̀,y{mcqocdlPSZÂDE=xQh}9w+̱ơS7wvSR\B~^>* 3:?svCOO/EED1;?Vdxh:>.+CCC R^^RL&R_OpTEA_dh$u cpplEJHW|x iz{{𞴷wc֞KQOay-,;TsL#020sj%))."/&(-'1`ll.fϞq|222B,#//T*Ekk w2 A_p|)..Rq$3g`a _JI%S6jN=|tD~<D"AۡIQ^^:::)];Xꋟ 1LjC;)QHo0CcVI !hw7le-5aa?-dޱI[O?Xs/gǮDQMF8QN%lejAJDv /^L4#O^=SOr+,J&97_|u>wN:u3ij$"VZhlD2oKGZ?j^'ds>~|lj56(0T{.~O̘ /a<~Yg_=XwEVtSDD"A^^B(yyl[X@I8i8:Eݓ/Q*#\|4*dxk RmJ2qkru?.?H^9[\4LD_ā3؊ c`wsldZ5ʌ)U̚5p_><<7.S gĉRWU`o'?:?{]bO#Gyݍ~k-=]LjD `͆mh>)sf(cpA$ku 4}?eEl۾EF+/xN4.Oq6<,eӧ_RBZJ##WWw:[Zط{VLR{Myq,\~s̚9'~ѱ$Du%gs'p fT24Bi~~F7J~`Y~=ྤ1_{K/^{-g1?`¯rSEJɆ ,xǎ㡇⥗~C2_lݺ)%;w? 7nDJIkk+>>|C&Sgg݊v{{{hnn?\PM4.8VuΉ{6>JX{aFM)6 0lc&[NvUG?av;LVa*XVI)I& Gqq1xBPc>FFFn62u3R] g_8O85oR)ww`?cH-c~~Y&\4LD_PȤ9[B d e'{egO)]C= /vod_?$I+27SS^@<c_zmII3RV\@Cm9iGr/E*(.a!Xz5l޼roH$կs)ko_[^z%jkkYv>UQQ>ӧO{J yزe .wN`l۶ *++yx뭷7o<#>|n_y7w}\r%8pV^g/^e<v<{y'?ꫯ3^9sk͛D"<#DQ/*++M}}=;w_pWEEE[~_n:/^LWW? FFFY~=>(/~l:|Et=,8{<8Ö&PdN>[[|d3ɔd=kU>*|X&M}!w{0//)%CCC{#b_"ee|ON M̜9)%po/a0]}ʒW\8(,[2T{;C}DNر};MMM_Hŋ#$LR\R޽{9餓&w.N)[8K̓;U#RYy 7A;X: { 졣{ct$Or;nv9h% ݽ&O24C2{!No_+3gvfϞ c=Ɗ+زe3mmm?窫"gVXASSk8]w<4[n3<'xW_}̚5O?aٲes=3E?ٰa=ӧ`hh3u4)++{@4qʘ6m"//wyGy5kޡz.׌M7?L"~0۹ xWhii!k֬W^!h"~)z)-;*8 .rΝsB6{<8\Z]"\'Df7Y>&adw,*|8&ӿ?*Bdn bL'qyj$,hYfGW-'Oc@R7%NYYhFOJsBhD$N~!LL9ĢVg-[l B3;* 7oqdDȋ-rɟDQʯ$^S4IQbXd1*{2NyBǹ8|p=% KΥ 83Yա*H)F*e`h uOLwVUdQPPȂM8٠͗i SVV8y STTDOOZut)SP[[Xriiiᥗ^"Jz?햅 Q]]M,ghhi̘1a:VZ/~ oǝw ޽{׾9tXQƈ  pw$B 7o>(˖-㭷[oggѫҹsP\\LaaTTTH$ L&qtuJN=T."^z%8?fh?[= }IY3-l5qi͖e'Wl;a%aMOddpdQ.8oa4LVfa BIRV(..۫.=|'rIRDfo> 2N; [q'(Дwwlڴs9c6f&8&Mƅfklv3dhiI1gJp ǁu: Ncʔz8H4e]i>1Rcpy5cYV˟p0 Ll)1QXO4"87\O[h& $HdۤY/':E@Ï/^?Gy%to&$^߈!=<–~=О݀d<"2RTL<#/(1F/ ??rR=6%RJbQԲ(%y:zazH8-\NqqC,F~~eeeӧ8]t0e*Īwihζm[Yx1ӦM }:;;ijZ /~C+>Maa!ɤ{u޽tvvPQQNii6l?|<do*vgv& p F6^ƙpMa<K.z AX2`N:\#|dhG#͹`沋lUD:1}"SBz{{$ZO ǩSqV0eTdf:F`Y=}KJJ'/picS>d1Xulm6Q'D4ȄGobzU &9n>zzxٺmIH-p+7i^}g ?4_tSj|J5[H_&_LIsN0GmyÄc=[MUe!4 ؅|/&1qx{X̓k%n&ذ:_UnttvqKs\edVޞQQ6/M̮M-53 8we̞1dohz5wqW3٩,MKC3]v xɧҙ-ẩ7MRPVN[y^/\DF3g2\n3ӌ-nࠏ|~wO;HJAӴ2~!!C!bxN2g2j8sJ<#G )BKAA,??MAA&;5(р9@(^`vdQ'??ߴ3gΰrJRRRlz:aoR R;B'C 6UbTHx2T}d 2 g鑨O'Nc/0Wmq)r5'ު.N>-Xa񐚚%iJ+VѺa|AId2sβe '&[IFn'>w+^ 'AZeF3=c` @b Fnn su,Wrj' o-cթ^qɎDuUN0QqT0lX3fD4:aoru> yG)5&͈M瑜ɉ ?觶־V$FbT(\K 䈒al@ C.zz_P? IEEElMHF%j:US~a5E"}.8 m8ٳg)>TGP(ę3 D)""%9%f<$B!>3/.B!Bz @si4 =Q:EF~#=UF4-0^Tjc!@}cǤ4b3ޣ1KS8q:>t-!/* ;E_(D%w6N/q۪夏%òKa$Mn;giddy|sI[Ъ'''۝e8a3ԶL!c&hv9*n6vd8acfײIN*| @?NIˣeɒt??E$yjR@8YPe+W} ?|cDc;%q]cl5\.`JQ|}.2()ӧGtˣp`i˭<ͦOlB!CyچU&IW@|lrUZU/QXEaX|Hȓ7hJ9o @.Rf/ Jܷv KU36&M>hqF\hJZC$++#c?1jV ?`r,˲ 3ҸfU$bDo̶xFz( B~W,$nV5ET;eZMeHCx<!Ldl4Ya2φs\M[X": 32tZ< 'L1Oslm* Nm {vW2fa*!`k|1xOo`dӠQ"}?LN{H_,#b~H_$=}>3nfAJ"g(T_ȼ4FqAORm#N/_aw7^V[K6?~6Z~Ɏ(Bc͎πMOIe.ɨkqܬk7Q0/91XY2*XjOD9ZC5Q']d4NQBO,cg,bᬃB#V7;HWH#թ$_\E,aʰc]ͽcBW? OCOYMِgiM(F ˲w{NX46o=/<ȣ$C0gΞuaɑu'?e] -R=y[mO(-, V. V - YGS6DVWbnoig, H  qŕטu6nĶpÍUf[!/_mvIw,^>ظ_/W.Hݷ/~efAo7zt;믿ljFsm!ɈફKclڲ-|F_A66~Xl&oC2Paf 8IvL:g2ƲXuol\IODk\)|%SNjp8'u*B +ID:] bH*>27NC/^(qjUG'Yr+'e9r~]K8TUUm)4^|5\YWaZ Q؜ IDAT[$LWth4lZKluZSmd+'tY׾5ѣGs9FY(1Q`<'XH۔ݖ夏Cӟf - g-RYf|]?gj ).w~Akb:::$-5-[>Í⋹tuvr*++IKK?hjlkuvO?Ï~YY.R>999tuuQ[{Tz%/g͚w=>'Ovϣrxjkjem9s={IU$}=7pkz4ƏlR:;ϳuV2223{$ 4i3a\nڵ eٳ  [BLB~?/{ ֯۷G(2yASN }8qM~;(:پ`0;Ν;MB~;'NK's}>v >Q /'!իWzkf1p4 zC~n6֭[g  Ldr 2QǸw}>b((VpQWaNI4-^̈#Ì[9s`TQ UxQm, G45j~RSwy^|>[ad\ҏ&=Ǎyd& /"??z' 2~8srMFUUmmmfDi)" 1gl@s5;αMۓ"}.;'cǏSWWAII wa[oɓÇz;~ƦFƌcS{v5.Ќ#a%̚9q*=z >ٹw{nn#jGL_S7a壑0=ʿFFɐtNh1d>ӵm^W]Q3UV˲eկ~~3f̘޽{|vmL8{ÇS]]͉'{9r/~??388ܹsyWˣ)S0o??X`W\qk 9s000Yd /"3g[n9sp1N;woo}[qE{nꪫذa\r%}:#Gd*ƌ)2yd7TfΜAMM-Ք(e9Qe7o===̛7S_Oyhƍ  PXXHqI1AFTUUUW]IUUUL0 N‰)g٤PR\E/"5%6՛5k&_P@ffYfhpM:%웗/h9٨u|ޅ`M ٷDdR36c2Pl$lV$8XYmRe hvA}YC#--ڣn8ôiS;v,cƌa̙z̬Y3ϧr%%%,\8ɓ'3a.\@jJ ËsEͤIU0qD&N㡲j***(,,|>?%L:r|>1{lF+SLѣGr,XM(**pUWQRRѣ;v SNeʔ)5Fŗt-u'NPQQhiia\~J8i"`F9ݕTߌ7|3555\}<#L4ƎҥKimmeɒ%qafϞM__l߾VZšC8s dgg3}tIMME4;w.FWcnVZ';;Lf͚Emm-Æ Zz{{)((ꫯ عs'F ʢk۷דٳ$%%qmm6<Ȅ "bpE;u{wRN<=Ç~ܹsٴis?dҥ4662k,^uƌCQQH`ᴶHNN43gΰl2n7_~9o`ŬYɓ'3l0z-&N.\Hjj*w^n&O~_r;v,˖-cDz~^}]vMee%ŔGiqGb>s). @+XR2VlBr|ER>@?n7\ HOK+`ťPu_fL.}WbX1/}IFVXaJe 4&Md0;ɓV\zu) 77kcH\pI[YYIexҒּŴd<C%i_3ȒIFXrtrL^Q{D{)8Cdڠ )( f+j)h9u曾&OFCcԩL: _\{+ai,\0Na\+B0_U@z-[Fgg'̙3^\.===tttpy>}:.r >(Dff&x^~CϬ,/_ӧIOO7cQ"Q">bANxNHzu~\222 ID'~(@ " +X\ pi\pʗV‰]EDI?eZ] s'}m2DUo5ŒeF G#_lߣ0 u&CUD/OH6;T$Tr{XȔiIh`0dC3tKȐ:Gdl.qao|b&;82U5iĠ[$cvȤN6r$=$Cee⍣HXiF' ѡ&UkV";ԲӧO駻wOPiSIzIDko_;,̬ pϠ!Bple@jg]_.SgcUPyRd+P_HrTRԨoFxr" H" ,Pe$7) ` t>/Vq0B {]!Cޥ8x%xWN24G݃* ϘjˇllcI$:$%M?]2PMsz4H&9X '{'XnP 5}%C͉|TlԲDgu(i({.ϿN,=' *S[ |>ӧOÛ%=#kK|6U"]tXg iLvNDɴ ,C頨-ߔa+\u@aU?|lvDlEiiY=j'J4L("Xiv˵(sb٭c] +'dZ:+XIx9b(\m+Rdʎ06X9'UʘٱTr}6UF*?!Œm( ذ}WmGqO L6d;jm2dL<$FU0ir2.6ou|AN<(XJNoݻ9xY7Yz5۷oaءik׮yq?gZXf }Nvc֮]kZy7c^zX:ڨ>9:tg^U@W}}ND~j ba$ 7y1ECMh@]ї鄰υ"Ũc$c5H4U0yulIg`IuidWdGlNf#dʒc8'BMi2CXeO[m%6"ڟ0eQNSSMT=eUl3MV޸87f)̼Jt`M+ G]hr̶va0:E١?+bM;vHxm.aa9xXL: MWKFL=UGaia&C&[zf=ϟ'99$q:tvmܹsLAAeeeٳ/L>>cDzqFM/Luu5===vm<䓴2k,.bz-BrJ{9FA0ϒˎ;林.y `0Ș1ccΜ9 |d~qy͛ǦM3QPP?w]:::hnn^[o'0xUWOPXXHKK TWW|,VഒW:NM^99{Ԉb)~p% :3U0'J-ZDzz:z+ q\.? ,[&|M.V\I?ֲl2ػw/'N`\s5=ġO[[)))dffȖ-[hhhٳgS 2 IDATTT%77<8e~ӦM hll|~m~m 444ͩSL_|1 .Z RWҜZtZᓃXN*?SDgG.K(%k295jV9g5ʍ3 :R/ZTXN)FSx`e:>mvY8;١I:2b}}qC;Da%*VH"T|2~+d;aea9Zsi/F GE]*h K7;vvMMÿfk?/#?µ1(3v^3KfjV핷OXYm'Ѷ;r[Y7vYf pӧ3|p 8zyHMHNNСCOPQ9Kmm-˗/g<o~HJJ㱎j|f鯯yiii!--44M?V{̤OJJ"##m>|i!`ĉ<䓼=z| Moo/Q^^Nii)ռkܹӔ[WW0)))߿ƏOIIo?999~ 8wٴmKJJ =<@84ޑ{788H0ӧC__ɸ\.S?#2_ARRRz^sΑoE?iiiƁ]]][XRSSr1l0qi)**!8vڊc`;wP(D~~>.P(Dss3:;;M (**|>顠B=>C۲$S?CUp]ӐDzF !~酔F B蠥~aA7 "GMN'5P7XtSKc׋}uddeQTp<8BJ(&J epP@xG J_%_y<ܚFFF:^/`0?4rrsдI.Rcƨ;#&U4η@%_.*O\=zCWΗTUdݨl|CSSLL|MH&4<"2&N xW4M<ݠ`mBF2QFxK͓ˌ+nCiobq*CMPc,UWtr,9NFCFf&(j;--Y7[ʯazzh.R'I"ٓKq#?b\͞~ׯhc~,aQx4?-'w  GXQ"co8?H^}uo ]YܨꫯQtc6=yYI7\ݻ²;"|Yg۶?~OM_+F6#lX铟DiQ >DAN6UX9%%瓓M~^L7 cLsmA#O3&f>4L:PVШiV=c[a{w'6Px;2\ωV~ftL#facbk2&m}@{Y K:)mGo}d\bL^999ۂ+&\kWF\Gi+蜂9P 1' F9%DM*f*VtVxi?SK0ʜ"6K*N)NjЗ;XNi<'nrEaۢsrlncppÇ%P(d>vI V}>'O$Iff} \A]  ^W_˥ o}֖VzwL2P(HѣTOk[;茶3g'-[]>Yx7m6ilĻwSN1w\^{5Fĉh8s/|A8s=zԔ>t7sv`̞5-oe{9r|Skiii!99<Șcxn0455qq}㦫Qe8v8ã_0֬YcŊK)/NmgEn'BuiҊbq!WU: b$7tR~VLd~Njj_,lcŪ#ˌT&ǒP'c(Tf|7 J/89!YU[͓Зݚ[ZHx<:::~<>n!G-2Ǝ;ȼu_Yn~JJz&^FZZ,Y dfdL>E(k۹y饗1b$cǎ噧` ŋ/`֭L:%ϲSdeey.x93ozd?SLf)PSSȑ,Geϝ;Ǚ3gXpFb˖-̜9ny:KEڄȆk#/)2Baz{hmfJU}} Cw_P ^o*{gYB!232HMIi0}LD!H6I6e;5[;Vo_ ;+];wEV' -5e Sy駟r n5kOw3ct3p;~o|]4;?I^~>#UU8p%%毬rrrLCg͢xwy;v fȑV\ARR{v;ngbť~u'>|8y?ej fpi#'naԩ< 5R1#{nmNHF8KF(m7AL]T9OHs]%MG"r^ @Ӵ(;ɶ \>9%)ujXp2* c82es%g^iFg.tӧOtgӦM@-S݄7fFotix^&NGZZZJKKYn{ax^rrrp ǎrbseMMVV55G ''ax^6%==ԔTOoMAAMMi8üyذa7#Gje7ȉu?)S DFSINIv(++c޽1zt G}w?+`xFV^Ȥ$ y\ mBlvpt?RV6F蛌׭{tʹsXqWhiiaӦdgrys){̟7Go66m*>ϼ)α`|tuuS_M8t W\qnguMJ"7{Q" ƠG]]\wu@Tz{{z$''Ã>ҥK3gAoo/逾W+))~QoOʁ8u_~M/ٳygOFF@Off1|Bc`vMhƚ5kXjBdddgp݄anB!S p܄yfw]n("%%7x#\#$(SD[XW]{[!wXY&wy(++~E8y8z%NYA~E- @̪H.C2*E%_^uq"bs.ʲ3)> 9jZPC :[k@MM w 77k&u qBp)ɵ [>aÆ1qЬ#*`\o~&O6r\c4`OKNƥiC!@,b/XQ7+cA8 O8Ęr%@SS+r y󃍤UG<LjRc9aTs##|Uh9evUE9s2ۑ#J~t;қس{W^u`i4s. ˝WpHydo]BE}C[uk.ʦEZJ }g!w?OXz51c…{yᇙ4iw(**geر,Z[zW|I蠲ɓ'Y`Nbʕ9rn׳ex<,_6KUUv=mmm̜9W_}p8UW]Ŷm8x UUUtwwl2Ν;ݻ8q"P-[{x4.;vpa}Y֬YC]]cǎɓ3[nUVxb֭[nj3(..AnϟK/D]]fdd>L0ˋ/HWW/IIIa|aFASSe Ο?Oss3eeeFEgg'---={P(ĉ'fܹ/)jR/ƙh.^o5-D+^N"4V}>ӧM5.XN8HaGzԐDH7U^&2rD9C!6m0|OJFDMF2.3hRkiXcjs(&+y`l9Mb.[q)_<N!;P!2iwi NL`&L mW&[nXZ1'\s1"_VVf… 2eO2#lkssIKD͊muTsa78H_&A8,p{zKВ\'xho3234˴v[x:PЖl;lX_4;=-~RE},T%%_YbbQ@Uuu´@/B9(Ym`1&>&v ,`J6]V@_=BCZ-"E")IAq*utUuu@FfLב#_^-|fɒ%u]_%`Dm2^C~MK=3cb;z-,띭A3Za#=$ ~?/_kAkK pXMhBW5$-)~?djB"CUU4U߷( ZAT4MwjGqUMEͪЎ*홻6~eBb鯦4tjz,#o3{+yNa2[: Gi=0V/_S,Z haǦW [~U-\E`٘j2>O'K5_o#4QQUW=A\|.G>/孽VN.c*Kg!;n›9t FteDըhVQ,7,---֐aXdxxbkj˗ַeD"s}4w###b1NŢ^I:F$xƈD"!h2. ,C|RD:q2ӆa֭[ǟ~۬}|AڪϱXsq7lڴ I<1w8]]]_8䞙a~~)hnnfnnoD"N<ӧٰapVR [[[Ilڴ??e~~ҙwBlݺR0F: ss{7 r:KMd1_aB/Uʉcd>{Z}<[oEKK >(u_x B\v͚렪*ͩ$s7qqZӜ0˸p2B` o`jz1\$нY¡PsJ܅jy2Pvo=(K bHڸ!'j#ɺ7麣{o]92gc4҇+Y;=[. dggiN$H82rv>ext!+4ke*+\rx.Al}C#~zgs06[)J\I6B>O. IF3*W*KebMIBmn6*zqMTYqu+=^7 9@$mV>y.;^idg!ېӕ~H IDAT_{n֬Yg>G_w~ -$ghҮWa:-z2UPn-ft+X@&l&O| r{իWvs9zE5fphoȅD#~>=y4!xzN9K"9Ŧ7[`brO޷1VX,ds9._$ }QHڒaD~ ]݋P0? F3gޮ,MquevzYnKc/}KbJ#k=3GqÍX a[D{N`ҳp9Oʔ C\h"}=ݺ_,~EB Q<`!HU˗3:60yaN=ě\zΚPRD(ʄ рyY&PUժ[pmxxSTAE&R$z"MӘazj LZ;zdsPC5{}N]or}Ze-DYuZ9v9Wv>-Jz:5?Q\}BߢjͅE, ;t_ۦ,d[|'q;FmBiy3/^qtcm۶ͧ f|7-/N{F~ 䎷X@fF5ag/wzx's{Uo*bU5',orP04y~?cc2:>,IhBE>gP$$Pt& 7 $ "OUU^{5xFԊJ2dhpÇN9} 7m"00pᅬ_{p8L `MsJmp˗xnزu .^"3X({;>Nƍ8q5g˴ ,Tu߶!y@}4EB==!rIcinn"5S)PKD~Mk31I[k+19>Nk[;>HhX'O~ZkiIQfi0OH&Qg](I{n "2J%U^TMC@e "!l:d(gXC8R099Ummcy_33bQzC&o>FG@-l᭷ޢRD,# r!nady&:$KYiٴyA+&dX<#G&l…ֵsqOl]TDU 6Ф@+J׾ű]WA=6{{…gc3-x܅AH=^iJY=bo=YM^2zѨ'\.QՃU5F@B -Vqz`\LbnpZRL&3ϝ;vG2$,H|grYB B!1~~_~Z[?dR̻{j$ID<cɰ&4TU7XZ{d$YH2{.~=%\v9-/LOOfyH7a^u>KewlC!2n?{RĆy뭷|>$_2<,r/^cppt:͊+xwYr%hl.mY3zd287D8y&Xxͷذa1ԋ]gny ϸFvji3SS,鶒}.*12$H F@U XܒIAG{;S2nGU$m  rQV-_ny< I2Ǝ&WF$I}1!s(dXr{~~BL l.׽7j"U d2k P,ÚIV^ŁoZ+Rܳ(> We}YSo02:g>/3V\A(⣏>bIyy>JWW'gΞˬ(ߖʔInyEa븷 =U&Ʒ{teѵâbc3t7碑gyL;Qdouiij=zp3/oB|-f 2B-c(-zr5 =[à jy#@s+og䚗jblSο[FC49 HAoīO3@1Y VyqA6*CC/,$$cd+}C\-`gq {9KUVoԚfnҭiN<,[d2ɑGYz5/_A޽n NU{<Dٹ9~?.]9!bz{Kx`0D aٲeV9D"FGGxXڷ qj (>r'Oq;ϑ&$ o #RaڵQ0??O0{M*BH$$\tND^c5qTw r >cW,㭷"˱br$mmmkgf1fw /=hK{55Nq']޼};?GqK AظYn\ΕYFw2"Au]'Pq:-r0w_z"A]1\G4-Tzw<jR~!/$@]Ûa5DkAnWmPBP֫2 ΰּ֙3Š!՛)xY~>ϑiokYcd2hBlj"ϳazff}Tpx_U%R4o}BH0@6 ~Gd Z:~SٳL&C{H4]ݶev}HիVҒnarb7'A鹫-z ='Qu#?a#"VH67OqHCg߲"I2Jm I|K%լG"J%tmhYI"D55|6+ ;=+SSY~8zCny'_Em}2H$\+jx0w-tuu&[6mڤ'&hmMSzVQ+UN wlX<αs]\rC|Y?w;i:::VB5ziԸq0ƙHcױ٬;{֕;5l{fbI+g|6Ǜݴ=G/Y7>@gsN   4}偍t)TM# 225l+:~3Bz<1gܴrEE}ՔïcC4};kn!=簱_(~#Y#`#൐BqN낫A:LuZ*K[+DP&ّi=_פ0ܿƵ]uybǨq%'}s{{;͍GZwÄt<|aK+~$80? Pc[}|J1J~vܪ "`xtp4nMW@h<΅WhZB[{;f[ZhIB2" Q\Cx^hBCSmBW~t/˖-cɒ%tvvZzw{gH$b+{|@?Q(i㱸3Q/hoo'`EÕE{ZǝoM6 ᢱ1oem ey8gMqelMr )2M}"әB@SuIb>])e& %0q|/)LF(hx'#D*hh,"K[j"޽ʒAT(hix *@UP1Uy2/WTzgmMM`Cד7τY<=څsH}i1`dGwR{%yrW7'{+zJCaZ;:9|oڈ(h ùs,xY.V*I ϟG$Yfr9Z;jsʄs'r,k մUql^0k띑dx.[3kp-;lVSՓ`ʩcG/-[ow(bFaaOE#ڋewZ3zy,ƃ䥷zޢziUXLi x-C}1TT8 fˡsf$}2Z+'EKɍ^IӴt-ы&to,ߠRGi_[: &[vpK :{I{0?Zu5ÛhN.2+` MTK%: HR; u6tX{chۼg6X=8#h<g.3ӀON#dzEM=0)*7QW+'+B*% :\K$ěQ|殝#ދ"B40 L8.St0e ҺGK@WASS0[FJE"R2Z062ufNI'YA*ƪP(DX$ :ʧX,% UӬBVYǀbH0\ϪUc_+[6j:gBKp84Tbjn~]M^#۠,HєJQ(d::F7iKIR4'F0ҔLK.zQ,lƓje5ιMf`t+,sy--'qu6bIhNҖn[9x0UߒX*H"aF z}1$IP*3mTF[ }Ugu[q!F.|p(TwR Kecҽ;7 gW Ly+^=#oƋBz6ǧU~]04ף+w^kPm=~<뽛FhE c Y>DJ}uu%KCWX%\@YGQ E)A*>9T YB(6?f$rttp }i^}U}Q8|M(DQ**dP0SXf 477#+2zZ3@={5kP(?|RȾ}c^xB@\&  w4kL+ +gY A8ZNf9es OR6$I @*(1 KZ f01'yr"Yف(f6A1&W B@E4ReG; MD˥LNLO$yAJE}:3(FæbDBK/WUYn)B4ezzP(O~>}>TMd}~ssƁJ^|o \V?g%R*GDfhϠ!!>uu7cn.C UUd2A~ӟ~|rV=Z)T#i(L4uvj76nB`4ϼF^d\3a! VO6wz<6Jw`zO#BKT|\֣i>uTW {R՝7{Ú&QLSL"B<ɋDJ+(M FPBa2NS"hZ$+.'8wD9;K0~dE:)e)Μ`f7 FAh;9p$ɄqLO֯u *Qք1\(KVRR3ص, hfOҞi8%pfsY?1]]]<'ݰS'OŁ O_h+=ruqa>[ч=M8fժGd2[ZXz,Y3:t 8y$MMM:|L.]ƪի|2H07;G< fJ>rQV xGWYv-.^DN'O$3>>rl.42 L p<+V _(o?llxf5?'Mdk w('SjB>DSUАe}sb֢z]8u}ky/Ʌ%6!`jb$q}լiœS|2W3z>ѣ_}ڿw8t?;;KEUȑ#tvv233Ò.P5ٙYydE&R, ?)ͩϜ֭[9t022F#j\|6K0M>hK֮[˝wwRfgس}lokܽn~ 344lp#E1EhI0::#~"mm|GU6<s Jr*zHNOwIj;-޽EK ˈdco}ULe%YһbQ2Ks24O, 7 H&lٺoF$VZ›l!ZZZ%N {? CÌOF}l~lv+7|35)lݲ?t:ϧ0>6G{{;$ ~v(<ܱi:=Y=mt:M0?9,STL줧<7I+'Nн[rAUc:wpƳjz%;D7QẶjou{,l_f|Y ýv%wxFw7N65~F0dmxre* o:TY;:'޻J$I"NrJ>CCC"lv/~A<'J"I1<*2B8ņf$I^kK(lذ׬ٳlv+OKϰ֭g2B {O<.<$Iv6{w|&=ZiY,FF;wՔ]jw/I=^лFh4wF] 2w$M=Y z`q1K9mؘdhiI;&p]3[LAN K%ORanzV1BN FޘQa#Wb!b82an FarZJjf efg !ȗ4KjΟ#˱qF ~| Nbmۜh5W#t@Ow{ڵ|Si{WT(tFY&Hk.3GX*F߮L &2Xra\` H\)dllD<પMHOf!‘0zTŢ <Ҝ"#KJE!IP()JI$YFf`biP, %(XQɕJQSɫ*s%Z5\I0+Oo*cfj *ϩwq*5 cx(?@|11O4`[#]ye*V6_t/l:p/gfjl. B4pm+/"MNw وFX>!/'9wl.+r+<#6FO"`Ջ^ Ls_k׮Q.g׮]y߿۷׼鯪wz{>###>|^jxk^޽{MF[cǎk>~y7իW vSbMCXVcUAݾ*=EQP|>0RX/A"@\FI) Pl6G$RPJ%=O[UU|TTP0j̓&Ȳ!WWW?H |a"*TBY)>EREX [TGTa4zӊu x9f@\-OI֬qae_Y'IfG%عiZ^r 2 ,.fZ5s~w7@T+ B($;,W+$qNLA$70-~v/^d۶m$I~alÇyYz5CCC[@ ~Ȏ;r ȲݻYz5=|K_b~~ϳ~z:;HR\rT*7 Rwuq⋌H$(˴255o'?I^|E8~ϗe2,<7=^}U>#z!~rY,@|>,7o&i&Jo_yT*|!z*ǎc||zg6ژA$R(tPwyp8~-[k.oR(;v7 ZZZ8}4>S=55]w+B&@ydͨJ0d޽J%>O!__N>gpp˗s6o/KxDQ7QLJƒ>ȅ {Xf '>Bnʮ]}oD"AX>`lܸW_}ロOLOOH$D"|`|[O~n?ϝmj:uO>$O(lڴ ^{'|b~|IQ.ٸq#޽JB*?eaΝV,_Џ=8I KьE 4A,(jTAI+oY(kʨZTqW%8_bٲexz`g=;e:==#GqG;z(MMMp^o"~>###LNLrzo`OvN J5~ x+W׿َwBX{c^yꩧXt)'O Mӈb3,/_SO=E"ظq#BC:F}Ek3!\EQrk׮WB!|>ǏgddIb(bg{n vB 5 _ĉɤ%-M8uꔵ5[8{,O?4۶mcrr)(GAe,/W&ol`[*F|Gw}455 xꩧXf Gtzjp8LOOO=LMM M%.Y;o 7t$?~uɡC׿w]>rE,ì^N$ ɻxZht1IGYԯ$wwid E@)JնIBP(TMd|iS\ ɲLޘ#56 K%c?}EQar,+sFJ%\B$g fl }v!3 xn=[Qz{'K`$zeP*xgsaB0o255c=J~~/?W* l̹s瘜$Ͳ,p1V\ATfdtٙfgg'wߣ$Hss3[oSQ -LLLXA^l۶gN3xm|>7LX$ݚ&˳a^ |s兟H۷s!$$}tzzH8¦͛8y$WN"SO땹]ϩ@ EvDTUHSaima}!t{݊kt,dFƉ&.#yQ30xCA0`?r~rT*Eo&ͥKd[6¾}(44 x>l G)·m~w~v}%EX~}jٲ7551>>nm\ǭ::;;KXL&c2yOyxg9r;v찼I搣ﷶծ333|>">|JJ;wy;9 a 9XIɒek͕}u߱}缰 擃鑏%L@ $"18;7?nu;n`vW՗ꫪJeJ: IDATۥ!633C{{;tZV*r~eW08MMMrɍ%Q4evvb(UG0$Nx$@IrގiOOOI&$ fgg* Q.)kHRs|;W0;5ES~Wם94h eR0 AX,FrOL 3NhxRl\>OKs~Zt$B:T.nO)沄a]|~Z[[j 5MkFOSTjdS+Ϫ;w/0 200(kˌl PP1[y*:;;ٷw׮^Ą.s#}:&Hk.޺~ 2N#wffLMN;}{h4Jww7==!;:dY.ի?nr<|nS'OJJ [vm\|7oco~¾>t~Pd:\J׿5PT*Ecc#xwLT=NԂhvAV3CH#=nϖax<^{9   dCsS1*LLNS)W2R =˲QxyԄ~YΪ>pEO22:C(R2koR&9rm{4_/" կٸi#;w͹g]l+WL019A$ѣx^]LOO1|9gN!qx{NΟ?OwOYnFGG BZ:G9,LLdԤUI2QGVNSBIZuFM)pLD"!MBhfFyM#mB!9$F$,sʼfǟɟŤNFCCUMn¶wBx<.Y Bpwm6h:KHXpؑx5N20W]ԫ6R3#!>~kY{tK.1 Hcc,JEp>}Gcc#?Od$\8wK.`lv;y %_O$a LsSSSo~K\=BP(xI6c%GMod~F.^$D8q˗/gd,_2VQ#!jahY ݣZI._c'Nڕvwri #ƽz|^>9~\7h5F aN7M LWTeEz,2vrGz_M+l/^̯~k";-_f}1޲uAiBYK i?"jjh, *;6S1UڴڃPUrOgjqV1͇{^`7.h'W7N}j,7Ms/lz48É'^w7t[rY$IZ[[' 7sfY_)A,a/~僴67Iӵ`A]`>R_55(+U45(O?{)FOoy+UuU涬uРZouci8m9߿?-'SU]Q^3?}2dcaOMp¡QziNASsS[OK[n^48p~YT)vnFS'|谧s;_׍''zD0|>5z=44$}k>`9Lz|rG>! L f3,Eh e#͆X1i055MXE?p%r L$immlN'F4XsQ}T2=hWf14*ƫp5MSV-7א~[Y<Ke .Iщh vrzj`Ho/]'ijjdrjD)>>pp8LmXa93|p8ı'aRGujHy^Ui؜&U =ǣO.MY67BҔ4Z_xGzfLc}!fx:e(NJ=xA:Ŀ9ҚjSQ tU !SՔw\FaOٍTM3ܨm^dEg1פX,xsn³3T*z:05_|G'c)T>u N*O!Q.yR:9v un !5UNӅ6O;'ܬjRm:Xv Tw z{ҥ@Ҡ^jf\_ƫT.ԈfI"0r._ x+cc466J6пԲA;j߮t3z[:,lHD5ˤPMTJ645.vMkũh;JK1 -͚UZ{<Az{ :%MMM2C( ^JL(R5AFi(u=٢^Kpq|°R6;m 3˦f-tEG?*zd/un%\+ Æñ1M`8>deViv4 GYYt>\hWUBHcAR$;h4J*R"< _ yr/2_rA2l6wDxwf~}kqUy}yooh><;;K0D4>gy;3'|bHXܹs,]P(P(QfD"r9J%0lnZPR$7|hblڴ|>/aER\$n݀z'ַE69g6m$zi~??7,@@)3744HB4>P(̌Sϋ/c˖-lܸBw&joZ\FmaPY%D/R9zRLCL,4p8,G&fWc''iAMӈŢ|>Z[[B?4ѧ(A_hVhsDD#x<~3^`= 0Zmȕ*K)da2f5G:لT|*ayg MBɨTTV=:H򡌾l4X);B4VZ<ϧ G̭k($ge rҒUoMYKVy:{.ʲui14UQh|E⣞|XȿZ@z̙3;lڴ^z>G}W_}f/3ʎ;(عs'W^T*tRs }?a~iBܹ@ oog>Ë/Ȳekq>cƬeX;eQyGN^8{- 4vh`&'O022vQˌOLgiOrE>x?'GC> .r;^s_`YJ#rbl|!tT:]2F:X,fI&\|>$p)ENfSwҖ/\Zm#2%I311AXqL3MҸDCnf85.=wUfckI3C-Ҡ|<[׆Q9k2kccc$0331q*BP$/G|('W*JlYr,eC!|ɩi{~SegNL,`7]H)Y7z tC2v:YGl#sA(QY VʇV.l+ìwUٰa`cٳ>$%oͿalܸ{ϣik׮_7mdWU,X@Rsαn:oڵkٿ?\s\ ?44W&Gɓ,YD7tܑ7117 N>͂ xW^{a<./}12dR.Q}>,y|```@MWWgϞe…ٟLL&?y9i>>k׮e[OGG~ 6p 2\t)=G !odӦM;vLb hmmd8fffL&}.]J4ef7 !77;Ğm w>Ӆjb?\=XsKډu#;:h¸O8Fχ?>O 轟ީ#,lt8-T oU>'O~^/` z||4 h$BKshZeT,椻@$`,@O\! 1L0v1dBz>fgf44^{-놆ell>EcQ!x/q&ٸi#/b'@ zh7rU G rEV$aɜkö> ,2v攏W W~ѕBt&t/V$dR6GGGJ8`Ղfb}yk$ .4qmmm\s5~V^L*6l-߿MӸyD"\wu۷O>/˒t:MRٳ >c6l3<{'^xZU{n[mLo~NzTJB\^җ+W.dV.zƒ!W4:ʐ,Rٙ)V.#h3'O)A<&&G  42ə͑8_~8 q4SwT 2 :"0BT:MsS#bIߡ 3HT,)+%9W NA\z2/A|>vMMOs~2.B t.l&kρJ֭XFSc#dfJÔJ%}"Q`L.O,(,JXce - h+c>rʹ$``~]KW1v-/rR*gNq)-ovKKB?(" o۱cǸr 7|sM K L'gAN8-=z)шp{֣雉Gi3;Y~_裏Z|>c=Vs> .#`\7GVNVuJYǚIku |ŝ7Ч UaTIS~ښhkk1=AOG m-4"4ggFG/319I8TeJ^2\W?y8%S2)tT{Ob-4e ^M[4oXOKk sq֬]KGg6ϝgEz&mQZZZ\|b[jbJTٔJC}'^[>a٧k2+>=c^9U7Ẓ6LgKuLJREE:;޳I;juݝtwuhaF[oC7}*s鬨I|^4o {Z'Wƕvz4 <58[[[?8Nn=nn[юǞ6Nq92܌5`B"pXaNAUZ6|&\gNV!9J[K=m,Yŝ |3LNZZOOOX,|'oB4M? !x)Dw^t;;;eӴPژ;e3U,<s[edN2PU׮d֬YCgWhA<ڡ|rNE.ZZή*+WhjjcQvJh봶1|w… ?dŊ\tK ǎd̙a{]b(T,HR[hYݻw#D5k۷~C^{[ȁٺu+33 IDATrJKGG/^yw׻,^oI^zE[Ylp1b|.y{PKLOOS)Jzm(FjyT V `jTmd{qds`鷷s9 |_NqnƑS~7fN쁹tEP+IP.33ghF_JTX(p)Ν;DzY k~+^Ydeh-A,!꣥)L{kG(vtPL N3;;K&FxgfDQB 4*i 8?veNͳ(2!imkefjG#/ P$PB|^hC~N{9n۶ ܶmlxWT.AE}r-C޷w5߄mPkVBd2MC:dj: 4ztXi4)r5R5,0L~5 lɇyU<'Z.5㼚< wB'&G}򐜝嚐r{ 6 Q,~ \kgB)ZYeboҮVW#Ϟ={dR'eɒkxwЀ{\.;wrKx}>;N$! m0^ %w^>K/@022'G!} Geη>}t:͚5k{ Hg|r.^b``iimţ wv|GM"] k֮E zj,YO,Z3g8\ps$[ʦۢ{ Mnnxpʹ|!O!P,d e"TJ@2 BrxR4~& ,l;EMiX5ElzO6M(ų< TT:Vݳ>[>Nkz}P GxvYYPqb4(akieU@NX!Z(ȝ E$ģ!Ϡ1 |>\iu@Txx//bF ZUA񥬍B/m +,Nק{?0@A{G;~@OߢEEfd2Y?N(f(gNx5\8rh%1cmF|.koζma50O&<XڵVy7Lla wlXzU '|{ᩧ̙3>}o1r!'arrs뭷w՞+I}>޷zv1X`U{4}{1JTݑf2 G5f/4. -YDOohh,ʂ466LrQ.\CUcX,}zX2E R\?A#(44(KXJY_-mL$t|>;*}g5֖P(z24ӷb\ҳe7f,0XSϢ2^8ð*\,(++D54 j:;U f~5ISMXirnUPCO}f̷J8J~g8"ҧ.M7 A4 ˡ~~~Og(Q.b(,Js9~?MD"Q7{הZFJ!iJ\U-ttǿa $L۷~|>D".^&Dr D?8MT*&'&iim\*1==m\D.eV&|{|hI_:ƶnn6$q'~x/+V ј rm T<뻃)J]W7UޖRF󩧞ԩS.??y㚦\xl6˲e8x JAZZZؿ?R EJl.GAzoK2TJ^Wbd( dY))LX]v 隲09Z fWmТittK58#:.Xzp0Gn:4ֶVI@τ-*> `-]an>ϟ?ϟOL?v{aǎ)lݺi8͛ihh{яI$xWٺu+^{-O=K,aܹ-[d|2>}˿P(xr fٲeݻիW'<£iģAg/wc:C}~0]-1{Lrģ ~c`ocI,ee(vGJ馛x衇Xz5ϟw[tvvrM7qN8ATbӦMhF"?!k׮exxXu9V\9~8߿X,JC;k֬PH$ªUN `ddt:}RR,sy3dcM:6{=uS44P=X8G@%l 0a7OMS$3D H$'$ y T.f 47&o(5@0p4$Zi2$NYDjo;Dw(Vu\;6wB;Z{סJ*+g usn45Sθz ˹y^x l»+}giii… wy;vpu JI$=z'Nd"`i69tRFDbK,^cxxXf<<CqF;Fcc# |gbbǏ:M 8YA,M\=I~ ã7ˣ"LFq`k!s`j6EmvoR4kW  BpA4~[+o)+c9FG/xʕ1I&!F#MΥKD"nfHqQ&. ) G9qOTbG]OLS6 rB --T*Xf}$4.Rk.t̫\,ez 8yd(ŽJfZ+fY\v\=|W77zHer$әzJ#ì_f)s' fEwɹxЧ!$>O\,P(} WƠ@[0|,g.^GUGY]V`Qk0Ib+Qs2SꋕNl#(gY|^ڛ@9X y|>O?4'OGe``Mz~y`2 ]b:sY54f>|>O6lVo͙C\l6A|>^aMP,S(eJ |̉#4Bز1VbH$r9 "ls=ZJos oY^cDLybPkswyyyVq{^L NK@P}A۵*m08_©Ͳ÷T[;F8vb^Z( E.\@PdٲeTeqÃ[k,IG Fd40==M2BGQ655駱mT*Z[ZM[ oB<=;& <D´O4WzCTԽZgolG"hsyB PbD$01hE^ӻI_ܝJwf2,7RVuXgUþUq`mG8m7,8w{Pʧj6pafCQ=W~?---8}nILƧ~-2u+GSz[ռu:%ݪ.8xZYiU}) *l(`0( ]W TW'.U2op @@FtZMʼS5 Y/ J4_W-LOip27o|h3N؄m7 =p6<9fzPSf+B"حַ zV ttt+ 4S ELOOyI4&|2--͔JL6P{ѳJ|^Ro^,ihh@3>1A4% B^P(L(tġrUR)zMnI>KfZɗynWT=\pgy]rw玝ZG\痯C<gյR./`䗿x;ﺋǏ1h3IRqr@gWez<:g) +isyCXrd*yu3sΖpĉ9Crz3 =~K>c꬛g҄z}Y{~IE*cjGcNFYN:]5n>s H=/|<,v:nP^N8xVa2,>aey|DavrҸ2y+ #zeu4E:^MFk^8{-Da@6;SBA<arb&}'SsS3N,Td3@#  w"V$MdX>Fc-Lr`VZ?* mmmsy-}{#뮻N<ɛAEThoo'pIBNX(pQC:y##,Y{ﻏKFyW"*455Y9}}_BCCH4Bkk+slu+}B6]={ٳ \*;~x*’%K8~8=yq>Bli2Tk4'X8/Jerzy"3Dj囦Y jKU ʥ k]ɽw}-Fe1էiZfU0Ws#gt33}WߑG8vl‘03٭T2 ~~S)N\)300[}|r֮[޽{iC3-n%388QsihI^/_4/^dpeV>x}?b` ll7d`` Xv 333E9uw}7/ OVEGgK.ev:[:5l T[EaCZKEZ/>=+'D6ubH l KZP ab$?fZ3\.ѮRe㣺{:3(6=BSUn|7[ XU+y_ [n݊Bxj~ƦF p}񳗶ygU+yyŗhmkeҥIY `æAfi?>tU׮λNz{{*-}W ':x}fS){֭8~׮X(pinu+z%mLLL;=^N[{;&H(&HOcXɏ<2=Vny2ָ+މ6SMW}<)C7n筞2t28 W7ݾ'z|eď BT݋H̶WIpUR+;տF#np\T(S=*SHS"!H" LZh9rt:ŢE ===466Q*ijnQ(@ @:tM7ML˗롧GSs3Gejjp8L,D_?1==MRlشEꗿ'xijjÇEF#x<^X,$oɂ :"5kϯʕ BW IDAT+W 8(333}VҔ[T<4Eޖ>S/J|=cK1B~?5z{[{|EU%d:[#'d~~ vYHhkO?9x_ \8w62 ==y񳗶i[JhJ[oz>!"ʅSSDQ9rh GhkocfzS'O~=R)>x^"(y瘙Fh prr7mch NĢ#b~D{xƔz<4n89r`Cfawsjx#tUEz(4Mn+B/T|ޡ!#k!.VZEcSc-#+V=4ʖug3`֥U y=f̤5tדn"p*ZZBAhoS/2W\02`P' ꗹ˔\醌BKEGrE)iSnR kyIj2V4[=ػgǏgp L%BmɉIV\Mw|>ϛ4o+V"wб޺B[D4~^:[A@$E9p_7쫍? _`qxxˇ @kzߞA:2s5W1㜳nbb$Be˖Q.#NH܄f1d2rƑ(L&C6M!mP* T  rz_(*Q,aaQ7_{\~p5Ofz?w-tttʺ`E?L}ז~ߧæ梬osʒ*c:"+Tù8QӸ}w[/?'  '~ۏא> 7Uyԣ{|F=Ap6Z/jҞ+8_P @hUy@س~$B};Fgev R5 `CPC`घT=K QCmY 7Ub\<f89dpJE0%kmS,;Q'tp&/. AfgS4552>>NK~\,tJ"BTǜMfw !fL̐ǙI&g_9FGGikkU#eMV&Bx-qꔪHYɳꯩאfH+ƙG&1w-It~W pU_4V_r2ShSdng2zaNwf/s6x2apx` z2q/2JZ/~neSS=&Ps ֥Mii[@a0ϗG&FSˤR~qt +<<@uuuɳt#*! I&gk9Jn#- V݂L/yf޴5c]G4Ew jR)eY-:JFcpg-㓣7FK4Clban]HA%L ;2CCAY.K֙3^Ki;UC!=Kr^z8s7T͋AZş?E*ՎŅ PTp;pITk5 X(Ço"T*/&P*^BR.LMRz'9,p4u <8~8Ca~n<#wށ{^K\B8kp5kH$x"JF0u?*ۂ]2qyݶdw0/}L~r_ڑ}ÑKi-#򏃇B!3L&.^@ ~PJL" `<^,+.\);dF#TKr;hĺ'̆Cx´-U/9YΜ*us"R]d'=JG"0Zx8]h;G*_GS-=8̯c=WB8@PFn 箯⃅ s`箭`Zz})'oj"`w_{ ෟ9T2D\CXF4D88чT3+Yt:0?8CX(pgV8{m*޿Uܹw'q\_`=û)Ў>7K8{obp'!XO=?}<|=iU3 5ek.m4^^~ΚEZ:z~R_ >m5m9 Ex&ޞMX;3d4^%x  CZsҞMrFP(pd`0yCtza< >jպ9:3gd={b$:q>Nc}m?( Ǐ/}(W*FPsswމ6['9AY/i}pwЖLb=V*CH$r>Oկvy؏wwu٠ #V^#ϣh`dt}}}~"@OOڐH$JL&D60ߏattCCCرcF8JŅ)ٌBuE_іp|4Vjd@я~K0GX7KĦ+Hc/QoU~Ά:MB wb಑ uai=F3Gw'^CG2k'.LA zRqܾkJDD_z;7c)aŖs`a9'aײ%DB'oû]a-[p-P1 J)N0 el8v~'w/HتיX ,RJLjUj]1bҭպ͢r'veKSSS;kʒԺw/- CWwj[,dFrhTmL|ʦl3bvvB##B^:H4Q{M"HXqZm(bBpݓt" crڒm[, |Vcڟ19iwB0qwPFqE!P",_M1"eSstA;Kܞre9D}}}Aoo/ׇ^766 ޵ CCCH iku[|M6 * %(׶䋊]?STSXC03=??BV{Y?z055APTE~| c XT;&dz?._}OqLMMg}`me8v`# ~`p6)nl {Fיř5E"F8Vŵ< 0p}1ۖ+ux< v<B8@4P'cl! 0ߙD$#!DBGHʵ:=q7-ɺg(cL7Mca-=bj:X"޿?IfIűo\~G+7'XU/ƴGz^ Jj;:ܬ<˃%B ]Y^l=}Կ(ȗ]Z.VnVniq" (q%>cZSKP@ZEGGJ̆D2|>ϿFaͬ#DR X4&bMsyX__S_2"(=AHM1Dc1u~i*IժP&5Qa0 &VO ޼ߞj߿wh^hD3yT 7i@R |A LDzevDaֿiai tuug}X|G``l}}2  ًٿj*:zz?{5st/imV .?P8ln4dpwN}`vfvƣx 1~PJsN9}Fw/y rػo&''H埾e Zbyq D>9~ɓf7qE|SB0¢=k|O# R`9, B 07;L6Tb " #gW@ [U"!TuDATuTjBj Dj@XA @Б@MRC,D@a0ёZhm V,U$b!5$!KU*5 paQRE/ !W@Zya}QZk Wjur@o*rX$?]n%VihX9K:ǯU9xz ]=/?{k\'+{0BuVEVGT={PՐJuorW ݰufaj (``̺ΏF'MZYvԡAM±{7 ?d lnK:,uj^4 ~vEGM B!N..O0{zzJ!Z Gmmmx`MS8QuZ?SP}̟=/X Nt-,COΒ -u97`ԗxiR0xuD"|k_w\S_2wA(Ӄ[o T cX\\ϡP#  O?{:wO}+3wѱ1:t+!{N!H̩S/~}OJF"\|mmm(Kس{7^{5ttP,pЭӧ^]Eww7$# > ?u~ ;'ŧ=o].w P>PP{Nd],k'&?hJHM=A(xQj]REA~^xQmgˠ İQMA= Ĺh{(n5/T,A !j^J̦%B 4{ĈA7GS*?:78[ qNPa &H;lBQAfNa\U@2>O‘HӰ߷D?*&ʹWU~ѻ[`RK._ڵ ͏p˾}_%fgŧ|XY^v -Tcr.^B ]o}KKCVԹC8FWWVWW YQtz& ;w-:RHuֆ twwcnnUJ K8t3@{*y[/Zbiq}}m (3Go޲o7岽}wA!<@*Sʾjj4[CU"&I%V/ڈ~/utU~[iں|֭ԥV˼|B K h)XY^.~ѫf/XnUv*&uPb F VϷٻ$EnRNsmלp_7 ruC BQ>cj#B!D1 ,.,  .ITD.Jڛieg;sBv"mI; D2WtY_tD]nk[CO>M{۶NF/Q`;<硇ZF);1C!a..HOId'*:[,Zy>|b`1v9<{tʗkx"VKr0,T\^% _h(OX E,9rmbi?BA\Wp|zS'|z1hO5Xu 88-JAxm=9 |#!C3#`{2YF"[AJ,'9 6?hZ;S<]sӚV""Z,/"oQoQɤ_^lFC_3[j/tto?c]~h*vxD:ڊzlăcQ^UfѦ!ɹo&Z$ IDATnE=t.z~d2SN\Cq HF0_RP TuDBA0pe-6||usqoޔ:mDp); xo~/ir3s3WWqqfRl Z2V[0M-deynW3x0M [UKo+U/ryA$h#vy.<2//r}>9{R`4MGmm?.h4$^/Pb= m2 g&VVWWZyi|gP*@L߸5,.,T*YKKKldh40BF++˜Vt:uT[(r_5Vm` Nn6:<@|l`/ -r^J\7t<fffēO" ҅WB>N勗w-xׇ_xOx0;=ɝؽw/n6\~X/aj:jvډt:x-!UC_g ?:~ cmXH1Е YMFD0@umn!`+ٕ<~tdO=}b00h-5& Pl7=.YY? b8rQf{N2`=ǮDR%ة@… \sg=߇W`qqze;uaj ߇\_|)\x׮]YchxtMTLŋ(K^{ FqE ˡϱ!_n3OGQ}p)GպdWךR D4,J#L "_*#E@nRj+jr)$ad[HgKkG\Fn )CA^e/ ];]WE^2tuF/4w?[؟n` St#3 r[|ۙAv}j߇Qepl&N0H&Kĥ٘ReDH&ؽgB:ZnEݷ'@Ww'pZ°f2ݨط?B0022b/wM ^}v쟹g} juɡ"ubh7JH˜opOǻ1Еjû@`}ģm*IRhf_0X iFÅ( ˖R ].ȗUCRa\{Ǯ,С[q{`^tT,J##? GbE2qǝw{{X4}]]Ӄ&㣫LcphH[+u@m36 bo^Z3kXϕYBab>CJhIqc)ٕM&.ͮafe\_`Fo]BQZW6P5,7 9\I\c>9LHR+0)z?y4jugHg `XkMs9_tח!o0rKk]zn b%S*^|deЎOჅ xi\[Ko /&HJS7N@.^2,RKbVA/"^"TϮI[IPVA=m 粋b-fs9oH|$rY號DQƿ0 `3  {~v#ՁZQ\rr}Dhkkå 1:6P(PpJ)"0::;b}?ЏW_y]0Mr;`yi? ְ]v'NnΜ:T*Bzڣx; 3,Ts@8N̬PoMb.iRу7_`Ov)T f2عknjS@t%DvG;9~ A,=&0mQS8r+u)cP:8*K"B@!'_.eYᕗ_B,CO_/ @@?qe y[ȑ#x套lk{ =j^\F$A rn?rLCCCz n;|d4 H anCch[u!mVLWㅙ4sqv vRs?ރh8y o/҃:޿p0v iLubhN^Zϝ#xd s0>XB:]Ý2g''?@~d ePj}JQ/0Pօy*xuJo0b`Rg^9j aX{,tO?r.ͮ>q-̅OX+={Ǖu^mawՖ]XЮ(J ٺ2mHO|m$.-'OGC&G?j?Z:UH urB3WDc1kZa/:Bnj0RqǵyO@|tݻwEgpp>9 mPCzs ڗf7j닛@K-'[46`7\V Oh9 }C=1} (ɀb"%耔]*D`QVzyȥ@ `G{+S#/}RÏ<BzTRyyG@)EWW򵧹_o6zG\;vNr[#ٟEq]L :rvyD)W>suzI,obhgqd ^?;ݳl䷰k =qX̠#3%Bm㸶 eJ) lJ P'0N\h_ ]m1H%HgL"-a ]IDBAQ v%Y=F0FN0hmGoG2M::8}e 'z?8#{AA"s9̭n"`q-Y{`R#!Ey*HJ^&]=.GY$oZsuކAn9oSx$- 4P[.OF_F5g[#L:舵!r]G8'RbÃTx0Y ԧlBI>%=\d߱CRqHvt`[IYtRG-jXd+ $JS߀x!&_\}< @ubsYfMH>*rQV["`<ߩWu4|Gh{';@`LWppmQģ'`wGo_&yŎģ!w&@}km5rm+Yqnp)b {ΖP6p-øַa+JپsS$DEVڪ:§)\p^W >Jӄe/Wp(VcA,c9 ho[ԍ [ga5Srˣ/ouy]soV ¤/{֭,=qʙ9̭9Kpy.ܖ=pNG'T})eeWw.=M͑ G2>vN}gb!PGg*z,WS Q&+AMБl" ʶz@OS!^WI: Vb߉~/ >Nq D?]_]WZkڊ|dP~rR3=le;/ttjEfmzеUA\KO?~zڙzzfDMoϒ!q% ]7"y++JbކuTy["BQ[ Ռj/ߺ'c:fL̵*kEfte~vno+AV+G+4#X) _'z͹F=O}]@<DݤX(bx7e8a"!#AfKMBc0n@EPA_g wa8&0Ǯah@gm,Nb_ܩG[)kqavi~Fmwa+]xZxjwl]k5lR>$!l>#.'&|+O8njha>awۆ9P@ ϳ[تA@Xðބ^60VX8zĎvt$"nVo `X1pX'kPsl3WW܉x޽0x42 _F !J Z+yf,TX8A(`x;C qc1+ʦAM~SF\.¹O-oȏ:[ϥm5u2ҫp< ~_llVQ^Ql4PTа)Ҽ85za1u:j*i.U3pZZ jfZZef25M@aϿӮ^mla\Bʷ5rFw9G'[+G5ɰ?/k%z{i&Nfej^+t鼝>oVϏ@sR,BL7SGur]KVleyl0;GhILj`+Jc; r Ͽ= ت NEa@{"tv A p}iMM[OE3222/_(=<0)~zjaKd,8`v5dN_]༻ ~|r|Bci"7l]E1؎> a*u֥| Y^/ST# #S(7௲)@~O)a0)0Gެ\Q^W><Ƥh&8h}&~<4v+P .^BaU,N.:RP(0d2h4ꕫxpeocpx_7.`l|Say#GpSλ™ӧ17; ^;- ܵo 0~‹X[KgqO_B__qgNF,Ďt"Q7?SDc{ޯ>}bSL^uk D .6`WTK^QVUi; ['_3^6:G5Q3b_]{0D TL@SJQ,77s0PWOUO`׹x?< )pf 6.㡒y{DMA֞7c?ǪQoy!Pghk]櫿΃zBEmd5zd<9X9Ma|-@ϑgtvrSO7b 2_`4-w2O R_L&ii(8hRf{nً=n Ju VΣwo сys}xWO{;wLƍyT {bEv7PoсׯO~ ?~y.o bbD^C"DRXZZ0<:AtvwꕫמƏW5g7H:; fX*k}Y], 8}Nac1-X"z^uDa_4M&wX/ mgi uOp]&$x )7̳[NS:C0ITkNO ESL-xHŠ`3Q=\I xT ^6td8Qj=9'nlVZy5z&>zP.#,7 4@vvSNnTm;9z;ZV IDATvqPDpAM_*m3Jk=T9TVwRh,{hx qڽo?d2DºX,\B-ɝ;qMn300?;g%0}fnLc]V+4<̆C݆q 0 rr(hkoG!G\A"@B07;ǟxctl 3B"@PD\Iؼ 9\PkӬ]w"/@p8}0W=YMn7,S^,/.,zQݽD'"K**^4 gY, *D٣{[w*/PqwlESCeB- sOAO׶xܬ~<>' d Ee6?ڞ-tnV=TS҃BS![D%vwUq93=*.Ifz [q(+]JO* alh% ~"0S#&XW&݆^wYND[^roGU6^r~5m:yhnP7_?=x^BƱc0:2D"_4PžݻQUў`,^sk#M1P-ˆ狈o\ȳ#e|X[f]5T=\2@jZq䴄Ȉ/MY4) (ѐDxZG0` &|d,]!ȓf١mr׺5COxxmKCIm,mV^z0+N){,sunwӗ:2DŽi:s.4zQclb\SI6ϩˎQgOz8Ti+^"\BQ[q8RWݕuJCLR!.I>gk7o$[EtѵfEțLd E_8O]x"zRQJU+ tG z6.,Z311؎8_lс|բʽHNc MDj-n}Pxc&5#YZ@\--`H&H")/ &4m>v0E;Xya ͯ.=$8 Tlʩl.zvo(rq!+ph"_FזUtK|Q(ASV2ȒVඕ{S{ o\ӈ4#QF8Yk/-NJ휱R:͢S͂?~gb"W:hY`uue^ׇQ oJs ڲTk(=΢ |RX5"w@@ЕS.\-ؖB=܎2ܛD0`  `Lw3ÞA*A&_ƮDBP*ۇ\.{* q#j" Xax5ǭW7uBW[T99'q\g͓ ?YJn+k2dz~ y 8͑IIE*g~τ_ECl$re IAɷAږLo?-nW_~ّkkx2#xi\p!޺,ٗEV2%<4ubj 3+94^:5ńޞ.`=ߚ'm>[VFRXڤX40QZ * ۅ*} z[O -W}먫,j%Qy(f+,]I+X'L`kpa, Vڼ}IQlYA׏%'=]QDBt$#5Lt$]Spݩ(!]qK+5LidYڵ X_8ihx`׮]0@ŷ-@@W[ǧo 2@nՋ<Ӄ;*6kDD`CՉ(@e I| 篇lb"M"Rwd\y  L06mRjC$0ng̱8u]b}o?6(8ҫi 0 \tF@Oo/6Hu@{{;FF_} a ar$.L]=Tu#<޾b{4R?BLz?>9$*6MZ3Q,א/U0_BTŝ{T Bnb=WF2F&_AP÷`#W \_ԍ 70G&_-\3_c^NWϡfo`.&R4[xdO!E; #ۏA8?X|l919 J)\vd66߇޾>_,׵Xpa0 ̨)|.۾Dpy#J z`Pd/;R>:H|9sxR&'yטs&߱n'r}hw( BMAy<>e'NAſOz6wܴz<Ɗl2^ նi*(j' P|.#wޅヒ@X@>ǁ//RX,axxf2}<7aBw܉{w=co~.`ǻqN_MTXz$" `;sװg pe.x$okgA)ΥL cmopOŭD$@W[g3 ;X0.Rk@Qw ,Aa~5whFM^(RF_?""0VVVOW^A61:6\FFFqԻF2i6Z"J__ܵ{8p:~ > a3ő;SG0װsn ya+^I/ursq08Hj"dT,y%+%D5: ݭ@ iІ>:d$cM0CXGsTr0bR񏮞9t.ɝ`Hy ;Iݪ/ __W;A/9)2FDp"M"qp̓gpߢ( |8.t/׭w` &\/հo+-: %2sq|7z&NľRatvv֐+XcCܵ w܏u~?$:ޞ?O ~Jf2s3???w܁B>OV 3xӟ˗ W~=.^¯ opڛB9ʹq^[j-!`!DhR>%C!yN=O!@$ _$HpnA?QvBޮa@]]Vz~}^!o^ɋ%/[8s]|{1<2.|sO?㥟1 ĎI+'?{C>0֎zH&wB{{ uλbyD3mQÙWY=iϭ` bWgL,"Stã`&-@R~ΜĈx^ r/F͑DlS=k+fj;6j;ze%  z&z btĽvA~HPU}imEKٷmqMy@8F.L][I͟ovy AL CI&\ *5'rMhfÊ:[8 ]1O?JշCQwv oTTR˛{83[nznfGdQ}5u&&ĘIyc2I旙I&.q[bAA ;BMۭsNU:/NSԳW=O=s}4p)O~+yTss&JJ:$"|{wo+ LYMщ|<: 9щMso;SաG&sS#4|gS4Hso1f$H;uǷ&|ud¹s1eR΂OD՝hMӇ\K[:Ƕ?˯#c_R33y\$قSJcuЌHҸ}Ƭ'쫊sX{ysFc."*]5Ñ .18Hzm9xZU" eb;qtOׄDX,Fl.}L¨9q׊tO) @z58m&*٬]QBFDܼ Fl6hJ!>iM!M}aL/fK_YΡ^^h観^:{I&%t$E"Ijڻzs{wKWw2CP?%RF:]U7}2赇<,Ј Ƹf0Pv3ZPWf ]4 Ӿ64h%6rZIfm4yP4,VcrГ3}eNh&{4iZ4Mh4f תt*W:y6L8PJmDǬ f6wN3ZQBFR͇-lfȆG"ȣd ҳ+J,C?jDĚ#ICI8x0dS9j Rd6OS3ʧ^! i$NXaWE?$S߆hIz]ǟ ܠg1l57d-ԃ -Gt3Q!<)z\N'pԩqcijg(-(ӹb@ޢ祮];w43vΉ;8p`u8HIA[W7f3ylDGW2m3s:5$уKغ)`v‚l[igç]DDW~B2]084Ը@s *LWW-o('trHM|}-Z\ `{܀h2'OZ6#IsNؓKw>@l5^evs-RjQbniheiҰlJpĵpFId ? IDATĖn3S;eXZr։٬sHՕˣymɛ19ZB"JݵPTE ~ RV^(ߟs.87v^~E+XBN rNԱ@i_1{AX HJ@:}ؾ>neŕ/ڙa^_ۇ漺OPa/I~D-;"nl_jEiH%>͛9L3ym)#<<>t6-il4fJѐTI f5K'SmDx !o͇yjŔ9éjy&Nwkӵ푢C_G sS]J]S9"AqaEY[!l/ϩpжThsm/A_/>+K(|42H+>dJno}шa V*ZsL&T3OaF!@ ."B| 8}i*NU#Ռ(ҕÏ3wa§˃6O&)WWj# 8J+#=ߛ6oK8'N6'ϓZW2C]IKiӕHteaX]Vu)4#T8ҿv$mq #Ԫ0%NTҘѧGuWW_'@V/&PMëj9'](.HUc^o~_|r|j<`ڧLeu _œ Wq~'G"G˨Qhhhd2IVVc ~9EQA4F>-8 !yŜh* ~j /)6ħog C|ѕWJDۨY<H? H|sɣުC\* :Upѻˮapmi[R:mtݲD}=ixם9aE_X| Is{[邛֕]-@ӺZF"2bVL{LFK7u *>"¥E5\o~*p̓ɣD^9R>gs>.c7r5ϻLWyk,.@׾ !ߗdW*q8]} J:qKJhϾK4Z\.0Ç/9tvvu6cѣF>C}2.T(G;ÄNL@mB=|^ Ր”K0S Bjœ2B=[$td&! WXG–#DIA &) [/;]~%VU.島 f|ͣ>DUJzIa"KkjR~,m"ZOģNud@.v~oIa2򧿩j./,BAj&DQ-gAB4M"hȰ>Õ^aUzևmH5QQ5Ki!5ħ֒5ٗD)8뫪O+i)޸8 d ]s_|83M]qvvUe8Jd2IKK ֜:;;8Ts̾t3. F7 c`fn&kscgJ[ ozS$3KD4gӌnj&]GQЉh#?3py3aaW;dzgK"S-YT5|Yqry֔ɪ5`n/G7:=s]֎Iҋ_\KiwnՌAàeKgVw,0..K&ih_L3Ù ;`2 }!|n_&tqqfk_v$<•N83e|=xyC,ׁ| 3߰Jљ/5A3qFdДd/39dO G#5>i$muwt389 )W z}ɡ xhh{0Mԕm+贐A/]hRuv|X#R8յj&4Ơ*-Fs:4lR!%g MTrW/@cc5iU|eYc2dDN%}t<=ջW7YpqMnNjc:]G78>2a4T_7 }/.95 >$Lsa|gCJɤJ àm6eҩQoY2ʐz#gag%Jo\|s3kʞNW.&*q 5q+#]9rٛRh;G>?s7r! ^gh ԋi$xmvIÇ#ވ03Es6-[84tc77cACvpiy DW:ϻ+Ә`nBLte%,Vv܄2.vdO]8q}0qrKpS֛9˰Ot E- H$kkkK$p~n.%%kkr-;++'~3I0sql:>L%Tq8܄7N޾tM4$YOO9ޗ,$)zgɘd:t}>VJ )W|ؤNˬh̙^ASIq2ėScPݿ4F'6g'eR(ϣ ?85>AP8o񬛴SçvʖM8gnMRIFO1'{kɜ]2IPrc|@AAAF!$I饋^$ ް&"uI7 W'B*+!)%R&AJk%'';㢀+@t߻Ur#/])NM>*Pq4}225?NՑ>_4j*G8]&ۗcIK|64T_3BuDt"Ӥͽ̀ƗwdLJJ+_`nfZ>}Q_01y!۵2}t]ͤ%.[vr7{^r2e2yy9dC&,aE@%ed2 HJz&%$ %>߂͛73e_}kK}q{g?5}{g?Cvիw… vZ|9mmmuY{䈒xc|_̄<7/9|T\.WS)/MwZ1.S1NP?Oo@x ԧ= xۺ@7`d|^ Vu(9* ?E0wԘWb|ŀ/ XB(*#:6P#5V.TPzpj7`ZW?F/#|Cgn}ê6I7ѻD)E0ƌN >~3^}91OZ! Lfd%um:x}/:>. 9ndFJn~W_sfL';'KM&Dͭ^AI$7e˖3ϰ`˹{+_ ׿:1CSS:mmmx]4єC1jM_ C~g9 {r? k hk+Kiȁ4qȿ}#Dc Т9rio"uWko~8YyHΏ62?4}%FpFrҶ0SjF\)hr!}{l% ICӔ#. D ^!˾۹K_^Od,^? O=_}3s%'@d% K@vBD_A,!Nblذj3g0x`yy999<L2 6o}ロ|P˽fJJJ^nիWOs@1n8~Z[[Xt)S? e˖X{,Ik[_* 㶔'JG#[2a)#mk|Jr-o_K_'w-ʄ1TAX5=H$a!H$^S̈6Yhق(œ"utRE1 AÜ5&5& }Jțtd"@e%mx#A &S]%La+˔IDP**)0pHG~3R oʪv>qHѕ4|DHM*f<7eܤ|f"P2*[n&N>e=VOշb)]~Fm{̘I5n#ՎVfaoe|# WS7Ň>XpY}<8]:Ɂma^$q}DOW&4_ T^tE' I$T<%DEK8<Єr#M/.T.% ֤a0ZvdS<N"o95f<8T`[ . cȯ+Skjޅqmë$ԅ?GtaO$Aأ@)2$Bp2Ů0JL'*A-=U*j1HcvLRud֛Bbnс@UzD>{;isqkK}eǎ Gd'nM)d? ZϠ,$(͑gI$Ggq՛g$ ,`EYقDVO$RBC[[v>,r EEE 8!v㷿-#F@?+++)**[1˗/=j(Xl7x#cժUzlܸw}!CϠA(,,~1`JJJ(//26l]]]!+PL87iN̿*\.>,t'>yR;1~ӗ|oG4nÛz @%֙.7>B87k&1 4hD>_zAQM2RާDҐuu~pcv9r}4ls(<72N4"PfJ'trI#O@^*!S+ri"ZO g 6H#x$ٔ)9&="{F49sa$ih]GC8rHaa C\ׇhNjK >H4/?x*Z;r͢|: mdȐ~̞<۾dO; zZ+hn8D"KRdM2$ܯHȰoeB$,ϣFb۶m)rwy@PZtU Xbe|RN8'x>/)%SNeܹH)Y`'ओNB\UW]M/^L]]}ѱvrk'߸/QɴU9} ^u /I%N>?.?3[FsLDTћ#<Ы~֎gR /<5&T i%KJ_hHg7l46 uTs$+LS ] ;7i;># ST|?7\Rqylg|4̊Hg]Ll|o,JLe'B>umwft|<"\h /a.[:I#⁌1(0P }*HW&QLXuU|E6GhLa`;$AOo#/z p2=nJKJlttʹ*vdPWǀla:~y|4r >$!$$ҫYr wCS5L| wQQˋ+ݵ'__w8NqsgRd.$+XS:%%`_t"BH (..fyy~o=^t!{q-9ϩw۶}GÃ5kׯ0>== n?tvvy!Μy:CVSWWܗ__a񴴶٪lڼ< wv :C رc8夓8|06l?cO>˖SVRW.Eii)!;,v'+>eΝkشe+o(֣{0GD(4ƩhmƦjod#DŐB#Dҵ&3^=QIDJrrr峨<^}Y\slrYbV^CAA>ſxR%#a xwPZZJük|`ч$IF NAA>#;wGE̛.\xEEEH)7]rrre߾K<YB;r,֮@ss3w.Y,^1===\wl߹5kױ%!}eQ9?y|ooV2i1 XŖغm;y;vb5,Y,Qyf_q /azI/B:c&M̚XJj9c9QUUM_/~{=@_ IDATϼʫ3}۩c\r|Gxݵ?P⏗l≇+>[?xlj!y\57M<<_o~^suu>ξkoc=4:ijSNɓs_d׮=oOZ%뺰*F- i^j@ucHth;s{=5t"{ bJ(L3.awm8sX>^ρ]8XqHo,J $ !U*$ .fM0!&URJ 9ꨣ'՞7[&t3d9#]b,6.)xY-qB#2i"/V^ RriH0jHJ".b&M< ㏦UU<\t 2N8_|>0ؼy /m>Z^x~" Y2x }~.v32Wɑg? 8ca$@Oh\a!7i ?$|#mmm<(.nj1b0nZ[WIYt}aogQc8zQ<ē;pZ}w{o=–ۘ2y.^šu먪B xwOrם_'//=BYY)3O; f`e%|lhDVz/y`m;;nLό?z$x#z/e\xy8+WN_Q~nz{--)//+ygI'L'??>|1t5W&7n GͱS&Ic./2߽N O9tΘy:OI'@UUCbirP=d0Oʙ3OgKvz1tcP=dƏc*q(/+G^}p˧+WkmQg#EjX҆mꀺ`GD9{5o I$W=s?s%0n$zJ9ft;)S'2vxN2E?@]]-0t7txBqF.\ȧ~ʎ;lnuΝ;-]H)ٹsgwzsNjjj={ؾ}WPYd2ɞ={R_mm-ݻ҃ V)^2ӹ_:]~^ q|͗ a Rێ { ӏ`?;)'@Yi){6---o;ANN=1z(p&N;dSN:j2}6sI=(8Tش93f_]e{Í]K[{#Gܳdu1i<ē6rf?65kf}78i M?nPog:>؀3q-vM<~ܺOߒ2pp Rcz/ GBit厵f:8n:;9vdmȶ;Z̵W]=w}.Ə]w~Jشy 7]U']w~N9㎝_lz`*++o{L0N-[(ga<ēL?~Zpkă!Cزm7lcryRVZj3!8k<ēTT3h`߽gY{chu5yyܜ&M<;vpܱDznV]emҒm߻zx󷊊r?ƟޙϿO}崴Zo|P7}4pUtUdtfۑ2"9gO[s|[~ܙ ?%_U8f$V1qP:ٺmŇ;AƄH 3OXl?0 .dʕر;wpBZZZxhnnfѢE԰cزe 555,\f=?1k׮'࣏> 6b -Z-ZÇX`+W'? ;v`ӦMlذ-ZāL:2WrezxH3)ݻ$S,&6 @M =\zУ#¯{Y4:D8OV|JwO7Ujlps3O>,Л|Z'ˆ7r /R\܏+M(*, HPZZʞǍcg+ط?=8]r1 .bSɧ^9v.3/VUrqr̄jlD?#fZxo~`WomD H^S4-<kh1d Aώ;5ߙ-[;ogчuu,N.^E8 7~?F[[W^q9/_@gWL['L,ƏJvޓ^:::(**C޳M7s9gWTĉ'`GaFxw9PSa ho ٛhpf;Jpgp`ds<7z;6_z ?9YH4DY,D7moêݻ /[@ffbg]!{W}n9_slfeFpթ)6JSR1\z{#[]yȎU ]=XʇkXj_S%d2AUd2_GƍcL29sʜ9s8|0lذ;3+W駟l2 駟樣ٽ{7W^y%O>$wӛoٳYr%< &MFrssbǎ11~3fG駟R\\̒%KhooGJɮ]8yǸxᇙ6moK/Q^^ΓO>ѣ)//glذÇvZ֯_ ٻw/{e,_D"wԩSo~Ccc#Wfʕ\KdeexbyY~=EEEkJ)S.[rPJVaۈUj Z٬^N G^^3^K˘ѣhmm'//3N? 9/lB0YM\ Xf+tJ>]JVYe&ō!1`?+.c46yׯ nO`|y d7nps3r2gVYp̙`Xu5]r1'Oss O9p).e\c&(++3Ϡlٺy뮽W\wg,drGt?{;n/8g^|Ujj DV^ê5kyܳ_yU۲z7]Z"dƍɀ2x0{w|U8c:Xj ===~駞¬K.=~Fw&N̞}زuP 8Tns?IWo _H"MuEA(!v=Ȓwu !Ƌj)Gee%s\uUL8!;wdtuuQ__WTTĔ)S7n~}ذawytq3`ڵ1cΌ3&LKKK2d466eK2}t(//gĈ477sI'q1PQQAUUfСL>h< L>N&Mkƴi䪫bTWW3d €O}99TUUQ[[H$5PTXHyy9MMeeJUe%mmr+hokG"_QAOOp;_~D@Vfo2Iss3+ Jww!UU$ W2dbÚVU1Sw 6gyRhRR܏VjK]}=9pD"A0~<,x)0?K PQ^N呛áC>|2R__OQQ2`CuС޽JK{G;=== ?)%u0`[9td2Ivv]]tuuѿ5QRRL]]= 7f5үr9@iI tvuQ9`СF::().&+Cٳg/B f}dSYYIWWͥz H]]=~iooYYY:D^^.eek<)%%ŴR9`d/utuui6%==S&sUUj8Dii)yy:Hkk+Æe=C[[UH$'HhVXXHEy9]V .Ia^.YY!\gǣO͛#??/%N?̘1#j ~ȯEJg眣1 8fd D$r!EGC#wgǶdfzћl¿=]<5ww& R&uҘDBRc+? '$o^z)of7ߤ%K0{l:(6noAnn.\s :TVV2i$ZZZ-5@5ksa޼yTWW` 8y7:u*_|1o<< ٌ5 +)%:w檫>iӦQTTD[[tvv"`֬Y۬_nkre^}UfΜɴixW={6+V`\q|\z!&wq[={_Oqq1˖-?.cZ֭cҤњK/-[?SO=SN9!&;bRJڼkb}P2}%f.s]X):;;2y()- & O"})O?5J.7o< p-*?~Eӟ^ھF3a_fnۑIe"T[7~DclǷ^ga<_J& otuuћueIȼ'S }rX Iicm&AT_ *ˑoB֦'[>8{xO[–7g̘ѼKHHT3|r+2s{[էO`HQnں1m?~"99߲ ̮}?ā.jgr?ߺ5&XHIV 3fv߾vKG&oN` #c&X&/JmB}~ $ RRJt &GS1a2STUZ}&F7(5 i:/K!/E#% gJ$L<:YUcs5עd;whY[@ a$F6ul&nn%ޥKW^o0TiLfW8I]Ijnm6U-t-T9jCwHϨGq1w8n0qAd_ԟV0ht)3ϛΪ;=E8r MX)1F2cєWzc:8W}%Tq3cctq՝ . 2rE}ӥ+?qp-ˈ/q;NyѸFb F3NfBw7|c|g4ǯ, Q+0a5k@5[w٨ ۻp+ sJӤ! .GgA`57I7,^|B PzT~m6oD=UL~S~g"4'>2es72k< H;t˫+rd" ]I"iW uNħ)Qt |:}uz&(RQٸ#9X$RdSYK!/7Q֏ *;q~70d YH I/d(g\"|M>ĉe޼y̚5˗+œ9sx߿?۷oׯr/7.] @yy9 .k?ce\tE|gtvvW_G}DKK guF瞣M޽{Yf _Xd {졹d2s^s=W_}BΝ˽KOOo6;w䢋.b޼ytwws 7PQQ+Xp!g}6VYf{n8}83ؿ?sOd߾}TTT0p@ϟe]޽{YlUUU<|ߧgyN:9r$w1zhwy'%KxqF_"o -B?OD'/2̾.Ŕ)kkyS8enھ\+))aOfݦ@IW3`5-{vqɧPUR [RgZmNk*ٱi3tDWq?J[:LHEۇ(F-]nD"u]ܹs9t?O9x SO13%-[(**gaz-nFΝKAAwf;VseΜ99,ϨQXj#F`⋴q-0gۙ9s&MMMo0qDFɶmH$L6SN9!;; /s璕q˹ ٲeK=G?ᇃט|ܹs={6Z? ??!C֭[93Yb6mbޚ U1 G_DSkT1܇e}hփUM 9}6yMqֈ{*TǮ'H ΏFҧĤ+i7v,Ǝ)C yH*Mncȫ iJBk?QFuňf2/BteES+RFc\06pa 0ga4bwsaPNM*WSLGJ K=ԅԸqkLذYKwؐڷ"`P5|.R[L  ߿G{"F.8m#sIQZHv@١z\DM{wuua B脩|Ÿp26l#2^i͖-[())a„ ̙3W_}U諸rrssihhc"Bx۶mߟm۶jժ=S__O2de(]%I-˲%^CbJ4uQ~`2;Pu%"ADZ"އy7} =TnՁ*C:Y'҉LԎ#LC,[%s,cBU;ԜV(jO[꽶jH+jAF;;_PB$o@.v>ҥꞀ#$1 ]az^;ˀ7HMC3l,>@*G @ioop200c9\PU?Yk c2---qjt<@!I>5ʠWPt r!zcӣI_t1uFd(PI XSZ6zѫyMVuW€,h66b_i,&/bңyAQ:6:Ԃ- Bt1YX&yyطFsf~e1ֲ1j^%_YXykZˈ+F]5xTKGI$1v?ڠ=9ADƔ/;Lנd0E\MԈxSq۝4zH,A4@:lɊ=ӦMu" H +Z\veqگiNwr 0c$FGkՉx|;'޻ bp:O~'^/6K3-# Y{Oabӌ*@3tӍٳ隣1c[Yv 457L 1cP]]LuM R0磥OGG'5265@ϩ|DA Ϗٳ4k$))By|CΝkd„TVUp80HDuu ~_hO]])))9sIDz{{1چ夾fƌC{g'MM}ظa=^vDooU5A{ \.jjk=0ޞhiidAd^@ll}}= \.)+ac2N!<(m8vΝ;ǜ9Q*%N**p8JDm]-uO ى墬}Hss SVV0zй~ʱ'ly}HooGᰓ@ooregNgg'VRtͦ`\;r60Դ$q e@JNZ[xXV`ZIFLČZve'8a ?kAFzV!t YMM͔WĴ CSs35P[+p8Qi#:Y&N$sL=uul9sZ;pW@gg'!K@S  #._tFCII)==$%&uJk}Үljl?[aϞ}>}I &>$&&Odu r IJJDө}h@M0e_?#jTŦ +hyhFXFk1{4\[F|n*kOpQibxP=ܢ_MɲL0dTd6X0"c"(QGg';wfq8p)Suq!&E$CBBO<[Auu --)n/؋Çyv24>6JJJ/HHLW_zy7eq⋪zdʕ>}ǟ 22>|QQQIC9?SV^NFF:1gl|~=_&8onc2y񥗩Eec8r}|g==v; !K/s̝3}Ⱦ)--c2.u,~e{׋bg>.t-ihh ##2+dbΝ\vZ **yǷyϐ5u*[߆h4Qerr9~$7zD{$$a̟?>}p!~9z=}U+9y2VƏOeUOfhhW^}Agիiii#-ٻg>b͚.pQO@ 7u W_}%|ΙC)+/V**ز]xm͛CjJ Sttvp8;OwO7&I)((@x<jkhjjnE}}=<߹>!6\ı'53R\\BWw7;v`ɤ088c~@ϑǨO>ٍ73x^xݭ,Zzg ֱp9Q^QK/š59z+#IEEtwwt:9y2>?N`0HYy558 qݴwt$I<_ss l6jꨮ8$|gTԐNQQ1.A'!!iYYdfNa`p|>/G搟_@$NdΝkDG~ņ q8IHQq1zJaa}}XKmTVVBSs I,Z2}`۰X- RyGbp83 zy`d-x CNx||aR6&N'~}>{=$xm!,ۛfܹ* s, aiJL :822x֒ .6<b۩%))oG[KGuu5cBǾb6㾏7&Օkw|Z`-;jYś4멿/ `i}K Ќ`I bk$3[`do`رx=^5Mִ,n׳hB2fwqN(;>E ſ&5kV "g"fͤظq=?Yh!~a2)0sf.OLbʎ;ɡom>bp`lq,[v ̞=LF#3fL*3ς,7oN`]pݵ[PSSÚ5(,*fjf&CCJ`ܹ+RRSSILLĠ70ydFީ|fLlY6}f~{ ViYY&RSRf|瞻iӲ5>G29#3SW,_Ƌ~:EUA).)e\9JSS3I̙3 ^cjf -0={孯e/(_vqYknUnӑ3- (x"vW#̘1yygʕ|7O2ctnv;o_1}4y򭛿'ݻHHp!",Z0mZE%?p믻6B&2L15s Lwyɓ'qYuq̙nxĄn ) 93JJ5W_ ٻ)S&c4).)9ǎs.|>7n#ν=zY:::ȑ8qݟ~?OH `}! x7̚91rq;FOO/ ,A$5Wk<įI8d 8~EEŘxX((($33W^#'gE1~8lܰrqW Kyy_;>뮽No͂0 ʫRn}}֋6 ,z@b4WKdD3E.#M|Pwɓҙ1/Icx<~%YT+f_a>YB9LGx\yy9fb.]BVZEii)tuue]Y),,d``Kr)f3IIIj~v;$&&x0jn.Mɓ'Ϲ{y7ؼy3~ӧOvZN8cժU;߯0a~?x#33^\eV+۷ ͛ym< ^h H!vIS|bixߏWN|6Za(3l, YS2iR~Ա)jޥ%e>sY3g""+/={Yj%]ͮ]<{e IDATy3gΤҲӢvÏdRV\dh42kLt'Xz(e{,d) `1Yz:#ȒDFz:=ݤs- kVbϞZ^DٽS.]H,^/ Xr˖][lBaQ!aFz:<￿ii44`5TTb2֯ NP ,U u!2 ȡtii).4~,\gxÙgYr%$]x?>ODNϞ 8pEIi)˗_}?:I(**DŅ7at@Yi-V x"d ??y}XfNם撥K]f7\=OJbZh8@@31͡m‚ (**%^UZIVk#>׮Z{ś|_8m* :mʙDEC ddn.۰{>9BaeKf (d C0hɲ r0m6K/DUUIww7jΝ/dhh/Jǖ-[hjj7wa˖- o믳m6dYf۶m[XV^{5JJJHLL^OJJ _}>(}O?4MMM={VSNK/oڪfA>:Dii)/?۷sNN1c`{|}3gr뭛H3a*䒥Rl6׬j`X7 #nXν`Y,[v Fů}ORR p ד$I( r)jnjC=%!! >k/aeNJʑe0L BW\ko&C_yFWZZF~~WlHm]v}lfÆIICΌly=V\Ibb;v|Bjj*˖-cǎo 3oRUW]/3E_}U\t1-K{{7&`Kxlܰ;vF݃hBWڱbT0-|>/yǷhQӍŢr nƎMEӱqz<%J}i)]~[??)>?k Y)<N 3<<ĤI,[;v088jPss3ssfӷnBq%K8r 󙛓O?׏?W]ɕW\d6q7Fu҅x+,bAU;=ȲD f~*^uǷ\JDs$#}O 4DQd$Y#%$5Z^K]]MMMznV|>$''i&Pz\.(++#%%`PYߺdILLe˖QPPh$ Dss3&L`Ŋ8pݎj`00& s,#"gϞeΜ9*/8l6~%KBww7/2h4t:IJJRUHNNfBhcGӃUU*h.?!|6BWlX@w~(sv0[,(>,!@5%V޾~.GOO/ .gȺ9zɱ>іӃhAq\qx =r[&OIU |vNP)7/$  mۣsQ:Le{Txz=8$?84nG144Nd6!K2@__?<0ojh %t:%P,2~:+0|^&Al6!:av;Gf2kL<v!40X, %N |lV|>f^ Ta "Vv;CCCN$YY..?夷?$EQd2룾5Lj%"Cج6&CeJ1B= vQEݎUe' 8NDQ1<3@(0aV'}~۷zn>sn6:;;ٲe 4VXAqq1lݺwAoo/,X?ܹs?~ڵkywll޼Gr]l GFfi:{=|oWH//Yil\4e:uxR."#Vm!VF3~[ܯG`F'\b?j4(Q _`=PȢ(/̳a v; vdD$?=I4c7i 43X6e l; ztn2UQ .UbtPa\$ЌVMI|^xÉ|sb:6Oe?f1t1f_Q܎NbZ $9Mo+4zwf5qcFҬi@,!vź¿G5_bbӏ{.f-o\l}|^Tb lF/x@+nxϣޅ:'!t"}Ee%wm'\཭駟qwOQQ1>χ,x=> ͷfpp׋SHYRΠ{}aI~W1zCYI|HRȬI IA>W ~@披tttO;>AzR?%ϮݟsY>~g*4}~_y_Y+IK0+<| +H y188?A@$q dYVx=Px x uPi B 'xH:\b 2NKE>Ex=^ W04} ݲ,(++玻&I< 7+96*R»珴И B#hH/@ ҦJ Y+څWyF }FcfD!VQC:hAJ rH<^IUҖKP~7Dy}>^:A]5@jx|> ҝ0~򫝨f3ќ({=^>W ׇ,jR'!<9CP6urVX)ĤS+=P;4kFB@47ڡwq4ޠ觸1vtn\)$Otg -ؓ$$86z$I/ʸ|Tys;^|*aR[RKh>!b4i-%ؿ-xxH}1^ _l~Z>.xg4WJ{gB-x$VݔWp}?#GhkmCe::;?n,uyx-~,Ỷ')+WbMfP4ٳQiko#XqM>r"j~o?;Xf5MMM7i4P,#ybi~Kq!#1gOvw+y.‘*rBʊ(³:; TVUi7˯p뭛De @mm-fsw>͛m~̒ŋo~R?/S& ə3gvGzZaKh).)a̙36JK8{,O>kUk A8A~pdOjel#z!O<?HKK 3ط^q܂,˴rUWΊJG?Ʀ&e.sIII˃Ԙb̓ϛo$IyǷ?/<l$1uj&'NNaC( DVUU{~$H{{Gpvg3;v|$IL>rdg+GW~eZV<Ȯ]$%%244 (QSy⁺xe& cj x 3Q^IhjlT܄L8UV0wtq8X23࠲Zɵ\<XVew LAEECC$ʚJN J 1M8 z=fOΕoĉ ~ML^'!!Zڔof|/:QԩST|;]N zChqe7)74H8!ڴ ;@ IDAT8EV!N{czvjz (:32Y,Y```yspxwN 55iY\}U477Err$b RSSp:x}>2(3gWa5ne& VXFΌlD<~23gݭ&˞Wo9e _iYYI2g m>$7g:e")Ei~Pn)gjfo֬YiUeII?37iv}?>CՕ 7~={ۋLf3Sft:IOO#7W||kv9~   U>Ν˸ػo6SE~nD؞>= X;yU;c6ht!7g6L&v ٳwׯ HHp }qDQ!Gvc409},N S]] ȌIJ$??AًEEf`0+ʪUz!;;'O200D/+jr u:zzzeƌ|y'Nbr5W^}ƦfV\޽wÎOvJ|K׮alj*'LHUMut;ș1/^n'"fjelUW#՟/nPQ?e$ٶc߰ND4%Q4DL&G4Ork6<#?wrm0r7n71^ rAܒ/(w@`ЯcPqNÄ7h e5-\ke,g|n"aЭ,E %W4"C?GQь\[W% QibJay`%>A }qAHl94Ǧgu雋Ց|F)Ɠc,Byt)qbh4A(#Ͽ"{'N]wAQq ))<x={SgO>knVJJʘ7wN.tr?1{rfԩ,])))eELD&%9 c0(;GSVVΊqb0?~~Af3S&O993ΞFWg9x;Op9̙33sZPNz(*y?t, u:F3f`̙deMСC P;qݬYY3s!992La9$%%rq5ASS#NC YP@EeƍclJK˰ͤ$'S.nDQɎO+.bX(,,%))3g8r(J0UIR& (Veg4v-̜q̚9ӧ#Jtb4>=/"55 '}ݛaC̝3׋^:%288C\q#P rd9sClJx'rv1}ŸiWwo#/T;zv"( I+dY!HgX4DM z ^kA ,CP$oP@򋈒/h¯70LqHezP,0}\:<ٓ&&(N>OH6fz|Wǐܬ8&~fd$㲙9^DSΟy5%@P"cV{elbғρ³LKl?\EPXƐG&A3ȲDxBGpY\4 ?G_Ud/Xy#C^̛̩f~M@6lf#fG<#3m,O4,]#u-k#]J>?;ΞBT 6wdj%XV'S\sm:vtS:.s;˖mYVYL)6$ANٹs@w?gwg6gvg`]R`h(SΨrVr?bDa0s@cijjdQ{y4גyO(\Ah@[m ʎze0dTrҭŠBgT*n)U ɐm(ٳŋS7mk"xٳW Y"S焈kR'Xxӛٷ-̉.77sqhC,_~G;:iތP[[#GYjΝClXp>I,]~7C̞5&Ξ|| q.[Fww/^d IN²mmm9scY2ǏP,2{LYj%gNwI6lXυA:̲KpaΟ;̙3׮Ýwa5ٳUVr6_/%fY'A6e߾>zzNfU }}}:|Lבy}$IVvD"uR,ٹs .dd2gWFFilhI֭]M ^d^'#3Z[(,^{ߤP(z*HWwRN;"_}m[@:`2z8B{,v\8LGg'5η&0sF+CeUG`ʁNmӠ|Co3=L DL3I,a#T(m@2_B[̀,H&- /"vǤ ?iCB980o/H4㙽}Ċ%{ [u:Kc'~ \ \ï몸n qaxd"%v 8]v [yjw'sO:E ©s\j.ms|Ѧ:`ʹ!X8\o=K޻y1Og2_ak3, G>?LіmaZUTCݏVO.LyJ(L&6 z6 >X>c *ܼ^RˇJ4 DzJSN`mx ޙ_1AO;j ?3[>CxICp -HaGR09e=+#j 'l!Bx-%aeu}sy JpEuTlebt713 ;H$-[d$ʰWǛ@9e; PUUMUU&J:{LJ&g.5Ṭ҄<*FJxbg-X a} cyB~\,R |j2 IDZ {OJ`n4{b[rbǸyb뙷J1˳za+dg͉GsvRov7{-<_ϗغbdgl7PN,';}6l[B._ WK:HO?1KX#t>cJƹnxdGe>SdHV%a,h-QT@SK=J8l 3G#PXIi2$LxՀ 4E)a8` `iPrI^kN_eI hA|}TVډN_Aˇ1*|Uc6 p}YZzf.G_XI"#+T ]X s* \7 @N$*#:h[{ +$W fs842+pMu4Ԧ̓i(u)P˲ja+{!+YLhm&Ċ R lyӸ&&&͉3V{\H<4ԤYMuU O0:Ê &HopֺmnaX* @Uaxd*2~)#J࡮E$%PVQT,A=K6m۶:4q>;ٰaCzCY`PTil6Dakhjn7o9{-͊t*s -.Fj?MZMt(r }e2IA9CA܆Qכݦ^0ZO-E,0ȵsTe`&>FWҾϤN(gad2AL[Ikv+W6ߘ޷$#ҍ;wR.?җBsRR+p&fK)>J?爞BMQ.. n3!w G鵱j[ ZVɊjV-ʞcadKAМ˓LY$1L ELG+ lZV.h -\_a,}Lĸk }=:.FwkC5ښ}u1e^MXr:|Y0%GG?uӖHQ ʀ y N>U+b~oW^)OxٳMbEVKrsdxwh1t*'3{x'1G}T*źukhn.R(;ǒ%K8M:P(pBb}}}>sٳfqN>K/g3mϳpBr'OvPϜٳhzzj[0N&74 Ќ^БCP Mgi| Z> "('F÷Uc'= gcǶHbs-kqa WNk9/Wk/iW݌a###:|͛6z< /~2ۯefos)_MfhU+tvvbY޳\q6 WZ6;uЦzN"ɓ̟?3ώ)QFPnF/ mn7'8~{~ʍQR,=^lTB\#{1 J̣Je*eѩQn%*Qx*TEשL^iru= $ʱ+!>``}}}|e~z]hG'67Ǭ39x0/С#tvc5K>?b|a͚twzgx[J~x"=djrf";7PK,xy!h;yoTMmjFl3]=`G9 2fe?QAii^7\[y6H|V x녰R=k>y ׯGov:::9w<6n`475ph]'XbӧOg4440sLvtp|zNbttx< ٿ#\m+H۶ꫯɤ3|胿s=n]h>w[6e ='VMb%}=شfOu0esu͌aGrYB0qv_JXL}TrM߂g%17hJ6mTb$(k Qtd0˗ϥ,OLp8W-/kϠ>Sr9sѓ:DJշÑaP訬ԯrpY3g2k,-?aqLV딳,,Φ੧e-\jyY.\@Kk }}},ٳgsYbVz^+Ljb$q 6B!O,#LDn2Ϫ+ Teazs3xÏ gX ޽XhY纾g b^tBhz,^{V Ҳ'&faY˧T֒н_5-iy]++W s'Ҧ`͌>Tȇx8|,[!.5lRou']˂mZgp2c==͛<mms۝c8s3f,khOe˖~, Zg-!/S)&&]Q(8y$ӦMc6:o@1-j dǻRP.߼16m@< p"/9 IDAT;w }7uۯlr9'Yv-fGJI,cάYr⥗m+BUW>_ڷ"ʖ맾UVˍa' HTZ m^/7WQȤ>An M };).}1}+'KM|WQP mUn9AU?X.GT,sr>S ņu05 cv'iu\*J9۶nj]Wb A"-gsT!,J$\};_aEnV2 Wl|+/Nٴid{,T [o!w_BpqN ڦd+fbb(:.y[K{,z@褍Q;ıJMٲap Y/C/ʷװ.LrMwO?;_s 'Ntq9֯_' ԧ?VXΜ9ٽ|{O'/삮Rڵkܹs||dYg^z~ì\g:[ɤgά sΥϳuny|k399(---qfp Nbضͧ?Y~w~5kV8mIzFUzi,Iӣ m+# q8QTTۥRLyŦKɌsīBUgP~{{rC7.-a- ա7E3J(+eG\ī:Sգ Bc*6eF*ˡ2Rz` a^0 VAkQ'j+c~A1;k!4Rj ϷbB:z&3d!z:{[- igKWm7v0m$m֬aǿt*v|>O2/?ضoD"APX,L&uIgWbB@<',xb+s,SFEtr9G9oԬ!7{F3Dx}Gz:o)Y:fMCF8\9Yn4-l(f>.xFCj?ˠnka唽@yckΨ~:~o05h/FbN,lXE:F7_#$ggRʼnG*ҿ~I ^TPb KH'SZT2eXAB,馒IyN]ɝC+(IRPYJ&}?Hx-q m(/qfd2%Fbc&x<>7N6p~;Ĭ-Rˊg泎53DI>VP^q(9^wmKxx}̔9:R XbT3eYlI o3zAfUQEFl~X"11Y Kf,Lu:ގ^nڲgSW:$'z/q,F'lPN򁫗siͰe:OhS(,ݼo=T"W.cס`Eb~9{Q镻V)7(߯U3oF`'rtdi ̌AiP:SA-Xp Ÿ/DZ~?UY𷏊;c`l2"T41S;cFOR'565o~zξ=v0z< }N0Pw.sIA *h j/'7,c|?t a:Jok zw&T]` * 紣p1.^xKT%ۘ)Mc' 0a8nT'f[([y- W@_|C}[x_{WRF$|2 V8TT"^,3ٿVs -jGi^쁮s^-8O8}] 2==*I2hIN6M6W࿞yu0B[TglI:'+_-ۖd eny*mf/<*+;A|,%{P)n=Npa+37FjЃ׸HDIPy-=~[+"ׇX==ܲ?ZxO58[C"d!g_0Tb n:~@gS?F0e0`檞o2D :%ՙcEȥe<ߥ&N/|ۺi } jT%RE6P-c,09I?ip(ϸ%=Ҙ c(᳕fJTcƸmM?Y~DխDâK/2x~ U >Hӂ|W-lEڒ7;zIc$1^?C"#_X}]k mK28d݇OiN&){{I-{h|wY$b;fH2L[9Y.'sJ@T[DdPI0e RG(5B%kêI,AԓE#PRAW[R̅C ` !K'Q0r jQ'' gm g^Q ]AP M´;߀DV68|v*ϗUPr i<ٍC! o*#@)K$0faOjG%*ාy|].m3@$ .m_a,'^3')>{ yx6yZ%75}O$_m8om6*~Aõ{3m!=YPEL ,^p̟D{: CGQU^)(\ЫTScz3HԽaC^Yf%hPFk+ѧz@$ԤwL{B}3._3tF \kwOt6 ;b#O~ Qd\[-τ**<}DBI>/m<ɨl$՘ |jݿy>X:hkd!6l dNYVX ³]iNJJyVrqQNP`Fދ?X.(G%0*Ha *7̦ʗcA;t%2DJx&xDa &h%V.XߧY#zP3JÛ#'GΥ"܅aTrf a,Snk%}KACrރFy _`RE F Cb<#[4#>TyxYBXQ@'ɔ^G/> MPSU<}=@δV /FSü, &f^[i4N^>+u |մ?lNa^/r&٨z%F45dcDgHLe+<̑4L5;,脸"0  *w+<{EOWI`3C,T-T{~5`E*e^7dDy G J]>䚦%O+<7Q~)z]<'U2&&L'*;xJ(wu)zEQi*Kg0ۄ_Ma$٦!0} &1F`?1ʹhW砞gaRABh}l7M5#C^Եr+$ 21a,O1U^.tE:TA٣VhLFSWpK#*9Gjט#TY[t,DgC:N|'|+чB97HE( yf\ m#hq<$MV!j6}f ZىIru D+ʵ۷SOxb( eڵ \ӧϐd8u4fdYYt eqd7lڴ';e tudbbkwa͚Ռ͛9p$ xc'>4Oofioogw^~p['*;6C|>HI"h+R2220X1fϝ%u?OPn^Znض'9;Za޽z&5Fe?|H꾠%3缫$,^y 8;v|kIR{q'1>a9eY'w š!rcdiI硇/Ț5ٶm+5prPqHJٖkC0qPI(݉U,X%=G4UQ,x]yP^9uF/5~FDu`# x<1E8xB02n-8J@XO²4a).$+6RR%)TEr_@.W`2W`b@Cr[+PrG0V±>׳lA: ],B,ˢ^M\{v9u4u!3{6G%NsΩS>Aٴi#դΛ7cǎhQ;˖-ȑ#,Xuk8K~d. Noh0+ٹs55H)ik˯BۼytDiu>{^a>411=ġm!avƲU<7@af3 %LoAb1 mz=0 $s]9;!91hs #$E)4K(J-E\mXۋ43X(S[S〄tZ_~e,\+c?I,^ܷX,:!,7oraXd1/%&''+x'ݿlj]MϩS2A?9xi \ѣ\wvy";vri(Ҧf2xq<]w:Ob۶ B5B7v IDAT}bO>4VW4=`G*gMnJJ\E_)RIK)<@\X&(Ja) A +n9*JeÀ{H.G9|+\TJ/ʸrA?Q?r&ٽn@8br%e ,.B*K8Kr΃ 2E- ,.F iU($mD%>c/Wr*Je#SڶEh9l|xŜ ?}ET*k72+ ٳg/>FFGiw&JNfܹ,l{I*r?,ٲe3*&NtuQ]U-[8wgz*$W_O<5W_ͻ}{ʕ+c[gΰr W=sbzḬ43\  =c;w~G @ 9y99ԤɄZ.5lcmo0sz_X̢<mZe/Y4$ O;ыy Ҍ mUњ6EAJXD[rp,D6Gu&mGECBRɤ"P~s\{v#%d2a˝﹃ѱ1v 8q1O.fΘIU ۶A:KTI@pWdXt eʕ\ne``NYZ[[) H)%_@CC=-[#464kw~ar}M2 HASSa`--ӉTIgh7%K}H,LRy{zx"l#vZ7vtm夆;í܅Be7;էرcF v m":Q gHRvZvݔQO A1Ig+eŦWdH%Y/%2L^92ݣlWrN5aJCx\e fאAAh@ |I" !4vyc϶%²IƋ:{eK$ )pgl4S˂ҕIo))%R؅ͱ$ Ţdph$ y3GJJ?/a 6.E M!|$lۺE_۶u W\M`--,[Tkkk|f.߼F֬YT%Kdbx]T`ڵ[6T?ϸQ5|(Bi`72q'^wtJ=m1f-c $e[ZuU 6pjx@0uض7c3Ɏę>v5iN#Wg, 'a N i. lI()mj,k)AL0;~38UUU|l~¶[x78۩^ds{k5\kLo/6)[xGz{9r(nߎD޾SXx1˖.e޽$ $W]uVĶm~Q_vkVm=]y2UYD";¢*æMضu+dQZϢv=ʚի+V,` אNoazj=K6/]vcKgxU~jRtQ^Oۄq.a=!gV^eYe36P@-L22>1m#J:Y_ ;`e<6 ƴa^ѕ.o}6 9=?ȣ}Hmr2hyϵ 9eBѦhH]q}, WЋ!=NKBdFU]B\>mu ùWmOdi.J.hĸ!κ˦ mҖ:ۤRMqTb`ҡa[z5kW$b kz .<.]zez !=l\xN;glm|>[}胿wJ̜1͛@֯[D:lR/ϘkɆPܶm'%\CtJM/ky/[4f"eYnRzKeI.09O{Ν?GXiqf5ĒI۶ ]m_pDJbDI_R׾5>믳sN&&&xc>,\}լYzL&Ë/H"[o7`ppX ^>O}:غu+B{b6?tMWW|`ttbSO=8{,7f444/r`nf^|E#Hh[F2ZX8~3T3XQYJ,Lrɧ%rH-La*V*{#PxK _3Q*a+0hvO]!A5ۓRz KBj)SQ`DKIQnE Xb5Hw;]4_j$jiOIhP&=2ㅾc׀-ow{,O9yBKCCפ-iLgZb>'d;}lhô̑AB2VRIUǙC*FªFYX]ped1m,~G8klW.hLY\ٜ`]`^Ʀr,K",y;BJI.b! e^?r. Kqm6 _ oCKDei0-# .K'(_]JJ@~k ÃEG8ypi9*-l$/>,?~7WlFLXaU탘!6Ef̘#Gx衇hkkϨ Gy۷s}sa /=='> v>)l٢ZX,~Z[[rG?⮻b׮]};vv>_G>w͇>!z-Z[[9p;vG_{!J}jy|A>iզa`% Ȅ@0%WPQ9=*yءd*(B%H!˾S#" !W&X >eYmiNrVQ*5mc?\U(ܹ\bh"#[b.  >(eXB(0N]ZٳiUcZjm]&ĵEl6X\We}xK8B,I"dYb8oacоxl"$}-$^#U_MJI|8V XIkVj-4VqqYKe &,60#7YQIڬ2E[bbE&''grrbHX*!6^W1]Aw(z23}Q蕌.S~3>`` ҟրʤ\C:J!3•&ȱo}v Q΋F8iLǞC0<:BQ >1.zJ XŸp|׏;Ɔ xG>?a^.^H<ghhiӦ166ƍ 7n+_ Lg˲3g_ꪫB5 ~:uBecc# /֭ihh ϳo>}Qٷo. J656mZ浠QKY/ ƇOy(ZSZ" ޛJr{Ԭ `5/4Ė01>P(VMs2T# 7YLyWgHL8[2A>|kK&aPXa|lX,F'q,=Ȣ}}g@Ass33YDgܒz h~W^@Ѓ.4AP`hxXqUU|  {SN%K TUU!l}1M<p?rqhL&C2dxxƥuuӈOL/ ŨX,`K0v歷w`{PA%'" Nw_ۖ -2̪ZDiZĬ\N}@\/9Ʌi?)E.f{vcocx"GXxD Z'F,  bq''zK p… 02:$S)iljrUA_qo9XP9T*[~u0A~d$DtL+DL>OfR]ئѿ՗dHjG6|Qr{ヶ&JO0ĨWH)"##8}>\eaPJQLr gK(Jg3%<'b"01>}:7]^nFyQ_*W_}5AUdٲeX6eʕ|_,uTWW|FϞ=c||9s+}uֹ,zGXsk.nJ,-ɾ]ŋ̝;믿GTU, (Q<*.W>,)X\rz Je9dl9f7Rz8*cJ,_㐖Y~qdx|q(\d2I:a~[7n>p睴%I._)I01öf aK0Hlw Wq9@ lѐ+6EKdD"d,ȴplqG~~'N0>>N.#[ni^v!j0gcǻ#_'jkjiLMM ]'( lݺ;w"6xt:M<'fEd''c$ Ξ|3I:xd%KOkK 9s ˗_M7޳y;ΡCMRXrqwV,vѱ(Dg$(6I$%RdIQıkon77.|Rnob;#rn,QIDQ(M$$@w`9s8e6~9sfy7= &ZL!: qkYP_hxpbvq_/-[Xdll1d.o%ѬƮrow>͛G0dѢE>feχluVmTZ*iWxQixxYw=ـT&- t-H~ ̖_B2;GNWg'_Ӝ>}cHs\ kz X t7  C*tX] XtdC,Hd`4JgXOR8܄rf1WQ'4TեK4-J6~]َ8N]p:|'O?Ϳ 3g5-MSl@ZKM.Nֿ->n"Mt'A{zz(,,֭[||j*.\7KšHYm@ZgϞwxQW[CnF Kuu5}}^?h4y7~!nwwc z9zw`;{_( m}ca}f7e2?J _ʗmu萓r)_>V맰BXSgi\߿oV0wb1Oee%owKSS7nNՒ;/ m7('YLcvH$MqI)3g7nܠY!5a"'C#G?Fq(mld^S5ՌCee%5QTX.KmmD"QNǎ@Q**ʩQ&%7ov!ѝv{8qE&'9qeQWWWrSV^z^&''x?:̂8ws?>'NbU|:CYyKJ맶Z4N/ry.ib&C!E\kog\xRNƆK* "׃"8rh^?X?WS8ttCi+g^d2$RUt{*YRL>lu:=Δn*(ͤ4}QgCS :421a1*ek~H Ξ啗_Ze´}B;TXsHMA32,Zֱcz'c4U䲴&ʫRgvz$cEQ="#6ە Le^磏>bӦePOҲ~mg.آ&nFC&K#; ֳ~:+Ҷ4Ld6)=D>HKC$=%+zk/!NRJ_WHRJnݺE8}{8N8?<ǎcݺuVmm$/?jntvR\TDEk[B6>6\],T"c3FQe I y8 t8=TAAQpX:GL#@Eq`1H;[e3[_1ׯݻ@??xꩧ8v999̛7oJ)K.t:f޳^s\qaZ[[ʧUUMO8̙3,_wXzjΞ=燐.-)//OW\AJѣGyꩧ2;Vwd@|eҳ=lrܓNENձfBfi(Ti4H67^xͅ 7t{322ƒ>ݻx<JJJe߾}tttSO144Į]رc.a~_3g 7FGGYx1gϞSVVǏg˖-ttt_v{1fΜɞ={طo˖-#GG⋜:uVXomK.YU\\͛/~A0/墺:&bǎttt'X]]]ݻUA6oLkk+}{gܹSSSÎ;x4[nCcc#r9v3ojl*;錓 6Xم eݦĞSm MA:@JA<ɓ`<bIBVG ;U J U̬*f !(9  K`.HIq{FE&Qc㺡̼4hR/uϝ;~M)@V& $DZyɤTI/?w^k%mq$XPvo`wmvf c{n4SamRLiđّkvdʭihƎ=MG gΜ1cCMxسg[hT?FUGZ1"&HGWHEQ 5<S[[K("'G?/i۫nC ̮?4[}QVlqށ;wH$~.Wr>CDR˴hETnI2fCj}M{x=/YJS8Ф>"ASUy~VΚKmHKN^/z%KWPӼMr 9"\/n((Jblt:xwf|sg _OdŹzj~:>wy AAAB qQYbsM[~[ i̙f<y&^D"~?=/noRYիuO桇# Y;ϟ޽{)++cڵ[P\\L,cڵ=zrsV6o[o׾5~!駟`ڵh޽{=Μ9ٳpum65z9{S-ONڲy F DF:{}2cObdͮ,x^ea{+ H!.SOG`HM<fץ1捻ֶzD^u%T"$??N~XlTj@U8DA4)x!cc `%X'h. ʄS 5(0'^F&h:$!l #Mӌ32ʖMis{)0n***Êt]XHَrK#YFLv˼< ;wd"Ux'clx0!4CY3t YXP$N\.Kq:::ͥh48LLL dll!{/##@EQRtmNR{,PL~1] dJ"QIp wam6HC?"ES$>%HX@Gr_ǥ?ەan.HNI 7@.%8d9y~٥t@%>収1bѼ:oQQZLi ۋ(J)+0v=sx[nQQQ#G8qBpU (磾ܼyӊ3??1B%%%[Cjjj={hOvUk C!_"^3|O, ( ɐIVƾ7H 0rsYPJnp8xַk0sRFGt߯I.5g|b|nw"77C\U%q 4ܽjNN^/HbTUU+ ̙3O?@]' [&aa ܺu'OR]]ĬF ,$v%ǥKm4iN;eӣ`w7 B}}}044 `B$좪jv.> 3]%iѱ.Wߙ35Ycs)t:yd"nv8urc?Bpn788ȗ%7oѽo/q;gϞr100@YYVBȈ5{X`c&SOY~LNN"} 9UVD(**Ba١y<^/,Ǥ_W駟7ޠm۶܌(A,=_h"ݖgW__Z+*]1e3wVGRϖvꕴD8Й755jLS]ɎXrQWWsQ@ 0<6V5 a"g|Bon\D} W碪9'bTþfro./jƒɘ^UUQ}墰$/DQ/Z,96$cx!8w{~:Ν˯BՌ*{#ǎu cKveўg3!!5ճ̓;/=N+]s#FU*ĂekB[-CT},7]Q6m7w%ɐdc|E>}3U!Tp8d Xp 繩mLb؍qq(Ux/bZuP(k@s7/*G .[RZRBnjjٿ ogoFr޳ҒZZ=:aARJB8q;vp~z8Sg26::0O_[lx,/'={rP_ϋ/D<y P_χf^S~xX,ß o Je6kiվi29ĕqteM} АhBC(`/^%"dQ>ˋǙE#8}( àƻKtl^],6K;iN>x B?˝dnz( %%%wXl}d}aO##CSXS] &A"35l/ԁ=S*/ IIt&"-9!=[PSSJ2N[BP62,;ot,$XR&%~n+/14 јJ421iRQ5h]د1\"RÛߐLVR^^ζۭ[7"zfս? TUWn]|ч<ogdt%K0sfqsy6onяQuwmx^}meVqxM=:;ǸVxŋuzx<^jjYf5}}̨%-ܾ}MKw:S{iH@T˶SXLRU?:&͛|޾Ñk)EA dTUUd̙CqqTVV4%KR]] s&c IDAT _fj\R?ø\.*++)..'ybʕ+u#ZdӆM'PuZ‘(55TΨX+'cbraq~M@ #Mì|>bq݅E,7v{Ƣq#yf;300Ȃ xZK##7nvRWWG4D?Q>WTWU쓬XUj tru\.Rn7O<86lHӕSڲƜ+0ˊDtp=SQKE?SПW`hBxʐhѐdH"]T(Di鶘,bLY1-e*;+A^So$\TjΔlrȘ74{@gv/{"p&4UFSI*ܬqlNW~G6'Z[HECjx\el"J\PC(:82d]Rg~7#p/r,[d~M WRT4f.T]XHct"ƁNΝA|z5"p_5)z6]wiV r"puEhQP[2_./^9sf'慨ʶѝ46Y/]]1{n ZPяVl`|3ߜn`:R%Z~z݁ GAAk4U #_{^Wm E Fii>Uxu***ի$sp8SWWG]]of3l[i0 2DVZ/&rr^/~ i*$PTXF]]-gVˮc%z{yy"lG_c۶9v8H˖u5Νks&>QZZlYb9NEQkm+>7nBVX`msݛ|={ܿm+%lX~6RT۷uj :45hϓaJSߵZșXŦ%o.BDbz1#F 9IR=eIL RrN]LyJG̙3,\0wEAҕƒe^fap8mmm455e|n7L:T`wǞ}421]64FLQMmd9*g:JHB jve@mvzzzx_~NjHaJT)q 4McMU'Ch|8{G5rb*% EE!a&n#WY8q̒9 4N'gL4jC=zia\2nGi)P g64zv(IKˢn+`Qsss[RiBxnOӬ+"EkXiwMR |cj?Ёw!2nfցe,l~?~<"ֲФϗC4 '~?444pMwnĉV6Yw7mH5g촶\SSc9,**:i ZZsiVXr~u4$kVk֬]wyU+Wɵx"- ~KJJ(*JxlBX68`)a!lR|XáD3|RXU Gbr\2|i)0VS5Nk05/]]]˴سg;vSRR /k?f&''r /ȫJ82k,FGG9|0> `xx'|O?ׯ裏rQFGGYlOrQSS˗ m6~xb&&&~3gܹ/_ꢫ2^}U{9~_vZ 77rVXVeRYlaS6;wbbrNORAWf ܚspN3I6XS!L]`,qS)3=#TM  _|:~st߾$'O?ͮw.Hd\2>%Ua zad(8Q\\/~@.a<.#HJ}64I4.G%8sSM#'g~>]jɭ(ɨ?yimߎ(ad[m;L ʰ풶IR;HSi"|ҳOGaO\1krYEJo].#EõѱQf)$˹208eT$  r!BpJKJP nˍ9޾}}QҺ h/K˘U¢"4M';w. 044D8& D"D"4M#XYIeuu~^jSP/dtP*mqܼbUM&IFdx'U^RgKJwDJ~i%Ǔ,_B'fm\ iKgb)tS ' 9\d,6lw| ꫯRUUᥗ^gy_|O__/eee|>^uΝ;i,XV}]F_ΡC?>O?9 # M{{;̜9]vsN***Xv-NOOΝ+W299ɮ], TRAɝlR{v(λi eG*,a2.+c6)wԨco -֜ۺŔ>GQl޼իCyk7/\Hchh Vphg9Jb8P`"5+@X?>K,v}^TU P3;kEjpkll߹yFF㏳q& =:yVB?z~DR |'Ick0=Yq=o;+$NN`cZ)9 ŤI~?9 {\iof.U?rrZK9In}322j-ꃁ0\BfwoDYy99>QUb͉RZ.FEm} GBtDSU)).v8{&Yb9fͦDQ$ 6s1.]zF%x<4H$np{(*)asii/+qEJy˜e\RM%랔=&>47([~%v&z{ v?Z'M]c>K(Y7ƞlAlatvNt 2I<&WLy F#Y2 ]SUbo"Ӑ/1,fWn=~Ӊ2$n ˅p(p\?6@ۋ%P=nb=V;.=^5GSUnܸ8999tP\TL [kb?Yz ݷizQb^UIrU0@/\lӂD|%j4BD]Uj 2~eLS&MGn"'`2#IsZDjy/>S Eb[ZB87), 2c|!P 1pp""R"ьډɍ^5iRO~ZZZhiiȑ# 1Ldi0fjq CڙG$ m—) ϦG<ۨF!c|nao^oԉяt2XqI5h w_}+#Z2K 4'T5\0| 1tOPyxT)r>v-˒;MĤV&$lW{P˪U,LidcbioܸIu d#ISfJ"&%C{>ix{ѣܺuKڸFMI3 \&j.$^i @ JtSZfDK"v6?(D#!\N'FEtt:Li.^dzAJΞ=Koo/~ʉr4pq82A^$?1HXGJ.7; \%MqILY~|מ-Il G~gZL˳լo:A1ybH`L(Mp+]?@ͥo27W#8(/q9]$#vKWQ hKI"$M5TCꘖRY,cJ}uִp4>d憊Lyg+NRӝ.s' t' 7+>zfcO3Ur=z<<\,A+lt@;݈ZB>L nwReL 45Dh$O{A ! #0R e~BZjH5lӟ mXDڒAf4?T ڗA(Ӗ -0h6,[]&sfssݜ=s3(,(t:illy!EE/?@cc#rCCcq P^Qܹ̟?jΟ@cc# ].?@iiEMI vw9 +>n [5%CB l4rxrr9#E!7ׇǣїN ~rrrtvvQa Ը YJOmK~KKI3[{o%qy~2M 9E PL,@uYӃcDo A~Mjv3癛>6_<DRPU͈ևNd Mda7Y|s>Ȱܜg1Or|/品B<,ECkeaѳZb+ = *vK/A_9EhQ>dmn|gBfDcm3v[`nvDOj̲_&^:\3Le!!;d@7ԃںZ*̠m2< +W@ւׁ$1 IDATI>1`$v}ͳڵkLI3*edc܋̳?>]oRܛDl24T I7&{]poc͛op$Iͼ`w,L"="J=n 3(*a2D~x^EZ*d /z~@eV\%\w JÃ>@2NΟ7QT\D[[O?s0I!NqyZ[[Yn{͛7zɓq:]|xYF<G? 6t 2[n!ϳn:8@SI&x| i T. /}255l۶w9$@Vgϝerraz>&syjuSv9G:HZpʧyP *vEӄ*)H "#L=k9/Kz߅x5vKgok)T|}ܒ6c,zH\i B)25$^g=<=ˆ LU;Gil9i@38::8yOyy9eeQ\\rQYYIcc#kz*CCqRɤM_1ɨnCh1Peּ Y}23x3Ԓ,Ʌ,&;|d>$Y甝 A.Cr PVw0J<&ލ~ˊZMv2YBr:~(`ltX4 ڞ4Ip47B"b!@]}=.T2ډÔ2$T*4Nd"ݷks(Ym8{~FXRn|>/lA(X,FII y%ž}7311A*i6oĕ+Iy*+hUo~#ݗ{&466rZZ[*Am+uϞ=H$xX~=Nv).)K/iFN˗drrx<MM=wΝ;9"Bh~:uohVg4b$uFI.+>ZC.^xŖR BMi* #_yȡ \/Wi'/Uׅ:FRqn]IӖP !1<8#a !T$EB(Yyd>L{e3'.M @Q:{k*" "^>CE4B`+_BॐˎfPHeì`1tP/O`#BZ*7蓋g~`̱L3FGFoX,fYo~!?H8FRWWǪիYb558d,m9LpJ1??E륥,>|H$1. S\A0@j38Is$SvFVم~ӛmL 6[.g͠U)IټX3NYj,}}}Uejzh,JSz44?,i+PR)v#LjTXL,Ls8gq5#QKk~_ZR~op82#?lܸeˈTWWl2'Yv-gXb;pVX\(,ɔjKyV,_R(.*ҥK_FU+WnZ^:6¥ذʊJ$$vNww7k֬ŨaJdaCebl]مeUTVU222,[ A";Mihh@BbʕDi+V;hllдWKhs)^XnAB"'J&B2w|Go?ΆM B2po \K#ē VԒH*L)/'ٹ9c8up$NG[ oT;ۛ9;F,i _~ZkKFynk(MrY0t(JeWw0\#g'Sܼidj6=Vq0>ґ%/+`i>a PYUEss3˖-Q[.t֌x<^gǎ1&'attq H7E*U3Jګ#cZ)T2L@&` Ke|f034G&#TgY~ݜ@CR}믹/K2d)8ٕ{m}WEUQI25=E]m=dp8eeχbҎeD"DXJJ%FMɾYOZZITDVs},@CdNX_qDeU%pܴLif;:h&nfgӟ}X lْvַg|X޽;e}b<;pj] h_͔f*mHɞ{2V>c) AJL˄{C1mqfqPR$RUC#4ݰ$}}"yh$Qs38o$Ȳ2^Ē1Nbf΢ajKwnah2DjV5V=4 eK#3ؾaZFø]jHDRXKM)'GٶXDJaWG3g&EwnFMN)* UŬh( ^㳼}v=[9=ʻ GGYk-,dX6Z ʷ,h0sg[[rQ&''dzfmr`Khĉ¬aɚl \?rWvjt"rN|]]w젴ө-ÔS\\\ y vJCҖ9NoKkRѣLOOAWvYf ]]]Fsx&c,f lYx7W2 ,ݰ&0a$a0K cr2SAtm[fk{ oiРKKߦ>r @K_rxBt_ H?@e|lUUwyV|2++yX7'xLxb6=7<W Gi)~M$r ɗhf<\BQUZkՉ>zGܿ{-Lt9*@$ف/ e-P/555_ԧ>O<׿'9e5XTv´׌vv%)ton})?@ɓnc9Α>qp8rYK( ===q=@ wGpe6C2_$d2IgggڗeW֭c!b|e6Jo-<33ЎI.0ig2ݳ5AۮgZ*yԓ.'kc b Tfmay,v(NMބ4ґ&yf@d&L}V4-hyN'M Ś&SQ,u(pt9q:?Z#N&* PU]M mWL$"[[M; S5̭o4RVٽd=iV&9h5Sن8%+]nsXhAv^*79-4ܼf@y^`jWj,N% &.tQT_L [@)@P$;$6(QPz1)R )<_ӣ0lJ88P5B79㕣?͚hz$I(.LS]3ӝϱmJBWJ$rDJ=(B۟*H!gd mSH]RJ,Ḅ"bYx!ies-+!INY4~7%4M!sDZ(x.B q$ i>7E~7C!L$\:q6[TckX)7^[ɓ$p: oșz| ,0άyrgVBsa~cH={dڵܹZ,1\ôo^ҹDJӟ h)^#мJQϺd]x'~swC$;? A3qaGk]ɓ'ټyKV\FT|4ee^ ~ږ8u46m2Mb-}mMkS>cHT)5$@{8. 0x^e]n|>>cJRR)8f465徻%?ߜ < RλM#9}Bt&&KoS4k+!gjֹV:.s2x;ヌ~$<4oGoY,@+C̃.H=!tW=)#OLh./o˴8s8{wu>djjP(D{{;/^]]H_Ge^zիWi&=6}Wn:#ops$H$8x h;3rdn6DKk+'NtQZZ{g[ի;m32oMf,fG|A'Nכ%I2~ҵz[ /p>C 0fYie^,4p%XXX`xxիW!QEqI;;;!Y9v{]oJVcm۶!I'N Lv%G аNj̆񍍑JD$T@ŁC( R \B5}Z\d7Œ9]9A5@M_ ,UUrdԬ9Gݛ. cۖ\f82;MS.F>mUzKk'_~k_̗)W|zbՆ3" ۗDh!sBgq$I>~,,, ˲_Pf~&K6|VTTm]c5sw it-@0̧92DSs,'LR\\m.r&@UNu17 'ly78QY uTT[M88ES] _h4`?׭Fط# .s9XB3Ӕ !pIUmj{+VEp"(zؾu3Ãyl\zӓl$7mb(Wdrrd,716t{2& i1mҒboYMˁN@9.M+Jh4B*mXU} ZYbj"FR^8dII}j)-C[sC&O*K.É ]&ڿDJ5te 6Hp86YA(4G$[Fņ HI ]rHg$I" T:a2$dhw%ӟ2נVTtgdzў277Y')$1V5 i4>1yv˅z_K~jfЈSn1_Բ,% ivRt:RB>̅r]N'E]Y>$)eABUoY@mes%߭gibPe˖~!s=\/=H‚f&Dillry4wwY 'Os߽wpG"***Cg()2>>CYE)m`ykfB(7]J$`p!4xZ<˯T)xDL}ejP(DSRI (377G"}]濌}N 뒱%>l~`@1ɘe=¼nhm~n.) O*'-}Ȳ~xG[OP˜Bs7Ծ33A**ʳTU7߿(z_P(d;WA8'$J!2{/!ȲHi 7}RZ$)!7jv[wg&(+Ll֐g֎nn̯9}n= KLNN ozk=M]}3ɵ0]r*_ʔ}_ڴo_9Z{`/v] zIDAT_ʼ'qbK*DOKuܽTRTT*HN$'<ѫg[x|wph/6Q^^AW) /` 8xn7立J(++FY=wK9}|sg Gy衇xG`v?ʕ+y$;)++aZ_q=$S)Qe>g%| 1==w}|3PYY$$QUUE,gvvd2$Ɍ!I+ ø.jkPUA_ C/^[na }uBA(D"Q 'Xsݬ_P(|>GHR477L&~H&&bҥnoP$d ŊgyzTw瞣+tJwӉqm92̂"g)./L}tr8BSr< `<~]GD 1$3Qn*g`(|jeo?"ٻ\BW3T8$'>=˙N00&Jz$IsӅfTy~?'>rVejRB}> ݅dFH}qSi닪g۶mlݺIocs}xyX)yy뭷c׮]9r;v-t z狥-w<*oך)eRv|?L;/ENK-7!^*ִK^J||ZbPܸ) bmGÈTc8Z!d1JX׾*^;w/Ou&nٲ~jN@ꫯo-!y^gnn:n%*l?AQ~.ϋǣminnԩS,_Y ۷ߘ^==Zʕ ''ObDQ..`'N`[hkk?!D^xzRZZp==rwpIFFFٳT?|DnUV011 O~ ?.$IsОH$X/_W_ctud۶m1^bQ d'|V:;?r3]"|>SvMfW!(Ӝ9{fNw2CR|ֳvZK^)3Q `IPW} !QY\JY_z&rW-M~s|ꗹ+DՕ(Íu_.O,[oG佹X"&ǩh=+FN *Be2Ho{ |VfϹj; ?hДe&e+V/|aE5NPOgff)Mrz!RT}UUy`TTTkG8{,##æ2EnDQbs3R"-od)`ӦM;v^V^ݻY6J[oeٲeYe½˥KhjjbΝisBgjjjkF7:PUAoo[lk_*Hh.2ʀN=w .]OLp47w˚k fA` '@5W#zfffg29HWu9=U&<gٓiѳ'x~'L$9~jw3+[9~ 9f.D.RU+p~3*FG8/SLhnaV^DF[_#Kee8;F p`4l8oR$I1VNqCtٕ{QóD":W]XjЭ0}}WK ;vlgrUmi+^Sz-&&&8x ?Ν-[SOQYYIII ۷o'o LkQ__3<Ýwә|mH} lpۺ՜e!RD2$N.gNymٲg}fȲ|f֭xޜ9nڶm81:;w`ڵHĆ I&򗿤}#pbe~Ú5kȑ#ݻ[bv,.." I{_}Jl۶իpw|)T!¯K{GO]MgE85İxpsc爩~8Prg-gD7&s<9_XPf9?BGKKMAU 8:D8Gm:*96Ld:9B_Gg# WȪY!sssiin5IR'x6g;Led F~G17Ɋϻ%5_BT8$IpUU5cc 0'RY\MO?EY2t"--iu%4mkn^/H6<_qjZ^rrihdkixX#ӹf!w/7Wv\ܽ<coW\[̾k|XOٷƾC#~Psˆ|2L^nmJ!l M>Y]kYagRN`~̼fo'rL=v_㑾$^rߏ_.u\s~4UBo^}n =?s>'vQlqPZ&gTVёN[n z9?Y}YuUǿt<-6ڧ-^cZu^kmVN_&wE8#[Ƶ|)L:O.ҵT't%htߥ`NyU/Y8AYP^gN{t ìڄ]OeNga ,'ebPhT'(ݢ-5G*N=:jy"smI Jq^YeԴ"=!*๘ٍ듺1teڶk21Lm{icK E:-:D^Pli$uq΂&8U^m{bs],IFlyZ KVe>`4633kh*euлjuYxn~.itx^7 t_*]:ʦ+]'FjaҧOݗC"hnn &^ E֮SL&iiiaϞ=466KEya{t-|f.;S+Rv,,h ]^ ŔWWJYGO4iWYYUy7mق :tW;24[I[x߻ xlzqEY^:TiVƭ/]:kdnj>y:lLonҵ!Y;h)(d)*~I LtcJ_2o廨ŏg.㳔`KO:/ѻ M7xt>󞓰K[_sN4,ˁ?=*hRJTWJQa!kVvGMu5$% 4\L߿j)..fDQ-~---8 IIckm{Zٷ/E *%'' URT]m-TOqq1Xj:::H{m9 * T~Y҇Oy]G+m^ӿ̈́~=:QX88֎+ [[[!Pѿ?hFjjjWA'zG~4JۿZ W <˧ 477SSSCnn.C}}55P^^N$I aC}}sA3K EX'aҶS>`+px% jkkɏS\\ ˜۩#+Rw"wmpq3-75G} frԟvNrtNǣ=bgQ\|E;,Yd2s结cہNiW&#(&.W `_;gy}|-<DַQ+\mXN^˦M+?g}~;(..vغW[u}uЕ>RTYAwʚ~r@Dg&T}vF)66FY<|ѥ6hJNHK fU'(ymMg QSxx->![wLF*Od,E\4p:^x>u8Oz^gs2ФmM; vpOΠ>m7~y>0:@{{; 7s""@ccM͔;5hjj~ad2UL&ٿ?⠴UJIkk+MTɉ(r644JEE!bBX5Ȇ^gn1LȾ/d_U3q%w ɂI BfN!P8@ ֖V$@It#+wwSPPW~ .뮻͛ү~T.^UxzjNQ}:^lȼ/={p};9Qw1m4neqEtݡUd[7|ns=.fs)ء+4]Zm>Cիپ};|3\s͵ۡSR/?1cDr=wo5;5}}ikk?)ʺ]؃Mi$u'ʶ0Ի!o٥E~+lF\g8Xs+ P)RrJy Ĭ4 Ek#Zq9Vݎ8'|Sz?:p//FeAt gBKU&kZ]-Pj^2P%$ZQiʐL[o-Re:E&U2lhjUwK͔h7Cj:e =ڃ&f9ǽdlJfMH\/U!~!O]0Ic|HGz Kל]6~ y !>K3Ǒ>7&x]~N[կyU~ Leŋ xW_YQU 5BRkQRRB($ڊ9ƓX,F(J2x{Y4S1wԵmy9t'䍲2)GrR؆.ҠA D`0蚅PY9}a4׳n݇԰|r***D" %۶og;?㎛PbYm۶QQQ 'H$aܹRO? >۲7|KOq}:>wy3wL6+c}`4ڵ%Kr'!%Kk.BkmeŊܹ2> l[oأk裏&:}Fg<;m[)**bʔ>|xꮇSWW ȏFMgL2i$Bҥl߾0NYꪫ ._NKk #GOb5lܸ".]׿ub+W!C8inn7Oח-cL OJAR RW>O H&/1i!LBN&xC#ҝ*w¹uLgarvnܵx=ug9Wc$© M$ңxdzk]ámtZyxl)fKM#/!BG G:LJI $;zFMqIj i  Lp8#䓍4662u4[s۹wSQ^ΰao~eҤI)}9^~e.ZZZصk}/-X\… Yf-v?\v;paGyw٬[w̘qaÇnX(C}?ӧ7 c1TWWsϽ3uT>$O>z]dĈH!X~=QN>zl_رcٷo?0CT2~8s NSYPXXHGG;֭CA[[;EEE6ˆ_zik:-**bܸqb<3^k_W_}/̥ˎټy3C eyeG?b.^w}K.y^`ʕ\uUL6{ӟL&8vfϞ B0drss),,dܸq]vrg1gvwe9y??)AAW3O4))GFP~Kwزj'gXi}5 &Yז@!nx/W]x$v<]lҧձyɦZj\2Y<ʮMWƟ|}5,985CXvJt.//zӫPz랩l>#0N8 w5CtsmH9]/YsV=ëi@gIDOCO`EYn> '^ $I˗lR.b&3}W_}K.S R0xeK|tmZpO?,I)m9ywdǴ47r3j($(rQX{n{>!Ni`^ @Cs{ׯ&VW˄IqT@xė DH#L0u$`4X]$0yUVV2`@-[F,? /O<X&E8fU;g?}cܹ//P8 O~cMOQQ?O+2+kKrwý^z Hmm ƍ# 裏b noni?&L09{-Ǐ' RY9N8~%s뭷2dH%d|SN>c&5\C8S[[Kgg',[n A1yd۹۵kP\\':|+<ԓ1޽{lx f̘Y瓟?79jhᅬ-ܹx/ub1FsgZo{A"t1|7pxcK.}ʡ$ ~i?RPP@~̘1۷3wg?g̙l޼'xon6K.7n]Wj[iޝg<4LHRn%O w)bs3}[fں>m]:rlg4@)g0f Le iˣSj4Z|"BST{L2Z7w ԻV@c1Aj2h,}B-גIl֖EHhf *]fUJyP إ> H>Az~\*T'wԩgy7|8v{m%,Z[`iͼ2I1P|Y+cT,sQ/>/-+M:j[2ɬߝu4s[VʴW.1=GOpNV`g7idy;?5WQ9dG? d'2I<`kKXr9s.3OqeWPXXft4a~8sh3N?G(hj#wy 'ўu!Y#K:_E#w| -մ -g TtIE%&Sw-y->c= &O̖-[`alK&%۷ ׿uFj["I֬]I'͠~?xDt| 'QMsr |R"Ivٙk_;tVg_!GRϠtܶIMboipF4=a_(#nl9_yHL8 ʇN.oXwx,1>r3ihdf=UQ}OEžu,ϹP#6LR}'ӃVu~yy9|2.z-?aĉo>Բ}L2rsʩg),,dw.Ʉo ubN-5{YF=zwb p-U'X.-U "!#pcu Z℣iLC@ @ \P9vvvLٍtl!1ws<´嘣2d:(ƎC4̙3ٵs'55.Xx6ѣG= @A*0줮H$b?jǪ:t'M" 1sLjkk䓍.: [۹[wI gijj3δJ[|cҌsu]HҩiӧN_?b}~!.2N;4Z[[Ե|c(gu&=w'|@LJÇ3IæNkjtӧNJIAaSN7`РA|'yYZ[y]aW1Btb }`@ճe]wRW[CQq)pՃj˨!'_@+Q<$\!in0ۙ00`$=$) //Z`o52XYϏyUY4g\tEsιAu> }?0|p9뽿 IDAT3gr nE` Ƭod޼x4h0 ׏!C*5k999=Y`ĉH3 [orJL9ѣGc=@nn.Əw/B0mT"00a5y晌9 !g}q1xiK?3p@;<|KPXXdSO{߻W_})%'pV K'Š+8c`JrErr7~|LBEED !6inBpM73xg??RVVFii gy&̾o6@_篘8h㎋L:G `ƌɉ|?Sc7|pf͚e ebI!d鴴#G:}?3p ;驔 2Xp!=gy&f͢P(ȿ2p@[/Ϝi%ƢkmL&eI`2Mzg#ImUZs.I9{ڶXzaS73Σx:fyPo:P29+UN ȥ-<HOOЏ'XhuY134Mi?>cuFXk۞~A"ε'5 I­DMUW)?Bfn^zIЇ++i> Ai6muL^xwb0zw`Y8Jwس5Q3B)5˧҉*bh{Ҝߴ:&ZeӃTɊ&C/edɭJz3FM|Ud^?T 9'5 ӕʪ>kY*)k*T[T0Ƒv3͂@ @NNMMDQ0T2et()) PPP `0HnnXD\8oJ7CBawDFGYr'r:{AaQn^~~;OnaAd~JFs|>bʈc$88a*{CiiR~))-ЃBVyfoˀW Hm玝;Q&pw -<*@5]ҕ'^/H-V=]ƫ:UO3h[DҀu)ZIvP#^NـbwԒ<#Pt.:`U-kZ,%Ͻg^Sj& uǚL4-mE8{Bթpr-x9s7/OBuQ5f e>e\m)^YEw(5p݁^'i:py*U*Q#Aޒ3NmYS$R_<ձf:Pdy9jk3եp~0֕qY˂ȡ?1yq|ai~iY_,' πPh4J `.DSSt&O)%{%QVVjADWܾt uuu44SZZj|AR&ٿo 0|CEJ@Vcgo-(#R$29Z"9O ر={4N)**d>K![HpnuU8m ]jQsW3[&Mfw{zxo~#sa zfLskjJԱMn%suЃӍ=`)r8eƙ} gOr,,ݼ\YlO𣓾t(-d\ԄC2e?M/;Iѩocgz0nխrʴ> hmm?DP0H~5p$L2-/ RWW˶mhiiEJI8b d9B_[ill&L8`*{'xBыϻoy}%҂)U&pwuN-c]ν;9 5i-jufli8pQuZ)6 85;~=AOEt. "JKg4ځVl }v?,>w( 5mgw.ؠb~O߽9:z}p``xG\?!D" $I ZDB`qf>KMMXh2?jEy:WI#/ " օ6ը^ɴi,1[[l߃U\YF}|@ZN[vW##ͮݞYAE;ozlY=r/>`ʒGiz-C+pu& ?ϋUi4-W4 |!u^\Ӌ¯Vt{;>k[i#\O.<7/;ʠB4ZI¡?9֋OZyM>/l]ʺ/=tw$@o}|d<+|@H3Ijsnqf'+;m<|228~k~#PcqK>ɤ4Iq &xNHJ*䪩+%ݩ6qs|%͟N: p?UZm CsEªWkڕGq4dsTuΪש?LM+Qm]_ڗm7;tӹ+F ,]k:^""*S>>>b@! v ʩ }M?㹾Om_@y颳X=Ϯ9pZ9eItH8iUC>dӑ n> KzxI{M"ڜ]<ܾC"[GߪY887>6- X:~um[O1:gQOR٬Zt'ߜʠ E Ǜ%T{+D@yCr)xx=n՛DOB􉹻<}|>=5I\/ިe]uKy09ɶt:ُtIW)t ų.&bɻXqZ.-]_7C7m&r^dfC/=-]3;?F'6z=(A8v{>Lo=zQKD]eZX)bj7OP_wFjoP'8X&LWp^MiEWi*+놮6f:;x׋:=nU;n:hI;Lzt}OW٧t}pJ*LH&Yz >r& XeLi?R4sMgESZ'߮vJ8>ĻȩxUn^EVG]:ee5!Hv5NruOl:@~A|Ʉ􈝥wG#}JÌwl vЋ_(]p_(]ٕZ#|W 푎M23N7>\dV Nډl]feoݺW-&aLX h!t]j:_?dp5:|WLPBF]FW]%t}pco~lq}ߤ>|z|oٿ&XG(8Ṕv2@5Q2+ԴͺS"Tj953>cnjM퇵؛]TZ|~{VyoL&aӦtttg^6oޢȐ҅uM&%UUUW3bkpk}]HKT˚->K]5S[$mj]tWWtkk}hi۶%mmR];:i}NMݱMetZ]v%]4/kfKJ푎M|G3pFAU=n|ﻏU+W6ƛ Cv|H^yGIF xǨ1DEXvh45C3{ǣ=֭[mIN6cY O;9_tO*tOn]stD{{,_VZJn^`RH0$/77k孟!ӛ>eNѣYd r 6ϧ3SDMm-nvlAyy^{ p'z']g{F&L7G?bرD LC `ԨQtI}yt[ne477poEXjg~:_={/gϦ{wf+*;a\yL6wPVZƵ^ȑ#u^ݻVo2{aժU|iinflcE]%_믿΢ŋ B|K0a۶m㮻~ͮ* Ƶ|\^|E>C;:8K_ . 3O?G6пn~3Ϸn%77]F!ASc#s}_?3QVYM(sΡ7n[nc,\7x3k_ .䭷& r gKmm/TUU1x`6õ[nٳgkW<?OXv-ϙv6qDrJKK3y-,,dFC}O`ڵ̟{),(;#F ʫ)Sعkwy'/ΟϻKqq17nٳYˌ=3 䯭fǎq-0thYC.ɓ'SS]ĉ+^`͚0mT~gn:<2D"ߞ}uDꪫ8nʱ2s|V\ 2w\[na{Xr׭c׮]\o;+.]ʈ# /b}%\tL6աl߾믻W^}cǒL&o䡇"SXX}=ϟOKK SLK.0+Umb푞 Gz.i@{Ht~ӇOMD|KK DѣF j  7'ÇDss3yEB)! 6c?NIq1~:`ʫo}ÆQS[ @$ay5j>,|ӧO%`^z)G3<Úk7v,\TVVNDJ+(L KBMx0?7]>C>ݴɦ7n_W9s&&sկ}N<ŋ…\ua9ƎߟիW3rHOqSVZʙgsۭOYXoe`޽466|rnw(++3hB^^_~9 c-L0'reC?Lcc#m؀_u'l E1b7t>w~ Uj<{n>h+X|߾ی3;w2gt38l߾|¿|_H&<s-+tRƌ?=V^g[ut‚ 2~x~~gqIcƍ4571h@cw{#Ŭ^/-7L8se„ lڴ*~/KMFo…L8(nV֯_ϳ>??9!s3nX}qVZ͌'1t0f͚E<gǎq߿o>m .>s7X|M>lN<$oۖ8p1yiK>m[oX;+Gmlv+hninݶ\:,~뻨̖wދ˘t1̙3D2Igg'-W_Mee%O>~)ӧO.w;_>]7_tҕ%*.çrw 1v dRyrr1'4wsXzaRJ7v,p@RTTBPYYɞ={VX; B466rePYYɐ!C`0 h;i$:L `Ϟ=x 2erss [?rss:m*> )((`„ !6t(}zgCd2{)'L{{;ذanj&Uг`B!JKKڨ`ȑ!(//H$–-[xiiiam\{52j(9jQm[OXt)N/[[׭_OKs3)\tE}~.|ѭ:F~^BbHhkk`Ive*;\rs6mO]eU>]e>ܺ’CIhDͲ ~q"Hjdq!65B:Ψѣ[FDlݺ!CP__`iaڴ\ve=BH[)WC47ƍȫ(/gL87he 4Nw{TVr6| :#Uh Ν;駴E455СC]QaPUUEIi)mmm pɲm6>޸$ ~ ۶ζmhnj`!L6N=D"a\ַtȑ#0}'8Z2|0N=G|&|IN mm搛C[{55$ID̻@@9uրɧ2bp>##(w`#[ӧO/gegvbW5j&CQa9spy1iER IDAT$>^?VO0hp~4jbUtvvuVZ[ZIee%z*3N>xݻwӟ-maȑlۺ!C*ihh`˸{#/?ٳg# e466R__o sr#dǎ $Qn㷿-Λ+-=jH-ZONN$H{Я V{ƍǖ>ORJ `9|7qFIļ]/f.t/ +Ygq%P_O?/Rɖ$}xo|o ﯧ~Q[[K{{km=J&Y_o8R&It&Rv)7mDNN}F0CJűcǚOr LtA~~q$999tvv RF B477P B!Zc1:z4/ 19λKŌ'CaSQT~cc#[l!/?t2vX6BBXV D"qZZZBO8!99ƦF HZ[[ioo';{gә褩) mH$]vf t7!gnNz0DA^^P:MMM<#?&//b1|rrr쾐RsUGG[n5ƔY>?/ @~~ƉHx97:ݖ\MPh4j?  Dr͚H&455H$lhniN‘yy;6k8!UЧOWCoЕRG^ߵOW¸L& ܹX[cF&4^ C6}0wث~z_]%\uՕ  \L`#2=ݬB0h҃Ƞ3X1θ瑟g"p88y:Om* R55xFm`uBgAʦ6o°aR棲? !#GK% X12Azzt=*{Upuu>8 @qq; L$:wc><=B7Khyև&<w@^k6t^uAԉçLv'ޮl.,C^]zOi;(>D:A"]to{,=ݛd$KoOI- ~ӇOM"| hRR\RB@- (!4 0C[ġ%qʧl /ȟʓrjy<ڢ%]̣://pr nr~M6i[ٳ!d$5>(-tW N/ݧ}>}u%,k[i?#z"RZ*4KtV Xhy-uZ(u4@ˉO=mS۫iavA:dWdk[[W+Wd6N溜w=LnIY:|wtq0҇Nt8x`8ҕU]u8+/2$K/[A ;.~86w3bQU8Ӡ~u/ԟm~&RA6eZ3G /[ȨÐʋ0Yh>?/xԇ4P}B/MlZ#a|^ „.PS5eL\4 ڑ)Rry٘'մVA oZ3"&DW.jC7"M!g6lP ?1bݐ0Rû^i]w5m*mKwtNn]v]|oU3JkGtdck δ^c7ە)Wp>]9]!TVBM':2j*̴ M.3;δ"-=U>Gzs9k>{K bN<*/v=VfK]Y|XLf+ )!H 3]ޙX !1nȝN&444/H"%%%Bg]&kLJU&<`*-*A*g.VtZ`feUkAK:^ dNj~fttٳgyD"9dAAAAdRJ8{eA)5L(//2VG ڵ<%Iɾ}hmF|>wx\?w{Q]uɖ n 6-@覄 %H}%B`jMqM.^ޥ]mEUq y)gΝ{wΜ)Bى@rrJ=4ee<! kÎ, >J7="F4yyݒ *H1!)uRǥ{Y<={NEqC 6S{$ D!U: D)l("=.x}_Ȼej\ jEӡhc'*&Iܡ®hGL7'T*= Q=#n  BBREDADLgg'&)?T*IJJp!iT;ӁRjއ  & NGY eЦW9"0Ǽ{*$E}!&:BN,gv:TWWQS[ &:C0 F# X,fON&wa?1Ac*j#ٗ ®<$!/$\LFZcFmԡxGj*}tUH]|,zjl#{X6QQQymY )$:5CDYk9I[d -cR@Κd͜dXVlV+d!OIxbD{tS[W[oM{[?ѽ8^ԩS7v,QQƾuJ*n0;ez-I ؜8d!T=t`wؑDೇlJ{>z{ᨫoz&"?"?QWNg JTr!.!p\r,5vHZF!I4`'!`h3;0w:UhѨ؆t$.cޡ.{!!!'>{=2N-fС(bSe-{NF Izz/+< Bz-.Ʀ5u fLƠUbnRz>8ex$$?,+( hhhf#1|H:ۈ_ ۾ 1:?y61p@%%=~jlV+qqdfe2,{F}"5~BP]X>w..֪* N sy3J%-MM>f IHRುh r+񤦥T'j.~Qw] wpOU(k HB8d Z%J@Vܻ*$Xo#U*Y&%%%(J233N}y;v`Μ9>y}̜9%#U[#`|I6LUUZݳtV=6zR h&m"C@+7a5n[hG7bH\4tY=ɑc9*ke~4 <-Z:OA(HtqCp2QQQ:qlhbIImeʺ:vsILOw㯭獷rq덷f%v>h=$xzƛOFFD:. Fcu $N5M @$8sNz}7p귘={ -_4=f^___B&Vٓ2hPR-\2*2zOt^wͤ @:,6mGti7ؘ;.'^DQtlu Л ݣ:E$''bu2p fΞC]S;6NKKÁJuilj|<|0<YmtvZxgcQSSI'yI]Ɨ_]rQTwͿ+/CƣMМLTEFKcB'@eI©סRS>1L7=q(Բ*ʈVJ?/ާxVUWWvZy=j <iiu]\r6|_tyKK].m ,kBc4m@&b ܧ'ϧvl8y$_cǎb6ikk X,!Gn݊BzBͥ t\Kcc#[\R)++ 9:Y[[K^^.EEEvv;%%%ЀuPa~V5 ?"~NOS-b6mO%%%p8hhhWG,BjjjͥWN{{;'N䓗GKK YS"8]utt`6jF}X<;Jߛ.f{C?3~`;>~{V'n(i$9ׁxfVn 1]1~=N/Nz}ݻwt8rx? Qv:{ԂiU]}D k;DjINS詭GBtR3!}VBYU Rpmh4T hAVbwh5wR^Ո!CTkʦBY#*&^^jp:]4tR*lb+璒Z[XTI&1o<֭[GtL4m| Z6 @*bq847qHK>)c-ZDRd7]tbok܄Z>'yXii؜N*+hnA#*Tqq?62 d^.'?>L;Wznwimk /y(!$ }TF@NP 2.d +-t͓7ofRꪫ)((`ÆlVjkkXz5wyǏ&Mɓp7rHZZv\l6 Nx xu477sw2e<믿NLL  {߻7rQ, mm|-L:5@/E}}˖-c…lݺUö IDAT7$Is=㏣V ̞=jjяM>|8vAt:q | 7t3Ço-r2lp2d(---۾`4ٿ?dffrQFų>s6 qF5f UhU 6XďSv;?َ䚫f}Lyinmnr!8?>MvЪ5 Kk[!hnjBc?t(u8 FΝ餭R:h;|hd3zMdg&5%Wzd|kyM?T_ ݎkPM|{7QAYMH`RQل,d uqq?b|.;+aܺ]z58~8Ç粒k۷3g(,,$..D:DZZ2?Ν…7dʔ)Z[nqQ__Ø={6cǎ;p᪫fȑ?~W_}ElV\ɮ];?< 2ƬYX#_mۆFf̝;?OYN^IIqVdpRHLLGAb!&&BNB{{;M̚5سg:Jꪫx׸馛0a|?fԨQqm/5k̙3RyX,=zvn:̙),,dŊL2|rMFss3deeu:t*Ip燓,'YhZbbbhnn&&&&`gDo Iӓ%/>{>cl6v;^{-J[>rM]k<ü }]N'>Mky}jqpL11vbtTu )$hipY뽌X B$cSMf_r$ ęhu2B%+AAҺj FP*$UMҨmӐlAzrf CgdE`P킍NGGG?֮YF~} %FzD|^[!Fa)'c<'O 19֬,W36WsV?}=()-fOHƈ|򿰕?Vrk~ς0~rkO+M4ZTjF=HuMmڕH NVt"1  Nb}c,c0lVdYpK*;vlg|.lqKS[[ҥKh43*=z4 yimmjx1zhJ%X,B0dJJJ{Wa;pow1|0|I9+̒%KDZZZvO_Yn-?k&N+̶mp7FiZ-NFKuu5BƑ#G8y$#2bH N? Z,,^p Z&==hmmch"66h<<;exGNdOX%dY0ay^~eV\… F8" $IBlMfa|~) /qO t`~LJ~HJj K,q:P %=ڪ4u^i :;e\+Ý,;zg2uT&Nػǿv~U=]'YΔ90O?YlHBƉ&Fu-V)7t6ώQ($&OfrTV$@VjuTZrv̉;HNkNݡ$<A}]--uQXp(#jEQ r,ιo(~k<(P+ O"Z4OÔ֗zV|}{ȧv[_kraw:mVRS:qBH'>j223I2a1#Ictrw%$jIiQ*$ÔT(Ii$$$"3C+#pڐV$?9(@RbRQy?νCR3S(fwjkk9zRP^^lQFFEEcǎ ?;;;v`6wi&F Qؼy3f{f;zmX[n5chddk.KFFoG?1cs)lܸ6֯_tz(,ld8@uu5QVVOLL ݻ,ٸq#GAT2mڅ}]kjƏϥ^ʕ/|Fn6-[ʓO>~ƒ>w3OaFRq5+1,[v%Æ ?`Q( 8hONVVBudwq?^>_zՅ9cY~f:C 'VK,x6o1oEtt.1ؓ\rK7ls'Y?!! &Py"V+qqq42gST\M˗]}R{{hƻjykI~z*UXZśM3jtv~YC oAy Xdl+*G:.J]+ 6Z&y6v4cT uZz NFiISIH5Z~d#G_Qz#jWK Et4vp:iHL {Xf\h`B[]1)jT4v%E>WOޝm- BNEBIC]5 [ \6Oe"tr!_쌞ֆ :]wKEE9/X6f̘qˏ?ΆL+Vu]3V|Ʒ^ HCC=<o;±]EAm!hmmtp8A҂ABBvj5@v2ff.={yc2>ps4Y4j)j23pd JغGetFљ.9㗟QWROI%%?SB{/<-G;V+7o$%%QUU N{{+MRqqn+{ !be J7_ʶ/ٮ؄JEQv.jtha>26##}P>ݮĽ]&D%p{X_ɦtTH Ɂ5ځ$$tm.U9Xc "8nh|}ɄfC쩯!<5@}od1nB!Q \iyE,C߅m鮻 5璯X`uzlVg& pYt4Nܢrv?uKfav 0% "~'q}$0t tz 霽tzNz @`?KjjY*6l@5 mV[Q0}$MI&S=g{0oaqvjK$!5 /]J4 ?4jSB"5; 9y3.JsS55̝3%_]g~{"l$%%ކB@TT|F@,t zQņK1hUn#Y|2tB͆,|@#_8BZ HADGDWGDWMWBZZZCq/\2bVksCAb6iinxCZPeT*j B[PP;ɀyP(-)y0Qy3/}oN't:J%X,fφnI@R#M!m@ϛeAd!|SF B)&0o9|yu  'c*t^puZ:]vt&b39|&F.<= 5܈F`@Ak po&}V, S[׈: N!X-::̌=ck/е8~Wo;?d9v2 _0o"o}Yp&Xo:oJj~#*a(IA$?Y0Dr~pGTnYmFO:9~_8 lѡ Ymg=OW yU03?`phaLtU]Lo~_5xuu*m燓,awPxm$(||JMwOrp:oR?-~?dO gAʷ' u >TW>uu词N5B{Ù[E=ϭw[6N!{R0śzH ?ޔ7]pBw,H,ȿu <#8:!IT(|Kܮ<"v"{ӨQGvՎ }X~| gEW+R|]unquչIpIp"A_=]GIN>'7*++tKTFtusa mPDOς$t8 EgϓPHt<ֿ'H{{oؗ+<B!d N;N;}.^8 sPWgO!ͺ2{aK/{t:zUvv"Ij?z_Й̫ۛB {?Iw祕/3yD^>|l>ʵ\V7Mp!DCְ,Hvv2bĈJ?DAF{ccc?~:'ORd$&%TVV:B}:a}un~K 6ÇСC~NcƌTWW3hP& IAL0Ʀ&Z0~'NP^^NBB'NBL& l6v&=#_h$'dddPUUń㉍رcX6\.'9g{K`2ٲeEEoIG' ׿Mrr2$%%׿> /mv-u=z:l„Xf-vcǎ3fh?Su Kg'k׬#db}d3{, zCϿc=@{Hc}Y5tzk5Ou9M荏)n_.$T*|QeYxs.^ZɌ3vX_]n6;MhZ.{jjlV+6oa<ٽ{C %!!Ep1֬YKYY9;vi⋝+\}U̟7ygIO?)S&P[SGSQQITTK.eڴ g> {464r8Obb"zAfϚ͆7R(((dժL45k2}E7[QTTL{{&Mdݺwoi3IIIJłyq9]=ψ#زu+' HII晿?GG{6o&--]v?ȑ#1>ZOJr լYSsIJLoށ^'ie͚ٳ믿&JK()->cȑh::̺u1f~hinϿ&[Q(TW+1j(6mތf=j477mm($O>4cǎȑ#̜1Z~?tR^}5Rp¢bef?slܸɓ&t~o‚tP}<|܏aM67+---ȲlIMM`AvRRR$Me|.dy ,FN Agwy:RS9þ/DR(ؽg&##Zä6_!CsA+asER[[˜95zh.lRSS+QTznR\\Y3jR[[K~~>]FcLJzz:kYdtϋ/ɓ}u:|8]vs]w2tPVOK.E /%%9,NLrREEE466pa4 ˖-%##;Ή|n_~Ç x 1 ./yݣh5֮]Dž^ȲeKٴq3Z?eK@VEB z<C@2_&!e-^u9M7CލBlL񤦤0(35k֢i`>Ge}seHJJbƌinn|&N@eIN(`5K.ϸcilh#9Gr2._'{VV˗/j<Ȟ={#33UV_|M7-/|Ut̞=vJK(((>ArRY]wڵh4͟464b2œ$'%s݇B!w>=JzF:>Nuu5= FּJ_&o߱/eK?~ EQQQ,[raTUU| 24YVVVeV.z߯IKKill\:::zT]}Q]]łZPZZJmm-#Go#??\BA$NxWܨ㍛=z4ÆGQ_@eE ~Dpw(RW>Ƨn… f…߿nD_}QY?pʿ=1//o7O?ͧ~~{.t i'}u~v0/o/ ۷o5_W,)+V`ͧ>//{u^LdYFRdE 2$w2ڳ=?npSzf|Ku~p=Ipz*5_֮[/44UlL JҽSz@qqqֺ]PnQQFzA~5 QQF$IBa4,v:>s~ٳgoΗ_~I|\K,:K݆ h@Va}|AK*$ضm<_P\\CP(CTr8\`@*\l64 zNKccV^f! <+nƎ;ؾ};nu 03@yy ,^CDU%Ru!u%BJ*"/A{G}W]!CVj5466=رcY Gvv6:m|BqQ1v+Q(ia}پOOO孷AȂ{>f=(Jbq? I`1 c;wbKT*EP`sȲLtV?.'vF`S]]OS&s_u:-zÉO*nk5Q\.N[莮{.Jä+HOO;>eݻeV?9```;_ߓ s=Z! ,`,\M6PYY)//窫"66f,X0 ,˜8q;v`2㏹:M$xQ(̘1 6PSSÜ9s>pT*h4Z3gr饗_hؿ?ӧOgԨQ:t}ǥ^Faݺuu]Ǚ6m'OFrw!'OLss3}MMMLc~gMȑ#oL׏`֭[ .~"bccIJJZw܁B 99Rzٱcyyy @?8@rr2l߾]v֭[9yoCnn.ݻw4 /QTZ<֬Yl޼?b X|9ƍ9s挝|=V"00O?ӧO+AAA9rFòe̔:I\z5eeeb0HII!55֯_Ouu5$%%QVVN~8v۶m?Hf-vZvލ^###s áChjjbYBYr%kƩSoYbz~m`deeQQQAUUռ;lv풾7o&''K"aƍ[&J%+Vl޼.nnn0qDRSSIOOg崴P(x1 9s[a9̯:&99Çj*J%|$''SPP;~!ǎ/ ܹY*ۼ<~'222X|#ۛ5kp1>cʕdee"ZyZvg{q:]mFwcϞ9A$#hƌ͈  i$<<3g2jT#G2om;!!!3 #!!3N˘1qDƌV/cƌׯJ1cFèQ j4V>|p`ԨQpX abDo_eA|&d2wu'Z cƌFdb̘ѨjF%c a*D`ozN'11u,qǏDFEGhE'"Rb,NgȐ!K}k^l+Obڴ[ c ?̺s_~%G0*)~gΞ+K1cvwRuTظ|q9HV>rه,xa!/C^_b/骽gyj?K#2fhvî{XAFJ $$ N<أ] +?\щ{cvᖶӃQ8z  (:}O??onl"ALfm0ocbٸ7ߢVYrƌ@K<}^Y0?ho1Lf'|jFgdH=$$IFf& >رcٽg ‚3f41Ccx߰ǝe̘ф 6w"O'(8no?,Q6u#tB 8_H/&,,{2{9Μ9Cnn....Xf`jkkyQ*-'ѮZ q1j(~&NȰaXz5)))!ɘ:u*III̚5 Rɹs!&&NiӦ1qD,Y 7Np _ "_}lذd2YD{>̜9s  66 … Q|?~/@d2wqo׮]_̆ 8|06lLLBFC`` 8p___ػw/aaav{0k,z)ݻٽ{7&Lȑ#TWWΝ;IKK\.  ># \.g̙tM$''#669s0o١fm+MFZZv7tw( fܸq=ZGXX ۷ooonVd2f"55)SpBG `Ĉ" IDAT8>"""\s5dgg*$88qƱ~$⮻_$**~NRRR1c~;iiiTUUzjIf`h4 ;]s8r| * B!ȃW܌^d2;5 ET*%x*]WU_a+ _Z.}T=AEoxaҤ̙ә1s -?4F ˥5ܨN@Ku-_~K^&HLe7ea)&LO҄ۋ#MПezF"aaRR1c:?WŽmg XKA ҡ/QQQ<3mء[4y$&O:Ye <\#хܮpkVVg\2`ٛ(T*+Yْ=ݳ~Jh瞻瞻g?kWZ-sa9T)k;kk'=;n,ƍ p5.m& 0Hءv,С1,O G>vf>6u*S"Տ^{EjO?dgq! hjjݝ9s`mۆ~EQ__X B=JKKl5wapww'==<^JJ,+)t:dOp׳m6e2ƍ㭷ޢV2{lFCt=z*Сm屒 ϑ#G5k^^^̟?> ^O@@'44{܌\.w=ˑBd2hxٸq?[566+WD2b^uf3O&55'm6  "88իW#7|?Dvv6O?4;v젡HRRR\.'))7|bL&_lǼGFFOEzz3c뗌pD>flFV1th,>v#]2B']WK>{J{'Ho1,d`Y+Z.݀Gl+=V~6EڭAoρ#nΥ6:t"pIWtCrKfՑ:;K\|zzr^Efyw<"^{U"#zV.w=A=XТE̜1/ȭӦ[1LpY%/s=-yyyu-`|W,^خ_?իc,\d<==qqqAj͞=={Fjd,jZ-v^gaDEEkdB#}V,?'ON~OOO{gի;f̘1rϟOff wޡ???LBII ?gΜAVhh4ҬeːhZ̙Ã>HJJ ^^^t:4 Fd2;~8:#<w,YgK \.5MA F\\o_qq/_R$ ￟_|e˖¼yرcVKQQo6ƶuO&N?aaa3xj;~ xzzr7Oj*tn-"J777A@VXx1o< RX[YuK z777靴>S(Y"sB}]X]]huKZM& Dr ߝm;rhZ٢  E{gK\d?g: ;+:.˥! T(ZMFRQ]]L=Uߠ+].U= |4**'394zW馛?~|aΦV^zN;?ٵk7$$QvIJ]SffyT*d2diHQ&c7_a'{\BWwt;C\]NrDuQʸ+.N˞Sۍ 98 Jqz9]zNY:~UW={qъw"2Ku/]iY:ޗdyujMRL6?{@e7k'nݪ,[CyvCbYK nv9;ᎺpL|plwr茣3KcvOs܉Weuy\.YA_1] VYJU ᐷK3Ǻ}eRLB[i!tGgl/s43D:J,:mDptw&3}I3k\pHqԈu[1.ءdzwjk\.Dܑ-=[YWKcЊ ai\m 󵆿X-Gq[Ҳ\a ]ve[NbLYYY9VXYcc# /;oEQgڵ.yTWW~zvaw^oӶm0lݺStFG?: connf׮] ۽r֭SygYt)Ν`[n yxgᜓ( nhK]-)9PW/fXG6g6!ۦzD!{f;uu;"tԅsuwE:㝕=m…uѝm/&^H=x\Dt^h+PK+g]~ ڌ*]zh=rt[il6HKKt@]]rZ|@`` QQQb2jt:A˗V..t7477hiiAE xzz܌JBRQWWZADf3555d2DQQl6lw}GHH!!!466$a-Zd&x7ihh?'$$0ҝiVyhmme턇ICCJwwwӟҥKrDQhj6n܈d"22H;L&c0?\NKK nnnT*hnnFѠhhhHEE׿F"LCCj777jjj$Z&OBh4R[[+_.ۥ9V\ѣ]rֻEM6q7*ckWWW4 z\\\tTVVT*j477r^ o2EA𠩩F4  ۷wy p9-[+RR]]*g}}=ͨjA`7N6Ě͜Kkcݧjm( vc%;ٝkG֑Fd݃œOD&zOoSOtHezӛD"Hmƥ'|ה׋9]2]%qi/_NSS,Y'O~T*b NK/Dtt4>>ә;w.Nbݺu|777ϟ?@~~>Fuֱ{nL&fƍĉy[9p|vob زe {aܸqìYATd^}UKNoᣏ>h4믳~z233GksA&--#FvZDQodĈ^Z8=p̙3???y9uꔔOܹsINNK2qDy Dtt4-bʕ8q`-ZČ3{;xW $$ロoT֬Y# ,cҤI,\5k֐? .dW_eFLKK ~{o$&&~L`` Zt֭[ŋyCbu'[#)6ײd٨t/,Xk:/]_/ϸ4ue'D vүl2X|9r9Əϳ>Kss32S2ydj5<}ݬ^^{7xZJ' JEaa!9 6oꫯvZ򅄄ORg...)SG||<~~~lٲi]wGg鑌r|\\\2d "==R'O&>>g}8J~~>}uuusNT* ,'//sO#YOx?Aj 2pA,X@RRIIIgϲj*>}:O=]泡ooofΜIbb"|444e233`|8w}7?01hkstׂdo.3yc^#arii8{@d CS~8KoW*uCzw|誮zNPW=浶hAZ!ɐdkfxgHKK;^f&i٠~e777"""شi*( nnnhZZ[[Fjjj777tK,]]]immҴ.HLqq1999L<;)22#66uQVVƨQ(2Ao`0P+mٲI&ѿ^OMM H Jss3hA@&Eyy9G&33Q%8:L\\K.E͛;Ԅ':GMM GСCel͍z F``t̸I9[d׮]rr7xb EV(xٴi>>>PVVd\ڦ l_*H!Q]]XUY t:|||!볂6|,GdFkDQwkgɦfpn_+e>%\.CTJ2b2u=btu%ڼ:W`Յ>OWusD4%Uہd2ϧy1p@rrrPՌ1b>|8999ĠA: $$4DQdذaxxxHid֮]+ 9Q9r A詯{_GP0tPL&h42tP F&11ǏSWW7ޔCC&a0عs'Jpill_g޼y1Bh4'NCCyyy1tPT*&>>ӧOs Đ!FFFRZZFD^~e^}Urss)..&((H::[d2I>}`DQDTR[[~~~xxxp1"##$??/// Bfϖ IDATfrrr:t(dbذarENNh4= $$$P(F.̰aPTH~~>c2ȑ#466Mxx8H9r ș3g8q2#GJ֒%V 2ӧ9u 8P9^'N IHHÃgϒCpp0 1l*++&((B qqqaH2v:z((Jd2YE@@tmK t:>x$"-qj1Z[ FZZ f' ^+hj4..T*dBQR^~J}t ҕL.CR"(`WZ7@Wҙmm(475bhl2chi(TP*Ѩ54@s:6 [eӇE%YW J%u2;)++cs=IM_|(r=\0&H\.JOOgǎ,Z2KM[neʔ)5͋%%]xu =3E}f*p>Cxfzզk5i5; &:+*N^߄  :b['{,35:y;Y:+\ZDvS/ʲDD:Qd(r..h\P*(JiL&3VMMM4 E+xz{K$1/D^ۯƽ++hwe^t)+ᪿs_Rچn;TFvB uͅ~0D A4#&N(Z!dFFԝq{jDbҺXy12tF `2bhiAr_:v]DQF#?; >駟1{ ,x\az͟ ƌm#GP*U [o./y>cg|B$[l%((Ç_]6oU^r\:{]u淾Yd4b2wh.(J,ؒ7R]UMUY f ~( 6O=W3d:~9Ei}?sl韯8}4I.AOW$%_~]qNZjwS{+f/ _++~29}Iˁ;3P芢Hkk+---#**""hiiAfD"...r  --->l6[ hL*M|H.#vk+vWrZ[ BAas}Ǐ$$[.ɉH|={6mlܸ \Ή7ٵWւhnjC)((J7w>r`Æ/yh˯F!W`2$yd1*~b7VIǑEM"l۾S' PñСè*222innfʔ%9F'눉BFF&Cbf̘L=t: T.YYHp ]_辻rg?Y8m3fքu^!CŶv}ے~SSfL&tWcc#ƶV--Klz0LK¾_fΞ=l& |圗G^~~{Zl,8޽pssɓE %ho 233qqb0pwӿ 8r$o___b d6lӦMERRYQ#˴߿_:upnVZZZPT8q?i%%|L6~eԨQ:u \M l6 ̜9JSO=͸qch\P*F[rVpP0'O"=7t $X̟{}?W^zY86ԩ'.n[lE2aBR]+ ΩSdggj0nuփW= {_pmew:(b2hnnYܴ. hK:{o-ǦfQ*U47 h4РGTҤoQ߈JL\fK2p(>{~A(U*k mY-z>~t:o#;̙Mxx8q4EE222@``!1lX,aawQ(DFFЀB;fcNYҟ>BO?O&;wݝw1Kz'rsxW!//صWoh46*feee\{ >LZEMYY9>|U⟛k^C!c4I?|(7AW0aB"5  bժU?"^~U&N!CŎ?2w\^|%BCCh4Lt1vXƓCjA &&fCa-5Th1NGAqiH{2_%].]9f -޶SD ,5e!v/ :755QZZB\L&ٌh30 ={V:ҷVNf;(ւq~u$G7j 8 /(;hD-Aky(X{ȑ$ӲQ~DѣIOO'88RP5?#FpQΝ+&;;)SnQ߀25.R rF Ʋ$PDnn.a jjj(+/ՕfZd29Bۉ& >b8=(d2vڃt%IWֽ]TUU1i aa|W(JCsVo* HgepuhB0s 2o6㏷\d}̙N VBnQR\\̉':4ހ\zbg2:i^WkUVRF1LfjJcB\.LKfq6qujV&QMMTUV(-y,$}ۆpZn=zǏMDDzၷnJhh(^^܏+EEEdeeѨۛfΜJ^OuM ^^ /u{5yd=NP СeL&f(ؾ}MMznr wAuaIC[* Sȩd2Q^^ S]]Cee 0zh _ QVqh,yQdrbsS{n \_2u- 4@C-euKst\E}撐@ѣ(:xAh?.H}q6姟~b„ l۶5++ Q⮻:u?##J%ݕO?)Hjj*ƍ,zSG%$$϶{+n%g}:ӻٻw/K]oRx_rՖ)yUXgmO my˳hEG]JVz=& ZmdKb[[[A2rd2iFy8/Qk;VHJEcc#z+`Sa#_ .nDTX:p6pm`\N8"n<07df]+75lf0!֬Y+TM?eEu x?}gTV#ˆ!մiغu ^B˱ҥzxhji_|1X,f3,X0) ASc#d2t:e_|իWF$VZI 0HxyHgݺ\zԍ㷿}""d2Xx!=8Ͽ"3g`Μ#+N.F9S[\~Xv-: Kn߾EQ8qiwnJZZGQ(fܹAhrg}+2 -;||Dqq1?tXj7?d9eM.O~&y=9=09LPhӇBu(zDezzzp:IflUU`Zx)/%ߺw 6lk$J,!4b KFBjrbAW p@+z 80qԵ}ZC Zم&|'زeECB',HvV>~*SVtu.u1 Y,z!ºCNF1iJ*|>Ze\Y):xO%EQ8|" F@[lg*/ǑNKK;` Gs$FQz:uq>BD(k\yF !݊_scc. d>ٱO>$)S?;vPQQJNNw}}}X2~_1~x*_f&N=ܣa?}2曹ٹs'&M&??'|}kqtttIcc#|{GeӦMpwpsc6~3dYСC<4777MjV^M7oK/殻bʔ)| RSSܹsOY|9\s <{%33Ol2=Jee%+W'? ---,^oG}7z)ol2><ԧXz5l+Vp5;3oktwwrJnVF#wy'կ~}VXou]ŋy9x ,npcvZjkk9|0|ߦUV#裏oSWWwkPZZJ}}=>(555[/~ 6l@NNs<!׾Ν;ioog̝;wy[ngyj9s&o?~z.R.\Hss3deeq]wqWDXf/rOlٲkX1X%Y%t!%YHdUJJҖJ$xf3}FclM A8A("6R3jYs>4cՈ G)6+Nm/).<=nRby&MI L}+!hrkb !R !T"`V1BB{ ^EfffE\HZڳ$Y嫺OT㾣#Gbw8XrEg꾼 A,MkRYYa*9~*ɱJᏠsČ4nFNdzE hh/KTQTB!==}D0[A6b1Y#FLX1 hpA#y9"xEfOG$cd59_`i:8:qm'fR ?w >w jmmeѢE,YYy'xcҤI|;,Y 6*PUUEMM 9x |_b#0exgu@0w\JӃ桇bԩD"z!+|SL[n?㢋.ѣW8N>#u`4Oʽޫ}裏|{㪫|8N~p1.r~q/E3% IDAT}[n`0Ⱦ}(//穧/O~:?< /ĉٺu+[lرc̙35kDkIj;G={DQ^MFuu5s̡G`ٲeFXz5[lNf̘M7WUXbtww2~x2220as ̅%Yp#Rq.M]4@88P8RFC BH1xh4swۦ j G@Fz˖. e `W_G[T $\T8/N2iRN'䣆'!GCNSNד^$!bI\n:0+@EO k+D (8j@=aF,z޳f)a|p Dnn.s"H-SQь./"~*\}*չ|͸]ARGО oap`ޞn`lfG0[,gd`ّ$IRC ,B! b2V9$Rb жI,&C9 vx& Si) 1Gip1!2VsrSE=LX=/ZOK^Y/$ JڱV'_lSO=z{{)//i3^u]| 0gΜ!_wu\.^~e*++P5IKK#;;;i O23uuu|_$P^^N(btvvr-;;;dɔ( v\l/"z z{{o&t:q8 c4xUVЀO*nP(> ` &/}P(Dee%uuulܸ'|~CD"fݻ,S\\BߢTQQ(\.***4ٴiAc{Czۘ4iiiiIC5Yˑ$W^y2T;.0$৖;yrg9H';pOީo[b~'t$ 7_j"IA;yE3ߏh"`e P81pP@F"Q&#H!ئxEQFS]]=YYY477S\\f\s5<쳸nؼy3._3fh䡇EtR?K>9 H ~=Å`ݺu\wu^|;<#H*#s3Mk!?Jo~70@Bu bug:+x6MPl:˧"J4%D< EQGӷl¾?`O1~:~f=Gົyiݫa_2>O?eWŁt)^Ej$$.V._W,Kx^‘(i.%ŅE(!dTY(QX"}>QE=sL֍Qb .u,I3f̤ ?դC MYWFx~dOUs]O?e$c2'hT)e -->,F#Tfl6&:sgXX,vB$oK;$$TDQ|~ #tB>l2/7D"Z64po@49>,\y$[꺔e<^,[Tu-Κ#(]]hmOKOoDg[97w?Zޅ$ɘ AN)//LN'_~9Ǐg*++Yf &MdRgW0;''\233 B,[)SsA q8\wuPXXH(bɒ%L0A=+//GsTb!//ɓ'SQQseƌȲ̞={9sfҬXvv6. ˙8q"TVVb4appdzxb}BVVv***oՔQRRfk &P__OYY.lPIbժUTVVRQQA `ƌ̝;J N̴􋊊p8Q uml6v;W\qH?''&MDelUAA dee( +VHuMM P.[L͛G}}='N룏ŮzN99s|_~$23dfeK|G*yؠ() բ (_}>z:)!3#C_=Q84-A$FF &3~PeBd2#P̓"W8VOLFz&!{2ba' ;?<ќoq|BHbHn0hlCf^>vI^^MlfP0@`N$,].&RLHHc1LG,}RN$ؽ4SsAӧ4?O~n Z^4Z{.۷o'77o|I3gK+OrַŚ5k5kyl_ϧ.uϞ=Cʤ!NebYVVV“DJLUpky,h_FI78E Iƕ=z~Ta֬Y477so%Q[ς馶Zji?ӧR=`~J2y\\t1yyy=vV.tNc)., P?}*丳aXXalz]hKAOp%a/y63~ꓯ7~2Ⴧvgr%=Ot"4DX{㍍,^hD>vڍ(̜1QS]*.ԙ2DiI l>lnl@0*eՊdcdfd$?)`8A4ń2"I`%lF#fIpZV 5N";wOKRB+(}>=D4J5PO̤u N'^C]\OO³soll"??寋 1O'WTT5***G?y믝?\~LSɑž ?)e9 X|5Ni'DÄ q'4Qˠ>>>I("lP(GQu/OU.Cc߾^+O=0좼ݻ6}| K/3a|$aZXy2,V Lf&MO8Nz 02ӧSUYM7~~_?WHggx0H4%D5:$)J5յFĎ{]Cot"IiTtd)?RJ E!9OnL”)Sعs'_-lذKoo/6l$??ںZ^~em{O?Ák;bz@`Xz_mx<^|ⷱ7 =`ںZJKJx뭷eY֯y{֪{ x V\o~(%%t[nϟ>KEy9MMM졟!!Ӵ'NdW)(.e%Hz{x?w>UUqUWaeKHs7'gA|~̧y7q8,\x 0Qx[QIڍ?@4Z[B"~V455QVG.""ȘndȠOlsHOʶ3b$J.,IX\iD ĴɓTW5SH>A)JvKg-H웃$QT#D|Ih$ frrȲAՖ˧P0ȉd8̞1I=p(8L!wff&nl>Bcss1[,z3m7< ii4i⢙3l߱B>ߘ3ggfÆ l>y\qO0wjkkx_PXXHo__8r7I8fsw}+2w<w7r7`XY:; «ݻ&//iӦ3Fʕ_0u1R^BNm~*tX|O wźs†cw엔B=~֬Yp1 q8(J4Xdd}|xCGG>okݍ{h<^/]B`4YܾD=Lm`=ў02Lh4 liaDRx7S{wKoloog ,\H$B$a~n;~nG"{+^GmL?EK uضmܱSb2x<ka淙9snw6W&0F hkkС\JZ$^5՗H??|ġHj7C;ۨ()-$F:x!* x|> A0 *d#F{%@Xƕx **lv&B͆3-vKMeD8(L*p[Lf4 r=Z3&P@jh2`mgq:]FuLu B;R}O$}?b* y&zuetttFnA!&);p&l_flظ*ώ;1̬Jrn~iL #`7Ypuuu?lxc3wxͷ:ujimCf~C_<4׬ʊJ^ze?Nkk+̫J``kb00ͬ}Y4]u>$xRjؾSy%`D"kw#$ܼ_19K69V-Oٱs7H>?HR,KЎP/RG)g~6)I?ΈB7A^ÂX[ Ig󑗟dڴ&m￯zsf#˜gKɃ2UN$v؎B&4}fP b4k>GL:ߗI$"2$@J$1ŁٙI%UPQ2325PVIh2K0{{c+36`!GK~  \ii)sB!ю;#rэF2PF+W: HH$@$2heEQkD.rcޙ/rn(**b嫈F믓$_ڵucrrrHOOG$!͍ttt~ƍGUU;wJJ Hv3gD 2vw>{-v4͏;g?/ `1?٤Qe1>~$()@b0a n[qH!B`3 CQ&vHT7B63&0ఙG  .9~S0n9IQ`0j GpXG/҅I5n6hcIO-sRHM1)(Db??;'n+1T4x[p8.SK1=40, Y IDAT0S5!:cF=_YϮ<=ƪUxqi-6of%_*. WZw5r9y_;x? 1THwGGbl&Pc z-q=)JL?hE<D(p88W_} FZz:w`$IPXX(ĉ6V\N__^6nJdddQӦw0|8G#Zg`!'ߏH$B׋D(/g)5 G':%>MuT]V 3 ~>XZzH|z?_?4 /HF'=#=tА$úupp:illbiUn0LC]NjXlJ\Iydx@raZy &0o\mنaGeȂ3XFOO1F׷BQ֜"P/_Y0 ,5z pE8I[AbV]t;ϼ!ey[6ⲛY9v.3*h7o8kfZ 0j GHt㛱tvlg{ 3Nw$`pOf'UtL|Hn+ҭFFA"L/? ##g'{B !7wybCC$,DQQL|h4qS㿓SL[A5sqS''K-'5|XqP|OI+UXp.iiNBB;Z.jܹc͚++_2XVp9s̎L$$L o'=}Yg}SLE(XtJdnc;{aF#Fv Ōfd6c6D>_?h$Ʉ,/Ū._%#(JH8B|z Kz=:Zgnhs[T$dUcXj&9tQ;>4Iu5ZZh!B(q+uԍ} {#;;Y]ab1dbl6+PZZʌ38p'N`ڴ)deet:;o`pp=M{0qms8x H-cDQM:v'Ц~R:Oody4kS˙^]{{[xw12IH{BnUsk8>Ɲn皅u0dM?` 0=;:៯o 7rrՂegluX}{K`0S7tF%M;iٰ9yc!&U䕭 CvY(dߑNjٲ8v$L=t4[OxC;uq* 2pYyssҸҩ|xR+比N$t5?龺\Go}m)|FHLt⬐IGcUBQXxE+HRp,;&dtq\H WuՏ'#!NMIOJ݇#?`rG].d>y4qJlN"-\(龨P>ꁿB_'MrbϞ=;)ɓ'\/^lesQ=n{Nכhy#.W-[bJcKG8h 6sKR<$KdLA šG%~aHs}y&S u~uשdAZz:?nTĽ$H$0@K؅s+,B Rd%!* z:pgl0=}efa0h*NfN'>Ny\.pswuB]qK% c5[{{(-)fK,^v93'c6D"N'v8b$(/t;v:d4m9ˤ$nc]xA ds /B[z~$$# `2i.r3lo>K y|zxBh DA$ Y.>nF$&#F ATQdf8`lNRd0s=<-^폨."aG.PC'zyfT^$ 2(kMijn /oφ$?øSřfZ?d$(kDɁ) IlN/9qtƞ>4^l[Nx ,dCLxzzD4d0%'(HۄUGH2$kZIBDHFS HH1Ad#e?I %HrAqRá0PPm4Τ$YWdBD¼i|FUJzУ!QۘbhL&LRz jYTBL_5qQpwefqQ*ʒ&vvv2Q k&H asf`O$AF>ln&"7ڲʲl&'7J+05Iϩn%IB6A,~DIӛ""tvR3~}立ɨ;?uOߗAd9q1.t;1$X?о'!wx|.,&_]3AwX9t N6.L'rIc]d\xyy.n^> `_^ϭ87>8Hn|NK&x̭$wnAVGMq6]^qf:;%r&Z{^]%9ɬ"EpôqM>}I|q;YNnlvQ]qe -ɦ- #Mc:f]hhWjյDG[Qho=NyqYB6+IPv1غ*[&6$- 0@(@JML1LOOw7WXDfV#9teU(hmO[scA4ϱ#G(u3*P('3M(UANgv:C@>Nrrr ;:_*v /_\`qXmjf#fB|xW/;}D63gͬ: }:cIs}Dss<50YYTVYYٱBA~Jt*)R}Aj2  Pׯ~`Ȁh`b0Y(;},2i$غBZ7BD5Ʀu&I& w~>1~$4e2iÆ!*!2g」:E {gD 0dIu}u-WגZ{,ْ.%Y•@ @$23ɹ9tt^ήSkWڵw튴tJ!X=<N'hh4vh2ƾ*ؽpok> 2 ;gjk)/yMDO&b6UU9ydJ. CdTª!Iǎ(hb?뉇>ɼP|buic#9 VK,| u\fEVPCj_-Kb\h.i8.h4a:%0ZD]F33dĄL-H53:gE1F $-DC ;h`Q#!pSF15A=z* ѠAFA^Ӟ3!"w'jy +ӂ٪ j`i4BUĨk1aQ B Q%ELЫ ?.. Lh3u=}c>uᐮ]>e3BLKKr8^otxm{5BbˀxG iO3]utW8fU ?/Ǔl6n/8>Jo0s!:|]xrp:Xm6 fWJb>-V+ng9x4-a>*ӧO#vAhhhHL^z˲LVVqITU%++ UUf1ǫ Gt!ϕ%Z.xuK^Vk/K:B-=W7]jO-!)=Ys U⢀k7\JDJh.'MB4s`Y/$#-Xsq@G{ V@FLf|g.B@Y|2h>Kh(SH4{&6dhB oG1X;0:h>*Ŏ*a2K& [3t!1MtFɲHf-*}R4baO#)Ժ&ĴJLXTn8h Jj9VkNӨGIp_0iw@<uVńdSR}0j@CJj$cLBKҵol1J 1d=x,]˅MmcMں E+G\+GʻX]RxGjHYeW---44a102 ",D*&BnNN\4`0 E0q'*q!(.Qu ;2-44S{#KKI qӚF"ΜՊכhy'WDP_xhXڔs%Z:rtk}QCQdhӵB2Oa?iI-cP4N[g!5YEajJ|Aa ˑMDd0M[wb0((cdu.Dk,d0FCr12 f.ifѢ q3$A;AP#ad dc`uoAUؼP!$hߓ-BIi4*i1UT&&yVȵ D0CcP$LHTݷ]~Rx-~M$.^|z_(~d J3xQiii&,v;NݎhL^obfCuE,G ]\NPe-e-=u z{_^ !MyGxdKDYFؑ fdMwRHF3J8$a9Etg8$ـ J0[ɂD]2tcDu՝O &hFSU,^!"RوhH=AH!% ʠ@켓, 4NuBX6+5z&ڂaUf )*t;A#bb54ywvCRi=iZ߹kUǫ;9u<[.ΛCu VZͿҾO,_n O2jTt "~1}!w. dG`?o?܋')uMóKo[_orncǎ=o\F|_K1yap<НH`l66TUEUԘ}$i/ UQiooxM `h4bE4I!^ ^ EQE1a ij/Ύ ~ChoDeȲ,#Z/.=<*`Z ~Qyأ?Ǎ~x7Oηq\x[[k֬0o[y"/4-5ŗ%\.+G%Z.\KC_Czk=0 i$eR/M%L $QFUIF2o8x~-D}pH&jq% -U_EWC jo@ G>o ?!\;%؅;5~eiJ3 :1;=z4M`s &4%; τ;!&H]oU #.8T f(L&~ ȢFQ5$IWjۮWZ=ؿʄ Cš >ka̾q6ǎ|$}0q<'OfٲxێA;o.nF~~>U̝7<"vcǎaʔ)F/{ ɓ'Yp|fN:ŨQL02m4N<"(;w,FM$a֭ /.bDaMjɓ|d,\[U'0q<ӦM] w=  H( }eۥSHVfTeF_*\PS5TErrr~Zu%T5) (MFL+E/F,# ]KFc߾}8ƏG:G|gL0cǎt:ٽkyy?^ŢE 1lt3'N|$&L` $ ܌``ʿG8uzmF$AenipkpWos|𫴶2zh*W`8q"+(++-7z;wׯ'33l|.:ĨQرcYfaS_ӦbZlA[IQQ*+;o.ԜA:lJuu5ӯaExAVZMgg'p=Æ c޽Rvӧ_h!֭[~fΜɞ{D7gT? zdV^wN8fㆍL&22ٿo?3fΠ.%| #_U_3nGyLRX80|έ<-o\ngjn/?`Ğ[Sh& J8l6c;Dpg318b9P[D2&`F[&$! `p?R*FDQta J@$,|3gp?]vs1Vm5ÇcZYolٲvN8ɱcǩcmlڴ?L8%x$e/bM?7±cihh># i&b࣏>3\adgguV 2u Y/|~>X>:ƍ7&篏B}|BXg_IDP`l`p8p88gQјy~%PvlpM2ϧ2JGJ()+h898l6f%q-;mk>Xïk~ˉ'ǟGGD$ [nc󧟒dڵtvZXv s.P uXfhT! %kjjO>ggywXV֯@YY)S^M$a޽455n:֬YKSS++b%% jG![ߦFvޝcxmEa>} ۱c'p(O7mDmm-.y]AFΝ;9|Gf5԰}v|P8a8qO>rD5G.#F$?lVZMm];*vPZVlg;[p86mBC#TTTPYyaxwܩ~/yBsHpyi^A}G%Z.gMCHڼv*OP a &!L%eDVXlk=Zd=)ޒ{Tx1x}}{ w~W&My&ބh4kfAh(i; H% X@b<3NO ; 5tFTMI?:K}SIȰ8މĹMURr9vmٳ-ޒa`3f'|ʬ0i9ôpIs0cƎ ΜA h3hjj}:￿lW6 #zۭJwÊ(U͛)kjhjj%91ҀfRTXPT{ԫ$濴 G+ͫ^~)oՅ5WmZǵ_ ?ƁOFE4Nね4VnmVq('>le1,{^ 0X3R!ƃ,jD н_*!Z@YY0K`3v:jP *](N {0;ܹswͷmƌ6eiY +WD~^n6t`ZhhhНV47cZS\]ٺ{&8~8YYY8TUtiFE{[;^KUUUZ ]Ԑfʆ 1Zw"DAL)fQSsv>XڳH!FC oߎ``̙m6DIpX!k׬exIXmV9q;wõk>}:ee#bY DF59r$v6n9. ŝ ľSM -  V݆nfcZ1LȲJnQ0-_p3aPZVGTPU*_;=DTTTy<^/;wDf͚ݮ7kzٷw/o=nX %x@d2RTT5k3f ƍPZZlB8;3 ;Fe -)裏,SZVƇ~HYY%?̑ÇtfᠺBPJloo>MFb66lc4]lW6GaԜIX,cxP0D]]׭OuO̳5гKǁցjiE'?;닂zfWft I]bll\bO=Uwk wrf[4J lb >ҝǜ# ϒË-G~  ! h@UJkBKpP!6kxkm#Ø hF"k6!D1uMOS>ŶlWOp,fsnR8j(\n7Ӯx&LI3f YYY<3P^^N~^>h "eeeQÇAE\dffrS'OqAӘ1s&0eL:E_DiOׁHFΣ|&MāٸaȲ̈2\nK3f ,sl$Iꫯy`0HNNǏ7ވ]no祗϶sϽz/} otD7-bx@0dgg%Kذa#HDy&l4l6/faԨQ1]L6 's 7a1axL&7x#KRNTW`xQXXȈ2cZZW_yUUY^%??oIcb֬ڹ{NfΚɓ''6o=OnN.ԓOqL nܜ6ߐhE 筷lfeei0zh].yr9577O@ @nNkhOчYQ W CWkt/ Zkޮ575`xsR  v \DitCUP`09ݵw[[[f:).i[Ucqo8d"`Q4Z b.Ar>&M̰>"P>h8-Q4IEUŠJWd3uBB/ ]$ E#Q GT2MhFƎ{p8Lk[;]]tt,1}{A: Fs25 9f]7W_=SP}e4&L imkncp`/lټ=>i 8lNp$&NKTo(-* $ƪ>Ǭsk+c?ɧ4'Ru] xAC)=?CJw}9}{B o= x~gQ(41r-C?h?hTJ9!t-9m(H,JKJb"|lIe\3c<|XtP}v%$'!-68=818D)%i Ssd){ҥ+@ D]v"Qh"QD QMAUB/ҥ (IFT]&i46D,~n[W>+O@/0cטwMI>^ijIQLax^<^=n>>7~^Π!~ɲ\2mi= ]J$ѝW {'BQNaC(Ωo ^цF]gduCN=Þ~32^\LQp e-WźFqtBllJ:Pe-e-ɲCD=ȉЭMK]"ATUf4hmijYdggǑAVVM  f!f uoh E8N+))NUu~yD[kYYYJ χle%Þ,˴Wk`2Fttv"IHE$&c''h@("D#j`0gkHQ ) & DTpDT!E%9RU"JTF5QHTLA$|x\Ҵ}r{.{x/^F7>Mqu9ncq Zz' TӉd ^+>.%/ '|x7p|N|:;Fi/ f\y=:vd/%o+/5O/ܕŏ5j{ssλ|ۍ>o^ /.>Bҕ {aӦM~x*++ٿ?|G?FUUp n6^/{[oСC444p 7W_}3gpm1n8֭[)))ᮻ_vsqz!Lٺu+ӧOn^[n%ᴬロV^y~?ӧO뿰\s5TTTa*.\p /L{{;#33ݻwS__ONN/fʕy睉nA2̙39rѣG3w\֭[ǒ%K8{,vٳFnn.w}D">C<7 ˖-[oW+27}Cn׮]Z#Fp=?+V?M?x饗dńavڅ(w}^jv;7tuuu|X,~6l؀ng߾}̟?Ӊd◿%1|+;;vSn:m… )))oSUUŭJ^^ov"W^yn&y1cÆGmx<}>s.\Ȉ#x7;v,'lCCSNeΝ,\'OR^^y?~<|, ׾F~~>o ;ӟcowNZ,XH$«ʃ>`w%77^z7rwxٵk7'Oc 9_T|_$~A0Ml;&|`h@FAf3^y&233qddarpd{X,8N9^N#L'.W6YYv222X̸].vCnN.v$$+;쬬DYV,rޔ;QV׋Ȱt:q]xcϳpegcH6NT&=L$3OMBw[E4 FU젦*Prpכ>EQC#0fE7D$C4s%X\A\|8I| ^Ih゠o(%'PZ&y8ccA1 n&5F C'1\`EQ *JbѢ) Sb_D6674 aZ4JE&IDC/"l۶ 0qD/_αcǘ0ao&|̟? ό3LSS&Mwߥ5k0c n݊l橧o&##W_}o͛7j*͛ǧ~Jnn.y,]'raYv-Æ _~I&1n8<:'Ndǎ +?`ƌtttPQQ{kqRPP@kk+oٵk>ӧm6}ф@tR"yyy[dffa9=K2ax OK3gyaÆq),Xѣ|dffrqjkkН7`٬YOIVVÆ cݺu~oε^6l=s/ cǎ矧>K/x<ϒ?ŋ9z(լXn Ø1cXlNB4n7u]رG?Nx)..ѣ3j(7ɋ/_^3<ýKmm-c֬Yf}l^E744ܜp2UUUUX,bB} ٷp<{xZɿteIb$v㡪j1L4E$~84 }aiƄlc0`Lk@c-wr~}FƋF q#~DOih7Ty%U5 AdT U QIE44EK $o)X*hZ,Ly~xMYJ{Qˇ˕M 4jZ2;?D G(JEQ0hnc MWW~MȠHf@&&^GG47z;v HxMӗ~5hKɫzj03ng\۷{wyP(WDI-[rsYΖ-[صk3f ''H$?~iӦqM71|~?yyy̜9SNfޠgΜIEE:uSPP@ss3ׯ.!83f`߿0of3L0k͛73{lF%/Z{Ax衇زe uuu?wdffMvv6wy',K/W=o\^{A _TNo7:m]_P0H8FEEAm* "*~e_B\~tZ:A'n 2p:%|0@C%~u>to㏘}ݧtIJ^Ip$;vƌs.) \4N'V5x<ݻ7v=,444P__ϲe˘4iR\vr'N9rDjl8~x455j*NHk0kNee%vGy3gΰ}vA  aX9s&/6oLFF=ӟ׿NGG7nHljš1˨{weYNIƙ3gx7RF֮]$Iv뮻Xb(hED"jjjؿB8 TTT0lذęx8|0c̛֭7wy@U-7`ۉF455a2zN􁪩@ @K7줵Ξ=UbFgg'vۅ$}Ϙ|>^6FJ,0Vϗ0CTrrr62VVXȈi׳) h9,Yfsb_*O=|> 'N~)Sh~_,Yr{=~ĝqMA(..N;w\y衇;w.'z,Z1<էtU9uN3p@*++yר{7~PRRBSS.Æ JkbaڴiDQ%%%n@1-M͍TJJJTTThueTsW-a0E;zB2;)&$IrZ鴴,ˊP┝QLב2$IOg`0l"bAs jC&+&IDA@Qwta#))eIe$ 2J#~*>u_FH˲"ZD|m^vYq[:}Z,1T%N6uVDA1 u,^}F'=t^+ټiZP0Uw<Ofڴƛ̜9m۶܌(\5y2#/ Xf5xɵ]?@dXv-x衇`1>s:#GD1**ioo' qxb~?&뮻O.}9`h,nof |Yn">ZDŽ Xt) PXXȑ#پ};w/k׮o/ů~+L&3?Oذa^d2ɭڵk9~43k,倾Jp$Lkk+?Xn}F{}ϩ9W |AIS8IqBox`0ȑzN::}g=T}Y2ٙ vAPVUKf;UP2(.,  sByEB/{?{Yx1 رcy_f̽m yioh2b1r[l*]b4t/ĢQCeÆ 9@ϒ%K{>yy45b@1mtam AF|gy*緶m_<-/6>5.zå2_! <3?)GeŊ466?Y\@F_31IS!=u:p: +W0d2KBft:EߗxzȮ]bM172HHhAh5[ٺCYh1t:vo"n} :MM SݻW[s']5~kcc쳃m6ڊ}FKs (fΜɈQ#yP7_a)abqz\ƍ|^^R/wcȫDvCR]]Mee%jؕԖOm}͔ΦAT2(j nɹss-gdf:}>HQ1&%EE|oee jѣtttv91;p46RQQNKK >=yN#T( IDATpD' (/'PCt:|~?.S ppY Pb2vM5#?/szO"wȟs]ܴ`j26dRBi`7iD&yhZ:m*Y9wa6[Zm}>UɬʀǛe 2ư0h,>$s2~UmrnHldmμo#??}٢aqt:¿"%^j6>aÆT9B5F 9z(N+ԂbF( f((8^Ҳ2N9!H9g6lJ&UY\A*MA ܂jj0z6F+!  0cdN;غ}'HT{$=!m6Jd2gf4Bp9MU%Ԇ^Sr&S쇐VU3˄x<2ȯ~k.7-t/I $$V`׿NAa>{vvk"))F#|>LF{eiTV*4d:cE9.dZN3XuܜUSI나ެe@Հr(%Ņ|vDEy) R^Vd@twSRTĀ2>;ԀaNc4z<Af3Oĩn--WIe 9!9暻FuM eqWxb>^r&SYB40c *&* q]c4ذ ʘq\,]DKgiSʫFj(.HG{;_96R.|g8WhS! rKs,`w8((*޴E j@;Cž>!Faݼ<"^@ $IL&q8AJJJ4| d2ə3g@VF#u^KRJB$p8tvvjġPMsS3e~:;:1 a+3QǩSAgg'3Yb%Lsd`7^LfСH$MgbaXhiiMF.2HSX8 4773nE$Q.'PAss3FDH4T& Ӳ(8N.HDaQ!+W(k/vp8hmm%(g{Q4텅HKsrv\'v BzDQ$p8hl<,I+Nez=66,Ve\.gEgz2!2aM.?ߟr|J{ Rg,Zy'Ĺ2{|@N!&dS\qLFdO,ǭzvƢ8^;R^uv5O0RL:0g9q.S5٤";Fz%(&HTMkk.feTsX Ap]\1wLv~qLmf鬲Sќ @YY9t)DA(QSu:7YNzPlRx^<&7 g-))z@ |>?aQQ\ZE¢BSLUuv?t9~bª lTT*Lfs4ӠvPu83^kGU,Kqդ;vJNVA(6tp@"pǝY=T)2ɪP(Z%(hL-(R36p.=]~>xQ$lBP> L ɔ5 6b"~SL*Sւru~MR8-d sg(.+aE\,Zf>o]c'ڪ_k Ne+|:.d`ҴI>sJ  r)hF#> Vh4'XaFee$I"P@ HAA>RTB>?ȐH #8 F#_0Lt: .'aӇ$IYLsk+(H$]t|̚>^](/+h4>L$S=F*S{T!N1uMDQH9>AP\rZ(N|"/Oř;CL}Ѿ/J_Wpq7s-eƟﹺ YחW[YN;Clʔy.:qA hƕd?Z PRRLQQ`(lf S23/e*B,~ ^W P^L#ku:TD{{;$(Nz=Çk$YF'DQJѫ&eI[ifܶH b&6+Xh$$8v"(xb1Ly5Af' uHҎd"ɀn+ӐƟ-)|EѪ fҗ3/X)Lڦ[Z@,ީi]p ! XLG} _ԜQ&PFAjL[mF ewOԩ3}7e/PXG4OY rQ֫ IbWF$yyG{/tO~W ǏOֱe֬YÏc#<~|>G<;v~{=bwy'1?ym>Bkk+; n gƍ{ ͯcРA,_ {jKzw|ӛo`[oשeԨQSM6ҢŖ+1hŋꫯH$5kiEB㻃޴k)P:őUK=O=ly:02rw׬FtÞu."g۝Dg j_{s̟}yc:NfD'ET$I6 tYSel6#Iy^/<桯 ?+;үԏ\ tϱ-d܁\r|U7BBA]j~ JLvVrn/Džlݷ/y{$I"O Kڿ[~}EUj%^Y+!6n(ڬT^x瀢zgywp:,X;ٳg3Euu5K.e׮]=7xԩS>|kg}ӧ3{l/_Μ9s8pfbɒ%FyY`'Nd|ԩSB!=K/OSAɓlݺS?o}k,_ƶmzΝ;1M<'`:tR^|En&/_Ά x衇b [444zwͲe(((Of /^ܹsy'yG8~8/"?Xd @#F?;/̜9z>c(۷o?Ϙ?o,_ &n:***عs'@۷c0(..駟fȑ477y뭷e%\7+}] )..뿸xwF#@ t:ٽ{7̟?;3{-[Ǚ7o2={2e =|>TWWcOxbիZe|@ExTmTJ UbHvAFBV2 (;hllDuds+Q$IIhZvbqYq::;:p8FN9d&,**D2LL&V @ߏ(ֆf#5@ 9EE[lj'( FMb$Ifv/OhVI`zB0I)=2^ݦsZ;x񮹌uE42J3TeLImJ.;ϢN;$ Ѩ10)ssmYe@V=$'IDQD!ԺiHo-h!mѹsӖ,}&FFy+={66]vpBF#z!CdCc}]0?\{9{+vYd k׮L2S0yd{=˙>}:;v젰:L&W^y%gF$ؽ{7SLaܹ}tvv2d͛ѣygNO~^/_=7oj2eʔ^d7ߤrJ֬YM7W_ƍٲe =<bdYW;RJJJ(**F$nVz=ׯp f={vV=K.婧tr뭷2tPyߏݻwp80`C ᮻ~:o7tSL{@@L*qEM&wq(2okqqʸ;4Mq7 v\y啌?˗8q"7o?>%Ki&or /2555t,[oe| //ѣGa֯_Oee%1 , Li&}<(2c 6mĭʴiӲϵ,F &:ԩS+ .~]4g{h,LVt@J8fHBKK3N zAxP8M^QAȒ,4;ZJÄ:H&l6$Gb`69rɄ fJJPbII45 J"  t:b%%ȲLNO~A>>9Iٌ`IED2=3Nvhg劕L0Uq\ LUUeV5rW`wH B7#b)\>ft.m-.lWH1w),/zvdY& J|\sp8L8f9vxB"e8ϭWz:SPH9+IFf";9X۶_v(*{dq?_拦w? iL j~a<;v*c>̩SfQ__Okk+Ŋ5! 2w}7IUU .{+Vh4ڥ}Yj&R.\.JKK8z(}?Ohmm=+27rInN8@UUon"3gƍcŊYu%I eZk^zk+Vd=3dݻwSZZfbPSS믿δiW^obH5k0`e`f} b,_l8}QSSÒ%K={2JO>(l޼K?V+---8pK2o޼,aaժUU=,Quu5o6裏r72zhyxNԩSg:K}VZ`d2͛{gH&x<~n6VXN)S ƕ IDAT/Ř7o^ 8j(^utL>Yl6s 7[RVV5SLޗS@Y OD863f`L>D"ʕ+|8|[bذal6ijj7f׮]L&<f2sLz&h1#FhRtx<\velذ'|!Cd9Xضm 8uf;x<nE@>k,{93<|L4~TTTHrW⮻bq~QXXb__1bFٳgOrs5h{g^gРA|>yy@ I1c7pk֬ɢImm-ׯ`ԨQ3Fkk4k_V|JFHŭR5)n50}32;Xg"8Q-FC-0}iK`{?"Rk/ukYY8Ht{<.'&Մja3cح/PD"av Q'gB'O"Fq\tttbZ>yHDKk+g`1 # :_p4B0DevNq:&4SY5P(HyY9fD I2nh4p8I+$)h4AA=NtaV+yy:ڔ0vTͅn:p8xos-pUP7Æq~lڠ8ؼi~H{,KRZZѣ(.)As 7`x췏1cL**(W &eLf3ƍSvxt+C#3@KφnJlRmWYv0ü=m'b`Ī3$M $Ge(pAoaIހ,Kʋ`ljFQ)ӡduiu圖U czlpsǟzuwa׋5ʜ sΥۛI`o~ӟS狿{5g+/#Gr>/?su˲b 4k?AŊj2VKO}swz{뵻N\.]WoYc>d Aj`YM8^ˉej2'!|m$:Q Y,MdƠ7^WN)UI]$곔QmK5qG(=q5DyE{pN?O:mۗ.Yȑ#).-97/ķvlZ],PVVmTp.=z%iK_f:+}im|]]'N( .Akinc2GJjL&޼|4p!O1©OrCr!u/7S3ΖQZiLY3--tzŀfnW4n|7^yD0 &D4d>gBEyY)%%ŭ`v#4LxQD1b;^Ǎ)w/kL~!SqcYn=H$T3.k:^/˗.% 1yjmZlܰ\ Ș&^&c|WhA6-z˽?_|6\~|U͞*TTs`dYքLDH8NP!Ijd$D\FQ] A_;Ɖ%^+HdŠ(J>H$B0DFXmV*H$0LHyB6DQ0DB@ ]QIŧ{x6$Y"#uC!ƍGAa!!VS[[KSS۷mn3qDB k֬E1r(dV4QU]e#FٸahQF!ˊqKs 3cV^Ţ)W#"۷m̙3TUUQ[;={>Ô1zh~)^瓭[8A3gXd '[~{r0C aȐ!ԣH];w2;JIi)kVl13fX^n`! :D#ǏcMXV&a2(.)fϞ=TT).)d6j*jjj?a<7mQ_f#**9yfi>߸Prͅiw)-ےx\>-˨#((,D ")g}F{[;WM$eJ}jΖ;SԆӽ]o׺euL&=fEa4vx(o x鳈26A I8s"(F-B^q%^,1$IAP $ t:,'()^ EK.sn7iQ6)) hZKD8:H>almmE'hֶ6%DF{ *Ao0!t:‘pÁ$%b1H,Ӽ%FA!@q!\scG୷/(|CV߷o?C%Lo><+VL~A 3gfj u5WSQQAkK ֭ѣ40fy{_k͛6c62/r9yg ݻ j*&¦F$ λ e׮]TTTiFV_!ϛGyǘ1c8vSN哭`Xx嗙:u* t: EEl޼Ӎx=^/ZDĉ,[+ƍ xWzAQUe.%?S[z/-ߟr1,()+E$Ńfjų {Rx}aNpˮф09 } hDuM"ji7qw;I{! 2^A՘h `A\.7Ph$bӇh4R\\Dkk+>L&#`rfP+q:;@ހh  QTTصO}H$v;::0 RLGg'V L{{;6p8$bq\.' 5墥N^p FLfܧKr0]rxAOVURVT  5Q#5|eU% `2ϰfLX˖seK3~6~_Wne 63g0c K-bDH]]&__p:4>cҤI Te |u+KC 6Spa%DwU8vDQǠArҕtuvw豣ٷo?/'΂w ̘1&N?ᤨpqdƬ3@IIJMYa6c:oN_{+g5Ո 2eTSOcْ2{v;VMii)ƏShs$ƌ]TSò%KٷoQPWGeu3f`'[}M9s_WpIb PfNORN 8}Lkʔ>]mk.E/t_0wKȝ?Ng)Ag@*Vm@w%%*+ZʼTP#x]JܪRڴ3qC^^^X,J h4h4uD"졬z%eh4BGL{[;:H4h05JsvϧD}Q;vlp1|>?{v寧Æ;Bmmm\w>}e8N}Lmm- ޻r7S=p Ǐ@'\cGY~=i3s0xF%H$?ahN illAs1-?vΎN|>F~ כG0C3Wmmiٷp|M&2ٳ{7S `o};8hWkAP[{˗-Wq82gcǎa۶툢9E.$I^z%x^.^뮻_~oo9oͨQطos1G}D~~>ÇirF]6{w^mڻc 06A1ƈ`DI  'uNU3iF:n:7{StDo+˟NDk߹4a-4G)Q צFOE ?wa="k5&%$$Ca&ɌQ2{w9vp:ro4NG A@4nǘ1 ??N33tJ|>:lCK=7n2L,?H)u&It d21v09lFfICE4^A-)ŏ\!Jɣ9ւm:+՗9w>E!v P] p9[EQXwݒj^QdYfaZG,jg5N;( dRSd*CETw)EꆠVW8.>ư?I(\-eEU̵̟?];U]=1F[[G?fy,XO])~as5D,?gu&#o><"?Ze˗'aw`6$ujb0W\u%c(ܼv g b0ohI%aZ3g6sf"Z`޽?~ŋcD1?݆[uO1<<ª W`=? hq<0<<gsnٳ1B׭ <\z5O>w.dj"v`ړ7h, ݎh4p8H$eknBey,#H IK222)())!?9>K,`0 BX, '9ic.n7/n7r288,˸һ X-[0| \H<-=dUc bɈl$L&0DV(0f P$A2%c6%uֱvZ"nH  v]O'J1< IDATB0tO'ZN^E,^h'i =ș>@tQVv|NLpCF H DA*}^fCXYQ~}ޟ..GA"l5QRÉ.tR\*Z.u[VIädA$!">&IDB_R5ᰮBE8N+~0+Vgrf. ,˺kTdYW1h-m

˚5kxI$\q{A??sFQֽ`R/^ `LFk.n$&|N}}O[(vY)vX{I#+kغu+g}6^x!<z??|H$Bee%z+\p O<HڵkyWy뭷9+,s 7P\\~;ݨ)--D馛ps=w}k<3殻^@Q֬YCUUo,X.sϥ;3.Aغu+/̝;>|:n|CQ__WxZ[[eK/Ě5ky衇aܹ|K_GaTTT`6y&3A!:H[[;wsd"N2BN)YP8Rm+_P!Ttd8JO!+8է^L& CAFv!DDIun1IXm 9"HRRd@D0 "]ݘMfzzpg #҆޾>^N;mXVBuuClA4D0r9=F3& QR\L(tp9c h,68%>P"G::(E8t :|oQT`0HiiQd`pYVЀzt0+Z7K9re9g祛2%Zf?VWgGDy}BE[65e@ O Pˍ$Q!b(Qd͆$I9 Q[ʌFbp8L,-v#"ee$  6~X^/_WIR477s=/}v<˖-^xsl2o#Gp뭷r 7RZZf̙a:>w 8|0P{w{M6aWf[o ]vaٸtgUUUXVv/;<ôaX/Iee%%%%˿ r! z)ucxu[;~lZ:H&eZ:Mm_o<ġazrLFwOP3VRQb&n/r-\uU,\~֭[hF.R|MNŋ? 7``͚5sc4nx?䓴qa~?^j6l؀e9v;wu>([n'z!C8qOoVt[Oȗyh[F 65,Ԉ),@њQY 6 DѠQ@0L* L2"[EnS[Snǒ$nJzg604.\,fS+`07 ;\̙3H!%Ι=j"?C_?D~?eI bN] !B0)9h Cx>~mf3+V`ƍsA@f3?D"{=(s6mum `ѢED"g?Y/Wro$qsWӣoqn04=:~*EAP"FY0=c/DQ~Ѹe Ȋ-7K.CSJ^V|LQ HRYYE<͔Vf3(Euuж%I>_\EE(3L&0Vn<%R`|ŤR)FFl_\FX,t>ΘѠ^Dk3,G7z?{~Nky+nJ;ᅵ~x%'M+.$yli7aJ|>r*$ zrb8$UYY.\N9ш(BPo,e+T*}v'l6jkjZMxly ir5+¯tV .䭷?1pz@}v;G?*f3;w >RhR'੧K/.amƮ](++b /dL*?1CCCq<uskR;vϷe>9{1y7~֭[u*)/VW֗ܦ&s (y#qV/j/nD0Hwf GMPi8|0XB< d2IWWW$qzzz/اW^y%?0Fsb69x PN^/}߿ŋcXSTTCkss3?}}}|+_~QYhV'h4ʵ^;~5F^|eF~myFFF׾F"`9sy裏G(k_}ݴ1{l$>l΅Bc?h9mM?`BLTzyB}9Ch`x̴ª3js;vlO_PiL&CI)}}}SR x<n))UX% l666(eL#x+dėZ7d2Ikk+QWWY|9Łزe /~& "t:9t6CX088HIIF;;|:;;ikkYfc/_￯NRs!***9s&mmmtttP^^o3/++>8 ,NKIz28LqgE@2%sw0N*㴙QPMGxn>~J)rXi ceg֬Y455g͛G?mmmf,Yo J<g 1<<9<<Ν;QKL&ijjwW466288&LR[[o~n7k֬'%`ۙ9s&@]v( ˗/G$`֬Yn;w.~@ O2q>C gl6 'LBgg'555׳~)//~8 Goo/ C{{;qYt_pBA;Ckni x, (457 !/QPU}d>ЊA0a$.a5s.e>LTzQV AL& Myy9)BCVWmDaxsQN***0"}}8d9j%H" N͌ ҳIRgH^qE0Dz{Puj<WJK|xEbq{F#m67TJ!+ !%<雯*+qo+A8p0DoApƞ#'_꣑v%<ȋ1fDۅ:y#iDAIEQFcsAxߡ鄟NLw $y xx w˞={Xlwy'\G ~8;zq2 yՔ9 w7M˧fqeb\G?h9ƛbi@FN{YjNA_ivPAs-0^r R*ڏ,׋f#D}9h6Agbnۦ4٬XU(zHVLx_qVc(c$Eoّeu(g)8`0sE?eDAD",HF 1o8R,/9PrCA)|l:&χiU ~ҖW;!K'ό@ Ho_]΃#Ilee-ƟeT8HAw ˅jjoTV;v.6Mu\\ԍADYY)e]. 6n _q1n IQz{$ǥ){sUtWj KKԜh.VE&|I&)FcƔC}v߽x7F2C/B.>{S?9sh2j黻;$ ~裣Zx`} H>~q}lzgӸ-'8QB0kL8x-D$YPy9D^?*M;j~̕+2 ګߑNJgjhhW62E"c ~\̏B8ռ wR?_wBi?Dg./S#ިi8~+4ZTl.c$;OjHoAIIi ## &DmM P]U`ՂŢ%IfϚ9c5@  dD$Qj0`h$hȑ .@ @ULF,[u!]U뎟YF6i+FL؝!(L$ lټ7<Ϸ|gE"`0P]]MSn Ք080HMm :/ﻟ+WFgGu-8.xe+,>c F}{S,llg˖ عshZjEC4nB29cRZ۰X->tYf+o^̛%KسwQ$+͂ Yl)}iMHĒ%g wFE<~g$O=$G _Ύ#RRc9ؾC=s+)J+mO!^Lt*}qGaUP?/ F p8(++c鲥 G)--c֬FχlaƌwM#ύCÌݻA4nwߣ˖rX,rfΜIwW[ sʏbؿ>ʨcΝYXEK#I>؆#YLWW--xA5ޒIHDP" RF$PŢ^Wm6d SH$& 6#mǀGQ/*/+[hnjno>66srJ5Ca IDAT:;)+/cOJKK_Buu /e*+*yqR6< ~fΜɛ}{"{_ 3r`+V֭ҒRo|ٳ ^oo/ǖM>]dǎ*/W\#=^C{[;f)-+% weo$&J1322B$AeC4inqGCS#)a~?3fߠNoyj\.'==\8Ji[_{n*䘒;s<0;*5fGikXSKJ~DXN(l:\,t7]G"U2ƛ:`TQ`QMQEJl'eULVZiL{hՅr99ir-K-W^#Lf3(P]]fK;o.aZ`u.ۘL&.2>LMm ^v\Gz{zx<4Ko>:::PS[|k\tŸ\ng_H8̯+n˖/g֬Y?~W\:Y{gӼ yٱc%b7mbeR}|-r, qSQQo(I4.jToM$'\}l}o+(IXT?qa~ իAoڄ(+jd҄F0NQ gV}DOɫj j5SiafSQ+EH3!?c n* | #Ǘ5gb/.~Snf?`ac# 383K.%V,ZSTvnm8\|M:;)))+/'ԻrXl)- G"zռh)H Xx?λP0DqSOF݋'Hr՟fMtkWg]U]򵶰kN.XV_vmtuv|[[oB@k!J!"V|j%K. 2<< 9sXdqӢډq>0xMD>d*nWdEA(*K/H!L.kA{fʯ1^ډMB=^:1oUG[Uի]<wv_ &@4! +t8rPP( "xp8BWW'VB?)YfSo6-ȩ0A AFKiikoFgGwj" kYm.-/aL}w ###\{gy\hx_鸚r,DtǟlZ4(/c-'펃CxdWT}!E;:&D,)9QsLsSQD9Q|eH8L<1sA q"*0xQ}܃) ƛnK# y7p_[%S_.X?Ot9ia|}?nfϙCuM3tuuTTT'Օ:~7oDwW7iw*|~_`4F[[r9ٹs'\Q\طw/1JKKOsϯ% 7^ȓxO'oR!CqQQ%wSV^ 7o9& oSme}4 4>KRqR~>ŗ\xW=l6Ӵ{7rSZV]=žbEml="i1EeU|>^(Z[nu <K$A2I8 +_׋& _MkP^^ΖMiok%*?F]C&|%%x^~r)))3]E_o׈7@sSS2ǂ.~ r$IK8X8-$'?hDT2Ӊ[F1A²f\L%a8`0hd.zPH!njSLa:mW8ِJ8pC#$D11}b)mUܱ0x v75U]hU3צ б2:Y/G@2`?P`*-JDvh3PJax7u]˜TIp]ȲL< `X0M(dh4(@Ɍ$IeCaׅ~2ԍFш&b1$J͘$`Anff׋ &vfT2bl2L&1՛EQ惒O)'8>s,fΚKVΉ!ٺU^H2"XQWWWS:%/>> éUΌl:itBi?Dg 4&".#ٲg-&BjDOeCiAB(" oiՙz'$I8dA۔h>b~tx0}x5nB-͉hsZ0Pnf`xxB d2 qh R)b$0Il bX "I>O%:<2f ?0fcddYV0$$`g(L<b   J&)--x<؇,0Lp:TVVb4GMeV͐S7.g|:2jjc+dT u8j^]Uxp,}5ٲ;'s*:곬-xS6pCr*3^hm776x92O5h"`d6c[UB6a8:N?v൉h+dΫV'@,A[! IFNzӬ,ldE"D2 v"EQ bzh4RTUoɄnvf"?Š ǔdh4J(“^qꪪ͛D0"HFQ]z)>qt]윆p `,=z;>=w7]L|-Ė)TЧ :JXBȱna.U1ZEE0mA@D}Y\!ӟ B:1P#,~(-:VŵuarmL\ 6EEEvDQ ѨD"L&<O J&ddBe|>=hĘ&}EP$f3,Ӌf$IȊB<c0FDQ≄lFE$. Ip:9g<THIp+$E2ݨ>&3HE$bjJPW -K9>cd錟NLgdoRO̸#6O@gέBӁjY)^ގ*`~EQ" e^u,+5՘oeco?MMYbԔ[4-5;0HY(=tYPW¼ZK9q'WSYDҭ~ӉVFL[\9,f~h/h'ZDŽh.%']Q W1$E1{6Q2%"dV0<8-3q<t)@Hs̈́<tvu k`2Y-x4.^QҫS㥟|PV9#88>3Qh4Qu "JQ]]E,CeDQĘ>OV)'H`N B81͘L&T*s?gxh׋bAN$ ^/>F}HN8QXzf˜6lnl[ÔϟQ DžO^hLw|Ic-gt]^%w|>/M|i^ʫmyGyb̝ç^|]\ˎ;زi3ds=~?xj6.dSO1<4E̝;{rv3g{jDxͷ՛-7SYU0pKxSU]k?y(+pUW̳.{%jkjyՅ憛nwYr9r^w^}y#<1QCii)vo@kK  022[oAiY)\z)+VFM|>}6YN7jñ@K B~^ub!322o=3 ˛lMq1]Ӊ?u?!cY2RQ$%̯-P00 hF/{\Gvo!LQs.DQU8&dYϼ5r3s٘_KW,$'RzΜWandv~⇄b V-î^>oVlf._£/`̯+ྦྷ3vx"$qPZd/޿U7# Q;ywONnh!s+i䭝Hd.^xzΜSɯ2VvqjV5.Ke3'j?_\EM_/oE5x (\}5rÍ7utuu֭||k*)-县27ofϞfv܅dBs,?,}pMP]SMMM o.nZ>^ؾm{z}K-cђqH8N2w"8oA5Cnkc2pXM9e)rZ~|DN{ w\n6,&lfֽ+tp(=!rhxB{4w\H<(^ u nsFUΡc`0+k 6yy<+;6swD ߫nV&վP87BEK5 y`O;gD -~WPhP˗~oLc~8ՠ?J@VlCLWt(hKiFnԑ0t^px(YuQekto&)k ^L^dѼj4/5w.F۶x+ LQV^ȌY3i1C7JJ1 eee8-^DkK e8Nz[W~J'd.\.P tsFtχ墸P0D<~ՊYf  +AQNB$h3@CCTVU6uu8N,=$a{8{;9c"r$1IZduݏw-{wٷokme%`A'`Lӡ=$|zU:u*SֵZHOOCxr2~pT^TSMRbdrs7LjGYnvi>?y)<_fo8tQdt 30=Xbgh}~?Ó\j%, ^~u9Y Z$Vp;=R:L, (O>cts.ADP""Gw%{&!5o%Aޫи_UX6\Q*+ǯG%)l%y@t:IKKtbٰZXVCl6?$554 Iff&)))8d$ɇՄZNuxx?>33wm;vhlhT |{H5uU1҆޿|7&-~><͖ܹ}׮\̝;w@СCs,Cu%\f W|ke)\je[mQOy^cSnRlf$)t369C[03^,FY/﹦.:{GIKtk?])kM19W;$sT ٷ#Z\N!GRdlr<;sTTR;FCdat͍[@ӱ}TeMibs|8._~!g`Ґw Vo 7cĉ՟d99z223NENNUUrQZZJvN6~! سw/33ӤxpCXlָ^-jm4[{&>/dLI!ZidISv}hֆ]BghpD ?88ȵWy}Ԯ^fcbr3iii}qq>:vaJJJ$޿|sgSOqwnEiy)o&=ncvvCoͥi~ 7'AN^Nȉ'NW_d4ANj_H:lΝ9|1mX+W((.B<^ϞW_ܼ\n7/4_EٸyS\zܼ\<};oͅs))-axxUUUjꘚ G(``bbiJKK&vw yf( )BR읚 ' oݧ$eEA)V{jZټeKpPT\qST\k0YaC+- di2ٺ}< X>i'?!ddd/+rsHKO!֥I!mv;6o-==X,kjUrnݺk׮eppCKMM 6nrkބt2IB+z8^mPRRKj~pﲆxXVհ/}Q~rWdfeE 96lڄٳ<䓼JJ;̺g^Og[;/,'=8}??rqUf3Z믽O>IcC_ͯqIΞ9C[k7W3OK/R]S35Uys޿| 7zj:DII1V_}yǸr =ݸn._DuMٵ{W^34W׮]c붭148[HqpYqm;3<4Lݚռ*Vkpi3y+۷gxCέtua{8u$7n4qy~-`0P^Q{GU @rs9~8G; .E?p1w?9repd}>?CQpt9>2=^]imj !;>ҕ0ݔvL gjÆ_W҈6ƨ=qZ{<%)x{<1B!b?UA0-H $i_W)ve )>epof(\^ ~@5j1qI{ ,B^I:RRR;T<+9[a$VT:I' '^t3<4{f>ğ㓧((Gh}hoK_2$a`xp)L 8Lf& NyE;0>6 9HU}=lF|z [n.PS[f yjb4wp;wdxpB+(_@^ ajdddBOW7)nߺÿb2Xn_Xf-:g8)fA08Hq8xgh'''zhN$Fc==x\.k׭v#tݾ<2刋_HDc2ˉ',D|eQ(mnG YǓIȫ$G >оC< N& ⅍4M4ddFkgך0VF#f1ʌ}JB]̇&8XQ!~u p-TUineEYJK _KQOW YF\>?P . u2oŞ{'3398477SZVʆ|li!/7.048)hoӬ߰>JR4 زu tv -7zRT\LZzou*3噕߯t |<||cl;-7[̙3֒AAa.`xd֛|uS)-MM\.N|N{vA.O>_z8JKKy _䉓x}>nuts3}/۷b=456EiVV׮A{[E}YlēIdǓIxҲ$;L,y^%B١(_touKDB2(3+A|>?^I{ ^kOjʈ~!5|9`Xsee9G̑5VZqC$W\4erd3J*e wGZYJ7aXxf.5'|p٢C30OrZN

4yyV,/TUWqfo/\GpL&%b*NGyE9?$^~}{:v۷Hq޻Mu֭kc4y^9@iY9uWf?67m"'/ի)222?)׮&##<^xY_ftd ?Ovv6w1tX 624<ķرljj9+z~k_Cۿk *ʹvo5\NuZV^e t&ko>-7}ߋ[ V|MN14:4Pzx+|Ihln&&8 ?>-tz0Iiܤɋ򅅨A狇Յqm#ue pDvZf3Y^Ibi164cw8().h8;'?Q]䤧;0,&$}P}wtw2ɲ[:ioo|˿>^~ϋbNW<<~$ ++ 7FcmLe(^8)I~ M.On&7ŗ6ȅ1[]q,~P2 m!xZ^?"\8 '8Xh?&I)$$.E+%W?Z <%őPx"NZp@J*6Hat<ͧ+@(BQuJǚxK⊹N)9HiK'V I;^i"Xh#x2-R<-7.Zܼ, Tñ~8_'n$EHR<6<"MP'{}ZfG/t'a"BA`R gk`iP E}J"41|Cy.{HvIx+f$lI+e]YWDO׬ Jt@ !_d\ԋi+JK+TXm$J&]imj !;>ҕ@IR߅ouR`VKVAWh4b%*sKѮO~Xh~ܑ~$xSD7d"R&H-8׈qyxr<#B)!$5L(̋]O5/>G{\?]O/w*ܧ̤b#ldǓIdWJm',ˁkՓEu:'ŒW Gf<X+lmi$$C@yᘫ(1D{S=53 x2E£i7H%N$f7L] Glj'%I!&vwKSO|vK}j$ӒB Bt:ytdWY$k7IMK+?2ˉ',Ɏ',heIvW6IxSfa: o===?÷[?Ip?Y"˿ ~])GO_^/]$>gr콦jIlg;ݰY=]CU;|fS vbOS q~D92hO"޼ɏa]ZoT###꫋* ?Al95Is}OF!c$r 2x7@=T&xcefIOTXR .<"QµG)F±i1䊐w%$ &&ilhfQNϑTq[f\.xsg3>>[ t{rG ܽKum 7lOp$LHN|t;9<6md2qqܷOm;xnO#9GZ岱*S׻[t 2,tQSN[(\OMquŬ\f<>n16i7*y%HY[U 苄Мa84AePm4H0LW R !B $it:IJv%OĠpvSso܀dfxxÇaGǙuSU]ͻ%L0 ddfr1tvtvz֭_什Α{~?Go>ߥ^r㓧رs'_ ܽ룠 FΞ>Mf6oBQqJKQ}Z NE׺uBAI]lњA\>S}l"?'4+ʌv;V1"Ѯ6_DJC /SyO&n1 ȝ1xE˻fPQ`?s_-Q?'uSXTĪU:xÎb7ޠJKK8y8~UU8֛x<464048qj9_WG~* `  j!~#d ~gO֭[Tp)icp.gO;̠׫<&t"gϜadx*^/U|b|)\obƙ7ߤ+.Sz5Ν'3+,Wiii|ᇜ9}ё6mLaQ!+OO/G_ r{g{l'Ґcj8x9,/&YL^nB&gf]4}~?cS M1<>`jش*Ϭ4qTmvD YǓICQWT?X n+2H:wV?5LW)J O"őƏF$wt>3s%jeƐs Q9>>cLO+>EUGf¥v*MMM FP[WGfV6z<\zΎ30 lc%Anz) IDAT.)[s$4 2g xjzXW"9BV{ ʢZM .DMïȣR+~~Y!~H3JJ\l}kʶ;شywJyy׬auLNNRS[C?ƕFvNn>}!li|>?[me]45`#}tweVV]KNn.iQ+Wz q:S;R_r{ixVyBy/N[ȕb:vnF6uqvL![}\lar|o׷8_v g˪y<|G/N?_ʦ>$1/`zڴ%?JxBlBxE }C,SHMSWoN~E ނ0I;dr<zJMîDq(}Lkk+]]~/`0()+#aFVK YÇfq<8I:ܾM?# _P@Vv>Bzz:|+_frb122ijh@VtV'-ʹlJp#;''b=>?zɠ,s{*l0(@MQ:Tն z~!2.1R&,fdĎ+F\#?A|N@x =A\IC5qfH%MĈN}bJH@O5w-o+vRiok'33S'NRVVN߿{^:z4Wo0z;@Ow7Azz-- R)tW--rmrrr||~z]Hg,|qO8Uo*֗o^KJHҰF\orڭ҉kWwq{c4y|+gp-<})Lw-{-w =u6f6pۃL̲su!ǮtRok]%lsou_bu$;>ԕ$),]e2 J꿹M@sՆ F(3UB)̅nr>"$vwp׺"ޱ8!!);\5P5C(~oWD Vq%^WS S%_WWԇ$6UUUo|Cŋ k֮e5MMTZ+"_x4ajWM+!-[e q?ݻ0.V*Tg8v.@~0eN]6N\B/ITp8y'9̸=>5R{TMF'ܬ)p bm%-[ *V\} R:}HOk^x<eI515mXK)_(ᤠ7*r\|~3/-+u[۶oCDzz] =f!ɟ'YTP'zgPoS< -KJd8QGav +EvNg2J)>Kqq0(B_`4D#|DXgnO+BxksQyLՀ#<}wB-K+Y*V#SLrdl c5*(Ni7R4(OdsUoOnoxB "%B:IkIzz]uSWEʕ^zǮtqUĻobhrF=!z} :f<Or {xlk7n l~B+7x9rAGMq&/c[+BV^eQQf6SnGζJ{MzSUjj. $$;L,K^2 {,Qh- ͇_(s4]'\% tm{H^[J˿PC"\rrinKV:XUUHn1+X&^2[,WPWS@/JDyWg"b)h@eׄBr)1_YYRЕn3N,^])uyV織j 1jK.1iv)bsUz;(taU.;j Cx}nShpG6S$~[!%Yj0+8k8ONm5tobui; )sW(sᰚ(NaL^޿FvMU*HgcU.onWZn淟ȏ_F/lS.s#WyhC){͗OaU.6a* Um;0tT[OnG$9zY~ {jyὫgQ)ֳgmI^m,_9Ѡ7w^%̬>ob3)-O&Y CG%B_ '/D_/)N'yQM6B0>VщIFFcQ-h>-:l'$$LhՎx+ M5iZ/Jc1pLnF*S0,)bb+-Dx*>>L͛tE}DVvNp0!kg/CNP~+!Ձ`S%_bX6{}d)y/%SxT' m$|Ȍ! $!i,:A`[и'h(.߻Xd4@ Lhޗ$?mI%+I+J'_*]Ml)?3'wT u uQ%BډqMnk YǓII0 L"P/ZHJ$|BwU@W;,"d z/KLD'Vuwb׹.^݊\HR`ێtpB-@4u/)Fve8ZB s#q C^w|u5O_l]|S ' Dɬ+Efi3 uSk`4h>㔅Jv!ߨZ_SI Ƌ7< qQ?$IXiWz ŢH5?)BH~hƻ&;E։աPVaummS{US4xs*c.fl 'wwVH4uR r:"A 1[KcPXy?z߮˷9qi#k[-wCv9n2"EexxYK<,:GsNW]j1/_^z5Oi(Ͼ4Z#䅾aT4:hː^/Iaʘ哛%{v[2+孔3JeaDHC< 73>lyn> AӊgplNGZAؔ/)6 ab}EC}} 0>5bi3qwt Absv<Ƨ= :F& :Rf[JCsǶ/6)&7h3ƪl_$A2jEan1b5/Ut z NbdbFYÌNqwt /mEI g`tN ^'xm* H5@ 3%vP!¢b֧CBE]-;.+- Iku,"/%WډO>%Fɤ+M $dg?[Ғv0(,OGv Y͖ѰQiڰh4bM1JB%Y(B+Oѷ~HO/f<%Z?T^)Y忤rQJnV#7F|.$1gm>8 z^˭qZFIjeuYg{l%iab\Y>ajڃcG]:;W籮" !f(n/nIuyMav /mf[ \ُ٨gku.'uQJ} 3^Z38 6}w\'Y.+^[SYohpuI2V Bt[`|r :o*`h|km+\SN yb{)#M?2E TW\ (GDGK/f^ VM֕#*9e]i|)u%@L{!x2ɒrˢP$:',ˑW CS̸݁aJhV>Rxi$$C sL;ܭLvt|}I"8BKȠPPJR sm{rLq0[krIosN$L{( if<>?|^?v#|\C蔛,yX-'fe|:da5kM>>@Q)N'{m>3v XMv. +F lɡ [K8OAcMy&i3F4;=>6ɤ0ˡ~#JKg{yrGf@~3<#;)u;8ռyj.~!#\5pe6; d]ir"BmēIdǓIxҲ$;L,X /a+ I8N6[`@a2ّDi2G)WeIκ,cPe%@~Cі scTY"+Պ뤾cϦ,&X5۸6@ZTٵ:iF=:Din )stglNb38yŀ@I`ej4) z&e5=DAY"`er4.E1ɠi31#RK?v7jdc5esU6cn$ 7UfЙfu%_xy !XÌѠlԫ~)m=|qSPj";Q‡ףƵBlwewuubYԑavSXTA_pDWmmm +뮮.2220t୭TTT3%Gާ>^:L, j!T bٕCeDJ4IDBH~F K~D<t]ܬ,*X: >OF%4yXXtLr]F&a|=B} S3<^eyN||g0<6CYd:-~^;q/f|rV\v3 j0ojk`uv'Vұn'esOӭNbpt;sj33նLL{"_|{NyGDW@g:XD`{MδsZ7nW-WmYC5Xqx/8ʖ e/'y.%HYGCouBrϷ(E>$TyƏ'.HZ$YEW3'yӲS"9F}<)eY<Ώ)+#{tgdt:B*$)D)Rb_zBT@%ZϨؽͩc1Ϧ])XHA5Di.ؿN|hӻqL&$Ibê,616&af}Ef4> ib2{m>Sn/)V71ȖbY^'.Yf6w3>)F*/6c"|~l#OJ̌^V/]Ԍ@=e"B*0˫B2LO*glҍ_KqXІ IDATu/$ni716 Á/ݏAch|_jE$1M{b3ص:kws89)cnJ1u|q*l~ k`'\A+O6(D8ԣCe%N^ b!} 2<4ęӧqض};:~'ikmٳ\t;wë/_ M7fi&ղmv]heV@u+9tSUSãO/'>:ӧX@kkZSScry.]Daq1pT~Qo4NOwǡill䣣Gb6hܽϾ3OG.+_ W_(ѧ&?LVZW F ώh>eP^Ɏ!@Y[t:233IMMU_Kua\aˮh l ţ=s$E@%7Zt^v  x+|kEvZǮQH! /Eǟ^WdJ~C_]KAYFȓ^,9KO^bñ}I LFA֬d2RU]M{[ z333~|>6n011N~AA|u72)^?:_`[+<أ]||fffppb4)..{mm-bTwN4< u>ݧ[dC .@?UB>zI')[F4uR1yE0\ bC".4?4NP20qETMyKX@D1fԁɂqT\Hs`(cB ¢iqNW`%tCWNYyqJIqPX\9<եNN! $?{&UNu;9朥U* tM@2 \|}l` Lᵄj+i6lss4=;;Tz~9Upp|E%4|SnQV|w]nSy=ݜ>u5˯pIK]drrZ[ZhΪ5kxWXz5˖/q311I[k+VW^͡\^N 0&nvΞ嗩`||^x+]FF~/[))-}cn),*_?DOW7뮿*vIgW6pp$%QlWgX3Wg\Yŵ5nO:(蠌|]44Zh ݼ[8>`ժbbʑD?'b^,IǶ}~6musNhɚkBpuׂs7/"zmPRRnؾݯη߅墸6nh*ʫs1VZɚut[=;moutwwsD#l~q]vq+^oΩƓl޺`ʕ^ݶb6lHxM\]{ q}RS[mWr16m̒eK),*dΝv8~ʪJNhdUWx(++OϵT{!# ;Rc놵8dA$ةӌO+$pM*!4y"A^~UUx}TРwh,t$00]Ɩ)՝Xi3gdd,CuInw{O/cc s|ﰼ?>8eUJ!r~_x'}\?,Ȃ,Ȭȓ?=צ$9wGy/}A``0qۙ  "y!{B?-czҬ+ѥޱJMi³Q2sxqV\s6r~-@ ˘%\ )S7'/Ȃ(힁,3˱Wp5jPp۩TFc&^*Ƈme8&[:bpt3jo`>g(Ơ*Qdq}v>'EOA=}Kp:wj զc0e1JH)=mwޑr.G u"pAdAN$Df$XD2u2<2:Y(%Y6:esE6seki{EX:Vܼժ% 8s0;{#~޼Bd;i„#%TV0}U8t܆X,dK[њ|=.rU H_TCxN %*.۩PBAˁ˩01$(.GT E q{<8N&&&5|~2::dkp2$U۩0P8D*(B0>q2P r*x\ÊL!L(8UK 9IZ%g*6ٗ2D̉x\i׳(K )smo62+x`@-ճ4T182I "ܸ7;$Q sܺ21oh훪Yd㡃ٽkSSxZ~_QY]E_w}iXtNε09GGk LԖ峴*g_obIUׯbב tQ Ubyz,`f|Or9.Y*y$Wz*IK‚dM4kimMjK+W{u?Q敦x"I6+O oCG0 b 4mnLe:29U~x Yܼ\@U\n~丝EŴn"4B*Y]WUN(̈$ ucLUl6 }>g ={200ͷޚP3]VcpuKx}UsUUr{3<6ƥ\ ӑ\o?s5T䲼0n{)_㶭u{u=} Zs"S4TpӦj\td'RJ/.ar~򇓬[ʘ &q?"z„y324N?MMG| !P;=,sB?ocUuEtuv+p:\hm!S߰_QbV{VcM=r*aPVs9v׮"7mxNgn#7L#²D3d9|ɉI:.^UkVZKUNVZ{^g-x^ڿ-W\A4| +SRV(}tr`WYT+Vp護X~=AU 6nڄKKٸ[61:>E8t38:ƥ%x\d*aQx8 Mrt'׭bQ7;l9n'+jhcE@(-)axlɩ JW('li@P5lX98t[Re k줬ǙAw !͜hct"*( 2*3Ie]P'2 s63$ UHTKQ)vpÆbr=WZ54fk&dd_ 2l_Ms40>+rS*:ɶu_+c|_Rŋ7Gi1OVj>kmN6d9fDI;ӓ,&UMk@e| Ԗt*LT6,) CaIύnZGMQwJ rqC5݃ Mr) Ͳp@xm܂7͛ lش CkK % _q:v]%Bv %~|^ʋ|E9ܴޡ n }z]GMgLYnXeo Ux/{ɓ瓓CqI 'g .EŜ8~¢"^߳%Kҋ/p]wo| ٷoVֹS((g׫8yBimi h #Lth;SMjp8׮d26vUq5JzZ }*T2_;N"?k*myUUd "uwpm_:=̆~y>pSmʇgڵ&ABa"ݏ+W! M'ո={[flG߹14g֛׻p%k\\e2MإYt)K_oe(wE^^uuZms/%ljZZYb]=-[铧(..lMVsaZy?-Z]Fo^VFuI.l\ S8SowAP{=[em}1 8N;#dP`&䵣T,E65UvS]Y]HIP PEDW50JS^PZh8k"CF+ãQz^v=t,W/x;gKMm-gN"??Ҳr:;;#''+W2:2ʹsgQ*uu,*.PXX(>ɉI{amH^n.o_R]lڲ>Μ>$EYt)p (((dي4=KOO7~. ڊTJjc s`>,ٹ`\մcjB!-ǣ2L'zʊ>,nj(iVx2x%i$޼7x2=I/<ŋ>/\`ppё.>M$r`QInϩFڷla:JJy套hQ}L궆GE5y9666ŸB0of" ^r\t38d 2U_ϻn\dщn7TqZ=ϟk(63LqGYY[D"tS[biUc k[m2zt{aqŒp*{qsU5^|j{衟A.jPE .^lS|!o|o}k;|~$/~1?Ir /{~JwMoOn)'掻Naa!omw܉.<;ݻٴe+:)P׿ʚu8r0=Η_GRTVU|<?+WIaߥ%k:Wsc]d=nS]n@1m#6o9'r I""[&1>Nb*Y۸ۥkps^KQZ^Fm]yk~rs)++6nQRZšuk%7/k0>>7uJJK#(BeU%5?vT/rR^Q-9^/,/7mu҂GIBi7Up+?}$NЯ`Um;N>"ݞs7lŷZ$/'Q.Cንer^8B wm>Cf'_QS_>͊BJ rذǙ ٺŕ,*N)-!BJ/'DLG=DZ~I Y\bۊ(BUUF &''QBF}~7ׄfTO07ǏTc#짧6=&6lڌd֭(B?HwFaxh.7mۨ淿%'NpA:.GyECo:O__\w=EEfߴέfp&̑-ޤ Z:Vd=Nx\$x}Nb}eqCN=p4E􄶆oBvKA|aڗzPl^G0zi sJ`֧t##ab $aGÔN7[;UtѾ'$:2sHg:~\ҍgĴ*)r/;8on~=5 IDAT'U\U\ wީ/,*|ФO| R>`-W\3]]t2{G9qyEWE>;X)7ut/}!4PilcIe~ҪSގQ5F6/+wpܔnT @"?Ó^mB=iemdZ3)OvNuV7L$d$C )A/lBΟ?oPA]ϢbkjxYjp:BIG~^Q2M.woӧNQ; q:C]ĒFGGrǝwmfQq #xiX/S'7\\hmo}k74pwbi"584S]^qk^i:ˊ'~G0٘ io:z>- y%CآTŌcv$g*)ZJ 'µ#h_gG3ݒZ=I;.~闆v=JO?=S]s^fw\}\2w7ic;3gbE8 |x?˛'G"lXx  >ٿ䟾hkkaTTVe7+T~aa!C5aM⋼s^={?gOol޺+] V/aͼ}v{ۿ266eׅ6v~6mL}}af[ t=MYGݿK>]6L \`EwE_o$yTVUE)arh[)%CȰ}CtRp>'[O56lMd*hy $1l26m5v{Sf"Nx4qMHZ*7'R}nGGs|/U!|/ezrv:m>~_x'0~?Mm ږI&m"*n`ի& |Z !p8k֊Ƞ6\Jr|>XEB~^>$##HUx!ן|ccc|r|$04< z @˅?7EQd*0"7/OLNM222x<^|>~/6l"[ΜKq( t:xྻŶsqAB MgLNLBSK:am@(HM 3..ExOp\aMč5ߊdnS8 }kţKqޑ&#g'c>f5W4l.1O0gF/M\nWw"9X[Ufp ږlaJS[aD} ?s]J̚-C}}t%~G0Lb61ϯu!eG1Whwnt 34c"%S2ʦظL s:).5kt4ȍMl5kvjL̸H!ݾ#]Ct|2gWdzɖldzɖl\@caZ"%JrtH2-Ώܛ3\Zk/3,Y`\wSpX vP0hHi\g&R WM^0'!C"uSA1VWi C>a/-DwNm"WxHt5?cq~IX| ɌIl*Ro"u&blVf6\k+e|=z\WD㋴$'뺵gK[Ȱĕ [ ,c0ԛ;IO KcJنEʣ9!K-RHS:*s cpkz^7pNÓ:YԽBp̸sHpbJ86r+[軦7lq rBWsҞd;NۋEƈRO.K[*.+?(‡$Z-4kXy8I)saiI_ZFa@& qBNtA :p1"c.463ݡ {M+g8a]Vˋx\< nJ .OhΟxln w1p5Lv>W;m<ՌMp:ԓ≸q|I) OsZ9:`啕qTU׬A ;zb.O4_UWD[0)nTCaS} OQW堭{!K h|JI[0c*?2As~%U0@d$K81,'#R pYDx櫍?28v\d '3mL[ezu%3՛p͌^(U܆]}{ߤEg[~MF&'&c``nOuno3tR 49`ב!V[m\ =k젬 8#zOqcyk׏_0sܰ.uq@nq1m]Ô=*U,*xGSzR?qAdADV4ԁu<L0"fixDYhሞپ[Mj92] ǭ뾌fΘֹ20!mlpVe='RNJ"_u<7Ғ[F+ȉ#\O+3SҾ>Șd4:xIƹ/)Yxv{xCC};{cGpMYt)o|:;:8zSʛ1f]UI.~7kӜjv0PYY[D"026E"?{G9}a,.t#ܶV9|,/c2$ Nh(wp|rsBk1UZ++V, &/Ȃ,%!.3jd1M6VуPJ;ЅW- e`CDjsӚNӓFiIH&; 6XP;1(ntgB近\FSZOpOiĭgWV e\760smK|k)(,믣Y$rq88N$bEzBԼcIKu-\ (B=Lg(046 @IaNwPUJ st].)&4] )`d,$^wSn8 "gzkSb22g-َg-@<d+nmhh_C,Jr0}P1f\m6H7q+;\F8X\R2c.ҀǬ/2r փ U:q}h6 lIϓ_vr7%˖bxj۶[l^VŽ7}4ThM8B eq8Kt[u6TV1Ks*"\;\]]ؿ;|یs|U\3 Md;M$%̈́-ńP?MھC(z{dgGyTTVΘ)%CzGwp>sO߆oƎ hHkK~C> j=Qx[QLdlK[}7okء5raߎ+;<&xD .2N5gW>/eDA[W7C#1V|4*f[M5L:e2d'k )EB\%LLx(^Ӊ>~_x'}\6ς,Hk2OSSǎ'7O0FG8xn`rb`0PZQ)4[`~Ha+!pnϨ/ߌ!>>h) 1KZMm2sng"v0 5 xܤ1mv2ؚb=$-\i0O^gv(:\l%deOҦ{qe>.]єڻFD{aHǗ2-Ȃ$-8ZzOWF&_oĉx^TUEJJjoN#*LX%?vb[quM)DVӔ0-QdDh\{*}2'u~"諑 -h\uJihsb\J[VNu)xj n/zI<[B3,ǰ3xp)cQ-2N{Fk 2xW|d5[6>2kmcLn 15o 冓 /)ns-g7JzZ{a/Ȃ̲Gtݥ;%8ϟraZbѢ"jkBHXb3&="32=3I`ɲh8GdӃpF!#:QQhWn2V>Jdrg rDrJ$K+Q%D:qJm-녈vq;fPofkWO*v>2I\_.eZg^d%++ϗ5z>d_L9dW>뽷*Z,sd9<\02Vjy^NF|vO9)h16ktS5B'x;( Ndp \q ƕw}DEp?i"ffcĉgWƪF5$V=|LUz;d;UVp%$qCHgLijm%[mv|mĮom{2JFIPCNgJmǀIs>FFŰA1i;"-;vM[mީbq29XF~l +dp+LLq,]9bҍ[iu+:39\՜kҍKifs/p)%BJgϞehh([<奷ߘ 066FEERJ:::줠:[{N:>qYx11󵶶R\\LNN~$^ǕQɗd|JQD)mtd/,,:G~CZg=JC=MZFxdOZt\\;(͉Vn"蛑 I|pN˵2k>S.҂#Rcp%-YM6Gl2GqIѶYJ'Nْ8YdK8퉝D:Z"&/TkK d tN2:GKK3}==QT%KQCq: *ӁfC#*RR(8R jp*RUd|c/++<W'`ll/| |_;?1EEEQ|A~|Iy䑘|+ZGyDEXXOUQ͗d|5JLv_z~Ss\Y uğ< ;asmւ,Ȃd\?{S@[L2*BN$ѱ )E`pIΜ9Ǯ;˜:RU[ofӦ -duTD쒒FATBEUA (!U3[/~I|Iogg<|{Geǎ|ӟ4gO~“O>ɳ>7 ^x7㏳h"z{{wk5\?LIIZttto>V^͙3ghllꫯF믿??n:|25Pb IDATEEE\s5<䓬_~s!8pgxOn7gYr%W]u/½k3VZK*y3alj~hSF5I>r_l{f7>yiy--KT9~򓟰b /qկvwA__VF~#opW~>8vMMMlڴ ;w2<<ŋ9z(OtR֮]s=ǧ>)<=cժU;vN}Y6mٳgx; Aztt;׾$wsBUX:=IKİ/gɼȂK.#'6%*$mx6J$z4]ÛlAdAI^{>*]PA'Fƫ|_21LA!@*H~OBC((tsix?fQ~A 2Dwe;5(̆>R &÷e$7; ''(|ꫯġU 7ɒ%Kظq#===A~_| __Mӧ=իWb nvC=ѣGijj5pXo}clڴa[?Oc rhmmX;XJFQɗdXb1P"Gbd3y9bXt kbȋKe%' h0[Nl=fڟY]0ɴtq6e9 Uq#&ZE 3΃w `cL_g~7Wt5֔HUIWx#N/"@A2?3=ZQH$E!EПق"$B?ɛ_\s oz׻Ȉ_}|>kLLL000`ihh@Q( QWWGQQ%%% G9PUK gw}K199+VRBPSSCMMMhNJJJ(,,|LMM1:: ܌OGWtjg1`C1i1Z$i<&Sh m126mN}iefr=mq%1&8ҶՂ,e.O2UﷱE`]/}8u. J<.yQ)ͽ1U0%  B5_w~-۴R\!:RTlj@ј/nbjj>N)|8 yꩧx"?MzȢv?!SOqM74CQ&&&x뭷kAvΝ;Yt)UUU9UUU?իWl0ڞ?v񐗗Ohf>$8%1M`rކf:*3h 99/49Ds5a4se"-p n2h?;v<N626{ASa7m9J҅%Bħɑ*yBIWADOʗ7_Bt,E FQ"P(~*}#G}G}O/??~ٳ}{'t: ****++g>c. l2N'رjOo}m۶m۶c/w(=鐎 EMf:RčR+SR>}^CdDϺo=fa<IuMbNbW V%$eH,K8[YE? =7y|!ؾ};o񐛛O?[n[gΜӟ4'(,,ǨJ^^O<<.)}q޽x<|>)((rޮޮǎcҥlڴ4ғL?{WF&_*F :IHt֨"F>]<9ԧ0NM+5ξgZuٰXgI&h9c>_EWĀ/Ȃ,HVChͬ/SHIh(Zo~Nw[#N9fw?G{( RUW͛Yb1oCDž 8pOsLN t8:oЭV*EDDQ;vb&~߯Qm Qu]2}_Q bs-% y:toф+RJ<QzJKKzQ^^u_5޽￟J_ُO\Tu%|IW$46!Ei DLN(iVL99͘3sbYO2^Lf!@gxoءc M72T  atL*P~F'(,*;(/G_$j@Ez{~NIb~?fr}F%aLD̘qܖxڸf˖KnL2Nya2vTUTPW[( pY&''+lS_o(Bܺͱ'`MSSSSr"[Tߠ𢥓=DWpp NWl 7`prssYl)9^/;;inn&R\5sp &''ٴa=h_K,!?/Q+0|Q(-)'Ofll,Z`[+e8x6X>%YBQ0DQ-]BIq1RJ/^` 餶ʊr&&&8s\Kw!U,Y  7QWyGdtt}bx<?T 5WmS4G255sM\t|X@MuZ3g kC H8g5GmM^R'Om\hfrssY\innt;ys~m7grr'rF8u4/ׯѣ_6 084DmM4g(Ērst<TWUdqC'@?'N$PS]MC}};~Uۮ*1JKxc^ TxH= (/ce9v>6_Z(FYt \gC04<̕[˙QtwpIΆ:jkjDIrsٰ~^Oiv%;:h:LQQ!֬abuPU8v`eK)((`jj \@Uո|eLܔ۠dy翣cI&U}"(BQUB^V5,OP r!t~,%\9C5HkY&GKGt)F U%̛$48WvZmp{|:bk_S?M_~3sdxx<^,C)>#k.ɲK甕R]]C~OW?1nW^֭>{{G{ipCo]Io_v ۯvQZR¡#Gy{?~8u ܴ]ɞ`p']>^|jv=/#>7㓏}n #8/_Hmm-Ѓ|k_a?wG#zoE >I6mXO~~-⍽{/\n[oSR\̗<K p'}߀d4긔tϟ? xwQTl ! K E(W{k, EDH鐄HH{:?dwi@ϋ0;s<3syN+g^6B2Pҙ8͚5eȑ!ܵ{ T=xY8:9ѮmM gfыٯ '''G٨}٧$cGIii|x';os=!_r9._aSO<Υ+W/g׶-\z ??_ҋ 4 ܔܼ>9w_xfNtcSpu2239g'JA"a)6<:y>ء=OL{Ro-j4y/#cF 2iS$u|}|puq%%rtО;v 4Ț?oA;vLJZ6Tʀ};oq=!Ѡ3gx׈qcFޱ8gsr*۷ecHIIJj*NKG֭ط :!GMPl\CyY|NNNkDEBP0deezcF໯'Opgtܙ߶^sݷͿۋ#GRyz|?HKO'6"~ w7%fώEMLaa!IIV-?|/B6`|wHNi9Es1nN%zH5b8}8x<~ % fמ}z?OKioDE_Lx̘>7o1<HR͈q9 ;16:ʤ6LJBQݻiqdNdsPıغTkA"ȐIunkD.P8XC-Å"\"<]]ފ;f]m > VPVRNqq9aPrus*UYj333A*%z^\.7 MWWCXuKnª T5M.]2h@?'N&5&7n^/r-!r3:.wdK?4sĵ V%zXJ6Y8sow9CޑѩcG:AkdggP(H>>tߜ:sY<._6jzrsؾGmN)#1_ qtѣxb=lشJnh2ڶiÖkh߶-7h߮-!A=]IZz:aa9s,>>R}mf`6M>†M"l.X44䱱^\WucQ53/oO~Y"IHM'֭|bڶn܏Ͱ!ض}nJ%zp>&;vdU*/YVYf$~Y0M2K0al4+W&;;*2/]RdDEFpB `_hs_ `/]Օ1hyœL&ǦvF W yAVk6hJ<|Ĩ'ڶFRUJC 1"=] q1<==ѭgϟ'$8mrQs/= cb罌hC wdo>/sx'x>=!v]+=4 mV=6[ӤI9-LƸ16Yԙ,^ ӦrJz=)#%%%Ve 22Eqz|:ƲV"wDU^^TK3nh:̴)e/ыz|ɮ!``Rի fP.7"JærgZ5 I/^ םg片;Ίs <:ċsNEszVoاr(C(4 5^RRBIyyyj<-ͭ2`E kZMaay!Z-t:<<<ڼlX;l B\\\ʫ&BQ5ݻ;NdNeuCh|>5¸am8ɡ>a\J2Z`¸hm;;;2kXv]t[( 4j5Z6t &fJ$$q gϝۛfM;2sU"z<9JtLʹ[;wZzYpppz١}[f7kl/.)!33;L͛dw7%ZL&KNH$|=M411; '&6NOzF&7̆bݘ0n,ECaQYټΛ<:eD­۷k_{sl:oԕ TXxȞBMߧ7nSXX+=wԙqJJK@`%UjwRQTމ-..StҥE5!C3.(-$0ѥTRRZb0,"GJ^Fr2M̫3_LJs-Jr :Fs5!ќIg /Ktx:!.rg$ 25kF&!,{$.;!*i(Ww*SУ[Wܔ8rsi @vmp`7NmNE.gİ8ؑOp5>Ν‘KՒOڵnm( DEwvGJCUL87|INIЩ`eAd2skۛ6Z$MU5pE6p(Q*'+'2^@Ћx8hA%}څѼ &ɀQL}t2CGEӶ@v$mgZt{@RGGnniےVmMmtuLk)4*e[B˞f&3pAfϞMnݘ={6.\W^M2J~j, >{ hg Q3gNvO||||mr[̞=7xzKߔ?`K&NgFEe*_PCE6'Nf/[sxh>dIZc߹G~A>}"Wg|96mʶ;INN1ɨZAp[uv*:vhO˖-nԱ#zq5.lʐ;;[02w|9<0 ޡ=g_ 99y_~̗^`άW5O?o hղ~B``󱱸)ڹLJ׮q37>z ];w"88?DY\R>ӓvaåjdKSק7? k[o][>{BcS'q9 Wt:j6`|1c<<)*.f} C=v v鄛Ri3^S^4/+/b%6KzFfiIRDFGbRqPTlFW{jqjhv錏׮'Y۷ݍD1_;t؟'xNHVרK}\\\)+/g1,E.3d@t g_q}:ǏgÆ /dk.>&LSE͛?Xm`o^̙3Z ___^xd2~)*^x,N>Mrr2dddyf9u͚5sr/^3s̩˗Yd r9spAZjEӦMYlӧOɓl޼={2yd͛Gfٳ':t`lٲ|:ė_~I~7ng֭DDD0i$r9EEE|g3PZZʱcǸueF 9{,'|sqƎСC͍VN~3EH ^-6m7.Z572,gṿ1׮'io9f\/m[&,4TDBlvb/^d媵( rs(۳o?Ʀ-tDj06D1FCL\G>ҍsb8u,M0x.ơA[Kn^@X'o$[mgΝgS\\LQ89:Rٹk7= ???ߋϙINNO+>!33 ??wJxptp =3ðV +//7o^.Jh46_-o/E";7 ___ڴiç~jwmN8 (((`Ν̟?(tƒ/G!44CƱcǘ8q"۷un(Z'`޼y\ty.]ӧ;w.;vslPַo_ڵko ^b-Zرc e|gtЁ矓yꩧx"ݻwM6ߟ{ҦMZnΐ!Chժ_|ͳ$_0tPZhQ(ر+WFrr26lo߾?ݻww^ڷoO-aÆѺukf̘a_O=/&..M6ѷo_Μ9þ}W( :t'|Brr2gϞeƌ$$$бcGZnٳgIII\~cDzk.Ν;GYv-I6M3Huqx[Xvyis}kI{]oʣ:cmaն{^/k֑YPQqrt$g222գ;##;'6Zj\=3αd ƏCNUYQnA"Kp8 Pz=1qqixscprt"0 19?GCV1h@ܔ<4t:k7#E攔ыP)sjcD;#* z=׮1BZl d䐟omBB ~wwMl5]"0u#t֕#GwT*Ţ&.s'2V{e,F.3?(ݻuD1#GдIˉ¨=PT 4CG_`;IMydyWzvGw#Hѽ9NrJ vrvvgnOJ0-ERoaEpn޺A:xԱ>^L4^=s)ؽJ]7I͍? Q%''ʉ0x+}]3LB7ߕ ^ox$AC3:VQW鈷>rJgg \DD7GD/ZWrgN(w%zJSb9lWFZCcjPM]tѪU+zA޽)--ܹsܸqk׮ѲeKs6k%*.Zw0~\4'>''O)xdx>9YYYhޜcFViu,Z`Q1V-eЙyx)jU*&c57S|H +~^syfhZI) Пdޚ:v4ϙ2K};7},Ng5Ecj7_~aYqWqYE#goc9ªM䑗S^^ߘ'GGV.37ޮ1/NOVӹ5 ӿu;7}G&N)pYW*CpڵiMQq1<Li$maEw0Gx"{Xj5bY٤gfWk?۶勯ML?ٳF`@ WW&> _%5 n^y%~A`ђdZgR)zxb6m믿T*7Dt † h޼9˖-cС#֬YèQhf\ܹs|ɍ7ظqy1~իW?lNp,\мR#}8wÇGV3|pJKKYt)`wZE@5iFt@tE+c°d哙/x#+ v]I d ыWK<!wvAb4V8俳^ߣ&h/nMv;_rۧilMppA`_JKЖ.Ճyy"wqFQE>3ͪ3U4:Z袱ZUUUiY6eDuCE;sﲻ}[ҧO /Bvv62nʕ+Wڵ+;v4sZCo>vʈ#شiGիW㏣jٷoAAAn:$ jӧ#JY|9!!!dee1sL8ryyyуC6m߹pÆ #""X~wBCC #>>w} @jj*k׮EV#ظq#IIIٓm۶СC u֑ʘ1cf۶mh4$ ӦMl~grrr߿?AAAŤIdƍVzJ޽{?o߾,Yv;vUV7\~JKK`ѢExxxGVV?#z3f͛7vmڴaܸq8;;#ɬYL //5k!!!@OclgN%W*^U<dBg3^w,g|x;ܔJqgJe3 ^B$ƎBT'N.){YOִ RQLaN:xD J@at:UZ4j\\*M:}):G"J RauRYAAX5N:Ebb"Ѭ].]C=ɓ'X~=fbĉ޽޽{j*֭_}/Kjj*QQQZ 3Ć 9r$vBPɓ't8w]:tSXXZ_~sNYd  ?g 4իWiayMOV!c‡Ǐ1VO&Xw#: IVHn*`*A_)e盍k-cXoTI$:$/::AUuE2׆=U#bއ?{?75Qv>.y[4tАy-ߘz5_=j0˙ } UAv\NLehrȤN:D DFbJ>W4/_y!AޤSPZ]y!EE䗩)+p )\VMnl`\6lÇg߾}dddPVVƨQG L&CPФIzÇz*qqqUݦMn޼Ɂpjΰa((( 11!Ca233yg9q̝;sЩS' ԩS[QYbU۷glٲ'x_~k׮ѧO ƁȠ1cĺuP(Ҽysbbbtqrr۷Ë/HBB  .0uT:vƍQٓRZZJbbY^zqa\ŋ8p $>>7oڵ\vHʰa(,,$00޽{Aff&R={һwoN>Mzz: 4P)6l؀^g 2c2`va p>#Z->(sAPдiSg=GOChQ0U@ѳf ir1@[~*]kEM(4^mkICEZyUPo 3;a2Wa7 LOO4/Q*"Hzu<==|2=zWWWA &&e˖9{pssŋr=ʭ[ #<<htrJ% 屔߄+V0aڵkǮ][ݝSNիWfDT*ё^zpY`*:u 0WWWW9{, X?2]xQXɨŚ!4.*cRCb&/6G{^i/Z#`9ߺ+0vә1R Ѳ̂qT J3&F"ILlx"VG67(X誶]Ԥ+{)>y֞./P.jK**d\ੂj՝uUJmdj(KM|cеŹ=n4vJK!&B@D'xFRN1դQTZ@yie%u*Uq85Ff!Y9$pR2)y%sI(ݜɝIΥIG6:crE0`-!y @ǎf$''3uT7onۭ[7[&N9rmڴ xϩSXv--[DPBfhӦ ]ta̙8991tP<<<իC !44Ν;3qDBBBP(ꊿ?fbٲeǣT*yT*Yf4oޜ#F9s&QQQ3qD.\Hdd$3xzz2sL͛|@޽>|8۶mUVB\\aaa<[o1i$|}}C1JGy~Guj_퍻;/K.%44PCɉg}B|||e`=z֭[3i$ΝիWiժڵcʕ8;;ǬYXp! .^"~/̧~y7E͛["U_maKh78#ffd nnYeRT&:Ak69MטlҰ;\"M:-^BM:TV&W opX++Z6 oQtSo1emhest|2?FUGQJ3[6ܯ窾x,\[Q Qrf0nXU2uTi!Pv9-ìiTՔܸFJm uo J'22pw ӫUrqׅ!Qm̦=1ƏA"1Q04ыzDA4v fM"ZRͣs 6F6|^^vbʔ)wt}]>|gϒ7]*?w% [ɤ<:~}N8qpp0:ZF'M41 C(卭I?M4hf8Qq.T%be~r?L;-5'm/ZVBu*eO*餶![^_]g 5"4w1=oҕ-gy^oԋ-S uV'"B*eM),)Ĺh6 G3}qtvc`RG)%Em /?Wz=مdF*H3*A .&MdmyBCXxx8ݺucǎV~[kmYBbN:qhh, *{, B0۪$M<*9v v fΑZ<-jyFe:F7VIW[;:Z'j U[]oR~No2(Xj+m/_C ꕷ)[~XsذWnNLJ1cFѵKg~Xs,QA-0Q HM$-@O-Nt،`f([4GᎪgӬe[BC)R8|*Ođ'V+U`l WWлW4kBVhժUYUecXskuXK5LZUϫV}v '''1cE~cE>%e5>wxQ\ꞓtVL/9 $$$T}5yD綺WB=禮|q}ܿ:o[ @teϞ5YW:ĽFTb5!P44mq-2qrUУK8}YOin:YEF.E0?U n\\t\DDZoG@ztzD(b؄0eoqpl>^W獥,᫊W,mIEHk{ KR_4sa!+Vr-tZ-R*GE֭_ϩӧ),* ?" cEuZ/.)&?VK~~yoEsr(//CӓJ"//< :ss())!??2DyGUm wf2I& EޥI+{ZW"PQ D;HH JL2d83I]ϓg嬵{c1TWR3IQUQkTUWSU]EMm ""7oa޽jT"VtTVJ4F~=~7ŋ":J J^GVS[[( zT*5V "Tkh4LRJl Ɠ(b2heѢ8w. MFEZNCDDRRVbYb:۵u\* QTi54ULfGM݈!CMŌ&-_dz栭raV]NCQc2It:ۋ#b4QR&_`hxk.գ; FMJho& FZhDDگ!"WUiTUR*dTUWѲiv(b3Rb`4PUJVi饺Ҳ2^~_>rF͢stXE+MUDs]fA"ŭRږj5]PΜ·wj'T*PURD[b @C MF} ZT*UjZ[[ޑfH!: zGdQQYi{/gE5RVcZ{?KHUu6IV[FDףRmRVACFdd2QljMM *MY dE#YxɄJCe mmMlRDJ̔lٺ?_?F#zŲjjjPk4l677n.}9$? 쇼 (pQxB Ud^$GIt'#këΈ`Dfaj)((j:>}{ 0 6(J}֡7 _Mշ "(ҜbjPKqo%oW^9ns c{AF?޵{?\@۶I<1y^]:sEMJbD32~;3ާa˖Xу + 2AC2x{;y(++g5ro d @#:0ͤ$ɓy 2"*x>^-a'B&W /?Y 25Zu3gPTy%^R\#!!sYY?gݻחW_{"t̅y,~1dȑwϳjj, i<#R=.]:;tЁͨ8a<#㣏g#w.>^C,Yӄ/>'::~'[WΟL&0`@ƍAɏOFzɮ{xӯ__ ؾ}lظm ?.][yG>|%%dov"t:#G1|8Fp)9s ^^9-n]9p""q홧ص{۶@&t҅O;v~(Zm Eż|7TUUӿ_v0fxdsi4U|Bd? &_zJ~j ^\pϠ]=1_q6ngTFӦMKxx3jQK.9#ZyWquse'0 f_PYl6۷cR'} @ի|3ZjZDEǎ aC̓Sf%KIaG8} Vj +Gs ?C`&ҭ[Wɍ:3eȣXwtJKk/.,4 ^`(ݕ9xN TP(ۧ7AAڹpH:4 IDAT,'Gy%cǎk׮|&Nc㓤ߏ?HSV^βe?1t`x5p?~+W ~r)''Ks=<=>|fl޼=Fрӧϰ~F}oDEE5Bwvc&h;v4~!}ݤ9JQQ -S4z~VY˗`ժ0x  W_~+yyl޲Ax5jޚ/K~\dŋ|g ]RSټe+U`0h*;whlܴs1{Χ;t:ŋ0Xl9_-Qa&y=ibf=]9s8{. z|^-i3gHInKLL  Uɓ|<.]ʱ)_zgym۶͹sY'ڿ^3GG;wBRK/P[c8pK9 (\lذOp4/Y q0x /L.}ƍûߦXx""糙=)(,7Ne ֭_Hse>33m.[F? y*+JO<ʕ =?jR|*OʜO?cҥ lںد""rf~ .DV7nĴxg#"y͞K~A>ǎ1O8#O{Y;lش CAϒ%Kxw Vʂ|\VY~wgS\r/3cF*U|3xǎI^)4  tsoV 2/\JơXTFNQ UjjFt~ .ղ"Naϙ󜩩ZKd_'8  +"- h_ lm'œ9s|20o[/uF#Fm?"@II ^^vU2/_R$!>\NA~#z7ˆƋL{cq 92jjj5S^ƍ9t0~~~|甖d| |}}IKHBBO? ѝA;;@-[vzvEHM͛Yvl޲j\%Gҏ2yQV^hv^Xj5u:|}P*3ycG߯ :t7ww>OeE%1?%-ɮBA/(hMB^^Q^Q`MB>a/ND$7t_#**00p.f(l(**⣏fPRYbO)))e>r|W:lݶ]ЫgO~{_kX*l) :ztRW^=s6nLuu5{\VJFNyyo.999|0# bɒ1::{'\-)!,<"'7s?]Yu7Uӛ.cHΜ>$1)S2o9q}4v)E~ /? V9pf)Wyػ3ާݻvz2Nfe)|6 I#=z ػ22NsI=FnY)*,{;Yz 9|1Kee3?ٳ6\˽P@VͼrESfPʬ\-ה]tH||Ii<9{)jxT fE*:~nʤ=@qq1:"N>Mnn.FѶqΞ=Kii)VI&/r9Z-b233)//l6srss)))!//WRSS˗x"V6\RRV䐕^ٳ˗cXСԩS$''V%++K.a2tdffJr9 VWWEff&Zb ̤Xrܹs\pl|UTT`49xx(jk8}A2d5~>h$00OOObccGƎͯǏӊx***& 0PA3g3Q* Bf3" zrQ9"Imի'۶og ,3y;wYvލL .6HTT^4UU:b!<<|2/d$$SZV  &(((*++-[={x*8-UZs&3N8IBB`H C f,iEDD7a1x~@Zj4F#[l?3f(RRR{gұ};  H?-lH{ger9 `6[X}r;d2QQQIJrAo@.#}ǎ1qx䶕CȜҲrΜ=?\?&лw/P՘&G.`(\tTko>3=4}:#(Q0LX̙lڴ Jʸz/;o`s?e^X yX]ψ# :FIۤ$%>/g4+;xzy.%Q9x0Yx8>&WmY4zb FXE+-Ξdlӡ2X/lD%X?ے,-Z万pٌ3ٳg兿?#F@En݊ m777>L^ d̙tLƿocVZ 6ٌ7SL`TVVBnn.&M?'..NY,( ZjETTGf޼yݛ^zsFeP*pz) ;wߟDrrr令ŋ+}Q/իHtt4%%% \\\dƌ,XVdj0kѵkWvjE& tRZ <*[P(̘1777M}%;l^pr#:uW?\F-g\+,륽.8P M2j|ͽԷ&\ezU AV)-08Gkxaaۿk,))eՌ1~j t.\B'&SRRܹ8^:"j-XB4z!Ch߾}.* 99Y'J˸DDDЩS6l"6)))̟_DHlHEyDFF8d/z֬חÇ5zKr?;")mypbWawͅtT}O͝oMc͸ҿ__:|~m޽zҪU4? o`GSfeLnJ-ܹS'شekC޽xg,XLHӊVq |b2.,n !!>xx#ɨj1B@d4hVEZv^ߤBCS'^~&= W0XV,V J_$YZE:\]%|< r. la2Зh..'o1|:B, XYŐ =;SڷKiTqNqmϢڴ۪N\\\HINc9tCQiŽ9A&7lcNҋxzzѬ9sILL;:=cѩSC |horA,J&!,B (Wր 큶O<Χ}κ>Ovy5V{pzɊWre >|YGEEѯ_֮]GMm '_<==P5lݺ0!8qo]@ϻzf:>bccu! PƩӎ[ѫgOVZMmm-*m ƍCQa'NHz:& ځs9r8_-f]Lw#jjjG|;z.^Ȍ0h@R;v(å\rr/S^QkBϻzu6dI_-*~}}ƍ͚h:>{SSSáCGXju $B'NBӱw>g*ˉ'>mx kǝ/Z,/%VDA"HF^SuG2w'&wW+*5ۏ{(}2mp zBt8L.rA2X H$6fa~(//Rs=GYY}'OFE.]JHH;v@ )))[?~ݻw3k֬&{-c&AHHV0ik X 9򩪪|@_jm8bvMLLdtjϏ{nzـV OҹsgfϞ H79 j5^^^ ҇ؼ1$d2Y&×_~B@kơCؽ{7;wn01ݘl +yF7ewٚ߯vmM乹b#I  bu{q"]{xnn$$KnX1[- kJٹmegj*ʉ <0Zs\) 7DvRHnLBf%O(1x.ڊnqi2A*|;e%+T\Ωw~-ʡ<$wjRZZ rrssX,DGGc2L&EEETWW@xx8CTҽjl4|#%%%h4BBBXrsss쵵SPPGFF;88JELL anXpR-rV^ g$"=/?. UD&(GEDA(/p%GE}oX.P(S# ##n.? gޮldҊ]9PCCm{ETQ15MQlDUYZ;:;%zi{ دuiXl_t/8F:ˢpceW( vŷ<5;Z4o/ :t; WK? pmá[ŭ[te^]ܮ&ew?" Ȑ C:$.eű,= EWQ`",2_A`F1 " 7:DHBHћ.^ɣBhU\!ꂷBDRk- r%.Ӌ0 R|hHb`<2ODQ9 X_?Zn{]NMMmWg8Vqzs̜͛y6BB P(#$󦰨*DYY&F'R8!Pv N;o:]-EE8֓༌̇{~&O=RB:Oc]Z ˖gfk$>5 :MÊZCjRT\Lh\r={Ͻ[)+/ն!@,X ldWVVƆjkxɦF-}#G4S@:9٭`m|5۳pF=';VT.AT}*[@[SSobg+dܵ ^/[vYٌ^D[}Cae|ڵٵ{7[3t`\ǥTVTڱ#^^>}7^ A>o{Bl\ M 2.] xSVZƏK9s,UUUKRWg$qѣIII慗^k.hZ\l'}N.[( ˯xC aA̙W'ZEG9L&ZEG{2[ͷ 0߸l a8M3ݫoI^(-6I7qz?Hii)cǎ&''W" ^gzǰP gѬ9໅<>1/_Ac$m˔''X|x#QPXk5j$S4 gΞG]r p.+mwzZ:xK~\=Ftg˨T0~;w^ˋ:dvaԩ vً+E\\ޞ9u*ѻWON:Ś5먩ݸ瞑ֲdR ӷ# `01rǕ<.ZCZjG|~ >m0ٲu{هB ӹsȑzW:vHO?ʎ;l"-, BM5m4{^>W_nt(oLJG?ض}ӓS4tN',3OXXF>ݝ6mxyI٭[¿/1h)q2Νbƌn濡,իr)),,b;8yIII7={ٴi3AAAڿ@ZŢKӋAлw/, ;v_twg٭2AFyE9og/<)7T~Nn.3?ū/HqU||`gSjZ^^:vh@?s&A 0%?.)O>A\l-ɺ9#,~ ݷ?7<ܕME~~C i!1!G~۶3p@N9ˏK19{F\k7o~.# 3fF5l9um~4y*] 20A.`ueYYd&ZRr aCqp!&%=w?no/]rWWs!.Yؘvå\UW^G|Jff&rHIMcr FmM-2c4vImm]{d" [[Zyٺ'lFȑVDQ >~4111Fzu֭ChhӦM^Apww#00A  )) BAw͇¢nnn`4quuaPThZƍ%*2N` ۢii4SSScWnBhvKCC-$$$Īod7"{l͢gs/d4Ӄ ,\0|4 sa/h/ݕ=@==$'ˆځp߽L8W^}:y2~=\f #  c/IDϚ5N`0pAnzxi*}iiiرrX7|'̙=A d]v4Ϛ5Ғ2qDFFn~Mل03!I,+I𠶮xܼU_|ΫNOw' Z5<;Vijn֛o_> XzP +6naΜ1*뮽nEBBqq1VCUU5 u:D欬lJKdSqssё+goS5\֯'/??}9k??k} IDATAWw77@fF&wu'1ё89i 믥^~_AFf&Hq&vލ.IL`0%wtvRZZƗpc2شy '<<Bٸq33O'7/\]]ͷ{{^~s̞=)IIλ<#s + @:xEEE  _/z=j@gG' y {իװ{^v ެ_JE[kYPxx.zYܟz r|4 MMsם{WqҠio;d6޽x'lʶ}g%k[U]΁qus?mI&rӍ7 NB8uQ2I 0rر &0ԣCpU!Hz3dO$T\RAdGppR; :thMFQBʹڨ:8NrS+mxhqԨFGdF+ 0aLeEBD@, )LtZ푗J<)ylHˤ`]\n3QfF8҄So76#+[8x9p醃{;ۚO?Av%KDDO4(..,Y+w/ J%!B+ݍxO&%%)'gA'<#<Ø%wǎ%?V.AemiQd2!ZV!*PZh40o\^|o̝3[G[[;˖-ْGDD;&M=ʴSHOoRل+ "" wtuuYDYV srv ףj8iP)U(Dn8QQ4pvvl2ӃRDqBD˽8}{QQ)7,Kp5Q!P1:.YfڕT*"}.HQҋ$\\ϳ<iik[8Y]P1\]e={ow=_Kpp**ACJeEg'g<= !&:7WWLISR9ޏ'kƊ[`>11BQ( +uuu8;k)..a?0iDLI72n̘1GG ${&N'GDZh ,KW}P.x);vBQQQ쀀r@⻵3&9geVj9s:47Pp05} R_{ ^^lٲTqqqodp?NN|w?_upݵxIxXӦMwZ(-+c/UJ%,mЦ̟Oll ɩYd1F?9np3F`@a++RRdF0JgE D-٥3be)L#Y|\q*JVUήm`kM1dn.^M|VSfc/-hLtHZA@/ V(ЋH:QZIN@(فmtNΣW% RP)*G%#ZkI0MgfDVOgojFӉ$%ΞJ l~}uh$=Z=F.m߀-e(~:? /=y"8KRIvZ ;55lܴQaC , ?0;\s)Z̛37ND^m/_~''"Fݍ"ϱr~ھCmx4[p@K'|.[*R9p w~~~:xݻ7Ҩc L:??_BB?~dhK.Ý0OBƉP)bX '' .`a8fΘƞ={-fU^3::c;VnӒJ _>LQq:ei뮻LvSOGMm---|r'W_s=-C@сÇ YpekB?oRk\\\D1~XL&oo\]\8x GeD[?H`xyy8q})A#5 u!)ߟB`ƌOqI)흃d3O=5W_֟~fׯIIKut>fΘ/#\\\pw`dV3g`˶8:8_l=G`ONhZm<}F z@jF,^c^TqppgHVK{{;NIt_a0 V Ʉd̬,ژ=k&nnvVqvv6KPPӦNE捷ɉ/;v fRj5]]ttv×_~ƑQ Fz{tw~\\]0~ eګ/457쌗L$MI|Glܸ-[2*<o;0 zzZF&\]D۫HǏG=Ě50\3Ǎ(g4U}miinU$&6Z[P(GxulٺAȑ\uՕ)-7߈(H̞5OZ[[Ahh(~>H?0Əg=Gۇ#G){Z.d󟨮ÃIDEEҋ3&1oo/^zybc8a<11ttttDEFRjYЃOV_3Q,[z' JEll =0UV=鉀@LL4 #1 {\{tuuY7 ?n,*3_wyh{O!byٳ 88g~j,i}'MDFFϟ,xzz2&1X&LOtt4,Y("##Dkk+#66X"'`Lb^[gG>GWW&M䒋Y^|#Y>K~ j>]:9 /H3KyG)o7'.$WZP ˦Fc2K|+0?{aA"ҋ)hf0&FPLxR֙lO~o!9cmb]H cط]"ntGe FXX ~۹Kptt~A|}G pus# 0K#! @@`DkH1jyylݺ o/OnB5zRdx3kAb>m]\R8x}Xhո&2s?:.cxLGִ ̷NJlQ.cEYKL4 $yѝZf X8 HLMJjڴŋuJ9sG (8ؚLP@bBo/]j/xi҅j[o9V˛9gZ=̙=ˊȘDk\V ""(?_nq/I\xхVZܘ>}wK,:9;h$|} &Md}>:6Aw 3BTTZwo'NNNV=ͲeK! ۄ0 nCs˩P(4q"V]wc +d <7p|>m*|G%::^vK`` nnn̙=j !yr32B|}# \uh4zz"#"xGY{x"ddex"nU V^ af pss(]y'Y>M@UAAUu'㻵b՗̟7#|;HMMC x{{[eKD(*+[nɧŅDGE!>>DFF?z-[P(+pqv&:* G\]]"0 o_w!)i2Qlٺ{Twfcض'y!G1SdI\%899MpP>>>T*f͜-[ CPn%3 MLt4!!DEE`<I^=wš#E<}?ѴvRRBza57.H䢤(>ۜ(oi1>.,p,~>DWeSs с &vf8XZR y$I"p5MHe3K`sj#x_gsQR$u-dְ`($IGGp"X#sDž~Jj,)`_7Ϧ"=t9qN,[8x9^‰{֖f pss' 0Ю=ΖSۇG2ockؐ2m K9VoD?[rBlAZ浒5iVরcؔn ۆm;[t-0.u)2Pfg ?e=Og?Hv}Hk_sW o\rϱdéfϞ[w+UUUr'%g?Ѩ-{mA$ *v= j`/x! Qdۛ36 ow'f 2e\ |K*&7Ah啯(nV4ރb$xw+KD7dZ _n?D\V|@O-?4~ŗ;Ͷ IDAT&dr$1ܹlE|@Kg/ ͋}(XC}p*~=/ӏg^AN#޺w<|ǟkY3{,rBBB; GGG1yS@m'Cˌ뿫N$+@[&#.&09(atQ !!q3:u$6R1ҋ'yٌnDQ &ȇ_d*%NRQߎ`ő .öaP'Ӯ[Ӟ Z} <±fl|CzlW0K5}!޳2س+ d;N!0v`kµ`u> ` v^S `N/N7goZ|~秬,oŀv.>oo3l>OduZx;i=xqgQD8g :?e?8`6IVFRQIz^NA*(ގ[+ [^DA itm9ˇlEyɴ .p_ QdZB0j%>LIt]y~\<529n$< qΌ 5H IVo Z:zy{m*K" );X<9Һ_Ց};;rxG4.c\T#ܝY:%-%#*؛yo KF,}\qU#{v<о4M%O].ϖ ?du}}@?YyeL4Bѣ|Z3?C&)0" a:3ˇ/P;m Kae[:Q|u(T6X$x/.7`N,h? eeGrAVK9^]U]ÿ>7{7MM}+p|RL€5};qvvۛ_!LPi,MqtҶYy?|?ޘxtni/N?P'v+x9>dǓَ#1=gBO~+~`?v<^;6'$[>mgiw}t]碿:]DžKu.|6AUqTֿ?M Sxn:SN~88>Ӏ?o== N,-TPw()-%::.Fڵ(fxZ{vQO@ߋ?؞x]g0`oş PTR>A7L鹿pҥL:uOTpۗ?{9syq#_PTvxr]SSCu9N#~o"q8+08|06{OJm>+d¡/Olփj ;v¼ysIIIad6ou=:V}AEVUUQVVŋ0%^xe:?J>#ZhP]]Νɔ)IysiooȢE EK-={ʕ_":_ƷkQ[WgPIY5u$$$IQQ;Ǎ%5-{b=$0z4 Y570?``[X\MPf@(ۂ~ʡ<-*xI(XFi_\ xIY}|^a‰d[xݲV$t:Չ$WWP^QACCfɌhZZ[$ +*Ng`0PS[CQq1tHHSRZJeUA@k[+GHq455 pшDSs3%%TTc2666i:::GB$::);v1j{9V^NCC#6mcMF)-- JJJ`4 uUu5mtts1wSUSMLt4-7=O;vMchoo9R\DcSc6괔WVPVv.$K'"{%;;c/`YHHQ\Zѣۡ2**+űhiiX10L44SRZJ2*GPdj0M%355Ԉޠb**ikow㣏?^OEh4w\W/X?go91YlU{vL=p|=8NY [/xauw4bXx!C=?q<+`,˯ؿ?oofL3xgpvv梋.o#5-͆69͍-DsK fEs-[23x oԄ( 12 7WW)_wFk[+...("..BTIQq1^z1==|ɧ *ILdz{z0 }CvNI'3*<BGGGjNAށV`tz=;qw<3/JY#ltҲ6iϱM10GNK}HRcA5j ߆߇;vY?kxSD:mad% U2HĦ[پ};Bpc97߲3O=AyE%ggyyO˂غ'V "Yټ?Q-ysG؄N㚫`4SϰرW_{?|{[%,c[8RQU]/'66y@oOw}&L4 ]SPDFFrۭ7ͷk蠹j})su6ƍk͟g}3j5==WYl)7^v~w# w}zcǎ_QgfƌhZRS߸i))ܟ@uu W]}W]y^y%99]v#11,V\]x>HOO/-̘6/?:/wv-;v SO<oR@`m$T*b"ە7 <Ѓj|mQ(<gʕDPRR), Y/WT$f5:ʎ1axfΘ.if$  % $3JR>GQT@Aa[̞EBBROR)~$ A6Uj@T(,~Kpws7r(7B(? 4G;g6&M[n"$$4veن}WI7- °z<>`I %zxxc@g/ M}(XCxi~8YYf^(vgd2r*FǏ榛n`Ӧ8r$7tYYG"5-:˖8p࠵JKpsw㖛odLb"䧟wbc*νsh%ڪ[n%۸ᆍ)[B%7 ~@/B;0`p[%[}{μg0z@Ly93o _>(:y[b覸K/8]nz+[Xs֭[OAay<<ضy~,Fd0t(/P]Uc? GsKu\w}n;9ӦMe [\}9=~{4,YJMM K,3N?ӿwP'ziӦrg˯PQQiSO~z?)((`sϣK7Ħ͛ygѻ|gTWP\T5 fٌ?,Ιg~JMM-fU 7}/8$I2VK$KO5+h"C&>ܙY`ҕ#MW={bO`r(ʚ4&Mt ByOckopݵ 8~?:tYkZ=7lݺ(~i()){ŠXaf`(cxf&lق(d@ `ڴ,] MӘ9D23QU n*xe TUUKEUU\J~^[lawS› TUWWk9nt~4HO?6b0ǺPܪ?R#=:jH̓q|ӒN!XQ v=?T COd<|rX[H6(Ud0pKkk+yC6*+w]w͈#Hz@FF D-6r2;+*ybedgʐA̠Kcʺ}8//M]]B (**$: w(y[߸Nuu /  [u _t!&R4443ܸN(J뮻)+!𥤐2x0o6).|.Av Sb>lH97t`RSS FUU5ٳgmmvOv pE{‚B[JK v B ?E=ƎLMM-/2yy 6 e_UU裎"7C^^. $c j:b>{^f>(UW]&^y5[3t}zLAKJt)ㆲ~:4QMO9vѴ3­x2F;0H(>o1%;+ϭU,.KO]Ym_*cv]eW)X4o<t㏙>Xn7P]x ?fLLYpnf/~Sjjk9r$ǍaРA=z۷`s%jjB0'MNN6_w cǎ F믥ٳf2y$̵,@a\~%9w9߰UQ=(^/}BJJJHIrȵ yکv:9s)> ]J^ Ƶ̻w3gl}H磡cMc裨6|Y$LqBJwx 1B`Xg ˏہK={fe8a܉F?7rD9|v-EEyVVLrF-7[o/bȐ!]-b@[oG(} ]{ Yb 57ۍa|qcT:B t+Wf7߰rj XRB †狿mvSZ:Y3f0s >l YYX/~CO>~6bҡ`!ػw/xA:t(..\fx:uuu UdtTEl߱EU))@N #BEdffnvV줹C"SYY0`0H/k/tfϞ͌%ʲc9 lݶ7zʕXsS]]193ӟgK3oTUU3nX=,d2TUe Yp]ODӉMΗs6~:PuԑR0  Aiߵwz=4~4E'!,I?rK )e%wCIiQ69zp,?I},=' 1!y\әz1Q d&MMiSsԨoی3:_ZZJiiits'"~; A5*:Ïf =ɩ;>eEº\.~rӍ㛰f9TUvrHsӍ?橧a_믥t'3+t\.YYLz W̿?ϸ]..bƎÂGCQ;UK /5_ /".+o`sϳzL>ԴH_*or%S]]ýٳgq󨭭c ( />g{+V0p sYpAgz?$~.ﱊ* IDAT~_0= sܗH ! 7/Ǔ9;W[kB؀PJI0 R|4uR4M'?; U4ϋeL麤-3qztti VU|n{WIIHp !y\5M0FJ#"uTUuqG3VwrOWjYݛ3aAJIFfE}g'=y "ۊz™B,E[^rbyie;j|.<ďu;tliT*sgݔQvŗ2 8vp2IR"Pe:x.nG)]ZZZx1s L6S?}IJyH\ ~~#/[nO]RΆ۪ڶoBjـrY#9'BQxՌ--`Æmx\HE΋o`@%+8Qdy++8a`UaC88cW7y 4u {mMAӘ7yӎ*ྗqQBAN:GKX^ j[-W:o33eQ%lô%=g_K{0DYIgnk⒚o3Pѣ _Reꫯظqqyҩ 0Pl i aT$6=1ns@qY P@!]„EyZ;Bx=n&#^'3:tmMTB"d{4+/y clJaHh dǵWH:e'We@6(t(Z;Um#Q[mce*a)//.NW2Ҷ(N;;n?%75=A~]dd$}*L"RҰn-[HRֺ!BdrC[-MZuHOPB0&-M(EqJGg8T5l܍*\6wk9)U!`gV2Ӽx*iIKy^nb vggMW~ғƒ6Y#;҈aŮht;8Ab`Й$ gM0cME 4FHq]'tqJnGX4]Ƨ=" e!jgH%bVхs|T:1ҵ$OV$u'1noɡ' {M}a5U z7zCklK̤(Rv0,(2;+6~{s&)W-(MUmcݶ:Zk:|t;ʀOd['3U~orR_+KOd@;[FomO ذpTlDO]1 jA XIKVsG$a[DUzUk18I01[\IEJ`y8eةI"Z'V|/e|aB EFڎ籥\IN 3~ &R/XϮt[d,[LR5rؕfgE?3 FJ'dF%S?IsMS?Swdh6q)uG0) /`*pGkJ~_,ᖥ5P5v+`hv}PFGF1%ڌ$ ##-0#G[VR;p)?OOAD jlmKbTfY꧳$al؈D;;w&n??~zWC5]Y[~uWuu0}IKu/qP&Ou,.KO]fw}PbxlWF $|L~dd{mY6vUĭDVu5 %S$-D,g*FJi((026Q'3!5 KI3Y#faCn^ Kwٴ x)`N4~("vk5 K_wuuue"WtM= Oc +',\298Ch*GQ$ZJB8Uk>vUVά8{o=/pW +}u |>iZL?r9ϺF:]}0r˨eALqX2"wvv܌<iiiA/k/g%Md3~xx^e ia iiH_M=wommzW3HOcwƗ؉^/y.t[ uзa֕ދZH,w/iueM$w|!z"鏄#m˹N&WBe9 V0!H]C pw=w6DKp]ѥxC'BZ4[7GaSeTf)]`nDDt!IEɌx^atf@+W{°R<^]r1e"u_[newtn/M K)lC+C /8@nҲl۶_x~[`adMwnp' D2a6~ P8vT[3l]W|v֓vtNcs^ܜމ?JSW;CiWQa޻:{N[Wwe+ĶՁ4}m[ݚe7 i6CDw"&l;i"y5lYEQpT4-i[W"j$:qi&cd3YW"I,;>%.UAUaHTP#4s"r’V~N7.d9'kNHNum --- 4۝0SO9_<˖-[y).*fʔɴ+pEG1~x233yvB2ȧ'z#Fiƛ[9g1ed\[ /Kq,z=B!o)=k&˗0vhN:i>ֶ6I99b$~!K.)1c^;wV0q^}uÌ'RSSfϚ˯–-[8qÇ] n_g3VɭtUQt]g!lݺOGWv !vvR\TD=Zq+ѿq8VjFCU8[Q65U ruc5m_I[[;z3gsvߒuSUUСCYd_gy7oa3t>cFsS3uuؾO>I'MÏغu&+СC((,DHף1o\jkky']OI?\{XR¢wcIY%̜1)'&''La4W0q,Y|>())w1vh?-_AsS3}B,](̝;'|K#@Ow0 ] kPxhii!+++VQ__AC" RUV23mj73Πh !j;!"}#+i t5Z;ujY̎&=iWP !PrGS2(*=?Y(?J~p쪪W_gW v҂?ONv6r(..B]Ut:#--ƆF65i&x JJJ@ee~P(DSs3dgg(ƶ vUgKr˩@N4hoo#IOee%.)**d2d`jjjQ]*߽Þ={EUUBVViiLNN6TTT*pT'| <0~ݻq\deeQW_ϗʰRy1233شy PE}|)Ivv6˖-CJ(/n^dmB!wک;Q[[KAA>Ǎ'K%ix8{w)'?sadŒK<_uqꩧp_J '1f͞o~;$%KrlRKjjjPPXM|Yn=sO?cXi)=sYX]Yt---nƇ}Ī՟3e$jjkIOOnn+BF=o%Y:PbQ":N{ZYzRV'u710A ]DyHkP0BID1إ B x8E bY).UAG:>*E0f (@QP,@z.!yM"@h?&0.!%u & 4!{:ᰱjx v"T\5`]q0i$#X8Б {$feԈqMi@CruPWWiǷ'xW]1.u</[le7P_@/ VZ̙3ض};y,[|CGGYQ믿[ZZ| 5ю6?%%eøKYt9+,*dU+D{ IDAT1qʆul8`qK,%??21qq=`|KϿMV c !тnR)>t^Y{zf+45Qd&ڏrhoR8eҼ͵*Or㮟m[9ܳ4h0$mWNEU 5;:\Tii4\x]*JK8%@'5;cv kI#U7[t0Mp(DʾȺEAyyӧIdfeYl9' @Aa_z)=;wVO   rXr%k7n>.ǟx[ b׮*r@?g? -O>eŊdeeR]Uͼysϣs%?(—_}ř+7wO#3Q#71GSR2/?~yϹcA]raҤq٥yY.C ε3d`>ccERعju(w޹+5j;T~?DA~>`\5:223t jgŮ]ds9tnO2ߗd$Kwte} {0d9e] ZQ!QKOfaDC>%N>L,J; 48 !iT\±4HTUbVXZʒV1kPc)BAGGAK4VPhRAH EQUCD: iDFggƮ,}JX#w4a'*^Id@qQQ \~٥),,k SO=/"KsfÏ0uT&m̳qSO%\CUWsÏȫ/% >cM#GOyl-X_o7x!cƌ=M?%%5jBrQ^^Ɛ!Cx⩧z1dgg0g,}qә8q"YYY 2K]EvSϱ~)%_?'6|ؑRվs6юv5Xj+SzJ?tJBnvPWH6vAI@jm.t]bOZJEBiQ HWh(B2tZZYH.V7M]zֶ2^[ '@ @QYY 777SXP1chnn?̛7-.U0tPW:::x'󗿠JRAsss|{ػgq93}+سw/{5 b9TTTR2`5kyb< ˥MVf&u\H,b4 E5~ؾ 93UI/4 ~)8MP@ @VV>Ǐbg%Ǎ%:QQQ^o. ; ںZ㚄7w; HN7Ǿ؉}mW`jhh$i2j"5r$H+cB:9G= IOY~wߗ=e)W|Yyq&b 0yK{+8;\sAhխFO$ t OWdDnq2N!|<h<5g^Тm?~i@$  钰u¡aM'+cW&EkdqԘ d_b\i];+*5rԩ{rrrhmmEJu#;;"}={&xAr9hĉG/v:|>W^1x|<xA<efZI&׿݇izɼ"qر'Q1yمx9ӣS$/r/uy촩淿~#^r~ _R>Zcz{;vFCC?F+8ԓ_yE]:KGE,HLJ= E_~%}nHH@hHt$4}tMv tPP].y;bL6+bOJ3x7̴/J3{Vd,觞QO]9/'dᒑ hl-kk?# +%+POw^um[SQK;@ŢȄJd7}sG2M:ZZtR:i>1ga:U5)Y[a6M[bD85gKKe@G[Fuit%yiLX ¤i^`g'HMMC%WUN|Av˫Q>4ϡ  d1Ms*fHԘ9J !kՑh9̶e!+@-$1-C8ZCVdWteOV814&KKx}k^'n5@=U:[>lQ2uF}՗`uMaC(Bkuje`i)cpN4TEҶ9'ON 55‚HQzkaԄ*hnnf1S"+36jkPUb*:;C呞ƞ={hii%55m߳Jtvvjjs&++Ӭwhuu P\TD]]--9dee!QA{{;YYYR\\HEE%EEE惡lR: ;ٻ !`QܵbsCUU5:ux<Z[ZIOOM!-Gm}=x=^;+HMM TڅF7kA xZ[[dd@It[8X1)Dž"}~#/\{%ii $>2mC@CS A[g$2&Mz:5Ň[UQaf>l1 `0)~7{5cV_,Y0V C̙… y衇x7_`>Jy.>3̲IoXp!`:ӹ馛J_.\ȇ~Ǜ8q"7pw\p~;'t=>9W_};3{/j5QX'} —pT.>"+6n܈q p0Pl < 6Q9LGV{E z ##a~0(Y7]MBJJ =Mf--ͬnT1kg-ͭ%fo-M#O`ǖQ9A뷔&2m= ]II^!~֯_Kծ]dff1 u!mBV0[p/ga0jIH0$@XQ#No?~kcM,QÖg~,7VW/.&_::4\RIAk0i]>}Kfv|2hꬣ)auN;4wpѩgjU(n4LN@Pea-MIun!ϫKA#\WekfpUw !)|*SFf( P(Hx$.kaº$Hs{Ps\*yy"ҀСCgiGDsrɎ?5G㲗z[s}*m[Ob:01c;)Qtv&YAHIP.rЍ/De=zKʴٶw5^ SٸmR {dRީ͝L BJ ĭz&e#a\J fsx"禱 4I8KüVbWAtJr=*3J܌+Hr\: M v".UN4M#55}!Ǐ;O4a9Xtە=GI" mZhnmaYd ^5ku`>V?KqERR\董z=,u@ 1$bIcE1#]أQ4vX}Qկ~Ń>ky\vel߾oE~b{OD<#ZO?(++'? ^x!`|/gΝ̘1{wR;fϟC=DJJ 7p| 0k,x;쭷ʕ+z`,_k7|37p_|gĉ޽zکw8zn_:X*~nQ!-}ֈ:r@8aDpˆdVZA/nΚ)(EnhTT% 42N95:u,lew./Ґ^-a(D#itIGPtlyIC+Qƫk;ԡUZW F1/Ia:GjNCE ]1ntu-i=;I(HAdR)HD4 \"<ƈ c;.u²BMCu ¢]j)t\"Mj;G'[aEM H":-,~sXŰe\}ݵxR a(jL)x,@h"DUUϟOZ^x?~_|iiiu]466r5װyfV[U(.QmB92u{OIK՚Lv'I=JT{y J~hWiTwѩuQϰ7pUGA i*3T n5@^R\e78?u}wdqETPhF1I"f&c2q%M4*FBAf~ϩ3͹rS|yyWN֣:O%,db 2AP&p@$8 m: OTu%M<jkK ֒b  n6͇/{}5(J=SU#)C>FK $>8"Sw:$C]}Ou uO THRAHN{c3֋p3n8V[aG^mշ+x<~CQQ7|3< o6箻㯿z*++)(( ![rۗ5%KPUUeoYEk˱SxG61;GCG{`q1?#9<:tKiiJ041VEU In%Fk"Fan\DAt\f^S:D E[{;px,fH$u5R'DL(i5N)>sf7Iԁ%~?~}Wa[Ze9 ɾ gBDO!իxɬ`W9?G@kk+.wD~cիζ;?}ZJleȶY U pbbFHRdz (&SA4umm!4WqI8u=i3D%L4wQ׳3e~GjkzRž66NWR¤|?Z ((..EQ麮JD"MPU˅ RL&Jiq߾ ?J=ӤUn,WZQBdp7 [zi2,(*Bq# q}\*.7UGw(\.7>ƊVފ4ρXF(QW`04'p?uXreBs]wqw. cK[8wMyy9'N4K-c00l0֮]Kmm-ׯ,['n555ܲEUI%S3UUٹk@h,IAzzzhjn& `ǎ]ē)qܜ\7́p݌3UQصk7D#F, K1:;8i"ihh2oA0 j!ҨNCc^;vR 8I߀9[ёHF]BַT ^\Aע۟2}]$)k8ATwIRO՜l1vXf eJ s朳  m:Ԁ*vT-BiKc}0Sk%m}ꡦxslv"n&>C  D<4$dBaQګڵ۷v8f8VU[L&akv} 3Jǽ-=ފ}~RDžy?@RjRcp]gaH1*憐\>pc`aR ]?>"-S̠?fϞȑ#{={6`e˖1}tY|9m@X\\̴iXlO~߰i6g~߱m6^/gϦ !˖- um(?9 _KP 1 L% G"ZҦ@͛o---6hdT*Ess3 }=wrn,p6p]iFfdevÝ9c@>F8ޫTUV`<{ww+Ғb^{ sb'ʕ F4sSRR̼gqFWTpEl߱־O]AZwYR\Eks.@2lX!9P[Ǧ͛<˿|0Rw߿C<7 N9bǚ_@ R%ƏcӦc޽w޹qqi3o x8zTEM10øVk+L D?Hi}I.:n"; n T>/M2#k@,UQ!MMMQZZ(YKv2!DQZ[[ill$;"aK1XyuH$BJKnz$R]BA"(~!aBXX,ꫯr%IRTU%5UU1}j4% t`G0DRZ/zhZ^0|^E\nBeB ?;yp nfٺm[nX{p˯pՕWH$uK4FQ|>H׋fp\A^yU.b`oC957t]|)].TE8C0 ]AuBnH$L<ףH4zD}ԫ0 4$W))I$SD1ڻ:]o#56lTʲC:K81#*%{ n%S^|7aOWT*KWSSƷ,bWҜD-t[BqC/( 'x"'xl̙ɓ'3yK,gff|Ǿ?3931l-Zd0A( 'NR.BN?tN?A; 8l'L {fּ;g̙V#14yQ:x~>hYcYfk_]<LuU˅"J=]=RuB(*fG+M _*-էVi1i6t|Th.Idʐ ZS0@iA!20A2$$ pz^IAS_kλbT/ӌ 6픔 ǝ :A0~ 1jϝ#I&#|>?dfd 0v)Xr9 .={͔)S??Q{T*Wߏ릫h,F2;zz{Fdffynkc9457Dnw-.ee 6fPd2I2$*Tg\tхw I/tS"ؿ} 1E >X!۶nc͚5\..BJJJlo+hFMM 555x(++cرھȜA1}~u2GPLջ(0i*J!XBS+ߩ6`EU ˂*~f0a]]]ttt( SU0x<3j(zzzذaHC݁ JSi(:S($I^;wY"~< B!~Ǹxhl4/Zt5<ԟbp L61N,+W_́H)9kΙۿNƌ… _vq̙Z uW]vkoIKn$o?˔P{|N=eQ3fu9s]e^3>GKK+^ ƌMG{?0?BuM w.?|z5淏`|FvE /B/76lČ'qckk ?^s% iii74hvGgAd2I^^Əq&f̘UπiNSNa3LYY)\pX!͟)i4Ő{4Of]8ZIj)$==nifӆ :Pk0 Nf~zj ff2^j ͡u EulS#B&gO=oO vh9—5Hx4_xH+Gy!mJw 1h!rpg NPh}C].8)l0Lt)Qt P Ӓ&\02".[!+c [rٴp"Iӷqha@>> 5]GW%:i&e\B㿟|}7}*iSi1,TQ{{EQ_3b &Zm=dM:3Ig2iз9PINz|%%\qbFbٿ߇c6lOZe |"|;+~Mff>[M~^o-q2iz=\p\t'P¤6u $ %(=?99999 Vzf+YۏGcLJ @ѯNB"u 6!T{_~ϸq(**JsjDGI& lǘ1cWMf*$hmu݌,as5JL{CD.1;"H?68MSS-*X߼ip駳vZTE~m9~ߴCƌżys'WQ^^Jdys7ޤ3gw֬x*;kpg0iD~7n,`p?W^}͔U0u&ǹ$Y\zńB!le4nF"G~K x,FUe%gq Z-p0IepƎ˖[1}:[n埿7?++iinAt8@SsPU(/+\&N}s $S)&OȴO$3#Vڞ3V|)S&3qx?CZ ~QF%{yqelٺw{"^y5N9X@Un<(HōpUϐģ)vu{"IRr6KR>hJG/1M%xO3\dt~MC4צə̺M_ $+zUO?m0I09%֮ {븼Y4$jJ sD_@.T2 (`PHMnѹ?M0FM ,QCUŐR*4fq @)=Eovj˖ -m-agg'w7i'ȉ/~/tJ %}zƃ,9'A<1u^/wu'  ;n |>WD"\*P;m4]GUni4ƞ0U+._L}}h4SLk_C C `̘6(L8ȅE! !`䨑VSK.Yh妯`y {FĔiqIUմ3QCc7n8M) Ai!`p8̊+x n>C򗿴Ϥ~o͛yMli0BKi$) >M'Т暯PW[dzϭfWZ]]=lٲɓ'mB^gpNep | (?//@{τ 9xoAwHR~6l܄gn Fy9|k;xUkrq7oi[oҬD"3kL\.cFc12}qP=3=NJ֢ٳM '8_ GamL HR;LjF"rB!2sXt"cJh% H7. Rq#UTB 9)߸ZKRTe ~ow4Gۦ"O_9g_GPtr,m&%x9G*r;X?FQS}N $oőDƘdأڠϚ#H bn^Gv^)nb/G )F9A$% 5RP p&u#ۥ*tvilnC"ͦ7Ad^sXĥ 2|!B7n M@h0RY25$UOn[ڧ}iY0hf@+B8%=p?{c$X{PvJ8(+x Ӑ', w{<'P #0  s{}EFI H+qڅ?eVWigʶےaZ"Bxh2{iKH|^Gw9zD8c;l@q4[nKpY1k7nf?|X|9۶mc޽Z &Mޖ*fcVH05m~AJk.V@%O&D"{gԾW 1:xtE B(A"e򑙟ӆށF;5]$Is0ƸBtV&g(G=SboYEk˱v#2čGJ;=wS,gc='u.i IDATLtc w BF"D7t:Z:SXTE!7'hj7ue(E` CyeOXWU;vpEaƌvm|>&L@GGOj[ } 1icѕQM0lH).zIg YZAh`Hjizill1NkPm5j$W\q9DsΙKQQ5j$XpED\y2lX!%%%~5j{ ^Akk+&NgIyy%%TW NPFZ|L /,`S_@~~>=(B0v &+(C̜9cO@"ٳy&`;ĩirK~];n,Ǐ# pDɂ↯_?A0fh]*Rr]wR\\(\yj9L hlldĈ*,YrMMr,Fi4 #3î̓gR^^Nnn.^, )).!J1rBW.YÇ)**kA9Xr9tN;'%1ItBjI(B(a4J(E܀`*Bu<( I ֒ 'P7 2(Q&J&uZrkFݹMӮyh<#!o7ΝF_ל >hH$O~~#׿i{~;:_6e}{s=C>z{_禛n:bZMӸO~rwNGYx1%d3XeZ׮]Kvv6cǎ=bӟ; ;;ul^yoE͆ r@yK JSeT.Jm6+0lݲ׾g' :DSU]Kr nK%l BTL1(;W~/=vvq+{"D:=1 [A> &,-)uQQ)5D2ν-D"]XW&u9UЬ{ 9T#B_Wys )G8MȤ ; ,<do@ IJ>>}3$H!^/I?tVuJmΖdВI)IR 444W_;ݻmPh[u#GOBnn.VTrIkI$DQ')F G4RK-ðL;s\|>,Mjf6iFYYa;4Nc,wߥN83PYU /(pܼ\9HR1|8ÇW7XD7&MdzTǽ@srvRIqI1&$C!Nt/+7\pddfRT4n@2eJ鼼\frrڻfgjQ0]|@VVmP@r0q}?~x; |;cYU:)6@ZFB!B!S,J $UzXQÊŔ)>±| `JS.UυkCTVU  Ato7ZPWݐJ&$E b}'5,V0;a޼yl߾K^mۆL6ݻw3j(8@QQ#F:222;v,۷o'##BEaǎi&444 a2vXZZZػw/.˶a\.cƌ0V]] 0~xvEFF,_Yfqe!}Ϙ1cذaT={HUU6t!v܉gʔ)466R\\LFF[n%m6lقblڴP({ ֢EahFUU޽&=z4/&Jw^uL:L۷D"(̛7,:ľ}(((PSSáCKR]]MKK~ǃ7Gymޘ:u*mmm1ǓH$ӟi,Y7qF&s͛R2e۷|IDž̏PL(1}Fyi KC=rm455 ΫHK7ԟz{tq~~`4$ca}RZ4fNdRx Hj$q:dCʠt)E$8tc(;I4'#(jYS_*hW-$6ma23ck=.K DZR!0?xgsT:eO"^c" !Loft0h >c u`0YgŒ%K8묳Xf=vrss9l~Vk׮1.LxEhR]-t'+7ČsL&mɨJJJsailb&vt]ÐV///X?#ώ;Sy'WOϒVVZM|aOG ǔ#I 4̆UBZnnAYRO,%܉AְR x22lPq#@HJJ=*!##ÞߤldO (ֳGy_~H$ԩSyWp\Q[[(޽W_}P(Ķm۸yGͥӟn:+WlstuuqF.]Z۶mzN:e˖΃>Hnn.mmmr)Q^^͛kaʔ)?kkײe:oxb>C&MĆ }.rx N?tVZ׿u0uT$ .?)/xG^o_:7x#555رo!?8X>+WrM7j*TU2$/" K8oۜtIq.rN=TH_6n܈;wK]]cR)ud2ɮ]Xv-k׮eѢEtuugva L ufH? ~:i5Q-(" }{L&흋`ps&banTŐa>EcH80+LsG!6kk:/B]Ҝ/җ:HsCͨG<$4It2r8v6:Zfжbi0RGAODHttN =сK$ z4Gpư٘FRB01 sha޼ydffos7;o#zb{z|+_1yd|Q\~dddf}Q:::20k,z~̙3ٽ{7۷o9sꫯRUU_00m4jjjشiyyyn/^Lff&?\ve׿f֬Y,[+V &gq3gϷ /$2 t3뮻jjjx饗3|߰i8`:<"JWyb\#Q?x66biDQ2hI@g0 &I' #3BL+"5 H'YʓAwva ӥSXĢ'ut2?kړ$Љ&5m}%XF)4^7*FQU"ch & 켥3.ܜև}LootX d2cF;-M@;4%쳍6ՏfNI H))++}.ƭxq󚪪,]\&N̙3?nhk^ +UWdffL4C>EYHCikѰBF4аk7eǑT&=n̮P IűXZ螞"RJ +ͱXH$B<'JٖU(ƍ# "|A޹Sy~Jx㕎Tu6O7(BGUtTUAWT"(&/XN>K*hhm= LH7jnkֱ_0Hv -މ+fJ&64GWTC" $J:S* ƽnWe` 8ikk~ńT x^n7d͚5|k_5/ 60ﴵk׮zI$3p<&h0 ,pW\ڐi*=gtO<$.3h,ڹc'YYY(>iʹd1v[@(!i;@ܺT/C0$_Oi̚5}:rssDH I.gϞ=sq{B~$]]]!0agy&̳-i} uP?w|N`lT4u IPf쩧JnVoZC8V0Vz[*&Ҽ"Ijwm$LQVϫ<(@~<5kp\\ve< 6  p]w*-' yᇉb9?+\tEG?bȑG|u-`!ѣGi}>#d̘1iey<x 8vַEnn.r v<7`y uә={6˗/̖E"VXA8;'mfʕK"୷ޢv0}txnvFAee 0rss>|8K.j'77{O<7x&Ѩ᪫Q+Zָ>+~,\Yf g˗!}M cߚ  ϜTAqz\.a0&3MxD3! p]MuNͦ\ !A2OqѤNkCU4$ "a,Yj$EښxEIj69FDG[0ϑؠoHioHHbdggӋLsSS3"(--Lcttt d\EaXa!RJ#u"^/mmmtwCVV& \.4Mx6lDho "?/P#mI22BmA 3ZQx Ԅ9V=6Y!1sw;k:δ+WT*<<@t*F[}~?A"ewbxa*M;QKGGG0K&+xvK-vۻn7Ǐgĉj0|>nCtvuс@CVfp%xh;؄СMeevz{m8Hno*̰2*-fz?r4L#3x!UEݔBı[oJ(..[oTU墋.BJINNͳ._ x衇H$k˾ꪫ袋i\s݆0}tb- zhf4Baa!=xU(vmv?,X,.]J86֯ z)~F,#Y[6+©jDPO>ٖB!z)VXAFF~;.3fɓ;Ύ33߻W#!QU(26 ћmM'1|'7$8. / 6HHT@h>wLs*}Vs~ gFl1O1+މӡFa [f!,PaYSU2jԨBػ*K;C@_$S,YOzV7U} ~Mwu{g7V5MaZ) DA ,h :V ݺ2v TV-9G嘥=?1Q{Gәa#@4?x;;yer.Y³=GkK :yoΧ?}&+Wc⤉?s?=+|oo夓ҥK`U%~'е{7Ҕttlڵk=RmmQVVgA(;ާRrDS  T*YJt]ww+!師D"~JJJa^ylقi38a;)\."@|\yTTTu\z5.lZz@Ή;߸S}}稫㴅 ޞ?M7åz50 EN-[F[[=V@e> t8:B+HGkMa9 Sm=4E, RP*-%HfK J 4E*(m2!{5B!Zvmc>TB "BAι2o 9t/RJR$_:W-kM󹪦N^λQI[H$RˑRRRR֞ R(㼃<TK ʑH딉7a `9RF,b3dt. T:zX~ 9(Am :;f)?4HhrjvR5hnn)$N7:p軤}晴M@46lxX4?HUUx]8SBq0m]<̊c%iX;P [UUuϮ/~z g!6l]hnahx]Y 9ݠ ~?BQtx7)//s9]:M:o4\r i)-RHt:Z"|DQ***شi63'P0*B.cxxUV~۹"u}}чyGY;$088u}X,ƿ3Z[Z#CCÌ @ Νd:w3TMCS ~fvŊ~zmNۄ \z%lڴ?<3g+򫎔X?eR**+yy7={K.ᥗ^>wP;9#\qL}bZ[[g?9##|K{W[ٳgnݻ<L~FwMWw7g)VMxinjfS/}.cy(p1ݑƄb6a</sZϬ*T-H.%%F$h  T:&~P BHV) Ua)D$rfR.]t*ӧN @Sc; DGI?ʲqEW#|(GQC?Cxқw~RҸ~jܾ,MQU?Ʀ&z{zԣتhVQH@BX-uS0Lg d2&A&k2fMG2ii He zslX~QN@YK9i 4vX*(H,K3P ?B.,vY14^|y֎/wlo3ne!ȔH ZxdcM<3TSSSkY qܣ#ap`% 2lRPGQQ^NOo/7vǵcY;. pu288`IͼAmm-o5ۍ[BU5%Ï3`QGq ah}ϔ̘9or3t Y>0oAB3o'ΐpgL󮱒:u0я~DWW~h4Jii)͜9sijnq /%WT}6K})"0HɌG3<46Yg~I&_%wn]G{ky-[6gi~rg>3+y z.9RV\֭ۨdbD|sWu6k,?.pB,W]u?Ҕ|xꩧ&Lr_駟agg'_ A"'b^e˖vz>h5ծôiSkwHc\uŕ44sbSw=7pk/ݻ,}мƛ/ ~Lι=PH%>(ЙB73CdZ FQ*!Gг HE" (2I.(AZEK$0(U TLn#]V.U[pk(M'{>^o4Z {;+v$[;R釒~u9PA.{d!à9_+cy#l 2u3g6 +M>H AumeLB: :$3:ttvpYFb}֕m6J&:@Q@T C|m Cz3GP_F7I1VE `d#[3Y0 ZL<ݜ[H*8EtN` ^gDϽя=X| sΟ߅a}q~jjj}n~"g,9<կ~M }Dx<? G~^24w.[mͧE᭷SZTТ ,R^VOz'&KbC!N,g.5k8i]wHXc`ۺa,~~=ib2?\{wVbH2׿5̚5zNO@JkECE UX*Š0iIPvv0M)XAoMUnށ@UޭN|dYR:ͣX,>2cD&a{1 8_kII) Y]GF0LzZZZs9֭_Ϯ]9P 'B_~CCClܸ_XYgIccMMMY|B&MhYSٳSZmP4w7l'9s**'pB7%VCw70<<|/o 9O$8qꩭeԩF]]-vFuR4|ap-[h0:x x2 }'²?rqio_߁)% hnٮݻ" 3ycƼc?q%:=w(|p$FjC 4tC@ʐbArYI6YQf 7@!΢$r(\yja5GÁHˁnoЙ0g{'p`+?9V|Uxp?J뉕5@ȴ_/l'٦9]L39i_SiTF|hJ`((B 1 >ڼp| aP_!wL+2Hru'錴U `1m6!fZ4M;x.:ܼCGޅ ]}L$/3 }ٝo|{"tZZioo!g?É'1 )S&i\xR*+o8 UC$ PVVΌG_~/.@ @mm-η>>vnX̝˳Z|| M9OWRRRp0 ^z%2&@ ɓ)կ vuvb騪icr SSSCm}=CAΞ !0AJr^vwF ۯ13?B !,8ι2ywP(2@T*E2$J:lD"vNk׮Sz)S&²eM!lVӦMga4g>|8:4U#glܸ+VefZ'4vZԲ=6W!V\+˗#;ɓ'QQQA:tSS#l!N1sL^|%\իWs'ꫯl2ۨm+WQӧ,_g}믻mW^YҥKĔb 7zP( ylq뭢(hmmaY˯ByYoP~رs'ׯg%YN a̜9!|qށ/"!'D G0fO:NȠ&T=' ALD3{%L5 QF3Lߏ~qJ'Ry|-OⶃGf7Duf<>}%ŏԺW?C'a>yhdKM -ZE_½-_xurFm0Mi1kRV'T0-Ī._mhpfMUX;k er%B䴙.>USԗ;A&ҴXb)iR^]VEP Ie Y^]>_}{-zuqzRZִߠbYл`Hk#>Z+E`:i#;::8]kjp *iKe0pqyFR"|>&"^!D7w.M.rW2 1e7^UQUւs,:f:KK bu`{mP Cbw#ކi|+_!L*[p!gv;FV Bm[  Zڥ>Y[}6H%ٺesqߊV4:#( ;o+>qY@$qzzz\Od>W8RJWE4hkk;vگ=G}5kri c6bL> N{eҤsUUUL<T*͢R_WKmm-:6Q^Q{͙s"MMn-L8j."{yӻzK'͟QGMgժ׸+P]ۜvj9,[G|>/&r=yĉӱi稭qq饗0uN9d&Lhu q<ē<Xxj9 AIXvXŋOcI̜96~f\rE կMxg8ihgΜ `^ye<3pe~ٷ txzh(h-$iI) )MT@u!SLI%T\nFӨJk2==jESY ɠp !LM>} &$P9fgqm6\+>N0}ի]@^6@W8:ܘH+w߶~۹38/{{@@1j+RJFSy†WBaIO8-4+QT;.+_}Օ1b&i8ʺ騪!,zXov$ɌN*CJU۷PN^*\~LD[Olޓf4m56=ˊ;@JNN{4ý\Qt[xm~ Y ̜5&0$H F08g+o0ҟ0 2>{voTJk#Z*tvP߹\r(|ƯjH]ѓw1aBst}xɫKlOY ,^P+zT^?# !U? 9Ӱ,\Wvs֮'nje(0]ΙV<#?,( aFYBRygƒ]wð)x"p؝]]پm+{H$Hg2=c--l޼ٝw+'i0 vMGGÖ0D8lj=Q>q᎟as=l۾$|3p /mqGRØ/8kㅵ6ȳ/~^ŸgLB]]]i;U>#˒ɥȌ&H28ɖ+۸IZFMEѺ2L@& ;@|4K0#.#-.kD0aңAAF+x6C(Ttk#/"$tc N]8p} 9ߘ;wJRrWrS^1[o+p}>,>\G\]E߸}֭[/͙[֖f2OB pgjni?{nò/vB8C M.;#̢ZX̜ш]bE;$6=e4"29)Ld&Ŏ!dsF7v/m`4%j)!gXFLh- 41LK/5oʕ+ m?/4M*K_ /t9[y{'eI_:qA*cCdN;8e-j*.Z WȏMUU]n/tBʆaʥs>Äa CCN(| j'JQvEyy9dL:ìYCEƍaDO4L:i&ZZi~:{:^KZ*imHVei̘1[222t:]P\.G"`hh~Ǖid2kk-G8Cee%|q&EtreL)9lqg``fYh Ma}SL̿XB$_0L BQ|FF'HMX5zH10>p h@WUt)>S̡H[ p4p+Fyt;3zO>k{G}fnnfMkܹsy衇\w=O?43foc֬Y曜|s=,_;vTWW3ydvEmm-W_}5wuoMMMmF8 IDATtM|_OdݺutMӟnfϞ7o~V\Iuu5ϧ![xg0 KNOAUUvTr9qwd29.2f͚EEEeyY[?0v_^T5- {%tp гf%I_8qA~wɋ4UƖ ??{A ̳ , V0Igr$Sw{UH!d]8a5e$eJ,GBnRm47CkWڱp9C΂Xp9$!vJ"iTVVrE3k p̙\kXxB }9.w\ˢ#<$!^ұ8RCԿ8AqhGJ[ I}E@xD"4:KJfppz2,L =#>:QRRB $HFRT O:"`v"Dd^fٳOVϢeGfQn1_ϸSO#P}.Uua4U%02<Ċ+9mMF?{!N]c2^2i 1MXƖVB3NP~:C 0q<3yF2"8Wg]+FJLՇŐIѡfOY T9$*ۛ6## I *L)yk{Re˖g?qw288ȹ_UW]M7D0dppxsfe 7sWp7/ ww7׿O~կtRʸ˸Y~=L믿t:mNӰ|U55$ &HX]ΧNK[[{Bl d`)Y6O<'娪Id2I" [hkh)/椏iܹ`2E=t PZ~=)dsH5!.AP[NxєTWb*)tx ٷrdԬ̎b(*h):jsV́cvoy 0uT***FA}}=GuO=B?ut:MEE'|2 .dڴiwc̙Vߋ_]]ͤI0 3g IRR^^NUeuɓ'HRTVVI&P]]͌3f s SNe˖-L4_|ݻwsWRRROgg'3gΤ1 =5\㎣X,Ɗ+rr뭷#p=կ~+V022ԩS9SYp!'Ov:3gtHOj,]Tw%ɂiPEwSv}dȂ*/~0?]%/㎣o^]qǸ-_4NKYdL2YDR0MۼR d(adu/,@7`F}* ާ,9C-pgڲ>L$aD7L2 ,?*=6'zۨK.s>г L1T66-eWAmJfLy5?l};qoZj2OOz6#'TBQ,uM[BE \@(Mw 7悔h~_XրŚSTU[]#ST ߇b Ν;hljdݚ(B&ʪj4Mc̘1)%d\.GMM p99s/PZ2zT63+e 1yTNF$j~HIe9|!MkY?G_8д'UPT _0 H'2Q2$PPu9&0K+OoDvPSa̘U0 S! c獽.3[3fϞ#c=L&Ã>H$aժUL>UV188k [lrsoP[ڏ.F9q(*"vǥ|[ߺ5O@ h!\.2Ϯx7ݥv$>* O?;9OJH%OrAf3vA9 I+~s#o# 24ghh(C# 9M=}lޞ mB 14`x0P# Fć90dG7׵&\馻Ӗd1KEEeTl d <+>&Vpt H"$,şAХ<ȣL>u狃G2[$X&|iZ[[,Dn}b ΩS^" ywzz{gt4N2$[s@`nbUQUr"MH$9HFFGL GIRilD)atd$dRL;wv"$E6%J#RNii `ȑNg, 4ՕnR)L 8mi׊Jjjdtuu]!GGdM#2aGϞMKP~$7q@Z>o|o|a5U }y3?U>c**mdgO"-?i69rMI'H%MT  i~!̡i*>E%R#PF*"#3 BȠcu-'4 "{,.䥼?\6L$u@0duY}L0-[0{l/^L4eƌezܹqI'͛9cY`X I'Dww7'p`R&NH0dƌAN9ŋ1c̘18묳عs'555̟? PSSCoo/H.B:;;|\uUFYYuuuQVVgMww7sS]]M}}=555~F)Sdtt+&R׾iL<~?_җhmm3R\3!{$zůpMtĔ$ĥ{)4LK0%0 ݵ,5Cܸh:梋/vOny;eg JmhKM#ҕWziugW{9.gvl<9*=qr8t]﨩%LR_f~@+hp8lX9pх"i[\.gjϕzg`phֱy&ҩtأhNq\a>YYD!.\L& :3rAIi9]0rh=(J0$F4552<<wB"SN@0@J 4i2m  1JǴ3'n㌎XS 2Tv"#D\y ߸8Ƙ~EWї_~)SPS[c2oe0tרa tt&E:>@;ߧ7Ȗ0h  @QƳ9B(hFDD# a*~[HEI᎗"o;l2fwHʋܹ'lݺ>|+y7PᮻB_Hy}G]Co۶զc}ܥzޠ!t 0*ldx%A<\1(ɳ{Ax<^P}_(fk :L :x&"ph#3wBkQ ĶDjAƭw*roUUy嗹+/vr;xγ ]ܪErxQ8Wg6Y}-,Yr>_!Mɵ~{4>x8s465㏓Ig6}MM<ē8jG} ŗƍya=֭[b\s<ȣEMM \{~w/]z T{~+dӦ*+:ee!"m}lي28gIS9E6|~?F"a W ۠Y.\+xM{-C{㇓a7c.;8.0WH8!ՇJd ,s|~RLl -"PE!3:"C P 3L:BE&g mp(HsP8;Cxna|t͇y!Fihlp?Dmo>hӄ3A:4@b!!Zc%%c'PHf:2#FlՔZ {a(zf8 6ps\Ej&=L61#yw=MJyX 3 rVKau*1f(ePq<)+8d,@qoyXEu= =r DQ,9믻^gc۶ "."].+VJ"/Į]X>&Ns-d˖-lٲ^3dW/g-̚5SOu @ &{od`pr." ǟ$Κƛ/ⶐEj6uDŽBATTMCK^HO&-L&iX&u=g!I*}./L!pLq)~CyKєyk) TF]a-9f j0߇)} $WB QT)]'7Ȯ.P4C -g9o-kv.`?+?6~(y}Xe_;>k׮-zƛغ|:Wsv=1{aqì 5_}ooS ˟GUUywqN&S$X2 窅JaI@A`.')NIRNg55Ԫ5ϕWV_T*6u0o\|~?LƝ/z?^V'/'E?p82Zg5z^iآL PAhdtҒ(F&yB#H #dUINOݵޝ;E#&p93( Tlzl=Յa\6qLq8;::}x{5)/:@mүۍvy3ee^p'*~1=߼f N9_᧣g=4ik׮'$Hp%DXrL[Yr%O<x%KΣT2Ōf*(ƞ=m{;xɧI,i:,qW1GN(DQsRJ|i躎0 L:o,.Hm4MC|riaک2 ɰ{n">:J__iZ׭]糟=jK[/ ?>~~o{؈DIgx78S`̟7H$o0y$(}}}T[q#`潦_xa`m  r7}GG #aa/35ykwдKaA<4-Wi7H`J)r }bW{6tt5A)-CV"~A΀#l]>LjF<3'dԖd(*L&R"1(1 ,wUv,N.w GZ]O0yٲe ]~9---<CT>?! g_uuu|`hp ,i@uXa}N97뮣O}O ϙ3g(,,bfkV+LP/u5tp(wLs!I//LSc:瑁}^n喌 ̯2ǚByŗoEo[o)mte``{toy.F]L;s2gDdIҒ@̠eQTX6XN)mP?>M2`a1(|B`0!CCP-s|<*S1ֶWŒňhFw󇰯ބ~hmH!hb]|_NeY\{)rxaw<_ɖ-[ryCȉ $O9yLl7"Y T/mCwϞܝY|wǑځm /ϰ+Yv-%%%B! l9Kw쫓iCL[>0bOpI>̙3g<&s5lj9ٿ!z{{XzU^/YX9u͜!yW$5;-02+S1ctB(XfϮs[J$GKk PD M46DcF]t֜2H$miw 44{A"K6̥P8ĚsP\R䴒"L;-1pmu1۹sr\ڰ( c&݌2M,m۞$ֶR^^OQXX0RJ&HiEH8BiEHA  7=8~u'QG".0iF~+Ww>ڷC 7c6{쥩wl~ |#G~'>1::??o3}}46.{k:_xoSQQΪիسg/#\{6qAv;x_SSSCWw+/c?G4Oʾ}m?z-8xRZ.~^"|#pT~}yP/Xm˲طo?(m+Wpn]p+oaCzWӟ?Wl|;;i[~]]|_$ws뮻#G^>K8?Ny~\͇Ri {wҖضc  E"/ (兔VF( Qb9~!>MgLw:8tKҺv5-ZE#SjR)̎W SXגvm[ KIǿMb"kRvWImcۖ9}mj&ߚ=|A6M>Bq'uY R'>q'eiZbTWPPIى9 Ӛ/^ş#!ȉ=8eµ&|>q TEb;WIa8K_狀ں:Ǚty9EE#ǿa.мyDHEeU1=,A,gzz1 144XSIS˲|+/ِL&9uei,,~._Vܞ |+"0y̦4~!yx7/@ss3nxef{f_E.mu1c6a8kJzWUU122B<gxx"Gqy!9iOx?ߡBQe{{Ff,J" J&4 f¹5vg~ UU*Z_e:^wBDxs|twwҥKyHRTp}p-;si2195\Ú5JAEk_:7x#wnogO?Җ|ᮻ<Ycc>vzzzdfUw) ::0 Mc@89G8Q ~t ʜA !sl)XFWz7lbǽ#O[4qRynx)euZf~5ըF*Bu `hh2Gݹ(`ґ4((hAI "Oa: VL5MCJ;9acipo<8~={AQ5\u9varr 1MӧPYYcppxz^>xyDO'v_uhʾ}(..Iu'ٴi˖-NZ[[)((y>%C %'N`Æ,_jjkPTA4OG>!ummmX>Yl))))umijkk4O󣺪"i][[KaaA;i +hhǶmBH;H6%%ְwm\-n8Vo &H,g>)lSb&4_eX/%<:DMF S@80 (n ()0iDE|TѰpm1?X#(XB%H@I`݇~ٶEִp6JwgfPYM% ޽efoM5%n`x"o`:n ͕]Ej4'?<ᎫsJO84_൨h<3._ɳ]{iS3gx S>KZ7+|Z(|y欩n D՗y6 !3-B`+2@$ᥗtR1|tqpOc|b=|9}ùJ--0HBZ600UБqFC(BMDфMTI XNeS>E# _C5Z,-AF >_!RhJ찊"ۋ_ lT$BHtTFqik 6ES$INua@ ((,A&=UQh.`짹PЇ N N7JMim3Lے ЉA6EJI16V#$QrJ Gy0Ʀe5NsoiZK(o)ʋìndOGő -uD:#}*Ó1tB7, 14K 59GeIIַTH9ZEtS34IG08 觭aX?jJ$'H BNܩx]CڥUTy0\kf"wkbdQa! KXl+Wa*++= hNif:::0Ms}fнY6nbb믿%3qzHo2XYE:$1k'L~ْ|&,|6SX|N {rKxfg矍.=O]ό Ep X=3O]gx˴0RIKKu!JJG0t"!&DFƘgbrUU@J @ii)`xMmW_ҥhơC[HssD˖miPWW;D8֖Ld-XիڨGUT***hk[Ieep7%j 6n@[[VGCCfjjk׮CҾUmmKS456uk;--Km2naeTPV^NUeTTTpٶRoNeEUUFaa!>ǵ^Kkz[_Q`RԄ@Ҷrl2lٜzи:ߵU дzʬ#ttus9Ɔ=' ~ pCDIA0H 2IFQ~gX@&bhj !2,P#%% ). OCFyEçP,DA@an(HhЎ:rC*d0J(SQZjvt"T9C4 EX\s5ʊ 荳}c^I$fMs%BO]{YPQ(Aƞ}~z&/XAI^>˦5!xb7X]&}Ē:gG&tECeO&E!^dy}U%x5t$m&g^*ür,r<_Soxe Q4Urkô990A]E!?d,őg1L;ٺʙ|}T3y| REʰxD8ЍOSY_>50-% E9@B·?״v EQX׶⢗R'&&>&O!{&mG#HHǟ!.5fYxxIWJ#]^I,G?>|䑜ze?UUglFmlhll=N=܃2S?E I8& SUU4 C1<<0S5Mc֭۷/Mt-fzflƲL~c\y8/spɒxLgM ^7HɄ3CaTf |F$ hF@f |?.ۖiضM<'L155őW miX05=LLNP$$I~bd=LH$7= QBL$-8\TO"rTƥc5H Ueֶ3칶9~u 9Ki^ڜ~Eehߺ A_}-*.yϞ=R\R\ eFyBۮٖSn@YyYHSn}rl2yg$5լ\rF[Ik]vf[ AM] 5u9z/T~۪6$tvvҒ6^oɯιS2Ʀ&o.D q/km;V.p\ͅw'YQfyuDӲ0l 6Ű!!05nDe ұ.-,uO}heNR@ Ql,@HA? C*R@pp o1fI ξ }H-* Qwϡ}hV"m՞GؖkX#y b`*١!,;1T޲y)꡶Mk 5ё8IuYek.ᥣ}LL'x8rrޡ)>94꺥l^Qc͙Ir _h);.޾uE Ǩ*T]Ɂq^ iR(*䁓,o(D"&Xݼy)Ɨ}KfgiS3+ٓS|;}$RCM5%eOrZ^>1ęIF*p͚%lYYˑ! G;N]E!eSYқ_x=9 S]SW)![aEajj߰w{S!q9uu4O%HKDW]&4d2s,kٞaUUillrXH) B~ ))- JJJ())! 9p* frk+j*)(g՘랙h4J<'0>>ΩS/ˮsii)3aUUynXYϭ(C*zˌ9 g %ِuue̺ _rْ\6"9-fbrW_{qzN" d D<~L$LzCCI&B0=P\T$SSYE]q4M, Hh 54 lm[ RPR5?Χ/%޺i&kG>˪J`!,q!}{Nع)4 *B5)S9ٯsp,d$j!e9Lr^| фi(l[޶ J >;p۵uQA}EaNE:u' HЏ*w3SF#\"TtBx<pu[=>M¦޸<3FEqp@0:`d2αӣ\Rd,EcS "~bIK;188+'aCW(#q&xx kXPOw!12d(Pjy-vVƣE( [];d}Q|IjG6:QE }&py- C%w$VEl bvp Ga򖛩BJPUUU||iUVn؋\B|D"|7'Kt.Pujkk)..fOmGv 'hn^yG[ɜϷّۭrZaq07{b:z8;X]{msx,Ç=y'OH$òcJ>166JaO1-b1:%PVVF8HG>N/!_w2:Y`:#Rq`MYw^a|],VKVNp,.QLb$ a"Y&F4PM݄_+a&Mʰ%($DL#BٺW(+EF"_QяGsgI )P6 sY:*R 63*&E$lj+0)BM(%EE>IzEBA*BR[TpFm _ݲ"ɪ)+SYZ3oUkHχnZ;#NEqg'~.Z/\k8HYT?^x =C6+ >^991wϿtzB8!o|Sy.s2nEg(\!D+<ɑ[!wsw?;::L0{A0 TU <$Io#6n$x4đjQ1~Jl5LY7fSxr,\VRχӯ4҆V^_Lx<{zug[EJ ]'H=9F]9EI5|sEMqq l))..& `IcED"w \E~9k9r8?d5]x) Q"/ㄦ%u ȴ$ TӖM2Z:vR(R`Dha4Շ  `qBJR5LsuAb4` 1$˥^-'KJÎk޶RbGEi/<{:;;,疲c$uTq)NZt=~ޡI^kW.wd2ί^<;]IYa(°sn:[3 ŸkZ?|a4M|SNzxUU9p?H&lذ~sR;`9!0?a#Gn$%ccǹ?_H=毤+WRQU3O=#1\Hr/5t%r  PXX8Sj%2@gC羓F89 e5/^a=zt3oE4_E4[-ԽNOOsAP[[}̱hzzz~5kDfi/BxsqJ\eˮ\qw=~QA( p@UUH[l}g8I\i}~EqܳnNt?+WUufD9j\+fs(es(II聗i9EmJ*(f "A:Lj٨RSuNSڤlPn6BKv)4qI~XBi1(]fpzَP"wEHRܛٰ}] є%2%[ۮ))>gTrô]Ʀt=$ijXt]f qF'9[J)B7>ieTᝩgY3zE( IR9d>&C(3®|YAD]Wde|lmlkAS5#THEE%'Ov}u%ٝYRɉ N8AKkkxD`3Z򸨹2/bIqg|j󩏺<55MaQ1gظi#/{lv!Zweο:J 13E2+G˭z R?w7}9[|jVjq=3|_u_z?~JKK|碂G97> Bxrpir4)Ʃ\kn*e3~@۸} @U ~ m}oh0Tse.$/7@K&A~CGħPMjKib9p3E $*bk>!1,ǬC#l4  ~EZlEU= &GB(f.Vè: H% "@Ր4k*kf]Ky 5B㾞*BaIkZWYfMsl7[tttp;n@\7"L]Wz&op⟹ 208 0S?-0-H$7i^|~8r0}g_4ee9j,EҬpFe49 c6FFIRTUUPw!O0>>8F[笠b^}UV244Dg ik[ɾ}(*, ˲8z6ÇY~N&m󁄁A`ʕXHOO/`-Łزe K6I&m+=u9vlܸNOO/TWm%TRRRB,cu!8M,]t6]#!rտ-"3c߷Aiz]&.{wE]|d֝l8ٛ6 wSSG;:طwťuo|fY{H΃|"##LMMѲta3իr ٻs۪@ @Muy\<:AII1==ESc?OH;zY, N+R& ǐ6~B֊TP%X@i 0R*'T-z@!#@2->RDv:^B{j"\"JeQL؊E ko料.:q碱(Yi.?|;22SSE9 ȷ4Ɖ|5L s)̉ I90ll,}}}OSPX/Ю7іnKX̦>d~Ȏa[6':0_e4?D nflJ׉>t}{}x;4m˚%{M6WBGLGۿDQ>Or5E=دLdbbp$B*b <ݔՍeY{AEe%i97n_a|A-|*~-Y~WO$xX|yat#{|(zիݻw/%p||nNtuFb ݻ?ٹs'?Ǩ~c?ҙ, aE9vE=c8 mۄB!u :=0p|q_pWW7s M?3<<۷oCؼP?Yx0~LLc C"q.c<2"MmJ!@Q RIRZhR8:RFG@8$@S}hS%z4(T|]8+RB!mE؀"ۮB L]s մCϧ :|ҺTey/> 8,2Ub8_Q/GnZ2q`qY8<7cDNG,,-u !Y ɤc> f]CN?es Gqq BIJm ]'J B3XUK-mɉI$H8B 0 ,Qь|XCOR) ( dp8iDΡD"p%O#)``=D9:E9ƏH%S( JyR9uFS5AbHS?$ {~,)4iz6iHD"A0<﹑3乮\*::}lȟ7M`0訒Iaw)`zz:qJ8'g0kII4۲I$Dp˅@Y|0-TJ' 󣧒(arWe>3s{?v+lUpWVT ˤ:&+j:e C(UQAMxW \7dg q%%9ŀ+ݞ++3~'?֢3R"CsW@zzq*3wgnV?]w̸Q~mfjjl[ z6\Ϙ>?͹$=N\vq#Xlz3򘯭{Ŷ_zy^.m2 o]LxcJJI2DT)))FQ Hi>B~Mv .ID' 4F &),.cis3Hx~-0pN9vnkfZ/_Rse\-u۶H[$ְmi=-]O=ޣMKa\.r=;Laa`ιRřg#mun+ZO())AA" z&3tZu5᫷bm1>>l LOG$˻eI>IEapp̑wxW0L\o a6otQW \75D"aE!Jŀ EI&9 w1w՛n$STz UU3::"5JRPP@*-yζj'S_]Ukͺe>73RL(,,@ v+A* a$IRD50[>̗M/\_}>}43.cc}chdli ?RJ$-X3<Árnoog3R!W,:C1y6 裏~z*++8 9[q>y̗l6By+bq1Ҝ{87| Hb/r]p9Kr/v^ yt LD]OH$sԝTUT"i7緿I&S:\I{qc3VVS.:x6g4M~inJQQSݻٳ%,bÆ\uU'}^,/a%ky*n*0?kv{6`k;HqG.,NOdxֽS$S)|~F͸LwĆ@ܵҼ5cHZϭ[A%!t=m[H W[o%PRR­Jkk+LL{F$hq8SbQHLOO366aH V c||[X9 ^{-iX4Bn8'?[|y? P犿Xi\4jj>m{/r]a]*TZ(lYgiB&%8>Io'wrغywc&i{;A6HrD"t9?8:1i^sѱ(E4>L3+`ttC^ƶm:gΜ헬X-[yWcY9]gbb36pvS$q\5/w܉eY ̊GaժUX9#>}Fcc])wB806m9dAL6``:a?`Y6m4L|%,l3t]g׮]444я~_<|BGG9^x )++$LAAX1jkho kmvZ:eaFFFhlld6ڵ SSS<$ VZMMMM-W_ŶmVZEuD"֭[K8 34tI àm% H)9~SNQRRƍ_=իhmmerrW_}x<ʕm44c&Ǐ2V^MAA$g 0)Y(AIDAT%"FzF;.˚ߘI]1c꺃?5}T7'h/z66Ē&  !N&xi , o9.y]`- Xx|#F_N9VG. @ŽoqRvEH+QU,|'{4v ,G/͐J*%&;h b)S$A%̤9LG%,,FH+gP?Ig4ҍ۶g:pB)mvFuuݯ|Kr]wiVPVFjr yYBnj=/{0!Ӆtw2ϞĉN>{$K,3 P|w/}/˄aqg oofppo~nnfv66l>SO=ɝw9geW \+paJx h NcC-3> ߌ XYi? ?i3*G~?=d,I}mWmwQUg޹3I2IaI aM(mʫuE[ZkXEQ*Gn I2d{dA8??x,y97s9ϡ_^#Wfһ***d2e),, -+ӧ1lXy0ϩS'箻~dŊUbjmCccL8?hL75PZZJFFmfDQGjj*km۶1o\FEjڕgA`԰RN]g%n sR:fq7'#|ٳgYx1fo}[<裀¦Mo2k,,udL:̙3ilRUUȑ#xw1cZ+oln3n8zj(ܺ#>S Q(/C8t ~/^,OZz=pvy|dSRR,u޽TUU1uTl6~ w2vn1x` ^ Jjj*;.6[ *eee46Nd 질QN`ܸql1_ΝX|9m+(gLgYɻ8OqY樴f1>q0Q'Nm(c7#dʺʴyӴHSl] &Iga}XL~aL[NƐP喙<~V oc,Ӹ>j}qY$+a#-ͯ0/G|ݭ23$11m>{[?`a=FJ h3t:Bh8N} @jXV6)_C0LѴ?ˇc0$PXXD{ECqAz=^ & aUU Ӽ|z#iv1-K;˽]Yi5 (^]{wu?Ȥ^3*8͛ܵ`E1iw#.$yH>s{yK'cX`2{PWg%773[DQxx"zRSXzNGaAJtRSu+WzzGp0`= ?1l/![+-innfɒCe͎֯^w75YYv-YYLX7LUUk׮cʔ) 8((@FcI=?vb<:zz>;l$-fEUH6mu:QNƇ~Q$$ķ=^6klƏQÍ72g\rrql߾=xL)f!,XQ3˩ mmip;NdpHMMct'OM0y,+L|% <0mts5k6(bرc-&)) 6r |SXX@NNNpP9}>1{lJKXy\^zc1LVaY<yI_PӶٷ2)E4`NK\Qv)ǬOr ;v IiPl*ge<|^0 -R+j/arڵlsk>A?`&/Go@/yu ᪌.(prڵ J@đAm̯plG~&gzd'aٴ(TCu vSRh4|>.1)vGG؟}prmeHRRS|[Hnn.&==-8D9:h҄hJ؁VrƎرcl(^b#c[.o=˗7 !b[w{{ (CW{ '%h\O9}z挲p2i+f1!LA )!eVx%4mSE(xx7xW@/QQQNII1vꫯX!33NGfbe8y$C P䚜fC=+h4ׯUUU >6mĉEzOTVVvɼ^lٲ)((x)*W`3gΰl2ΝUOC`oδ*4G{ YƠTCqJJQa4mpBS[( X(TWC\s͵Ԑ&Θ -&>x+ ~W\qEjFѿ:-"RFA  hjc F bC~N %%qRSӧO!",1cռ̟?.U"%؎?Yd„ ߿odI Ci#h- lW?st4y㱛v- s~܆EAg \>._/=G-P:czd 0&ЖRERȊCaѫ[e;"a'YLMj*.Yi)A MR%GE:Mj=Qjh2}Sڛ[Z0űc!Ji]!,/~1{vv5z4$3jX5Eu!{[;2ȑ#yꩧhkkc,Y$*]6߱Oß/Xg]Ś5kHMMeܹGqGq E ^GJ"dEQHNͦdx%zqNUCӅS= æoGIH0#L!R ⻍Fa֙A >"!;M7L?a+$Ib˖Y|9 0zhRSSB#? O(~f(oCJ(JèKS4k?@@`ٿbCڳOD}s߀gp߾b߯y<YcsӪuIZi'b`e+9;;Ry9}d[,t;nЉ"|!Z9=MVl}}0^.,KqGqǥs \AQU#-ː]n\NlfWxI *,(=_<=?SWO")ԟ`ZY|9(… 5j}v+f$IbTTTsN֭[Gnn.-8 ڹs'.J|InVA`Æ ;s7Ž;x2klONmm-UUU;v1c0g\AuT]#+?>iiiuk@dYa2%"RP&"n\.^RGkO={`-^ZsYom\WDOAb0E=p8/%f3ƺ:( &0H.:sWe FvޅO1([)-JaQzE$!| d ºz ^/AW*3BX"Z'@PeɄ,lS"qx6çhhöcgsh[Õ+WL8 ~{LYnљK]~cߛyimmS}*d-#FO>f^nI!MJq}C|Ӹ$Wj(8AoǙ7T^KLL䦛zk2޵.>پgI6^| 8}ځ}cGkL= :6 n!,MxO@G!Fǵim ]M"j]˦;qÂʤ#zt?]e|:q3,6&9 xw߭x*ҩLFHDJrbD{U+ i"jN|;@Y#%=LbHROB}D1L]Pf\n n~ۏD%>eʔ(ꖾ3BNd9C*f3r3$!2C <QMwG$!(;_!:r*OO\Z;'~3f &'wx011Q5=c0(,*?N#% aphༀB9X VFؤ :$;NŞEGe?#Teu%Сgey]9ޕ[ᅗչ#sG~!՗*zSVr!Id&tF)ǁ (U95.8#;BW=Ω }\HSB1.E(Z8}4eCb6{N)frE}FM&dCˏe4PAmzA'2Mx}]X tRж' A z,㶣!z=cKk{K_eU\V_oY/@(םޗxK"˅js ^ep& }((x7"U,ֶ!% /aCD5JYzx-!9uL/Y4Z9=V=~wuPEr|='od@oˬoʪ \Ie}e}e}Xn<˪[2Q}$qgcX.IENDB`liferay_2.png_documentation_1.9_applications_liferay.html000066400000000000000000000117301325274564300463050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:liferay_2.png [LemonLDAP::NG] />

documentation:liferay_2.png

liferay_2.png

liferay_2.png

Date:
2016/07/19 12:14
Filename:
liferay_2.png
Format:
PNG
Size:
215KB
Width:
1239
Height:
574


Back to documentation:1.9:applications:liferay

liferay_3.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000004664021325274564300420370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{lIDATx]|6oe/@a7eR(Heo M{)]({eJje'$adt,|AO_0$==IKOĝCF#ca222>д04M32GT\p՚D2IR&jbՅ Va|[y: P,BZzL NF O+P)'r:fbcab.CGk4oT@wI]%,/ a6RXO`c`eV^AoXcb z{ 47߁VG#ča 7sP @v\ƒ)2ee223uZ6˷0L݋{"88l>>>IIQW\\Hƛց |x=iR\& Zɹ>xBPJʣB'H #JC˸1I,fug9^Wk4|c vU@f%,JT3/ M4(lx(JNan(Ec9HK.&\64` Xߘ}7b*!l.9xa5`+@J!3?.dj! -> lPPhO>0ڂJkPG!URB;Mƍ>,Xw4ѐ(gbbE 2VEo,%)'gd ;Sd*ʀ;<Щߋ{q¥{ i2~EU0##+Se+(P0%%w)\`P  dQ2D]ѣBE(9WLL54Q0R GVA;~$SZ]NIoYee#a|" 36$mCh4 x*QF4AqtWF. -@!ŝia;uNK7#EĐbܤ MCG+Ps1)ZYFlec5a߃- m)xO.k tR&Lc35\΂D[E?«)/ Bv wyo=Y#FuPlZ(wLD9*\1/M~}W(,ό{?I㖾9қϟ ˙3ؘ.}qws/TPl\ltttXX[ e˖k׮\Tcǎ͝;'&&܋Ǻ8raޑPx+?ѧQMv)~"!4qlJ4':yאPF=(,W^Q^Hi^hPkváᶿa*A$Qh@O^ʣY@">s G+@JDF7Ր#G +B(ճGpvv'Lm0VIq.36#g$0'YPhs΀ t\1h5Jf)us[m۶ =zr}aD3gxŮ])r V-[N2F?n޽ԻY QdSw!P@zSg ;ߒN%N9+];B2S#̡AUe{NF b=BA5 {Q`d҂?46p\lbpVA!Puf&# P\bI JFoG!_#Rh}>&<#|(Ij@6x 'ԙ) 1KΆPͶ$fwdPNL0^p (CrZb/(L!x%Z#F ?\ƒ\H?m붶ʗ7/{?CxEQ }uч"zpɉK=2SSFdĝ~/*l}|}^I@^4WX.^,… &D^8q/^b^K|<اw.i\ǰI IѢ<)3_$(‚DE*U]4/~X+Br"byDa\c*+KBX$hD2TBE dURulkyqQ$zc,M! UF(hW\B[kJKHɆ8!ҽ [fn7}"1,Mpmit `y o ;6fR:Zl08Kk8]"YMBuӑی4CF`rO@Nc)*)dob b雊@V֡3jc/Tė7?Y2W$zժ^TXAM&̾}9t ˛GÇ~ˣojqB3ӞfGN4%^i:Yk8|k-1P,h4juzFP(Vc2Xh^tcNg"AK. fƍ?nyt`PPuF^g[}~٦ hvm5 cJ^/P`bB!9r;vܧ~TB5`BK6m8{lP)yݺ -?͛(P`߾ \f@)$Ҽ*ˊ+RgF_T9BvÏ;|5k?w_~53g.,E FgSl=g1^N& ܝem/'P% whBɕ^U%V%J1"$rRSETTT MQ/w Ť\NM Fc'bS K.2jHtl?hvw6sR\} ?Aϟ9xIfըgGiڼyHHgO.7'##cY˝;LF_j~:˗?cE<݄L&)SԮ˙_vٹsCO?:wh^={{`o:glxU:'N1"!Y,y !Wc]|yqW@'?ʕ+ܢZ% wK*j%5Q8Bb(՗9auo"k_0}kA."F܋ G]jDJ_.j$ڶ-PR+i2%|$ND{in6,Zb#%W6[4Ыms/>Z1UVlC{ TF@,#=bG6>}P.B%k'4Uqox(Ot&ʃ~9u37n^1*$$4:*j왉^ޞ_02Λ;7w6!LF!!N3{fB|b+W.(ДG77=$-CgͱU'>u܌gϒbfr(dZS,ں`Οrg-[~>uJw ǿ޹kWjת ]~ڱBXIFW;vZ|Tpƍ8bH @PJppԹ[]7lO;Pglp?}NZ8,t 2^?_΀p] ݴicxxѣG@' m۶]z՚5kݦM:4ӧF`N+Y)IrKԷ\nڵ*33sɒ%oz׾k!NA *V^{EpT~p<*hJsS]hb @J "4_E_d `~$g/MI 7{BS22' a JM7(B^J#6a9C50ʊ-QX! S. "|$1^9Oh4|.=Nw+)]2_.(V85`F'{9K ;-x!1T_ad &`pD,6'B.<9*)(%?k#"ʃ4lt? /k۾Ï[7ki@7yI\ll¶÷ofg`5dʅ5g߯^&{RPA(W^&_/}̣;v.}P)OIK_}޲Z rbΕAu^:>R:oojŊN$_|?Ǻua ]^(8wp_c+r QHV6בs t-Bv@H K04ATqZXUi .ghU2d$a t/% N{i(;)ɰ^-ݹ/∄h(à48̧H^PFH7j,@>=5T @ؙ-cP*F%~/F7|Oi$(XE^CHBTIh)iZc[jDȵ57d.+$2y/TN+;.6o*JvwP4xII;oj谼avϑ#Ah&q M3再*d*`E\|}}Q A(t76P`вON]vHz꿆q9jS >9h4* 2 GR(6fpsJ }eURʅ (Kr ySEN9bQQyH$&&իWzĠ;BeѮSF ŒKkԨi1T ]FX{W,㯿փ@xx8vIΟ2dd8abƍ*QRهz*n]j ܪuk4^(os"U,@ JW*G[\;,k8f* g yZ ᐅꋓAhH>ij/a) BE%k*U%wJj6q'.((Agñ)cz*'lPLVVy6{KU}'3.O7 1YozѨuo_PAh&>Yl?ۖ:k{ݸWr3qPe_ڷk^ ˓~&'NII޷s{:zQF/m H}XUݪC^Ә};VKxhPN 逖ɾʵJIN*VY}N:v| _Ϛ=k#F yʦ͛VXֹS3g̞9kIիV ׬^qÆlX}eeP,ڵ}UY( mkwfFFHR se})Tz;As;h 0,GD(T*Po^D!-JB[AL&*Bj5 rhbyJZ f W5HwP5[^UJ~?$ yGR< 3܊#z HԜ)Ƌ[Tj 6.IAD`['<2?Ѣ*b P$)@Kkذ *iQ X.#,Q<03ϚQ0p"pVw~ T)jNa ÿ /q$}mRC&69 )&(7SFк?OPrq,K@@luv@HH7(?qIPhE`= \OʹrbR2UXiBorGx V\/TOU_JH 8ojҥF3Kx;E\ BW~a,49Kc4Yԕœz}ٗ 9Srx{ݹsSfh6uZFSPr*xgAg ABL~?q„U*Gn/Ev2Ӭ &5K8W;ȯ &R"6(<:S/xR(k VЂx}/ȖoDN9-<;}Q bb۫- ))+ ͅ5*))=2va4ThRls*)X}F cmUvPoheY9)p zȔKqcn68@TA=lҚK `s Hf(ƅQ.*c&4"csl ( J mC+Bs+>ٿ I)x %TU Qv+AI5'?OSݢ90T eq=sh&0l~}VG.pډSB R% V%+*RlJv a0B#8Lq4 ODm 1Hǣ]Z$)fAj{WAQ.mMh!Rw$eDfߍNJ@j,_4VŞsMk%PS;.Aʦ2< ~ bEӔpΐKzD$@rTN O'!@\EP6 F%0oJ4{UZpUW-SXC2xuA㠒B9aQI -Z q; ",l9xJ# sEK1Xhw`%co\He[̾(>}/ Ft{5rE}L8pP#E ' oBI=LcXd!J)7,[!+nnXU; 傫?Ċl 7-[# ^¢ +M#].+4#dJٗdT{iH(,xT!~J0æc_.o&n:MPfm:#4^c2= =5*be*BQu3Th#6I,TUYt,ZIqkPIXM$b- sQ٪V1 ă5yHV/4& :A3U 7(J,Ou K-ty KTR%'U婲tG w+MfSeW9-<;rLK@@@@@@@@@@@@@ ~ԹB B  % x{XGiF__Z<5X ƍG(([@JjjbRsZr?._~C+`…bjpy'&& ߿`„ھ5" xC=n,.]hJ.i2_[ Sr%jY(Se{s>!eSR&Jolժ8޽,\lY%@8rٲel}q|za¬7nl/Zkтa/ `gϞΝ;KFJʓ'M ӧO? ;~xq Uϟ?hGw޽_@h߀ybMhbUnԴbJGmִ;w@{IoBm o G$]>U*s;z(L8""w> )W!NDlAÜL $t,pss߿ǎ[zkÆ9{WhWCBBƏ/)`yɾlyl8yޠ/P$gyL7kNUPL}%Kߺ};--vABB ,C)zN ̙3g5>|huę(X^o8.) -YUjC#̝%g:Wu5ͧ_D$&$xdG(LE+ 1TSRE U' 2Q"(00ٳ|9*0(hu9m+ o@aa >a9Ϟ}Ο??n}FՂ*wԣFvȐ5jxyy֩[='^PH>Z`>.\4yY510lzz:t||ҥkĈ]z5rY9sk-7ed>ܳw/"I`QÚYcMhb6Px-7A6mڰ7QSJToH.?,UAB:u8^rSG(w~/" x?kXZ-EFx+Vp U,Tç. X~Ǽ{z Փ~kY(|*~*&:$>.bw]zJmN9XY(DĵȾ)cn:##Nݺ}uIB> nR]ŋ+ ('B ;wk-SɇAJ@@@@@@@@@@@@@X(ao=J@@@@@@@@@@@@@X(aم% xA"DEX(av0 c6Fa^ji-,|P ,HLLߴ4F>NHHH<< *+dL&Sf1$$?<`0tN8_"< ,eHNN~Q޼yk] x{{f999t2&1))IMV^^^Ey㆏VUK*F$Q 48 Sƚ^~|91%6C:w7|:WT$H6ggУbqƏ#ǎ7j|bbb+N (ETqvᝲT%GEEa2`LdYef}||r[B VZ XO?8/u+M;[3_בϓ'Wi쳥w[k7 ƛMf@ U+T=rP_YhsEN3ԉ"Oڮ;pc۶u?mݺo0Ͽ Up=x-۶mڤI>ޞoNzgbBnnII kj_~^Fn,ѻŋksz?A]cPgFD ֽl1tb[VTj9rd![NASLH0M PF.cfJ3Yrp@8%8&galΏ^H^ޅ1FL6/zk \Dp2GrI)Nߓڷd׵_Y(Zۼ7:\Y\t̺.릥1aJyi6[(+ K:Ŏq\io϶Mo7_E6^rٖ}R=iicRZJZ 8Q1Q2CE\lrU3Z%G& uʖ{S˗_7oݯߩys??܂U㧟jpjZC^ԪM]Hr]{m߱LRGa&O4cƌ/ukԮU\}W-֭\0w{EN?7_}%z[5?ee kPPr\["Wy sA9IjjUOO,*'-'O9Իue?|AE߿9P{oGhҬAvk׮uܭEׯ^$%Ün?l9+>;y1ɭ z6}-_O}O ۷O]ݔ_i?KcѺn Jv] i֣g4q3W\7sf8y„]6S>ET-3K?%8hPј!QAC̘1 Ycپ[D(q3Y,!>>> Ÿi6/S!=%x@kj'B1V5X/^Y(T#GAZjf.\0e'Oް`.]73۱<l4F0L@+Vݔ8}:^1[y3H{ҥ ]8x_پ-VxySedzMLL{ׯ,_reH|ظ~j&VlO1Z,R twi7}W\n,d =u5vUMܢhٯ-bY:̌U"anG[=6r Ƕ/p`@xmq_jb`&̙̎ߪ^eKA@ t;ɉ ٳq^ާ;i}WKi-O9y{͚5]{^,GNa'@k.6mԩS7f?H&Y@57_W)d5:::W\T! >_J7gMRpKv<˗~/^d{P1ZwDN?t9&>=% penep2L]q1_oO:95CtzD=H=Z,Vj4:>Aç;i̕? utjpáA=4 uy .U+i',4k׮]pQH >|={Rý}׵; Ø1jgfr'%&'ƿz /{̜9oN+V ݻY ڢEߍ/'M_~&M Ort)6搻ʾd0ߖk#ICA\7YJݻEf *a}( ]Tiɔߛ7yl2sULYg^%:V33L\bG~/@ (&'AƵ /!!F:|;6N^ 00xtsrzJɒi2{=ݴ-*E SGz{xz{{z2n^(X6[ɒb5:Ƭ~7Ӫ6q`)dú4whO-5S;̸ۧn9oÙu$w9˙y(Qb Pt~Ve 78F{xldulk'&i9w.m@3g4czX ϞZm|C[+l=-֛PZ[OKHR;cQZn:cQ A2<>|Aߧqi&뗨K|Bǯ ze1syTPv5%yP?~nݺ)صk}Lkܽ{OܹAxi0}ܸ9\) @.\䰾ӦMo֬) KIIl {{{ Y&_ۻtz^z9be`w6o޴cOqnB:! lwlzG+YՃ{Ç/<~<x ,;T Odh eh4#v 3:*Z=J%?*}_Y)<:jln%>(66ړǕVfbK?w!B_x}|aɾIx~-/~i@g&))qI}EppaUmw+ߨXƝ:-+I{~/O^kk:pQ9c^yQ E.ú>6Ժ"{lz#Wקȃc7jS. k/ \?>uϭ?өMWM%вmZT~Cΰ jmMteX7rc^ݏ3yzi7sxxxL&˗bcc0vFqqqƆAa/^)q%yz>33e,\433ÃgFFr<] NOO?w6L~ Cmu^ert\bnP|ejgA7CՏ?5m͉VPPjl[f0Lh<=Ԯ-|hzDyNOr3i>=YrtF3׫ɤ' |-û5ҹDOӀޟ:hg֮]'55Exȟi&@A)ʒ׹׎7,<}0ƒ=^ZOCZ%94~g 4Բ'O@ w63fဈٴQ@So`|R^t7'2!W nk/(pvSp73v* _Qؿˆ)'o8Ǐ ,O<hh^zrt;nAS`(=pZ/}쿑[ك!AtZc_'4:Ieף[@ؘy/gdi5`jj*pb|5@@{5ٵP~~ի8QmsQ4O=Ǘ(ߡCbcc׬]]ӿ\:LY~J~FD!ֺ֒:ZNk=r~Fٓ"l=7{髏Nۼ%".Th0̀4l(%%f jy KiajɤGuv+u0L7nY?VԾO\,=*I')j ;7Gp_b' "DfM΃Fc݈kLӦ ?93 ZѝaЯ?+R)eݘcǎ6l$|9_,C(Zj5f̘%K1[Ů}F\h1^()7== ǏM:U}EJٳmc?C-YM]ŋ/\w> |%l]vӧO[n=e ;-<,c&_̙3z '̛7o1쥮]}jXX;w,X05r-e >~ DJZtǎRc: 6dn }_R]u"r{J䃠 _d#񸱻K+K ϶H.n sh4ܹf@o^uQߥʔ[-.+_7_+YGrrPQʕYe5([PיL[6<h"K_h:KD[ ?c"7-Uw€ZWC:Jk}SA* @-m/+~ϻ7dD֟کn6,?Ĵ0L *>GSΫ3i^FNg䨥;x{޻?2KSfxd}R)1ĜK0yTh¼̓\_eɊtz}e^6xڿwȕJsO|Kooџ]ZT7׷a'c1[k@R6|PgqmíQ!IVR/ uO]4^vOw?~OSRRXT^f͚ e˖Gƒ;vܻacǎ9rDʕ@d2fX6>"bĉ֭6l8 H) N]> ޽[eEJi֬i.>|+$kbʕ#FD,^Tr*6K>bwpǎ A0׭[ի7a^yD4 /dF0c'}MX x&8pB1-FQəS .u-4˟Ҷ/iZ7~L&c|I *qfhvտR~^~k+,bcr;vlڵ0*X[?,PiӦ?4 gϞXnr+UĪYX={R"2,)yD E*UU<؀e`-eC2w7mƗ)S-Sݺ}\|0| Gyyy-Z|CnnnVV5kwө^ٓI#[qJXڨTb_4j,I6Tje'B"D*Td+sckobjX` vmԹrj^-ɉcݓ{L駟rgf,xfBade 嬌2qfֲ];e?ҫդWw=л;`=jU C;ϩ*}#B|vZkm  xӉEǏJp-9.D!]&;_>SV,!)4TP!([>묶:u|E+^NNNZ>UpeH[P ƍ7j@o8oDR~ժ5N:>R @D}d,uN *2AQhnРA2MӈY}f-'<;@&AY(s|0/_V;;pW7} -[]NfpƎjk<;g ,[ *UXyA٩?ܥW\rJ'ԹgKzv8b [uGЪiD\oV(P&7ͣc< |wWm]׶m*Db^J.{]Y L'F/s4=/˾U@4QYaD//[lܔ+Ç>|8 IÇq#euD ++{9-_4f0.KB;.%I>kpMp>ϑ# 33 ;kNnn6UX)/99ظicu_O#Y<,DdɎ4HɎȇ>|1oA7\r˗믛4n|r:Vl-[VZK>J+P>|Ç>MjԨ!۶mV啩Pl|(MY>|ÇҧreffVTyyyܳÇg>|Ç>|(d2'Pl%zvH><H~Ç>|(MHK>|Y>|Ç>|H|Ç>|Ç>RÇ>|ÇԁdM[N>|q"#GjfxI4-Uy HPBT0{HPK>3i@+5(䭤~!>J 8=#y-?v.anՓdIW}ZK]xM% NJ~}K 3tyݿ'H G=2vk-kn}IVk{IiK ~wP}<«?~}K |6G_4u%Q@дA[ddhGDl%]~ۦRs J4eu>Jv{]ƍLJsip̸cir8d r)tɃ[؅[!$i3x7fFK.-!k]o?KIrVz•|D`=Mw.slLjqt[a}~,\XR%s=ۻ;ݦ|9M3t?~6kF[n7n]VxrUo݂әTN$v%'Mq'lo ׿dzM[=tǼEЛrkѵVOrP a#+V#F3fLvvI.nÇwPRzT'Zz=OҖ~!5pPbއ$IӦND7;xcfqQk3݇&\`M9l3i(FFcBbqADdw/]v۵5R(z{S8]NANd SaC`[Z`1ٖ#ng nwAy/= r `zQX<T actk8jTj*Nڶyr^lX9,/xbv> ^: |mm#['-pmnAn~21o aw"R_7n?Zd;?tjVy+쾏6m~?<^ 9rnݺY^M6C_-Zxex \%I^(`{kLckPڄPhGQtC!u.Q5dQ)$T2|9lsz ҇%F2.1D zu8KD%.jBdeIĀaT,#d($[?qaQ`wwқlj,J+$fɖOsoonk^f9kfx͙dV]VyZDX\\w'|Çիw͵~k~߾E_C}D32ud_ o%=eGKĶ!v'QqP݂ܒDЃ"9H͡3y  ta4yZW X.1Q䙃4$]MW{vn-9^OG8Mt]ĭqZzy@ɼgȬ̌*#-X4K\"c,d 77ob-9 \-8̶Y Ɣ\w g"-ؓt.xHjWpLXfWD*DOjOC=Q nMؠIJtA!3 ej*'8(Z5$Y rT^[3B%Y 2"k"y-Xz萈tw/\<9ۅ[ݤv!۴QM/ᡴHNUGD~żŵ/oM)(ÇZh:eG$Y알px}q$[7仨'3Le#D59-I*M2Ln/`t$eΧ¯PX\Er:((sqQ3]T_G!*&JHhJJ 2T '5\%%DcD`,8KKBL=&S ~mRa%/E^ITrvm^k{yr+va+Qq"8&^qŅEE|nSi!_qhXPp<GWfp6ůQ4Z®L$$Kn:, 8یb) rF@o'A6Z2H(OssB+DHxHH鑱@26&Z DzT0}+ L-zĝ K2>X] +gNZ,YmF5 CeUXkQC1Vv&]oI!2g$:46mY[Œ)]#%'..By $ĭ^'8FqqQBOTBϐT>/[aJVR&kcKVl_L@PaP`F9:̄0",`(l'/D:7 -*eꂒ+ʚ2"Y1P*. Cjs&(Xj. r !I>RD!աZ 7:zvQ{n7x_[|}w&Sf]i*xݑ)(aGE]7RBU->.[ch6aVlFA:d@ Fv=l&dmM<}4ןXNPRʋW8'b5l`989 \7w^9it8*cQL>I0 iJcERCLl/nE*rܓ Y dI;*#Hj{9Xl!!#-.vKf^Jw6z g\mMlVNNH/+W~w299u(3+KG/iOk֬Py+QPBA< d|-Eidd 3LBbw ep4JANݏx´Lp:+pƽl,; ķ;5>kę-dD630 fN#%Aq>'v۴G>i7sZ7Jš-4i y|xEXhVv5=Irs&츊 K;#9ܱC̬L=a쬬V-Z~lYٲai|}-@>B6!Ch!s )c"u{ltj-Nw Bc 3&r[W-oq%FDzK+*#\"aZj05bĒ>i&ݭ@aSx)*AuI6 Wz"&}H*V'/JzB]o`KjFܑa@B KQ۶Щ㟭_swMG>ԙ|>5O;r}=Lt{F 0:Am?ڬ]}RcZ5{ >>VoӭlBs *T>krCqQQnA"gJQƞ9ّhd+EiV+̃|dہWr"eHS ˎBOc&ɸ; Nf'ibDJn*_X> f}dn;qMqS!W$=,1if& ΥD6ѝΦy?f"aN Wdf=]x6(NZ`IomƋQ&&]OHWÙ|:T*J[/qVj9Y~rEIXTN+)\҆x_u W)33NSxJ X׮LX4ɮ'^ԳV&ଙJo>P`Po ]f`gjZPg _z;۝x^ٱ-:~j~Qi?{msnN.njђec^|p݊7w?Qzq}pI2X@f/\99ٷxޞ~e]Ë7fpu΀-ڧgjGoV\ty?OܓC׫¯>qe?{ҕ9 L<ʖ'O~OOףNIxq U-**oAU*%Gzw"olӔd# 2n/*<׫4ALt%p,Lh!8Ֆ5f/n ®Ob c&@: Uڬ?DZFg"0n_.hnMsgY` 'u&*9rA=ZnfhsO(ܳGʹ "LyiL-Fl= ޼2SZ LwWwտ#X!C̉i13Xp*Y_V{6o^X;neR :L }IKxky\ ڈ#+Т`@l 5QIZriCOmRb֮yZSoW_D"o6zHwF떽xdp<ݯ4d3!1j ?6o{3{}OOxg`9 ~h/Hl>v5t|wݢ zo,zs1PP.~B<4<Ñ!ҤF? 8.$]ǟ:;`% kxSއw xjC΄=;~q+VJ̛w| gZZh}cfb'dq 0ipS$>V°hIg(3 (3$9ҩMol3kAPf[pQTWRf$0gV#U\wJ@{iyA j;&ԗ3۴\yF+p+>:^B`b 8}lї Bw\P;pQBy $E6ߘ6aQ[%Qʗ&\ʾ>cێ mռw?M5/X=q|:]{0Sj$ i?6{Gx&E^!_ @{3bP{,#srCyf./+Wl25 m|~/,],lnlH&ǽq`3: [AU۠s ^e)e+^ɞ&|KWBl#+ZoKkVJxZq͆~NA%4MT]َOze D;ONirN4+dup%^s[nt[i=Y\xslȮ[@z09p=%y ={Uwq/_W|P(vvD@F a"KPأGrLb}z-U:yaO4Aݐc qhpj붛t93AG: .sM= /`ך:Z@D- -GGA\^!_ @4 vkN©1o䃃M2pjSN~ /ʽxbÄ2ٹ4&Bpa'\)1zr"qM >:+,q ޙ:.lwBwæGPN313`76b6cxpR5 7/D"`ە]I6N^.ʡq!9pXit}GͲ}{)۴.XoN?Wttƍp=(U׶ow9m̜6᷍;_y^sU׎gs~膔A\Ă{kڸmfZ\R j֬N<"2Hhw[{zّۛA~n,H233<;7.69W4 A=aJP !_I[(:y*̷]:_Z]#3#eL6C-$8o𛕓f Ra(K'J !ŝc_܅Bs7dR$#!wqz@ 8+䍞0uC{)|q:ƹW;Ġw\b{Bls&@P)UQ2I&J9Dˆv{%Eyg(Ë> N|vHXѨҜeîYѼ2e/X,GD]>2d >/++0h(>tcOLxy)/o޲GG>%~#'& ڲuǟe>=z!f9?%qhKC.߮\~4jXr!Þ:q#?ssEw/÷_Y|iW=MnP4DyflrǺ#q TTIPfMOi ſml] /6;)-,,ܼe+E7kz҅kh _֏q?uj#Sxp2Clt&fdκc[MH>'6r]~$[op ۰i/T߿wDa]Wtt? yXAx}ß4!Ĕ3ݴy3.ŀ-9&uEBExD4*`LvX*JϞ'B\ bGC[Z BYh*t=m槞}g tlB;/N}{ugM6 wYⷍz}+Z>tcו`'17mIER)Q?[]ncEIcÖ3x4`:xK͏5./Giu^ Yb& ?EE9>P"Z[;uhl0IĠb@Q5߯|zJqWXAc#]9c,$feglH1b^tlE_/99m+U6mSϜ5fvBQ<pt2m/ORgo`gf\}N 8Qзܴ^D'XZ2;V|b=Py5XoLoCyNhKӞ5?m5嘓Id%3FHɢVJ FG1 VZQ)HH ziR ̘)By DAXSۖ0niңLIگ!n<./-FK#.,52k|OZO~ʹ]Q)ҚOL3s> x`̛r[xO8şo}i<_?m>4萑y[u G"&L|պ_q9~[5gB]N Sޚ1=:\mo.\߭^k<fAg?]HIgY# D}P($PIųQ&o0PY+qȸ㹂L{ü u>RňUJMQ->NKAۭa|-B.4/޺Y` YMp Du ։C"񌇪h.tք>S^~({{ײ+)(OkټVģ+ |lvz׮]:̸<^dN;IC"j$?(ʄ9'qgr{[uA/,,D[J zG<ɒp]W-`;5;kQg4#E˖ȃ$eo4bcҍRWWB`8o%BK)yYwo9Czo q!.D~"'N!8t|{ht}q]BnѰ~=x钎#F>vꅗ'  : Xsڞ ..^_YoޞZ5kXٴ Q.y=;+%pqEMvlԦl p +< l*M %dVj26!r{ סLKcO7>ؗHD}Ɠpgϔ0s iK:k%|KxG>(Hy闰7lwf@ Z&+XF`0~pHefdUSS:z*JR85*]{9}%bmXy҅-sX0ͅOwBub%;݀~gN4=/lݎGM热j|O1%Bau vYmo8 ڢo4PŕsCD!`9v_LתGQۄsg̩3=&ܭk,Ů>ҟ7`$#r+7>J+\`T"tuW_ ۝8׮#u{C&6LJhoW~@)(g2! zIǘ`Y$c̸=-m&6LB> s:3#] tQڒR+2P@ׄ@xu6/:^D ®>v&'rIY6hGx- HNioZ!BQD cZFy??ԁM6wGd\ԟ+ "زyyo'g:t\|: BʮLc5ݻ^ 9;z~MSdp{,Lg>A+(1C!qV!&I'~TxYC<ȸ* fbXH cӄ7JSqEN((` ^qt%?$E]O-Ƿ(J LSvRMsM?q[{g[&OmpΌL@xšڀ`vb*fӾN݉ztR,R.dOr?ڍ~E Sf0V9Fxۦiwβ"@r^cǎMyL>i #O"lWp{p.P؄9SXi=1k)O6>X|Z6;ԤZN\<{ԦnYt E֘eykT?gO\.W)(Xl9rG:Onݪ? uWuCׇYm7P5?\xU&`-r,d丛3q j3GuQqx+껸 cN #+JxđHIe8U.~gA=x+ȉ4ӇNxzɶBݒf6W*aKz|%˃o-=RYh]̧5*dZaG~+, E_QDYO-YdIH躰Uͳj~RPYx)?l.bW_ٕB}z oڴӥ/\,s}{s'8xgJln3ӍS8VT$)ۭ-8D-4?ưb(dG:_y%S1BTqB"AɚYdApe7ńΘc1 @DdָFJvn̛N.v.9v3kw؇?SGzPj P:' {4Z/+ J{ЋѯиQ:;o>\i..ܢ{umۜo;_cqןiyV)ߧ碯D%]CISg0M4Y&-hNR1gj)Dj$<]6UJ@SGQ I *ע(1h*I 4NkT{4'0؂b D)*3GR㔞rǾz4w%v既үkM6:*Z,>JAef}Mw/<1_~wG~b˗k3)(xuGҎiiؠ~][oT<RiJF,wyMKq"5?4*۸,x*c Aw)\a#y)R!,H2o]7#{.sTYyCPV3+^Jd,.?$v[+'']B$G/ۊׇqeVli SFZC Ze*Vh~)˗߳g;xrssM4mҸRŊ_ǛMOi|UWxUhޣwu k~jSp1o(;7!r oK@p踉bĥ:w݈3ז[m: (fQ*+I\p#:1cި5YRF@CX`C-AZ,AgEo$NIJv ~S&6UظfnapF1F q?HElb0n5p59Fl~NANQ؞H*d}o8u_a%0J덌H믞 PмE[d!ȍќq/{6"Nt&axz $wތCv .>˕ ^aOןK$fO߱c/\ 'm* fKUxIV7^{5خ)K_sueUmLݸl367v^q`=ȜV 38 ݳoQQ]k2J~}31',LHki5OW)hRмbFKQI;ā]7=1CE%iUoβ+-F anݶ㷍>sBA[N3z'탏?q|'J[Y oD ox:CfLCfn)D( m&앂&ڶGgA8;Sd7 Iz˘fQX4"FRq^W ̿ Mu Q1A: 5,fyp))3ȗu':J%F~H1B5[݉x!FAZ_z<,G?[ig$"(sʙ#V]7ʕeF@M{b [#Ty6mٺrZ@qzmZVz5p1竳j 7fi,>87q <\6l]vqXxVeZl kklfT=3a?ybwנr$c`LbQCsX1:74"H98 T!*ʩI}X(9)sbѶw16c4 2ū)cSb#[)) IEu#bh0A#E%ˌ/Զ!cgHL-zV0 xS yPϡUh< GQ %)UͪAMeZq$\;1`Wi IM*?bixHd)eA ўugcx{ JJ(4LCݒU.!㳋A\Z@Yq`F 颌LZ/m^Rv mYL!f[6$`]Fj4n6ʜD\,iw­x@2݉cRApYb ׿JTk; v']-.Q!| }477c0JYo/6qrVAonբefN퓧M|閻zǨౝ׮?&>?t.btb<πo !|:̯,Ӌ{rb¢.\$=U& [ѳ+2iE >4!#U9Ullb7e$M:(WOuy/OD}XWWbYg Od̝&OT֪r"O4fM#7SпC/8zg6oYz]tz V"UX 5{!v1DjE&#6Oؘ7樐2!R)$;P&+C\}ІN/\Ϩ~^=`/*jV Uz92ӕ 8o=H(҄~ཬGW,kL 2!kmh1fkEVHYq=Oއc"j*L.,D +JoD)J^(ӽK#|~St%ֵUPU:LEi)^4}NrOi+C{fe\gFaDK=[(̄Gn5ϳjǑ6Eޠ9?Yn lA3ya X klq|cCs )σVXtlnA#F(;]T?3W\e=c&d[-)Z+ٰ[?#'5E zቈOcSz c7.轀"c!:+:*eo~g+̑B)@n],P2i7 8-IdL颠]t%*B0'5gpF.AqQ\\,.# 4zԢ䅵ո9o񨈌hȸL7l3MC1clGgFsVM6V0eMWg1H3xN:3`@mqL! Y o8ֿ" >1Q}e>|x Rd(hƇ(8G9jڤxQu}|AП^!_BBS q[sbfFYC ZQPau q=Nun& \ЉƠC>x{%!w5}t'vÏTSO񺫡KK; ogҁ|o2&,NB6+9H_vmT/](|x ^!_ ʘJ=DӀf3,o˞zf̓?\Fo+W2b.t¢E_-ǟ~X(`~\z(D#꫙#z2 3qdV,2SZ)aYpi)لX?\ }w ^. OZ,]~UtrZ4]$mp1xͻ@ C?1"J4p̬.@+x-AUHL`1"t]Ǣ{us>̼"\%KfVQڑ-voD~4+2(#ӓR?zzN@K2 EmFWR9&(w@`Q#B}KND@l*GM+lڙ&&r"-*#ͲaZÀ]eЮ~b/p̿Gs@KQ@+Mu*fD,~H$J*lbkP0}Hn#7 xՒ,i͒(c+`1 ?lgtK23]ڪ#oG^em@DJ6ݴB)GGWB`PS,GZo"= c[AMQpl^kS_}ӝ{=w N:tP1\O׫>3#oDaqVJ e(dʕ9Ugu޽'$}oGDVBi@qo#06a`v^SД9R#IҶ&.DǛHظAbE GZkvͷ0Bg NtE}"Z(b$r~B*жF|B.U"KO[̐zWO6uu%6|BA?&'dzgp"}&ld6ӳL-^!3, Nqs c7~3s{WQTT4j(+V6i;Y,mݨ[yio.Uê&>OkX?fM-ڣWM+A0muRKG,$䝟܌AˋB! )E?*p}szF :O6q-wWXTMOi .̛oϝ~[Wu2~#mZz3!vgB3'% 0x?8@RPA]SP7)#wzwfVχ.Z4{y̳}DK@knPrO?כeDb%h f-(Lg<"2V6LSI322jV9h38ZG1O9Ӓ`BlYPt1m$'H[~Ṡl@樍)'OEȬ* D kX(|)4Am*fF\y3?k-[+rõϔ3AԨ^MPVxγ?.֦tϽPXTdcuጌPժUn>3Қ2E0?Ǝu C)biKd8膺bֳ̤E=E֮a.A2 PVuN'݅R'wQ)8^IdrQk5 ,)i,NlE>ۡ.GF%i^wO|X%4Ty|-(:ܳjszټ2K-G,I^>-C\Սcro6~?ҼsD: !9xvty0yCBo@4!QpYp1 ԁ[JId&͐KsY=V@UN;Y%"4{Q)cuKEx ]dh ҃ ji,#CX# (QOB9OVظNO%>7h|d@{-8mQȬIxdД'LɧVӇR,$suXk748RQУE_ʶ#z̈́ kҪm(ڶĉf:z>P"!Zn?tO`g86;\†_Lޒl O ăɡLIE[LW&mhs `1"[4 >)SwB"`"B{msMIz6'8.7I56%DVl-m+,Yd1gY"iu ۴[>NPfi:ѐȇرq s*M١,̡\Y.ű>_s+v0;(qvbKekL@⊲; H\@[zwsMQ:±>]#G.GL p# \We*$V&'^ұ\OpbLI{D䏭64iEռ&ud&72fJJ'`JHI`t6)8K+A%(/zO>cfq$bq`0HGXZy{ VDf?F C=/߱b<]jCAx+ɒѣ` 3H2CPZ+'(+0W*ѵ5zܸ87G\o=֠៧y\x*YmKd*MnX>RP,lGׅi>X7E&SF6*ZI,^kĉVuĞ d-ۗ(lisE8EpsZm,IrT#?w}HpYX=et1653^s[TL``>Gȗsc ȀgHq7SXIO39z3_#=iV C2F^9fΚ-ѵmx;kRcɰźQ_V+rv:uV)q^xbNAh#2Ek0*KNT,\ WFp߀޿ TA"ЫDXM@8`r^pؼ۵& kL6!-,⩹ħG:>NgfI\}8QMKK:V\e5W/3Y/2yۤQ?=rآW^lʆ _g-׶lmsc{usYm~oaȈOm;sssF804@/oڨ$};_y_(,^KKo/~A@Ͽgw*V:hPx_8Vt_xɤAӕUG@&>6u?4n┢^2) uϽ0lMv{zϿ\87:\x75S_sU& Gj}ǎ㯿zk/#@ttF ~~2;oPjc#F3B5UI e5ic+; 'ao"'Sɱńw'X?w#F6iAƉA"Ȳ ¸ ./ Uy<938+Jk^cab> qƋ.+%){iP{dVZ[xX2Ns b6co>}ࠡ?nnF } h^{>39kMc^z圳yw/SW&7ov όw)խ$8ŗ׀9۵7 kܤ!h͛5Z]O>3暛lvz>Q#f򕫞z|uWw_w~pWg-[. ¸ /,cMغm;`3&W?ps̸WV>\TͯRf>V5?pԞITbݓO{===֌ɗ_u#4բL׻ǡQܘGX!?qq'˗>ٷO+ɭ_Κu?"zȫfZ&7{x5O< U1D]&g ebG`_jW?)l^s|s<8c6iiyO]%^FU޳]eF} 'tꘚڢd#v?"v dLѰ/4=EgoZx(ZC|̫fpP@Vz Ԑ#DÖf 3Af=Gc2H ("w&8P:ȗ kw];xV=܂F IrW~CZ{vXAuj=X/;\޽O ժEsi O(N،vVl) F$ɫ{I,J{d=.HXdJ7QBb\RS Ch"NWDD@ P$ޖ"^g.7NGP8+h9b4[9E⪽cǎ8hs߲ uD !>>Bb%2`"A'fVnn.m@ pOo߉ڸiKթ}.rFV /QưVe<4C :Vu+ Üy󁆝/Κ76*뀉-Tw`y1 DDߪV_ pC-7c[K: :)x͎+TO=ێ:eb[Y7f8!* w+=*k LOF&֘#bc4Bo0,@lN#@Kʧmi}DŽ,y|pq^|e2_TTH9mۜ_w//:`ulmӷVWͯ2˅/5㍷>f foo "_O?{UIxՀ kμ/?c6}烏WR%^*:;;֥˖r՚aj_u]~Ll޴eknt C/|}Y֬Z5WK&kPZ5S*l^=x@A[4k .)ˎ]؈esm^KH5B. iΏCjtT:nf+ziG2'zJzz<[G*G h &勬Lx1Q l͒8@!Q+REG,@F*"W]Q }B?I֡JbOPގ/IZ$b蒭zgh xG)esNtw}JIm4O,44Uhq.[;- ]z&Vpx"dC>ǟ~b"%"9s%϶n~@?_-3W^̐#yw>kd@~ӹsvݏv0t@|]A:B$ @.<;v2ygԿoUk2}fF hR }{/_nBrߏ=yGgnڼu _. &a@Sȷ *IyDw5[9"幱/?5lȩ4:mŀ7=wc¯fΚ# 蛛z)P_ ̐j};~\_qM+B5mtr͚7]w ޸e7[k%@*ǃ|Ow (kFF I+NObH0.\ 9囲%_D$6Ou"b'PAҥZhүs.>f|7;3GsO="mk n^3^9˯9;=rMrpR*kׯ/eKWA?1['zsu"hE+TatXHP4aVݥ⹠*C:>쇤Yz/dfpR8\f m,+?RO)~ '56} =+ EiB(z7S9G ܥZSIXMO)Aߊfj HPiT!JJ+Pm, ytKySV~)iOW"-W뮑+(,tB6 [h%'ST9|{j2q6J*S0r~d^~~&dg^MLHX.Ane'ĽMg u&4ࠏabcij8x81lq:c@JA1}^ż)^ TI ٬й{,9ɽ.W ^݌v_wIHuBA=iC2R|b%eCT+襒V pH ^#Ea 96 edt[*a[?P~:W MYnÊKYIX}NOAf5 Kx),F-dZ>OzBTŌF-)@idU Y5ΓD|:WVdM91_51DaϕE o+y7p\|/v>m*CAMD\t˷&[PXTPXjS)WB",K\xɡX8d`rpONz|6DJhTPGY}@PZ5RA&ݐSۿj`{lbbϳ%~l6[r$ $@0e_윸\ 1J|AAO`YH 0q.G'R[T9~2 _A_?R[..Z5-fu}91-UJ|3Ox?d秦AU< d@ԥq@d7jpu^N3TF2"S"*Uut$Iy~ZС^l+_t JF? sG(-1IzYLIJqja>MZPFW܈HT&}9fkdo^z|e.,mC}uVZ e(* (ظ K}@.}.o]*4K/YE4 //[ÙYofP<>b3rvrO7y'g6{;w-zሉ=gW֯8+o9M'%%L zarĸ=C@<1[= A@X^-39{BqS6>YnNnxxnW{ ˢ.)qAF!d$0g~n[|^p1Jb|&߯$U= } R'b>__>&&fgxUis!*?;b-庽ܼĖ >R-3cY']7e(EdФZ˝c1ܞRa?xp\\W5>bީPDFcD4y렴eZsL~Vk"Xqj͚k V esH3j~݌hŎ>PQ\QYXX#5b<-QYR $DTOBV@Ŧ;7(6#d/E@ɹdek0R_^r!4QU#9rU~%:`2+Ib i\-ϫIyk捘A;`2Ec)^E-`}>`?&/+%UP7 V i`X64#~HpnVrT87`j@(J4Lײ51­*YPZ4؅|O>*9T< AJDJ#$/o M&Pk }0VZ"7U[nCY9+(ŀXXX*1) `%c 5ZY$,(,)^e[,q$dA"[QOrS|1ɓY9-?!_JIH`(D5 5ɅGI%>hYnC A\˓ 4I"AsV *2E"Kyzˮ+t@dkr.~(Io)zyBfUjyi!ʿz'HyFP|{ᮉP`ST}XIV()ݵvbooZ,C-M itTiiɍ*$Ybz& l9E6J\){TC'd/ܨ=gYa#F`SO['Z ʛ Q>(DUC#+ŀZ5d*BT8]JsZ@\Qw ALK^IFbT(.X!qۖޕ#q[7ޣV1h;'S) ĩjWpgܹ~U<w_Xq"ȥH*F@+r}x.D)bڏ}o\X| WPնD!-ʊ]/ڿޗ_PDO[U{>_Pݒ2)1P,YOwJȊ)3 IBQ)qq~5Dr+Qq8mJ`XO!BbcM*yo%%EE+jȽ%9gYͽp1!. 愄*׊VA{X *v?.^H$TBOCfl6D9z_4ŗE2t(&ʟS)e_'`XxZd%]ZRa+<]$b]a{|Sٔ%-KTy1LZ2#fFf*IeيYUVs-RdCRe#Ӂk 5N߾}Kkyyy?SڷoߥK2Ha¨$4 ny.[(.((99>~3UFoZS~ǵE4y˟wx mb$a-&\F2Qa<{}>D&ن$x>NP#ǝ+yR1(]<Ex_aՍ9ҮG,HnQ[DqSʭ!]uX,Θ\$|-'A<ұQ4 g 1`BuAY@e=/@*P( L ~J9S]e=*OS0+VA/$lѭRV{!KQ+a%g*Mc)蠪ӟҲwԱ$3!L# ˕CșE]*SV8-VX Nua'CƦe M:-v(ի ӹnݺk6%%{!>>^+ܕZŢM|8D}d3=Czk-_ ~iĸQСiBF U[~JB#@؉O##l&E rac4sc-n<"̼sp-YRmTaS%%~FX*LVEq*sBN{5S~c|E#NvE%.t+‚:f@-y/f>z'j殟F+MBjn//{+Maw0 a>b}5{cXmM%#n݋Y0;,v TB>g齀e|Wĝª*yoKOUZ ? %W&Ge$%\ߚtIY?%!T`)chz J[;#ϡR`D쓢P-؍J$!D)ez&i|^4⠍1 cĞNyVW&r4RQJII}k`@ A`HIIŋcccIqkt=#X&w>m~ݰ.Ne1ccg=Z?1uݗWӼQjLzvQR̟7nXwCIwdJ**VB&t2ˊweVO}'%_ βW3 >4!-]hy3&?MT*oV .*D(T-Mңquj~hIe,_bHEd>iV+ ?rZ%~yxcCmJ"Dl2o\s*k-_/Xƫ: [Ӻ4#VXF4MdOp$4bOQExRy&wMl&m>SX,} ZfP"'^lH-34@nZ&)M.^&,$Rs:Wm&il~M*4zk6P3jSQ, kZ&/c VhQIA^4i%j׮]z.]$%%Ѵfдn [.&~]φh\\/w='_OqMlد^S+rn N*P748x2 A|Ny=}zK}"+D"J@ҰRIWF"[%5 (O#&$ĭ!ʒH/ĝ Q0g4QXbͰ43FEOPbi+[U=jh~p2|M2IHͦ_dB +"##Nː%{p=v;ŶZ-a M54\K&g fÕdeT(k0*#cF Q2-(ӟpPoKtH+K &h&讴:Te/|+E|?x@6&`el(B!+Y,פ4e.B) %:YޠYI% )ݍpݱc j &<3[lVݛ98>ҽ9tmY׫X/vupK+ ۚUG=r|by.|h"jZ[Z˿blMvH)a#^+E)\\g( 48叒^n)/@g$F~KKf d 0&f1XN(*7n t&Lj&`@>p&nk(1{0WLU QЩhK4%Jf=[ qm+ .UaO(D .]Aӡ 8JR (u%j(cUQ~#e0(P:1k}g k8ALh; [Lߔ&IPP*l(Ⱥ3U J) bDi'aw)Im *ShmOӪyV-XvK$w׉S%v(-jcx)//+W,((S͛].WWOmMm;~5=箭W] ,LU[evzwwn \մ`G mDFe_eB8*ҵ 'sMk2ɾ4#9T$M7E1e 4tBCPcKD&#T#{vz㣢}h?iQQP;VpgB[&ădDz}Jox @~U^e*'ŔU3. PUp{Ê),?~ѣG-KBBC͜9O>f4&ܟ۳֌%9] K@lPc]hͲͯNU +rUqڥF8r*QԪAhٿ~O_7y/_HHxTAYߧ)W^5!nU7[lA1@ք } UKaiS2Q y[b1YMa4е(@N ?9RRa10s Ҳ,n)ߴ/% )V̑n$H|4EЇ{{P-!J6uoU ;x$6jY8 (* .>e戩eh8J?٨RVʎgPRhamI|hۘU&͖I3*"(*VFAq+fRR2?KLTaVUqIdOF4Ȟ)=4V6IiRkP_tP<uDOju M4mf'N9sjȭ_+ {gAa禖F=̭}Q3juU5`Fi=GZ?eH@ɋ rC~^{_쌌 l$Z$|.U%bdMRV˰1~m{kdHŋ@(oof²Z.1pE6eŃDⷺ!>}̕={,}T?KzJ)u.%ןﭼPwӵ\L5*_ ae@%_UC*We`f}}I y__~G"r _qr1S'zr>2cĿP*\2Lh$@Ot2+K;\Zbe3߮ͬsҴ:a RÕ7k۶ŋ"> W^"R*A UIȍq+Jښhtb*PM_=0aKnz'k`ɮT@7_#xJ+Y`%,g5b;+WZ[yJD-1n*"ɫǀPt0gMsh Q[gTQ),6Թ:QĠ ue~L%IrdL@N|i-!x~l śNY*t"޷B.2OYE4{OVRUҴQX%FR^2=)gÈDiT$hKZ|JM* Tr=*@[B+iBHsߑ{D!$ kpi@"DIXZt!Z`KfhҀ?cGd/04>=}^WS{#-@i!٠#U%*p PbUG3[|W١ H'8i6X* S3@ 9D)rGpդj ȷdCܳ-K+t&}*B՝R{.AHԍh ) y.~sPꐜDyFbfLRa ٴC=!(2ƨj%AUX!?ߒHӂLX&H2rxEZ7(Y*"EIDMZ~e5[KؠZ*ׂ4"A<4Hj;V&*Z+YdJ*ճ4k[ $a=>BdLx|b8Y9$> )Ӆ_dp-&i)RTjR_E UJ%+^8?)J AOk׮F".A]Bi#SeC&o[Yɾ:k63mosN4?#z>gbSs I0@S GfvJo Wvi:H6^R˽Q|FQV9','#7S+e1(Y;2%$zgIbtdLi:oU~YP#WU IΤXRoK4JOeEz49U(եr)#Gl+$=Ԫ)MH|/TjI௽ &YjMhz ^C.5**Zmxz]mE݀j ȻV~uh=OʿfCZh4KI(%M0HwB±+$eYK';lGc%RdIiŤt_}J!+b1QuT$KJ*6uPk1dՠ:wSP Aj) Ԋ@4<"&[lBVjYB~o+9 V&%փ̧t%tZ%F%SG-:oUZba4ih|Y>R^QnTpi OiUUbEf6[Zͦ Q4$tZQjy+o>Tzzd[T‘ph1ە\-   ѡr2T'\Z X6\ױKNъE%jZK"  ee0PRaBAAA    (ђrh EAAA"A+rAA8TАBAA8ի, ?ǎ,DBAAAh\ܛAAA u ʰL0h EAAA"AkD+>3g[{}2'x2XFOk7Ycf4lRyEAA)_l2PR5xp[6WO/d{-emz p   Ri|w'Ǚֈ\(MӢoK:5kvK>/ST\ pV}^ݷ؋#=JHM}w¸md> ;wIOKI͛5۵{ϳ/:uޑ1W&}0b1>F.]B+NAAAJ-U.9t~ ^ .ڰ~n70??+oh|ߏ~h]oa҄q7} }vX;71a56tsO֘Gn}=ɇL|}~W5N+NAAAJF{^t{H⭓YYI^='2\٧.^ n@^W ^YXThR.m[Qr>coKW7>$Zq"  T*mJvwm1p!;w5sZ5~vGM}Y($si JHPѧV  H%>3v1l>WљBr_{ ٻFqy{wDyKכn#~Pm2hgع/ڣ_[4೩ӟ||t'  RX]?LH_3̉G[*Iė+ KQ~=r)P׻}uċׯٿkg$ym}癡O<:쥗eԘ}3Oٱk7qB+NAAwjJzȉԚEg/pTȍ3?ݓYYW5N꒓{uw{Pzu;~{)}*/q"  KRj4kwㅜ3r>j*JvV~Kӻ:_C>_UFߐ-riO|sWE  dgzu*ȥ   JJj}O Ox]ќˌIf0yh{n-*Z F[@vWrPkw/$BqE.  M[u5YؽUZ.hoL-nڛ7s㵵 7vޤ'e&^((?_'< 2;~ժ8܅q߬ztqԤnTv3V=vqFۃnY-S|1DK[-X/'xn#>_&*C. 4WRh EAAhr{Ϛ#j~.S^%]'lW}t'uOnwc϶W/|xmuԓC?.vc ~;ؤ5H6[9׫猾G25횤}6EzfDݿ~W %(bu T(   Λ7o۶mi?U=r'Q|mι{cbbIM+aGϽPgأw1Ѕ 8pѫm'*tq:n?Uπ(Ywos,,=ҽ9魥D)Fw' PAA aV^?fm|>Gnc~jh ڲAkiT;d,_Z\X͇]od}>-\S'EEMYHPXA,>H`T W"  HYhт 7o޼˗_6l֭[/]=ܣ-zhF=dw2 7xhA>\\@jXsֲ]hSoi}JzGol~u*M+N]+{#\Uz(ubE.   {[jx233?# DSSS%779r,BkUo^CRǛSo쵽6:z}='f~rύMziꃝG&n)]^iԌ?Zicy:mnzGMT^Fi=GZ?eP) BAA<0S:ܢEVZǎ]KIIٹsRDyU|m2g/VY-Nb6oRۂYsKKo)]S|N 7紐{4f@txo5 P]P"  HxԼcٶVjӦ Ĝ>}:)) ׯxM&Lr-AAAf_Rk&=2y=>i^/qsPW^2셂k$۬OF.-tvV8x|0w=[-X/'xn][fp5G%;AJͪp-^D tDjժ9ڵk4hD   5jh֨^Ӡ~9 qޒԔsYǭb }|[ݚnTU5[Y:nƢ珻 P7~yiضj29"eQ"Q鸸xn@T  6k]רم E"П =c-q^YA>+>6jR\\(c֪~uw9șI{cӻ:_Cnmޗ5~Mv属-\ton1L_k|)lj1LEdwQ"  HG?`ӎM7fbY\>7g0^㺐cf.P5ԲA wW}^-PFXH[$kw4H~t,PX;"T  _?9K^Ù>/vd,~R ҫB}ϧTom7ժЫYk ڤnGM~txrV k:wⓦ"W=:Gҁ*AAA¦Fk~ի②޽S{Ki (ѧ"'oh#.Ƅ7F { ,Сie&}fu}nN[[f?'M#f2(U *AAAhܹsI{ p7ؾ3fo=zС?-2gΟ?5k4Qi/iH8-p`}=+5)_Ln' wҢ}]F%3)*liJ T,8$/* 0_evG, n]m;keA* -((ؼy={4iRXXK'N@#iӦ͛C;ذaCcpRw}W~}1qƍzܸq3g΄GB nȠiz̘1ӧO?{,tw}7%L),4w4p.6m ܹ3\;1O?{jժM0렿iԨ[liOe1ccg=.G7};ad4{Xwc!Q8t*Gn f6H6~MuR.F 5gwW3i%U斘gG|LO<֭ڟ{`vo4[B+DBS 4v }Yp!q~=3O,[~{O _pcۻwo6m5œO>y-XZp^x18L,Y4bgUOyp8tSNM4 0rߵkWnݠ߿?t7ݚvM>{mps.୨#׍tC'sT(8v:4=eXKv(S]kア{s|ܷGbPGN]hZ7\-ĪZ.Yn>-zRW/ߩeFA"٫o<|cY5\ȪA 8 S%ոu)A˛A֭[7;;sB.Ш?'p+W[4J.^yhGQzuilfYpb͚5s8VZEֶiy64cƌ:t> >q|饗N'D[F ի?ٳgo߾… zP-_ԩSe3f5?ܽyϑT]) 67LONby.|h"6c85qp7Km,u"-O_JRlEZ.C GK+ rҗj>!Ks}~Vy]~TU DՈ>+pT HYs9P]veeel>i~i۶m#ΝKIIl҂l"[n۶mɿtӦM8g!fF59-Ϫ̟?_lh 6}LE v%ݻ!C iii\222V?jJs{M6OH_((}wwnՓ}ߜKe.Ӕ"Г.x bmy'ܵK@Ԏ&HωQ)aex"YFF|[}CU )8!TA2Q]t0`~(РM7$j ~75k|ܹ|/KǎU_nnnRRjrZ{s]6m:z^{->>_'uLw]CUv^xAt2I@5f-X,TU`mޟcg6;r*QԪA+~L3, PVdֿn_|H1۳֌%9] K@+$ʣDKQ։鰲atZ7xkXuʂ͠BCV) RਆPAʚGZ|Pt&W4j?lԨٳ>씔~A4 o>??6m8BO0m۶~j݇&D59-RG2'Ӝ0}A=fYl!7Сt-ω'r- 8駟V>Q|1F֭ۛ>v i҅P+9~R]mdjx!F>鿯zd{g~^{_쌌 ߬b+,Բިo)>ʣDKQ։ȲtZzq߬GYSV) -pTCQ eeB֭vbru׭['K&ۧJAG5>4? sPD^z}=K /iZ|*KWF" HϨWn, AzIֹ=_!z.]:A2j e PAA ()BAA$zUt˟cǎUt* N u>  b:ws/䑋Jwx%-ˑ ; ?` _BAA*](rN*Qӟ Ђ(?@  RB*Oj3P H)@  r)rlJT(D PAAʍQ(-+c J@2A  RYӟ4ǖvHK-`x7AA(.3#Yk`Mlۑ@s, m  iliu\\q\2++PD+T  p'fZТfe)kZcb 4P:/eܢ{OoqbŢ\/sXc[n߲v2NA.aP" TZne|O N{Xˋ7pVq11v+*vM4e ^++1vNg \jbJF]cn} k֏h_2U(  H$ j1M M&qᬒ2$aP.dWn*MhAB AZ|ijY8cGDt0 d_DH_wj^Cۿ>I%towppX@KT  n7a'bn7xv+"bqhԩQч.:u:H`z}.HZP64~بϰPlFu[OiԢ"u8셞#2X3Nz5jiElצ>a[_[2-HT2%驕 +B^רaNN6\cW4ؾu=6oɨ[.Ν=kEժv?2jer`]Y !,k&mV|ȒFA+  j6l6avb0&3 nqeYn[M&^0C67Dskw!bUK X6G= },rQgϩ͖{kά(y_n¼ 7M+-ݣZ,pWoX2דkw7pE nڸ.OT蟫WwW\NHoԨrYrNff v3  Z-6ޗܠźVx] Aƀ3[ttY-HY- JoKzY-$3!K="OhljM3sqnƓ_GܳOz'u\oZ*4;~ժ8Tv:#7oJ~b/&L|sZr N .fkT?&uSd4{Xw}UTrƌ{kkV!Zv_N^ݺq"WW mԈPЙ7v .\R*P)^ -d6ǎMw  Dy-N˲ ȦDdZ ˚M&<1vÔTf7:gkm'䁬#&>}qV m<R-.vc ~uu֞2%;7KH \C_/JZȤ5H6[9׫猾d>mnhqdS*NnM&i =}6^\qsi+6~xd\!\*TTYXXuS'c֬bPgKADusUVJ( cZj%$Ѱagݼ?|ErܹZ5k熿t:]q-3~,Vw}s츝;O;vU{+Flps(/^Ũpȕخes1EڒuͿnڜlݾeמU#+ T6@>L&b ÿ R ǒ7TNNqʓG[mcXg"DiRizqa!Gf1d8a_PyO2|F%\Z&\v8;$[SxNaUM ZRPu!-rb2z}BO~;cz`} Mx#qzmD@n=pjn1v#=6UpY޼Ljo\D67n֪>/z͇/r+3P6"9rq|{spUH"Th~Uyי֭FxgmҶ]\vۺw;yq}ѲIIT@qk3O䠁g~ӳwo.O&Osժ~Y#O/g:x`ƍ$;7O7y P\E y.y?{)֩O/@nTKzއxXd1yd D|rZ^ \n+*. )Ng 2Ex11"ѳ3n"->+X=BQΰՃ MGRK?Yթ>wS9OB5uA܊RԿEDy,sWר\e̘MY؎$jaDBEܞC帠y{߸oPmS>!CLzO{h/>W~#_N|g⸱ޛ4/|B袉 Y0׎4[z|}3Op֭Y>Kz(2DCqR\7kN:{{ܕwBoUYI\R  H9Ïu[6++YLNmqڣ8spٶq\lw S[JܿOi$UjXsֲ]hSoi}GN6Z57WK jZ-YթFj A\*4F*U.^p5Dl6wo ]j!CJBBTNJ$O?p8~yK]U|xNCA/8e!>>ރ??myykoF hQB.*1ha<Ϗ{W)~)|7P;*qAL&o`}F<8F˿lT|B,%1B'rIb-.)ZcT̻_+v vb6]X~"ׇ[q6U }WI!&D)cXRn; %)r,1b\nj_qf&bw卮F+JKc`g ujœ߃cJJ@AA. aaIO :ۼt|qmVN ]8n.;e[SF q(SeAY:ETZH@OV*6B~9[kq' 늍HN%pvm]M~Ɲ9>wG6 [aA*g.[Zrk٠ VOM=wke>322FM~a8z(D{43aϿ8;c:$#G4lC$1FF9TRxOG*ٟ3TtȀĉ c:y?zGAKno"z@ ,ɹ,;8- ]%[Km &=j&M9R 1Ym[m 7?(> r w^dZ}vcb&0U,wܨnINg3&&So I)v;թZoK,7O/@=Zj)HePPS?S}{)δ?2ߗ/{rwhV~]Lͷt?r3'S>ٸ~k#_Z{nN|~! ".T9n7S{ժ"Εh ږ޳{&R{P}J T6X @vr ܨqc7nq]У׭5j,"  HC̙:ЎќX;jX-r :A╜)ƫ !-Q uccc@B>hx:o~KM.Jr ZXr { l6V?a-..;Ҵ9êRZ*&14 H\)*4-==66|ߚ@^ %Zii:>>~>0b{WK'PB," J( ETAA< R#HUZH$Ͷ۽|/NM|0o׎׮] k/|zI$$$lٱ^=?t~侷]LWvlv@#u uIQ W^zر0P ͈Yx=ݻvZ] :MKn3ijhrg < K`0m!| fA)Ҝ pX,$ڠदT\B2_N7 6 9'o8ICQQvaV>}m@20Y--QN'HQ$ p^z<>Qc!#Jy2lvvvddh11uIV)jIcbcg1 )ޕF͆2e^{-36=.Jxࡇ)=iJ*h{PoTKi{2YVGkۉ`0LGK l^  +z\sh"$ARݞC2ܞ@."l$py^$ YΑ! ݂AYY9`SpB2q=5By. T2rp)ʿ/+2 WfiԻ#1d qlx81qk`0!\iKP\>Yw)7G}4f甈֗|Wޖ ѵK݄T*2Y9&^>pom}݉|WLu,1qj.JC>O);3L1t: B1:USժ߿`0 & xV"yIb^5ܛ>(Y )k?SV)d(^=>e"1`ǎ\*V;11-Z>oE  (qTd 3>) C.hQ3?)$ɈNrLa6a|J"M,A1Axx> 6`04 w_}j2,C;I3Qx^"m6lv\+.#Xb0% U(`0LDk5np: C%>h":!|>p-myn& +r1 `u}Zq$"(rAkUк… Aƀ` `0wbSݫ!N֐$j|OժU ŋmBqWb0 ܕi"10:7V ,)2\ `0 zH&S bz'(\ `0D%tb "S `0 )AЌ/w*O~I4O*C(z&?'$I2Xb0 $Xم+so8!ŀ (%*BB6`0 `0)%*`0 ^ hHf3E> )h0 <D澊 I]0|R,w`Yİ$M,EX~"OԛCPZ\ CF8dŠ/Z`"L\Q-b0ЁU(`0]e[lf1Qfg=05;v{@|4:Ui6scB}\9 R$u9GPz4Mrx|r HۭS=BBB&N$\rx k-yA[\$ErѲ@Fz xm6E*:LуU(`0IehʴÄIsl@M,c "prb3Yi(QaA=" dL&wD4d9g< !SMy3b_ }ѼnnfU~q;sZ_7/ڡ*@Zxk { 0 U(`0C.OFPAX^bHҋ]4lKįDj!D@rZ$bʲ$xO)3OY gl-hRMY2*7Mq L)``0 sx !W+)f2Lg4K9\iԼƄQ4Es}ˢۢx}!:n7fӆ.J% d9C4y}\>^F/zs2ڜ1ZJ\ˆ+rx2W7ɋ w}8IIZAf<] FB1 `.}!|3B84siCn~YQp._wëf"/LFWG(XE*炆2c hr>tQ3F% rzMwN̕N^q_JC&G0Wp^ʽb͊o iS9106K'vx73mhEkX󝐮9em٤ƿ۶pk96C߃5 ~qo5oL;nH չѶg)J*Gۭɘ"IӐ)iPu;4>N{++~U\ {6.:M6]k-a-f|ʔط|mCj sRzTpF͛S^_)Yf.+ 2L'TBN7=Y HC߻I0IVb~y*s_xQps΀;ܮ]>|bTt%vaT|SƬ,Sމ4!y |4:Ȇ[ *As 3GgLD+^=8T뇫=^+$DEk:OSnC޽;7:y-Z=͛g6~s5jϝ;7a„8p={vZ~q!~ ǎ$JMuݺu,XвeKy$u&ÇoڴbŊk׮mܸ16mڊ+\.WfVZ^d̙3⣏>xܹsA}3f̀{#" ;Z͚FKΙ3SE YQjZ驵(YPl$7]6'2UHlS+C7/߼%Q?8k W?y!ZI}s(@b~aZG޲ef{wkP7fѪיm۶Ŏ{]xnkx[=zԨDnD%#Au2ek\ =o׾ݻW._6mZ>}}A,\|L%8xxA@M9ٙ -SVl !҉$Am+-;wjďK󿆟 ԉ;48 &0J Ep<˗/O_Ο?ĉիၔFӧ0߰at .9r7#9<T 1 < c IQo Iu֭ z%)) .p ӧ[ $ OCBY:tC?(ؾ}Y``4#xub-Z_8ɚ_F3,( z׈S4Fa⦫fYR+C7ѓ4V`cUj`ktR@yx2h۷*;פLMmFgcC)@zzаa/֣LX=qUFj?Hy]!K&j…~cv͛_{ubC˒fnޏGAs\.pX,Gd0M[D#(KM{l""2`2;r2ɂ޸~L|b[nw [7 *IQB~Bu ozJa*UNǏQhJ*bڵk׹s_|?x tM6UK:u zW$#OB磢$B_.ܪTEkZ +Wr,Fp ѓ5TVͯP x8KO#NYPl$!nm&Ț_+(P,-P1!U41B505PؗWO?Z-Z:rG|]*NB#윜 J=2ڑI񓱐-, N~a*tR=BQvVa|rxL8}IIIWX0bĈyfѢEFHcǎǎٳ333?$$! 5,G:+Q#e&t6f 5#YKP"ʯ=ygh- L Z o1ZFWƝ 6E`ꉓPD2uԞ%Y]zHgalV6/b FhZ/G?z͛o3xwngG:rCvvÝaPW$h˰ : M~.-gf )~r$hxxhQ5"QâAwNj^=5G"twlQZF7nTgI&-˽{T]̙3w޽tRH$&&6lP4RKBLJJᐬYfzzz2e~[|lԨ\\~]FGO[Y- =kĩXz:[F'SϟWP̈́fGgZ ٘5 6FCͯO$k^ziiih&MLᡳ)5ԩs֭ *uFF|v&8*Q :2c =YYb՟}hGJ*tѣ/YZNg_bNvn߾%DҸqr?ަ4EDF&I3:u\vVG;wU&L7oظ_+Uҥ}ujGEŀf̌}=ԣGOyeUp:g飙W?Quߺe+gԹk,/υb0E=B g7-t{ȽڵW^'O {RRRF٣Gh֭kuŋ$sΝo۶mO&^֬Y65\[A_ܰaXhѢf͚IHgz POBѢcFE)H 7oڵsƌbcьm|t#ΚlBjVl_1B joMCҭٳT/*2GwyۨmȠAB 5:_xڵh׮];aҤ'J?IJ;ey6x͘1J*999=߼%11TB j5+6C܆ y|W˯8b@=Z6Tt=vX nSgi.QnRR ^V#N1Q~\jy ~ &B ~3cPVGZ]zMЊk56j$lu\C?%KNNV (A{&mj*T#N_ߴ+a0 )=*(q\/0`@q`0L gn4=,T JI&+W@?t tv-pr2(؅gM3w*sϾhCdTT^[=/]:u~W1z:vZ!9k/^$l={V`0B@nzǎm`0ߵwy]Haq-\< G<}Ӧ[xItT$rV u&θ6тS* 4-0z *v#GN}W._~ߞZjx:)B F`[}Zg:YYY5:vf{x@ &`~O_K.En6R2SllzzG}ԫW=RIhWobIGpR˩x@*,ԩӎ;ƌ>~7uօ ꫯOSDDEEuO?>|?o߾}||v6Kw|#+@,^*x`Z@BFM׮]9kԨYGA7+jw(N8T9}6GFr'n'˗P>)k)Y FB)Q)))NNN>uȑƍ_xUV0ӦM[bj֬٪U??6jԨ? yѣGoٲfzvaE͛g6R(!\޽;Q\sM0\fϞ]V}?~\tcGqPiӦM+V\v-d=%p  lYFu,Yd̙pvܹ0ćFJ)7@Rr@CDf_HvHCD(~+Ւ0ԁ rԞkra"wӦ'Ux@|U+ٵ+)9I>$ O<&} 0a%(SڢE<:t 裏S')) :pxbR[\z5<ı-\~=zIB;,/_/?ĉ/ }F 4Ɇ  v9ƍ_u?m۶b c 2DZ堩!@lZ{hIQTO f?0Ql5@ ,@oUS=(QP ن%k֬\x_vtRJ5F$b$N6x`Tݼys\HߊSk9jyۺuk%O?7|ӡCpw6li!zH4i -^;B WuihY- xa)D,\{|_<)TeN z͂ k>t16j4uۧ8#R i, II'%0a#-ES1{-iFo9>Zj E# ŋ+V6uTHMM!wu!`*U䱝yY. ŰS9cX# AFM(K+?Y-\ r[st* k%wMz><}_N xm )P5#-ܰGw-tWӝ3 ><7JXc^uͨ'$f}ziG[+7W)2J [ OѢE 聎99'MµC%`n7 2͢mX|9 n~={BoڵW^'OD III:tQWtt<`Ϟ=w٥Km۶M>#͛7wܹs?ikU¨ٳgag:fS=堆Z1Tʈ_+ί͡j÷oߞ7o^>} _ܖBҮtV\bc)6eԨQ7222g_ON y> ,\K|[P=G޽{C#WFS;`:bGkQEBrT?% z$? Q~o%7 g Dn@eۯ1Uۻ5tӃߐ={'7nY.o]lust0záo.t1ңB[lxf͛7.OؽSNP9ڵCk0`s碟_MoXaƌUTy&8\Ј\̪U`|OК5k `TݪU_~E1̙3B@!׃iF̅+گ_??r?ثUE>xқ_>$ua=_|bz"Q_5fh >n}(=*0.kh#S ! K+ ѣ S q NR`yvȗJ]d2()Y y 0h %3:SujZl8E{eYD/:%Cg9ee])7貘4yĘۙ䏿nw.sK~lB^ьO_}kh<ɇVt3 3F.T&ꕁ =,:Чu%(Xb&Mر `0QBXnԩS3̝;- 'p!͐)Gt:2:6w:s=s^RFA bS IvjzEvȓ0!."Ϲ;j[%2_$'俏/+guUutv0 U"{fOծ@֎_{Q\ǫQ`/ SLGJcޘO{2DcHGp'U Ikw@&࿇B4g5_;oh3[Y5U먘"wl8䩇",U(Xb0 5Phy]w [JU֯NU׮ G=F(")Q%Og +qw_Sϛ?U}e۩M.TbN|~QukŘUb|+ .c5W$i?*ҷe{<=w?.j_q+ScV;/EOX-3ع V `0w  *_Ft^z2e9mS9}R׭WnlL;79\.qvvVzM5%jrR; ØfNIE5mיIq6芉-fXkԨnz=>sK&r3Kaa(*ժ/W.;'3,<]>$I{T|vMabO9+mMOfge={\jVd|`M$E[H%Tu&?gEU-q:S.PaM҉.}ԃWϪ)_&r>~l$ ѫ(J:> X?\Vex/S$P*)[*3)H4Qkܘ8q%ǏA]uؿ ZR~QlFVZ VݺORrZPlp 'Nie+^ =n#'՛_xAbN2Ug_xiUOmٜٙ Acʁ{h6 AǏعo0qfSJHd6 iy}]rz5+ƿc%0B FV1e͚5^8EC5 Qe(/8*I}}WޯZ;"AgAeG|Z~c(tZJcB/U2,}Q}h.UdİaYYr7[,֡Cج;7w IQr+9aI >ϑp E*A7l #}Hԇfi3DƖɺs?ED1zX(m"Դ|899ԩS#Gj꯿+;vyN~M+0ԒtJ/ZtΒ/E|\|3~ނP#{`?'p]@="WySεjE}(֥}g<wdy )pW4n|ل2X$#c⣣/Jnŗoڢߏ9dEM`ly_aʅWiG Bcz<("в+4NnU `Ĕ j$))  \/^x֭CV^ c4}FF˗cϞ=Ă! ੶p‘#G Exݻjժ俥 t0A<Ԣnt.5F8dI"4(۷oCx1imݺu0kUQ y۶m%EXe"8GqapO8Q}$ \;9 jm@E#%&7nM;tIrZlY1f~Uk؁BRb_[f @ *W\jӇm-ZA`~G}T;x޽A<')YPlEԺC-YOzS4&$n!b4LnO`qTTܹsLOê`:(2T kӽdh1 sPਫ਼¯p'^% s߷$)W^i>^Dݾ 7)JĚH XL-[\h ")nx -IKɉ5:Xla^Ұ }( 5G`\v  QC]x8EyȏNxx87nxaڋGptGӇmB8Y;vǏ[j+~YSDћZNI1BNB9\BhWCJwqbЄ RY-Ѧp^ĕXx^4o hF8b-%VI$ x} #LW,ۼE =sjt4]0i4o YV-4:*)BJ MMM_.BC%ĉ'*V4h Fiv:wQFp@jմO?ʐيإSN0b'7H_JӣьDž_\94UaU0hS {K! 7Cϑ;^ڗ^h@"nV Aoݼa'fgeed*z))LgLcڬ0˰4Z`X6IYL|R%ɤeaX  GJEgL*|rxbM8}III`Q?o3[ jph#GԬYS;xa| F[NIW /uFeJ:UUtP65T[Mna Be;o A%|W9-Br or:e[^[}yxt$( %5D.<733K`$i79q9-KPXxs^OVfFL\<,& 0L)=*@0P<ڵkuVEofΜCßZ *7jB-z=bƍW?75ˍdZݩh:gI|`j(fJYI{m*ju!^4YL7IŢ0$e%~KEx\N帧OOٟO='Rp˴gz S3fh53#С*VY= bX nw8NgtT3U˔C^kdJO);$(I$'\pA J IBB{r` >ASd@/^xm&Km-U kM/1_9ڞQs7R,Dq`J;Ay  xOlX-ЍТ\rȨ>YCG>j4&)*&6͛dرk֬x̳CF6% E] xӷPnΦa3sv`0=B1 `FGDDrәЦ- :k۶t;(B']`y>Ѯ}-5kj٪ /Yi;sm~& ="a^7֯_o˗~_p$;-2!fGveZv:]0{`3VZKSgL KNF[l3!&4[Q^ǑBd2Je6e,L'mZ0?Xn\u:H GIf|^g60L0iFC K `1ri5B4؂af 殉u:%< e΋I,CggV|LtLC$J322+3+SbfwDsC)ˌfXaܤ(ɲ\(3\d_"F}b-˽}JsWؒάt i gVLa6 jX6k'bU c0PQzThkB,A1 ɏdҜ(p fys_(4=(?Gٙ>o AAFā%ӭ PL rp>R$lFe;eI .[Ɲۂ\dtfSR?i.^ӝT[ Ltɹ32)`BNQYʥKΙ3ҥKE_[n7mTbŵk6nX1=z-[l6ۻᆱ?9`̙ ,X,ӦM4iD`0  A>ȟ4E RSq37gi%IUmﻝ'XЏ1nw|vdgGD 'As>Ar&,KqfDY l,x=\j""Pɰe2(Cܼ͛q^"GI,⼸9EIk8AF&hZCnꟗ}~6qb0w;Gټy޽{V }Am۶]b֭[ ">X \x=zПܪU222._ֳgO3 `BRq$MMlD8IQ 6nŗH*&&&=p7+#jVvL^?L&mH~-.R9' R₦)O (*Wօsr#6-6 &'sLdl{T'Gvw Q &ҩB,X.un>qÇ[^<:ϷolRgr˖-[~}7oa0  C) la]mqURɇ.97<"Rm23bx{NqhG"+ 0#)Փ&/f|4%Ģw> 1EX#(,c(Sd;cŦ,!CMkOS8]t$ pׇc߰hzWk*Ũ0S)*T: mhrfrq)&ŷ `BxO =sRre<(D*1渲@R|7n3,l8,zr MC> jEw9I@ŕz *)DHRי ! Pf+EZc1=:=Q@ $oZwv޲xO|eւ˪pՔ*>>u̅?/}=uM*7 Knq">vtκsG>'EyTҩBIIIq8~}?^W^ZZ*ڵk+Wcǎ`0 &W^h!rESx5%qaE"(8$f/ŪU7f%  ; )2;וxѱ+P2/(DЂPG.fH9vgNr ))4K>}K{^N,Av;k}'/޸/m#=od{Vdr 7jVkHʉ1 bo굴WB.|DwbrUbztQBpi9nB4:W3̞(}j7ok_{HsΝ]tٶmO>-kX6oܵks΍3Fp;vʕ+ǍK/OnԨQ7222&Ll`0  evJ@JMP\b&&OvMhwpM$mlzQ@^>{;{,g]Oγ D=;g/XAD^|$%,+v|L柙yYXp+0Oٝx8p7vp<|0ضUu ($4w߀nVMA]թT$ZxA> O!V/u*f[@"N# ucNZ! | nDl&-͗7ddd"3CqνzR3T*rHuA V@en<5v!ڦVވMwrAqJmtxbw0U2Z\n!e&d W=4_71k@@;Y|nK& <6ILT>ƓA14S}2%ڂҩb %  $- \Cr1IC# Uf+Dg_irD %e2U,Labo@Żv-Yiy|24wo{ ؐ>HW5S;n0r+\|Z@s[r_t!\"$\Vad@[^bf:*5{H,orZf|q>.%3CehfYe* 0U/+!J9%BQ3x[++={ բE rŠq$X%&Les GAɒ U9DU46kJ2WnitF}G*eԠ89r*W'"ڷjؼ.LYKXvdL%7Er1's%!AUG;nYR ;9G~k8;;/^UV))) A  b+447fe0 (X斶J ˴]:5TQ;9WjRuۘ=dU)\ @ qA7z)3jt iԪTKK+4ר$$h&HDŽ6ek2=Hr5}l^V2gh3ƒ޽sٙДxT(Ԧ5 "Ew_iR@>n?x 2xY)wnm>zݻp[]o\ȤE0r!Pq"w $֠RC-OJHM2S@ Hh;yw&=P.{UŶmFս{w:̞=kAA5dMMIGuzZd s§%(SZJMVesi@pLisO'iFQ# qi4M{pZvp%-1 \xj  aCp_{qѫABGTmܺ/?3۬-b Fl [ޓs)تf 𓘒5sM|cH]jN|5?$HU>  9\(ⓑ|:F, huj{qi H3,l޾JZa=LR"=)+;@Υ1zR.g *б\̉q1*ursk['̜ ninY%[1%כ)U L{1+Ӻkkܖɩ1{Ka~[=.Qpkig[Zȏ\ثS rB -,Tic;p4IfYɼc3_nw:ـ Tsw\]H vF `͙/"w!7\.'axݲV0HU(  YK(Y|K52#J Kؗ 鉠Jsy"c6-)LiL+["%%a R ?L˯4㞼l,"! WV)-8wEJ^QLlμV eߢ^Jnee;uyw[ŨPP܊ܚeyIӴ3(mت%ʏ:vYyR͹ؼ]iZnѨlғ4tuPj%W*-4G)sبNt]^=ΖQtM?4 7G&ɗkrWANA  򝡦2]d j}|Ks+ZaRk,m޿tp):6J{jXSX+p#fA醻%n[%_d-yl?nt9Apz! Xse[y3['%q~ AFr4q&'v/dø+Lάuvs|:U ;GZi_J?D112zWcNA  }΂2ӡ?vo +,,mrا%%x ]f kYE[Z;YX+fY}9Q.Kw''1څ{&KẢ`pOk.6&t V.qP2<"~rǽyj-yF9Gr AQqܯ\!3C7]30R[rP%TiM&0:rIKNN2,8T  BAA}Pfzϴ",nit|xLfe3 Q6d6BjVᕸ֦N!ԙz'Z\f^̾yeA PAAofQf<\%X[[Ce#EL2BLfl`xiQ:K$/JĦVj>Ґe%^~j!uʟ 'e.˒^"9A  ͣfPtT{άhz4XJ'f43G%\6).ĞJ-yfTB>?&S%^ /~ZgAWd,ͭ%aϏ_՛S W}4hr -]kgAWoWAc0R*CkQ&92TQĒ3yAV2GR*ZNONӺCE mhfTwh^O7zX=75ʉ^[hȌ('kyFt՚,n^bjT:H4*E-///TSɓFT  9S"B1*7(VGd#.5X˨̢S2N&Tey"osD?ye֭%Q̋ 6"9A  =aJrd9.eXug_x(bHPyRa)Rv[Yt>)3d^JQ*:S8Zk$.3R\mtt~̠ EAd3 :#R@!Qw w#FeD0jhE"vPzξ̩Funs)^U21CRԬ$(Wn!=Ͱ=Al*AA;e(z!*׻y?M$ڌ=4S:;#$eUZSipQ^|$"PO&SFMFgZ yye-5 gU(  ȏVrftҒ j~\aIlQUjf r9A `6 Q5w.(zI:+ә\R)&=$N?E*AABB(ՠ4*XmՋ.F|DzSdWu̧D(uI2#a !et)\ye/)1:\Ґ5Zċ gU(  ȷFgGky'Dt Ep *hFM2FZ"P^|jx(ZMr[B)9#Ly97i $ԭU [Lo*6N:z] v%[P5ZIhѧJq߄y|riϕ*T}[U(  ȷZhZA)"LS/efTՠ*L72ru-5}jJFy2cD@VDV)EuS܊\vQgg8H,V(Rnv)PdiQ\.aT%cT=\-dΘբ2nVCN7 "󢟇;Bs=kf ĂTk3s 6BAA'@j&EM-dIQ9cG-սeV< YˮP._zԟB">i2TP^U^@:L'1sw4BDU7*#3Y'8a$qp,ٟHPPU(  5F/DA{*PpOtsZI'w kVu}FxjPyO"DebZPd孌S0'\@p WvLC+DZkCn-Q;)gxd߲'ΐ2rmIP #Dԫ5$z,.4*ֵc3>GB]*AA{&"*t-3'HSjFRz?Uv֑HPܢL=6:W5A$k dWDzA&3MS eH_R&i%;uI|ܤvA/s0 M,*ݭ2"K)ݢ`bvJ'1UTd+߼Ζ0OۈCef4bs/բFqۻO>HZrq߹fR>Ib EASF‡qF@F CLTg()X~5#Fj2;O5Z 4K!n=JVsZ\.gMٙW̋ -#{•qhxe$R*/Ľj$חM ye9{x}A) J❋]Kksq ;,4KK42xٛ=oկX\XwY&m+*AA{p_\(e4Xt8 ʝN$wwYCt$R5Q2I˴2J7~zED2%QlBL $Z%ҝM1{(?3HY_+~)@yN.ZiX&W®^ʘJMͰ|mU!ʫTjUwQm|ɖfӪ>n==V:?QݷGw )O?=@)?zqƣ;B?м~8uw_u#g ןEy샯>8TH!7ɛeyAU蓻գhIG3emm=zІ:99xʬy <|H eeuYssTJ'$BHpkΓȧ\\NNÇ W}TT,Y QY-N jHK͏?)9kG}* oNήi=^AD%58SHe:RcJ(& Q~S "+c}Sy[JRSNmɜI]FW(!TJe@U+rʺ@[4v-DQ+]K8n`V#b/-2?iqz m:гOW.D86X4=F ݏ_zׁsmө~Mjz{{P-kvߺa7IgmmSCoDNrz JV*R AU1r ZwۤMj\|I"kmj0.VꇎOH<6µ . #%ðbG%= zxq;صjTZjYM[)A@LHAr "KܻoOhش n6_6yC yώ-JCOoz ګA};:A6W/[,//5 ysLл@~r% \]{T?oi9O 5d{'Y*Xڍe\ݖRfҚ%s9!JPRv'Ν*h4ѻ6OYOJNҺS<.3ZŘ.RYN FEsɥxLs'&!s7rGqK;9Xs1{kec-,ey`0Rlm{^0xשqWxàB;SM :َfX=oxN4pxԞV:\.yIYze<]@?E>=BPiF{;֯[^/]ϱS Puj?OE:u F eUV.{БJ~%11F~+ȯ\2ď`v..p%)Z6yi3L˖.׿O)3f~{4tĝ[O8 ѾE HK#GcժV6Hk1+U=a3{GF0UӞą#g#i9sX(YКy QQTeU܉-rZ]˼yX.W\v=[K֊S )3A]Ќ(DiiJߕ?C%J,㶽\idsaG>s7#e2έ|^*/"!sט3gXZZ,Y2)))2,T >E_δW\6vG/>HM(l_e[(Qp^GV'4ؼ. OϼG:ڹw˚%,/|By;䗁;ƺڶiҔgBJGPׂߦԬ_ʒk쨮7m;# cݯBE޸N X.H7F1EoVzߋ텋5V ܅ϲoB>"Rў ʴ­}]:v}k9GU ONXvJPiF78evjG`K+AuFe2OՓݘ<$Jɨt6)-eO ծ՚d]. d┼i )Sed5mdfV2>4 4ٲKU?y\^2KT-YsDyuwqٿb!-UT n5D u+4O&.ʝ+}R<|tl'녻ߢ[wv.1-h'"~nZa;0Yɻ{7ѨrދBOP-o;rx-,Z4wVAO-jW]jJϼ ;#** O(K#nTҥJ*J 0\1O~ tɒjp{ׂlv11ND "[^'7o+QɅyQ ?+}DS"Rs@5ia/ݧMfNr/R_GU I2Sɠv@ Y*(WnS\dʒ•Yʹ5 jh 2:ժdiua(\ؓ@*-})F-#g9Wp?.GVKaݾ}~]qiry4-m|w ,5{GhÊEDR3Egڣ u1kLy`f&v>aCp_{qыmͧǭ;g#1-P-[w **jѥJWϝNmE!dّcaXnHQ0,IJN{~Ś6n7obo_ҥ׫ciiw9"B E?M7f%۴^1c6p$"/g&fYZ ɲoH#quHN=w~[㏝8پMn:N=8b YG?%3II$(?YP# @ձGx!KH0uZJ('*Ȋ\b X]suڹU3GUYkW_F F < Ba AW y|N^ Bc'^RGicnz+h\ckkh"51p+Tᏸt움knf|잌g TZ`hvCШaC {quGRH_/2^@Ɏ< o?2n Mz4S eaקWwJߋa׻"q9.kBK(bJ3E &/_y.-\ϟtvv~e*f)}Ut׭\fgkϿА{l% {䇥GJY_J֥Ơ315(݃8֑ $b=X*={c*ZⳠ@3? y1y*JKU)W*ONN~葽sڷ0? 7P=lӳ;tRN_tsvvv"1+[& \\99͓ǻLiJ48Wwh׶xѢ7tW(,w^3E &鐐.;TTϽm~m/qD0t+;xT)d#//5tu3렑ÆL%J>pMZ4۲}'w {,kO-F~M8_Ey͙i k٭.6ntqSTQQQK.-^8mtڊ8gtLF/ P=II6+ρ0e#ǎ׭.G9&8weA  G I..^B1"Jd& O&.ZL|hD_jmF xuPPC~&6v5k57|~eƏy "طH!{}xԽKNoپḇaPpw] *|87W a.ڙ3Sju:&]1b͛vN䫁*4lݳY=r ͎}m$Ae6n>͹;q)֞=HBRYBB;w5=xP)))w{N䔔g~>+{8O-|/<㤜Z4n{/;mׯ˽ŵha.LMe %oh^2H.//5ivЫƌ+Ӿ&PX_N ^AZQX.T8w573 ?? 叱׉~UO" Gb<@@}#`lz,Uc`&#\ơd-<yѯ_)`k\S?J(RDx ծSW{A*AAA* EAAABAAA/PAAAˁ*AAAr EAAABAADϻwN:s 9T  kgAr8BAAA/PAAAˁ*AAAr EAAABAAA/GTׯ|, ^AAS3U(   m*AAAr EAAABAA9*ۇщ*F:(J#4vfEshTYER9;;~A$gPZ Mgia `ХHXDEeу#+5ҷ_z7d=<͔r)))J}䉛FtR&M222>y ə*TA{A0/ޥ(-Q|$Ҩ324*"u]C]7G.s@ś>{pQT)B +W+U\PuEQ"L  ]Qc&H5 ()Y꫽E[dQ;}y*4** ԦGllŋRRRjurr#G\vBT$Er3kz՜?o_ 9 T  dk&(ABFRQGUp"bg&KkaR^zEsMtzz:-QD*˓'OΜ9/1EZ.{a#7W*<(> 9B׮^|1Q88ph5/_TeK?24hW͗/|Ԭliir&XP<{u{s wYՆ(=o.]^jͺu?DHVrqq ޥ[m&}ZWo<]-A[sO|P 9AAJC2Wꧾif%{rv\̻oPnnn0NHHHHIIILLT*^^^0`[III 4Y᭙FPj4Wr\A>Bpi$V׮\"q*'FVާܡG\= A^#;#[wӧʕO8:j|w|}E^˗4lJwveG"/V\bHi*Ȓ Y#'x$V%{x]u>#v} Ӯ<_Ee(m<,v^ƅ񙖖,>} i>{%"c"WMi4eFjZ-0sL. KU7n̞=ݻrܻLIƻ`hZָQaC(Ƶ)roj_r1##8rJ [6o|9uڌ7U^Æ:s'OGO|J5pܿ^y*Q>[mڴ9s9bX~|]ʲ~GʘR-^lu4F0sgڴyˋW/Ǐ=tH]=T` .PY2_Pڗ. ܟ>{Nd\cPƑ}vO=ѽ;T5Ō?2}RB!==}NNI.;qx(AIa.4n4ju6.[bq-77 kxG]$?obyC236nXC5oa)eI|f egϮ\u V^Mb?MJs ;jժ,_ˠgύ1dpA4=g@,`7kaس{M!4 U:ey==-("Evig%@|@>wٳgt]zcU*у`|ƝG"m\0fnTFMsd23h})Wʌ^g՛r{"?)͛8R,,,Ο?7o^'O a%v$J|AB%1rؗ/^߷7ի^}~5zMV^c0vW(&N߯ "iݪ|y3v|ΝV\~:v8yJlqԙ.]::} |˗g"z^_^5j ?}u*. ۔.9}*+W yhƠ-[7FR÷M^=ɭ7 ܳWJ/H(||/] #VS5vAa sѢ% n'O%+)G,R?i.RSncƌۺ% Zs.wwz"$$ T(:_|a !Xq/ZtpD>ݧojJkNjԨn)} F]Т (Cv T~c6\j΃%"<8еKg&lk^aoٮNDD_O2m%S~d*ҿo 0~ \Huݾ}{;tZlEvm.]ԱSש3f9_%2_m\ ~k|1Sgv&>}S*r7o>{y03n~?+ H_jnnaP{!BunѣG7kkz%" )_n111>>?@| rA!g2UZ63d)E8x0֭W*;LZY0n:ut ޼}{<#ɏHplh֮]> +iH}'**z s"M){|Ђ\S3EL!yزlΝ:u2e;nj}N(O?%"e |P,+a1+٫Ne ThhYS/+| E>}XkgO>U[wqq))vVѯ֖zK.j􂋄2J 1>8w3ކJJOJ޼y1p &-R,3LsP68wW ڸ \<Cώ tϸa\{f͚rM1իL=̇Mf؏}ÖesnܰᲥ+?н[}BA׸q)' Jao3 W9;.od#_dS.;;[\+0Ko) 6ΏYJfCɬ}޿;; 3/Jxc]oIM)325j7nx=LggsA͚53ҙSerZƸdP2xfUF_ YCPGGǘ$.o7S; 'NVm^s ;k#ukx&M"~1u "`Klظ˾|xSxL*>3Z4˲~&y6׬Y;rԘZXZ( ||"0~"qoIpR(T8"odDՋ#cޭq~k|1*Z|5̃i7xy׈{{bWSܞWjoݺ;3&] t˖-[ EƍSe -[ۿLjjf;4F-xVfDJ3"}~_k?ٹceE5WN޼yk{/$r6IÆ lzБŊtֳokc^J\Ri԰AЦ'N(\P=ʔ.gD$iKa77FUgeiY',S!=?kV޽΄s7n8~q͋g*=< mڴjU!Ҕ>S8L6&v(OG>l|ԾmM0Vڵ5"3IdA ^Hf%S̯bV6ě) '+S ,^pA5VQvʛYW+;r4 P)a:둑KZq˗ϝ;1/byZaEU2Պ buif9AB Ul߶c-7>|Ś›>sրUjocFǁX"9%M>>z8o3?~8:d'L>}m; J>\ݪUzJHSxL*>3ҋMJS,9:9}kb*EgZ)B!4 {#=T>wٳ<ldLy >ҿ>p2Eڸ`D[㋡J0t(q4A:%dD2Z-S(dͪK~ђ%Kh4iA|cǎ3f̮]Znm*sUjZ6b2*`3hqL_}J?APƍ?wW7eK8=j/'A9 cw, 5q"c1^Q;;;Ā Mg: ކ 6H}?nF ۭ[68*Ç"p0Є?)0nNѸLE.YzhӺ˗mťd?ςю3DLZ0Y0'?,Nj! q-3HȲ>,YwoJ^Ei.E׿`=lқ30yފ7ag@bYsE|? ЀUSg6_dSu@IP?qo1wk ~k|16fQe҈ ޒ48i3q~~~?UĔpYp!TZ%d]<g)W)Ըz%G vB wwI'T~ m2VXl-0~<§ >}SG{W|~%u5:)0nݍ;OӴЈZ䧆2Wtk`x!o^jժ;v,==UV ---%x~Ŝ@^zRQ >`A~dr n:|#\YǦp|wgj9/~oSS\? ș*IA>-ث  |*r # UAA> 9U"   "BAAA/PAAoOO|Swվ}={j >{z?6BM5eϟzmذYJ64iry6#âT*-Z2%B5V2JLBbf~XxL\R&v`c!1W]w|H*sv$uّE>U5RB#_ TTyߊz/]^f5g6~dZf*UؠA}zc8U|dayCl#`s'}LƼ[pqٳؔ@~ysgd6a$g~!#ϟoݺ5ŪЎ;D;6mڴ~C4iR~v-%"ȿSN[n!IwgN{_q^m֑v3{/ֿ'LhD$T!2Bqf)222NjUhwٌ3)e]p*4!!ŋ7n(Ydbb5QO>izoooPwEP׮]G;;m۶,XmҥK,)Q\O:5((.?~ "P=<O]{|1sao<#X{k+j }۱AxBlם"vbz0~IH\zco"PohW5`IsKgG: .|i^=s' kxڦ cBk:m)7xlԧ|1Bn#W)7{U|r 55j4r /_1a¸͛͟pNei([[lXUk^Č7Zj)vN;جY|ܸUV% r1{-[xjwEy9}7n22<== \R%2xwEiYÆ RV``]\C*%ۧ֨5k>h\Nr'&&Dܸi\`f  #GyeϞ=shђÇ'x{0~\|B̛3{p]l7iL0o5O3[\Цƍk9OkGO 4M߽{ȑ#D"rR09SR@vd2ѣǽ{V<{Aq{w9⇇שSl&MܪUm!Jx+pdPCc𖔒~I]aAb qYlE@2U!5]؍g ~ί5g*/qБkƩsr=MR79uÛH)Jy&ז 2)X.L`ޮj t_v")8ZoܸN#lT 8V*٫R 3Uʕxxzyf/_qw|_ xԙaCvk޼ժV2s;Ǐб3 ڵmdΝM>}ـ{ n-&xƍйs˗ݿwc.MzkwFK bҤlmm37mڸi֭cǎQT0ٳU}|ɌA.\r߾Ϫww )ܵkqcm$1Ǿ|EEӷߘ1?&H,Oqs_⓴8cܸԩ}3t?`@qڸ@EIXر#-O[`{J' "@ױzs4YtYvV\=}޷)֮qws7ȡAxRH|%VH=SM[xE|B&ᣟ:mOb藑II@S<==߼y. զM npp0ӔJ%=z4#Fcj ={XXX777bLyHɓ'_~)Knee͛y+Wرի޽~QFP-6mZfp\jvߺfJyz FntX4BiqǚaMu;o҈cz63C/fbrnHҝ!$-c)iWr֘OhTtkPnaBfY2Q',Kg*A*Y!iP)V)ȡ*43nnN~uxDͅ }޽{Yn`s33x>{Xk(cݻw6uW$[6iϟP|<offÇp\c͈dj4on`WT@sLuk׬Uw'׺ ɦrGl߮mHiOA "͓kqƍzj z&y [. Mcʔ)C{1r}\V1GΝ[ G]{. -h 9K=i//OeB2Y;*0[|>"RQ2Pgϊ-;PrCL$s**UsxbŊ_(@UFGGC1c+X ԩsʕյJ/\srr R03ə,_Wgϝ;ײeK}2Mܜhܸq>} \<<$$O[w_nޅ}ߓz_-9L)ȵ ]OeDM+?w;F53vRo j?q)˕%-Ǯ7\dSE!>-:f cuB}R9yAِ^:SwY[(*4*E9UfzݙOkOoظ={(frLaM]K)g)^Ŭ6=k:qH00MgR8ۂ哀| U*;v\@ȁh$@uʪXb;v /^ B4w;wGd^ʕnٲڵk*D>cƌ+nٲE0fA&g3ߑkOj![gϞ?\.)dDUT9sfݺuHm۶VZ 0_9Gǯ?R.Sz0kL]nζЁ-*Xte=p_0?U 6kxv>aCp_{qѫ 9YKLI߹Hپ5ֶo3{_3 =3o\'~\k${Ȳt"ߥͧ#,AΩV)ȩ*4d 8*YYZ;,fccbd+k;w޼uK[nvbEv׷+MynؠAǎWZ0#X3TZu0*z@ߴi -TP=˔.A0W]t󺵫{,''GV`~\tA~xr8Լe@zB\ B.޽ C/].$Te ¶, C2kk.P̸;mLEy'*?)26d2Tz2]J'g@IxdŐ%fCKg*ܖb\7B*0V:`C:zuFQtw]zIJXh* ^ UT R'* ( Hﹾ;w{H {S ͛L>c1, #G#Gۣw~l2-zhm,5u̯h˯:dA/ ;YW~[0xUy0[,-~xҤ 5I'f~\R7pʹLOF?l23 4ܳgosS[1n:=lW֠/݂=ßS"Q_x~Wxݕj>B)cƎ#?T.G5e&Z~1٧OocǍ?. ~& Naij:= WbR XXX8q⇤D"Oi7 Sz|}Na's].P/;2)I q@0?_\\'/<҉pS;QY{w?~P {uLY'~ğQ': zD*m,e„D0 w;-BE߿GlbW$ ^U00000000b```````ܛHK%mа04tQt 闊ߖa``3,.BRǵӾ1٦]>Ev\!fw'+ ů100xUMHد0*8PRJɱ͡mO;(lo3Ke)(ʏqO~P-b``-𪂁q;襍&6iD,Adzf{Z1B qo~P {6+(? "6 8/X(|dcB10000000V6(PPvcI)k{ZaT0A`C(=B10000000">R̴~ @YXٯX:+h(rySa ^U000n #ae?!q4t-POY(ƽ >PR,qf)wf;v {bW ; ;Vl'2ӒNi_;ɚIY *uMAI~eR)S'+|ky~Oa/ܹs Vk&X*7MuCdff_Mܹ>|BB’%K}Ѻj~P {i$y D4C>yZAL!vXTdtV&Q:5'yn*͎oJ:?f͚g}̷֓MTOG!o>xe˖o 2/ڵҥK!OFFF[Y(݅:qikem'?Qڭ@AӹSCku]|̙3/_ z^G]~=p`kpaÆ:u* j:t(+33sO^d J8p IIIYbEBBԿx>Z &@:e_|C=?SLL _Xꋤ%i(:K/8.^8qÇ_(a{oǭi\2w\ f'NWk.;K uTiӦiii7.,,l޼+Wd2WڥKB6tttȑn$P R9݁hѓ';=HiNxfH&wWYK.;v?l6ā+x/\TM !ӦM{O< ;=zزeˬYP6o޼iӦW_}uΝT*A>S H7|k׮[n?p/l$; SGju9O<1zӧa~an_|帹FpxDFF۷eUn&H QK%~ zɓ'ӧOA ZRRҲe|Aqӻwׯ8%0 Т|8̛WEcLtvv(7nZիb?ԷzwuI&Î;.\j*`P[Yˋ[۶m7rtѷo_I4~$خ]֭[Rlxx l֡pӑZ^r%ݙ3gy W|帹aGIÁSO<.r57eOy߿O<,x~+*33>i>{,'N8URRW_tpzBk{Wp'Z^x#}?H ;ȬW$g-79DM4ٷo_޽Gox'|۵kyIKez5???((H r)i]RP鈛R|N5弅n+r\$@|tppԉzjЂ{P>ϝ;~ڵq}>>>F ֪.RTR\ mR{ X`%ZDX9Ƹ=]`0MVj6erRi*J__aq<110C 2e \$jNNN*}kri۠yeSՈX`)0N\j%\NRynڝD^|=X(MQ/OL&3eesUGEFiNZHQJ Ο:iX£JMu%BmX|>[n q@|VDD02|6h`Ϟ=.shذ](ٺu+*nG<0::X,˖-KJJr|n_|K7[ɞ=6涩8|u5L0`NݸqK bb D *v ^^^]=yRR%&'o{ܑ{ ދcQ;gG1n3*** S:p`$'}|u]p!,,L-jJBh/^tQ,__ߐeggC'.J2liQ: _kerYG.5K@шsϐHԝϋZ#mPG5*N%y{Xno qӊ'M|LzB|iO9gs.^ӽӦ o^}~+..9cZroN01ڵ_~i޼BE`= '%&޸qcҤ)g&%66uJhXScƎ[&sMԣ=8`dzGG4h_~.KP;>0qFl,œFC1qK.чucy2l!a]OJh@ͬ͘ۑ-A{a}# ? Æ2ŁZղy&=#b o\ǀ/Μ9W_͍-**O[~p9MR ^2u,K9x-Eo„}oRVjESB75o}vBƅ&ã8ujjDDf00000n' ,eR*׮]& CPP|B:hKW^ `W=>IںkskݦWDu?lv||Ùfۻo+Z1kgZ/_O>7Sԣ'{>٭{kזN;q)NaC'}v9mu֊7*7P+}/bDݽzY3\VA?yXd W[D***s3i& \dwJR P ;AIJ Bf>KL@rrNNcG#c2n/^#-nꫯ鈐#϶/4d۶ݺwd.-x8&:Sf'9 ^LjG\Q ~I3lʔTMT~;;oͥlmWVyp00000n' JZXXXQQW^^p h4|2*(( ,  BUiAm@hѻ(r  wxɓ@ߍvs2MU~Q h'͛Ǐ \)#}vs&@ܽ۶  ~75##Oŋ/:tgR<) s5\[׮o?~lMR9On1~M[e.IGN1]T8襘{ +( oUU#Gڷi>\!+BBb,^oR4fJo4V4ZRZ^U)*>޼yTeۤœ:~MсAA`%'%%.6K.5ܿ_wͦ'4tmǍrU!kiA\uٛpZ qp]3鲃;w8fmV&W)+/:dpMA`8p=˺( ۃzB=\GP@O %SWYYIp yFgff&KCA-[BZVVv5N*.w:AKhrϧ˚]Ԑ˲5rs.^VZ9f3 F^{;:\-EEEuAaaM;|JJS3$'\$B3.<7lQz&YJpPswYU֐.DQ~P%Res&{lj2i.B>uЫ׳4*}ƪ}oZ,ѣ;<j/DD޶K$T*W(hzT4e:: Bj2K%%%Ȏʯ3E?888?,-))uy]+޿?p Dm'Zd\ifY~}pS\ʵnW?`l.0 #((,_N ] d&]>![cdJje8aX7zsOdhșS'lz{;]gԹO?qJݺw>|{ᅨ:mk}bS3{2yY:&^5zA!֮M̼"\\6j1cGad00000n_ t:jt4$ K\#NW_E 9nShjj&FZ¥m^d`Ag9Z'HPO2l99ׂCUkq UUU;upx cǎN2uEαSM%}}D:d )-ZxSvnQ#GNo6|3g?f씩S.]:UF:9uI]Vȷ(Mf%m r`:Ξ=GnX3M0QQm ~8}ƌI&cP NMlyF8qlK+45r YY:/(x7B4,..a}4`W*e!U~;n8T]Q#Ɂ ,#<Iپ}n H[ZȈ#_g:w=wGNε>1)^ęO^7sVq|tPϿR#>5r鎎xfb````Y(Д^o*,,)<OJ*t'v \֖\dX$c󚫾e eg%4p (_2"pe _3T+EQVYAH=}tA5?a,EDD|7»=ull/BXPX],^6mp”\."UA݉B5J.k"fT+f&Jv(0 j"0|nl %}zBݻ1 zKDhO?TS4?P((Ba! N}du: isDEfEdpaIQZ#r:B:$̔Ì-KAseqy.svf!x}R-lg­wȷ6˛z٪Yc8k+KAAAD#v+ø='_<aZPX: 3µ^/-+Vݸ>2b}{̊)\U^ޕ?_KTX%d$63^Bk*֝Pd#•qoE.g0._H:-,) _PT1@A088tjyQL뫐od6[JJK9UQdY$܂B8:2`4FH3#4EKVB񑣜RD9*,H9a("bDel9j CUl[rIAp/ldkZT" ]Q(/s\P}/@X%`# ~;ZsЅj)\_rTT*y  Eq6.XzcU2RЄR.%hF_$R E{ɔV߲17*b>P rB#s=R~`iBC\tR.vpPҾ͒qlD$82 t?>+++:4qDJL;Ng¸1opf͘3׮yi42d6e2 !{=,L?w}2 Ib'%4Mi2[(Ie쑕}ᇚ[^o0[,T*hSRV De=ARZse@eCSj헜/aI}ïn$S\]kuGBT#a WӳuD$Bw_ӊ !.򿅩w?wh``dJSV82L&a4kPn3u")\$6iyyyo8uÆٹ2>R7MFV(P*I%Zs4ӠVYde_r1jbh%NbP%Q/6TEe9E PVVVdtMHB,( wt W^V {~فUưB =8$LD<Q z␋~ʅ 3%P6oެh 7|ӧO4w\&?wb^~s}{]Ϲ^^^{62g\LNlr=7u*,$4:&SM'7jؐa]vh&jU[=4:* (kQQQHPPڂy?ڶw%T&ᣥee1Qa!  >tNmܨѮ}A9RO[`۞Qpl*%C$lCIERr H͏o> țWxHI;nۮC.>i@sson<m2W (++Wɕ e![HHH```\llƅ Oz)B[,@D=qe8[+*.d"'*J0:+Wj9OҠyRcTrcJŒɕ^j]dK]/2`^|%qL.uT`GV+0.c||Y4ag+\CK 5AØ8ۂwz &(, iPP_x122_;vCH*,_jQ>ZQ'nJim-Z<Ӛuwe}Ϥ>t]juk/>0gϿ$'%)ƍ-䢢(B!H i'6&z.^1‚6#999 s{v%`\ZRRl6,=!W+EWhRLUɘ*)dRI+è2TfhB&%TBUSvql:GFP\v ȮF!k7[egARXhH{@qa9KN84Ц<}~Z:{ {k1000000zf %mǡ0sBlԋUVV3tЭ[VTTgXXt@rdZ-eڶ~Ęaof2-^R7?}]oz=L&:ۆ?t諅1 #{ҥ+/(5ַh<9IMr5=+v;E%i?&}8WtG{%m%mH\$t'Z䷑:7r=vg !ౠڶbk(GO<e=~pØF)"<@j1fUFxEAl)Jj1h`&)""Bڳd25ZͰND%\!/0O>Q9oaVʤFH*BB40R#)Q&%f+VH,)J%af(3>Qpԩ&;,ͅhX]}jTxXӤD@V+>qiDo`NidXKv5AAK!g] یB=CTHc3Y^eZZmNΟ?_RRrbHҥKᄀM-s.\$JJ+(UVM& J SyE7 @k٪Ż#͛m߱ ͋,XV㕔DoKO?dx$.))˝̫&)8(gUeՉӧKJ JJK&J(!UzrMzNzm+ $5R`T6Q¡h:s26R~(Aǜ`3R'OE{ApeQw3ݨcfʶinu% [10yCM۶||`HP`q*kبQӾ B0yy8GE(%_Z0.eܫBSc2Uk(KQ$eڛ:Z>p`rs[~ )ʊǏHIgMz!-++#*#B V xKN= VOD~tzrѳgύ7M_^[7ׯ_ /̝;wذasp۞~yZ[/定.) ! QX](m۶:u_Jef233[?d䔔*||V+բ'?߷wn~G}^^D*۶cB.W{ x9s׋^bQQv?=y3=~\^l`=¢W•k|]{W_lCKߐĚςΗ}-UNǪH&I*I۬o$Fm3 'Sja1[CnT"U,dOSt Pd29ѱOU(QU1 }J2VBKSM2o KshX⃇>vo̾X#Nڶ {>6XE 5jWgBîsd: >>>ΤDDG+՗ ƌ9m$IrUqȶ͸C\D>Ea59%&j*?f͚g}"aLr uÅA[z󰅌8jU(XC\.٪U+H] )g >KFp}.O8;u:`06 7 ~[jíK3g̚HϞ+A".YVҨU@:z;g0@SfXikի7r%A5L@; NaO}K"(A89Q' {)< KXW=BZW$2r:W Sc(e g̜3gq8)=u7~ΝfΘbt~2eX0Ν 4iÆs l{+W={ܹ!%88チ3fk۵p{ᅬKЛ׬Y}`T՜}?Du9[8pS֮]ߚ={N>>o}fzWbŻueVИlڱc@A=<g>QKl= 7*%yGJrE7) dr.ɵTf*a53fTUJJrNp!ŝ(,(6EsnKRRx{׆ ~-Z>>]e1ÒF IR{:U(G J5 e2 zǻv2<h}-3;P{-g b IqqqEEE@@'< 644.7+|OSLy$r8Mm {1EKQ1,_|̙/_]W^N~]M9Uc˓)5=!HפI={kn:tpB6m_c :P?YP{ Q9u Ԥ/ǟݻw3M@\(:.S'Nm߾"{HHR5->gY@;/O@g"j*J&j4*r >Ȳr\Zk4oA|"gWUjb&J&Z 4痤m}g$l\i㤲%Q0#.zDF3A/mc.xN [I/INR(XL]rБ'N;のK>g<'N>s:h|gPbΝgξ֦ma$_=nma/͛fd\Dw wwx UuѩӧB tPлI -IpX5wuQ/MƎ{7ɿy ^+йR Mh4'"9y #xceZ}`* ) &TUƒlk,%I.Hs3R >abMl ˅DVH&SUbؠQBNv6v2|8%TՊ8'"x$IDx`5۶]LH~6/:( ~T=CgCj2(g1*?n^tAn\?6C 4}ER O1!IrUq. ƭ"w.]tر?n6͛7Jꫯܹ ̤iӦ֭?^~e`).+ܷoӵj݌ tM(|{キqƗ^ziڴiO?ɓ't'F=}tTMrd:;ȼ;=zزeˬYjAX.GB?BMRflNպI┚/ƍ߀'д=k<6'i<0jBd7 Fp%[YU𑠚(8i Z<.^ιb8z IHIY6j%ސ?/j?omZ"_Wz Sp7s>%DީyYHFG$avh?Em۶ǢE "Jળo{VF;Iu,7PvJDAAAo.ʅw z}>}>?`>#DnCנ,,8~NEF̳4MI $M{i?8&z˯dee\ow9#'1lP\+|^8>6*244T,|?EHirPeg؁.#a55{~_%%%h7J d r^*E-Bꐈ4u-SLU$r \(܋!."^\q7izBmﬓ t+Ve*|ꩧ`v8]hvQFi4 S رc…VMIrJtd̙-55zw پ}5kx^q/n u;&[.+Nw)q>''MS pȐ! 6||9sw.f|ϟ:p_Pj(/GfLD="*+eMTFI&7/a3 :8g:"0H kk?zF.S5;!2P"%Ky$ܓ܈%vԦ&+yJ[˨~ w޿ֶ\sZ<-XdJ3aAׯ3мyU?b])_Ud;wlּ٤IQ_| h[PA N,eO^ʕ?=ش3fQ9ݺrJB/|xu>fX AU+xiSg iJӴk_y?6aoY$enݺΟ7{2UUU:? N~@uRI7=/Ͳ7ƞFm$sELf} P!H))!|"WA嬫gX(xkwC7O,**ʸp`4Yh&$,}M*q[lo*+*m[@WBJx Xoz yvy' oS UT&,@s$88(?M6?t@$%&nxK?| 2yʈ#~]VGx* 3''' "w ӛ$ɿ  eP֍+ނ=䓯zv"""4iB\'5uݷo_޽ѽ.y>2K4~t/<#YvLķG /A͓ѓ)njnܸ1}@-\&ܿ,.;uܙfOAY C/ęjHBwPܾґ(:)%xMl V;n] ݲ7jd }QQ<&|cvv(ߒ-sulN 8xmN9%|$'&d_r;P7ѣGO6}ٲQ&OӦ~4qF~<)ǍǩSIBJ=3w3j)S-tɩ=ltcG:mӭ7zåtАA8h'mɯF|0bĉ6lxϞgwǘ1Ns)SS]xN̙۽kWG@1:•u>>[?x˰ӿO{6P *Jc,V{I)+ BфVn-Յ+:!FBbXӢL&W([[YYɧk4d*g&?'NsJҗJK^^:__po(+'GKb8 44/77:"z ;eH\AAo %t WVM&Fb} pβlժjuCMX@F f_͊pjEmLĮ $xyxP8!."^܋qNt%Köwacƌ??sC /pޯi ꫯ-Zԭ[7z̙͛GEE1eҥ H5hР!CxVrrɓ 0'L .eZa֬YYG=A܋F6BO/⾈ܓV'MSTOO%TO"7 a b:-{ڸqUV9r9VFKakȊm>>[/‚BYaj̄ua-$FXĞ!6_PX`qt%4]7$cڴ{5ooˈ`GoS[ E&9~DZoRMZsP%Wvl["w>P[y,|7S1*a7~:,*. y"}|OWToyNNξC""[nn?h6/a9v}_. H&jI e( EЌXEPV룍oӺm{`z}vVBY)Z|}}QD"0 īyw(SUYUK]&Y0 dˋ*~%Lg l`,4IUc)DfZ"ٽ6HyJ"`׉|h=m,JL?@Jv^;RNIǻ-#*j~)/+Ն55-[7oػ>l.111??tYNo:[TTDSj R1pߓ7 5w fH]e.Y LNctOnU``0HTca)n^*~`'׼S R$St~aNl99^5o2%Xk'˄jc( =]@x8u}6-? KJMJH.QsY KDX3"e,-9qFІʵBѨR+85MNNN~aQrrnVx`f)a3PRBddOa ȏv 9]NlB%ztzp\|`QZZr>r(bP KM^ ״B;Q?pS,ZsJYzPI(8K)+잴I'vQ!Si͊$LdO0aŹZ*_V^ {)du,:M {]aB *'HgsJcG.all)p { mkmOY,T@^WD>~:NI96oFeO1eoJ͝XH9 πAI_00qzy%…Ī*@ȁVVJe*lDS,؉*QJ"k )UF}UՌ X0?TR>;v)bQ(~rypH2B= PL&'8(zrX m{kX|wš>^e(4FRRIcokJ\mtf6V? UA,OD&,&P Z?U@U_Q}HU~BR-W{Wd%RhUb.r$`XgE@\HjjEKHXWxzWЃu1.@C!E"W|"'W$2U_^y.q@zIj AUI/10Jn<P~F%iBANT!Uî4hP7fq2|݉ lJ-".%m3T;qu83v(I .!)aG9@x`4m}>>ᕕA*'SCEJm%5Vu0e2dȾWpdIY&e*$\-*A/UH}j9lU(zfHZV(L:y^/ztͳhIYb )YQ5JhHPwpG Fl@A4jAf?u}%l*F%h!1 T~ᠢ +(\KT*F]q*R[RZ&%3_'Ԃ QVE)Ѫ5,yCU▌-ΗB)WD^EY_"}ՋFZnk,l C=M[8{bڵ֩rQ*pHZ222T|Pd/wrpۉEv"=.*m@t9wNTծW4Nrhy]c?6O(5n_O_#߿P4]2髄zqㆳszz| ARٵzbP ;;[)@Ay@l5dLM-|X?P`kT6K)%BRrvjLѨBFOfs*ON>5*2@HwNB@Db@H3Bդ#drBڈZ]g$ޗÅH6dl+r orzY U"Ic\ hmY|ѿ],K_R5hԨ I7njB(ۓ1p1Q)ݍ%OCIׯ= YQdGC^}/|Ӑzx?qKǎ ui-H<ԧ+dqqnFbJ@ P $(N>l{?6k ^A5V8g$ |r^Q% ǢtWPq]YNWyZ!ʢ$SPPV[ +Nq叠O}9y7^\pӜv忴*̯&'pPDQ$3bZZl(|fc@r2kλ F7Ƹi&td9h*|eQ'<ݳfs-\Xwa'K;!AeJu*Ef,D2 K^0ҎPjzK&ZgN[:0yǗQ%HscTY ~@áױIgGտ LFϋˠ)ڐC o"{B;PD+PX|孿3B'k$88KRRmRuEM$IW  4%%%M |*y|x#RTZjOQ=WHQ$PHfRlfdzri>_I K _Ipѫc=X夒; oUzP? 14z:]O^I deٖʰ*1:}?\zb",'߫ă#+۝5S#D+X ZU5) _X l)ޡCLn|OМMrֈPCXrw_%iJUTYu"[n   6rرj?~ƍDjH+TNZZO?ػOuY-\Qe8.   jW7 ?ӱ7PAAA^'&6'E&͋ KX7oފ+B]*BߊAAQmjs"N4͖[T*uݺu cGB.u/,L_Q "  D;v ((h15nٜ߻v{^o   G*^||_Pܯ}_B¥N|; EAAAʌݐ!CYlV E|f síQ"  b o[w5;_Y++W7P>ׯߴiӧo˓zʕwN۷oSE8côlva``;w@zzzªJJJAAi X%`dqO?Sp{p܏?^BAA0a„HZӽ1إK~mP}!@ "c Vm!,, ҩST*~>.99O>999e=qƍ;666vŊ 6 9ecs6o$+ԡZϷZ۷z}^|5k@ DH)O>dXn-q䎨BAAT*'Op@̙3GEE }mݺ!GrٳgO8jO0A:L:'q 6t֍bF4AlpҤIÁر#!!M6:rŋCz})x>>Æ ?\.wA?cc 29+//ĉ+W||\\\@N>#??܎UUWAA~T8aÆ3QFSL)cUDF&1XCB+gKg^ܻw:ywޙ  5 AAAWMՉ+YuN)1e괂|˻\AAAhZTįN>ݾ}j+PAAAjѵ`_ EAAA*J.>BAAAPAAAaB,PVVJkAAp}|ūT"@ lͷ*uﵼ#eRaaaBX\&_A7S\\,I.@ B]92):g8jfW،;yF,Y=FSKvzݴ.  HdA -Gy玭?I":uԶfܢRSSŞS'@6kRbc05ˈeDŠT* Y*-aߖ{Ch4Ў99nn<drhN;;;c큼 HspWoީ? r)f?]aZq?*T"r\#T ?3fԫW?Ӿ  b>]\\!pB?rta/v3,2r K/74i$,USxH(=;5<R yiF /܂3:LPV+H8Nqq1C25kj\+f+f-vV\٠A#>AydR mvSR" gyk0k|l#,,̪MAzw9ѥ//QbR 61 ڱcGz={voܸ)###&fe29 o߾CNfzz@;]&N6Ũ۷'%%ىZn=yOOO :Fu2EzVZ4kZ=HIfѳ*U˅dc%|޽+!'.nGPP۪՚ISSR332#G]9ޜv>k-;A߅\BTK[m8LƲܽ.(`98pB 0+K[X(5,'+yc0سg{[MP6+ %dϨ0֬ۙ:ujeAAWJ *HtY]o@ҭ|_'ŝ?*Tf.[=kmIO`P&KMB3QHDEua-gd䡘\ _Xceƌ`y֬79s}bbR|ۗ"]~6lXӦMrMfev1t/޽gƌ vP&=$e8; t[7nH%Rm^Ƚ>L0wӧOYÇ&N]&َejhs_믥"}_ZMÄGΙ3wέ~~~jMѨUJgMHwG2[Aӹ\+<%h,<#-ssZ֫wʕz֮}nnjDmvc.[M̀CupX~*J:v8wn47o6eT3fdd$$\qᇝ|>Μ;r .jժ)e={333k֬7֭g@ TMHX Y&a=:"Noz&=xQy T`Kv( ?-`{kd$ h6Ɇ ү4J'Dh!ziڠA77siݺs ]~}nz@@ё}sӝI(˗ڵی3 ӧk!"ѣ#G=<<2jԟ&柑>%5 +Zl\l2X`K4i|굨9BY*K@jXFgW HP#x CTF$Ȃ,ӧbbb-[rdU".@}1 ծ<['_WAppɳXB<'C=ڲ)SYZ}6w.7#1zl5k_涚Up7|dn߾[{vpp;5g*ƍݻw놅=zرcs̆Ç!r?A$ ;w\h,-AAɨdt3l˪}崛f| =|.Vm2o MuC*Ԩ]Rtr\w!nӦmq3g$fCBB֮nA~~5 ʓ֭.O^{ OOOSf9,~wHHܓ}zKo̟#E/[?لK3[Zȏb)+b@l޼I,6mLXjǏ]8{b9efZc- CΥQ?n,,,0'uz[U?ի)3y''  O52h鮑iӦ%1cFCI}#,ӧπĉ =xƍ)StèƒA:1'55u۶mK,/`PBP BA pBD!&nM_5dZg9 .5֫d4Ph/R!%aLnje$'QH틋o[g-‹dҤI 4h=֭ktb޼y3|[$?<<|ƍF֭[o7n8(Zvݻglm%$$tttg}Jl5jHJCgϞ3jT?G?fCjQ>BABG7 Un 6:u4H ƍG*OSI1h۵kw naURMeܷo߮5brOO]] 컺 $FرnAA=3.o!myG BAjr B,vjJr;[@Mܓ;i;>KH J?y9PlHг/!}}! )g ߰a7|^f͖.]b?aٲe{tDD_'m۶ \j5TVjJƷӧ;ߡCٳg888m$囫Ν;N>N``ТE̝&~ ^"P"DK{D j}|\*7y#F>M4vzIɺuðaݻfͲZh/1c/5tR ͋]ΟO?uF?˗GG#~Є :88<|0006Y?|?Zw˭ˬY3Ŀ@ rb,ggg;9;/~/_ |2vWKiʓ7?nbLJ_ΫonyL#>jw js%J=dQtom<,OڸniGJpJ+-(,(mtJ+NAQr FՔjˏd/(Uى(zYپ@7i(0^56Nx AA*Z3.DM?ö7( GQ%^β+8::n3>B<%j6JzV& _Տ2܈T .p hԺQԷf(:?bu۵]~~u8_X7̘[5Gb`'C;l02:#|>.A3eT`٤ dЭs>"+dx˯>>g%{!4ZE*1ؖ`.4P;wB(JjU:֒Msqu))}6^(\U*AA9tE]$޹߽FzI$譐`HD@ |қz?;,.&TI"/cc<9 {]&mrybb"soowbmۆE*U G9Wf< i>U\o*#;>ݻ{UV%(v򮱟G EA̘" T`bRڲVJ 'htӣGYY:Ui0,^~= - *A͹jvv"AAl9 > <<&ПiL$gy\||N!ĖCt&%--n))VNS`U)kK-F(  F|`f F&R`U'5R^۽7-A"tvfnRLǸ;R) -}8"AArZ2UrG&R`U'5RԽ AFS BAAAJE$5jX,s ޴3   H5G*-,,^Mͩ&Po%AAAA'۾C+) @ⰹlu   bD|6nhXX⢴L&zjM   TȻy ]\(v\LȒrZu7EDV=gh 0Xk;_ӂ3 b1H@$ GJIjbcd2jjjH&9 S _pgAXoH5 v]'?+՜M RHT¥7 ͔iB.m2x%Y$X4w.^uSX"o_Ld{|< TmdC Wxgĝ@X؝st|1ƌa&KpLI_OM0xJXܝrNБKX&9H)d2Iuu5h4nqD"AMM x|ʸ6՜$qJhNǬ.Oԟ?D*IKg2 !A$twBnX5ȫ4l6Ko_LEa <8CooOc0qFGGцm&t`>d5_r&Pl}gIԬWfM9:.sȇà_0d.`a| ?X6 G(X";a}r )0L!ˤ4gC%Cgc@Rn#P \Dxs3 }1|~k1?x/cUT~Jg@"L&ѣGb,ZrbllLӠdYf4-\Hm]=== )96:.i:w"ȖFs>{rƫ Z3T*E*&Y\o1FQ,Td2:)%;v`ǎZ}{|k_瞣KZ+ua1ܮ i۹}2A~/d<ЃpÍq/sgS߅qΤ%ݠmdd;]w=^ȑ#r- nF|GX,g>YnK<#V`,^/| $hn ۓۙ?QIBOɖ?TZ `Fc0u>nWq«M4ͳç?W%?++a SEl*@+ lsI3ba҇guf&wȱ ˀT!~Ow)6jQ 4UҲ!Ms@15<YZ~gxS};)̠'򩇙F`^ PVVx"Y;YRJ(*.BH2<2LQaxܦN윅P/AC{*JKK_\\L ?;G^^?I̿t/T\w%ҙ4DLL&C6!(ԫx'|:x5>l1xY,+W~B3Ny܏~T5k֐Ls5/uVF^ rS]]cx4/ /Doo/޽^{"zMyg-皫ɧddd:]ϙgEoo/\|%ƤS411O>IAA<bqn&^xYd s> CC9??|\5kְufϙ'> b/+&H0>>ҥ;6 \z*ZZZhmmYfi&ΝO<CyYD"ܹhoo/GMt ~G8#56 -¾Ofg^}xT'4¶9ȭ7*rP)$cqaUhÅt廍N_Nk“%^& G [/`|x@%}LxFJ-!hA..8KmȽNK%2MK`:d9VbIٔψEmJB/ bjek*'UsώTIAt6 α.dej?D*fdFCOO?sUH$HĽK=椢lSdY 8r0O?+_oL&CAA%@ڙq$!ص֭[ťwpJ/&{P%C| ,1w͛O?_?駞 w;]̟ -[sqJ/~{gիW}6jkkسgs孷dϞ=Q.C)c)4ޠu?+j2=<u̝7F?yX%Kun_gɒqҩ4Tƙ̜9CdŢnLL(((t:@uu5Fqt:'t2%%I|~m~Gg>e.36<2…~Z:H$“O</'|1/^_n= W]uW\q+XKWg?W\q۷owo (H$p,SZ[[7J#seO:-Ba"+,LPUUEOw7l >[^H$R)"b1=3-.jwJfywK_#rco,:,"s,sGۂyJn,' c,jm{|@Y-9+T3OAMa<Ȑ$3((ƌ*R?y2i͓1x6F%CuZ&]䁕gNX0MLDրfYjm* 3f$@3ɚ1bؐPaX9y\Z0 Xffmuf1S|%G:%A1nשψd3Yfl?+/z:lX,ACq838S>FO1781d}.:vPS[0Go2WQ;Dt0kw sE.T«).[H$DQH cT܄N;1 r 'pP;##̘1 Xj}lذrŃ={=,YaVzc= 0vihhࢋ.∷õjeH+}t]vs 7rJ}xnժU~o98yGill|$nʅ~}9Σ 㔔i{\tEDq;vl/ .Ҳ2 8|0477{n=\ϟΝ;ٶmW_} CCC/a۶m,[ s*88wqp,ql'%df284Ll&M&rid3)>ke4N6cY. #U_3t~/0s9d` ks2G:߅t\Y._cqGx;9اtDvTӤ:'Ft6owGcVб9mzE`!ܤ!> 8Jֶ9~p\zY`?']ːclLz&`UJ8T=ml?'Į9v.(q7 ]*’D"ǑRRU]E̙; [ 5̞shRjj%,!/aaG"Ƙq,>r kD t#Q!w#9gv*)yeQvFF>vH$'KYYZzeYfE-}ڙ#z+Ֆp ֳEw6 ۹Uw޹8, z85'CMɰ8` /t摌 TtNj'ݜ HwWL^O"1E}nƖ0T\\Su(9# i~D!8/bS6M{͏(AװT:L!>o f|o'Lt P(yMwhϥy\bEKSf&{wϋ0=6O3?|IQ{<&TtI^'wcRGU&+/Tɤ]-`%9k"qSivndLB!^ ʇ+0@󚧘?(Yɷq{Apۃ&o )S6LsGE\Xc]Ä_PyO;ٜ΃B?]J(BGs5X2P'KŒ'07գjk?,̴s|~Jӥ n^P4zv[2e%#UK'LXn5Xh؞k=llvpAY)Z-/s2i)wp{=ҁ [iΰ|a~#3v=R9dLkO ']+PҦXjk¯%?S 9J?>az(G&feҲۅz2X86bGvϠbyB#>1&_&L[g*dL&_c տFp0N8a+imV5eeFDܽ=Xڌ "xo2H  p ڙnsGӻ%ھ5oO{^pIATplLdR*SDP2P3y1Y"4 QT|xh rͷɏb nYGP|`*y᠞&1g|O.rua۴e0ͬv0o <:a/+C ۓ+A.Eoi 9|Y0Pj}I* jRq6]%O~<,N 6ts7I_ϤCH3%e6 |z|*Gm9(EH8Pɔ03iN0SQ J3][gt1O@}h>MNsDadئ 2Mj~8-V?V0^%sCB9ȀD~_</7~z<+ uUtH#&+CҲZ6FUt##dY.`eNH]o޲8|Daa4N &t;;JY %@7Ά.*,l^~;wÁ ֶVqҏb;7 S8h| S|, PyT =J[ LY!C9ZWe𧂜tFjVQ2o0]`͚\:JMOߖ[aa TT5eg|W қiu_C2LrC׫RwMn-JE0p@[vK*:<_u_ C۸߉ _RCҧXJ1(d{jM҄k·mAچj,[FSʹM&ſcfƍ!?L#?V*F}i4d믿ICvgsPbQcMe >=kOy7/|083+Bg!7~~F8|c;vL\Y"95v6nڈDu;_L&#رcRIKud#?яx7yimkse3]~iJ(7h}T1@,1vǻVtI>.mljCZ㝹BUtxf!>Ezl4.cwTH<2мi<ru 49m*q2#Eb>Dɗ/q>\nYzˑXG>&x jSɥf'Yc='?iٙc>S45- ?$mEWU]]G$b֬Yϱs.4o!k7Ν˧>)gi|B&T<wyw7nȑ#|eMtww^nҗظiUUUR6oL_?ZEo&[mۆRM|c%j7mO?ee\{7GBg[hS2 kSZAQp5|oL 4kZ1u2<. ]X>3&3zJ,}g ޕ|T~2`](z/+,_fdeM>mfCl \l+ɵ5K"vNr_f굙*^~mzT+0x1ٿv\COz1p[ٗ+90Bk{(zy=Fr1 "R__OOOӅYz{ygYv-[n.斛o&Q`'oY7xX, TUUٮf<,<,b=|۸t˗sUWoJ|-'M7%YR7%^\񴴶fZ[[Xlgy&W]y%{.V`_[~K\%L\}5կ{nf͚E<'i&lW.ԧx7Fttt4w6ogeD"kjXd 'tvk~[%pͬ[d?3۾eV~lo6K4-ʸ%Kill$^P@cc#ݸd2͟<,\[n3fXx֮]t [yRn;cݻN8HDpazzz_"y&XrI***|>tPZoQK&՜tI|4TLǙ9szπ)in^Ė-[C  /~+VfYn!غm2A@fPR +x&ǘÂ} ֫rIKyUE;K֧TǟOhNu3  C-LgNB 9IlóSAY40dNևc ifV i2ÐyXc=ؘMX$1?0aH0lZ~jW6d8$ Dħ= UNԣA0;!73ʨs~nW@{Hkj!R__ώ;Y?"i.\HMm-bjjjظi0WLHYx<΅^"SV^Nyy9۶mc۶m}9|GٳY~= f6Τh$Bċ J.^$8x L_'pAJ2dΝ TVWY`uuP[S͛ٹkͣ!d[˧z*W\q---9rDa!w===R)={}'ARRRʎ;ؾ}g"`߾}ر/˖g垌\+5W^*̙3N>dvkܹso(BbdvIOO%%%022(9DiZt&Cvi|;ٲu Yd ^͹~>[س{&+? N"rsavͩ+VELOZZZظq#Xپ};;vcl+yg8)6jՅs.ٻg/bVڎ#ٷo{!p\c,R_y[j'yJA5 d]QEZ=ov#nd ?,̌¿T?kPО}Y&kaJܠ->D.V2Xd o asW៯ M>0J(?M4jPV2`Wf'sd4ㄟG=},GXb[Op,k{'֗¡Ya G#`\zQ Ԗ"M ڃ'|%IFꢺq8zYfE:ۯS2q\N  E%K066(f͢*JJK˗s1ΝKEex]Dzm۶qa***Xd ---p ̛7ں:5wfϞMEe%'}#>t¢"N]B'-:и$Gļy3g̟?jʨŋB]m- TVV))-NbϞ=TUUѴp!K.e׮]xgڊfϞ&j5bX!&jkimm PXX0|2G93(㔗xbK6&`K.e=,_ٳg͛Vց@00a]]{nIJGiimMM̞3j$DnKq/^57H${Guu5uuu,^ZZ[Y&ٳٱcCCy4-X-[Ç8ui!5kPWW̙3fΘ ȡChjjؾ};˖/cƌsڷ`${.˖-qf#UԴ;vp)P^^Naa!k֬0xANP'Daى֍& ǟM^OF!WzZr P3JE 0vPTAi2 Lt~QtɅgACO`o$K ?0|Q-=uu^d<ڇIwȤmJm \B@ݾ !_&dj_*Ãc=X:&!DY]JzN2M%K0cͧ|d-[ðrz}Au_L9l L~}}ACIIvH$ )**Ի[H-^9:2L6뿁}phP83(/-B1Hͬ_}vge@:}V&Jyy9~~,`{ss3'p\:o-ᩧ&pUWQZZj<Ȝq]vY;heq96r͠ƍTVV[cW\E|[A \`햅z+2/*pI)y駹+Y9|߿W_}r>OUɢ;T&RrAKx?R+1abniiiM_tpe+arD@YO66ߔ]SJC}:&<4ƒis(\qOI,Cec/7ޠ&xmjnA DZ8SK]o?ز _r@Y&/S0m_?d]hH~DnIU={,B\>AMog@!:tMh4mKDDQU5>~\e& 70ȓCmL̼KɕJδ([j5@Y[6lMԓk*mWb k2ؓ/'|ph41(1L|l%oys:R0JcN"]jn䛬9_Konp*D,EI..?ќKIaG>ٙp>LܤԬ7m-ٸ-*{nWەOKPzW->q kh,ȅƣoG$ ׯ .0^nm3mS1S0ksA1rUSh,,{~9 5KCo*tu GPF,sIr2M J,Yl0ls >z00`^]I1e%CCVTY' 8_g|s ` 3 Jn3owؾ`‡&mǧGg~-[CӺPh)xd^ d_bL>[f pY_v%niufN2] CҧQܜ(uulװ3*yCB oixdsF:#8?\|}zn~°7FNNۿ(-.v,|Lٻ2@]}2Ʃd۩hlIzvE)]ޣʸ 5T*)HeTۉ6B6'*C<,)8n4hКriAI%w# &$PP*Ġ𵟘ŤѐMϾ>TѾP+06΢[Oj 1>աkE WZ|x8Y+nCðb'?36|uҢU}vmz +>P}d^+foJ%/Kx>m9j-u_Ӯ\3 lDڡUh];E%hWz:1lڙK?ۚ* q҈JAN#rYɕ ΰM!l>M12h[)sVEZ0GLYY'}>Lh /6疘۷h2m'*Rdl Q['D~Vصo [ŷ W ՗?>"ni.5~3 f\ՁVc+Eif!M|t%yRqa`% O"d"ma*9Iل?VEK?8XI2QrW(\XY8 G 9wFiuR*)AQ'Ya$ɆSHw+m*@fmM_z}4b6h}Jsʐe{Yj=؈!p[:m[nY 7uF͇a#=-9z?9tZA5I0w "M?އ! AťIKZ -mwi؄i&F#Gvqcsņ 5 V6hy`&Z?2 U'̶\١B(V ś;bm&Rj11 P%0889A|P>()--ŘEr) ͐du豱?<"Txɀu%FciWNЌdH':+i3`dfkLcDD-$*X5때BA[a5 d * 3:?svCooED1;?Vddx:>(+ Q^^RNoh 8U"`t:E4źE344LI2MH L{+l>*++'Ř'imҥ-m=2ʥ7Ptd[fniAHS5y/'UɛqعiCy~ɶ#xlT$Bdd[d"IIQWh oH?-oExa!##$h!x)|z>Ҷ<M 8cŠSNq>1;h$x=TTPR$YLALPZ 5b||nΝA2::J,L&C[[+w2 A_pD"d#3g6^R^:>RrUjFFrIR:H&a&庭!ҕ o,/n #axdʒ 2w!WV,_b&L2,: ;"imkbAsصh4̙3I% xT;28_ $;1A%bҥDc~j>nۿ_<8޳_<8_xJKaatcʧ/\GNYĜMD$3KJ?p?k_/<|c? >u]=ؠ xPxty :ƚt.ʧ;UdHRR) BF)(H;maY'd0 j,%vO?Z3Osœx'jN"HDPW]z}l̯yv%3J#&t 08d"ߘeӝ`O]UXT և0|RJ4###~pA>Ԙ{QFGGikk"{sq|ͣQJ4΂g|b j==<ᡡO!` sl>YOK3A]#S|xa(;ٳw/;{K/ep~!{P%3gL:SWd罹x-ZĮݻ7'?Im=!c1z.3ٵm>w9:z B```T%[1ͼ•`$ 3ҙ3).-#G mUP IDAT3:2̬El%{O{~s_¢"XlG;;ygoYt~}S#9AUy1gϠ,YHcm9YGr?C&8.e9!Xz5]]l޼rH$կꫯrɧ+믑HzZ֮]UTT3>>άYG"ylŋyЇ>իٶmTVV+o`&&&x8|===,X@/zj:l^ye.\}p w/'?檫G3W%]̛7^C]]=7o&CFihhxVk׿~vJWWbYndݺu,]n|FGX~=?0K.~l:|Et=,8L'xp-SMf N}xq.g*>&K0ST c;{<===/n dxx oL&IR|[D"49s dl}Ţ"6_RY RP[Ȋe3tt0GdL۷_DXt)RJ24uutNoA*"HMw1I"GGIi) 9t##8N*+Q r~#>mxJdq:8fboOs87N,J $36fo!(((pwt@v'70:KgGL9pOm=sиK&R)zK1>fxdQqoe͆mX`Qs3{{o?QSS8vXAyu 6qk\++]0; {~R\SÑ;:GQ@dXkxݵ̛;x,JD:Dģ|@fشy8kK1NmyV9 QJ G{GG=ͰtRjkػwg}6\s ~7x<ܯꪫyIydYkcΜ9=zs#rJlL{{;?Ϲ+D"ٳ+W/Z9õ^SO=֭[3_a'0N;>+Vpw?fÆ̚5a.\Ȍ3hq(++c̙{`zMz!֬y>.R~_366Fww7x> TÇپ};>/-tvv"`͚5Kq,Y“O>O<R]]駟e]'Ѓ 8yM =b'[]"O7,o|Lxe5]xӕ.1U[VV^addRQwW?.^|Eos 7pg8ۗ<rH16s,1ओO3lip`zz`xh19r=n"XZFǑt9BiiX~qTN pEA<#CQA G F"QRyvrr~::%uqʢD#0>6DZbsF# ""qDT:K:FXy霹b%BF=<(**bH9W$ 3vBֶ'}&DUx N$G1{rcǨ?PGuΏWA<k]?QX67~4.(]D4^9g%'T)DD"TSZqL0:4ʦA>>d>F[[r)\p<8p|Z̀9z:{?*f"6[Tk۱ Қ/9Gdno2i O>OG>Mg2=o ӕE>>al 2޹'LU38d2Dfo> 2: ?[q)Дlڴs9*xqhmK6%QXHaalt:MIi)ں$8cƁ248=K&|2N\ y-֦fҒ$e %"EuTJ}}A ~$GЊ?A 9ܲh/?HD}\M3SS(*L3WS?%I% R:,[&6)|A hpq_}ܟlü<[z6qguc 6!qȎWexn@2T[R*cㅔ8{n% $ I؀H)E3ʢDopNGG%;1xp=8d!#(3fgsEEpyf/?m۶dRb8D|$r!in^wڵkI$ H$h„eɒ%[}ﻜ}ٴsˈD";v 6pΝ;5N:SSSCmm?]ǩ;Gsرc;bf͚8\x8C}}=W\q%T{b}46b۶,]ƙsߧEWG?!Rʕt=w^:( 6q>k:M|3a^ML6M8 7|3(x &aɀ9K@'K žJ'Oc&AA__CCCDQI85>[ f?R UPG=Bgh`bqII . !p9|m- H080@[^*ô7;x aQ80vL~>3ۦJ|*N#lͲha3\0?ͩuCĆom[LLZEޞ92<#'$^御4[[Xga~V@gkkϾ@$$p9,.$C6ʂA4V@k M qɤS]S(m]c .Wgo_{oQ[{z>uɒmɲeYmlc,\1@hhGB1Pbl nܻ-jջ^uwǴ3{SN|޻Μs993;{X o[# ܹGVyEvSV\: bqTZ sE\O?aX,w@K6y2͗צy@> kh'MYtF[J/OWiʓ:UV:kS7ozd3QRxZ &L@-0+u)|F 6WLCgwatvƂHvVP:t!☆ ;~Q&λ޿o*eL01uׯ]YmXp.O,`g呧LtgŒ3qEbϞC `~SbM=!'ړ#` G1Gs6z(m4 ٳw1 .Yrh :Sw.Ll9'O9;6m@s7vOY(0gsRAvDsk`R 1q+/D1%ju|ӍSGps"3jFڹ/4&aX@\jf7]0u\sԅSq00km 9Ə>ޤI!tvNj2 :;;Q*$ >%h$,I^I2zm;;0~J?D4i:򃐛ܧׯ*0ĉ[Kw_<;G.m_rn>ݿsĄ d GVE9a =8`*G!`c>RU)|;LrZ&22Fs۞Α(E?'F!C)3}u&VxA`hx Bߤ I})?c \JdB&>fr T+#8iR{ p8Nt8:qTg$زe LNo8yfL8a / @G#8%88Foo~8^X`!` ]`C^P؃!ԍt!k}Yg*-СotO+4p_r[ȩ4fcμ1\c+UxkUGw4|њ_Z>?ZlLuۺ}'aEyX2PQ~TnFogh䫻O~çv۞Au#~'aqgmnly<|u:3ݿ!}V*ظi K<ĉׇ  /" ^576m#[O#˭l;xNrgou!` #&R2 |`<,<`Sv"@޷נpYItV~d]VџWc$T-k a<,]VuCn}?*-z(y?oUVWS ԕLPYH\rQ!lB(7d>*gn9YO+/'ܲT.w+Q}kC^8m[^kWʕ]7˷Ͳr*9jwZR|~ ).O}Er͙/:{x.e{=GVdsmEm^7 ]cȗ=f)~ί@ETMUliLS:khrY:;0`j`fiG@ug5_M1fnYU1Uߥ,~JR/7ט%֠`nW<`hKW)0ғk:_˃1V+~N{qɏ_ѷp鉁r%Of ұہ #3UZ7kFڄY<)`0ӅTSFf kV]~' VypHzt>`+mI_!|LViy(OTP[^P(LT7:Dê;.M-,Rfltd}~ML2߶̦|ָg6߱Rz:#S+o;2I`6KF6Jnuy?ӉV"m$'tLK 嶿U%8y9"͛w 2>YV.u`7d`Wb^#Qpn> d:}!2DTrƘ.uK<'/hY*;{X~+Qry9[g1%:bnv\M}LJu)J.}ՕnW]u>я .HEEKDj T:Y[3!SF>2n.,CQjCҺT#MM#wz/-d9#\eNN7:ӄk>vJRRK6T'S:ɧCBVF*{3pfƹ+2^Vs}̙v>Ay43f̩\Re(?cΙ'kO))JT{ohF~{'-Y&GN]5\e} )Ox3th{K9{O*c'/sk?cz2Yy)Ӫl+|m}s\V>q<81w` ͚WHpveJ+N2NǍq p3ؙ_|&ȷ[r3B!qj@zdEx՞uE O8i' D\C,^ đ%mt46K:Gtcdj4cTG`3:S-mC ̜9e$'o9DY9)}7&"tH= CPA0 XM_G U8w13QwiM]`N~.y+{N$MN;s=8Bc͇kd[R![ZRQ>):n1A!j OGz%V~lpjxը偾VcϠ~9fJ/ȏob~ `Evc{{008c!=Hᑑi^tuuٌw_3T(7iV:;;P.?4A3PTchxF%LfΝ} baq'bPU##VoGF225k֠\ƒ #4 ^Q31 c)ph`񗪱;)'XRABX@MA]7@bG%\i@SKHzYK9@-02Qt96A;j%*$ڟ3'W@lŠr/cB Y>1rO%zϘ`ONFï^yPrp})OmhQV~r֌Sw ?xވx/~;®={qaxxytuaۃl۾}&X, "p v܅Ӧ[nC$x_\fY3?z apheعӦNP(ضm"&N4@:qoS'aq7lj3"x߁Z5 '՘@Z׭ǕZ4S'ɧW #|džpw-:l۶{pYG睃{"|Wo~=cG?=zƣl\>Q|>|oO;± cxx?^vXL2_o" Cv ۰c۶@^ǷsO~w^F|犯 j<]_^,?V;пoov({?^bxqɛއ}5V<Oo}KV1c4;j.VZ39PTUX08uG?AܰT+#Xr%;k׭òeсJeyuXr"<3Xf-::;o_?2ok_׃-~kO~ k׬S0aX\RCIJsCRb]xDZn:T+| Ǔ+$:ыҮFmm-crz۽{Ʒ~*:pV[0 >~d;ЌMSSYHC5uo~|pYƷv;@#5 [_E;~|/~ 4RP7/~׬'>!wHtoc\gفi[txZYa,>q~7za⫟,/Z0'>!L4_70RXKi0v}Gy! $Mä/LF]#u7/-Bp! 9P;@J^}y,19(V.Y-/U**py._vGV6k4;g:l,;-utYqSԁ^dJg):(8]]9iQr')rAMӖ☕8w{8N&NADAB2N_g0yx̚5lF lڼf.X 9rmtucw?^X4>c!}*Lq$+&u̔N`p ]o$3ŢF9&Cט.;;a5s랷Ealo/}$nǻ-B]jEtuuaG{; xk_^~)o?տuH04S&἗ 3Oǥ]?u"yw_z4|ツO|xv*<,9 S&#"$L> /lwxGN|_P qހ /lĝ,ˮ @ !9!rKg}]!#O`*J2%v\Kax\F]^,NZӓm\̮?K% T-s")0E4Eغt&p' 8ZA S ,- Zjü%oF0@!=<' yKS~Jz|P,*.@[K<&&E5ωZ٣)C̀R)Et͚R~%B$޵'73MD~Z SRyit(p׿~ex',P(W?u75 s>C8v .B5d;{>ůbd17g('v"][ם7ԍbШNIDM$ S"b櫨(CDtKѺꬥC[aQݥ5q J8OHxxk,Q]"zJ#|߹TE:zmH_vG{4e@jecwVG%hm۹E̓&ԔAѣS\r>N1Bԁi;\OmR: {재R:_23b jYT.сRPвr Sz3`poדO=$I0#S JyԩXh!7λ GggIz'q=P.KRw^f< ,[_"OC{csV2K湉;x>j%/bcn Cj)86ͫ]qc@##45jUT#TQ^!n䴛k\ &:άszH8>"DQfS<E8uL0P{&fāO=|D.#:FGPeuHd]5<7I'!C&H7)hvet*O> Wi}@cLKuו/c HO?-FHfnjpLG.i/MK?'udG}dK%Tn&pl}\E6B ->$ C kH~V6*o]:-q#6$SbƹZM-u~ 6ý."Ό:]G1F0oY:-~Z,$¾WD~d3{Zzu1 7!Ry|/Fh?]R}U󐜊lM}.ZyoB,$pQ!ض}Zj *El&+)ccY&_xүZP\ ^Ya^ϬX8/+VW^.U{ޅ|x)Ou|wd};XyڝJUOU iTK!BB+p5Xi? %_Bxq>xRhr$̚5C Hݻvk" g4`LW$, B,`3{6C'-C>_\sa?h|矿@4|ͷN/?Wj  gq¢x{J7{6h3;h sR`./rLfΗ2a[͹}XR3s"YQQFHn=ni+~<$:&B+R9 #ϭvL~ 3`%WyV+(S+FZ|EjLT;/L^ y$ATZWfXOMx=YqQeId+ҁ7O^3@y_sX*z$uMKC^n>WZᨷ !Q ӛRc4 ҒgcRxQR#|EL"J \5K*݄/[׾2s7==~Q*K/8[n3+V}N}\ܱX~u͸-o|=yqonϮ-gE$*nO(wi%5|ppL[W֟b 3-'}D{GܽB?p'|dF#JJFsŲE8'@W,/M :jVs&*+4  D8 `Xz9$Vt]f3;kW]L<|lgWDx;[/4:=W y C%~ɞCW7/j?eT kS= ўBFFXHoЎ,'I/}x⩧]kpՏ~Tٯc0~QT7_[;Q7X v޹;7oٺ <^KqKq 23CZտƁLGf^Z]vF'\AX8&sqqvTmAhC?#qw>̝3>{Lps„ k}o_?8F@@nߜLki_0&,ctClRDS|e]P>̀tn !ٺ4m{S*έ#1N.TYH#d4,m{Ɇ2ug {hj|+hDQduƂSd ө Bq8#,к㝎;Y?mJsRQI־`kDO1i&>lGme)Ko|4'ezP% lJ00 ɛQ҆S3+ %]WX?nb@i8s'3uEw@w?TFDJ:-_T53PTӳH tb) IDATvatdN_A JE5t6_N*+L=̦i[m8Oi6zՒL8tˈ4W}Zx $r'3}1kV>omu_ee5gL?ru%JI{ݍ`84M5:H8^!u$VLB9}Tfu X }bI Z&n-F MZ62LS9"+?/d[ mغ5 tr3%O0ZG $v.L8MNeՀ GM5TF122ZZ@@vfvD"_d۳Y œ&;} ާ[Egָ W> c+=] 6?Ϋ>,^fFkWiJ7S}BoV3RF}Ȫ`8L;]lں.i{'iQ|h*`~AD 1, rK7k|n1s]S$x(-E#T^dhd.K~ȑ42Fuz5 IT ̗<=!7sQ!͋|DܳQ7Q|Il]%7X<퍭h q<(]~GySS|kI^ɽ'4 \@k\VTL`"{Zk)d6`̖كE Rs6.h06fpnP-'H=P-]U7tKNoMw?yxˏqQ.'7>w}zs?> ,!tvubhur \.:qG'D/,0p3> ^Oj,Ip/7_OܻBT}?hiU-%c ܷ##wQ8Sq( ( }Q$|m͡58aiv#X6020"!tLgO&$U~ht!(% (o(ii2)~Uʵ| =Xa9əZ~60:6/)n>%i$ xVN:0/ׇN2=0dTɋMS{YLCV &0~yno62vl@Owƴib;@!Įݎij/rEd* xdw#fTl=lؽFpVJ}+}Y <<) rNƉ(9R'bׁ/ªMHZ8{2{'_a"B'}RgBjsl&>={YA;vZ W$ i#~!MOo6#|Z޿' 2He6oCػo0 WI`mpeQ.QՄVJQS Jd!xH~{cQVS\'t2 +LJL3OeDY gƙ#:XUzLT_"=R2p^tFx涀6bI#αB~x =.8#(=&@E@@(vlz۟[t(=}] k}h%Yt<z#84.⢇snUw!_r &h6O(\;|tA9_ݳTג1K5)cFvʩ#NВBk{-iܢ٠\qQi!"jyV"ox\'rƎcLYLaعkv1R`Se (<ϿQY(5f#v@{Z; ԃ:-铉swѶ4K&GX[V<|,GzP93' m{bS}?$S3hG❙1k&[I{Fpњç?a]@!g;K>Ycܴe|H(ό8ZTΞBnP+oQˊ:J7 ޡx1 :b$ʇ}h4R`Ͳ2TP=[2wMTԠ1p=tI)zNVV6Xe}}ڗґUvEEtu; fit`fiЍb#X``+gd,N~o󦁍L@߄^}m3@[ms3OGXrIXC{l^,9+V>j-k..=8Exg0mT7>$֬]/šuOWS&OFTBbcɉ'`&N@3eVܽl9Ə]`ⲋ1>{,o93K]ŋP(0udO\.ȥzi #0 2S)ڌS,ئ˪*h,pwݭh(_^ny`յ/j|:6!I1=X>`۩5F&Uk5oRS| /ze$zn<446n)̊5s&݋'z|v؉ǣٌbs:e n㏸/ ^ظgv **fϞOZ֣a4u c݌K/K/8v@ooJ~qEKGv zwbtuvX,# (ڵOc6c{zw؉ݻv㼗8cO:Jܧ'5ZхtiBzB#>2*?whk^ƌ1oVJ;7n Na YRy©FVIWcYd̸`Ξ G1 GwWqÅ CƘ8/mnwe!#:}lb1&.=PQYﻛoر֞u4vhdNrH޼;CzX,YmJ>Qk,^MM=U?خ]; qw\Kc3f5H([&C%YoDTk5q ΁0 Ш7с(Frcp+^p3QBZ 岩'xь#IWEOWGu$:&&j:݋c,Ot"~r-9axL:B_U 8V Hb{0{c-HʑT:ea8 xL508~+8BX@TBG`Ab!D]"_ى$[oG3P.5-1r$Š!T&2!S! 0JRBX('MB` QRGVGGGH h6̫0DTB@P޽"oR FS̋uϲ+C T_0E_vK;W̑*n`i(oÝa-2d =M_M>@ڰ4цDV[r udNZKXj[5ˈt t<̓1ЩHb$]b)0Ip'H֝31::'Kk~{nT]3&4>%} Mzo'P@Mt, 2Y +Ϟvh*+SV=E+^II6ʣ;r`jВH}TH@fj kQb$h6v1 bt6lߋz3+1e}-c:0g$B;{Ch4L^e4ZZCR (0hpa0D! 14<#sZݪy(/mZUUT5Fh`W` 4BtVRz (H4J0cT1Q0keu7ttoXlज(^i&>qg8StqNqfg@΄̾dPMKri-gg(a\07a"}һ}aѡ΍#}OC^4ӠEEUSTj=dߜތ"A Ze"Ri} #/?-h IDATl4`,Bqfq8.X`LRJ/IxDn7/0 XӨEMɄ}rTnv?:ir#6zcL*>C(B ׿5ƍN8y_я;ۀr'=vZoo I8Voރr1ļQ*(`f_/{u\s׳3uo_c|wQZ#NîLԣ˵uG^+~;e|P`V3(WA`ˡ~ٳl4tm_>c֥R$hFM$51s@-?Z@8I8P7Qh(:2|3jq[țs]fplh6zc8X[/Hzq!c$qvDWQQ| m0w7"lܭV(K(_1 NsnL82^axf~ʹzKGD05-:q} dW" 41s3AoNVnZWj(l\˛RػcxsJ)mbp1L(P "rJ`;M>TP0, 4ՑOM RU09y2!DpľjJJetX("ϣX(Xt8^TGbZx@&Dkgӽ (ʜGߟNFL4RT0;;{ gΜ#??ښ029"hKanNc\qZuXёE[[Q*Ukqjbcȁ|w_"[15G[oًjclzzh31@[?],1:a & "LcljK~[GޟJR@53rT+λSBl9x*)< sEYq8@'cH' `*WJT+W_Tr| 1XHUn)oΫ8os 0(wvy.jg#0z<W{yBbω$ҩׇɐI$BmZ1Nz)ӝТFLS3%a+2.ǣ-((0ӎ4X1JfF@ ̵! LlVĜ8Y6GGH{ɠ=5yB22LU8 qjH^BN~/{' Φ'Z+JSSS8|0z{{oB".ݼ?x x֧)\e S]֊Ȃ1຋Áq.R)ڞft41YlZݎj*,*Mgj1ě(2(uH(!.d$"s.) xD]˥ >: ¹ $AQU8 ZVU_|/MRJ1THw;y?mA[o,;Q3/v`OWP]ѷR)ZSGuQZ {\0BMHvuA%8pr<7. ⡜WoDޡ!/S<@E8 $9MWOr @ ݞo(į]G[" vY%اT4PObg)n@yO5s91Kg,uo޽<pcrUb Cv[ĮQOIlacśqm)cffJcͲW5 /Z/ZX崽mgsp1_1\y*pαaEUZ6Z(fAžl:}yE>o3Ϋ樚OKK)Wdfr!OXqO!.)ޡGU+wn@n"i~Rjsﴎ!(Xny/Fǘ"&<Pb^6qOȏ K:PzyA/F %+A"_TD I,ҘvB\p/kXOO.}G{sO$E/Ic\ۤn L\Μ8`/ 4d\N{NhA%6L,?mMbגxlL]sT 'ܙS.1wCG^1a b>,b7&[N.ZvcչNp/Uz9I!s҉[tl+`SghL/N Ձ1yuUDy@b'щzjmn[?7,z]TP\Ly-x*в, $@Rܕ1&97;_*P2+U1]}C)E,F,e4]W4Lsڦ]/`Z[]z/+isq2ws P$ρ&;ӀbZLJ$@ϗi/7WixFX( ۸GO$l7< y%vH@bOΐŠL|Ʈ:*%ASWA|DRBl.R Jlf(ءk%4cSLbP5Ҝͣ4s$u7GEfvv0 .*K~d?&;Q&}M(SOq-:3\yi.W8  xntzd[n}MVt6F>ϚɡDo#7>Y_V;M\/F3,kl\-'deĕ-G#b9PO0}f*I g5SrxݎFl}>{Z+&94)PL7C{%0߹$m69ad~lK`ƫey0zԌF/L1$J^8ePBMQv $tpz_Ȫ00A:4N_.^R <3;.ǃh"dM NbhG|mG>ZE\x?矴&d6Q>6f4pŬFDkBSl3UyD eE6 4V u0c~=~_>[=v6 CCmZKal{'A9͠;Et궱:i'}rY0A<ݹ67Qfd0UϚlvmOG"1"$v%N̂՛` tZa4)O -:?]fSᜣX(`X: /߉pS1i7,͢IZ ɓ$pp;P Ҁo !z o0[~ mwLg^8ևţx2Pgj7Ps:7~{dC!Dlb௃3*u^gw ț0$h*KzŒy!dkCGגy`Zը-6cIM^ks}aVga:FcI2al6+_D ۜ*K 駞`i`WQ'\#zdrf0ݻqѩMƀd2iK54'Pǩv~Zlԉ t6Q|B*')Ǥ_},Wn[mon7G&=<0ͫWF=dcZяFOxZs㗥k9asn_a3Շ?"ޗ&Oy\{>'D_^9_%K7P zG,r-q??n#@EOhBuF:_%l~.bdH$~DBy*D 14X u X\Y*La m4[1\ǚt|zrs5 :#6zh>=r@G-aƘxpum\L+s5iQ =B. u~ABleKCAAeRF="&m*Y7J!UևVh¿#_@B ^b9T/׶39AtFGrzfOrm{\)%3R.N5Fi]ahiFd/i8|4(a >cr4KOZ#tLAĄLE%wɽxx̝KS'i,i[t~流ɘm:kFͦJomKV2H3+lDi_϶RYǗ-tt8=)sO@ V<J_6щ6z|lzWz J_$5Q--b ru<{Mu)7~;۵֧15Gks;LJ'qC{w]emM89:d"ΖFC] - L#WR\=ƧP*WX,#^'} Rɸ$g9 <=455f]\QdpkD 1׷fm^ DPQGpt@E;TԺm{)vh EzP^oqL O([Wfhc~L oPx6d2"LJ2&TO5S๤/RaUQmEGa2[)8@EI0&? <)]J&6îX1HvT~F:X$sxυYa4U_>šLZ3ϴOzR.\"q3 bvŌ򙖛yLQZ?S1ϙ8JLkA!r?``.NQP2@$ TT`ouߺ#`K~ lTul$yh(Dlo:=m+Ķ.J&l'=J;ȼ0u<To&T{]L]vp?MQt(6s/:}) IDAT ,z(DdGIe3h#| kMx4PE>LT|}0NI0ߔ.ht ~]$ߗd+K * >c'1P@]OwwBO[_ݎo\y9c[,hiB k;k'q八P*Wxۥ"c/`flz: Π1L*|`rDl,wdizQ8 }0fE% 'FP՜6 sOqmzϧBVd6{}`&ܔ,h`S:C @@ZLOAx@?_+Di1+b~dC%]Ft=tPoԍݎ xiNk>z:i׺SiOM܈x&R&7|&[i2*o<a@.K7&= M*2nȧj:YLbn9Kw-934>BT*ԑ8cy{Ww`j6r1us~yM^;4}uu$p5zruz8M2xsjialz3 dӁ`(erESQN{ۋbOg O B,PcZ'jJ}~m b v|Aw#2hjzF{G5[y0o6=#a߉?FJD"x<6 ,\c-υ| V>XބeM{[%}!w\ﭗSJk6a6E}/jZoZ9xX,X,S9 oxTv# /(1 ^xz%7p@~bӴv'v}|@`}}=Z[%3g^멑3{WZvazJkK nz.kkUV^jwwG/ iwt"\333s5lV\\+#_FY7آϰf*f ~c2ؒFԱaSjQEyE稶 ǥM-g-q=ƻ2'N`ժUqk16n|| O>$P.P*061jcƍhgk:5BS>)m=Jrj۶#:6.g6Ls_~q{Ҫ`D7L?[Z'1(D-<\|׶ÎF;̆Bo6z t<L24k-6D{)C\WY&ytyD'*hL}mbZA|lk6 s%#(m"L0diD[GQ_X`1-*G1 FoKA5(Q Tے`{V3jkͦi]ԍ;ƠFPhLq V66baZILz&HnX;XgԄ&椦{S% Qx<=t1`у_TAr.Ea0TiiJ_.e}E͖`7ŎZ4Gyj)L[_*sxQP̴>l6U(! 0 h[H[ s _/K+ŢmbO`W_ØDelhpm$ЎhϦDKqZ̍lEMQjh>!M=Yʦ&tbZx?Rle/EFVmè#˽)ym`Բޢ_x1/Y-OTg#RJ\kst"Fw4j+s鯔,>4sc7QgZ"Xł {mDD Mu-<Ё?WLrAfkl46VQe}>ڴζ^g16gF#nAbHOùǞ-ZJ[%Ӡ$K dXuPe _Т+?lmKAlch ܘ#^?ۼ?6Lv>tׇo(O0?(I}K&]͕__w:S1fmx058l%llξ% Dibr$JZLm9 ?ۂ{قMn 2&ZalrJvD{)%ʜFMXZ׶@AATg6Pe!HנlaKAtAzE_K6}d&0YAuhJ~_bqm*ٮS Z_A1!a*,1Жt>ai2 p\TVB2 PT6Qh2Fi*CP [A[X,yڣ2_K K-6?"mL3=Jkm[BvS1%S` AK]ifom>lzSa> AsD'Mr~a@Z+Y8L2m5]&}. m7RM(]aڌ+AV^1> t;$8,[ J&A X[0gږ&(z% GMm?lnQm|P} K[ߠZ%0z6yDMA9z[ߨzmle!GjsP m1'L(kKQ(їЀD+fJ_Ռ"J?rfp 1pU[(b+<Img/3I@Tch]O?Akd >QכArG?6Y/&s}C&'IgPTP.07;vAgyepZK kCh.,)=y-R;,I܇ZԀ/m J"oZlVjkA TteZND/(AiْVQx6b+ӽI& #. Lփ lEDŽc( ҩupe1Gq.C3(NcUgkzQT1:5Ob ن4FWmDkS~b?nbNO`1kşϗ.NaQַF M~k벛M~j;o8011!TUqY---s)։KS:= 6NStMth f7a&xA[ljJv&t6{gM:F|-ʵ6z"DYϺے(k*-`E acltM(1$O6]m EjcQogA&hÀI(<:u}㊕"~Xٻ]ۑZ)4Q*-1o޶x Ͼ::1|W:s ]ނ#*hn`ݲO/4U\=v+|&H0N6rIn򀩞ڊsBc1?466SS8|JEITEqҠ(XGaԅMm|6 OԾ6A<x-zBo)2:>o c*&>MzK hD+Z J6USazsؘUN&p'1Q\{)#HmnGH'2(Η117@(D<К8}\cZtOk} ر?x ~a0g"-&dcA|b6Q}$sBU"J)~X.Q(Usv <EZa%*cǎ @{{;9V0022rt:-MMMRBSS6ۇgyjtgΜQƉrqd2$XB!-JRũY4էLeAC&ƀG_)$1d>3 TH%>گt4 f ,'F102l}Dܪ'McS (+(HtP*W(&gP,WNeR3Jz%GZ:%NL㣂@*-*ky1noNQ &Atu 6惡0|hhDBCOOd̞٬3??'xBmmmF"團c Dx077.;v McCP@]]Q])47'FٚW<*7o79ӈ=\ e47ez G2dzbzoe"f X,p$Ʀs'bӋ8>t x4s {q, n݊V(˸˱}v}XboP(]z~yo~غu+6mڄgyuuu,۷#N{ZZZ011rȑ#x/|iӦ@a\x^}'Fc}s#SX+;s^(`QAGs=3[,4 L%ӓxAp=H8=1jiɱH&☜]OߟúeɳGP,aG|T*뮻^{UQ*,C,4Vvd#x냶|VGNJ(#l-rGA\m&D`QmeT^K%(襹=;<\|Ÿ{Յm۶CڐL&QWWr 077~EZEcc# rW]un466bΝX|9rG}===rx;߉ÇX,(ի֭[6lc ' W:sbnBg4 6j&.ҔA4weu[QV=w] 6hǻ.߀憌O~9c,R8:X,c6jF<C!d"| mzgnL102D,5b 6JıX,u$* v#W΁Row4Oaz>| Iepe82tsy\I/q^bZKm[1s_(QO?iڏ~&yM[Q 4&@hC6.{\&zmAq8ʜӶillѣG҂7M׿^\Gzs“;î. ^(VZ%wW^y%:;;ڊd2M60}vI֭C2{^?~HRhmmEP,vZbY4.C}7\1L6FQ7t}LO˶t[Coӽ:_s\P*p""o}+FFF0==[P(;v@,qwbƍꪫpwkAGGz{{fۋ!pαi&,_X 6lV^3g>֯_kuuuC2D{{;ۋ-[`ƍؽ{7?|\zxgp뭷"Jozӛ8vz|Vxh$0~zf됈ǰ x LJk`mO 7!J5880YhE<C\,I "]ъlRعs'z{{CWK#4.=f[V.lC 2[yM~-x#Xӌ󖵺38oY sH'`m'D[떵9I[ބd"7MXlODXFVn42ز _O?FZ})J˶|.J3,A"H:S#lvziiϤGME /m FO&zqala&/˸؈X,k1.CRAT[q\{/ZZZpu?!v؁+V`ݺu( XjbFFFc ƍQ(p=vBRARlقMƚ~\;v\X o||rpGM\m ig IDAT['qP)nEqlu%>G_G9t|xDӷ(`Ѧ=33c|sdFO)ttv!> @ɸm 8e C6TkrR*~ctr)VcY݌_yz0^8tfZrDg3wcVsGD-+yu5Ri/R@}RZyv@@4*eGgU}-e a;AY\###\`n@"=q$$hMKWX g;lh `կ)oؓ'O`Ex c lXDsK9D#Jx^^/b,333VC׋a>l;Tm̃|pD1\6T-yP6%QS`Lg`c!n9+ &ה>8w胁qGl$ZВ4fu8gzZգE|)?Zc@Wf/'cU{G\3w1j!Stf2eAm%Cǧ_MeFc9 &9ͯuyRz#y7;wt}tDG{ TL97Ѯ=Q],x|0k]BB!Nye:]2%* CAt#ȆަF+W M`OQ% XiQv&]b!Pꉃ;/.v0/3P P2lP\K$++j_I=?9@\g_iAeh.Y=x[l3D]?fV<&c2a2@NE F3#B̘$]Cw=t""'kBIs׋0|CYgy n Զ@wZb(ٛ)qA;S[{dEw1O-8@ ՟ˆnL.pc.u|Hgz$ i+JSL5]\WEAL %^l< 'VG@n_ |)GNZxvB9 }ltL0 " D J( S6Լv@)\jB "\<&+ߥ"֏1'Xǘ:hP9Q: 9JbVOb[i3:rN LB l@CoW-y0٬GHԷc@'9IQP"n¶NBaGt o M: ?HK:=AӔ8ڡɳ6ɿ(JAӅd3BN\VUV56Z+Rtx*t!7bc<`fç;::b1p$1ݯa~0,I `@'}˰@87b8p(XZ@А"S,h^o\Z*&I>E(2I1~Zx`Cͩ>\΍~QD Rgl8aԷY#"yӫ R, & X dBUVQ,ɜ{:ҧD3/Z%qpQTsNA?q_AZ|qW6ހ\]޴>l (/W6:֤[Tz0q4(X1$ RA],08x>S^} {_ɓ8x *O╽{144wPཫ"s”yQuz/p{uL&6ї|_Ph)HlfS|]0Oa [q~~=4Am':*]I$"=!e`qe$y@0#"S+z?ƼX 5=<"6*\֛tLRT=7<OJ 5T]s|D;3P{ 3488\sD>XӧZ(((ʈ<@LđN'P*/C}UϹԁUqGDZcŔLIƾQJW()T$~](UJʘS*u?}\?{_^LET6΁ȰO.A(\yLmA%"@GyX) 3 ӍlKAݤ!٨EGp{ ?W^F,[>݇vt1eI_qo|͏g 7?󍡧3`>x?xۇ]kdҸ[p'>4HD4>dN EIfa j+'U۴*4`ȩ`Oz҃C kCrS5GDS-1QꉦB N>vvtv DpQ0Q}Da]|7Kq6P:# 9ͼ)JSf\FJWCC?Oբ ?a!cO;ܕ1) %TUIs d $ ċ&u!C2@1T]87X1 )OMRcz~ׯzvz #93v@Xv~yvO=Y,kkGqzbc-ॾa/qzb'1/9 ;'zMi OR@"_= B\H'xrIʍhO㧻ayG e$1M.nu\Q/оz3 >fk7K=x~sztTv&A10䚛ՍÇp`~+e :u`7p? *ΌO/֛ޏ?* N?Y]AZŝ>Ͽt;>z|o~=);'p{_*8۟ ޴ _ssv;Us&N \v;~[c޴ `9|KE}y>t}n{ڳ7 "p8ɐsLN^>L"ZjrQC`D0SI iz5#;><X5@֨ k\!(BdPAZdVQL`A,8JQlO qWPpǓK̯sIO݉^bMiBŮf^?CT+U1,,Q(LQT$"x*b$p=pcȤK!L>x<pD"L:t*L&L& 824ҩ2id)d)eoN$`m .߲'c3xn|fbvt2yaC 8:43su>3E:}hOUo77e+vfYxGBOP8h2 4lR݅#}}xGƫߌk♧#}Xۋ}?»װww0=5;n? Zg?yǎ'ªk02|_/bm+?| ᒝbnvϠRd2xŗ ܌>I{]5^o7/O$z;9v^zE\q7!p}o 3X\\s<|3" }'_]J;A,_+=A[J2 @dp?MwB鵯xq/ Īp9Sx9=e_'u߃RnOUA[K7¡,mNXt:K[PS@ wF@cPצ461wt(*&<<pNCV o?%E?\Q| 6ܫ p1\1G_vc'ho:\S}tK"("ttf|**J Ur,ea!JHbŜ>>+ {g#}}~==x01>ʾ(J?|çOcffF^š5k7o;[Zs\o_cK/MxM7~J߷ }0p8LLf055JD"x"Nil޲7_FO2|˭}mgWj@ 0|G~ԝ]Kv^QRfH&(ʎ3bB)b;ꐈwJyi@\F:rJL5J\K.>7lQU`||dr߆m֧3ƺF ې޼m cXބ9 󗣵=*.ۼMi\ICgK|L*Bt4N͠1#OMM'T:۵ ؊i\_SI_ w lm ͓\z6l?hhhE۷w~~F!㋷ G||:::+?w~ĩA;zݍӧN1g20cNsHg2ԧZũSH$ ^(ľ{qQLOc.Mʡ}n7:&}hf1p88?|gK|W_s +?e_wKyK Ng+ zoa? ;'i==AA<Mt:/LLRj+*'% {\fCJ58`йf]_23P9Hi>l G&SU. .l/|6% K Hd"*i PrIv캭b+&5 Ǟ|E^o[31ʜk6P7`f\A"Q2༊XoꟉD *J ƀx,Wb(ˈǽhs/ ?pNj\Rȗ+ x{OSWw̢O_>Հ]?#L+¦M5% `EGVZ$T=?ېƯQ17^[cm $X/nV|zXb+ܛEDGO"~Ɯ؆ТZ>\:Ƿwz5kѳl.p+Coz\wk}S;4e [>QJ%1;3d2X?>7 o{;ܾ uw8du6LMO,nV⃻އ.|~ nyS>OG~n_ۯK(JϙðmpweXGFGG'nx׻W_/r*pJ&q9{&l)gS5 yFOS*wB76aT0(2/"G~a%(-Bgo%8rd kDؐLg=@{MAbRNqbh8ʌV"I `r'n()dK)mF)GM[ާBJmR7E\GCM2_<89|AYvyef[)6UpڄjՉxyDX._D"R~mWh(HΣC"G]].L +U_APDXtT:DyJb&64ߋɄ3t#t=n{o#W~ mA,7n_1\C1g1At|zZGY?pA2D9J}t~^Q OSSBssK$qaPrJ&' LTU#cvvD\jg&&knF*,_<E6q9GSK(x%` >!IlDGG'ظ8{ v{䬕` D=^So xpԞZ.뗩 AD1Jh@Ajj zEd\~ҢO?1O IDAT|=z H_,vg%eKظx 2t{ZLh1#"C(Qrr2ҋ@Dd$SpJjl1@ ( p\\q~b PXx8IJS@BCCIJN=Ett41~IP'& d2i$Gy:0)dZՈjL2梼jZɨO;:ə SɲՀٍ?)M;^s:e"{fi^񓕨p^t%YDUC{f嵤GT᧳~+SѢ b4ȁ05>=3+N1fVR~R_V2{ZXkj~8d&(PyUewz*)s88aS.^QLIO&wѹ] B!!{XBBM0pd} GZNrJ~w'9@8{Z>R k6] o6 jWգIOʿ ī^bFY1[eY?NiE$L׫,^KaN YI%@4AWdm ? V49-s f'ؽJ޽?a>k| jkYoD/` Zt(0{ꌋ^K 88z}uC,Q$62_Cw ZcFS/;"hׁLJ8hkVO0+d %8Gw䗤%we%}`9 FDU)Y2Б g$jelNgB}st[IV3܏Yh>+?6444KBweX^&'}_[K4 ڔ}0VKw,k.R3JvRѺoLLWZz-%@*ۂė T#Qma$q(ytt7?I*MFRSU,\Q'!̔Lnv3ʲVTF B鸦CC KIasت|&)xxȐJAGE7يe,鲨A5h?5$6%+/<EƩǒKE1'uy&i(+PAbz-(Ȫd4=IAB&Ǿvhr% AGS_)OV~ Yg?HSִ ~E_:5^'-8LZu8mи?=ǕwTWJP^k7¡,E|_W\jŢW_-\sM=JVpq0eы"?=Iݧa wO S-|_3= M9Aw( .'DLt08PʉLL$7VcGrSόVp~H% S|HA4,A:ZX !(ɓ =:$s ´2 R\̦TJ> G#Y4$U@$ʠU'J𓑌{z"+|_xO]k@0H޵_ Lĩi 4dN,* i1PLQS,t\Y|RR fCKf+_֪T8@sH&ZQ zhjx2á[\|/#H{_!B^9jA D䴟8}Q@!z"n5"{-6%5B 8O=|_NВe *` wEȦy /Xpn0=ӛCz|}l>HZߩkfŠ^= @iУWC-xe[i+ѥ_ OɇVCZ L0zuVl䣇CO9@|w64szMv95VKw4u-=Zh '^kNh 4|U >망ND 4$t:hbo*/@ᙶ!S6%{*գ~:!~eϫՎqD]2nC(nVK$9 c\\ؤh~7:HKtTYA`dF|Lo{AaMrvhd ]3|ӛ|Zf˻~7HK@}0bz31,hnУ5X dUU|9`b6<&hT(H\uX`ފL_ V֯^~Z* K-z2`ѥCO6~Zmhu-z֒RE}j[a*Y`?SHOr)7w/nw0=_Ov(2`qUISs7{f;Bbla!c#F9 L؜&pm Md)gBcÅ&ÐeP\n/QL:\ :2'~rn6sz!A]k`sZ Oo~+aͷ= 3>X;l&R(ȑje@`\`0cTT^ ߹ `c0[y+d>-U\( >+j3[֓l͆o=\J1#=y \*XnȻXxoL.p1D auv&Do_{^n58eDpwKtx m C+sԑBBw-ahFtdsA`~v6N7aBCH`h̆!4m}#v>8zG-dI^&oKlt4nwƮǁq2Xʢ Ɛ U!  k9Rgr(V{QMg Yai%;jzt }5 hU0@|yPZ?i8hşpjѯ/Zr dCf3dE=zM㳖'JttEWROw%Hv,v<bR͎!$z[O^>WOٛ8F&0E"ȏpy7X KVrn0?ĵdtyt0t#FsuH&@Ey)63Ɩr89sZ]tz^]sN6l ߻z*Os֒Mkk+---LNN288q\ OU$%%իWIKKq222d7nx^RSSer NZZgΜʕ+RUUENN \~233Ojjjd_yX|9'O ,222Bee%deeo099ILL & Ahhh ;;ӧOsev;lV+΅ \ 11͛79uS[[˵k`0p9nܸA~~>W^֭[\r\EV;h$66K.qhiid2zy&`0}$FEdt Μ9ŋEǏSQQehjjlhnnjru\"]]]MKK dffrazzzb||J} ԩSȲSˬ˗/c9rNBB|Hgg'OMM /^$==O?A?i!1{ xHKKEw^rrk5~EG2:&{azzzHKKTTTABBW\aw[nɓ'e|Nѣ rmvEo4tttp-V+Gee%_W$&&ꫯݻyX,pUmRҝAII O&""0N>-㭭%++k׮Q[[9gϞr_ ?ɯ]yy9TWW.eee,^r.]"77ݻwS[[c޽\t EBBGe׮]e8IJJzHLLO> ۍd{\.ƨtˡCɡaY$8b᷿- ,w!##OWWiiiTVVrn߾MBB?O(((waxx~lN:E}}=X,9;QPPɓ'e]q:˘f}]Yf999޽Ξ=˂ l6sedRod&9ڳgV{Ϟ=ddd`ڵk}[]]]0۷311d"??/r!9"qtΞ=ˍ7dUΝ;9u\WZZҥK1L;v;wJss3%%%deeaXXp!V";;ݻioofttHVX([lᮻZ9t.p/|r&''e>QVVFLL 0ԺźuEhxrrr8t| ~g?ׯ_ϲeˈOޒa/JJJ(((`8Nfٳ!=Ê+lEII,kײpBL&)))qqn߾=C7m L֬YCww_[iK0Lϭ[())!77a Yf NL&L&_b㉍bp9z{{ILLRIeڵ۷t]qFۇhr<ʕ+_~xF?+Wp(6m"''$ e匏ÁسgHbb"& ({\\ryYz^HfL!4Է݀"anK/#XRR£>*gf<ȉ'z/L~~>wy'R__ω'XnwqWfɒ%,]NEE~)555ŋYrXL&Yd .W!-x?\-?ƍq\.^Î &&ƱLLpp:HHHNۤǍ>f  &'Nj+5ufeqw}KK,jsj:Lʿ/z86Z|(iP WGj IDATEzԅRz(P(Krr2?8p)9-w s222ҥK'?199b!99Ù3gx9v옌?66>bbb" l(5 twwGXV)..`0xdݻ~?f3DFFr?ܫWfٲeő0f46~9sF ilXp8|Y_|aժU0>>˗Kc÷mx y9S"0<V+)))$''3::*\.1112-VDF# !"??~Z[[x<,Z5kx0 rMzzzɑ~dd^BBBx~ҥK 妔uII ?8_I^.n7l۶-#R-|¡+Jn7~MVf&IH2>>N3>1;ikkcxhIgΜ… $%'STTć|ׯg\xXFGt<Ӏ'{ׂٻgVxp3~0/& ԲђUz`(X_=z2ejǧu (IWFrMH@n;TV+] %"D711~ɓ'ihh`Ŋŋy&'Nlp8>};bDѷrYrLf޼y|'dddpwȆhrrϳzj.] Fhmm}]VXAQQ^|n7 Ȉ^DQ&?ΝwI~~>|۶mF6l 된,STTD\\dffLxx8nٗFhXhQQQ rrrصk]Vnx&##~[~%qqqX,<ȓO>uV8u/_{!$$DQ$,,LDQbglիҥK|_fٲe[F͛GVVIcc#+We%)))ddeeJVVnKr)VX!WZZJTTSUUE{{;=))) aa!GEE^rssLJJ >}x СC>(,,ìYbrssEݻYjnlݺp:TVVRZZ˹~:aaafvڅdbz "·b}6.lrssqs-:::(..r.\Htt49}4۶mOJJJw%??ŋrطoO<wqgϞҥK}#]͒%Ku8!!7<,#I픕㏓DBBdeeb27ohJ1;;ln7˗/ƍtuuQ\\IIIe%55p6l@BBfb>S222{l6322ϧ'Oe9ùsgyM~d\d_>`̆+mZ X,,1f%zDBJZ̦=} NZ^ ==30[z^}U3)k.$''j*]~K,RlV믿31UVsze \Ro@+bZ @1kIjjG@< Ԯ*AS.sѻ`=e8٩HeJyAGU|w=6oތfjb2Eœ?A{{[ygWwoArr2?MFFҋtwuWP~"#x_ngދ/KՉ,]o?SW[b~՟+:# S˛=}ʞoGG;.]v^%ZAxaܔVLϸIzĪ^z)@B + 3_Sg-! 3?uK O @q*!C53hMH4>ZJm#""x饗tH6Z2 WY*:RW;åz0f?-EQ$;;; Z%xHJڵkYv\K~=ӕ`Sy% 6ݿ oɏ#kKR}4>fl].^ߖ {rjXpdffQ0Cܾ}I Ӆg*HLL2`JBsmn&Vn -EzQ m;Z0hLouu?P?AL0:ׁ f CWBl~s1|O OOm Oe[-0/0ѭǧ ZE¡Gou%-R2Djy SӫhFd%tC ,gc+WѺf@4Т#3RӠ,JG ( ȤΆfwcXue\.Ο?G[m~#"x1gᝪNa{]*(0<Fz$W1`B?=Bb 4?!{(m0255@uVQ f,E-XaQq\TTTP.ycG}~XK.}6ݔQYY9cR(3.:X߽{/U8к+́t'zީS4*WOg8z}e+¿mnaI߻w >麦F PDQի2lVJzcg`W___8pŋg𯖳Dtg??xW|illdhhȏQm娵5mׇ(twwzAt}}}r}EE!'p8>rqN81C׹Z(PWQy|FZz8ղRQyOS;&Y-GVkgnnPڼ=t";SxI|y>FF`ҥ#LNNredy>c,ZaV+;wt2|}Qgʕ+$&&RXXHJJ cӦM $$AII#<"?G?ݻ]vaضmb 壏>"77B^u8~8dffrAyk;wr7oO>ߜhmm`0VTTqFBCCٷooXz5|ܸLpׯsIxgyq$&&կ~x^n$,,|+F,/ƍedd{͆  Ýw믿Ndd$_cW^yG]p:P]]M[[ׯ'** k$22a>cFGGc >Cn7[la޼y-0TUUOp"##ٲe ߰gNxoo/G{@rr2ǎA"""⭷"))[2<<?d``g}ǏcX_*~vTUUdz>r瞣ey޼yJ<&aoByy9EEElڴ7|p{1vErr2?㒾};aǎlݺĿ˿E___Waݤe>Luu5> K.e޽ 3/k3ҳ0TT40D5~Hjj*<AFc#N o|i~e||ܷAM?U}zz?uu/bsJ򂘈Pb# VKp`jիyP tOkѩT á, z,X pi;ŋvYȑ#l߾]꣏>寮\^j)(`4ټy3_裏X~=8N.]q:<#cpTUU K.NRRSSٸq#<  R__)//k_| {e…\p˗/yfd~/&$$Osl6]]]\tf _(2uuu=zիWc2룺RKww7{| ~֭[GBB2MrM:+U:'(Fa$y1>>NnnfKSPP@DD!//o q aҥ!<߿n7f͚㮤_Yz?' S $"l7Sz, CC' "wq;lX^ƄSfpCo$}"P M4͟:`tNZb Iњ^ 0~Uҵ^32[:g $_PWÞ"0+J<-|sj=ʟgA` {HD.^ÇsaNw9y|(TNfor5RSS`ݺu|Ǭ]VtR.^~;yÇSSSj8E+W38R*֭#""ugg'^&jM&+KNNٳ^d2q!}^/W^%##C6/&22R\4xw]8vvpqFFm hghm='{O62ddF&\mc2jk1m\ݏ塭wۣq5zmуW kѢW͕WK%5=D&4^/Z2R{it8YYYˢEp:DGGM{{;fk׮!aF*$###z*vCZ%Ɲv4fl6366իW!;;[+՛L&0 ر~;hCaa!ׯ_gllDϟύ7cŊaX(((իwf:::0ʹ-g$Xd r)e˖tߏh!e^:::̡8ns$%%j)((6EPꢹb6233'33vrssinnfQTTDgg'iiiTWWc4h4&]TT$V]]8111Ƶkװl\Nf36MnH |d]FAA)))E[[|<䓈А|%ʕ+%--Hf^IJJ"77 rbynݺ%%%XVnܸ!$e4%y?---f0 EWWXt8Klleu(++ngll!z*8233]=p8l6t:':: OllGGG;KTիȲe䃕]&ݍln; j* lmm%!!y''H2I(v6$Z[׮]l6.-=f`X,BBB"550dppP>#2**E-Zdnܸb$^ZK#޽2G%[oR~o^E_z#N?jj~eDߩ}$okY!dH\`ap e$L0/ۉ؎c"Y^d˶,w?e{y,wY}sԩyxѵtv^jk# ?w3X222?7rf䗾חBxb< !+,Q`'9]䤵0tmw{cһ(WWy@ Yi9~5M5^z)^K8{ GPJYmoh ҙe_O6b"Zڻ*AFCf( r&,4yV4V295&VɦSs+A.9OćS69Xƺs̬&#D8Y6ep;|6*nAS^zӧ3t[[';$;>n%)Dj(Tp2H;)‰j*r{+߁ؽ{7asH&/Yە;n8`m<7TVVi6Û%h 1 r8r0/ .+|??%+*xv 25._9|/%и`W%E7o_eqחBo',OcAMyRyiEs*)^tRLuq6vGv7LKŵl<=ܷ젟YU,o$/7^"=‘s)IEؼ,#_E?a٬rj ]-$(iIn+,-rcNȩիW4mރeDŽ:(6NI,DFh*̎Pڵk ܂Mcy۩pѕH9q҉DZMfnq׉DA1ϸL#Y4"ۯvlMל5kXf+_v[%cГ ±8tW9~ nuovmVB xIڶ'5 ʖU--)B84Iۥ6(+-%/?Y011YK#+&'B! AvN3g!((( aG"aFFbdd!zn@Vd222$U֒/s=S5Ɏd`Dmꆥ$P;Nթ}R%[i8y$ɓqEђ8`Zɓ 9,+N`9<<̥K(//gppJO:Eqq1pnn;8BN66իWn}t17Ca~VƋ788HIO:[o^AKKUvB1i2kii+s X)**q[ej˚&''9s , /+7>hm''^[R|dM5=44_kx&'')**⯞Ǥ""0?+Wů|/@_eQ-uH fGhm(ʹU!XXi~]il4$7^B]\GY~Ifw5 1;樏(ܻi9P4Xkvɘg &ۍ&+ Nmtޛ>!eNȀ7Q lݺSSSÇ9}49sÇGaa!۷o™3gطoӦM… ?ݻwsiJKK9p#//\ttr 7o&-- ˞={x|dffqFF-[0>>Naa!vb߾}D"JJJ8|0;w䮻ٳ.\j;Ǝ;B%ݻ={#PTT!6l p~_Ʈ]8OwJkk+Ü:uJלO?͜9scZ˗/ٷoǏNׅfZ[[կ~Ŝ9sͥ!2228z(K Q^^NWW7n$??_?wŋ?~ٳg#+Wկ~U?kƍdddobӦM?K(" rB[oɓ'̙3322µkdݔy&55U`eW_}LUU7oM@K.! }ׯg9sZ}? * pInܸAJJ 6m" ͩS7=s=Gcc#W\aϞ=TTT菮?Nvv6.EEE={VPraa!===\rt-W!?ORΜ9ömB}vZZZbbb-[011Aqq1]]]lٲ;wdRSSQ]vqQ lٲ*++/;vtuuo]x[:m|xWI8qO>$˖-#e22Tt!ڨ~Թ͛7{vʕ+G?^vEii)}}}?~k׮$|BHΝ;9uLNNeFFF(--[oŁdڴi\v͛7SPP@zz:gϞq8ɓ'ٹs'-⥗^bxx Ǡ)Ny]]7),W|DI=T9(F8u$Hzueժ9rw~q vHG]~FFF{((,b[bђ%s޾>҂AVZMUu5#!^aӆ]HiY+fxp!@ 9,|^\X26 !)a^]1ԸzN}^Yżbr3z`VI)o*0MNN&'vrk?Y??o>ʌZdElNyveZڱcUUjdo>~RXXȍ7hݺu\xf̘3<ëJ8 6w^-ZĪU줿ܹsyg_r%/27odӦMXƷ;yggaŊ\vsΑ.( 炙[{jƍB!._̊+xgz*۷ogŊ [twwsQ}ðv͚5^yQVVY|9֭!رc~pbII < 3gܹs&e8uW\ĉx^@TGW__)++CQΝ;Gkk+[neǎx^ BlٲEК49?ŋs=uV/_oA(⩧3gv^C|__Fe^{58y$/fǎ477s x7Q͛7디oHΛ77ng-[ssN7mۘ1cϟ477SSSkwܡիyg Yikkc&yj?СCo|rޱchlldǎ>8s ߿_?w͚5<ܼy7|]Ǝ;hllk׮uV_@KL6M?Lsƌtww/,=zT?~֭v;_6uֱw^Տrcʕ̙3_+V/; 砞xb= ?;ذaӧOgΜ9dddp1/^immСCӧ}vя~Ē%Kػw~T"Ν;ihhSc?Xp!;v`rr^{+W}v=ttvvg֭f7v;994ֵ+2LDV!"DdpWb~Ϳ{>Opl !`0FHKЎ&g f䐞|h׾|_`f.YyYe"E!CDd1*O>0?}`i|i߿={088P9}t,N8>Hqq1Of )((;ztt*)q6ͣ#Gjgƍۘ6m~Cvv6K.+(++n[nիz:RSSY`{? U0$++ ϪU._̆ x饗~Zl999,_{<۾͒Ξ=ƍyW/ *ի9y$ϟaynܸaW枕Ŗ-[xt466R^^N~~>۶m{g$Iӧٸq#6lpBl٢+n:Eqws){='Nh"t\mۦC),,̛79spU]'._LccʹxbtB ؼy3\yWxud޽̘1C?_ӏXr%uuutww͛444F""6m<ٺu+޽^xcǎ!I׳rJ^x\Ν;ٳ;@әt?m|d\ɓ'M`tRRRZ?to~.MMMTWWfddY~}ܹsIKKcbb}\fffRWWƍ7_æq4g)**BQ֯_ϳ>Kww֭chhXb}>|K.)qxÇSRRox< ~FGG3jpp|׫| o>q[B!}iQO}|#qϟٟm6JKKyy'(..6B0>>N8֕3gMOO'`dd;3/}K455⵻_b_Xla֮]K.?^OAuu5>(),,?CpBӹ>*I$1{l~iNؿ?7oIhjj⩧bzMצO˗|LN&m?y'{=֮]###x~z6c 9Š+8v{| _q~GyҢK%I.U79ISS/wa…ɟ s@B9xN,~;vPQQAgg'W^"!Գ>Ƨvg>Ns=|_dAjϜ93 mmm [KvNhrIݛE48Pcc  DŽ&'I?^o CCj𔒒BhrdH$BOOzՄtiG6 GtbllLo2<4ĸcxe}"091i*F>{ uzWkc``#<`)0u u;n8t:"9X߮1ω/;NIɲ+4X벢JyzFaꫯ nUVVõk8{,H'x;w={65RVVFOO}jdŋSSSCNN?|;SJ$inn&--9x }}}455M%>3̟?_O&`ǎ᮵ԐEUUﱪ`ݺu|`۶mvZ|>^~zΝK~~>A***M}}=(yժƯ7O?sNnvLyyy|r>}:<455Eߪ q>A$|>gٲeL6 I~ǚ5khhhĉ\zU̞= ǜ9sǟG={66l ;;իWعs',ب?;6m@3==];wRPPҥK~$ /R\[M*++yܴ1 IX3##B6o̒%K%'',8@ww7+Wǣ裏2k,^yFiTTTPTTo~,X@uu5H͛7tRjkk蠥 ƍ9sB۔ou8Vdc,wkglu|uSV(2(蠢A~?whmm%|~Sꢾ\io#284޽oq~= ֿ"( w}7~*ٳgsQΞmAD܂B2r'#y<ܾNjqı''Xx13>>_'kK#YFFddN0? nǺVZիYp>S̚5+WN':t[k.LaǟѱXŮ Fx7:;x?c˫AZ7i;ǎ EW%=9gmu/048'>>%%\g/xtZ~|"LK#55ۿ3cLz{Ǜ׾N?Ɗ+-ǯ[vMV{ž8H'kuoUփFhMf37eASTӽ[`A;}+AT;Vedd$3> n%;vi*s `܂-'vN0ʒeͳkNuMod͉o;܉;~OD}7 #YY%C+ S5b)1>*dL6;񡥸k2d@N6_]vdr|`FrDflt@q22paRRGV  ڴ fd0Ohb҂(L" D&'B!=;`VK \r!'?W#I9efO$񨡖(rD MV^>O-9.]Na6uЍDʉ7;Y9 )„}*ǎ;Gf-:ivIoo/?OBPWWC=ĦMcN"## {xg äåKxds=_^Y?M&'u[oUkl a0 Nq3ꉂ@: j'~ `q!1NNnވP:7$ۍ7>`9'99nߍoj鴛,MVtn9#Ǐ]CFw7GWDԷ$C=[alt1de `txT<^|)h,cON/AHǃc*MH94@!29ɤׇ~NDx0t!9i$nv˩dhHa;DADx4޻d-OX9'NyooOO̚5\.]׻tfB<補8qvZZZ|;>MMM<3<I7! k;EbggjN0Dxdp$3j"G2D鳛ô^M8d{+-vvt&ۤō׍dpђ(ٵOVYǮ`kڏ@xHII~RZ,`SAdY!AJ_J*cLNL0Kf6# HѳH0hd CVv6,367GTYdEO| GONz4[] ;7G&HM19DILe4FilvCȨIMMett|q~{{{^^__O KYYUUUnFHW248ՙlٵ3UY$NyVS}ևv6Y&0NXgN UމkwD4:a8K4Nձ˷ `$ÇS_cdqk}O.LՂLR8WKM%ŗ3cH@4x9@Ht|1^/)~?,#? B!ǃǗ$< W dY# [ۭz%+2YZM~Z!μLqovx%/86e eZtSA ~>r3t .3zsyxN+skch.CHKe S|>{Ϡz?YiDd`yF&Bss`I.tQQd젟ʢ,qf7fl8w?$48JJVVnVCRDsbb||))z vmn(32L. 9co?:{I Z;:xdY]˴ ֽ}tVͭmX.10npa)ێv(r~#!)TD72GPK8յAr| yLzI^,QʎCFQn?wR[KAvGuLٝǙ \烷@QN^Ÿz0)^z8~P8=0c2puF'} YU;uHDo6e$I04: ]̧̬8/T7idcXf"{bSIɌd&$Fl Sj$#OŬYP(DAAͥ,ϴ4,X@VVPH?R#`̙@KYTQv2Kc*D^A)Jʭ9LqjkllD[ddotx804ᰫoNN 歬%-+w&+Ɉ>qao"v0"L("cMB$$zpfVM͌ƣ;Ov3) 0LX'[Nf2qnkND&JZb(0| EQ͛gb>9 W׮]Nv].rrVv:gk)srk'}">IO6j7ꔛ%ïN;>$ }|IɌ|+r `LUVƔv`IB~T@ IEAHP(+%TfFJW^H2Y~k^M}lMrB>M_\H-7oqdEaNR5 \^:MnnjTBa\ 2ȸ7G(M91hO'Ɗz.sb.zL+UsA 2 omCQ.gωKHB049 i P]ͯşeռjŒ|r27Bqn:ϩ`;*f͂j8Ǭ}UQfEAjJ9{$4?~r2H6Ȗ%'Od-psk vmh,` !Cx2%J]ݢ"|IۺnmT3;E}DN*;\KVSmo# v;dpeSH4>`$3>͎;p ;99.>di%+&hn4J7dgL`yB,@R@&A!R OPW#]7XN_Po$LX饙<4Q_IU^*__|7D(4ɽsrʃ3yw IϽs xTyy IJEYn M"+a9 Wygz$ >q9i)&Y_O7!'.v( .extYQ_ytVow5M3cVQr<~O컽׼8fAIEᏖ4YZ4ni%v6HS;49s+vLat$XՠY LĬ~+ƼdemIvmKh[xv %rn2p3ֺN8S&›q;I7];ڝƋ[i ~p;p$kNidejKg=@DWy|~)ԴLK0=yd6x_,޿]ü6NM,?$xR=`/9~V5R~>X`UC1-)cVEs] `ϣ l71$=2ܚ\R)<$!1݃eNӊ;-xɬx9'5/xUVXikDzww o.IFN8'8' ߧm TrTG!qSInMVNiMVVnm_Sɳ˷nc5t&5N;QV׭O7'3qpʳ5YYMNrc"^f$azF흒%@W$XCF(50-hB:vZ[k}d#I?';txpND|ۥDOuIZX$!v+ZY@Ԑ>Nw K F =j̭vV4E(щQ#5xZ~@Ȳl 6/T_?nh900pK+$Hirrq}Bs[s ,'Gڽ;ցU1m6nlaM`b3+MF)y&d[gX;U< ǭc5 tzt88r1`nk?+6oq7>4]iҲ eiيT&u.  YV>m[6QYQϿw}Y-pîύ3s+Z Yi$U6XK=E,SưjaKb8M:uzƚgL[2aF,6s |+1оuY3Qd続4"d4ԱY5!q',V Sc>zGg41Kjk4, #Rƺ1舙 ՕxY4X@atΘSP#S8> ы#0gb/Lij0i /?@Ҥwq9DZ':A閮;f9^IbtQY'^Qx/4 IDAT7:^ҪQ>Ўahk؆x>[86ć?ƴQV1&͓@ P dP(IRQKLBQSj D_Ih^(OE EAH*.YV7ˊB8M $x6FZgCOryl]eMTOcͮ|*)@ˊÍ>%L)U(k],`d4$ vgT=64⸔fodpo[@*bfG`lPEoE]!k6TXg)L&Mo@A1ҍMi{15I/(j{;^/*]?Y C:cUcFF^u}1ar=|ujjJFo,k (V}746\)Zfɚ iufɌ(޴U k2 33QA|fBbC(Z6&'qMVmLblX :_EYFQ? ~@Vˢ(7F6% ,eO[qR(1KkhpDe96'1#bo+j|h51YegiX&gs+cs.r[mhnzFW" B .J ZG7@afmG(t!b٘y M@%Dl֙bc0mjK1̈6ƌQ Xk2áշٌ/"#椌^9أ-3f8P{/$ƧH?t N+F^deoE[M0=H KA[#qc8~u ͠i ܆Mݱ~1Y#oV h6t䯈-"{|f $>K6?xǂ3voHm&mȲj5a8 "Ғ@qeEFP(ɊZ- 0lm ms'd8y=D&8B>}+aúj2u֟A򚾈?m Gi [`ٕ%mͮd:$+xƍ>4d5U-vZƴ43a11ll!҂*cE;Cl]çf6Vu#3xXMlaO˷2³Њ"+Vt"VluئFz-|XJ6ŠӺ3 ^ۡcC['aa#?`Dq)Q8+c:P",|XƾQqPdžT)gx\n|S 4KSAX kk |Xf tM8U· d,7cs681w2H!Dt5>SwDAV$ŃǫB_ҝnTEl PWBV`DQUD7oE $uLQ ɸ/TA?JDQ1Ew Ϻd>F)8M*fϢ;q~_ .Lc8 ǐ snG|hY<9>te }AoM"h;}̘#4NBL6X ldhsѫhr&K@긔LuD 7U. YV˄6E+Nld ,Pί$z,P#"rz=c+uELQP#$)6 4Dd9}qRyޛ\%H@s"PSHF ]kxU_";Of0-;Wv}zn0sk{ʳ5( V]#B׎i'bݞ Q05MqQ=rL3˖.CC}]\lki|'NfF'&h9JqQy9nnۖqrMٳaܸyEV=k&G#7'KIq1^Y9p/[ gIIIeHDV~?d~Jeu\\s 0k&^s  XTX~~lUȾ\X.D# * aSOGo6 FVXX5oUZRBuU%=;u9ads9zg'38vYYYrL2232himeْżw(˖,`xd rsHNL RW;QYY8}}46fllgIK ή..\Tݩg&--m0`0ȅmtݼ^s&9hloY&`S WD'bdl[agk46bƅq|MʬCG P4"-^ky:/qͱ&(h0~+HE##gȠY]eG5.,+WJ=+*L'ǫ4JXne洂M.u1"#N&=7x'HW;sfhH`/<{CFZ '/ޠ8sj 9q?+fi_ }Cc\>N/cmL+Ρ07{-̝VY:s_9I|]fMnmw{L nU2 +ܕ՚ҩkEso[>sُ\ij3̜>1 w?|oܹfWO ~  (?^4͟g,v16>`a|l/_0<>OljYr-[Ÿ~ fJ eՊ*pO~qVZIuUܹ&jFY WU $^[a[]ֶu K_`9+] Glh\ۀP 0za*7ֳk/kS=cA720d~VV&ǿ'p{>(|>#kU+G>TW|Yה/kdgeqU|#==y`hĔ2ۣe֎: QNy(Xߙ0C70~5"HR0J+15q@!CGIRFe0ՂC!@8䮕K񨁐XF ~GA8"3DLג,+ѕ/u}("3 #(S׆8smpDap8/o7v_JWVZl+sXXy<u<奥v.044'9/}9m/ ^G`ddcOWN;N0ƢMԯN4}7uUŤ^1dDec |c֧Vk@Ľ",-,!`'<(:Ab"803U ɛz?!yƒ$e8^ǡ@E$^$JT%%eU˶8N(8Nq\(,ǖdQ*UY,V, DD/>l=@N<̷wf>]Ǎ񸔳Tnquzy=x? ۅv+].\ڝ~*xӟ[2\.ihOjVԱ%a:Pτ)4TJ7$IcBn\'#5UDg_A9Dyq9]\,(+\T7FuU^߳o[%34I*++Kyc*a~I%x.#qw-)ckt):礇SV.c4>vΖbR-,Nư*fD%8h:Hk[{_Gn0|3??pѐ [meoaElFgٳw[o܌6:/ O| |"X d*Ʌ-SV6|QcMi'U"/spp L&ǟ H^51aY>+`dmCY1m5e MכI$7b苺MpRFmNR!SjTe|b_ ' H&^\nyyDU-7r0mܲm+񦉏x{'wVw"c/-yy/糟{zttv)('LF8|QVX'2#cwv,sƫ^. pݵf YuD@j{;$9|WX`3+$s[i;X:15`[䒍I<90wFq+Twmhi=X$}̯_=mJJJ8r8]{ d^BS?xM]lj\e`pVXNbGy#/e>wd2EGg-Hm`n8# $Op7^jҥK<B#-7mvo~͘]oXHf [

W\"; s~G)}eT/lcd2Iùs̄|?p3g}wtr6o"IRȠ,Vel|uRQ^Ffmŋǟdhhذdh@B=:A-Ő"@loYW*`Lo ɐP ꍝ@L"y+c"-Z,dHF}&zUW)Cwk())H<;Jے$QZZHbVgZk2sc-1k}-EԬYk__W_ΪUXnm=}BVEz؁;e&ֳZ'`ߪ&'')**k6~N`j+P(D%nInYIRTT,ɉIWP\\bk`LF6MEcȲW ^~U}}Q5$/euc@Xu3 c eAY+nB C2OJX1Y"<UdU2KkmL$f#Ta$].0lyLgа_31jyo𩧞?8 >, kEf> D;uij&%.Zo+Z坕1\i;N/{ILy g˷ҝm^b߉&''rFl`Gg.zE+" ~ {tuux9QKalM]]=>T.LŸ|:$P{h jsg:.lIPLSKѓU߷ Hx*y7x(ߵw4 J1v30d&rd54|t=gk3Ta6xiR&^3DukL5ˇ Lf;YIFyTT?>MAXSTbsQVt2]k#Ԋb]+8^QjS$~wf][s.[CNz;hdV*˥Kl>@@Ү;NXGKv~֤"QUsh9)f_f%˘f̚Yd!VB@yvT\f(v!Ռ]&k2J[j*J;AZ 5 aLzYɘh^4h;lE0&`'W־װVvL=I$5i?%'d![e񷓭<=$C!Yg&bI2-V0vNe6F~fa}f݀+;`MfZ3vBY3]DS/3lF`sQپoyeQDfŃ qyf9nabbe`dg0dwt]\zY+#lmCMK{8d);- g|1՞k*!*e>dVű&=qemYYĜ,en+#V*&,kȦ\xaxiP*[Zo*Nb(.6@ I9?E8>.%76- e\NQ,;ZVYoE͙V:v8g3`qsl|Mr ª54d"Z'99Ի5IԽbۋsnū `Zeq$سͫFm{W6}xdSVsN޹Ϧvm.e~dmߍi J$I8whsUO3#JT߉Yd1KS0i-4ssXe89hͪ]H\\"INQsם웭]2hk UH٬ .l?k9BMfzz3g9s3=O9q̙;: 4+gϞ#Jqi.^l!LrDd@q{G'-<ɵyꙙ T3ǰ;OpҀ~ѣwt G8xV]X<v @JU^ ,ކl"!~RCL eY$ whz&&&BfCiwI]Y9qx<@wO==B]P০t:#Ghmmcppt,SSSztvv'SSӜ={V~Ȋͦy@ӧue L-4ԑ6-b໒eIGa2=b=C{``6Cf;2HSN l+ab+,7L=… Im?;=VmũSDdju2 OʤɴD2>M^7+ `zzp8$$ ͤib4Љ$fCiDP"kbNNBE]Z1O\y9MQS*]l 56Xeu.jNzXe v.@ `R%Z;HD2{?5WGGg'e155͉zrLLL>w|Aȑ'ٶu+#tttR^VFmaFFFWZ'# D4~5k/ gϝcŊG?fsrsyhiiP^^Kw8r(㔖Pw8]tvv288?uMLN8w"3>>N?w~-7ߌ RRR>,\ٳ>"Νk"좱ŋRrb(JycOp󶭴EEy.tZSSTTHm"0yן$,/_G._E}}=yFimm%1>68n.)/+ꓬZt:ر _o /˗P_T2Enn.NV|lT2'! Lqq1g4KiI Con|v{9{q`A/^3,XP1MIڟeHR|#__#255Dɉ $}{^I;:(/+# qD=H: (6s\WJ8_ ??H$¼R7MEEngAoo44%ijjSlŹp"}}{Z> gYPUIgg'==\liett~%sD;H__?+ghoogr2D<@2::. ?8ﰭ`pXoL?u\C*ԩI{[;TƦ   i8sK  !qyzpI.㡇k'<IMNrT*EaQ!O5088HiI fttbK+099HBph$TN;O1ɥKWZ¡CC̯q9|bdd^JKh>ccCn3h˥|ݸwcI"%B'wQB|CKDQbʘ;o333$ITVVr7Ģye.$Ҳx$JpSQ7Vp2 Uobf(vv Yii&vk alf6[K.g+k%E%I"E(J%I/{FUhx;AN$b~<I"҆GF=?^yU ;{ǃ1AS|{y{W^>}]L%yn1=3w܆ߙ619IҥBS466ko ^d x뭷40;o|[T/];Q__,<w}'%ŴwtpEXΓ۟xg|__/011Aqq΃x}^Ni_!44)..ŋLNL .^!/_FNN΃pD"˯ٳz=|dŊFYy/*'?555׿o_yutWZIAaCC^.OF6mΎNK.b>vx=47_`jj¢B6l& m< zRvÚիI&S޽+U^q90::}ZP8mmz-\%)rTWW$QT\D070~b .ʂ G?,[V_iٻU$In7E4=G%*#21.066Fkk3]=n</[m`#G2zͥr.]qdzzXd .H&##$I>vimkc]ܴ&-TwQhrM~~/_H.+VMM\w|S@IR )** H$utK0'-w:f׮̟?PHYq1M梨YNSUUeK.SDQQ\ny?}QPP@OO~H$D2Bee%HR&r3|uװA773oɿUUN +7xp*z_y\kHx&L*B!LrWv***eM=̹F6^}bI"L!-CKׯv׏fW8łl"9ɔ̩7z/_39ېJˣT*DQ4nm۶Q^4u)Ό84gž9vL9-$Q6ٶDzjg; 4 IێVUOd7%YѝmbWڧM2sc6bV~ 2[2;]r~ӟQ^QN~~>30b1"ѨiU5 /k˗t),Tr `hhfU \uټzz{ٰ aŊ{ xWS۟fMAqS^^F(4ŦM7 cyyLBDbQ]  //ܼ\4f4391۲F|)$IbM~BS! @ZX,ƢE FճmV6ms|_$ Ŷ$n[ۧtwpw233#~ON JV^E2/gYM s/p pT-ܬ蜈JD"H\s՜:}V?%%ż[c^re9|o{n/}LNNXjMxU9\.n޶G~SFYfuFe5HG喛;| }75ռ^$AoΚիYPUEEEFoed/^Lgf>IxeK.S.JpwSWWwxaB)]+k.W]u%ŌRRRbZhۧZފ+m۶21>Nٳlz˖-7yͨmO|32Dߗ!wggwy33:x ]pjݬ\iK(@axxyJխr ~mllx[Zk5-mm,H.[VZ[[m픗㡸P(DaaȬX^t:E(4? ~/Be8pq4|fT/QYa 癋nx eq~MRrvY9/TM*"LK$!E "ID`Nh o_ϱv"rZGHT$x=] T: wJ {HHDr9$I|{?'?OrT JHSn|>sH@2"~D2$}g I!$-mWx&LN0H$ArIqň!LA^ze>7fÆ+PnIeqz3ly=6ʆ7K tӣII$S2nrf+st{J5 111dƍ)[;16Tl$ ̲ed77erw\F oMIz|>ߒ$XׯC 599IYEřOMq+x@2,3 :}YWkD) ueW $I4>WYjE" YVk")Wt2C]h$I5Txp⯗ g|1ױF^MMvv i}\F~?@*$"IO$Ţܚi͆"lOP77}(F#6מ=$gСF1ʕ̌Zbxp8\F $KjCJ_K;<m]ilACSE2444022>,,;ehhhdGa׮]s zSNDy:::@e:<>K,ѣ,˴٩Ӵyh}y._ǥq'>Ԯ3<)ﳭ3>o,.LnS#0;[eV=5[5w?V4oo7 sgԱͬ4ÚkbS֞N6;[Fp@}V,+lHZT\,cmP-Y;C<xLCJX,FGG'Ϝ{۝_0"L~#6߸I,I5(np8L,3>>N2䡇fl|E1:2޽YpJzLOOIjSSSD1|>Hi.HD\x&Qw{{E.OngYM ǎQojm+$÷uߔ*Ў'6LEƐ]w8u2BLR}|9X?88HKK N͛7o0>>޽{ ܹ/_G{糧Bo89rӧOuq1;7 ǏgzzW^yO}S؈aϞ=?~\vӧ Ìo>ID}}= ,PJ覐xx+Wc`PCN1$ ]$KewÍ==!*K9pS-TRZC>m- 9utЏo$Due D4v16嚕 4NOw`^ș~0W.$psCL;fzCIϊ[4kT+ Iyi0[tj057א2ȱg݁률 !.\ȑ#Ghkkzػo?٣ g{cʕ~O248'X`W]uO>xVR+AgG'EŅUdYǞ`l|tZftt@=̟?^^yU\.zac՟cʕra=ν~o|>Op)v_ D"cOL&ԧ>~#VZ 455˹wu /RS< r+q0x2?3đ| PRT6Ӥ5M2$dCVz$iU`bbqַ$I|6~y\}sQKxvsOڊo7oMMn>Or "Pڼy/%7޸5 ѣ3V[oԩne55;wcGs 0-?܎I|ş~E:::Wƭ߼vfkl䓟ŋ<r}r)K̟_8?ԩӜ>u۶3ϼy]{czznصs7tt(эoƳ>Gaaeڵ63<~t,d\hc[޲@-W5rK_2jɒ+z2^Z$[ofɒ%D"\N<ɦMH&,\ clŽ;|f۶mo}RFFF"JQYYɍ#TtNKK wummm R]]ӧYx1DvZv[lAe)))!??D{Nnfڈ'S}rv׵58˔Ctiq~{ \VQHQ~@wil}g:fZ[eѢElps`ڵ,l155Ք1<<<)ܵOSS˗/c͚,_U+Wrnt=d6x=PTTʕ+tw+B]]=7޸J]߾~|+JKK+JKYv5kS\\Dss3KVseUQk@/SClQOeed1+h ZFjŐIZ6dRʈ4 ZZ>?]ZT%[%€d VDspmQOwW79Jqiٻ;nk|W">O}{o7k|MjkqFx"- {{x饗=TK4%<&ejhmmzFFF|b֮]Kman7wu'o6`%][f˖-D1N9CQQ^sn#Gfj񵯲brFG\<8S riʕP6O-n[}MLLL(w`;/~JaiSD<`(_"z?< 4,eSK^/,YuK/qWqF>QSSC__!I˗/W_}GGG,rXhp?X`x<FFFtCyfn77oAzzz"//~\***8<˗/vNٿ**Y&J58 ~Ir|<.=C!rs|QoJLQ'-˔50mIEݴͲ%rR-,a2޳ ={YYsCjŞQddzCuP$;~P ݣ4)* ?N~Џ B317383Q>eE}6dN?Ann(//}Xk|H. zj-ZHEy%lr#.\ԩSd$IK_+V6m… [Y;9pk֬}}L#w. ϭ{oqz2}% HAQ"%Rْ%K+d=yώgs<=Xr8cYVd2)2>DJ}Hؗ fGOTtπ}W<`wWߺ[ݪu#! }vDN $q]wqqnʆ hmm% Р}ꢥD"멩[oPƊ~\En5Tpnp5mDINrCGr4gi#IZWFI$V4WIpqx%5>.+u5cF04*dف,|t^qbI< j431E.xֺrFg kTKk(-rqe"@s}cl\͕e$SiZˉ%ҙ_~SꩬC0c6aes$)*J\R^͵~N_"QS^ِb& :&ҳ5:rx-907Gu]6 zg2l HH2tӧOSVV,TT3P6zyM>.7߼h4g\ŋ}.ehhD"AEyG8fll6;D"odf_Te py,!N#IO2=UU>sGuu5R4JԩS8N***7p ֬ iZ)//' q9Xں$]kcUikmv16:D}}b@-&$gkE:70s[e ȩS;+ٍ2(Kt"ISt9歷=믿&''nu;5kS\NR/ e|@RS={NJ˱LOMsn7k`(Doo/sKpej!LP__O,cŊdک䩓twwsi+Wf/S]UM<'й^k\ǡCٲf())!FF"v4w2@ u''`U\轐]? nZ%9}F`S=DmLLLRZZJyypEsssp9jjYd O&Ovm|pol0+;3%iOy9OhIfޡF#.:$ \`Y jbU q3{oB!t0'Od͚5l=W]U*:YY69~jʋY\x4VXQXqx}+,^ܦA}"+Њ(L`f)gEnYwNhY)5v-|i.98F9ti#sPh2_>j?bW|v9ETTJt|C0SS\Rk#Xc91ZY:YV%qzz{I&ZՉ"*Uq,(1S$.o1zr\K*d{-$) E.=/ELSZV4R,bd&՛^c~S,_K|.ke 6n4&Bn`d X>CdILi2:m(k B .Y;Gۣ6SRW Wgp:Q $u4tc[šʁ ȯVjn+.?UܨCĝE IDATl!`GC= dYwB*^Y`O␧ϓ\_wKux^^ڷ QW4˯N͐6%䣽גAWƔmʝknXOƹTgF^)Ε++A' "2x?C<LmP!1_l 7k|Cl-1bJ|{9/$S>Bb/ɇ &_Ri8b~&!}A(;OiK1)k |03X_1_+grmS,|d|=F\d WmUqz9 񤃕e<5OulQWz:jU^|9xT80 mdaƸK4rם c93F2~fFR{NՑhҚi&Etffe2slHAjN]eu0 r_:A>yY^:v0gjj#=̎^H?ށn =;Ex剃UPGψW}.䴙ZTHBtfXx-n嘙Xo˞j@&+/NTg|'˜{ҺDǗȧPf]вѐ SwF24r.T3j$&)&$ P-,Se!i,'3e)ˡ! |$vn/E9HOGQVv.NW-8_ⳜټҸk]j,#,Ɂ&rIBʕC<`bghNuű^EdGLwR1mܱYJYIFE A+{You1#0{jDS\dz.+ }Gs1Иf2VeEYE cf1>{3=+¥&iEryDLfe|傤/ r 8OG >wφu8trhX5D/YRuAt<y7:9l^T:Y드)bO ¢K* ߲P$W06X/T3>a@\jYGY(aK-i04FG5+'IFtm![ެmhe:1l Dl*,!%Յj\,uR*$,᭠o.Kys%r+jY2wwr%dɳ"E': MVvx5F%!_4P,O$s̢&Fݱr@ E a3+obiz#|ΎN:7 cފ}U3q6]LGV63BidfuƤ@'g^ VxZL+oB#-KX#ft3VDmWB(LWG2%oACg< JǟhRW zl7ѕVv M2 gȚC!خsxV0\UXbzEƫN>=t$9p=6&2.lN2֑r1F933"MY s>dF挃WY˘c "͛wNnGed)o?~\;wUM`29rD]j]1ߧWWNx">qe$,szSeYѣ*ѣGlAb!ޟ:uSNDzYEҌy749wG%ꫯFMqy|eHD$gaZcƧU1 nE;3K9|e|߈e"zXm>̮f œ)lѐơ%t0 p/)@<$ި赉8W4Uf\Y} Fr?ب*MPE IƇ{EIdTPfSe5*ˏ^/:EzU:Z\]iruEP$p$I9JN1JOG/ICˡ0ؤzUY7ڇP zAʂh[Q".Yɵ6&ȡ4ԱUYML1\|m˨+D|Cg:+$*fcpb$I; Hsc\<+7f2\Ow:h%166F{{;?K.t:z?DeFpAΞ=c/~ ˩$__ݻm۶D;y$ccc̐H$xgilly~ *gk? ~V\ Lqq1Ǐ&vE__>(O0??d /(*nvM}}=@C133~3\¹sxg䩧S]OӟQxPH9رczzz(//穧j?C8իW}ߤ |>GpaΞ=KCCO=c8~8Gn'|R;v\|Kk.Z[[ٳgH>vMCCf###TVV/yf֯_餯^}U6lؠ7q YzgEEhXD&& \,~L Kugr03S͝@)>&r)sfI$ԚS%zT_"ZdkY~0LaH bh6tz]ϐÌ@,79Qi}F6sZԡ+gߨ!:Y屫uEF:9,Ӕ_QnQIYCcrZJZuijv|#1Yhjew?Ԃ9RTTfGj(W^E$o&ǎӟ4㴴p dYG?y<9s={zjl{w~|+ZԪ^Yr%?0uuu~^6mDss3_'sϱj*}2+VG?~'Npcٲe O>$N~x<?̮]D"r-<3x㍼⋤iYjqܹ6^}Ul6<;w$rQ9tO>$ر ~_177__JwnF֮]ˏcسg,_H4Go'SZZ/Ky~i6m]wݕ|۬_*ozfbbo<,,,sNjkkٴi,xhjj⡇bʕ$IZZZ H]|㽈Ow3G,k"rZytbN2K$D!LH&H6nH6#M;2gYf^3B Xk4d2?E+NFF#)׃Meg"vE~M%]>,3Ґh#N9-CVA6l4VPYd{q$p941K˖-Z[[|WSEEE>|e˖!2ΝcӦM !IvP(??ellGʱ4d[fԆv /@kk+Nx<ƍy衇x>PVVϒJg׮]|thtt4{ɲ*̡**i;[ݬB{e'w%R)3T*##B in7,SRRl=Sx51+&ފoƷpr6Rp:~] ՔH$D"x^N, dLx<4-羽(X ǝՊ=>L)gQ$-_P%jvF3.D[ldx"._v>9eT\}:&%r:(-)vt:I$qBr]$(YCB;E'4`# MZDNTВe^{hkkK \tnvM:t:ͥKx衇$>|M> Ȳ̟ٟicU*y/*|3[Q9"T*rSbF9`fQ$$J$I$ HFcRʚ&J%q8ȏfStYdA3YmUfV4GB3lN'ͭm3305}pGM|d r9 |> Nkײ|yx^ vR@Y*˲p`H$Hs=?,qJJJXXX@eΟvs 4<ē|$ n7Dd2I2({xillH$JYݑ4dbIe~ 9r&/_#ieukd}:˥ȜLO$KʌZ-oC-(|NqƔ@VUH)zix~/wOO<kN@)[6Iv,S^Y|ߵ.MYLKPرc8q۷ښSW^ajjO~/Z꺐S?uQV\IiiiAZ)$ǵ-T>&YL]TX ūF' k΅7D"IUe~lQ^^f# ǩ'3;;J+++g:c2&+G >H$Boo/DjQ2ZkA08O(%gr]*={`ƍz2h I?i?aM7122Bmmb1fff -$9TWsbݛŋ|/ .+8s,h"^.\$ 100}Ht\ɉ'LNNp:D=˝D1073K.ᠾlj;>^Z6!g"TO8 RUUEGGԔ˙$llvd9$p|%3:1AiY2x٧JNDgIazf{q˭PߠiCg:tΕ=>%D"Q7255E[[(MMYKioڵk9p \|V"/.sQGSsgdzTrJKK9?x4F2_F8JyRV^q3TUW1<GK!bf3jކ tf(s!9tgxof?3*Ȱ@3 t3\-WeW^ell!FFFbrr!ӌ2::$SSSLOO|vL&ZC}R gW|JOŨ(_VFmm-555(WZ%g~Pan.q~򓟒H$4}l66DH8BIq ׯ%~2{rbrűl|+W;)//Ge\ɽ| ȦM_u]466r ui+.7r7SSSòva匏SRZʡCڌZ6lx˖-cY{;6uc3g4xʚ)6l-BˬX6qƩڰަ6m396.8G4r'9{Fhx06lgJ)|;p\ڧh4Fc(FvL%Gd)/O"IZ%K`|3וu?!idd>8x9> _ٰas.\Hss5aٻw,~ ikk>I\;nkcŊX-[9CQq.7Lf\Mg7Ltb]O܇ .lBCC#nV.]]wމMR~;H$LNLti+.ˢ>' e#F'&_d@CԺ5s̮fHHd+ Va!9D=9nf:6q2Ӆ3>D^4rΈLV3=0Vrr%J8R &_c2gDx 1$IY 055S討n:KOիWSUU7qHcqi+]!I׻t7oPTTĒ%KHҥˬn5sss477Nz466200㥲**++A (kv199IUU5H'NeVo'iooҥˤR)pw}o$eEuu5~CNuZ]dԨee9kKEROr6Ηm IDATœSW6#I-(ڴ,b1'&kMD"I0&HHy}=-$EҩfQ[]eȧjd1+ $7O ~/+<<n7[4ofž%hl\B* ~gٲf.!Ilx#SSڧu' }6}ʤ"Pրx<) _g9˨e׮zD !nm;?ill̬3ukaaat:E:"OPUU4?<Nta|_|uO&(S-c2(sƮssjj)/֬05 a,/>k8ICǞL$9w W"Bt:MZmB$LM6*Inmֹˎhz9DƁB)W}1cHf00-2d/#XScM--,T~#uɌg3xC~OO/G;BSo߮c!a_h{GMm-K0K̯gZ"I]OXlYNkny}!N191n9g&:::AEE Dd?ǚ_i sE[i!{2al;h`oܺV=~33grcu떂4,븐VW| "ue$}.]'SI~n.feP#a$ٲ9I\e!!MW%qf$)s ;\^Sʣ۷us0i6)NgRrVXʂpYUŪFnmں\~$&hȪs,`sq._\+A%݇J {~ɲ9;+ Y$$YӘrMɄBA8hî9Jl8q9]Ɇz-i۴ςbM&S̆yvqsaw(v;7.nlؤ.Aъ*c9EPqUġک[IwNE P-P[WG]]]ΜF/63/ˬ^իW O]  X R-˗5dtEgdu/T,ص bՠX|V:xfYD,%^7/vLJ[c/Ϫ9;I|ZVx EG\H>|"Ԣ#B1%,Dъ)3I~FO$)?-.--p]O$!KJdu)H3344LCC=cHRIG$%]lv)# iQBϧ8Yjt7w(H٨_HԤ)9NZ/3 dԌ̠),NpNǤ]xV F$ϱl02ۂ9IK&͒N&)RYYї$bHfiKK)*.VlO2~RTNއ6=l݌aiCWD#u붴g ZH8L2$T:[ۭn%WwWvڿz9@9;V\%WγMUΘL$EReQ8Wr:kaUaնRIA@sUMJuU[9KIA2Μ~:+@bLV@3#oF^ +9m%X U ǯ8gƳ_#FX+GҌ|X'ce/YRTg`Y3QHf^z>FDTFrg]J,M;)++0It\A]m-._iI#xzo@<\Oee4/iIa6;+W,' 27\<7˗-cjzZەt 8E"_Vme4#9,g+)Nzi,*V,+WahdY{gɚg4Mhd518$Ќư:fR UURTTَ,+?D,J:D;)/+X<A**rBԪ\$119I[InKY R,\R,q;9lAy`zt*M?: HUN DBNQZRBǣm(2Tp8.o%%#$!TlpY|š mx esYoR,؈͛aƗfr{sYhby2V,˹VUT |+cLtq-^ _>꽕3SHk-;)$LJ_+g GNL{S"Xj36,E,FEQ Pr$Nbҥ 144uh'Nbm ظa=C\z.QV͛A$Iv;u.en>@KS=.- s%3OP_G D$)33Y҅=>|J'y*KduʕY2#* .]L{{YW)L000-l2&.+xGµ1KcS./:"F[22ss8%XڴDki#$^Dx\N6;n'|l \ZIk:&37=MUyuMK𪎘L{<# (1Qzb&Y e,\|?"6]LęӴg7S<"Jl:%>TVOi;)1 sp,Ve EEll 6e+c-oşmgKWGf|LBt+1r_FÞEN]OdTP<O]tZ#-_~QST]8Νuz.\$ r%KD2EUU%zd(.*266NsSit fcisZX`nn3ssdU9;4dVhrݪJKp8:݉DBF>Μ>cvvd" ǎgzz=!bwy44sqFGJyYNMnq%‘0%;wgQ]U͕!.\S)..fp`K/S[[ÙS338~ںZN'XpIҼ2!=|R1|>(//C]#!HFt;5 `%T,Je>飳ʆ$,ih@e tSNMPHa'9CSC8JtGz<\cx3>oJʱЕ!t!KRf#A"XU:ed?xOIrdl>N]]eY8#000>OW?9o6lМz3^z-uuu}kii{ߣ^g3fyظqcNy3F<wwnFg!_{ƳUYQ6#a NtpV'b{aƊ3f"_FI#.,0sDMGG%%%N@XXXΜ;fGmwso"R*;KJ- tA2p`2J3AN)#KȨeɆVT}2A#T5i$ RҞ#(o|Mݜ9sd"IMM gϞnJKKihhWq:Ν?w}hŞ=4G;~q7S|S{Kϳ~z0GIbٲvv%9yM\Ϛ5kx=\pYv~r>bl|N[k,Fs%ګ$(N Ȑ],ث6/F,KG3_^Kg~FUU56$UJ  ):JʧЂr-WF.IUU5>(..FeV$ll6eC`(ĕʪ))!iUf9xLQe츊|Iak"iKkx=[ZxL{܆<>ӧ).. \ǹsx=,_wɛ )..UV^Ş={ʽ+WJx!/M8͛ٳw˖3;3ˮ]POWZ?@ 0agzj~(׾c!mVlUtRNQ]W hZNG^ek_"Pm۶裏rUoӧYXXwwimm_we /~G}/'';Ay|LNN|rؽ{7gΜ!LcgxǦMx71.\@ee%n˿vcz{{{B!v?ҥK?Okk+O<6mҗѣG9pccclٲٴivtvvr)of6}|2wuv(4w 9m/nYkI&"2:B*jj3 EtHRlcO33sgO|!'~Lr!&̞yA™ x@`P(D0̮癝c.0O`>B(D  ^ 'g>$ iDĔmb3(XLyƈ2XFxD"m MMMl٢lD;" 9~8:K]] T9@ٓqݬ_m۶p8?~V.00079y$W\! vZv؁s1B,3??Ouu5۶m͗%>D"~Ix x5=n^/>?#7nd͚5|+_ĉB!4^MKK /,377Gmmtmٲm۶i`ժUnmƶm۴ڵkÛwލ$I믓|qRVzه;wdtvv}wy{Ne˖-<\rկ288ћ͚{ZR()$d-ku"cZVeK5_'d] xE-"$a^R6aěg[r#S{E8^|իxؾm33ot:X^.mauqU޲Cswr MwfcmۙD`MmFklDa82غu+,p8ȲLWl6CìZՉuםk^>}nDMM5/^{FilliPEXbВr1Х<&deYj^7ɹs- xO˷c0NF"4K<9OiJ4(v;DYN "nBF*B_s8NIR4d ǃvnw&7tZ^Uho6%ib8?±s}'vNH$qݔ"tBq%s~}km+yWhook tgϜe 먩waǎ۴n F"nmn%KwFZ[PTe-=r³[ˑ0juDYZL0l+sr{.%!dtD5"Xf=V6%I榧hmniT˴|>߳ckj{B L)?ƺX|i4??Ͼ}SUY[n>~488fcIS%E^D,$Y3T28'BP;@gW cxW93SHINSTl\bn!LCBdLdP0`e[ %3͆9S0q2ddx*jjjtK  $?8?|Hqe6x(..Dv;{/PШ (R%(.)pד'OvڜwG;B@?۷o7OѰ$J3\؅_y3=Y]sx$$o}[<=SpiӦ:B|-Ƈ!Uksl̜BTF###=zT ~A[ i|~5uueZ1ax0I&H&uC`M5t|^ %%OWƼ$)Y, $_Yq&ˣ$EJ둅usKbwM_f3SD39̗Ugyx"/vX<9XOP]1 IDAT+P(Ex(B_ѲffȱTQ8c45urHHnU$ (Ыi~~Hο0 `)Mønp:jum=_ZՇodsS:d;FFd ݉S."25UZFba)9$]n),r*,+k\$ f#:;xpWI9o b&G:fjzXB&P5B@v6WYTK=v_h$fB6ZY8EwQrTU=ьfFFH9#c佼$HKssؘX~!vL FB+hFٷ?zGcΙS{wת$Gר7RC+'aIǷ^^@ODm:&3~XiM.1^Nn!MgԹkwWNKDcqf+C']p=**rnwq 公hy! sd^cvSSތJP3.C dY־$ԟ4b&ƒ1v# Rș?wਔ_//Ye+4Is4Ic>KC G^Гhb;ώ_ߴ5?HrHcKfY],UknǬ7ìʔkՁ[.Ƨ[l͌_`H.x l|ĊQ;F vr}SUUo37{m$`;՘ǬRف&ɜ0}SY&wt4ijS݀+PCATWI7㟽TdO,M<@ռ80YL /ȱUUQK j2iBU<5]QUM'x1!2_UV!Ҫ uYHZWC,39%6v͘'N`^m-m-9rVx3\s]TqxEaVS3c*a=>\N~ 8-#SikoX(S ^|&'Q8"$`tdq@IUUDd2K.faѢEZp_ZmN66yy Jn(nj|7 ( Xf}UhǮ Vޕb-+]YF/ܘvv1(1h]d2@͎;u!IfٲeB-AeNۃَ cT[.@8@8tJYӜgyӤQQ 9cSjeb|8U5$((K*:_ rt!;4,,Ep}QʌT/d>NN֠8+"X L`@J J~J^{98˼ OF"ko|:2 D+Ȟ'(kdG L|qАȍEIJCf*~^%x"70͟9dž x=̚ՈەbMQU!ȱf774)à6CxȗDgiI:&gwPd2}% ٿ:N'#ccDIugaR^6f*]H $˙*PMb2_%=AʜmGpJw)|+cK݊_(O4>j]}sQ Oe|Prҭd0tUjKRqy]jPjڣs)&G֏b4b[v~0.eV:nV[r Y ra`[1V *b8Y(VFz*]US GNc>*Y`Q,1Z/ ӹ}LHr/&w2+#};)ƏQ&+зF󥎜gRƨ O~3 fq|Fg fѯb&,e L +TQ׏%KSOH$xGǕW^gk.Nj/Ⱦ}زe [neݼ ;l޼۷kC۝,߿KrJfϞͱcOʉ'xg8q№#ww!җD*Ga_u vɱc4 '?!L#sN}Q0_׉D"#Ν˜9s$ Q[[Tԧp8l޼ٖ $߯xWzI$2xD"A>Oinn;,;ctt￟o|>VX$DUUinn`lڴ'xX, LMM0PSS$MMMbN>ˡCx7y 9_K$**|y#TJ:~&#Va];K3V[oJc%+3Ǭ|1yYk&|VKi{;U Ucgψ˪S0LYl&Ue,치~P,`1>[^F4+vaaJb/Fd|Fcjjk$IbϞ=={x xb8@?V"H 7ÇiooSSSCkk+r-8qzjbp뭷jBHjmmt[oqFz8W\q~ŋvꪫ$kt:ի]kpE$)s@,,[ I.`jj믿UV~Ο?3\ף*}}},_ Ο?7UW]tFBqFkxظq#gFillt\S$ISOp8xffnv 6Tכ9Ĭٙsrppie(CP2CC[?Hl޼Jnw#QYYYp88aҥge9X@bl*!OrO/{gnTeh*B/nAӱ.jq1S0&/=omoŠT=YUşڶf̥F<}鏌כY]}>/pHfD"971=s(.Hfn>:?N}  e( /{BUH(J!vkmqv-2xpI߃i6,.ևn[3)ʛPzHfD7$d]&K P /!e`b@WFzlu 2iٙ r0#px<^_Ѽe(C$15>Fc̙3'ov`!59xs~^***F;ǩ244NM]5p Wmq[A8CI3l:$MMM:L%$II:Opϟ1u5vp8dzzz"چ$02@~[•E,I&'qz+RE^dܹq!s*+*4T*DSꫜ9sIXx1v[4eVq#,^D;h3z,+ҍτޘQ<\ _Ƽf^ rlo% ;hBόp)6/E+vJ3mg0& _1䰳Ȧ%TUh_2)Jd2I"'# # szҖ2eU.vysȯwCxq^ZZ];x .Dtqagq/|Of++={}QUUŭF Gx8qV֯޽o͢EX&k466rJSNEQEIH*]qR1BS$z8a}M`0H8]}ǃ楩ʞ={X~vɦM4|g;F||`ڢ} MSS1TU 'S|{套^d޽GhG4)JfVų!% %QH ^4v?A0=4CWs/>BBEwf¿"{5^51=ԩȅUw[RM (r4BZ|+W׌naܯ>v5S1Vxse2|`/˳F\|/_ZɛLt)c++i3O7aKt3 )rK3_Փt:ONCq6[u5T4/"NiGx<T5ß$9 OF]xٱݯovaC&GmmGEܹlW$fl2~ 7o֭}unb`gϐL&X[ wIMM NR4wyU,t蛐xahZ@ ccl"---۷xqA2Q &&&HH InMMQUszz.p8hmmriR]]ECC]]ظq#===ܹBs׫#:O__Dyd*$}!S_\EUWi غZ}rJFFF̏~c>y222J__ٻw//vZvŚ5k:v ,/X^`CcK.EK[,,=UKۘ~/v)r3[N$?ve r$"D#wT-(';߯bUimo`t{sȑ=p7|3+V\?ϬYqǝ@fhÆLN9p"\M(b٬_3nMKooo|>/d IDATul޼EQP4owy''O$  ruM/yfX,?q ljb:\Զ- :ĦM?>O?4d[}-[P]]ͦM8y$]Bq>|~ 7|3>.===$)V^͢E 3q}'NȑHdٳJ9̪Uկ2<Y5}eetѱ@cǎqc&M*8GJn5λ ^䵱0X|kx޷?o6͞z{D"Ӂ,KӳB.NǃTWW,/֭>rH4exx|,tvv/say'/N|^H$B,#FOTj rg/_oMWW馛XdF[oe޼yy>!]SSv2z(4S۸{9~8'Of[˿ ==Yn-wqpϳvsݜ>}Vn$ǎ<#TUU4>$*dŊO|p+8xiI^Y۷?EJj7`}nVbLdJ пc=l+y3w.rd q,W+[/-{YOS %/N9>Fw +9:}>H0RUs|W/ۿqߥn`'Hrd¡}}tt,fd87[Gkjq8}ݼvo d2 B}}=@{PUUEe(C!3̞w5SHHCDkh[}'&k$Z[uz]|vmV.2 (W_[8t it:9s,shoog͚y-\.^p!f$; [o3gg8{ TWWoǪU+x<kժUM͕y }veͯ*˖-#L/2k,6l?N<׶̟?z ŋd׮]r-\vTUEQ*++DAfjmVc1wyΝ#|*ޛ_`UE9hO[Y2\W cW p8Bz'Ͼ(CPBt8׬X,8n$IBe$EQp\I\?gIIEoW/lC!T-P9 X UQH+ib ?-Ǎ_bZ1&&&hmmv3::$>f|t:ڞ!3{̙bbb[ǩ䪫bۋ#ǣ]B$mmm$Iz{{YhHAꨮf``!---\.Nt:EU8O=-0knndz>Υق~s{d4wT[==/rK_BT!I7\UUA'rAgpf(Aϖ]8c޻f=,tc͚ӅVe>loVW_N*KVzʲ/~kET:åGw݋Ww<\ j Z0돌~ݒ( R)-eٳgO^3J7m|A$+A: *,adC)@,Ŝ:uvH$̞ݬE8}N (pde Ӭ.z6,< J {$kyk2tK16RxRokx4K~icYPȪ1XPV^J[*b{g3a'VΞuiϓ]+^Jq9~Zj{P{;A~mgǽ~X3^O8V IZZ!frd>f__NQH,ˤRI<6%TT2pFsJ: e(CP2 fLޯ{." _s! ڴn93661Fxu =$*S8q<C! iw|0p[ɠ,lL [&0Hʼn']GY-O]$`!V-3A:.K>V աSd'D<3x2q>x}R>Ƒ^ՓnL`M>Ɯ (m6/sLxi^PW_eU˱rJM:d.YФGc۫\xYy4i蘃MZ{>1OAu:~c f&|&}|WWzWYN8 oQӍ/e>]:2y&T@SeWKGt9H[:dX]}t(S---+A=yuJ$ٳ&rrp8lOϬWeajE܎mQ۠+=8ᱸ2J)Hɔ&*+6 *O-[tRz]gsv3~cqɞseq9^瞴;^VJI"jZ(.*{Rطo5t&0h rssv2J S__OMu5%%% 0-~8 IIS[m{ط/ʈa *%'' URT]m-*h4JMu5TVVRXXZP`d*?x,zç.#OLgP߶ /fH(t,NxwvLv,8 ,/Yᕶ֏4uFp֝iJk!uGJ-_#8h|xB&}mJyrZ[O1=eKNV'^4]nPޯ3WWnޤ`=DP}ky}ummmߟ! PQQiu.ۿ߷@(d_}=uPQQI(,V.RBMM TOnn ԐCEEH$Nk|KJK' )Aw.!bN41}+'Mb@WbJ$֒_OII v/cbRTXD~~~ʑ;VLԻ6N88ݙB:3g:xO;me9~K:'P[Nk13jh."y]/^D2?I@[z@k'+he+0o<֮]˝ws=K]>nV~!rr"|*,'MK/aÆ|ՕW}?O[[+?᝔8lݫwJ|tBVNO٬;evy9 "3 >@ڔNR#ˎJ Tz}R4GKx'GgpHm<]զ~HJ׆)^<،c {-;r&xƧO2\.t8/<km'Sϳ9hҶ&S]^ڻM[[o 8ʧYt|o~gPO YRH^{#v;o`ر"|[͖-[ͨ?O)**r=UlJ[%;Vލ'-|.-=[)g4 *?SS\aHB_N*нP?T $V f-&YPl.*_js鍷v9#zIձP|1~1-N:K~8*^2Ybm ԮV,ո*!j"7HSdz{mA-I-2*TխidCT-]jDT)KWFe`W4ٜx5U9<'cS2k G:*M} )}IKE:r|דnXڔkp/]5y?5^5iv~Ϋ[xe*-^d-ʂnV9\5`5h{RB Ѷ6t1 FQBP9?q$L$477AiY`bZ[hoo'//\ʵ.$8oQUUŌ3(**bͼ[\xх1YA^w,h1Gy;C]gwPNQ̜]S ]7KȤLIXc@J!$ kСطo֭CAcCk|LMM ˖-H$Rmv/[F3΄ 8ʓ@UVm6*++8D",[;wPVVƴiSYYMx뭷TXXH<'Lp3nx||:p'0bH\Glnnх?̳8p o޽*/^5\C,_Fʼn'H `+PSS@qq1\|1;ooA0dL6>&;U|?ۡ|R8#aԽ ,La΍OV,R]*˲9ܲ O3pC!թ=<{[\xSe%;˷uScw0v=pxN-er]";Jxٲ+&nEǠt: fZ:x34<jD8՟IDzL6yK}8MN+OqG.}J;w`[6ܦxe AdJ%j{pvݱ;ξW3L$ACCu 6P(DGG۷ocawԛTD'HDˆo(=|5r4899ttt8fxQJ|gg'PLa^~i  ̚8MMSׯmv-'6UMvԯAȋ52 ϐKM5툎0%? 2)%@:XZFCIij i  Lpx#455qԩ446z/k׮ /"77;sÇ?ߘ4iRJ_xW_{ .Vv'|+s`VZΧ=]v9u\kYZ}]~֬Y~;O?N~w=~#R7,롾Pj{iӎ+$Я_?ƍKuu5w/8w>'3l}=}N< ӟS4{zz;CK?Ҟrd^}7xt7Ώ-V;prލ'Pkum &ꞌ}ۅGRnu&}ZkΜlυ,#{ tlwW?Qc1`]31jTjkIWҨ7<u394).pYzP;dJ7צcel^:gLs>ʞzM44_p@"$`>ZlK.‹.fѓywyW8;T!)! v^[ D",7r;EEE;O*K|RJ[s9>=>[)-M\w = :'Mrg$4I>+hl)dڕDj8)N^E2o$RF쫫cŲI&7xj0.ʀYt)h'+?+?Xo3 /{ƏO0'` 1. @YYGLhs"Z0&L@0dap‰;,^n!CL&yg8S8z\{a:::0atvvҥKۙ??74h'O 0^]z5%%%u#+_ ~\~c:u4(#G9sسgt-w= H{/:1opNoKsСH$<̳O~*>}:۷ogΜ~Ό3ظqO=$W_uwq7-/Y̿˿0~xԶP/)%Ҽ;)%6x(i$2JCJ2&i7^]c =V/{8ps2+~nɬx5swoª2tpɤO ex<,fesιT[o{K.7Ӽ9gCsC q{ܱơ6RzxB[q$m~sK;?)U4& %HEϖL*οJʌ rh1ÓOΦɓ'i&|*++c;~7iIJ)$ݻwLJ2`@. /}{)KDQv:' /4J% '?bԢw')%'tO?D"Ac[0@kK O?4 ,odƌ6K<ªի844wH$L׹O&C dtv&f.݃:W@ `3h>m@qRX0u--MGOn)5; [NwR2:z%֝7x;/K\̅G4lOs{{ӡo}'p]>s.,M#TɯUOd%nx_yշ!x˒!7=#cDX*Z|R-6 x'MG "w|YVb7uH0&Zɻz H2cD׺y x4d8;?-w==u_Y#]ЩF}3o3Wtkg}̺0e}1CzV9۝ipUTHmS&n Z6;φ_QQ%_% _eϘx䑜o>Բ}LSO?SN=p8LQQvL&~SV>tmߡ1cF{.VON0N~Rw9eT3dT~rw/ϣnm!U8&bH0qkT 2p@c^3gN B!-$ce֬Ǚ:m}Q2`@ Gc)(gƌڹM}9,_ӧRN=4֯u?i(++ f_җXC ̔)ShGX.nj<Fb:":`ذL4P(Č3ֻ$wo5۷o[n#a'/8^38Ӗ j˜o,Zq0)1Y"_:T6lopSRR|c=e]iF[[yyyOJAAgu&/w'|@LJ#F0IæNkj|NJIaQ!wo& >3"ٳÙgEmR D2Z[PT[W6l+uN/y<;nu+U/͹z)3ᓉƋΪ˱@3}S,/y\OE8~|zes.ow#KV  &ضegˀAY2I]m %eaW-B_T|uOɐɓCJɴihmmFuCaAwS)++Eey翳`^x8LfΜIuu5Pc!Xf _1sIәK&ELԓdj:(F*> i z\r|mm6¦nf* םGtBuԡds.QW5K[8x8#R*ġO.rgcfhBY?1`|;:$ֶ= 0DkOjWE3b[(R~=СWV*}@Z@mێL`(M9%)pD=g5j Ig&S6d kO57Uj9imuL%2͓gMziㇴǍ_Ȓ[4gj/HJ*+ȼ&v[asNt/k+U[}ֲ.URxQGU`#f@((( 3hPxh{;e7V0@V S #ҙHPgMifhPp8LKK 8cSޯ@ INm utPT\⽾״!lkm[ wPG(tNgCyUG'Lw^rss(++3_j4OrGCw[ٛCh%#x;/^5 ;v?1GLȝwioBAaPx9yf{ߩRKzH(CҹnW)CVOi׳zOj3Ѵ Uz@}qY޼< E̒*Y^pfqBp{edgu R O{cYTDqCJxKnXJ;ړfyOJIT@](yT>H-) Wo&ZWF*ge/ "ƴgǽjz‡uId ~~{4<Bohl ŚinnR)d޽DQ[`R2h`"7n7ʌ#H$`rx/.)QB7X~{ ʼn($9GM@K8>qtvvcvC8#GRRRB,nB q BYQV7xwիFϵz\-~7l4me2bfzV!@=%WwuQi9"֪HHkg}VPx)ۣ[V܋/XQ.zf} \iIϦbUZ5u90_~3p:WkU%ƎZVር+K'/ˎ2hvEVR'5:}pgVo^zCrl,CKB_/ 0Ҍsڜj: v01Lf+GIIy  f暟e0XܒƻO2A)G*> uR܂I7^R j*>JGI@wM_]eU>]e>Ժ *d=I^V5Sڏ\Ycl k{$+48-r*^qEۧWQtut{Ȭ;]Mg\=۴~:.zߥn2a'=bgŁHD;9?zJW:dJW}v0U?D{{, o 12v"[zpe[ncE$}\Q3>3W~2d+t]Mg맀LtW=LȨC誫/=~Co=-݇OMD|SS˛`$4B4Ҫ;۹ʌՠF ȬP7*ORIԠθ;.bo;wZ Rj2;h[!Sk2 6={ٸq"CJ5TUU^D7% ]w!--Rѿ.k,ucLmߟwu]]mmQwtmۖǨ:vtқdmicߛtdm鴺:K:i_̖oߛd9xh4Jg}Q]]DN,sW,2*Dag+>\ٳyblvr*VRZZZxKO*m6=z-f?Ўb%,!LrJl9-^뚗ʟ:7|ܹ/u477 /͝Ν;yᇙ?>;v`ζ|b\sUPշjH|Q#;fjn>(oE9߰j[ƙg <!6!Cxj] !`o2'ٳg/vwNii)vjC]=<97.n߾?`(S2 K>3pFAU=n|<+Vmͷe'#655#PPXgS]]cʥ$WtYY:[3;;<1{6[n1&e;ٌez0?>|}>ݧ}>}ue!M@|yYyyA8p `ܔo~p>LoL3y2cƌO?ev뭼;̛?>%jjk=c***k <3'?a< 4551qD Z[[QPX_f W`G椓Nr۷gb֭ 8xA…C8tn>~hiit窫aؾ}e\wݵ5QU޽{ikk['^>CwP__ϺO?.K.7xE 2qDmw]UU >&99|::8K_ . s>'ѿn_=O̚Ŗ~QFztĬ'OfzV9shll۸h5/<{)*,䦛nb\u5;e ;wb޼y{~z~*cnjatf-kxرc'yyv 6Ԭ!e_ɓȣ+"''^zUVԩS"_d唕ff=>D"ߞ5kD9v1R^~eRrGVֺZb / PVZƜ9sos뭷r|Ň]]vb n6կXd #G"2ؼe3%%\tLzC)p[ZZؾ}7\:ƍ#L?ȣ)**dAH >ͣ)Sp%P\\zC~tpO=Mh4]=t@q]~ç&Y D"ѣ  _5ˈ#hnn|"sep|pORZRik|dDyLF?G}Ĵil͟ϥ^&sϱjjƏGaa!7pCRH VP</6po3`nf>#>߰?~<_W1c' /կ}NE߾chqcRTT'7k996C(<:S@ RqCb&LCg˗kNMMM\~e :!C `9@ ќvҤIu6@ ={8aʔ)jMWl n·oz? ),,dĉ!>l7oFlpOL&y9䓉bǺO1fXaRe = FA(hJF :::D"lڴg}Vnu^K]m-G&//#&Llۺ}%KHtvRlg}/ںfZZ[ZH䢋.b΋su5נ[u< ĸI˂]UUw2uTz7ɶTޣ[lY+[mok0dڴi<s7ɜ9sA~x60a'`JKWill[y줢կ믧'?~<5Ajkj/ISs3UUU|tڢQ**+(++ĔvWUx">C \|1/͝ rqGJKB!ݾX{;pspOJqiƬgQPXUW^Kߴҥ9q"Q·+>dƌvWUUqD8xR{g MCm¥tO*tOj]YIaɡ~#^fY{?r8H$S`]R2axa^Hfa!H #F`1-B غu+C !<ԩӸ;f 555B!b!RVJM: ߟ9*+*ظq#G99Zwݞ!C5L9}  :#Uh Ν;紶…477ϰa\QaPUUEiY"pɲm6>]$ ~ ۷m6Zm!7u*z*D<ԭoUQFQYi--1b8#6lO?wE =Fnn99b֒L$;w-r?g|'F } O>u?m4.rXv+}&;GfҤ).*2?3t1k,;<&O+h IDATyzAÆ hjjVܜvﮢ[ HʩO3g'?u{e͌5m[1dYRGJIaAijjݦ߿?}9&g͚5v'O5\#ػg^!Nwۺ=s+{P P+|m)Dz{,G2dΝTVVO c{).*2_b''f|P.@38'zp(irwp9g39G)))s>P”!Ӡ}饗Xj'|2O>$_}l 8s_ҥK Bt≮>iF;%Z[ZߟcBNN_BAAH=UVqO~~>tI̝;&Lؾ}O?4G;fdzif{2ɀ>O9?5jf??tK$,\{ VOgz|䒋hhhɧRv*;\}Y"08! rg׿XG2.BJ9hqQ46{;>m_zuׇU>{ڃ@*҃M`|}}---׏Zb+'F=  DKslh0U)$:)K6l ''>P#d$G"d2IAAH&cc2$F),($LF<'[( b1 ͠"N$!HPRRBSSEEEtvvB(ȖYJIsK3N"ݻ]vv<'H0aG6m"fHi8n8IN!N:::ϣ#N2$''NZ@J BP B!ڢQ:z4/ KF'CaSQT~SS6m"/?t2n86"Bh6 D"qZ[[BO8&! ''RDqQ1I[[XH$3D'-Hih{Deg;}UUU޽;ۄf FYY---H)!L:玎_AmZvޞpQͷo8yyyV蔭A G"Iss3=8?# 5I''' );'+]uttuVcLabHDssiii b-{gg'vB (`H$oT\\L2D"aTWWGKk+mtvvDcܸqYM0if@>]e}z <}d2I0dΝD;f ɤN(郡۰^-k-555*ꫯP|/b:Hڦf1A4p999F|8 PP(i--)F}Ϩ(Dz BVZ޽{ڽO3#//Oѧyu}Oɗ֥K0h s$77 *RK^^6 :u"Bc{hAA>`9$ ݥ  ]c_bL238C{rd6d5 99>~V!lV*4F: w%??8G8ֳqj8q"H!0F|Nh]y@kk+'nƌ}є0n6f_.t~l3n6䙼 [ >U~ٵk F"xډz8J/^.i@{ឦ {.Vz^:ߛd9`0h0ZZZJQ"aƫ)%fh>!L&$ 'CNEwt%yU=-C+Om8 @II; L$:wc><=B7Khyև&<w@^k6t^uAԉçLv'ޮl.,C^]zOi;(>H:"]to{,=ݛd$KoOI- ~ӇOM"| hRRRZJ@- (!4 0C[ġ%qʧl /ȟʓrjy<ڢ%]̣://ps nr~M6i[ٳ!d$5>(-tW N/ݧ}>}u%,k[i?z"RZ*4KtV Xhy-uZ(u4@ˉO=mS۫iavA:dWdk[[W+Wd6N溜w=LnIY:|wtq Nt8x`8ҕU]u8+/2$K/[A ;.~86w3bQU8Ӡ~u/ԟm~&R~A6eZ3G /[ȨCʋYh>?/xԇ4P}B/MlZ#a|^ „.PS5eL\4 ڑ)Rry٘'մVA oZ3"&DW.jC7"M!g6lP ?1bݐ0Rû^i]w5m,nKwtÁNn]v]|oU3JkGtdck δ^c7ە)Wp>]9]!TVBM':2j*̴ M.3;δ"-=U>Gzs9k>{K bN<*/v=VfK]Y|hFLf+ )!H 3]ޙh!1nȝN&466/H"Bg]&kLJU&<`*-*A*g.VtZ`feUkAK:^ dNj~fttٳgyD"9dAAAAdRJ8{eA)5L2V;NWYjUـ@0`}gcq_}lLƀ & (*laf'u陝YJ+Kڞzꩧ穪RZ[PTTe麤Ph#RJFFFź2<>CUN\CHR Mp]BU6뒈g_i(oaj5BIWqIb!˒W.ڷ^8R4Ä0o|MMj?ؾ={m̆>euٟCQQB=_^؁KV݈7'C}u%%H]RQ쥼 BD"D,3P@PGRJ ###TWW;RWWG,H3 cJ8&>L^E O}9sb/>*#LWE(B&w>de<8mmw(/+gy;◔PSSC(L4hF4D)kw~N>(ySdF!@.-:LuU Axw3+.$9gjQΏP&5]灷瘿`>_RE x3o.=Gm]-on6!^MQu5ÝQRVF(&k.!:5a5yM=>!Nӟ+_׿z;˖QZZ2,<el7!(*ѥTx(JTxe5oxp${m@Q}TTME ]7_㨅>Jkc% hv}ke!45wr|*.ٶFpaL% Ng}^N}})RU{{ٶ)¥Kkh@绁 H{"W\IUUQU=<^OXܮv QEeF])2SVد*WE0\cJ* RIESSؘF/"k֬;LZ/WN6C NPܛlTd`d{8?L(`QU(*wLFYÆPii)mmmq^oNiuCUUm[y2t:/'2x}CC4 /f3='SU6e޽|ûs M?x<{STk'sV͡yo3>)uSd8qa2Bs.Q\ 튡񻧧];w}H)gXҊiQ$QOH6 W(/wK峗p4 @028fyuRԩ ۇ௬nf#vvFعuӧ\;:4 MfqsιEG9g"w tVΫI^SBUE  <FH)kFQQQBsik6m?7&oή];waxx_ arg3֬Y/ N9唼hڡ:/׿ yѿS[[_ܹoͪU+Bo>>:9 )))wsrJ /})o!`hh￟kv܃R&&` (`͒Uc Gy}+ $djИٹ)SjǙ9kO[Cgh X 羇_?aN)͵7no~["##!n?D:H'p,;5::;زu ]w=םq934}}V:NzB=IuG]E)3TU3JK:R6]<[ڸ{ fTUQP_u_O)xizg=xg"yMy/&mAH"@DURRQb$ ATՌ nI'!/ cgeÆ~q `AΝ;ٵkPugEQTRʶmػw/zzicmZ&n޽ͮl߾ݻwFF455m6X۷[{y |pAcx8#Qgضm_ijO? c>z7-x衇43f|fh38pMB::g۶mXb۷۷cm۶@?;v젩ݻwfhh*ɋvmFss|۷OwwUE*ru1*`|Q{(*wd;& ~JJJ2ee&7I87_yGFctvvr˿p"78ۉGYQk/^JyY҂d~9Wv%F7 ؼy3?mDWW']44`cqRJ( 5MD֋C CCCD"aFFF-)(mr2{vr?R'/IUu-ڇX6i#62" H|I /CJwx&3kj)c0# CDP7 IMB]k餴"⚱f#ƣD"rOrg2WM2lfJyGsSV^*]tZkqZzZ"O<*;cEk)EK8eʒL]mۑO#UJ|qTx.Б("~uݺu455G}4> عs'O<$hH$LGG;<_ulݺ_9bc IDATcK/c̜9z(6mK.!s+Ν;o~CQQ>{lذw3c xɧزe PA.r;8l4M?:g}z2p TVVߒ8ON;&ۍ}_h.\H4e֬Y1΁/|r.\_~ߜ}hZ R__Ϝ9svn}]illd˖-,^g|dÆOӟuFQT,X}Gii Hkŋsnv4MSLaǎ,ZA[?!ya.\? >gw܎e`c=;#6JJ5k6ϷgcnaaFԍDO&^&{deO&^&{Y~U~] .kײazEa[k8ą#o!۶sZi<_|7qwDӟD?_2y(.6()) w^4-TUU0c0{Q 0s&5oo3>P(+V˜LیA4gQV^nW3u6'/iqH\gG0 5jHguVťL%C4}TtzuJ:% >WC$H}Z+w8pMB]U)0}!*K Co1ߣϾue#TV7F q.]d:-@:} GCh HRQRiWTrEc8@Hc 6L0U+H]_(LܹttS=x<`?=͌ 2=Fʠ(«MYwtuQW[6u*Z=;>3hkoͲvTviˊ|WPTj˨*7vbm0KdP(WUN_ >[pQ|[>n."^x֬YC}}=vZ{=멪|GYv gu66m{,+Y|9]]],\N;b}|A?xG> Xh[n{}̟?_38{{.g!pꩧsyYp!k׮G?> x~ktw0u1YQ__ozEoP[[7ߜ5 Q^^(FFB ˩{o8pw泟,{_Xr%?O=$/nv6lJGGk֬!Blٲ%k|Yf-gqv;cws5p 'Gcc#ͳ56`:\ᓕO&^&{deO&^&{dS^^N__厝qoo/B***INW4ƒwn"tojK-~ gwiUU64R?R|L.@[Z<) C̝ȹg|,aL)]҉=qYY)??^J4M&egNUQ)9҅o+^ˡ(/.q̬cyTS {(BI&s;ҥKikk%Khhh`}r- $N,YUUihh  Ǽy|̙3&*++뮻rpeY_y.\O~6o=͹KYY)OYѩ?C|cժUs<\ve,'ikkCJ1c&/`ʹp駳ef:::8E?яz=9 լYzL>~z{{dTTTPVVFY$v2uםdJcfo殻⬳ꫯΩgƼ (@ @YY`r5DD"֖qQSShh^~?EEZlbO׈w2n܍ k}ٹ];?K4QZ@`(9TZsUtqE .gcL*e(eANཽij~}ocxG0-mqF4C)6Ftn{6zP! |_8롮~6"_%9q:; |nJ=55H)m;b}Z(:2A6>T!~TT1;3dxuf zlBqq ϳo> gbmq,[AA~i-ZŋYn`z ?A*JGw44L_' o`o9y׭ شi`4ZRRbuy1SO188? %P(8i ׯS__޽{iӦ[o2o<͛SO=Œ%KQUN8믿{oV9hPׯ>mԩؿ?TWWSQQIWWرݻwPZZʎۉD"l2>K{o~cmXM7wyf`((P@k30꜔6׈0WUW?Ǚgi7f'Hogq {l7KD|Goly%iƂ KXTSvbxR>?CbQz}H))..xVsJKmJKn13:CI~n8g9lzm#C!|~T|1^{ԸY!4_=E>*6y dzoīi:iٵ dhw7U,5Ʀ-^j!RW&ah:j4P4 UMվ&R&;䒀!Fc4wW+q)Y6Y]d~:JMw?SN+_ ˖-k5M_ft]+[)/  R\\LUU5;w~ I>3:/^ǭxO~lN8.v,]򄋅I"Zl\}5׿~)š5kR~z~ۨkE9s+O~Jss3^+Vpgr]wr5WSRR•W^O~s5\í0 ,K_Å^wE0? xG9餓Q3gRZZI'ļyRv]{׿uuu\qŕr#~֮] 'wG;ϛ7+V}Z*YsZc-bE~..sW0887%|s_W]u5w0\a\~7(*wd;&qTU%G8c\,KQ-xN؝,mJ/gTkVUpg?&P\9' :y7tS^4iive0_0mZ=---yC  e&Tʌ!:m{z|^sKkgi9p !,66΢i--|̘93~حf#%|22^555\-M8>]ghh ٽg'O=-Թ)c!{sg db6pJk 9R}AYU'_|6tdZ¤c4iDУOM! 'N1 [Te#~nŽE)+#>48ݵ5_S>}^!gQaZ38`65}o3߯T/;pIVÝcpؚYb_EQlcî_İ˛Sټ=x Z[[]÷m8㌏azrʘ ,U;ƃ;/Rwu'\p^/~?Ԍ[~G2&c* oH) (/7~b1jjj| ڊ$ҥK ]H3[w g_kg>E:b>z6%Lg>{A^hnjbUTgeQJI($PTDg{;#Λ?nexYi6l|:Z[[֍HA˔&k \QYiXM(u a0kެzr<{Dd$NLG+F2Mii)[v`At-ÒE rHB'=S%Φ@&3)57&Zz B vqά )Mmn0v*de8ڋ P(`,[>yNt2CUf)lQ}msޒ2:m*]@iPPx !t^Ay.zcDc,^?MNkR22ܰ SPQYm*ͅϴ\agS,r>3]SShVzR!79r3F,n_^YX͍wnuׂrS.{lrwt|:KW%8_Yeu&q })zrdwj<ɇAVy?#z_UY~!ݻY !80Z܏zd0gZC2  L%+lR6ܘ-F"%5ɹ68"5Ti\$R i ]N+Sާsp״M~\y>d2d1|6ZAԓqg*?*9ܱdU*q%B|G*sb5exeϺѢ~'/.xgD5d(rFfNdNȍ-39F't=ρ\D>&W GIdhPy$|Ĩ: G0ó-'s~o>װBxee '/01$?P@'F)`Xq$Y&KaC#`¶>x~ݓxT7tWV&#{VH8JOO3DxCcIQP@P@PyN뤠jK^. x,*BO)4ɱ*[&K;ŽlJ$TGf ȶ(pGU'ƃN~懂>\8oHj]Hⵀ&&o͗ H*'xRCg,׍np<OZ3M)AҔ R1^ub8D,[cɰiSƽk\t-FFDA SyxTΌ<o" IDAT|G8䙧3a~B 繍dt"P)cw)Y\SJI-؟'`&a'%s$=6Bp>vd'q8؟y.lqYD|.P.t:Pl0gqfZm|Y;nP:iڞG{Zj!3噇3Cϲ'llʀk}vf;n9&2<̫gnv ٞkp8|'R HI:#͒'\4\D}2DJsT$ 1$'( j(p(n4 E]->M&DڔTH ʬACut=I$}%!%E&v;뉲J[ PLY;|;M򖩉cbП}X0^VtF>htLeVg& RNÉPb|>cv!xZ-xNq>"h޽L|F:Z@UU,XȂ DJ?Cii;a()nvl d \??fl1$-+PmŖ6b*hXUBI%.3KKfaSFlv:}CK<'B{Xj\2MI{`R´%$P -tutB!mjމZK*㆚hSFm8,B\gY&; };"CN$>Le)j6N& "rɫI^mxMzHOH ~> M3O`(gu6Gu7X6Qھ-r=v9pʌei3`Y N EAPEAUUKaP P0ײ a(X0==M>RA<& 2avE)lJ\KQ9k'iPP"jBݰR5ҦXxϦhGz)Vdg‡nx},nSfkZ6W&>Of|k<# |e @u!+r$1a4K3eJ1~;]FZٹvQ& ٘ci[S}GTLy:2WÑ`)22Q3GL4t]'9$yŢɉњtZxq1+8yVab:Z28kł3 Kkg$5մLR.`(5 -UHqS-`ڶv *VҺnXkuݩ0Pubg$G@)u@"T*Ү,[RIޤihRoc.2n$ *UQT@u_\J::i,&H SiYT=\fD9HwىU:&G`|N4-K.<~>L0F x& @4FB GJD"Nf{ H \]D,bB28he0-&Xw t9K9]i soS(ՋRtXϕ{dZy88X$%dߖ8A~IZ0jLgC9ڠtA'*0SٔnepyRZHٝRQg0EUV[[جԌtW:Hݥ20먳grqKmчL0_,3K{,y1)`/ xԭh-Xt2z ;t]cddB$D#Ą:10'GFFG4-"%@-$!V ጑w NnȌ=90(tO6q󹻋SS+o\{'Σ&yTcMEQ/yM?[iRa*s9_Se= /[`\ʓLҚJMk*|sy"!{:uBIGmj.Ć焢$)Ʈ0,iF4N,PXifI%à/ 2%s4ɒg&Wo'ŎK1tr}a" B2 ;4Mg$xB0*^GSS!BL6C۰~:1N&nwH`"ݽ 2IJeX(¶FPLOUU<@u"erf0`4PRY)Q8WWG'LPHe1a NlP %Q(]cIWRڡ(Ѳ6c51uc˽9Q)dBNl57HDyBlxq, n}p _ Da<& XPp{Y)`iEaYh~͘cBJ#S!lmme֭,^U+Ri:Ze|m7]nn >Y:0 DȈP kh7R"&']QukU}'jݜ<<9cYB3ֺ>>,̕N6ޖDVl}e,N:XE+;p'=KsbFLYOyKXc0 >Eh$̺^fy̝3uϿ9084Hs' Xpõ_)%[Zyf4s9 fd$ly<kxظykV:_kӏk髙P1# zXq.?=BgK3ǯ4HXLmk?2҉"G䄟efc#R㚥ەQRRB$sM1t;^x|Vd|2#_UUGEUTb\$mҦ`9?Rf4 Sxw*SKH?cV7 7Kd"%oљ36 rU\ dѬ׆ hrl $h7ƄVIXlI)cC4IsdV՜-%Ҷ>SC8DejP1uQ)gL{v?f^QǢBo*UU֜|~cV+ogi'O8f:)%|Nհl H)Ln ( TyBQi9exEFqY%UE(f$r_$kg?Z]B=2QA/g}yVpxɇx<h?[/yZJN.|f7Τr̪ePcSQQ&*+*s_wEO=Coo??wZWC 3OtuuZyTU#dOS300vY5c{`(/\C_}'{EZZpQT׿=ɔ͙EO /+/Zp*V._˯IqO,Y ǗsW)TT22BQb(}=SDG90&#hzMtO]FE7f<#%)2|3 ~++* qWPYQ1.<_?$7bM &_lz)3P9s |ӕTNgY@/sgG뮩f*%ƹxnU=&I&ct9ib,٤zYٌlK %E4dm&W2'3_م/$'D9APgpJ('ID\#dݰLR& eRf_˨6I!2͵}te]hv7^V|ln޹G*K.C&Kɶ8o+\-waҏ QUUVߤYϧ9_v';>nvn݆<^}-~~shj˙gA4RJbx؀DJbq-Ģ%vlko$> 7w˯NC}=v&[ey΍LF:`j4 G123馷UQu{EѥN,&AU.8\8]|eeTWU2mjc,0H)觻 EQ5 DF$RRݰ]{v@i)?ɿ~ #C $)~TQ H< }C 5AuG:6ӛ˗r *<ΎV]f#4%@!UB fc6c88y$/y?$/8``v̎HB! 3zGuuWWW{gFHսUu~TWS| ws/Xp.M2a0}[?'{}iLa7V'?2`_P. #1ZwhX/x)NJ:~fWߠ 2Y؎e۶{ D<|gVGF}W2LVZ@wzH<ԵM:Vt$eGbI0V+- BA̼F,nz\2l2 ږ0]ؖrM<uag|OQwYXY&<ڋO1JŠZ*c-FyB^dpp= GoYrw-q&JF)G8tꂓz;c#`ΜhoufᝍpN?m!$ox'0< ?腟xh6vލAL4'B$ovo!?#Ry_C+Gfp9N8eļX(կaڂIu009f}H$2yDwwO ˲A'==DSS#&ttRQ,>a3OÎJ%1edlݶuuiLi9AwΛ;g6a8؍ӦP,b߾0M'L@SS#kO}X*aؽ 1tND"C{ uu}0n'[niw?mu6M7|m;z伓npۭϬW[圭 FmY6`P\]Q)e(p#WUOSEPzrEhk[c ]m^‹q/(uI*:1Vm:ϰ3)I]fS zc+Nvh*YVR;݃nϰybwlŗ޺m/X,~Ƿ&mਯO@ʊRAr<|砤 Rp$D)9"R[9Bi u,mI2]o9TFNH&xqOBΟ= ş>D[.Y6 hy sNh uà4uJ%1w&L3*xCJ)|k {H:>ۋWq*p6˦˲7g~d0ӧOŔɓp/#?ݿ_/#1~Ι ]p.ß+6,[zƏ)'Ქ㗷e-BfL.8\~>k-5 ${Ղ0;yF1=TYFW'D6ޚB\WfSsa.mt낚& W_kxTP5[Fkw4X[ZMaq YFQ4lۆ]߫v_]֖YP>)KO q=~#^\I[Ҹ `>Y廀ג+`xʏnaND"Ʀ&x}c6~q{' rշ͂1ݻ[E# t^|jŲ90+O=pKk ~3On'uoW'Y7g62߭3ŋrw*p[?t:k=ݺ]4i L5W)'{/$Kr*vNR ̪˅9= ӗ6O<ߕ-UtT_?JPN S0=ޚ ac6((OR8\FwrW?0@WjE>q+ދG~ߑ;lf௅K9NbV1@!ZO[Fyc}+ZUJ)̤ '`HQ*-"XbWPxS^~'#YsT.[([ RbS[x8(<~u6 ɓ:lEq] %`0p磩 Y4660 \}X:X,O%bޜ٘;g6jBJM?e$|G?;=YUwp'}{LXAyh*0Oj=syF`Sss19) U\ѪJ@ Ҫjt4)I#HJ# yRvj9Nob6MC#!ϖ軇VaR*޵M,,[ .`K}"KP#9QxMӄaJ @)o>iPrzW_ã?ɧj{a*\T0M,d {a /,O|fxv*̞5ik?;=KP} aq{}.%w':*cF$l=E;vg.u@,4 IMA)l$Rut 8ZR?@#Ws$[&:>d yRJS<kD:Hxy݌*,={]G<3a}V?BQ?Z ~>2)mȤ>B ~p@JסL N ,^zGTVC0<:zbʇmS{C.8̎.|tvNsϿ_q~ߤIo';pH% r㚫.ǖp}8[aubdy*<: ^ S!V)6VK[+nA| xk$YΨ~t2V51&  ol0 \DC!7\nBReAqZj~Kp(x`mO*]|Y^1nO5kb|%ﱔ꺍T#HHe4DGpjt0БHCuve'Dq&h)S%L'0XR <9 ÀH0LDT'Pԏ/&ή|g@{h"@ccC@VYJ$X~Rd9ƛػo?V ÙOCKK`w>PJa\x>4ghdpގ{nqIeё{LIoP"B([a{4[?CI;Y`T`0#n_SAK4F)lvyy J%e 2G2y“So\c.azZB֜O51g2Rjv;V8U[/cqE}8' xʏJPS<\B{D-DVZO 7tnV|^@&ܙ8 qMܖƵ9>)L{%$$0K d f2O$]07Bp4>' (urx<*V2|mvfVR&9 uXKő;hFYx$|HtOӨzqEӪDqx+:CkqKBxEaH~/,;B 0-<Rg o\o7)1}>BH+D/Z֨ĠcMx&9ۉ ? &1Iʰ^]| 8W8[n\XPT5?t(DJ|FͶm- 2|sw L͟|s:3rjp5l$j WHgjSxBÚ_/N$yBzC=Ev'wJ/|-4+KI Iʯ.xy^}w_ ޴f|OBdSI҉j_{JPL).94M( !- Gp@x9H\}ى~;wc kZvSOAXzXFvw}>NL}j<~FnZн?L4W Ʋ oddK*0 fF")APKVPY6T'?zu]MEN7N`%laqxi:TU0xp % l<@OgN`j+sAD~o[sW'LS%J}# 6{_,\cD:}#jnW'KsyVW<'<|Pɓq٥wTI'|)b-cuS'T!hPijU,%З$lٸ~[0z{R琈,;\?g?g`Q\|/ I58{/gՄ‹/<H[ro89?'O;U_E1yTtZ]$II#cHD+P m 9 :LY=HWIųpL$Px,n @\eفt1KR*ŷz g >N&I̘> -sA)Ů]~ jo?<u&?_}ͭ۲Ok?:̜1 g.^_}]w[_'eK/Šp5WAڣ֯B)e= ttu95DAqҋApC(VIw?v-n4!W}GК7wxߢL끣opA&*\&tނ-#}FeWv #@|D(YzT!.`Ŷ/_ߏt4xV)L bLz'(w9mD-:Փ姊cZҳm},@Li;D\Y""sILU: r9B(SO-׎~鏯1@"LYNeṱG5WQn1 Ywp:M9oSZ<|p;^`=ՠ0*&ר yCo% Xoݳ+%*ԓ1X/\~ n*i!':42ؒI?L`RKufV<-ª:S=QY2IEՠB- A4wnHӸk{wߡT.Gg '!O^\+!::ƹ_x97~KSPJc]W!m)@ޗB&,}G~gaʁԝGoqfPđx/F#buVp}z| þ4Dk#*B$HHǧ#i{lBlT>."88cL]gnо]A8XZ_x țw5(ܻmq0rq:sÇ1%!w%vV"@Ћ*|3 0'Fe^%#3@o&-sn|^[TF02*ܺ)+Toq޺oa&ul߿0L8i twc20/>׏o|hmm˖!Ns J2~J%R)O¼9{vG{@kk :Əǒ/Ď;R4@EuTV$W ~\w&Ֆ*H: z r,@﷚Mf4ŏ˿Z/놱@,='#DSU ׅ,EԜWKyP9Z~QpT-I5X 0"f8om˗×SȤr{p+D|z'.drق| L1^b!a )_ȕ{`L;æCo\C WEAoFj 藞oix8'hokg.Fkk Oo}#I%8ڂ'x'x@H ̭-7Xq2q`٢HXRweFN:}(*E zDk IDAT$bPC~! KiZˊuYUD"8e{ҼmidϮjT#eo7G] i7s'x"oǻur-nz񇟏;, qia:Ŗ*"Gfxs Ns:ex6kPLiCb5݃͏|f`Y0'apH7NŌEKc\?q9Kb͆. dX4vv쓦`޴qxX .:u=y*fu*+cZxUtBN$!t-`:pW|,:p'GH'ܽ*7B=p`dc u NGVBQ^L/ǚhc'N;v׿/ -D /Cxu{ [qҋf`~fp=GofϚŋN+H: &i*J 3  j;_3N0o.U-U 6<Pĩ`ϓu'}.=RD$ ǵ8O[iZj 3XGFG CFUJ)6l܄}I/~޽.Bd̜9^\C}}[o 02 ؚCe=!ñax&$>aV89?A (h_y*hG#r4^xI K矟[i8#0p<8fNjT{|PmXk!b!97hǍX c|31da ڰ2 !ΫI-!F/ %t)ᢍt^71V[/MMGiz[7;z!? qiX~),/FTexgD'#ŭ']zuT0G'XFɿ|aH)(࿇{48cPA60يLfUxɤ[IF)#|'t9$Ixso}8ׇs>MXz 2}~`M6u 2XrxP.[rNE.esx?NV_,2ꘞD,+( 5"٪1<"]5hQʾ/vo@0#s<~>ԇj+1eoI3z"2"mn1k׼9bdF"Ur9U]8qu(hC(Cmq8޽j)\9=]xLg`JTR)i=]߼kvy~TWן7w*tՋ7_7_`euWUU GVWoy )؁{4kygq/e1Hf^8H"-Zo-uNH2``ɩ1oRVƺdh9<+W,ǼsB' {Cxꙕrᯫ00K0 οfxY*Ae{1zR)|+0<~}9OϿbI*kp3O! ּ\x2z\t 7BF!ֆz  T BaLcjMu$jRA%KhS ߵb9HxL*X(ܜ:rjV,O 7#NBq@1Xdo2bĘ!ـ˘ "<{6ݨZqU'+y.=9B:bM-9^F~Er(~q>}Ol}oyCt7m s?d2D"rFb*>+T;98sr%^;/y9HoFg BۛM3lb&//%J$SX}WP%lܟGf!_.ȗ(fuqg>7~㳗oR˻{hEQF^ B0w ;[i˲/@ȏn绱Шgҷuniok/ ^_ɤ @sWVpՊ娫g>} 9'͛\>^}&o^2zko7^)i׻2J grؿ&tt\.;E-UD`V?<\?E+WĄ-FR χ7ԹN&b2G܋ ad\c@(AXGhv^}O 4\Y-uKH)K)!.&Vzl08tG3a=#v\%V6V[^~srѴ]0M?ʳD-_śC}Rj+D8>|U]K~[iV:@aóJ2S15Ճr:tӦMa( 5NB[f//.([xUVr { G~à`#ļLp/6 V@? &f6 P"EDaLQg dB5Xq2Ort NJ *$WxFyhv-ٮrhHL !0 Ϩ:Ej|V< t  u,jTnWIVi*gtiTT3OU#'.PBZ5Xya\ʼ]9YFڳM9mj8gy8gyёb J}R'"Y>z 񿩘{jjѪґlÁl1=Ah[` A\&W__ `m8s=e3,3cl޲nq᪉+eeض}GhZ޲,̗fp9ž $;Eh .suq?ԕ 9 䏻D"w ` O0Q>,1csgT!4iѼN"5#'oqϠw+ Dx$g թwũ'b#: ^C:FgDٻmEF2x > GFxaBZ}KޔUȟG.*h)&#ەV }RO#䮮ߎ#nN( ;M(xIQxsWr@ZFvQ1\;8$'nI[+ Y &]%7 ϱ\ɼR,*=V^G1BEvCe܇NDI^(}͸֊t:la0 1݅L&&44dw~ 1eK/F[k+k 4äΉ?n{`w7fΘ={!L| $?34 ݅L?=9ɓu𣠔4M8oLĮ]]Fa.Aٳlk׽3acOŔeYرswDUmj1)*?y0Sǰüo8t𨺨DzDk lR`m.:ޕ<+zy}qF@#k@(& nl0({=fmYr*y2]b|1UXq/9 WNV(yEal<0^ZW/ѿh$u~WAB2A@0~> d'bQ< ۥWӘ J(ORcĩSPԼ7ؖI8_%([, :/}F˻."gF\bdsy&|c7k^t5/ ԉ6\tyHh_G>tŶ;᫯+iӦJpi K/cޜXz ."tw`hhƴS`Y6Nه^c@[k fΘ`7-OO|VzӦNנ S&OB76̛;YO==l.]pi q9gbҤN\5سw.У쭟7tN^ lEU_‚O„wd9|+sfLw#X{0N7W\ vS1c4{x퍵#K{m&\s zq Ƶa t8 k^~o]w8qwj )aoLA!Qi{ݹ%*9k%ꁪt-J"/.8C*dLQdOLƮMa勊[ 6RZj‰ SW#7*~}JWxLz[(A=Usɼai@A^p xyPRR<o'w ~ɩc 8A*9:!s_pT@~<|duuF_;J`3r! :,,Y>ɶlŞnCf< X̚5ֿ&lٶ_򫯣 O=-w4M)qGo `xXtL}).\6Mm455`˯X,`\l1\>y NeK.´Smv477;w[CKK3ٴCW`Boa<Уkqک H$L48qӻ7{pygSc 81ax?pU8pBg-^䀮y΢`u4$ZZZ0ŗ_AoawYr((KbHUҥD}}D 2V IDAT G. 8~XT*&$6Eמ}xgԳ+0~8KP,eC\v0 456TJi5~8؍!L:{תD18 kPo'/4C(n#-ayT{$#5J͙=нIWdc*r2ő%LkWZQ=%wxDA,n=y H&L|7>Z[ZQ,aYEXĕ+.E{k YOG,^tV^~;,[z=L gOOr3LsP*Գ+srs1>݊;v!c}ǟ™>+KRwp3PM&@VPPs<"3'GD+ 91rxWk#U-`h%Q>k//gΦϣGmضx|w{ ](.OU-EE)Zڢ0=z{[tYвm4Ze=XϪ U*=i|z5 ýs/w*MBZMVw9ߒ$ :5`Z!#pA(e8r޵Wmm–L$H0R&0Ɛ,2z cn_o0Lc+V__RL&L= b !JP(rRf2H J%y̚9O߾=PF@AcCAκs˖tR RId2c, LTBBd (K` X,"L:b2dHRHR, B d|?>P@4L`6I^H$@)EXB2D6C]]>SF,yH(DcUa^O ^(al 1}]9 `H۵be!,ެX t^U]-ȬU.5}5|@Dۋ˼xZ@:kH?Fżxy޼ E:O;1^6%YPnegc"<lÈ)!HǣhMl"SlR6:bO5mX v<-LvݘS$}VH JS+_@/O2&£8 ϵjŽcwM_57?EWW6 ˲AmnKcؿ?,YJ;׷ChCo/$TLG >X_d4K.rC&JN*X0#@PE_,04<,'60 mm(Js444 gs8a2Ƶ#ahho]xčiH&HRKa,F*m҂bXssr^_& ")L%5L"ʦ, RlC&SRT*|tƆEL\DrMM( e$S)mtb2m. B`Y&tt`h ǣop  e0 Q޷mm)BgDe^Fh`ϫ J-h$=eTqQ*aX?p/5JO#1S_S&!L og\ P/g *:yr ĨۖR]0jT*P,¶)rBH$fs0&lˆm ~/01 _(Ri RR`pU$q^*&-PU,X2Du4Jl`flH$L Xl*i`E\ T F([6r"lFa(Y5m1eۂe۠a(u, R fCTp\ oBƐ0M  q[A`6ٜ =Yp,>=r&mXAVyzҏ|UW&h?x\-=¼Ccc z{(?^Sk%H-uT_N8U[R_g(razF哫Љ7dax|;A(ڧs亖YPVh'?9Y%3RP}0ycRwT1m |08إ#ڊxƳXq!ձJG[rVNPǾ('A`n5;Qp(?{]%,ҩuDz>l.D4 jSP[3q Z!Wb/Dd H@\¾!T(J0j@O;3aެH8 {TB`ECa6 cbѭ\6ϿsyW\oA7~TV!!R[e)Eyo.4MY ð +,DVE(i5 '(tQl,h=:Bm㩢rra9%) 7qrEK~W<: ʢ*+&]B]jݐ-`xPbB.-O>qqU^_i:ώ%!W_Mòe*G<3&^ /`#AB"}; ?Re)4( wP l*2IdKl\5 P@cD je119)k*?;ΎJ~o4QG#i# `D e68ڻ޵zmx_/]^{ ` PiG4irwnTf5}*:U]uNjuyѸW-1mu,}@1 BAA* S" N# '~S3/q5U Z1L5dQ(QE3IvĊ-x\~Ν$#;j*@6*Ơ;vƝQkJ\1o~fyBUTyS@/ (;oa |uO^><LjU(F%7y^ݠ(( 󐖖@1xq3 ̔QF*񚓝 P@֫ ^2_\. p)P(  Y>̅QRaTLdg844 FHKKF$¨ϧ Jb27R 򰐭1ol]W 5W:hEVVkr> cPQbGE)VX<%6q"#Z<']qL5YXYO4vzyA1]G`Ic$@TS)h}<(Ah̀h h 10bM3`% v; G+f " 8qV^ٳg8Ir0 qZ7Bf'j #cg[8'C饀rnQxOQa~hg*p( HDHPdX\,$7,*11IH,#n%bu6 a'v\1 KB7.ڠ!Qw]ZW?vU!0Ls3CW.R`{,?B?/J}HW 藦Ldly+Wi%87hl]oγgN0EJ%J";[U82խ4 q ^ٮԔ?tQ+0<.8aQ})PLԔwR6,啙ß_?vxLlߌmݘ_[5 {G/6,@VABܾfKKI+;l&猓ARǖ_ iE&F:ejT F"wtV~E>y9}>8C)qEēGEC"!F` B)< ~lnW>q׌,TaݒZ|g>s(/Q)Nshlİ/-w@UI?ou5;NRtdy?}eHu3LOD2.5{P;}r 3QZB(_p^8"ob~.B@4dx*J#D\)iS11a3;aQ=@`ie5J[aIb#VϞXD1 '#,B̷ &M!'tv?8rT%(OyD"j" Aь#Z3/x!qUS y z`0is& bb ¬-ӄ5gVl&Co dA-b/GkN茋8J('iW 1n`?$")1q5fyM74 BFJYzFiX <A 0^ۂ]>8Ûow}r Cƕ3Ӹs|jD0Aq~&v!Sx\˅}%.1..f@ |*m [DŽP9BC^DYaVͯBEQoi~~Bś^gyirnwa2eAuI6,/?M102g~ؿ艤%2xv@%>2}dPylyD{c2 (7K@Ra) X9 ?+PgEJKJ%Pꤥe!9me]_]0b)jʃ50Kg(p7_,aQR^IFNe*#xag.{ -<.r iR3QB(ԝ U"$q5 CT aLt2D<2ON'.-|CylWa/y6 O+Fi8y%BbFU4oIIaSv *OP18ybn}-̯h JQq'(O*o3qʙ}Nf~0* fO^PKtZ6:HݶRu_U{5~R\T ktѽ{0鉤%'D#2J@hk5vyđMȷ5,'t} 5kY6|| <2"LsF )q,YӐ_Ff3}O3PzoSLAzR baLQ d' >Zo@zq޲Ŵ$x!GزNkA4XIO(e9:ړfiu^fZ1uGҖ2CYÓ}fƃiL _ O/ ]Y@/hۄ#,NO8-{R zA1;SJa;Bo J宼bWp&!'QRBJֈGPToQrO_P30rIg&]FˏrƄ+c@=+$?^nhjRS "{!]0u;BTm VMzTA[5R TyKi1ut&%KѤ!$/b;FPfO&Qj"p1FgWIS*S>=7zB1oKlTFuc!֬xL&H̋Nv)*f$5NsCli"1$TPφHɔB]RN&'c*xJ@D= R:l-KJF:hAd湬G@ԧv e`Sg#pi3"B{4 C7[(+xRL3G3© ,*xbtu,(O G7咒/‹$L*g.j*P(reh%KF<3(b[7hӈk>FQКA %iKCbے-{ƨ|Y2Hx[cs6֩_ ݏW6Tm63#VW:hں<Xc| { =`΢M$R+f * GҳOH#, ~\3`3 72D3”ԍ_Wc$CJj\Զh;Eo4>tu$v(Qfm.q8Ux艤e.^ZuRTlя$PY)"q| |<@\.axQЄT(g<DNPy@^J\$AJCd_ O:#TpKy..rL qNyIc'& 8Vi<*%Moe'16Lj'4œ( KT}c B]U5m|XX45?-'ʦe4Ig3yO4M3}`IaWH^VSԨJDëReBCI+" <1QJ$/x{t{b_L)<("}/ WIeUS!E>X8}RPzy>MV^ JN)iKNݟr2yb1,-54-aK["/FKeV~pU mmP(oDȲWha/wz"iKBڎi<̭Q0.iI0E`d@¶բMtZr u8p0Q2⡧2ۉ .9]ZnBRqJACf$Ŀ[/o e5j8p%V=:=.u<K(=6)NZf k$2d!1|Ӵ /kV̫3Ri4NKMhcʛ88r8*qRub"}/ιFVSFEF 2J({ kCEQ 9Kx6D豲hY5>g w#Ձ8p2Avv fAgc93m˝.y?*/eOe}J{~1^HP1KKjO"dBa W3K|b4:p\&xsx5^At\eCOeZA/v'y<"Y$ tGrBJg(Bvp K gDi^YDKe0I#j|}^?6JCCxݿ϶.ӋW_/~tvuaxx# 088g_B#3=[o+<#%Jy^8p'x7ca KC) "p̼Ƃa b`1[ ތJ+=E/IE|PٽE6KK‘Jkع{7-o-o9S|(~ }sطl<|D#?&xͮ8pT،yPMfd"xp0?E8pbvL,_^+V\0GvCΐ݃k`@/]Qk''c0Nave!:PU@(]Ts+p hAEq.n^5caPPܽa!NEI~fUON|42<PUmPQ]XD:tguPC!`=Nl+~D5lƎ2Hkiʭ3 cQ^J;!t8H(A_0"č3gEwJzlt<׋=c*~9Sw< rn7w=~ܶv.vیWD׍C͝xj!̯+F^@Ϡplz}{`q};YYP-{0v9!=q>u aŜr̩*WnnDž5Ÿ횹XmXkV\נ?O$jK !7+Mŷ {\鉤5u@Ds=DGBQpC( 'TŜTz㻰3!:8pA|0Rw؁'N񠹹Eu(u1<-/̊iԺyQ^'^?B2\̓8-xP_QL$5%O\J;ECPCe(2']gBbƩI¦1gTH\lfy Pv8HKQ+.MCII jkkqYcppsK*_-_ .N b.Q-7\8T?|hP yY=TaenM1(,EeqLgJ/Gz"iM!ǁbp bhw>F a Ip\®ik*r8p$-jGoCCCnۨYxRnv5t;bwx;N؝n'^N,^X]'nГ ; ]BBn)6h4[^Z1TV-Rn&ݥ,Nr;SY#+pdeSQV.kwir2u`uK2")S* `*!d6iiDl6 ;4T"u`|`ǶfGeG8rXT6=Ƌ ?)FBdCI* :FUbś ƋDʛ L[#+pdeNbuj~mg;SV8!}O`DNH>#H"{2ŗ%2Z]ʦJXi[&g*7Tj!gMV0|KݮjYN,̡''+v*:9Bc=&(ZXO,j&xm<3rfmJ>ǣ=7.<؉;4wvt;bwx;N؅VFzQ{ݗ&xvDÎ/^Vd$"n'~SD+._Lt$Yؑ'|ّ'8pzEY@nb?\Vu`*߱^D2 ;eD :0*!2 L5.e9ّo;ؓ/;RY26̓ј N&haǛnGYS|#+pde( 8֊iAA\ ! o7Ӌrpq,Eyy'? Bغ #A[hr'V'xM<" IDAT|$ ꣌Q30mmKqY%xS͏l=_Je"f]mv2S SY#+pde"$fH?2Y"Fd!BϠ\ºR:G'30r^|׌eʐEIANNh(GeqZCf*ң!vjNr̙Vڽ p`aΜ9(*.ZHY2jYD{ڦZGw9]*cؑ'|8p x5g6=B8rn-^Hp y`ьR}X8j6~v~\sEy9fcv[R@(}':|Nǂ%]Ě<.aԔ桤 oo6˘vZ2QD%x2l444  yR%^e! G8ޫQ!co2x/"`KLm8er:YY#+d=h%8#;~CaU\J)nX9fT{^yg}A!uNmkqSG8Q7a^EX9],Օuc(NODVfrĢfx<p#D"aBa硪ƄBs3z<|J5hx#XvmWI/vۉċv%U3`^b<ل9݆S'O7C0G$G_7ޞ A]. +݃kk⃣ǣ=7.<؉;4wvt;bwx;NL}Cuxp! ւ[39Dcy!\߼.#'3 ]3~p9,O%Y(2lZ6V4T੷1 4Bt?ľ.`߉\52ezCOx^ χAKK JKK#okkCff&uiA455 ׭" 5551y|@yylΝ!K,Aqq18˗L㨩AfS?ڲ|qB:1bZ})̓{i/ṋҬ8sqP5eya=h0CUq5#<FQ^9 z 3+a#(xDbGyO?U_~{5Lkǎx'g>3~9dgg__#n.5B~fG)ܰ%qL/sA)ũ^^kpdʋrqK{P]g{ś$OJ)F_q~&nj8BPQx X݀%39,5,{G77.DfNG(}x@3˧ ݃n-vsxXC> q ׌Y`LUp8nq ÃZ5/'zP]>46wb*=q+mq ss }~|ᦥQ=:Pň?MN] W?#c.e3ޡV3WSۯh2v%;;8^t;*K%YYd*==UUUhooա ###wW^SO=3f+xWqbժU㬹<P/}Y̙3<z!b׮]_ +Wo_~+V%|_̙3~\^z ۶m˰tR<8}4~a,Z۷oGee%!==z+k O<`ҥgy?ZZZ~{qv {U%u0 ?_ُp$OlQDL>6me%R B#p;0ӆnˣ^a]eypxHRa 2 {ӓI+A˩#*#2dG+jAJ466n:9rį~+TUUaxGgΜ??a޽8s挜ֹspBn\˗/c=}kc . O<|]w6n܈'hhh@EE}Qttt {=.ׇ477ȑ#򗿌2\xǏdž O} ?3ϟ?ydgg_ҥK?o2੭yc8rߺs55wgcs:&(0dhFAJʚ,YYd*-- Ν;q7n믿&%V\><Ӡ"33_W_BNktteee7 e+ϣ^{-q<}{/kע[nE[[ng?Ö-[yfY'b̙C[[~mdggooсa8qw066&NB~~>^۷/^Dzz:>-[!z__i9xko3~A|bM>sbyM,䠢C]]f̘YZp-qCgNUnWZ?Nn݊իWcܹp:b\x՘1cU0}t"P9SQQ^z~tuuaҥX|9Sj!vvvsw @`!??SNŋqbΝBYY 0|XB^**ٳ8P+V}v\}r'O͛<31s 182!_ue(Dvp=>aQ9ᄣ}# bfe!jSa?fUNlgfU_XxEiAJ p5s qsLú%u{yf ^7bΝhhh@nn.+`C)E]]~ӟ8U ]0r31jGfU${a,BNff,aɬ2=i~M)7n*{Ϡ[dXHͫ-X /M+f"/+yhlD|uyӟTuPEQr2xdxrk :z/bA] g`!7+ ooG!/+EyfqDnw" cza6|ʾaa.7|36l /mUw ?; ò![Yw``d Kh;<.T-()BQ^&u 5*%f+[#+밃jkk裏;{+**B{{;0w\Caa!ϟ/S]]R(,,đ#Gp뭷7Ğ={tRܹS5y<477c…ӱo>TWWP#^'OTMСCxW+rrrPZZ?7[b޼y8tnV9 ~,Xop#+K98֭[|ϐL@Džx[7+!+;O 3̓?n١]I`ujl>eRV-fa(Q o7: B Ґ-i"kmD,#z !ŋQ__BS֯_%K`Ϟ=͛kرc0o<,--EooY[jϟݻw#EUUM`0ٳgɳ~w/8̚5 Xt):::P[[iӄa]]]صk.\o|7o~?6nχ={`ظq#^~e^0cG)ŢEb̙3qYTTTફq r!;;YYYLyJq|N Y^\GZEpۚ-G Ɖ=vq-clB?XP|faN "3?Z/ |03q鋘SUT>vqPcg?kPUt|t za5x&`(5#h*B$BQ]8qE x;J Q7=j,d{@rCf!7+ .X0znI-jAP_^J%#--- HOWeyPRm΀MWP% ík梲$pk%HG/,W̩dK>QX;ƓYhRI/vۉ'LntM84}t`ŨĎ;PTT[n|6m´iӰ}v̙3ׯׅ]f :kFhllĴipHr ҂ ^}}}x<+fAp hmmJ,]ӦMöm^{(qMB8pK,Q<<}U%yXspL7|c!|bu2HsQTʺTkWnm;ɪ *K,zWWz{{rT <222=++ dvw rrrQ:}:R ;Ͳ\J`i&lݺ5fχ/| x衇t\k7϶:YaL|uakЏǞ eyQKY㉯~lLku_3^y"ey}o~CɁ8`m6رwc}wG?qa@ ^~x lzaτ9p`JX:p455 Q8}Ӧ|P\\7K,|1 ;֍yɗyr񡫫K^*~\2㙶ٽO B#}}f:kJ$V BJehNLDy ȷyɗyr# }f` s={fd™({ 3F[Yc0+f)EP|9pXAnf2ҼP(?`H!z<֭h{lj(Jh;C55 p\NپD^xY4m=|vk!dA2%R/Q2RP$ċ)JB H1i| Q6B\6=]r2P_)H|YTR%+2EJ 5 Ar$FJJHe1:ƻ?|4 '"ED6}6-?asGJ&Qe݉Jzb45YLJxViNEz4SWiFm%lu!4jx"4Va{!g-'6=Y\㴉hn1I|̛:)O)K-e)RotVԃ\vDv42J!YYNUk˔$OUʒ,=x1. %bRۉċfĪ.>b sMx#l/J458ڂKtRND/^&+/;VFVigs)Ib}Щ<g 6/Jf,ѥbj,ǒu=z~*lȲЊySDox}Nկl~yJ Kh@{*9slI -@, ZwqעVj-VjVnnm?~[smնjZQ Kvy{%/#}>s̼yoR3@s ;o@WQ I*"oVRp/ώp}5lXsMH+^vg񷀾ACF%ߩϬ0HNƞY[O$K誧N?jpsXI$Q1r2_@RRAH^n_Z|ͧ&d:2\mE uw2f ;=#{F/@[lu$\ɫiԨڪ(3}k^ 4Q>Cjdvl}re_/Xp:yߝ.@)޵K}O}SJ$p DwΟ X`?/ k,X'GRc\ֿ:%C tl@J z[oku_|,qCihcGVJrKI" p|Awlh%<\&eliWcՅ0pWw>O !hlhvQhVmFScUմF _ikkѿ}U\D,ю*r_mu ,KXVEtr{p:]8.n&Lll fszB'vCs؝jn.բhwnwbwf6tj&NvC`P5$I %ˀ׿/> SXls ܕfp~/7tv!?O gpPy=|;\`>ToWMK3nǷѪk(yC ]zE:#_"[Wy꯸nJJJK#zVZ՜0k{ww ?dvnx}ދ]:"W(ޡq߻P ۳!8_|y >t>Sç|Q3NY~)ǎ;ncɂ?s:+v\.u lM/! kݹO)D]U']ţ5\uE[)I`YY1L8]n@bA-#a6tIZ8..!x\nmmCL%=::;|g$M߾P$Ih>@B"Ab0P\ά'p+9Z]L^NFdgT75/ZZDK/pU7ٲ0%5V7r{mK9\ѠPyeHÝx6;wm)lu6-v.Na 6n?Hqy=.:6n?J*o}̚8IpEdoq}|]T_QYRp xGy" z޾q0O2P7Gahd|}8ZrɡETVV^x80rk y{/5aHti I[0%'C}f ߷rucu5qdee(_+/58E!㏱d,[㴵Co&/sfsPse\{Օ,7#v}wIq#oEa=pݵ,7O;~:ձ|_QVZ kVd9ijjRKf?PQ7^\N'mmmwrt8K/rcYW{_Ze1kŝt)Jn.\f-lC͝EMkK %_¼Ӹwz:5;~,k(zKVӡu8o<מ5 <>PΩsq)`>CBE]+*ˈ8fN @}IdTfME~I-q14Κɑʆ ]?h?@rDCh|(CU%'(@(x5=i2GSW[7d2rmR[S-6c2f.ne5 Yx1GrSKN'{?l^x?|um,*b 'cW某 _{f &ʏ#3+>Gzb6>3f͙dG_/}s`XlGKJL6z~^?U:_IK`ٸ+;uoů:.^wqNaAZj>޲SN=4^x6,dԩlC~&L&ch b>ejE}ݩ6B~cpE>t Bvs2xTJ;ѥNm%Ngx__ B%LhV Eޟ }Ij5kgdY)ZSq{ǘw`f35pI]j8h^&mNkJ!7f N[va.,KXf'm~>>V™jthimILU**Cp:][sI e58PG,Tַpc5Yv7%ڝpznl6lVci&!6 -BuC)nbc,)ڵw{?LVvbcHXx> xwx)t$_gJwcgc]; .;+yNM1ZPz({ '2kFŧ|Luu5M$%'1"%uk™gͯ~˯Vy{mP-mn7wKxa.;7x_r)_~AQrGpor6Sr0dbW]u+W8PP@\YujZ[yujOaG/`_}͵'$pCf̙v;?Xyz1NZ 6P+Ӧqssyc/C΢Nrq8:XĮ;bKRX||m?'0/O tNZ֟^]ᇅMP  wO!6ł,iCW"v Xu`kK=\n=nϨ⾡ sG͡1Xeĸ!!=i,)N `뾣 ?%:%I ʏT6r 8]ZNZڝ<֗W4p”t!شy 'tX:3mK/!f»;kR9LM2C˃d K:ǏgyBеsXo l0<-)aͫIOOgj4~ #F `ɘ&<K{ƽ ??FEɑr撚3/ ߐ\F0~:d35D}pFAff&YvBWTToeZB0To"bRZZ[IIM%cf͞ zm$_;CE-/ COU'#1dP|= ra6KXklS?n 6:m`;$l%/__ԁ.z'fY'fY'믺"%${لc'I:u@@údFy74L5(, \_k1._C/Ұ;MNG1͟B  Wvùs _~vNʇ}= wuZܥ>C}M&cǎ /6ll63sll>?>&d jpʩN^݇Q&h&ΟOZZ* rPNMed$!lfƎeW\MX,+zHb6p:8vtmoxqp *˯I'k)9116ܓO<믾u+ȢNeⓗkaz>x cǎcᢓ|Ӵ;l#_Z  nG>*](%qHh纺GnGkP LXeuUQ_Шn!3XL8"s sXfjgRl蒌K&#{*/ y 5L6+8.9 o C\uʂL/ګ),{q>9!vWEAQ;t2Ͽ! ˒YsO IKc+=E.'C_T&LVYC] wQ\f9TsxWh?^`H{a_'cXXz:V{RҐeg3|kwz=Vu zYi)ULa(-[xאdSN;d_p%t)g~8+eHldڵT7-:p! zvt!W\}5&Y?GcCK-#sX}to2Ak.kx٪W"m9% y&[p(orebbX1Yo9=,˸B,UIUhXe`ZlV:ۍF(jp곆h [cH. ݁䝷j 0sxkhKgSOO[]gU~J!툆$ؘ?Ÿsi`4OwWgh5H׫p)qw]vオ PM?![3`IL$I̘9C؈,4\|e|m+o;Xӹp_'psOȲI'sޅ!nJ r\}%kc%< '.\o19V\y9$=y2/ ła8IpQQ~R/vJ^x3;H{Đ3%d@oXcee;Ēpi+pq1Odgg35ܾ1idN= [i#Gr(0x=\UC_V9Y={b+>VrA ,ю*rS[G{y^٬511V.7NɄ%3}\0 BlJ.[[|N..njrՑF 2!:,VfYa6.K[ d5hL @Խ-uHlJJbjDzt\WFuym;\ގi ζ@PS__'*ԽĬMEX]7BfXfϝltՀd{7OP_={!6Ɣdf!2lBUegbɸ\. ):p$=i&Ob444HJJ&N\c2ۿ{0*=E'- cXp9k***e =Ū]X@PUU۱?3gawN*+*#w43w׭-c28a֯{{{;,E-[8 SUYI44ԓ3IMM`[kq:R/}Ncc#&LPTTȴ3YEzKh$T.>[>.u!wUx<\eHB<6>r:]۵<I@ٌfE':OՍ}z$ lހR|IjtB8NB".Φma2NK7he,vlxaE1Dkţ!+xjZ )2mm-EH9/w(ݻ3fD.{mcLn\.Æ %su06VTUVĤ!Z@̻r DOIQ%D /}#EV(_vl=#Mi!!.-9J<Я(**OO=/[uM$M?>hz1ݛǷ==hd! "jlQ<Yv 'usK%[48*ګ'6dZ/*Vp]%ƃ|QFI@ם]!б]mۑ$)dIow O"h#Vjk=U$oB;~79Z2`LK0y|=e˄Ľ1|Ǹ0׭tK!jC٪#<Ц޷OG^gۦww`n[}U<mջ8Q$KD,юuu.{/(D} X2x/5/Xt HiԕH{-- 5400t:!nAȗ 4._8%aÇ!46(zhgNHw>s]>67dҝ {vS\Z9_1.ʈsrZ%Y~ s}[#}>躭>*U\ցm9ޯ Oq> '1wEs +%6kJ %P/\Zcf&%@RRҷrQJ:ޕFҧ 9!Df]Ez҇'eXo$l )i}[2V܉FO{&?L a;Ei܇g>}4"ǣbBՉ0HZD\ (M]MGֻUR::{v\ONB#E:PeFArrqS^45tXi̵ċB/De)!y@~/ׂ/Nx;B۲\a1D!ulutW~DzS[. C\t?й KOl%yuc_<:x£IhǣIh٫/ړ0BtG RW(ݙmrFwW__xW^lB%ߐQ|P;#XWH|K7#AAA!#EΛJwefZLwX?KeR]{zȨ>5=wV>кDk uK}\C(KiC#*#'eBz>UgWd\%soo:242F$S rMD;MD; ?D*$) ߸_JJ+/yS7_0T%}nߢ}L}WfW(e+eA+_Z6d$.2f[3x?_y8=Y5Q0ME]ʇLn]AQdY^n+t]>RX-j(IR˻nC`B$JHO]w,'Bǫ"m6ц;=nV@$0Ily=".2Dz;4WKxs-ڇ02}m6+:rGLrEL4Ht|/*tFx7.E`2l`y9_ZQrHx P.+E!$_|( n/@n@$/2<Z^%p{VW#PhΨIJuct=>z5ڳۋuQҟ)a{1-}ùsHFҾs,b,bc,,io^z&]" Gs}V.}zoo9diԛ:0 l$@7[uhUoҷ]ޤA[ENdR nw.W(p~D.c.=v{| $Kym >'f0"9,äiiwO8xx.|ʂ9V̘$ysqqje44yT70$ֲqAGpdpŲiAPvLm8Nm=jCF+`{ڢ[M_=Hx]Q&^m5Ht,K >Hv̨a )CIUM2V8U[鵡NaY-rVUatJ-a>fGh%]^o>(;Pׄpؓ:ԝd2=ut =:ԗt޳nGQB[|;<AIl :4,|kk+2m[Xcc#C $",1 |OE[0̋Dv+8n582åTÍP&لdB$!Bu^,fuU؜hU#%dڸTL̢Ѭ GcxR,Gxq.9Tə' _q!:$sk6 V^ݴVegȒDK1;{6dF~]Lr| cSKxƣIUO=a3VhvԨIR+D"̻t_j|.\EEE<UꉼazNރ#eH^)goᑞo7/ٸqc.sΐ텾_WJKK]GO?4<J~#s6k׮eϞ=Ac=FSSSw(oYY_ ZZZ4z[⊢cP]]x< 6:??G\r%8NEa˖-Xk7jx9N^|E."V^́C=ą^ȝwIcc#555^˗?e}ٹsgu}hnnݵFOgZ NBEZ>ߡ #%bݼxxGQWՎB= Enx+ȯVs E((!<;LBLƔ\4yӹLI #79MfL.[ؑCf2oJ:]cX} `3!7 /et.X4cSX=k&Iq1>wf 1"9$NeS5];{gyB5TxCXYYICCCN:CO-@yy9$$$Kll,{%))M墰vRRR̤\uBrr2,YDÊIJJbڴikr !Njj*l6CcǎQTTDJJ ٘fv3qDFEYYTWW3|p)..fԨQL42Z[["66S>3m4裏dΜ9L2SYYIZZ999Aԧfٴp!TN>dEa۶mS]]СC5Dobcc9s&qqql߾$JKK1L̞=$v;隣l222 r !%??Cvv6#G`\qQʌ3eD;ba֬Y$&&j(Baa!%%% 6hmme477رciooI&PPP@FFv***$==]vԩSIIIA8vÇgA)Mٷo,SWWGZZlڴI'dOrssc$$$pMF^^3a222hkkn8IKKno>=z4ǏbhDerrrHNN6QPPcHJJbX>ٳGMj/$99Z>C;Fjj*-=zI7nYYYsݻq8dee,˚M, SNeذaѣG!55;v͛IOOg޼y,_I]ȑ#:tƌ3HHH`Ν$&&j2Ι3Gks|<7o|ٶm;v젮Ndѣ9rEQ3f #F࣏>T;Fkk+| v]Y߫y!NرsȎ;0Ldgg=CeL&n7|_|Abb"1c CB~~w55j%%%hI}}=\.ƏOFFQYYɘ1c3f #G2e}v晙|l߾zRSSAeB2337nrQ^^"77W<̲eHKKॗ^keddggs}+Bvv6cǎmݺG+PXX믿}ǚ5kHOO箻⫯W;gugqo>˗/ !hhh ??IسgÆ `2??LNNIII>|ɱcHHH`ڴiƲg\.B=JQQvcĈ\.M0͔㡬 &h:Ͼ+(++l63{lx 'PUUő#G:t(SN 97(;VjjjhmiAQԅRHHL$9) Y{/3ѣxx2Cx,θ.v3ihIcJ1')>jl1'{Oxb611C}dIb҆k6>3! }D"BfOcJz:kؔ\pyZH.]91Uz<ы/=>jÔ؈3<'xǓLff&=YcŊ<裤K/}9;x ={/Ǐ+Vhr)Ÿ'bbbBPPPOS,X婪bݺuSUU_OVVׯV>SV^;|Y'55CG֭[Ǜoɲep\p5hu{Р$++zȑ#Ba܈S?ժ즛n2E_Gy+VϾ}Xj'tRPu֡( reqw2|RRR(,,$//UVg{nEs9 _SSׯJOS~mlذAv( s[oeŌ1zy8q"vŋ|r6nHAAB6mu]G[[ xgXr%۷o?dΜ9ȲLYY6m?￟B6l؀^3f0p֭ڵ`ʕ+K0Lq=paɞ8q"6lMMM g}6O>$Fbذa=K.TnʕW^[nᢋ.bܸq|\z饴30ydjkk>EYYu/ IDAT>111=̝;6***8sٽ{7.VU;l2nF~`2(..F \x FEEuuu!2qD֮]}݇⭷ޢZvܩ}xxࢋ.{31j(|I֬YCYYo6*<5\ɓ9r$ڵ[oUٻw/k֬!>><~_ /@rr2uuuCQ^^D>/aͼdggvZF{WXd K,xg)..&55uOp!qݸn͛Ν;Ynٳ$[[[yGygͰaػw/\p\rUVqOLL ƍc?d{;vL{nn&6m|ܹs$}-1ؼo4v͛oIll,yyyb/2djkk)++_wfTWWvڠ^QC{{=z3gOFFeeevjUCll,999O`߾}|f1|^~eʕ+la?9r7tRظqcг-[pI'p8(../7[orf"ꫯd2g֬YCmm-$1|p 8q"K//knh΂ HII8pÇky0aNO7նbme2nj4y xذaP# ¡2"##R]]Mll,dddNo߾uquu4JRhǕ+W8t0luH$팶}r#ɐNif̛7UݥR)ӧO箻b͚5__|\G׷Nsww6 lP,Xһwv4r )J̙Ô)S:;z^Рt:!tV ,wyӧO#IIISA"l~xyyP*Ƈ}mŋL6 B!h.!00%KoͨQ 2ɶk(8qwwwL&"vghٳdeeCٳg/^ŋȑ#vצpr)-[wߍz;f___Z-*ʩv#yR)...j?疧'|ÆaWkΜ?Ό3Pj(J$ j\q쵧 t:L?---u2,IsH,ж#˙1csm7wk4WWWazTqڴi,Y>}0r뢟oS딮蔔Ǝ˺uXvSGh? ]A4]]ۣ A,z5zJ @YY9.\`]T[M7b8wuuZǃ?!Aۿ(A()FccF7wqYKA޽Z^G=ZMϤĂłWVV\\^Z퉷7grsɡE"i4~]aBW&7rzg~R7q8C(Eaf77 C68p #;;K.qvG- :;ƍ;w. :˗3aڽuF u[YYYl޼ DDGGÇ9pƍ#22www:$h_Oqq1Nvv6/_… pHJJ 7oW^deep.L{=s1`  \;Ө-%%Łvcǎȑ#t:rssIHHhgZcdvMhh({aر :TH Օ !Ă/IHHH'LijjɓF̙%xWz6dĈٳ???Ν;DŽ P(qArJˉ'sNR)L4={зo_?\.wscƌa׮]gƌ#hBRQXX߿?{رc\rg믷{ի^pYr>|1[n8qÇѣGy饗ڝ-Oٳz~$%%7ȡC=J%%%%dggc2hhh \a]\B޽_~zRXXpzFF9rD0!++A9r$'N@&qyua]B*r͍f3԰o>a;غu+yyyOpa///݋RdV3_oooj@ǓNԩShZΝC=͛bʕmcO`` l6[1}t6mDRR0}t}Z06l6lo߾dee9`G8Üʘ1c&''L8z(ӟXz5QQQ\|ݦСCÃz:ݖik[د;؟`kXPMZ :[:}g9LFjkkիѸKHHHRv؁Nc̙ D">|Fý+x`NfBA\\>>>8pN'ݱjÇL0Apak&4zСCDVo>III!&&D`` zDӑ@nn.\t T?T*eΝhZfΜ'!!!ddd J0a/^СC"hel}dO{XhZF{Ƣ9@@@MD"aaaTWW O=Lmw911(N8Avv6C aذaOjө`DGG l4{pss#""JҐ<( ZZZ߿?\.Lʰ~9www"""8z(Of̘1~xzz"8x ^^^ 2޽{P(4jbu2EXX\t'"ٱc8t_3j(N8 aM& ^Ց!eyYY<@jj*#FSXrss$""3f8Xk׮qxoILLG 2NGxPPW^oe׏@$ ;w`00{l|||R\\Lvv6=z{ <`222DDDзo_BCCijjbHR|IjX, L!SLjӎ G!<<3p@ Mmm-}tٓp 9z(ƍcڝH8ypuРAt֍LΟ??NDDTWWw^d2<...GVVDGG;|S޽G+"Ʉl6 d2ATTF???a0e0b!66 .p!:ujN"p#x툈bbb09rH'ۊ_i_G???233eQ:DQQwy'D"{=4t:SLÃΟ?Off& ӧ#J ѣ8qhƌnJR9r=={N:`233۷/OFTcRj$%%``ǎbx ᛩhh4L8T@VK޽Jێk6R>L߾} H$;_:NBXH=tAgBoMM { >} i8dx{{ eݜ;{__uÿU{Ѐ^\FM@@2kR\\^5}-5Hyy9ϝc˖-\|Ym]zw܁555Kt\|ӧ@f^+Whhgԝ c>l{v0Y֏I;Ky*m*jk|M`ER2iMwusCT[Sm(+w5AxJ,?fk ~n }2qěT[7s_"<<ܩÇ9ƎKjj^x~`~o_5?zo2h F}CF}^ܪh-;0cf+lbg=̙3j///6|?aeHbZՆ lۺU0ߖ0y:u*gϜ@,УgO*ٽ{7 vNmBHNNF;(.ӖH$|g 8Qn!L~w3|a`rP(ظu'>ެYenw7 LnnK^^n IDAT%f`ۋ0Q/ ??//U!bfMm79n:ͯW\\SS@~NL:׮`$wv#,]!*ڮ2?UC\\\x7xyK_`Z__O}}=W^%;;_/_H$pFji&x2///4 ߿?o6~~FIK:sss} hxkL&KJӯr3=$Fݝ,[`@s$Nt jB͢u[mUvG׿AGfݻoh8!! 퉊bժU?;~Z[NmX[NK,iwf'e y /MɯQXȩ "x}Dŋٽk'_dii)_ ??j8?YYG(,(v,-f3&szX,&3F nфh (UVG6Ͼ4l7X=ZrsEYfͩo 7s3{ cvzG:LBg,fY]O,fN[Ql׼OUgy[V7V׏[Vv^G뤍-[ME"-fX%$$&Ŵ3vy8~ Wpin<ŋChnnF,ݝfU[CZ]%`p"FgV@R9LY{g1Ǖv_Oߜi쟿ֻ ! v7z}fFMn~j?JFq/1qs&nlltf~]յӺ3׶ ~mOo~Ҵb^eD?`;ؖSd2RIpH!! 83yKUjhh{ݻc1{E:axLg\hbXs`$Mů=ھqd/(w髛-j8Vmog⧖qc:9++vׯ_OAAAϾf.]z|27ntgXؿ?ǎ˗+pٿ$O~jdi3v~"5`_}ϬZYf1k,|I+8z kƲeˮۯ;XCb̝;\.:gw(? 䐛Ν;gŊ>_WWʕ+?}SG@۶mkۛ7o&//o!`O_^oݺ+W 㳡>ʲX,NWa`0rvy:?>k׮9}lj'HKKسgCئ_ 'Ol7^{5?0f ? `ӬYZU6! 30yU~Ek('7h*~~~\P# 1аcNmm$<đt:k~^`;߄.5?mM97ǔsJ! Nٛ8ooL&׮]#$$fjjjV/:(ٖH$\t RNʕ+z Fcc#HR9BLL `#$$DpldX(..///yilliT]]ϔ)ShL&c׮]l۶~28%%%pˑH$TUUL&N+&TMP\\D"!<<Lƕ+WB$Q[[bAVs5jkkQq95jt:JJJ0LMmm-455RPTV F"PWWǵkא儆P(v JBCCjZc*EDDp466r5z=BȆbD"MMMxzz שּׂRIjj@klql󉎎hd2BCCmRQQBHĥKPT466MII xzz&"88XVPPD"rhhP粲2<<< <@AAQQQ+bZMCCr222:t(4 CP8(JYr% 'h~ݝzhiiA/qJExx8r"IMMeɄNQQx{{lիHRtFbb0gE" J1 y饗1cFKpp0L8Q` f3>>>PSSVGKK W\b&2jkk5ӡ YYY|̙3$''sVaFZFP`4ٷocƌIX[\BLL@ѣG3h Ҹz*g‹PFCtt4TVV` ooomH^-MDTUU [% <lڴƏOxx8Ba"-,,DRQUUEhh(_h4RXXHKK s*++ٲe ( nZZZ())/@$q5֬Yôih4ݻNbˋI&qw騨s;#^|EVXzycQXXȥKtP{{P^^ /@PP+V@R__Ncҥlذ4bcc+VaRSSG&d=֭[D_b;lS]]o_|lٲٳgINNfƌ:uL&*++ӟDzz< nnn)l80uTOh$%%> Xv-G?}R__FO>a͚5u]\v+WҧO!O?UVQYYISSdgg;aÆh"yt::nݺQ^^Nhh(YYYL2r6mDQQMMM 0G}W^yt:?P'Ŕ0ƍΐ!Cpww_~9sPQQ|@PP"yq)={6{ɓ†SO=EffSo؏;wr!$ ׮]cԨQwcԩ;z~7nvf`6ڵ 6Cii)AAAL&x7Q*l޼|cҥT*b1)))דNSSL8QGmB 00NRtf0x뭷P(s]wr֭Dmj$ 'Ofȑ5V/V{t{hAy$}GWR 8}41a֭[ܹs a߾}3`J%SN%$$M6PYYIqq1/uuu"\xe]3-[444੧rچ[O/,fܹ׿2fϞ?uȶNss33g$::ff͚ 3K/ SNupB <&}ѭ[7~ߓENNN燛ҧO"""pFBBBؿ0nXp@}rw:x+ܼy3s!88e˖ lϏgrm)իSXX? JYlԩS6">>˹sXd gFR1c ؾ};9991'N?¼y4!! l2yyLkF]]s~b.\ /{%33x]>],3m4ӧ裏xGضm/"޽o={Ȓ%KCq!?RX~=ΝcС7\IX,͖΅ivxهIuU5{R1I&qIԫ̙GAOyyC o~Kj;x9oWhEDF3s8xhtT運\,Ƞ fNi(03v$:+wR]n/ֶC[iqr9?+W0tP\B^^y7 E,$"22o3 '&&Bpp0yyyӯ_? |2³gΜa̙rY|9vE"ӧO)dٲe|BJ>}f6*/r=w}Ǽy?~<$::ZВ5heii)NAy'>|8g477S__/PL&A(#11R)]vw}> o߼yxa…*?8|0=`DT*, bJ%=zp8=-Z… 'Oraaل+777bcc׏4x FhP"##DVfҦ!b8-bV544h͍8+sP\\̇~ -oߎJwy'Oҿ>#X]w@^,[]t+V#X,ח3ACCC3f77.wҤt=H$/˩`ǎ%տJ%"H0g7og߾}L6޽{Vٽ{7Vj'ӳ~pYD5Jx ;끇ݺuC,c6),,d˖-.Ϟ=Kɵ=tHx"ƍC.3tPT*UD=CqEf̘+o6ᄇFqq1 7B(O>rIDDD =z4[lqxR_~|wDFFrܹvak.x?~<>j,D"---Ҥ#qFJJJx777zH$BVcX0 īj5mrd2r???jjjh[nɓ7gA"0x`b)My9r$< C ᩧh4j*;&iotz=U5 s>шMʢZZ/#™7̛?kJ()*ºATAdT'6CS\άGl&#խO=L5oḽߘywښ1v8Ԇ1c19{ZXlǙ>q AI] /_n:Kwuu,UGll,7oV8>|8rR)|lqڶ6Sb9vX9LYYgX&::NG^^N2gՀUHؾ};BAll,lذk u8y`f[\\ҥK0 DsΝ///N>MNN"!!Jٷo_;ՆW_}b|||vmۛr֯_6 4ݶm`k777222gŊd{!55V^ͺuڕˇ~hD&총… {=z!'Or1,aPP撖ƱcicYYW\!--7x))++ҥK#89u'Od93!C8}4l߾]ӧERqi>|8-h6:BcccmCMMi<ȥK~[HRΜ9#mjiiiAV!9DVS__bu*P(Ŭ[ibԨQrE֬Yi^LFuu5?]F\]]ٻwo\o[2tP?N~~>Wvkl___R)nMp&ӧW\ 6lq=ŋϝquu;dժU 6qAVZ%liZ! ̙3xzzT*IJJ;v={twNmm-/_&++Kh ܽz*˖-& шhh4V6LU EHEfƓ Q1hwl'qw:>gE>`ILJ[9sDmc 5- &4ZM-zj[hncXhilYdd2SפEO}BLq&Kt_bXhihA3`X6Ь5Т37WۨEcWM"~oJ1euu}Y'~,xłi;=IINN&**z&O; 7ŬYpqqTm&fпH`` u]DDDPWWԩSQTB9ќ?kҷo_:t(񤧧qF"""dr9fܵk{N8!CD|||s .BHHDEEcݹ/$'''B||<ܹٳgӜIRYj:T*u]DFFG}}=k͍֬G}ZMUU7o{ߟuq)MFϞ=h4x{{F]]aaaSUUE||<111|0sL"!!AYs? ooo~z.]=Cdd$aaaЧOD"555DGG\.T=z4>> nv<<<6lAAA9 /@hh(=zȑ#lذ~칹QRRºu눈`ʔ)̠FAPEcc# axx8477j*;߳gOXz5yн{w<<3g:^"2֭[GZZpnذaZݘ9rqѣGeDDO>Zq ᣏ>ݝѣٱc۶mcȐ!w}H$0`@8zwX5L6mr_nV53d~2ͬ_={b6} 3+Jʾ.>>>TUUi&eȑ466 nnnjyy9  nnnGܲ2n6"##ill>#<ضƀUkglB޽INNxvl( >b((6D%@Hlv;3v ;{Ν{9?~gݽ `22JX}ZXIۆq"dC_ڦØYȶD9lJ-wQ= = #Xm‚HH͇BFnR} 6ǧ9>Ƶs-Na6H(`꽜8U{i\'8G!.ǽygKz8FXKű1.NYo8o)Ҳ )x< @V~1L3_cjϧ\*QxxUj322Ԉe<ʲ+edR?BNvXQxU!?EQHLLdܹM?жmF{1x`"""5kM4l]~4 ݻwg۶mZ)S0o<$^ũ34o$˔8]j02ʟ P/xV(yԀ2-Zdo~Z]nckad1z%y؈<8ry?/ e>v,?ׯ\7;t~(qEFDnde cR|8 ()*V*{PT"Prjad"YB& sѱY Ex]kڽg9A dY!HzN!g 8~&èD[1iFjE LUTOX_F;6|A"BIJecwӨkc{Л˸nul%wO K'5;]co-GS$ ֌eM lu|g%2lٲ|x9'7zNgiZ`DF+(Cx9]^kƿr!_%jv_s='V.ō>^x饗p: 4=z\o{tt4gfΜ9DGG=<#W*dOŢ*?Ͼ O)j>ϞE|C'Uwe{nMFB\=ʼ9.|K@aaԫ80Mns+EA`QQDіW-K.ϙUG(:^@qV5"DލX^Nn5J@3Of,f=yv6HfDpvdX fAGNL:˭ ].vA!!ĽmqwL} Y^@3 l(Xۘ(lΞtZQ@;$DA3(; 0Hϖ:y"; UUu@w y+*s!P^| u+y9mz³^???-[V!B_~x<^hwjsth`0T89q+EۥB*;{mܸ1ӧOI˵כNּTe|_ nUU];0s'..4M_+ފv7S( :tf/5jR#pE!u[JPs."°X, 1JQQE|*$+,P851];ZﰑTAQdAF*TSY8%ᔳK&*]o`k;$ eV[ uoFJi4$eCLu\griAuqZç1d:+ه^x}ą&\{sKh2\M9^ *_*W+I*Or^/zU⥪ӫ/U^x0<2@?WZ/Z)HgXf59ԫW-SVmLf? sۊ9؊Nvh"ӉF!=,Ȋ`~(?=F l 4EɤJڙ3$s`^%v Ce4"HN': ўs7"kSS 7zS&J'+ Y_rj!. +lѫ]TuωSYmRP-]1*p݂?sE6^ZZ-dZI=[@Tj[3ԨfQ@qzrWL}Gjimj@GMzznȑSY(Bj4 .j7*ۍDԲWkײnZ~^k$%%aXUرnݺOzz:QQQWʣUo׮]j@pc,11իcq8-r>'gΜQ:tHMx|K&{nZnMvv}zsqqq4iy*iڵt+ĪUٳ n<;gϞH r`ZIJJiӦtlر#YYY竁jؿ?W}ܰaJ2cZ*tP'aSZ91edVW$pFFnM8ʓ\^O=  $77 K@0 LR}c3cƨ;z\PΥqI;}?F<ܹ|:w.wtțo ,.hg'ZWUQk+{xgxh2Q Az#c+((Z"%YSX4t‚\y[M55Р8NށVn:Ć}'7s_k5_#\e;JFvA1K~ϩ|bV1(ƑDx*rRWEew_*K5W=bdffRPp$6Xx1.f͚u?߽?W+ @^^YYY2s ^W9%;;3gΨi9&|D5u_|ָBSN0?s]̙3g+<;;v`ŊW7Μ9'|r_hJKKSBh.k֬aΜ9:u]_CKM/r5q5 (^?Qttu|8}4+W,#== PXXl ׼( r'2SlH(RRh.&NYrpQ(PT$9^q11u9~ h"ґDGBb";HK$*r}2L{oS w`{yGK,fC BYK?~uy~*} rB_.?`]6^N' ,@FAϞ=1ckOR~}OѣGi޼9c,_lZnرc9qvN8Anx8x QQQj7I0ͼ;_Fƍy'8q"֭gϞL8={ЩS'={6)))s=<Ü#..UVIxǙ={6;8#< /,\uϖYsn:N'g&::;L\\?=0zhl?kw}!"z\&M7D׫oDGG /|rnVn( !!m۶Gm6ڶm3aÆN'WfҥԨQdny…lذVtҵkWA //t:V^0`Æ cРAݻƍ /Gk.f͚V套^aÆ :>}:9cҼysә?>[nE2|pEaӇӇӧw^zQrM0cy:u*Z3g2a-[Ɗ+hԨ/uֱl27nOٙ3g˾}ׯ#;;/ Vː!C4h۶ml63x`LII /槟~I&K˻ɓ'}Z~!k׮%**gyƍȌ3wȑ^=j__|޽{&!!m۲~̙Czz:}d{d&MDnn.={d.o^z VKڵܹ3_~%?3!!!+4nܘ1cХK~7{=5t$IܹO?Y=z4m۶eŊ߿D2338p ͛7~` 41c0c 6mD֭?~<&#Fжm[vͣ>ʢEHĨQС_5lذGyu1h vĉjߟCp8oX|yqƈ#[شi& &мys;ٳմ5{/F]vHw}NJ+&33}1qDE`(WU=dEQFo'P<VdfĴnM~~}ۑe3Osp"wu'A=:=A5Zt^ b &$FS50 (KjYW^e/ IDAT fG}d" P=O (1 2Jr#ȁ){A# XLZB=M\ԗueK`ٲtu_zY/ a{| .֔Yَ+';![o7| --#G0oswy'˗/gȑݰƍSK.eȑ,\͛)))a 61cwѲeK>sdYyü^z7о}{?w_KJJXr%o#F 4i߾=.رc̜9_~+Wp88p͛7oW^a̙׏?3d.\}Gll,QQQ\VV۷k.ٷokfX,bcc1b ,P۳gӦMW^>ƍ۷/SLaR&L>w3f`̘1|GO?All,?:gZ]Ə?ȬY2d,|Ō=X6m*7p[LBYf VU^x͛Grr29_/P^=,X^iԨFO>aҤITVe˖1ffvEQ+ϩSرc'OfҤIl޼LHHH?/dtؑ}`~ibcci׮у pA7n̴iӰl۶ckb 9{,;wdtEݧ~ʃ>Ȍ3HOOGQ֬Y9kq"##Yldɒ%(²ex'OfϞ=>}m۶c+##]vɼyhٲj_p!{/g&//u72(R?Y.Ufpo$/ߞ%oYQ kԬGݴڷg̘ǽ67mڈlz QԠ7ju/At3 Fk@`4Qvm=-Hh$MYь`B77 h &4:ב&@x{Xԋ#_ klDexUo\~e/WW-W_giP߮0\nԩSjErrrTA??Ν;7nFzcǎLj#رcqqq:tӉVK.DEEh aŊ?c=Ʈ]ذa{K.>e#GРADQ$,,lf3۷YfhZu!ѼysV\Ɍ3xWW}m۶v9rt6x`, E֭[y'0 <"kӲeK ԫWFcܹKfBCCV{zlݺ{ua5k`Yf>]4 Cl6믿f޽ 4uҤI6l^]vL>Ax%Z8~8 4`ƍL4ɫ۷s15:77sz=iiiz|Got:JJJX,<Ӿ}{L&;wfÆ 4oޜݻwӤIf^zzj;$;wԩS,_GrYZhE8u!!!lڴ.6mbرznVO/$I8qZjyYlhݺ5F.\^cyno9zOW_͛7h(,,$::_*ݭ @Ϟ=IHHӌ1۷.^x#G-RNt{QFHNNN]4++ ޽{駟@رcdҥ˺>~x6oѣ1 :/ҫ]^O&Mxw0`p=.. &h$##CM~1())!99u"cbbx;w. 8} @V(z{bѢElݺApr'44AA?dժU N'z1c@\nݺѬY34 6 wߍѣG 222x衇={m6655UGnѯ_?F#QQQرEqDQ$%%E}.Zm۶!W^$%%F@@=zP;3sRPPA)-G\,{hEY$oLGuE(m6@!<<Ϝ>nC28K?}U~׻Gr:(.Օ?v%զztzs 2 :lV$Y() ^ߑ~ a{>b<$yOKJM~~~j{6 F(j ɓV۷'))Ih4"|Xt)=zv|'ҵkWNdɴ쮎T5|Y3DAэ֭[3uTvɴixWi{2w\wN=_پ(8rU6|VE""KV)S2s[|`0йsg$עNQ( ( T ܿ1uT5u@@z]֭[2e SL)UYYYGu334i҄_Mpѣlٲwy>#֭e1L^rriZ5{}уUVa6{Xl;waÆ*Uc2}{w,^-Zx59sgi4uNq:^ `4ٸq#K,wՋ?\݂ {BѴiS͛lfС,]tq8Α{U9KEQe$uIkse:0uTmԩS8q"jY'n _}eN:#" ϓL8 yEKtCAtgٱ.%422M6o0w\ƍnN"nݺ3@`` C^}ޱcZ`0Ջ5kyf&N\aO\o׾BCPԘk_J-g3of ~KTaal":5bii{&K999X([}6Naټ$,QZJaAZÉ?F٧%(ߵ(ߕEUWeiYgߤ_*KeZ<1 y/Tg#~?6gK@5p8ZnnUV8p/@qq1<#q yıc0LtQ-0j(nVۧ^/IEEE굢(ҨQ#N>Hvv6{k!ͽK^^ СC(...GwϿ,112p8\(uAF trquahW璘ݏ-Z`Z V%epЯ_?Ξ=K~/4hЀ-[ҵkW{EQF8NS%e!:˚322رCuϞ=HIII ` ]vުU+rss رcƇX۷oM4W^8Ν;s ֮]^N9mѢyyyر#'O$>>mЦM233 !--/Ԯ]VXA@@@9Pxx8z\gviӆݻIQQ н{wX~WWυ|~~>6mڐСCѢ(RN5{h4,l߾Iظq#;wӪjtҺuk ɓ徯& Iqq1dffF!::BBBB(((`Ϟ=*o?6֭/oݺ3gΐm۶Ѯ];wNFFnqFshЬY3 !//{^'N'[nUVu]LINNfTRdUwHOKsC_N?--\v@aa!?\=q-) WNCѰ{n=e_[ٳ}cIMMEeweyںu+<#2M4pyYfv,  ##BCCh4hZvލj~XVV+۶mLiZ;wb00`>7eܨe6Fne?AZt>Zbri/n h:#zBuQ@`Pl;7EE$xq*J*j%'')Ȓ[J~^.IU5SQd$IQ~bH}kV5ut剪ԗ­gU4ܓCآE =ʈ#t!A?(b6iРÆ #,,˪w>L6}Q/ݺucٌf_\/^zoK˖-iժUHEa;vp"##111L6AѨQ N0C ?СC9>kg > ÇڑA=K6p@, ?0:ub̘1 2º|Sh޼9O>d9~Eᣏ>"99_ecǎ|dddгgO~m222h׮^1`~Uj޼9˖-cݺu4mTO,3uTN>MTT~~~9j6l*/7$IgϞ^m>y:t(| III4hЀgy\1h 6l襄уիWӬYr5kf`0p;3"u`ĈeҤIsEԣG~i>r hÆ 9vƍCѨg:t|Vl2ԩ{SN=!C0b}NCVnnV}1gl6:&ɓ9{,͚5;:O֪U+G͚5U R`` 0@W_VZq-S0w\-<,,g9}2o<̙C`` >yYDGGRPP@-m۶+ӇGyiӦ1uTx}( o:2qNQ&L@FFZRH͟?_Gcǎcʔ)CӦM YfwVh4cy[$IZdEed| _EZTd 5茾-W^^qs7^jr-ZPw>]dZ,DԨqªصk5Ϗe˖p8>|f뼨J EQ?磏>\1T{s5xeիWS|)‰'4i% YY|9^۪xn&.6l`Æ L0zr1{lxyũ34ot)IXK<(t/mJt})[^^+!WNjԬɣ=^o`YZ Ͽ@בǣ#GVXFChIIIԩS9°p>=ڑuس{<ŗ_cNdff{qa~;~2,v[owpF:jq ` bIu^EV Aaۦx񥗸{o]U*O_l]ƉJ}9=>>#G^(4t:q8S'2uBЈsAe*;#''y衇Z[7̮6l T'r|_ {֭[O?}u\ _Us\.+mVxn&n7:utY$x~sCPs+1W䲲l6<+h;o]`F=6ڵPpp[Lko,wK :er'˳ʗA@ՠhY&##I)lu%$ IZ6mhhZCMBII+Jz#,,Wu\HK֭^U*aeӧeppW}7bӗ/bkڴ)W 7rUQ/vCݺu+lB/FV5j`ԫWR/N:ѹsg5ӥw-Uq\]IXYĨA}n4h*;|S ^S(UD^opE %%HQa#KNJ% L|\A (8G<=zj("KN%$t:]2׆?QW +2ZF= Fū(ow4!ժ#d g33$ ͆( zYAQdWyAYbGpp"#" 5U}y\Nم}Y<-@2r,߾sOR]FE! 0 .aMsPZU ._U7eu7/QSVq\_INV&RYA?K rs(.*DQ\Bшr8r0SS smXTGa^>NIdffNL dŕ'ZV\(\X y9Y8^VCr:()))pt*RR% K@jբzXQ$K8<k FL${QbhE׫w[jm^^,_b.ΕrBy/|UGUy8n<}2 ۱ PfdI@F(2] K^w_ț"uxVZK>ۥI<~@en-2eM+\᫰ |/99AFFO}eq12-č+<9]E=¿+.]\s\ y*$$Z]n:\"%jV&Kz4z=N֠sA j? &?3E5L&J-w.Z:`DEР 0YTeQKI gYB` AXBa0YSҸ\S]_Zgߤ3eUn]Bs{O<|ۙ?rn'L@VV̙3,"\|u^ Rۯ^_)4oå+۵W*Ǖ~ƮIJTuzU⥪ӫ /K٠7.JVGӂ" 2@7T\  9؊ t-E8%(LfBBPHհU?(Nr B',"jի( ,(:NNo $j %0EEP5d7:QDNa{Ul˓ֹ&d剪ԗ½hPy(,,$%%?Fþ}֭O<~~~Z ҵkW5Yf/_N:uhӦ 6l`ԩSGba׮]Z4;F5j:_yѺukN>̙3'N:'OϏDv)Sػw/K, ))@Eaڵ̞=P΀8rqqq,XX7qpb7sr؜6vYvTn2Odr gط\EkVZ !=~?@fnaA~m"i׆S3'e$[EPS+4bMzbo8Huh^/<+>(wGculM^6‚Ѧw4^G˥3\i[lK?o<вeK8q橧RiF FADD5hЀD^}UGSMtܙE1vXL&j9s&Ç5xzD/U'|UEܔMč .{D2^BE8"^Q8[8kߨx}`3:3&h5xL+n)fڏ >A!HǴa|W3]-EUCo-fOmAUGHϳjuL~~`:|2\\=|Uqs\ܔ{qSVGU(8RBq:v"rtexmwQU;3$*;" +uåZ]m{ZknhVZ\7pc}$dO&.sg2$d wyyIzgʎCL*EӍ'+=TPq~&ir2S4cM x}*im}@æ>p2յ-,{tcv+3Ģk(v]Ca! Y[+ ޝpF卥됕e ==ݼOII^lu8 2$(-33Ӕ+55L`ƍK >v*y"٪;%b4FlCYOO0 =Q;/z8Ax&AD',NhY"AՃkq(sWG aO E! ZD][hvz ~HTՏŋf'!h@m;ʳhv;d°R0M^bm^byv:;ZIO2VvEt<m-c[|F)HwrYc mFaM+|ViE\3!6TU{="2쫜8;'hmmVɷ?S)Ѿʒ\uGWn傢LU#/oWNddgR%=op;y(0ڴ6=x3ig/[FA!hhd&Fke3e/nbc8^.Vż)<SL7 3m#dք ~y*?u8QUk[ٽ#ohu"IՊ& 0/@ mႈX T:\p444ĉc6:V}?VX'D!H$,z"ɒD%$- y^!4gLhR"GJ6UTNl6Uv G{V.g;~)x~<>_Ezp]HY:8=~lB`# mO) -h-r0gL)v4:QU~|in*CUUFpv7LoR¢:K3ڑ\>7x~~6ņCa>ʂ,q}n*swH [<, *rXzD:(-bha; >:DM}+OpءdgP(`Xq //%c+ Mcrf9\z>:DI~ N2gsiFޥOxc;_U)N34AP8tLSKffFqÆpINF*Ês9\g30mke t .04&222QDcxOD SD)$8!A 6*<-*@FoOތBAUP39mLȢ k;Q m0WTÚ6RhgUIZZjqehY7/⁕),\ᠥY̥8O)ZkqcwhbQn:#U?C yug ŹsղtHsR9sXBIEHDՇ=3vbKI#tS46]&1;# M'PYKeqI>AIA&LuoF'L ;0Cmi̚P%}fMUb~Bj<8aŹfؘ0o/(7UK^ކ:#rh6))P --E( ~X`SSaꄾ|]p3fg80IﻭH$]bG 5A9 -+ȸP{[Oo%Rv=<\7vVoz"*8'`9ƤIg7t{mn e@sƴ.U9oz fVaZa$wd%]Cm:2K;hеLPÉ{ *kaX,<$$5F;u]Dlw_8[uK-:GMzruozts[z[Q]*RYѻ0XFzE?ز$:=iH ?ۓaRc uӚ# hiz EЏx 10ʀY),0tH%@U1fZXèØBn"A i|FMf^ԐߝW@e}֑H~JO$YBW+~ZmJX{4&aZl2YևGX!VYB2Z nc[=#z ݢsP}RtIt-= ?lWKQt#ݸFPzb|^O}K B{&@D'm;ݰU(z$ "*RO~U:{u̍C'D׎\hY :+k /Z2C 4B}3C?f$PKH}ES*ApGOw =H$:yC5tvCBc|t GJ/\bכ:OP[DU":g30\={p!i',NO$YG^9L8kotҌL%jΛO5HTO CiEAHU;PJ R*Ea.Ze Rbq(>&'f_ IDAT V{J_AhnSAqUUEZGC`7RF3V}oJ)64E/k@JQcAh'ponI%s$1ݘ7J"eP$}¦}۳^& e\mVFwN%Ht 4?;Ff9^UYUJsYwh~ h.PA? UWA`4AټveW7l|Xavۚ7m'92d)O@#k[k .>^x#pAo^y{zM4[I%k*$Nsd~ ~ mOaL U,݇7Fl0JcͶ0?܁pNPPLgI4 eH-}BO58߯3h&A k7Fvq;`q=mTw|WiJTS֭}_ fȹ4jSЦl6K0͒H" `eL#1h 0GЌ O"M戠T#uz&POHbZ Pv߯||f},'@ӧ ]o1E60tOmD:d[zB e_C(,)QQgOa-{u94~뵔F7[ YcEyYXl誌Ridf~YbAy:FD$mDI9R  SPU}4ΦݤUՇ~)U64-dh_`:SHRfGچ4ZS@ii?SQTN!h nڍ GC?Ϣ0ҰL?'<.'(# E^a$wd%]Cm:0Th蚵KT!_^=94DX| h:uCW^a&a>,&z-'ͺ>Vm =$H(WwogQ$RB(z@|Rs+#2Fפ6EȠN,LFPTQ4n#҂,Um<~i3v C2*mvӌ5y ? #X0 Sh;1Q>@h]T7<+uB7ηGOWz"2ZuR:WTan"$=GYòNbΧBG&z2(L"Ă%)U3B[LJ6~ѐHՈvR PY0#k2G hR F )KO2:UU!iSUnSRæEeFXU;j&bbjb F||hx ;o$)vJt' ZW\|% uSă*Z-`4tqLQT6+=s稇D_ے(6K"$ 7[_E`hTc AD[Ac)iRqЯF5ja$9?Ƹap.ǯj|6}jv*(ېlE)UG T͢0y#*tr#R򡪐a|;i)6Czoؼ -N:\^ I𒼰}~v 3ն0nXa#<-hyOwzoxE;غJQܥNJ&hÍ6{Xot^*M/*-(B]$ K`Oش.x4lMFOD9-=uwW/m$H">@#&Ә:8+q$[)C~U˚=U=M%$`:,D5?zg% -83d1F!ll9T-T-1i(QY |^%]l{so=Wmwg[GCoL`ݶj{m\~k["3{m5wM;C,N$ﶲ'.#勄kYݔXNFph0t DCڊ ?MӉf#+3ÎWq:~n۱"4444|:]tt8IOO'3#Vrrrrvvt: 55#AK&ovV6 EVfV$---HΎ$`nxT! ?_kkmFVv6ņt"$%ANv=}*9HUֆf#7'Ӊ&%%̌ ߧwuHb0!޶d_9}z91Su#ڭ@ )sK*crhJ @p*?[3@*P4@]̝NP+Pb֝{(ӣƣ49S`NWogwuⓒaQMRtc78w<춀Hs4|~b!T˛m(㓋yq}X@[7G)+ 'tm>>wYĚm|tY+[qIU\=<%VW_=T!8/~'Oyyi*VYKګŋhkk;wؓOW.mٻo?>=Λ{.[pk(*;o+_VWĒkLzz:,^wȑZWO~p}&ZZZYr[~LNv6+V~tϿ"~ͷ]|Xf-YYdg{={ q3dH!6o~oOp=7z;bт ykjkX|/ûko/ȵW_%Ym.o:+k:kJ9g7ۍ#%={g~- --6me斛>Ũ#Ivv6/ş|9/cIII/kxgxy*~_Ua*^y<7WyJf̙Ǐ]^p>k^}]{pq.?aut^xy @pL^n.+,B_|)ne6o'^ hnin63\召Z__o{eůKm_7o37'Nb}~9=T|I$KWoGB@-҃&mҏ>z%#r"4?Vj]QpD<)i5>jZaUWUCѦbJ祩-t8+nZèq1,u FEfֿ**;wR% )J8^;c xc!f-##mh {wqC{N^?W=5|ćjY0]04[ɔ%<J3ٺ+CQh$?3H=S$㧨կ2]+`r-@ EZw=o\HKKe޹s37.&N0weCKd"c 8JƌIZ67eDkj(+-e;ģ oQPPW0k拟']Ǝ$aٴe+lęQ,Zp!-{xeߴٴEf_+?0j$eF{o6cl6K0tq@KaСfol^/m̟7 cE /d]ҏ; /q͕3Ʀ&>qu|7Q^Vƨ*l6{ Ց;2y7}e[>w翺 pKyc[&mrM<x{FFM8?Yz-gN:ۺ'D8AfAG2kO ͦh7fQ)jΟeO=(Q1:^QcdR AMmRJ;]QM#6E(፩_56z?_2 h%Q̉|#T; oTF:)*.$\Pq={!3'dKW)499rY*SQ}SQn(cR^ڸ!*9؎Z0fB6w( ?;%cDi7.ѥAuu&^"{7K(~_!KwUKr F1>Ty9#\~ܰZձ5xL4LOKcsYKlڼ\@Jm4Ɣ-i %%vA*ǏgY38psfR\TLEE9ol7v g0~8ΞѣpkfrVGKNNgR\Td%\5m힑7T@_kCD٢٤mXCrV׾% )d+Ps9ٜ7o.V}}::,Zp!W^q9}t]{t {{}P)[?s{s ;;47pgo!2=.VQ63+3vŞ}=s&U#F˦-[3k&::NV}gҒ ~>oN1b8߾N?4nߏዷƹzI"D65sBشPiH( B((vbcsرl(&r>n(6mmfn۴5|bݳl6"OaUn&",;3*|m:O@Y?si H6_='~CiE)2r8=~RdV98][ٽ0;Zr)p]5n=!O7#,4(3*`UCl.5s'U.;(-DSǪw<&F 0;}m`Z.xc;[ךuߑŝDe 衰[GM!] A¥wWި@$ %BVN'/\k"++0~P[[7~'oXB*P8s:6o`W5QU#O?FVq޼tu1Ҵ#G„g07zc0nhRSٻoYc_[’.5Ӳ23%\~Ŵrp\477W?^Usǖpǖ0yDf-uu|뮯6 faal)fsp޽ѭ+=`h7O~kw2ɧx{Zr9G>ݮkmmEJ3Ғ:].3Μ93J1s̊XPUɄ3`򤉼lڼS&sر~>:x\@Kk+٤X*^5|_痿hҒbF Pu zoǽ?I,$H5i1F 遐 Z!lZ1SlOٌ: GY/c HE M"|Y kEG19ET1d1 cv 5RTkЏ++΍QmNoKv7O܇{DLYĦ6v2d8/edXZ[H%5-8 WYHEq+xh rҙ7Ђ%xHuؙ Y.^E<& /b.5Qe\DN:oʹgpT@-O_ΖQ9sƗ[~H /+IX0Hmz9^5E 6`YkۿK_go#7V9Ρ{^;7n 6}())%Uv3o8vwUUYreTehcS?הvŋ3,>ܾ+Ws.Zrzuםn]v۾c'?}Ai]mUoxn8qd),+׫B'"[SfF*F#|?fάY|㋷FuamAIf a)֦mp7 /7v4eCKy7hmmsgͷގY;w{.<<[.h8x}>v #?/mw g9ٻχىDA{GYYtvv>rys琞4ڷw~|op޳N3|y č\x1_/I$1 0&t%BYq%_z=FfV .aX?+AAP${lØ=qXrWd6o2~ y<v?Cm[?kY5L6m*mmm,{Q|^3nWa0ӧ෿6t5l>ܶ(ΝMaAA̲m|=n~)RJ\6'ijigϘ;ӧhӦWpݵ58>m*|>>wgvMz`z""DH:Ef}8TAaA>}n!##6|mϛˊWV3udX,%.Y|FVqP5GaL#Hg̙t\#$-5ӧ14x-HwPeg ˟FR?n.d1sϝkoceȐ!~=^_]<U2ax-_^ɑcǘ4qGG?i[$HA/:v(Dg!MwL#N͖p3k0>ۉHU>}:v/{MD纸ӕ^.{w!Lm#:N;ѣ#\ׇs]io|e\Ȫ*>:xax=?v+O0}}5E 0qxftt X#;]U|;="k$evȪ|sw~RSyy+&;xrg?÷:C XUֽ& IDAT= +糟ͤxk>ܮudg3ct̳H)Yp0jd3Ϛagؽwo1ӊ̲䉧hv#(BSSع S*1RnJKJ5B]}=RJ(*rS[WhسomJ3j5IK jllmIqѐ6 Ob+$^phk lpj޼or媈v-%3=U8@Ss3WXP@~~^ں:m^/o}ϼ^/uuEP^VFuu eARSƱc))):ﳾ }jJ UU#9|'EC˾:\KgTGÇaRovefdPQQš\IMIp N2TZ[[?qOVV&8,JKJ'?/#GmTFrss),(ntvrq|>_}I$S}Y<˜b'Ε'0`Nb{{WUUmƔ)SEҷmƞ={p8|>^/Tf%%F@Xܼ`!3pcwB!H,P&*o`e (*)w᧊}F<H?-ٷICը\l6K0Y6K"$~IF!tkZO_ 7kOsc5Q,TPJL=s4Iix=I$zB"&nĂ^O1% yeH$[Y}P:Һ$U(X.RLwZC&Kee(D!"M @(M,d@Xm<r|$͒Ht_뙈)=',N,Yu$?<\ӝ^V$.CaY9\K307m f'MMSf im =&Db|M2IO$YAw\l۶2I$SDkz[v @P"ڶyZ. ey R醣0UMJOD9!= !16K"$ :r$TVVOs;l]b(UU7d|UpÆx6y 4 dHC/\'-RXm>aӺ|26=s稷l$H"pR&q"ӓ"勄kYm1[ٓQ[72%s$1=Y\h} k0 9Q\ UOIh+tmVI$qC0!<p2t' Vֵ_M5HALO$Yww52Q{CVEB9DVgNGm>E#= G^jmG NF[I mVI$qCvS m+!D }Ozwy+D~7Z k ʣegϞ4;kH/B5]oU_l/mxjb7p=͟6j4ز 6dF".uj4v8'Dݔ|S>kOfz'}7DafTk8%hFPU5!ԯSIbD$NfCrozX8EnmýW<qUlWC".R"E|SVl3<3g$¾ba8[S[DO*~Upe'5H$[Y}j &?;I?lFHy>dڼFx̭KN82 IMӄ֢!1$r/03MY *5g"Ѩ?=IpuK7^]mbnj I G]:_gw=;-BGHV2~@A2dԟ.3ۤsO: $KI Yn!w{8z"ɒ@8:O?]$@jE@Ѥ[Z  AL5#CgZfń!y,a02ZE&pu@BFjzBT]l+]KC[.|s#H7l%Bs.C~ynt$,NO*vzPS'a`U6iགྷ-4wxQMgLb 5T{򠝭Ѐol`ױv^&޶e٘9YcQq,D%[D',k@]w:PJK?Vޟ2֥%T>$Pޚ_8j8uSz GM7tV m'JFHcD?0uSv5sjэw-ltIHYl/"4;}8 ':4_ïxl[gnQ\KgM'jy=JSSf hx<455Z`N-pT\^ ^UH& Ma|o'X<$6H(IzVE)}D Ask mm ؈!?̫ wbK'MM~?m466vR%Xㅥ~B{[H륱F U&:N-FH=464/[H6Tp:iljϧH! C^"qux- tˍ765Ш6iom -׀mߚ.6/ [ӥގ| !Dh@JG^H0e,#$477knܷ҂EHPFg+U464rB{[Gmm``2"% \.. HUӞ隣s֪%"l{ӝƆF|>:RG=&v[].jxzwp{rǍP^^={AEO蝝{gG'~mmF}^HҺO(]8$5 ӛIM]\.bQUEQHOO'55݋GZ9K])`S6Ea|g m+))CwSY ?=Ҿ3=dzּ`nv%~ӟ# q;ZnX!O1@_WzuixMؾ}o~˶m2/ii4 SCZZZٰa#.+\EkkG>u3O[c~>,\׬X2lpR?~&"=R,6 G|o˖=ڵoz_U-{$"=V۶mgܹkmK{dy=﩮[GQHt\;1:"Jc/7ttt8N;9~8mmmtvvJyy9ǎ KJ+gSH+kl?n$"cm:?Tw'ѰxJt:?+Qm_Os1#żoٲUVr}L7Wg'Y=Ƃ /sA(֑DTB[[;;vdڴL6ׯg͚W(ꫮb1bpLرccS__СC;f4zv+Y<Q^^W\)XZٴi3پm;n)aѼze %%E\uL׭{Gq1nX=׾ziiiAJɞ=ypƒc޽ V;yŗ8\sfI]]/K(-)Yѣsx-=455Ml!3>[˩`Ulٺ̌ .RƌڵrA3裏ˇncUd+z.Zҡ%l(Æqek>|^s B+μ׿-c˖-,[0_lظkRQ^W_Inn.8~8_xxE1L>Gmm-.ଳxm-orOfƌWiMuu ׬ǹXr}U#n8޽B.R@9۰oȘѣxOO'p8,{|gg}OƢ MO#UU^=~s.ᕕ<3Hx0Tr)᜻ж#T?ώ;5j$8ä766sŋ/hmo>C2e2_dwvta*h"9o~[UWēOxXf-[nk^Z?GWWټy3^3OPu5>}*v:;;y'V9s&33---,hSNgT{ǘ?.~K.l׿Q^^Ȫ*V^[Ò%3!UIq1ΙMQxwY5|~sϝÜ9q8'O?3f [r!vBJi-ܺu+rh@z&@ڴ~ElK$D)=-w2ӓkc(~7}ʨ%% 3#ni܇Pqp)ZM73uoشiYcNoXE-eӦM;>*{gZ2-=3齐^ "H/Uu]wW\]ݵu{QAJY XUj* @{&m1ITTv\瀒R{~9Z?_o*A`Qoѣ'036JJJxיi3-ZyǑǻ]GG<@II >O>RIss o|bby:456eͺu>rx'3vh2239t(9ǹ|2cFB.3wl8눏e|&vŻW3v쳍Q\\˯R@Ff&w젦;vPU]Mm]]w%8ؾ}'_/왳$'%!@gϲ{nKٲmۀ@v{ 1d446}rGTʗzkgdL((`d;iSٽg/sVq1<9\/#GfF[{'OfZk?z k?JM a{{;iL4_^X"1L0RɊWԄ/x.GGGFRb棏?b>S8T*emt/_a;vPPOeuLTTTŕtttdTVVk偃Xv{G">F-_l-[K/BUUZ;vp%:پcۿ2&OhZ[{/ |MӃ~oJ%)#Fˋ+_?a/g^*++4i]ʗ^с)9ϗ΋/Drr]]]|AUɓ'3&p>|#Wa%SLԴ4vo$*j_}c)W_ݕÇᣏ=y={"H8W^}w<Ć?DI9@ffYY((8]18x[_gU\ZkĞ)ɼ_-}wW]z>39r_`9qh*MMTTVRVVF 4ur\yʨGSqprrb1gNFߟ:M{GF||}QTSVVƴS" NۛfQUUMcc#Gi=6"#"9ufnvtt"Wru8.>,?n,aa444ZQ[[<H13*qہA8p~maJN>͡Çi5R[ST*f̸j]wHuu5eTTVr̙$%&PVQTR@Zz:f(\@RQuҒ_@]mf݊^ö$R[[G3?'as*?jkhn!55а0رs G/PjG~ݦ R)YY444AFf&h4=Jcc3Us%BB3g--\|Nυ nj56X&T)VˉQYYEKK+ii yw5.̜y#Vҥˌ=1cp-Դ4|}}=zoX'Neš҃ IxDi?_ "g8qC#2\|/7nj_eq:u{Q&VZիH%,X0oPyYd1Z={q,^fZz:n;_zL'55 ZY3Hddf!NL> Z3ӦNE.S[k平3ӦMEV['˦NA&QW3!bmߞVT*q \\\:II?".^xCʫ2Y3oݝjb欙$&$͗p]BTL(RsSdԝ:@yy.h #$4477۽fӧOhl6S__OEEFU<"b^s!Z/tt\/\K__֭h.ﺇ+T*Lф\!G*?egs!/Z /H`@}EJOddq888PRR(rD>+O =#ZMHH7AcS8 DDknJ%18*yWJGwѽ5"&;;.,}!A7î,r[?ljP"2EE̖R)"#EVkyUlڼ^^nbw?RwpP7}OOk?vws%.6_YIpP~},vT½PzZF틀B.g0+NNNصk7EEE|quZ_Q1v)Bm7H$mBRb"i,-S:*СCl۾m*_>dժ7cXvbcvH^=[O# 6H$q.eDƍpwwz-E- AHoIH$~eZy2nX&OHxxm/"v:-cUXc Cf Nf3䝮$;Iڛ2, @^AQQ*íj5r3gj"3 I9@]G7^ZgJ>94K_c 3r-i7-E];6Нmh_ݍP_=2zݥ;m|F6mĨ#W^%3cƌIёE Yb%%%nnAzzgΜ$b9u* >b߾}=$ڌ JE[{;I'B~~3gj_RGKK+^z/bc4qUjogbccD~~}m۶ٴi3Dz1}:R랽 9Ǐ}~ك@3yDnFjj:.@ T&#ߟb̸e:%ťMMeݺOAٽ HJJBPbW2J%F#GRՃ\Ʊl.^z""#6,`ƎM رO>Ԯu_@`s3.\`Ԩj;TVVr睷al߾<ôXql ɘ t~>k4  sr(,+\p^裏ٻw}O:FL!ep ...8;9h"Z Ν+dʗ)++⅋]/Ą6nǟ|JrR"hmMNJKL< /o/ ϰw!1{ajDzFζmk[ÇE r |!NZZ:!p=֥uAA,[ Lf7md4"z/=\|e2ƌMHHY=MP7A"R>Q^V 0gl*+8v,y~e /;nFc eree=ݠh ȑ[ىY3o Hmj0nQQC9_?_͟CVV7nb)ٳgٶm[2@Ge V o]&UaZl_ 88ww7[,X0H̟?x""j5ttv2\،(B7u%N"# !>>_tDBH.Vח2!T*t:cHJJ$<,M 44{~tZ''G ??&M@DD8JWWWFcuDDD`0 qrrۋ1cœ9qqq:7UUhbmWWWpqqSȔ!J"&&Ah,Z???;nn$''<< \AP` źJG%I DEGOrR"1nX\puu!::$"#" !:: BADdǍ\NLt 8;;Hpp0?#""N||Yd1nnnxxzZi#I#4$vƌĉJEDDF ww/Ynn ONBӓ Z[[e9T"%::ƏGP`RĄx"#inj&2" 棲 "JhԄ2y$ғ20"#Ѩ]̟?xT*#RFرc> ϩfl;q(ܜ\p9D7sq:$ *Jz=zN xxx me4tA0q[*8) jOqK*? ^x;WY@8KJggg X ubuU% ur,{75 ƿZDQY0 bAo)=)==?T}%wvZ}Xٿ΋[qC7 [/_f(+/l63\n5Ρxݥ_o]}<&F<_6|ui ٳgy1?n܀}WK/FlA-ߠXدn$U]Uşw ,Y[g܂L.fkFF&ó]._+.7,msao/]b(/l1p|nyWG^XJ 7s-T7uڗJQjG>jнԩS OJؒ^r.DC»%sVLE.oI o^ٷQ\>|o i l=4LF\ *DQeP1[;ahl'"x%(^ݶʶHY_}'P^g[( o~P#|_  HD0[,n h^}~ѷ[a?ԙ BDB{3SﺇPAޠx]risT}b GwCoMؿ.w?\Ö ٵyڋ[ۂCwPMdzv-_Go|0}gjKAy࿒doǂ_]U꼞`! "~5mVs Ğ$,|;@*q|~=] WW c=zֵ?|UW |`=})6kԓOsv}_eee|q>| /6n]xqR҆E|p~W!iU1vZ&#/{oxm۶s)~456^*VUye6?_?[^X~ {@3,[ ~xhaٲG{5kLyy&eWT\[z)?|'}?w]s}y᧞~͛_5kϳkny1Z[[ٴy3l#!/ y1X{RSSC]]>kXmQQlWwy^㲹5kPQQ\.g}@9BNNΐ޿m窘Z>L||<'OW^u\p⪦J uC⯷MGzXVOe}+~Q<^-dh.qmE 8R5^ w+58(d;rpr{/&Rb4}f廨`Fٞݷ7:{,=swwwrEEńNccɉtvݿuTTTIUUUU  :8::֨BK'=#OO.$)AAquz`6Q44ihlBB󴷷V y{-BNpp0.! IDAT۷䥗^aٲhhhduO2~8)..ARY6Rx> /l=;Kf3?Q(ARrYZ[[Ǜ:::P* R, : a0(,<ϙgHtvvrJ]T@s óӧٸ 2k(..FQB$ Ax{pqqR&:O|dܸ1v>?_K?L&Lh… ȓO=RdBhni`0JII)UUU8;;JyY9eee89iĉ\*5]F#x{0s8::r%АP45\Յll6`kspss2, zO'u:txyyHqQ1]F#sWWP34 [R{.l1o GGG'{P(444RTT  BPUUEyY9^^1ؘ M(J%AAA\rLrr2.\'Ơoɮ]<];vpwp;nKJJhnnGETr)^|Eۇ< =?~ۇ\.gĈ|>|p~_qw2n8N8ARR[ne:t^f̘ѣygח#G2~x}]j<sy'xy9}4'O7u]F3* L)Z%?HK['w@E"%zg¬YTg hh]í)aN49éomg30]ո9)}{/!Igz<*G9ͣQ~]>@& EՔ׶ZFDP @s['jt{K< 2n掍 ׍9Gl@Y%˦C zBykoSO=Aayu|'L< 2{ݻ7 BiY9?(E*s'PVflg?{~^{_Q&NHpP G쳿'a坢sifc߾/y_LEE%(rAoUޡO`0 "uTob6hllbmP8(xq`H]} !5-3gIenIcc >38@bb"s^ ,4/iVbM&1v<~rya &3nn1rHf3[ ^|x o>x%wӛVA@o111 f 9'|NG}+^E [pT:re+prWeef:n} !ddf֛=z'2e2s@2;qqug?{JMEE`]5<xUMfZtL:_Q֬y4)+)ҕ+~S^<29z)Sߥ~GVvʔɴc1[hhl7b{#+mmm}םh4jjjjX!222YpgW[ncӦ/xxCh8t0#vͭ`,_PNb٧ĻPW[Y [nCTryjjjy_Ï2u$R y1>߸ \N}}='OyB6o•"~IkfvͷV1glho[~'|j`>Ҹ8Ǝ˳>k7H$(JR)TTTpiV^ݻپ}; 'xロ{ 777jjjشiqqq?~TJ%˖-CVstRf̘1h]{ᗿ%---n_{deeV9MTTׯP /YhW% ]+K'rl9\)eRVmfl?Wj95,4",Sqؒ?~.VMfϕ): ەә.[WMG1׬Ok7$$z5A^<8;wz2ϔؒќ)aZr0LQ-nNJ~2376q +82T+{69˲)ȥޚ(_B}\{Zoo͡/7MToЉvIX.lI+$b {2-9YbrӰ+3Gᨐ On(j~xC=W9jK>k̇rĉ\.rs8}4>> Ə`ioe!ddda711 &PRRˆ7v,9tuv*$$nŕ+WM*̛;Tls IJL?*+*쳃ix{'|ʤ5s(2m4Nq!|g |fz+߾?_?zbbe $2"C23B_.3c-},ӧNҥЇښZl[嗿9AO?'ed eKQq1Yh!U^]w_}"|+"i}h7+Dw]y'22O? F]wɉde|}|X`>=JC]]yyyPVVŋ8r(̝31GStR7v젠`-\HYY=<\t'r9r=K|||G;[hH~=?۞B1cpj't>u7WWvmgR);;ІNߏax{prrr"7$x{ysAX!r9L… KRSxƯ;ꅙ9s&dggs,;b|ԣ;w3nʺ(:Ldɤhs,j>uz72|z_^s un}SWue^S˸e}d2/ZΝ01}43^^zr]]]} yyEsSf''',3KߵJwwW$V+(b1ٽgv~͑#GlF"ttt`0غu+Z$$e xR)٩WkyG/l[.JB.c4d2F&5.θ:;S[WGCCZZ[[1<߰k>|#*g7]dfoʅOA=ߋ7?A7_/Wh _EA%*DVNeoAGg1x[ )qV/L@`f"33(AC9s{~t7NNӨ#q<ŋxx/pr#7n,~YYxW7v,ֱTRWWG{{;ht zPQQXxt̚5GSw0vУz3{mzBS[W3f(<=aX,/ZȞhiiaE "( *|N=rH&E̞}[6xyIeuֆlAסQkzGN ZwwW]ŕ+ fc..θ8юA.dbkWѨzm;{.c--۷OOOF@#/v:˟mw}#n npYCZZPPP桇єKDD2n8DQ7ޠ &'88wyZMgg'~;...디0o<>DcT*RSSINNٙ Xh^Fa7ܹs˰a%wxzzEJJ W^Q[[JX^{5nx> >f) >h`>U a\RGpRB 5m\h * F tM47R|X+BWn?)܋rLf ZF !l9-T52>.Oϻ e4vw!ߝ]Ymj"jxu!ޮ|zعrGਐSU6=:Q;ʩ7eQ!#bz7̜]8Ȥ$xY׬Y3|7` YE{PV1ʺV倓ʑ U`DDk[uC!2\p=M~c;{X x{{QWW^glٺD*E"`4v M`B<y / /+^^zne:2Voo/9~ 4rl }D2 Γ5xDՒi=ZEPP ΟG*%˴ GaDp&L+B.c60gm=z$vDlzϡCio%W VAJ]&^lf^W 7Vu}o]mzATT ,]z/ggg{A.1kL$'%o~T*v/~srsse2ax~C:1㍫ 9TTVΓ3dgs%RRF֥h_` 46=/u3f4[mzz^t0Yز gggrٌx{{3jH6}xtwpihhdܸ1.LJŋ'Oi}qpPz4>pvvחsQSSCnIf̘ΕWXp555}=h|h/`Ѣ):OO~(HJ?{Wq^{` lC' )$!ܔHn R(6S Ʀwݖե={vbI~Lyg̙g9Onn.?|mߎC̹p'o;NϿ/`ڴ{ş-|ՠJޖ(--'cnvIKK{͜9HQQ{erNe]ƍbԩ;v0c N2{G1bFl.vŔ)ShHB***{aweQVVhgĉ[&.rZZZ8=z45G}I&a2BORPPJ"###Fcsikf(o`La xlW08.'j MA+?/ckx(J$ѿ\sD^ $[쬬#'Mrڑ8\ޮ=]GQULZ%aki9(QudqH=f&fVsYysH2qQ:Ii%!mn?.^Sm$aЩYr43ux.! 3o7]GI12?hjS̛\D^U鄒 :h*O%.<}JBM/X}+eLA߾h )f&d0,'k_V֑jcl,z Lɠ2C()Y^# 1($WOii &M$332 rs8tE3 >$#=RRR(**J^Oii qqq!(--AaXkBBjLf3E$$^o@KOOC |xr2ߞ2%֬YKVViiš}> !>|￿ $ŋodVäIᄏ[&==[nUUj}ؾ}S ɓ'Y22҃:ANgg3OkO?#== I`IezZ>p?W;dEsHO7dU>x?yyL2ons1~xyVNVUqA[ m0 g4"$4 &cQx.Ng NX6Sc>ϐ||Gwl x仼E^~r3s,}}Yv-p=w|JrEW}RRrrteffZr455]w$;;aÇk/e1|y(..=z&L`Ϟ1.|,vƏ˔ɓټe *8"|^VoͿnz>rrs>@ְy6 wƍz=`6)**$++uε^x7}q8H$&%RZZBR.W#(--!7'xwؾ}ߺt>Ɍ7}.r9x <}~*~>f(> YZ-eϗCS\\,c[^ |[ WRRBIII99939jԨqΝ$I!X,nn|{9+[x| Æ 4ަrX;-$ӥHkwE IDAT| 2"bo[/!W!WQzJlO{tը{G !e9\ezgnғL}#W㑨7/r>LjnjuubRH_aJ6xÝ؊tAb}I|甹(Á< w ˾P'EYf("f\F_DgOxzUC+~If$/Krnp`[\)4qdא*!E}9H!^\G\.Of_"1G zŕ7]E"7hG^tWˁ!ILAK&9&ӹ}qc37U@Lz*cDXHuR ~ E$IfPdL Ǖ@:!L(rI7} jIEw|ptwki`WQWɧǘbIWCNcX/yGov0WUjlX9oz^>ΕtD#>8K\]E{5G$PO٧4WBQe vJ,d- j3 ILۖ5T5d0x]/9.3Ng[WӿxOqz3X![U^2`04=ʅX_#By?@._=),yJ?![y!l, ):@$ +_2E(,#HCw",t WR(ڝigXf>7'"ymVc"0](ƙy*4!p" _x ?xh=*)_=BL+2xѕ6>IٮbHWQp}eeb@W"t.!"U{]L[ N V] پ?=^|{9AA})t%DpL˙)`E]Z*6:" [{ {-<'/Vn` 2_DEefC!ΪrP-7Iʟ'4J>zy)cFU*XNnpg~\r"^{ã*0 M@W:C^Xg] &o_1Ktw\Hi`ID\G!ۇI%~NΎ|tu Iҁp[Zb Xg3YW?Ӽ(:|-7%^΄JmRBnF<ֵ'˵R`O.B1l6.5BK!yKQ~Gz5zjIջCu~#FOtqڸD^4v.JѨ7)RC9kjhKtw<1'Qq{A0c6[JE| tJ*Nu h4${q3YW^:x/gBHFcW?|kz&_`_hS^!xCV֙&E?Lh^g|a``R,1ŗ$Ihj~׃7stb$IBQ]FBRFR#R2رC*v|ɳ>tUUUlڴJ*++Z۷L9NEEIIIeܹT577S]]MRRimۆd5^UUZSNсlȑ#fYSN؈j )7+++$]WW$555$F)$=CPT v;555 6)߭ر,-C二VV_pNEENكdrcju6p: !8~8[lMJJ Bعs'GRaX"?8x}}=>@z1XV_Z&;koh4XvV 5lq{#F E ¿Cx9:G6@?:^ ՔKn*'wXjf3}tqh!際梨V~򓟄9v$x$ ڵW^ye@yD#GPQQ+z/s1V^ڵk{O.7oC>|[Z~{=466v{ᚚ׿`P$oogN+e_=⽥ sN?PUU%=XH'Ot:Oyginns=.e˖b ***UV믳f~bن\g/}mCط؊=x.m7^Nw)P5Bh0يX_U@zɤi͠cLW!l'cLWN_w4}ܷ~*U'MɥƖW)L.Fzb"RƌØ1c~墮_ܹ{u1n8?cOΒ%K>c„ $555|;K/E$Yv-F`͚5lٲv̙þ}?IAA= /aZYf III<#|Ǽt:JKKYt)]]] r=TTTp=RȚ5k袋xghhhfqwpQcǎ'NЀcǎdggf~iƌÆ ؽ{7gǎѣGPYY?Nkk+[oO>e~G/~#8y$Æ c<3 I?)**࣏>7 \pFz͛7h"/^[oʕ+ZW_Mqq1?Yp!/2G?W^aժUql2^}UrrrxG3f ---/aƍ\q,Z%KSO=fBFIشi $wy')))Obԩ)SpW_SO=/dx㍌;Z̙ÓO>7OSظq#fŊ$''#P]]~K.K?<̘1C0j4{9vC=W\̙3__϶md>oNUU=j*-[SO=?#G2e ^DVV~)O=Æ #??_W(7osgwe̙3W_}p1n6jL4_W$wtt`ώ;)++cݺulڴz /ory\vZ< ۷og?G'"ɓ'IMM{巿-f唕m6֮]ˉ'ꪫ3f ;wn`ƌ/alݺ"/-+Oyft9vSL!>>^N{BPtO0P`ﴂZ[a$*iN b"O;|Gv2+h=W.s!)!+r9i >|B>̞=;$MQQ{/۶m`00gj477jeǧ NGvv6ӦMc֭8qiӦa48q"ǎcȑXVV+&LRSSÍ7H^^.KL&~gqQ>n7$''!{iԩJEuu5^W6pi񤧧's)&OLvv6.(--СC!y3n8<o+ш$IvfΜJbÆ 1/\ Yn53g`4IKKc|dddpW-B\\$%%a2(,,prAN<ɭJzzl6矓č7ވFaTVV2n8FNrr2 .D# >> MFZZ\.c۶mL6"OΞ={:u*%$$ˌ V+/Z_|1Ɍ5 +7a7o۶m#55:.\Haa! 8FMSSYYY=?]w%@iii׳}vΝˬYgpWv8q".F#\xlڴ6k$_\\ng̝;qơh/(++'r:v!/#2e R_'ry瑓úup\|{ߓerm6,Xqz$%%#*++ٱcvFȑ#h4deeQZZdbݺu=2n3͔3e ;v,h"j5$j^W_Ncٲe N:`֬Yr 2MU#2n28e=dBjnj I-V2Î t".ߒ=n\. npuhjl$ZZZIJJ͆GbbBOJl E>$I^)X hH$!g]w݉8Hλqx' 8Ass33s=Ѿh4w{\O>!==Q#G6o}!ZAyrxL"ꃮz#v#ƆZZ3IKMb6!QztuuDk{;*̬,$?}: |g|9k~"| Lяtꧮkjar&o?mظѷTq./2߽ ] : :sբ 6ԩSy衇X,Q?3rɒ%\vY?{hooY}k.8V䤤Drsxې.3aItbʠ<.233x%VDӡRpx^t:ߋB&;\ܸ}:44Eu3+%j2r8J$N}x^:W^ׯ;x{Vx9sfrQTdggqN['yXVjjjd,]1cF3gllkp]p9]T>5s IDATj#x=r^))dggѣpqT*f222Xv-'3:T^Mgժu;(d߷o?.'NHbb"9TUU餡L??rIOOGt2&NO||<DfM] !FCQQR:!55jZZZ<:03n5![+viokѣ0 rvl6xhllJ-VRRSNWhӔl?΃/Otٻ8q$YY455z0`?~UUdddet:;+ˣ/fݺٳNX|'N;v;ѵ^l6iѣ|3.U>[nbWȁ5zb7Bex;[WNW67]ա^F +vmmm PhJ$ZE\QRRRX,T}Ih9 [ZL>+*h/cOXx!}t:m6A?FSS3L2fj5&g\s5b%ZV\x9455uk9ee~-۶ml^Ikkz\}U!USS3?)FE^l-/gǎ/0ś8qUxB_v;y`pgg|*@$ptuuɧ"l64嶱Nz<߷4n !8t z z^<+?O3v /E tb0NRdfd"o)Sxضm;qqFZZZYhEEp55uc?rQ^Mn{ʅWڊdbϞ=<Ϩao0|rrrx`9Nc1}ٹ+1xr%c0 55\?Ν_2o~vkw܎d旿|Il۾GP\$Dvk?b1sIQk4T*4 BVQ >= $I>%tA/Z*}؅8ZF㯋6h:BnRMM=*#p8475.I,_m0.{NE=//"žܜ>\!iȒ%d2QS[Kr?INM9 ,@o88P>j5߾z.{~T*<{+g$?ﭢX~=:^Hr!$ ݁^$f:;;iii!..o}RlN-}n6t@[所xt:$keVjkkhZw1bp>@Ccxߤ;&rR޶kHu1}{6n:=Qjy:mW_҉Av;x^`&6ϠRc/Hifp˿eQe.RʸMh4>(l Fs)Q$ys;̌9(`,]o:-W_uʌgϞo|HQQee+Va련F|ﲲ22C<Ç ߑu]Iygh4\}ռ!yٲe+eee`0YhQ7LfV|ٳغ\ d<))O?FΦ# .t>ϘNNN6Wgԩ]u> hmmc„ Ƶ^FAVYb%:>!VZ:/_t(ϯ488R.NL oaHax4Yv:Z[6y$Kו$rځFNݭ3'n.aQ Ezdefk^An&^_KZX"3&S<}F?~ǐ̛w/寨$A}Cmٲ7CT ~?`XkyBL:KQmyCy3f??WRu믦L;w2IvMuM -->eddsQZۂ >m*=y_̴ixHȠ֭455r9׬Y˂ xRY='RSSٹs'\p;f{d233 x752fLo zQG9:ے*=- ̼*xWsSfKB* \# 1PBnwcۍ `N*a#T475b2YB!D( [î~ l>KI!${CJF`E_wH8N4߬nGբj^ᛑxCi=˅J7= N'AcC#58v:^#]]]zFۍfCVq$g...&n.?tttȟioo`0`ٰZlܸsA eDJ佰翸IKK=OxJf^ݎNEՆW]7ot{^&H֎ldj|5A kjz<t8ݞehtl<~Lrssp熓ȑgVw87hկ> 4kodIdU-G#fMFdUv}7$<ܗ]㝝/'&&ϦdBRIx:::drh|v >Znz9t:qF:C@?t0  N?A})*++[o j5ě=tvu!IfI~&}N4$Zruv$h4<E^l6AOIl||< tb4i@RiZىο:pn07^f}6c\ܷ URUz=j~O= J 4NkM~t{nQuN?SFf:D{Wek2zׇ1R% )F{Vî{ٵ{ Ax$yF407Ep\otr?($IP2XV Z@Y2?^x 1ʘ4t*c iRq+ScMH@R.d\%Pn*ێ/ 2n3@@* ҦM O/Ɋh@$޽{9xx}aB2sZUMqٌ$ |Tj5zV֍l6[L!aM"IyfV$LHX&f1ae[f0PYƭ`[Ǝ o$>>NU!a%YoP W-|. i'B8rP<'R~ڠ!oTY {#7o I7_F !a($$:l\4B|`c0<Ǜ∓-B]8~8 \ub$2/vE֠7dY|:hj 0Y},*Ih ?.H\QN,?P6Zh j`6?ʞԭ]'ꃓ2ӛ Ev twb86Vu"ZΛ> -ZI7l7(T,K"b lTTT`1 L~OY!o RQE30t,z+500F# \E,Ox7Q)g:/V$G W?/s]yu@#AGtd+ チSP| ?. >|hR]w^Rqul4:nz. n@bR ! Xx^NZFO$ɷ䷣QVvm!s%U**ѱ 'EF'4#% JRHJYm&(E]yuue3kxv5Xe54б̙cXclx,r&d wdrz}P(o ,js1 H$hzֿy䱟`DR?(Mqʓ  ܅}"U!<^g HƛJ6 >!I7KǏ4Y7_/ Qqof&C1)bFG =Q45a<\^bPaIY9d'}eZ6AY/B*:>s tkШ%Z>$ zU[)|H0 K=ׅwwŞRR1KC+aG1E%<~XL;A_Whׯ0*99;nԩSHO`0p:9}:BB6VJVBN~&ii֝b|aӦLrY\DKkN!I>jL||<Ur9QԨ5j+j5RxPV|P:R7r`<$$Ih4j> ?O@)p/1' >xpǫƳ8Kx,*0SL(|mM  ՒBҐٸ@T*$f3x^hnn VKJJ*jƿ_?=tjHAC;:ИORq$::Q?|Gsz+J /BȒ$xhkmF?QSoRva6[HHLoax柱3#%!z՝s t-^4Z0"vdQ}q^/ 455a69~8 Dn̻ŹC9f:ͭ-Xf zG{хS\XPTdeSR\ȞiﰑK}) suy,{k+p8 v{#77fLx2Өohh0iS]]+7Z? N}~ AWZc50Bg),o *z1+ y^[ ]]4ב`%91 wej@?2%9däggc0A/,Ӂfd2u`P馦Ӎͩ%Kv$4j FˋV-a [/6Q9L@A~o.&cXe%(>Ɔ}e'j%?7X,N#m6|^ܵx<΀lݺ_{ob6zz<6nނ( ͢R-UQQESTM֞>Uue)|q79 Sÿ%Mަx'/Sw---{Ix }ٴiSZy~TJGmg yj>/~* v#In TW例{k̋] bWøf&f&2hN1#G %zS9Hͫ(6 8z8MMML&]YMVU훏Une fMXvٌb 5I>GY9R{MB{TUE%$)FSr1fF Fnn$JbI|ҝKƗr1|0F$?;@_6֋ށ^Gfx=xGۋ>3Hm:eT\OIJOsixEd񮞙P4SE3y%U{&FuIwg`I%$IfQ\\̎;b̘1~eeȒKI@?^GuQfϺ\qET  CAXX)*AaTL3Fj3_B j'q⦐({Au'8\UE L8 4hinCTW|Gl߶UQi6Qu ϋ9r:<&hllLNi&<ǎCdillũ9Ç47PWWGuu5uuuHL0vɷ} EQQ/F9JcC#ǎ'RT\$TC8qUQTAQTZ!jj&&R]UMUU~3hׇ*[4+NY>ԿEit!~T7tSK;w^nf/_eO>$k֬G=t{BVdqL>4N׭[M7?ΪU: &-> gYb w^~ӖչJW۶o=ioњ&5NH0b1Nl.$f߇?̧p ՝ hf|(u'ڽcǏOf歄QcJoD01/",PTV xQiOqZQ#Gpv@s H ʊ6}/$4gA%ѻ m2J9X&ZMȅ;feyƗԶVhumvy=tهDɅ/d|yff=FYY) I2#GJ,fro>̙x())[z8e]ҥ<ѣGݻ}b#Xf-[oSwy=/%J^xa ?j~?[nM|k7uuuSUUE~\1E "P_ҥl6ϟG}Dss30``r{3l0f3W\~9k~Ƃ XjEELvV}\x v%$PR%Ӹi\0pM0*u'XVfK$4{w!8[4G!&C ,%P F446Cv^Q?\eV+Cf*l6s5El2t8ݹD}QÇQPPfLSǔ04@L*b~KUA,>ĆbBQ"cQT&L&^#ǎ۝9(kUYD8~lC++vA Ŧh_;" b11"C x] u'(.+/o+gzȌʫ8.?y\uU,[߇d9sxbΜ8Ik=f4l 1ax?2ax9‚ 8t뎣hӔ~ ~K9x 3.˕ڵkDP0ȭ )--m۶*Çg+N9pԴ'\z\z̝;>Z.]ʑ#Gkqݼ{|>.R hAgsro2rH= ҂(L6e˖q .n7k׮%c2ZkL>9sn:V+[laԩ5۷yfrss3gVzؽ{7^x!'NԾ5`ĉ|>>^/'N /jl2^{5fΜcرiӦ1f>svĉ =x<(k֬a߾}L:J~a X~=[n.O>$E~P_~u1|&LKyy9۶m. 6sNٳz\ >3g2rH߿?~)EEE\qD"}] SLa|lݺQF1m4jkkYl,\Ľ_|TTTf޽{ٶm `Μ9X,|MbW\q@A+|Jϟb_s!6mDii)+pw 裏8rcǎeڴi466|rl61,]zfΜɠAO8p#G/Ҷ|L4 {W\ngŊA8@uu5̚5VZEqq1_~9˖-#++3}t&L@rMuu5o6\uU;#(,X#\өKK!7”7/%YfϾbQvfРAL;}%{&#Gk!:TO7W|fܹ+I8pPw*S U4ET:(mP(Ps(bºn & DAdosATAB;հ0D&6mHN53eT(#j=j@H/ Fbo%isUd 7 |jrٜi3S\Xʕ+9{Æ 忞/FME`߼5P]SCŐ b55G8t'NKAbp%31sOr`ֲu6͛5kۄ(۷+_^i:9ݛyoXߏ9r$ OOjgy#F{n$I^?xl۶[o>p8̍7ȇ~Ç)--gƍ^?ӟ1cߧf"K,a_Ç;P\\udY^W_J @VV`ǎv}Q|AVZň#Xt)=%K IvO,ټy3C7(//gȐ!444s_sϥoƌTTTsN 2z饗袋0L<,ZK2uT֮]Knn.+V7e$I}Y~`ZD"K5*QUUEvv6~VիW3f***x+h"K("#IvgyJ6nmݦ1;;f͚͛q:,1OwF:9/9@O(`!1@Mtf͔b[NZH$B5k |cF, 4Eb(cZSfE!Ֆ&m {L>QGqATݎn*t*h &K.#X**++}9|;񐛛o .\HŐ >`)#G.f;b~X}&2Tf\Hʧv,d2ٺe+Ǿ+#J`.?E9j)&C{4˕l RP=zil'M&.`wn>kWAdj+jF ߖmAЬbBa7(iIPE]|~<&^ ?ŬxnQQUIhnnF9[J[O$W8rPO?|:^@QlKÈnO9'IٵgNh$ITa)uvVZ3y4y<,[ϟ Z}N\Y.B!\~5?'IE%.ŵr1&dY撙3AE.!mb#9ɼ8iXnTix444ӟZߏЭkY`~;^xhL:?>Yfqyd֯_Ovv6(rW2m4-ZbVϟϬYSuV6nٳټy3oo_[oQ\\(L./_ζmXx1 `ر0bNl6:7ޠ QǷzj|ME᭷b۶m[رc픕1i$6mĺu(,,1oҤIlذ|-ZďcFɚ5kXf ӧOgǎeVZŖ-[Jib/"#Fd2pBΝҥKٲe 7n;ŋ~7|>v(t2|lW_ܹsG(l6STTDee%SN /1p@&Mh+,*DQdѢEl޼/;3C1qD=a7n_| ,`|{cL&.b͛u3uT6nȷmMu_=?2dw}7˗/vsuױ~zZ,X-[z,YDE{)8q" ,rc}]V+fYCI9SһM76W_k'fCD6NUI`)phaY:Ӷ|&TڰļQ4Ό''ĠF0(xI0iSuW _i?ܶi^'aTdYMM4=tr{N">\Us󨯯uL>۶qPkh,;'.a떭`.D%j壏>D]_qʧj`7bw8d#ַx1'd3fs-DIgΜ9fz){1*Kœ ŵ^g}uV xbV\gq]whmwMM v"~mQ!O8a8p|;8q˥+<G?b̙\R/cQ:u*?8qnqƱuV\.vaƌ )$Ijjj8t;v`ѢEqmkJII v `Æ D"m_w'SfY~;2 BL8}YN'ƍWpQ6o̽ˬYXr%X#FP^^Β%KA&3ٳۥ IDATO>! RQQSdbڴi7Y_"/ ~M8:ޑ}#S˝x)\y\{]1JKu^WxN*_H'oT }7_y&KEBDq;SPSr1cGSXTH$ ,Wa\N6 LCv`D~eH룬o^L\7-ێc\d\TElwmn AÄ=GKX38JU~yKNh,Xq:HCkhf'0ʊvՊffqc6[n0&ѠJgGUr+uږv ;ۆ\FAA>(A% IH>)( yyh|Kɹ+.'7/BN'7~^.¢B E4zcZ(,, QXXlrPa„ ׫#`8^YY\uĢ1ѰCE\MQqsΡm۷QXS9t(PZRe&S[mk&89K66Ln~_떎3grRZZ}=ÝwɤIp\.]馛wx *l6,l6N' {ݴpB BNN<\.Çc٘={6O=r 3f#&eƍܹ#G(,Yl&O̖-[ۘ x'el?~IO I1Lz=J),p8 7@ w^_|Aii)%%%r-?joo1W_}kɑ#Gx7f)JʬYرcdHDNNNh̛7￟bxt'>;;Al\.qEѭƾ#e\ƍ d{ImGUUrܹ%y:ű%6lj-c0xN,򒛕ŀ~eH~hTpwB?51q==n5C^V!O)<&NLZ42%ňgHOLVD=6<. #a-A\Zt<<Zi&=M]c/_;rXt"myN0L?tE^A~$qj$ n@)ߞJ,5\8i2-H߇?lfsn ?gM%g&Ȳ@$mhO,=8N* )O:PBc?~B6nr=e͏Жsbחl&q(`H'%//W &~v2.u9eb*)IKP5ph3tLtxz cy \M3#Kߕδ;)9\>@Yng2*9fA4#\fԂI[|d U˲E#0"!GDd&,m M^y&GfgP(D}BrrܘE41: >OQ$~gj}je㺕lXX78qf̡bH,&-LNN5Fލ띟}'wlΜZOU߬,nlW=wg9$>@ .yA`ĈL6 puv{լ](\x\r%g6jI੆;񯻯7 ܸ+)W@{$tQ-7) ]rhIkQ̎lvvGh'G*R dlw:Qŕ-ȑhB01;ABu7ֆX0;PUՎ,ŴlNȱ"*8 QEPUA0%JmnA k Lv/ZykB&f3,(A.'c9%˅vZczϜ!67KTҤ)dQzy'_zva99 t<{gg2ldg!YVDHT55dE01JڳgM4$B>?9 j8XUMqY?NG&ɫ(^rnA0seLd6c2[hx$հ41=Q(`1MX,]I'&2L4y ~{&g ig/⭷dKwuF֔S{/?Ӽ$)غ~GW7ғxvv6D"7ME\CC0/m Vn#!HɊh2a)|((EImxˍٞM1E#V;fܡL4Ј=IDcvfav xvE*Hψ[MbA?hFGxKQD Zٚ}ZO{ݽѸ4Oo,s&Ko{/]楷㽉f#FIɾb1SVVF~e^ OORwǗ9jRTҔ6j8^ /Lxworcu? .GVL @=VGR$Fl9EH f3RD[N19bqf*{nIHUmS<$& Al%QZR_E!'VTTPgi !XL8Y8\deeaOԙ % E$ahcH!6΢ &3 &~Z^L6' R>c g(#hBIhh^܃G xqc/CAx86#,vT!?Rk#UU#GBhH1,T9"K8 0ۜΦqdEʸ&lEW̒ʠJBض$4ʉ2*(+*ŕQUYQ&.5% )GeYFNt籣|wNYe3婻$_^%+E!nn;(!IIQm a̢@ruVN(K%$Jlm( (YzJ=ᠰ5GhmmehjIJnjTScJbF%`X8p ͡0EYn7sy97JKUBDA=q[oW\bO_|[okS矓ȑ#a?o~.p`ۙ5kv[]3<Ü9sHy*/G(g?ӯ_?,X?nW^yɓ'gOG~z imme߾}x㍝|\wu{ǹ{R4m /t:O KwQ$%uvgJdCx1G}G}G}G}G}N^צH$Bkk9sdYsEt:O*LuuUw~@~ڝB*bȉIӾW@QQQQQ D" 4!_ P[[ZG=K_o!D~ a׾ng aro>yJҖԽ:CvVJ8_g˖->>>>tu5l?Kg/ 7o|禛8p .[ƲKkps1t}ag&Lt@gogqg USO6J'+۞J3zӕYo>]RO\YgM&g [~Odu'>Y`:$ię4ccܙ3O>=L3a: Dy=TpEQhnn&??Xb˖-cϞ=䐟aǎ㦛nbĉA.2JJJ6/+WdÆ _X,r |,^Ӻ0L2)=ӦMpsN|M߿?v+W[o1c fs)--M!=]8 \7.^\7f9YfIf0vzdƌgzg#;R2t2ɲptu'ty+'iLTtd;{xGyO4~*y9ӲzrxG7Ὁގ&^z;ޛxxo⥷=k.&OLSSq^p8 }Xd UUU455b ~m$IQUU<&~m(긢(ȲL Hy5_f~_raTU&<@ݽÇgaDQ禦&B~( ߯aСC\}\}Ռ=ZCSS^)S3ϐڊQU`0" $zSHU5 `2`0eYfҥt:9x ngÆ Kyٺuk$S>fPU{~۰x<U@?R4qLJ_G(LG:fT3}:㵽:ƺ+,Lu3tȮ+Yv%Gc]0oQ5գ»*SEwU_y.lQV݅w~I^z;'KozxwȪ#uuu\.ZZZQEQxWYn(R\\??[o%nݻwO<}.x fϞ)**B$~_뗿9r֭c<ŏct~;Sr;C=ĤI`,_ロyv_<=s]wꫯr1~0vX$I_~{b1mԩSS|:u*'P2vd gܤKh#~2M״3}a5 IDAT7=FY>qg'S+t3ۑ,Oteq2xWtYn.ldte̘VYuޛxxo'ګtwKz~xWKxo⥷=֝wI<׿5<;v젵%KP__Okk+K.oGX)ޙGU}gd IX%a "hAT-Zk[ާ{Xۧ]*uuAPUqeQ"`$, Y>wqg$8^s~|ιwmpUUUT*V+?XPS* u#ZF"E3RC^}dHh"Dy8Ѹ hh:pX|(Εx#hxCyxs*Qmoys>Rsw^}|t2uTMh䦛nbzz=+VbPTTij>W\Ayy9<ì^H PRRBMM ?8gϞl63sL?CAe >ǫf |u#D_hO<&M"##n!&ܹsKyǩd2O}j+Wd7t1{lZ[[ܑeZ͕W^ɩSp:̜9K#8y$K.dzMy]9*N֭[v~򓟒#<‹/@AA!q%mkA ՒFAaDЀ(= t`VXkG< Φxa:_bH/x%q UHr;\ŎxpHYYYXO( "I999hZCXCcIhnn 8ljjkkStPUUŗ\.z| 9y!!cHG2^h8dH#uE-pŋLg(1yhy9PE1|<3;DonF*Qc\3|1-UB8mKC1^SC"ßԧ>s|8Ҿޟ(D%PxC AYt޼ygYd .+ :0܇0:zy"77#w-0T !?z2Bs5VdޏD'-.O$[]H$Bڒ$W|壱%<\/)R,q?ȸDd+^NG ?#Tw4nbǚ#hTyx#<_.b|MU,P#2^D*^D%dXɖwh#(ɖD-;(QG"Q>:Ăh`zz)Z5 ;YTFFѢCb]z=:F$H"$H"$8ƴxK"bG^Vw#}'X^+2,IH9*_'u7&DI$DAXY,F~T*m erII|1g%V)R+}yH<|>}YW Eٍ!'#Iџ!#"BTw ($ J~~>$c0DC{u(:#{&#o4=tǪ/Kc8F+''.t?0Q8J~b_<1>\ɲDQsL iTp8hFt:-6V@F~IyYQZQUtZ$Ij'??GGfe̬(`ʔX&Yb•Fjj5HD?yȲ``oއGϭ'l0_P$BtΞ 5ՌJqdddR)YKǧ; Nj3ByU @7tߏ78~hHdZ$yEӢj}^݂et:QTaXz5\q#L˅lq8ގ$I)I$DI$qQdJARr94@Rft@&*\^b1zU摖FWW7l\Nf3.'0mZ5[ެ(}.d"G[[z(Ba6 T>HM5v{xm7sٝ8\"Ӊp06so|ks^o@Ԅ˲&-͂t"24551aB)'*X9`ڴiLiCJ^<]l}-n+i DQDA_c#͛ɚ3uZrHR h8NߦMY)#gϢѨ=.Tn3јZtC(2Ix"oakjjXdɠq,߸q#7ܰnPtttuVfϞMNN 2hchEGq.i h<^:s(0:xa|Kx$BgRKG85&x,̛+`Щp\ z4 ̧1Aee Qg}G l|3^AHā^z jjj8|0v 7WvCoo/׋fCw^].ys[o2e/^̎og0ywwb9L27ICCmm,^6loٶU{9<FCOO==ݾp{@va0|=rr"g4sAmvgkQ:?\KAYNJQ*ʃ>]w#z| 8(0>ZoCvv~XNd/25kk׮_z~ .\3<ôihii!77FCss3_~9k׮O˘ʕװlr_1 rg0ӝDI$D+?e;%VQ$)VMvSOziJ뮢՜KHh=JCD៷&2Q9v"Nbw~g[khh8uFJ;`Ҥ?W^ Vˌ3ȤzN}6M6qh,3~ saӦMvml |k_c-xbK_#iKK mma_e[o墪jdY_WѨ ^xjkktTTT~a(NF&tq e> `sUЪbU*:="&لinM6Q\\g>yfn QTTǴsN"33ٷo?fFOO7,ȑ#HDKK 3gΤ0?:;;0aSNSz5C`Zywq:TVVҀH:L&f$%%8䲵x6m*ȲL}1NlJet&MD_5bis233l6=8򗳌JFqq㳔MYF@UIt{ZI:.ϸo>HZj, !:q>Ȥ* SP 8 K@H_jʌ®'3XWPoNϙ0;*MRC}˸$ ,h2xw#&฽v逎3>IxvTƬN紳 x.N@=$'U }bux2Ӵ&6 ke`Hө 8S7aGx?f^y9C451L\}V>UKtF*>{Ǐ/߉VeÆ tww2%%%yF}YVZEff&III75k׮ gWl2xYl6mbk׷sm $H"$Hl@+`th;ƔLmHo/-k>lP`[CY9OVԆqSt椨 QSO==|{]%%% z-F#ߏb᪫3}ze&ַG BWW'GvAOO^z)j(=馛_Ç)++uT򗿠 .dz{{ٹs'Yv͚5kݷX$ zzZZh8xFr(91d`V@tgr87ߌlW^y+Wb(=&=(˖-bpWƍ={:+VK.Y?JMM qwy4i/ Csϱc8v:n>? Fp-rxg$rss(//h=t7ndٲe}ǏqFNʁZ=PN%T555ܹ S[ 6?ЯBy=2ܜ[v~ȷK{53Ra''G[侓!tVWfUWNc[c^sRT^|: e7w䔹ʙoߝ#4ZsK*5ixA^z/݋ZVsO\] _ǡ1.rG{pޡ^e Gw7^I%o,[Ɯ8@,ax0r@wϮ?PX,ޅ:mZL* /%rv7|F@j8u4 'OA`0PZZ?|Le Z%lvd;F}}=… }/8}cXm-nd_Ƕ QnI\|H֗A1\7`q8m(^o'ҁcX@gx^N|̗ THm SDݘZOpu!^gގ+q62}@[[yyY֢Vk}ZCZZ:7| z|hwvRSddd(C229q$v2N8n(>Ԩz9v7>ǧ?:׭[ܹشiV5Ǘeg˖-2lٲ J3L:o7ndӦM\~R&N_,cYe,^S'Oa\[e@U,=ox" ^\.7&YVv^!Kgye˖;oS]]NgѢ\{j4M+W`qq֭^\.{ LSǙ͒{y(Ofq:g?Sm ˕ɼ9KMif  IDATo`RJ2jL4lk:+_$O7n#GWVСˡWG#?J좈BNv;L~>'^b s028wSR@=HY06Ŏ}C eHJJ K^'?y#yyy?~@e "lRcpr'(C&TVV2o<͛G__Vu-1= j[m9t# n׶ QnI\|H֗A1~\~C HS9ۛʇVW[hJΌUdg@ iƬ$="e!dcS~='U&50c->\ A@3 R^Ourq6 @,N;G>l]ʮ]Eq+DQ䭷dÆ 9sHKK ȋ0a{/*˗JYY9gvpXRyǘ3gn\x@__'N@RquS]]ŶmXhm۶qqt:555dsm4fUVpWpWܶu?( oD.?OQُӥ8qQT~^sn@nn.۷ogŊ+LR ~JKKy7p8j-[Οg*++*?T1jjϲe9uJD}}=G֑b9z(n, *;wiiio+C_}ըPQQ;8Q%++ NܹsYd1fUQ[[Fyimm 8g?pp< b>Рi nfe3KҵVS9vO+]:C1^^x?dMW8v-bٺbOU)<|lt~<%|kBg0v=Bq+ i:m{? ,;nk]OCuX1vuNt@";NG99#bIc{p9L6Z{Cqq91=]2F[],W1L"$H"$..Ze5l:]LO̒.Cp=dd,j:B J.ȋ?e4YKKio:IYFNJ}~~? 4530S;Ʉa;z̥%̼^14GI̾*^e̛75k`6o Y`g뮻e{ŋ{nT*o <|cѢEzWV\UW]EKK 7n̝;ի y ߿ٳHOOGV+No׋$I F^1ѣG蠸ķ Ӊ㦢"# ﻷơ^iqdx3n1+ z AբVkڳ=O#rlFF,p![LEgWR#Rzuz؇2kv*nb4KJ-*D$!ȐsAMjx<R B3:BH8rNrM 1FY{y) t>bmLJO1hKx`>pqb?2r491X/;\G* g!s qL4$U<s.&pj֩T*3 ljLTSJT}~RѭB 5//f̾-/ o{qzzzV_zTƷk3ϕMƝUH$I@wF)>eY"%f֬ams[&S[+])1OBkvoOQWըjN/~I/+wQ6\Km4R3k_CF?jq̘9xonJJ}b2za ݐ*o8XŒq^NJ U$.D=PD&FYXb2\x/aoK֫ G 0gE, "Z:'?hN?%x-סA'#7(Eq4c$H"$H"$H"$.Nb!ŔlOKmSdFD eP⣐_* =ʁ;wDƉLcZ"c1Ȃp"m&C674Cg$򡸐cˍ<=c,?yW8q1|p9T^\I.'-.Or*Ǜ(~Pq?D%N'>; o'g?9Q&00tA }Nd?I"E6 De%`*:åFHCq"ih`B !ZGK'yX2< documentation:liferay_3.png [LemonLDAP::NG] />

documentation:liferay_3.png

liferay_3.png

liferay_3.png

Date:
2016/07/19 12:14
Filename:
liferay_3.png
Format:
PNG
Size:
155KB
Width:
1239
Height:
574


Back to documentation:1.9:applications:liferay

liferay_4.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000003374101325274564300420340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{IDATx]|=+!@ hwh Nhqhq-;BpIpipH -N AdY^r ;3#>3{od4 c0>д04M32GT\p՚D2IR&jbŤs,7J!aGU1c%d2|CUt=?ӕ Ԑn/}=A$P 2n}#YrBJHQF+"`K\B *H(U"_~ bfi<4;j -O )EoYP ވm`tHiڂ9(__;82 Ѯ.ŦJٗDZvsL Z"maQ`Q* ktu9q'1)1PRPJ14^;o+4m?bu#V" b$jXeNi8Q1 Lo1xK):-fKbytFc0rT@w?~Ebrw=<xBPJʣB'H #JC˸1I,fug9^Wk4|c vU@f-,JT3/ M4(lx(JNan(Ec9HK.&\64` Xߘ}7b*!l.9xa5`+AJ!3?.dj! -> lPPhO>0ڂJkPG!URB;Mƍ>,XwShH311tƆ@oFLh.,ARrqrFfɀགྷSx;E ၀N%\p&co.WP'e+۷oG...*5! @! }4JAQ`+Y"z?U!犉AF: F c`V1*|L桟-TecWSқGvDaYH3L I g,͂i(^Jx,-@P•>lyB PHrqA|s)dÍ`01di4hB#v$94%ԜALV8Eɱ0'mf:)Qj展.gA `2A{ůq@7AV=Nqx4[ؓb]] ̖?p zȔS\]f)%̮n{ц+6i_!l~y|//nn% vD+VW^!y'5͛ p{C~|glů]+P!eZ ˗/"۲4ΐu@C;>m7E9.4':yw%p ))eC~uĘF1Uevhg@HYWe>m]IcVOxJ8z(ГF'$?Ί">s G+@JDF7찱Ր#G +B(ճGpuv',m0WIq.3#\g$0'YPhs΀ t\1h5JaE:bitS@V)>K-sSx;EL&,TOܵ3}/U^o|M5͚~K H9L!11/9) wJ4f9))OA?lOUFUhQ*6lqYWW׷2Xnzƌ0rҤ`}$ ={,Y2:YhP~RR7~J-d8cqEqƿiaJ=SNQfؿ;"F1a -t|=U'Rb1KD!t=g02iA\vy[AJ]!m( %EA1HPKXrDہᛙ0@q]N lА%-B$YH}LdyFpP0("BA(ؾD j%l)'%"D1*8@A'%Xf?ij0R*v2YKVJ@~)<ȉd*mێE f/=]tdI5dB__#_ሢ7=_&|G<< )ר"^:v)A/V'tb1{ݹ5LLJ2xjѓ'Mݶm|SQsO:դiSdn@`~18B&9Tw wB5ms$o@T aI\k׮cׯ7=$lJ-TBw~5l1m.4Eg?a0Yh*ePfvj1Z@ c2O%VbgMc;5Z_q!0H`ДbXrN  E yҼz̥(&@1 $!)t"ҔbqX$7.]H^;&f#at"'&dM$;{/@;:ǂ`q akc 0HHRƔ|uZJ"ɂ4jv热2G/i٥DZ~N5sÍ˖Ya$ %% bn6km[P{FpWvJlDYi))I*_<nE{v;iAb9@нYOx!b"Bp=)Yd> Sxd&UPj58 28z?o~p= 5͛7QL&,4#IFzLZYz+ѵMS$k}LUG{dm.AgJFNh /OtjL&fMz6n^5߅Yx&Ԩ^uvء}{h4bE[]e?yчAV:+à7`Irz}'O &~t UkVO: Aځ?{zz>~EŊ;pۻw-wڷTm ;f8s07nܸ}۶cԩw}}7m>7( ݠAÑ#Gũ2:k׮vn߾C޽ӈ7*O@ͱQQsJ$J#lO䲏ԲOBFEEmcйs / +SA/ZvuбO߾@#~ҏ`WLW6,ک;:WMgx]pP".rO>…z.,,]ΟKHH/_wC(,qWеK;o;bJ;w ͲPz:=z.^_~`,E<# կW3wѣG@_|)A[l6A+Vdrʶu N8 ?mX`AV&Lܳ~Eiu=z$X{?C&Ol2g̘ڙBAY`v^t *6WĉGH.@BAvQdɓ'̦Й!L?مija@ G۰a[n=iu.\W^_(/뜭]fEC,Y b}'M|Ư[~qN4_OQ(Q,^^ %ۿ3AMQ26?'.X rzܹ-0"vi;QR8J؝SX4/ zl[24DOW DARR !h #mG_I4RÆ`*^mbu'VY Fp-PDX=ϘH 6Au bV\rGI P_# P 9g߸2zlA1Ϟ KLLmg)p9nn yld*5 *P`йa KbŊ=6*Qb)S_39$͠3cFsӧIdz^…I|w8ϟ|O//ϯ[ke|C2F[Oz~ԨQ襡CQd<7h<}FܵڳW/!w)fO7L 4,QD:uΟ;bb9`ik7776ܲU+/ۧ?CWZf {zyoz^i.];v|.Eq#8; x\mX @)_u|m[,{Çg_jX3g) J)}1cF^7s0|8oXg$)(*""?@mݶ #22r떭:pmj׫W~ڰsvBN6teddDDFWpT~ycZLثӲ0] ̄r_-B3*5ZeNG|ړp NS22'=&dwRD>=+AS /0Iќe!2I'$ULX1sbҬB> 'Ĕ.GAH{b  _e4i[ 4ЌYP %CIWF RY^+l0"ZYO]Zy! TF Rꫤ89 *}ըKLLX|6v2Y uPa{}di6O3AFZe KB/:P ,I |DbHX9ƣ Kkg p#{&U&M&z̥(&_/)_V$&ܰ% |/J 6 JvK4{_$ؕOjW]\Q eUQlu!Q5SZ-MQE'mpxk8QV@K%MJ#v`z v񋽀 pK\{jN7PhDXCK:؛On \GGW *>) -@#,maIq3"1(F5`mXFV)#e [ ׆ċ RP iAB&G̈O\2m*U?Ń;Q/@COG嬁J!ue'^!ĭ ]Ha43a +M@ÃjKUQRPJYU qLe 2woE+Xf_}đinZO(t-@>v)jֺiL /_]wdkVby߭R oq@Q7wD=gIq+TZuqrbӦO 9;,fKtQ}|f sⲈٳ{:sѢ7N!F))݇b (m2 }S;B_FH*+&dkW.|Fe`тpD B'Xk /g% 1mIVPG 0Fn6P L I!((-GJ"v$yPPBA[\<('T*S Tr6 (rt*Ʉ.ucUm!/1AE-qi^t . c6Q K5طoaT AA=`1 4:Θ>)hX|rIߒn]^/V/@m&#Tm ,b; inkזFs]29F +v(e!ncTK跈Kl<oPv; F+= TZY,f6Y_e?t'gTH;_ 5^5Z{/-LOX2J/A'$2#-PMJ(R W JQLH K)wmL[_p=hX!9A_Z əh(g1~O0 %'$Q# ,[XC;xG@)""D3|6hH(3*AwP R b4e_Au@$)j gUH3l5Kf/YWyB"j^+P,EF/^(oV7iN+H|?/Rd>|iI1^(@n%(*&P1\Oq1EJ) 璩lSj:U_ye2aʖ}ݘx]-nz}lfNhJR*^t ! M2fn[62I ُ͐"1%K PD-IY1#UGeUI],,c4laqh>1\v T3T9F(\PO,CٝZS40|cNrw]k448uA\-l,<*>6#H\H͈XL`pRQfE ˄b®%HnSXg**'FPN(n4ĞB Ġ2f?t DŐBz ELA [P$ T[ I)r{ 9p zȔV0#4vժ7~t65㡥\uJ4yԮ]F͚Cm%WWFk2Ҝu'p:իo[/x֖۬RQ>p}bgupFPk-@VX&F4o|2>*}GaQԉS c$% xbZт,h$=Bm ѢG8pg(: ,ÞXlk ''ƤNR4\=ekAj ܴnAchQ>:mE= a!6LZ` .C HhUm:H|lNlh99eoh7'q. hцh͔|2G`<[!X\-h*یV87f7tfb#ɔr9{'@b2_퉀Sx;E WTig04n/P%5%Uպzzxhu:ɔF% e<wh)!֣ɠ-v?u`|AHZ%1DDR@:\2>0JNdO,)M J Հ*3OPzAzZ̀4tuA2?DŽi,x@UVpvoXq݀f%]^_BWXHXyֆft!`IS98rw/ xc 0gGdcڡ+#(m_-߈sZxv ?2RŒx^m6hIHOi%^Ih.T%8QIHa+%d G^24n+. *ϊ >7X[sձM#x7o V_ wL9E>jJ!9dY𾻔z螀do\Rjg \d,`͞Md2:ArQI!áЊ܎n#uoBR;B~C UU"CJPoM`ϓǔkh0帊]О94XR 6?#d8DZ)Bku15v#c]VMC nRT"lFhd>G)& V9m51fXxK y`?,Hm!.(ʥU3Z/rԌķȌ~XJ1k&ЪA0HGTVB5# xNl*óP雮 VL iZpo z.aA4L,A" $֑ʉaI3ЇWGa(NRlK@P@ j%Z=l*-*NO#SXC2r{uA㠒B9aQI -Z q; ",l9xJ# sEK1Xhw`%cѯJe[̾)ޛ}/ Ft{5rE}Ld >GH%~7AO8I*.-9aP2{$&QFe VȊ{|EX7rUbE6y6-[# ^¢ +M#8][&DCLI0lX `V J1B Y qt vfa֦ah0BK35>F! Yr!.XX!4N%QBJ*;9b(3I0K ;WRܚ2TilK*ŜDjU`uN ;8MV A:A3U TQtZR~y KTR%U婲zY*: 7GER/:8i"hpJzN{/ ; , @X(ACc>:lئ}||޶.>߿Ok4ŊU) N0PP\@JjjbR: V_.P XhRL /X4!eSR&Jei cED,,[,GDFFFD)gLf\(z micΟ??ދfzܯK.}q" Vn]e :@Aܞ ݻwo7ѾĚx%ʻNijՎ?ּYs>w\b/1sBm ^B,ݝ CRp{+Ww[xZ_k7n4sfhph7-[6{{yϝxɒqcDz߸@d7oD< 1eT£-߷$Ӧ̀(1eʔqׯ_ի׫׬A M,SjڱC#F4k UVM>}]%9cڗ/sy._R shW\l ֫Wwҥ jϦJk/ nݾn0Ji2קj˔.-,T5P+VϗGǎ =w_f #-cS :N7FA/cGI`w~BFjӶ܎; ?`BUN)[ [w0ϜmWj:dj) R (֭=kVI& /2ePlAâXmܸ:e ϝ7IN#M5`6}#Gv%Ӵwuuy鲈H*`ᢉe`|ȸ]pam5kRKEmأ{x'tΝH9l 0M*hgl>ٳW萠ʕ+]r%"2bܹʵQI`k\M\r;wVP0RFC{KvU*u ׷ŋ;w >LիWϟ~ Wh޽{g_B ڼEK=?Kս[nݺa{J >U ܨWʾчCFBcZh>wꐩ'O4ѣw~q6~`Y-Zҹ˅.9"b|ްO-Vt \O8qk׬ ̗WϞ?<;5|]J:}WPV |QR & Q"E&NI+Wk  FfʝB@@@@@@@baK۾?)PժU-^72M]@Fڠ Oa@|]/iAY֐4{`{N4)۶nbVm,+ɰCnhlmƌ6^EC@M&5ǚx%lL[6o۷g9QSJT9v= |AO/WkܣTB`@ߋkZ,VKѴh|ժU2d**. X~ zɂ 6~kQ(|*~*ARyF1;Fz=%6,DĵȾ)cm`0o`N]WiNtI|HvB@@@@@@@}-SFY@@9gJޡKmkSxOSB&vg xAX(APaB r % =J]ds4/K0 % =J@@@@@@@@@`0fh4Zsa3aDbb{i40#yɑɑɑ9(QGɘL c(*_/^Ė.]ݍ@c?Px2? *vM0/ٳg gPB&)))YeVFoo x& /:wVq3ħ"ȍ,I,4[_8إf .d6m_*QJ=aM"M@=mo;~( ό5B[MQv @"zx,^B#Kbbb {-o ߼vXlTɑ@0j0OߏIze"w Еf' kE*TY+0e 2;- . &3B 5]@PժՃ %#KYgo2B7_q&o~E9 Vg̢/Tx_~Q}LZtIAY'uƽKNK,VğWW?KA7o?_ODF~֫׾ٳ?gohh~vO^o3QQ>6=xж˖,3x`@E,ۼub2O&?yFDs|&5N͠hMo5NuvF`Ia_pr^*XK\\\B޶:8za$3fv{@_bG'yY~>4l%))ɣE ӺYf" Λ;n|Uh&Sɔ2kYIbL؆?n"tȔE[3լX‰Edoߣ[IOOl\޽{w}d2UX\uvu LJMROt}6#N9wߝ^O/Y.t_̯7tؙk,U2jn߹%h6{Y|[URF!0'*}6Gqpt-߶ūׯ^ڣw}.U4%S tӽg#W_ۻڑ{c|47S8BJ%zMJSkW7Wm6Hqֺzo_Wd2jOyG4ㇶOub؊?ٳxA-:0S-;61hjqݲP۷c!o֭Zێ3Mzw7Wg}:+ˬZwO?g^Pf`J @hMNFo?l>u={\XX~cʔ=QQuBFYjܤŢsd+|}wLÆFG >{_]HMO ]reb)P)Z0m@;>z*⒞Tp5{ح4kwV0; j޻wo̘תUkΜ>uӧGE۳g'vvTz&rnw={vϛ7;NgPAlē'O.[?_~5sL=zt萔 phʔ)?Uf\+!(Lot(TvwmP5+o4Ad2Ytiߝ;w@Zzl gϞ\Lv?gS.ONU hU@j\oOwqxwn|b-^D, ׮]iִX2ʅøTB==\uڨ_e˗/hu2wཚegQnhO_}1#JZ2g'[k#p|Ǣ!]ڏ?l;N߸˨v tlA+99yܹ a ~XjX;INOƽ:5\-PR^i#۴n9vرk֝;kV:s& ~2K/3n\ܹgomgAl:3t__t13Ѻz\,F{,m;vI&޽{7olu ѧOSN0p@W6m޽{䩷juwXjUR߿oPAtė/ҏ?_pQ:uf;CS5QFne^ϟ?$, SH W3S{,48r1ݜ7ǶK[/l.]L+Vp=B êfWiy|F]/Oc df0E{Vl>^trAtzD=H=Z,Vj4&>Af?in]#o=oݛ/:<vmpCM?0ky/߱瘕w*\MG \k.^@_5jtvOMw<?zHhRP0v˘c|d %%&'ƿ~j|̔ ??tv:Ço8v"zp^lrInuiƟ[*R޺c;>.Sj]P'N y dfh!av8wD ݰaŋ(SeK˗7oe[ld O2%"""%%M6sqI'O4Yfa0Ǝo76aaaFvnl#G4h0|Νh4իW7""R" SNw^F 22`›7oFD,˗hРAJknIm3Yvmۏ0)G%%%Y522 'xpoE&iTYAk? QBQ>}F +UqcQъ@TThMpܹsL$䦈7IRWŲf *KcpԩJ1Cyr2v 7@JWW g|={Ӡ y 7mpON7'd,if?O?OE :eCjGR=<<W7ZDh, E-dIMHM3%h1elZ>jmSy`L3WiGb=#7m__L\cG].=$? ̉'ݾ}왳 .[iA|Z,-Py(:^04#cOaTe yҹ֢ſT>,|Y=g}nEF<ؠnZ6k8ʽPWWמ]:=yz7!IEZb ZX^wHP^ߓ4KTz%>!֣.ze1( s KpS;9 7'N4hЀ]"E{>p L;W`A93fOpss8q2\) /^[oppE3oh͛GA??rb)))b7 h=m̙ǎc6cL6^j l5hI+dSm9zb7oڹs[ɓԯ_s:z; èQ!lxԨUGKknmv:id0)G%{.^[U:%Y-@\~m ;vdO###@ӧoΝ CP[ntE$!gd@8p`֭Aq!$dl:)"MfT33gT`*Vz*ր\Cyx"`Ry+]]jrs/Z1/:PEzF=zx ,;TcbZ%edٳ5znCh6a?!#v_ww3nvuuqu:ѴVws.32tz#OZ;lTOb֚cco4g݉^_91w+Lbӆ~3eIZ_rӂ@MRR┩ϛ#G%&zlV~>c7З׏ noE:EU&/߻∿t*ODmb)uGom(nF7h@%ƏjHշIi}g&|F =]Aok8>tń^-۾5^AU`f0XZdJ7QfAnS7iL:cZ=dκ&I:EX6:Ǐkܸ8 nil)w;N>ի$;dEJٰy慇5+I 2o8pes/{! E򨉀= apCUbS$[ -Z P/ ՃIоσnzDndd}K, ԡC[߿as"3x -ZjժhӦsΜ9SRLMʦ"rtj&I533USPYZ1c:z-[rU o+WMnE >"!!!9ds/T &9?lP4M7Wm|Rjj;NYlz=]l,%f ܷ=<< r<%>~6L~ Cҵs=ur|.ZWɫ䘸AMoUrelW݈5|?t))Z/?N'.6jj1A0FvmóBtz <łMi2w/6~M-?4mh1s?V~a? _ի~<_u4e)D]܉'Uleޟ=[.y==^Zw]^%9}Ɨ_~ 3/^LrTRe͠^~&V dsW=z;1:-]N^1k,tk޽&Mܴi3?V+5l6$%%T!*" 9#K?.8p5-IL3W6ܔS3IY3E+}ҥK,Y&+ >}A 7@޵NϽh,q08Bm,.-ÿt?&ӓ^ߴQY?O KPz@MAVpiqn?;ծ'Omձ/ 9#ѺNgpuZn ~]-|}}޽[dIS%KhM0]l$|l} $!ФaG}i3gΜ4DB/v޶*͛7 pRw й-xA6o~{=m_X"uQl׭[ cbqw8ZN'ܱo!tܱ y}{׎JIMݾuK|<Oi¢)$4NQ(J55>x.;'+W/pG*Dlм/{my#tiP z(6l_ t\qep?骉\ wɬ}束fLu9:;p#1(n#7,X<_C)~@Zɖw?z PZuM2tM_n NH̴QdIVBa۾ģ =`7dw@?fTK@ E5a⃺j`,RCC#Sw5uܸ^7JpT }}᳻=?b膉KہG2 lΘqoaa!,ԩSԉs~s|+T{EP?5O?6G}z['"v#~@ކwo2тuxD{KםdC%b< |=Sj}> h"Z}AN[DU:Z`0H,gzubllbF_TGz& 6v;^tOl񊆿\a sw*X臌X6пxݻm F#< ݹ}۲mqq۷m LKOol8;=Cn*XN = (DR6oټa&7pfLldҡc.Zi ?r6y^x>pqz^쎵hzNOfȶ,yp NzwwriB!b6#-^Cv~D/[t'^6_#bo]r/]wRwnB(~DwVh_SxGsG{vBD*rXlx644GVZZ<%aUh[SPyaB{ҫ.?~5%I<7; :cd2 y2#5ދ>鵷v7#۳|samŚ-|>8(u_h`BBT>-/22 ^PHpr-lfLI8u,>\Y ?*}՝MB=nW̻[woެɧi ׋~s S@,]_yK u;Yߑ#_GoΝ_>펴 ʦݮ=s߼f(AccXBP_hᖲ-+9x !?)>}#Jma:Tn;{e^ ~c@b,c9=g)Y22ֳHNN{ɷ?<1&||̸%33S`w%}p ՚K(@3 kfeխ:S'(Q#˰G{{W֗}ޟuQKPȲ+&d6);x _vx‰O$ Q jkq^O,;Q&55AY]=$$?U('B4{ħ=1~ݿh=+r\Ηљ3 =5vN!Aip;ե@2 oyVM1c{s2.o_?\њ5U9=2uV&ܴ⛯ɨSQtEJ4~`h?|J,q6a T8۾?9dH98@ Y,.+DY{`y=iii`Wր:0{i۷3h߬:Y$茄&G>k9>#vha?,~wZr-pwSY+#f$!\>Ԅ?.[_W4&L#eyI}oi^{O=pSqA<[VU /JvZ£^׷WZ[mZ( +r|>4@ N 2q!joظn[Uu+x'i_H)8(Dha4MYי%x՞+-UtdYkJ,iYKcȀ $JsS\hXRc6Z"z[5R  K?{vm!*àX֨@|͒{]W~ _3}aFq춈7|?^fo[w~O=9̊;}|A}3ӆ Ev.=/(qz|~%=;V<;߾X)ζjꆹ}v_Tyʝ^C=c,,@ri*WgC~J5/Gg[N9[T455=ujmm'|aˀ@ N'+tǐ5Q!A%*~"IitE3fSp^P'}8hIoS\Q惀jq@ub$[վƗ|9Csz:`ӞȻnn Oi{i?;-qfz~0Ldoضߵ ze%mZ/v7?vtمyoVօ}_weyН =@ #lV<>,˲]DA*@Nؐ#Q^ T5*j 'pYq$@aEEcB\Bnv>}cIyܗ_64cv+0Ā,AzYdHG PA?!H le(uKd}3|w ~KV! GΝGy*D\=a?_,[甉[ۍ;m]7M:w֜mF )x'7_}T~@HqÄcIРŷ<: [6!notsq _ǝ' DB7!RNN 7[~;l[3H"8T#+]pܒ=9dH:E5؁ק ~Ei{W]Sd^JkOh F]321̥% KѴlOizA/Kgb>(qLaqBE,iV^]-F]WfX6- DS /K(u8F tw)vEq^CqVqq4M-rIEj()zc c}nYoJqx^| Pōt [__D\1ױ$7?Uu̓ sxjfOz_~7pˀ@ ~GەnKOHKO-> P'8L&Lͧ ıQHA+9 ɢes1f- rA$3P <1<$ >Fah 1Q&^V$?AeH 0i)8%q/zZNݛaB3찚 h7`x*@5[S Oԕ[gf#S d' n.Z: ΧnfDq#tLturrLOOxUFKmrA}ӯ_S]82#%@ &+bEuK&-S$r"S #ADe5,˒L(|ehX ,HN8 }>$*$9!ƴϚ|@pY8I?PzS`0(1 -b NcF֠4%8IIa (8@ FEAXJNbA RKQP0_(F 2 @ȋ4cƌIJ"=K5E=ضm D7yNuQz===wױ yl+Yry.9%B 35§T;@2E1 SDIeQGV>g>IxDfd 2qG(Z*SթIi`p5py^tWI$Y r<#h)+ܶ{_ZZJsTqBRQ$&„AeEQC=sT#ш Ou)Y R= ҳo>5+W45bbF3L46v ᢢoU*4 kVG$&k45mڰpKϼe/sUk6h@~Ү @GdEeuX,i<S$bI y߯W@us6Zou?>)Z @ǐ$ @%#H  hAh%d9 aY>n CB MBH@`j&|#XpvCrܶk.8A*[Benٴ119y1KJ{syٳk:hzᥗغ%)% Ay5Y(JKHs*q1Ϗ&ۯx`zVVMU%~ŗ@ u^(&p FcݞpJo`uVQ ~xJ]AW8h^Rh&Z;Ty8^S$ ~Cv-Fx4i#eO(H”Ifok;=='Y`Bs7~v^qfz}S]qThϣmj(qQW]SlF$ض OH]KǞaO$I%$&^9Н"iaX $EEL[_WcS@ r4Mk0@} H@TU #hBЦ7(煀UHH ZV` iin@h> ÂC !Gdp?BvHs9Vmů4Go %8=TDB4XFCLq h&G"d̠h[YW */AGќ槅K9Uo,,4'M-^q~8 ž: ˸%}7cӾ[ /(n_eyMC#I`͛Ӌa$As M6H?S]q%hSS˲xHAtV@miiݺu[QQ5 ;.>M[ ?fյ..mv9֬]v$.cDLa(&d$Ֆ֨:k28Db r(@녞++~|' qߋPhtۥjeU @*?ҽ{lal\|]MubrJe~N ڽj47`N߶ySڧ jĤdaS4+)YVJeQT ԲA$pưjݦ-Au. H ː:)(]AzoeKeJ>KCMՈ`l:J3(>*'ZlCu6ñݜt`2vKϿ`o_!)(IA1&&a9%Bcq SpERMF :#垑l߽# cx}U6,+Q#읓u JLsz㈊@ N[[FTBx^(lgX=fuVcc3 0[TuG=lYr{"X4a6Y cAG]M"Kg|Œ <7j4[[[t׮߼sSlי00 hmޱd[.?f4\g_Uא$'(1(\4<Ie FlֲEE9tր$(%-< P-X堞4h'@v\V Յ|1@0W{"$;3dPB{IavsٖBL\:ѝ ;W.BiB#'YO|`~'CRU7 ۵{I p9 gMSux]ZոEZ#Y\ee̩?G{FZn]ܿ j#h;c`+E"Бb$[NNFUA Ų+vjmP`Fe%ۈŒ+c~ ZEPr,{5 m;w} ά[L=z@]}񱱲,_["@ `HuNI@ D JCl֘Ĥ8# " {SR˪qE9lPjU[%i5M=2&!9M@ *(_8Wreni!Clٖ`kXjd)虴8ƈAǝ?n޽t Äz5( ;HNtw=lR7]3iO$IκCmro}|S&M*swoqrrNN/n59iYYIXesSgI$oo*--H'47)š‚ޕT]ٹ)9ĝ;zHaB,P%B1X jrA<ðZ}}^пϘ>쓓!>n~}̈TVfe>};}OWo8@ E "2$&cW Æ|Ͼď1fvB rFϳZmvuzqhf7PHQ|}=e16ߎl$0e):E&6+'55)~qf^ql`1z=EQr5? cyR#l^~mA%V @]M: 1nO{卷h >k9Ņuѳ{XJ@ z"H" IQpY H"\qQl\Hk5,! Y)]A 9s8:{DNW8vR %9Vƃ`~a•YtuSUFB(zb(Z-g;4k1-=kƵbdu)v(Q ެű28nEDCSs/zfЯ w=?w;D³@lT{,7m>hUUw﮽%s^|% M^mVuypEgޓ //2Q @\ E "Q!ưq7\'aw8(Xn UOs:\7նRgбAlmJOI<yAIM`ܖ#>9ʌNG[czX|}MuumNfA1[ mnLΈ⏎P| b#C TggNכ[["s_x}]÷>X/;w-xO5ڿ/מE}#H7޾IOlj ?誆@ z4H"  V (n/x;J/E:LT MhR 41"OTGKBӘ`JnjWao#4l9p8Zv67[j\fnק'ڤC08K  慞$}4(Dk+ +>;we*˯is4<@U(@ Dg0IRp SW(L!q9/+=E5f@u{Uy3l é+ZVٴqFПkvI8@k&}%^SOX)IU57M6;KJ+X͚}ձ1#0ct ~FD҃ØE%,phiyũ. 8k9}B &@ @h0HbKu66RTk_}9Y9f&IU---ٿ?slcIS4LCzW&'WQ²H0$Դ-5NcOR0 .+A( u0 A İUnP[C**뙆N}޻_Z-;v RiӦiӦ[n^|Aa8'%%u@)SNŲt]u `6n5WUUU\\mޱkꕫLzbM-.ǦaLq5xA!)`aE"E_KZiS|YCk @2PA0tr.! K+ j[D_7~"2q2A*@ݻwYFaÆM4 j“YY.\df8h/TS?)Zϲ }~:-k )) >I޽sc,;Kmk,#:{ hXcf H&1(zjcXh@afVcԛpD(E1X.' zC7Muy. 8A*@jjj^5v+++Lr{oرc^ի].]wW_Aǿk=g͚oFO??C*-ٳn?s]w]8l޼Y}[nf>;yY8n* pPFq:Z\^^e+k,=KRB*TvPT,6Rr@OBX޺ g E$K3='m*2m:=+˼,$@ b8&bLi,8َ㡵.BrUGD.P.iaܨk 纉WXT@ @ ?<tM~{At/(lg8bQQя?suVP_Ǩ3`PFfggO6 ж훗/\ӧO*g J'4If35{SbM1UW+蓛y^V%=TZ !NQ nPBa I^?_^՘[ 8TT)Vl3Y.OBF ܼ x`e mbr`pGrp0|uXe]ęʅA*@B y475nۼ9%-W~F".xuG1FG<ãG^`;o޼9sĚ;aʔ)NJ?,IXFtNrUW4 MMM]g8[YQ"cGj4FIp93ĘR2m F QASL@d%(S$Ypj޺V$K*ؼkKEEIu&+b""ndAx/X&[FQgRDzThKˇ8ɿsNu)Y R= Gkߖ 7^"ӎa!y䑬PeYq}Ȱ {i0F V:t̙3/;v|駓&Mj I0 ô?Eֈ x':R3 `LEp ^,63cYyM,qܤ"F[nn5}˷z}~1(d\I~hpx--:.ng(G "͒:W6ztCyy=A(REy8 ؽcG^adŃغdZ6mXt8h/5#}D׮\X_oϚd B+kyOj[/)q3 h78)QHҰZFO3B9ܰt$̩t"dThx@ۡF0䜰{UP%$BY T͛ 7Օo0<j,k&C~ONK:j7E8iƌ?VMvyP/ ͛F^O V.ߵvw5~Pz΀ⴱ~IqH1U8gM[f(r&-Wnr߷dÖǗTm.U,qWMɸۧ_U̻|5q&H@ 8Db&ʪB=Z-QpfD)@ N(H0hdB0n?:$Ua>hSH"h1 ZfjhiɪVOrB yI]4cxc'5Vm{eb8b9V5*9 H#0M;0iL$ aFwC^&$v(`7+<q*@*aN 675_/˖'&fdvWL eA6ϮG  p c0(o 5u٩M8 XbȖFޒ7oqz2'Ħ'R}.>Ӓ$bULV$|eZJZ\,A+2`f {J\u9SIRgFrF:@f]p t L77锗g=e@ G R=+~]q=6vo\O9~m;}'$X^l߾ϋE|bbҌ;yv8@}@fY->9Yɒ;y]IvVĚJD,iXPڜ4^R2,>>(TտUZCvgAY*uMV%ZCSYzImo arը/t\gHae 16(tpU-Cs넯~ݳr!l>v+&B z6 E qـAD'K̦͘@,|moCSYBX v->)ꖭV MzѪ%./uj75y&`Cht,==,o[Z{i^9,(,+v~YGh{ԅQ%ZoIGÇoU;V^R^ >_;oXs_gH?Zzɘ~v|߬!k7$5]unOQĄхSqJyީ:vs0W?oVNqArPqb(^K8lp0g!c Ȕp~[w =Pkfh)l-:b`g),/xur{F.p:}8+Ojs9Z-ӵWZ^/_T{F@Ca!:eTuQL R"vBu(W'8P@ Sq}$A#I樁^FiIKS$Y -z-GQ eӳs"d>KE=}8p<GjIWkR<'JOWlܹ){w5 ̓h%4iEP$AGO9p}k5k3̭O=2D1_Q;K@ E z.0 NrII?+7`ѕT58厗i(;V(S'|*Zii%,.hJט N%&wWiUKK)E R"꒡ Y Nu"(CA*@0lR r–i 9h g0 0EK~XQmtٴX E+ F4Ws%1D{^Lg&{%$ft$׷:U7xJt"P$&<( 4N*KSC{AѠ\с8 @ &eB)nUiKnfθʚ /2DNyc4Vbu}Zh1s9Iִ8H]%6U@@fq4`T+^8̭K!"4PqA3,IZIjҲѠ}cɾF+[g6H<dJIsW4F=&''G c |`EmuM}. Is$+#RP@c) .r0H"4P@ P B-rX2}FQzw B5tEXPS,T}֧>oGEPNQBHP % =!m8Br QIByy܁$_fwŔ(UPޞpOϨm']H_C}MqPprPfK*Ⴕ8(#Ə;Tj<$#4M)H$YfH% )d`cʹj.FsPU$ ) QJیQVMZp- h^g=[O?w^{m۶OjϔexE^LLL8{`Է8GF>Vjq @6+y k4fӀ-p?o%,Cq80k5DH 6x{"ބUH4* =? <'"CG#F]ٳ߿ނc=m"ªܥE߰EՖ(x598"aݦBQ9W^-Vi8#C} @f"E }xӁںʁ3Ԉ!M,*D.)`3Q5rx*l'nUE C1 >`Z\RF@ mc6[ӡS15;:qݐغm%zd _>]ot d2̘qG.w5y3gI^MLLE<ݹЗ_DjH=zBQ7 $I&yO~X+S414OcPt#5s8v()*  8$\GsgF)e{U{-0lX\XYfǐ!~l5o?~/8G3Ѯtb(H`h,*arB.&`7\!MrPI~ Rn1bY=}|sK'wt)**$ Ufj{aAkW~_UY:|VE H[sHVd-2Q1")TZvPWHmO`PPIa!I1 ((H^q}sϝ%~;u9woJR /$,Ŀ@x8#=$,,a䨌\p{Eڣ4ȡ#G>yn褄h5zR8t@dTԐ#3wv>{K8e^?)i}w棏5gw}v?/M:yXfD[7mDzxڳWQ%/E)jPQ=姍˄ D1EkO#Zt4hKtD];Ɗ,ۭ>ޢ-6ZUޯKW*;s_Etg`(9ŬETZ,X_rQ.r4 "Hm`P։**,vՓH/cَd6===Illt[n}t}rA^u7$4M䩓'9Qm[C sy}Húš2A_cluuf6Уsɇ%]ae$;)b q=T.:%&% bYq1odVfW(lfl.;JVH?vD=‚Qm"]ۭ8Ca.@kxy{*Up8V}L/ #yǶFPn'9էn8 Fc'Mp$TXN]":^~.0Qӕ"BMu.a On:]OU;$Z$RRdd2 ƃVOYeYTE K*C fڸ|eH}c`3 HSZbCNB$#U24,L쬬cǑJHH]AmeEEE\>1sÍ>EQN8?  - lw.4I7~b=t;~p}.W*+dTvl)PYF E,"Q1|KqDQEifM;m̬^Q<%qXEYSvY`5<OITZT+8{x6e8K5lRJj'Ia)m#{ ó6YvE3eEֶϰF^4sjyWIjƪkV& UVs2K)e۝cty휪.FNŪ]q _s_(PPWvnI程Gm˾>̼)5kđt@Dlj۽7JR(rݕCoM[72s+|䞝ß$q̔w>ٖL+{f]9U ڳ3 /Ϲ ($1j*HR0EiF,є|lw~IK:SZ%#*J9f́D eP-vDMahF9^8VG[Ai3eTA sLy ao_p&Z%!#aD~%sc4rUH<("9Yrb.RiQYBF$ XXw = 3F2RNPTEmT[W֞_*:pyrЅ?&}gБͱ̄q?mт苏ݴy'䞷}X>W,-iWY7бe*uXU-v݉V%,DPBAcCZQPLR /}gO(= EK"m%X$e!9qj*/-U'  2$; gUмg  w.*ɧ,I2F+V3R(#d2{et4j it"eL("*r ֖;%IM*VZ6Qqkс:1I6ጁS%EK[|\:D?=+?*"@/i u X7z`!~ަgY^}ROiH#ǺEQN$|.o$폼i@s h[{4H⧶; h$ޥVoHn!ɕvZ$)1M"#O~+$|j-pxz} &b8SYV1$~2GR#p'fަHoI}G)pU~iv}0 ϳFLqqZr(IBPXs@fYYcP*YQErTbΰߤG;7ɏ Hd w4?L4J[\ї=;ٖ>_̧%59e|<7+,උ}?wc9p5vf{>)$ТHQ1bQT|;6g,&UBm9Yّ$1,I,3A7#2x5l<lժNBٛ *$2-YYΈ$cJax$ XQ2)+cBFiX +(Ȉ,e2*B4bQ -rD#VWAQI5ZI4emWcETk@$L^yII9it2C`slUáV!ڿWӻKĤ ?[|eh=ukW$|]oL*Zͣ)!F?_Ùe 2fJFQ.ZH .*=k+9e)*'2*F?gc*9lNrm%ENU!XXbTDRYU8fDkm)K풌U59DnfikvحJNⓎ,A pE&Gtى_m[2#112%"f0o24KFGcI\aÒdm6\cL Hz yy{^u<z3 k58Z-vk`Z_Od(o}8#yFX%#I")TV%#OYI2tUQ&![і%HQ(Qv*XC{}2E/LUqIӑ|+^; ZaPfF£yZ}g 2+Q$ҲDK5%D1"J$kǐI!;-e-[z~U4,9@#Ee((r^E%ei/i{*$T8$U˺C od| Se˅JUO)E0*B$yjeUƌX:W.FƔ(Yۃc hic+#@KUFT|@ Z,r*|Uƾ}oto v䇃'.8% _Tv IO (U, ھ56QmjTK}Zt+J"*}TjѦ,m]M2cX?ñ"R% KvUlļ3y KDITLL" "0ݷMj Q#CYUH #l X&'W(Ū- 3FV8Ќ =zڔe9;'m(bqVV~`2|=j;իBcN[MV)4*@RoDQ5!SUD!Z'?\{PU[ψr3DiR 2Ғ)^jWbtK( yJ#m=[-b'TvD 1@%#jA"uUaU(Qyʂjc*%[%2^1cU DzVip2Id@"[o:|ZRh[Ett#ucWꎒ€C8+R+*ʻ虾cssJI&? yyy oZҷ Ѕ!U{\:9SLИ!2YciE8+Hഫdy})TV2#D967RmhSWt KԪ`SH?XaQsUtF\}[6T-zR5rUPۄ#oU+f,TP IRbǴ'!B4#Hm)$;+ku3:r'I9~`Ry8#=$,,a䨌\-I?Ss{wƛZ @BkMW HmP"o:GFE=QS{ 4Mz 208 ٮe![{@ m{iE),߿{W.]k*e:#8zn8 F i;wlۊ)jĘ~~-lqd*k92>2O?8eN h&k:Er $JYg$xQj $|ʲ|6t궭r@ݙO?treʬkSgk?1FN mE9ux@`PЂvOWFDf9aWˍkֳWxD$ zi9![s@ 5 P3c橂YeY:qd{u`OvEέ/qD,3WT#m;p]v-;qT򸄞 E sׄ=;S r%Iܻzt?S%2׬E]-s2 *=4T9-hy/DgXω9FrAݚR5!=pm-? 9UӠ[np騨l%˺L~lHOӚjPJn˘02פ[1t͍$[l#?!'=3n~n[yLg)P@sawޭGPb=_9ƅ핹m)w '6t-?o912ID3nMc|'riٽc]O8tk^<4g=cH Ң/ٌ)LHOBǧ{zzΜ9СC֭}w瞴|ぁgϾ{mݶj*ҏBO8qw=zjR|rW&cǎ7o^hh5k^xuI[lw%YtK.%m>sJx D ӦM[dY%n]v$65jҤI;wt[y?9긶KiC`gQZBͥ405L8FR())!֭[`~L^=/ ɐ۷֭2zTꭷZJ݈#<<˶\t)|S2Ç̛7oĉJV駟,vo+RbŊS?.>>>h\m,C)B (UET(Tu,% ؂va2H$ о}{՚L| R(ʫ;k׸-?:=zڥK ߯-Z:tPN>>>/3fs~e+!aiU($cD!(XSf5;O.Y[W+.zdƌ ½p4:uێsUVAos߸X]mdCQ/iAtﱼ䌚+`MbDQݻw^2 >>>M52 " %$gZ7tԘIcL cG8}\(h1III=peڣIUZN1U Q(XDs}ю>}-6`2n& jPz˛tZZڹ{l->Xp0w.<11z:vctaNjM/9\9Iݧ_9KХ=_8Q!\hPS _fYcz?K[û]xo+2jE[#"?47Fɬ 5H_i7}q>82͆ ]Ou+ o7'y$e=vS/;rF:54MIQC)T_͇kDoe>6O4r\h[_YQA ڑBdLRhEE_'e=}j7X5i/e &P0XKY:V…iVq@_S^ٱqMO@\#;x%o 5ۨŞ/9c=}<9o+Wd #o)I$u}xZmD|ƭYCQumgNMS2s"R/}5a}ޘ2˲(u*I$SYKZ:Q]?8uCMpE@ mK(8^p8 Mj#%mإ[wɫuڗ?/n7L1^pMc9DPܼݔ?PݽVi[Cj67#nGUkn] ֫=e/6BYUTBbȯѱ*g:Hisi+:^ A1vM6ݣ{/zmg߷Hѡ&P=vp]\ܳs$޴~̜;Y989a.1L}Oj׸bqBBBA GP79;B[ҟ>V2v/.:|\MƯsSR]"nA6W_m!kUsW+GSz~V{GO8Z,}aپڳfTgj-9]3O̺o,)'Oٛcfߺ*),5X;D:xK\Nm#!.yE}rꪪq?~~vi:\nK g^IZ{ybMھU6Q[$ Es-qDAH@}^KAI@{([%SK9WaӘr;+WP@6MΓ~>.))ٞΉ}"00ln~oT7t&SCnRGжtmױqz=;fulyv=$u ِ|K}_yumF ZN.!keLkRz`t!v:wfWK_)\1?z3f"۵_Yb6EG0SzjwKIx'׏!ܹ'fH?ثwϏطw7_}fےF Fj&w7z>Pɓ?VrAAO+mgjxxy bΝ;7kƴ{Λ ""G9ĿK+(Zvc>j]x́ 2??=1>}={vi /XiȠ>KyH/XIbbbe~7/mCXL'[WL%ShŶK6c 1C,ۺC}f$|Bkm[~rcdcF!gv%8ћwwjc#>n?Xn?3mkn{,[6?۶m+=RI3V#OyWH})yy@32=u*ck^~gCۊ[STnk>!ȁ]W#C\ ~_(1utȣS_n8޹[Nf)޵kH{R?oikw{Iw~~7N@Rh߾mVv0ڑ27N|iPJ}lqIMJr 3g}fߛs=Д);j2٘txM7|aZZ_|I:$9wz 7qܩSƎ0c-0 h3\k]m.;b0A 4 |H6PUfjE*T.}յ.MiKPX-oǎwsا_|8U+W|e-u"/)ƒM)f=iӦ}_!jC'Wfe yp, ʜlʃ[?~Ȑ%WbYvu 9}jjZ 8p᧟L>c'O#"#v{]6nЧ_7ig~#wHt5hO?O:Iں7^ߖJNɂ3fd6Æ_0w̙Q#{ꩧ ֛o}F>dȐ M1s˖-o;Rw4hf=> 'P@V'﫯 ѼO괙;3ms?磹H믿#S.]ꫯ8pԼO>ԩ4B ~g3>3Cǎi;SN9}<p R(y`"/9)U](3f^ Oџw犠9B>GeMMՅM{?N;yy$8!??oed6'j%""r叫k״oub8<"׵EQ\Τ*>>~{rJE"(y\O/򍊊^n}mZKХ ZR"P@PYE/ ,7^{-Xf5ͮ^OCjf͜iFڽ{9}TlWf Fj4v3 ??Ӵ`H~]nSj?.!)lhBrt?ؤɦMxң A]X +- co~5I.rԚՉܹ|senF:ikUqi o sm'UU=}ľ]ƌG@#U &5v-niNwatnmr5Rh[1n!}>pJQRTk֬jR|%K 2dĈIIIM?~Cw@+0yŧ)TWػ|q1veXƹ9vHԹK~^ =%Ň?.*kϞ&ScS֭idH+-}ҽ0F@Oz衉':jԨ =WX1uK-Y}vqꫦ|{{'^|fz#RhARh{>GJ?'Cw$:,<"x=Ҵ} l"#;tn}Uees *ڷoiӦ\|||/^\Kw\WdQ"HmLEYٰi^q}qBcWꎒ€C8_l 'P?<5{z9l&qu#ǎJI2~ͥ%%(/'׾CcϝA؝R\X<0a{챣G{ƅED29Q@1K{ǦOx=KW$WTw3}>5U`L"BNCC pD>ȡ#G>y&u]^H߷ CBa H %5!a];I '3>W4M^O G{,)]:KblxSGz~ϵyz_pFzHXXQ5 :`4q+))W^s 遢3gDDD@3(*ȥ 0d.0{LH#y}owdTn  ")Sg>8>_] b UzX{[lhk4j5b7JPPQ s8o\fggfgl9ݭ,+LWiwHSuiֳ {G!L:$HBWR˲{^Y}V֏>Ak1/// ]xq}{w>ot56GCMY̺?~=\7aеe)IT*д0JJμ'?~ث$#%%Ybu ő]<4Ӌ-.*"]A>ٙ'XyK#J^mL (Ĥ|,)RI-W4+u***R,x8hK*ݻSN-]޽{WsN:UDcWR^0 %-L5wRb7>e'"h|W'z; D-̉ [T>NKKKxȑ0&fI쪣Kk覦a+^1W"LL-,>Q=huaAV٩sō^$rklY^祤XRRBQAS_-Hb„ $b;v)SmFHKK˹s(njժU+W$#QY®\BHtr.gCʪĴ{XaTQvVY'u려DtdW[V6v$SАD=m2N.n=ɛ|MM'ik٭G=·oő)-]~\edjQgˮܮI,\55޽|lll"##}}}CBBF-͉'ݻnxjԧ;]^>sc1;pjgH&KH  :j+ǥ7/_GUrP)͒Nlc؅?OT#c-}N(/+74V!l⢢_30pqehl|aL7?$%$'TEtru]RR+Iv5eN^v]H$%%%=,,-LwIqŞը+nne-vМQ!(q…G9rܹs6lϧ7 r#G \Y2rDSS'F(+2r͌x])nTLey<Ȝ5_0%^ zqI;nmraNrt)EC{J?(El>v$ɦcYYy= $X!A+WRR-|y} o_*Agk'xJR^~RfdbB~DŶLwIqŞO.q&h_~ʕsZZZ21w򔕕UUU/6b!t_9)@ '1b ʪɓg:RAg]{td\4իV0* S.2SŪ'^I͟0؃ڔrZ5 J\D]YZZuo@N81x`W#TÇӧO?|={|}}<No+7PٺԑGS,sć$&s Ӕ#?.#=9z] }{?xArHlyߥnt6ӟ1V=!cSеӥ*#K /ϸTZ(ܹssΦs֮]hѢ? Т%KHO??~ڴiMђ&?T7o%_R+f,N[}Uв:$Ǡ*oBEo"TaYy>VD O֭;|-4i$z?.Ps |RCC$ڷoOM}#G_NGHG۷ɩ!lIz]hM*@Qܩy/B7G/^`uɍ]7ᙺ!'c uuu_~MV$ĥb/5446mT]]]SSS  jhkk׳}M2%,,gOM?MBPbɓh X# M]55uGdЏ\ ğON,̨|*ZUXQ+YUq*"?Y{ F+/>JFeq:" |ZN\|Wɫz*ϭ2?2F͘1+c5kL8qΜ9ݻw'hH⹈BAihh(O0(#aU&*%Ye~eYj2Zô\#u"2ФΤCfE WNEGS WUvj .ua$W jr+Џ!W`&Rg*s[oP#5=uTݎ f!!}L1)ەYLqbʊ6dFmbz+>hG{E R®W( X(4>0Kyc*[ݲej[6B[D3ƎoԨJGQ̱ԨURYD3&y\% B[:$(Oh*#PEo1ЌaL^dmkonaA2Kc" {XY'%&PQk泌Hhjjtrn}֫ڌܚCP1q+BSZ5Nv]346HOK@E =}$wtԥKֳ RMkh5X-̌M녋 T$SQ(Al5$"6MⅡR m ,btvVfl6L=,u #cM]h;<"%%%Q;W0 RVǕx&m7*HZ3r["gΣ37PVQ3]=c#/3Y,:M]h3Kw4>bI[/vN ?L%4ynڲ_22;'9馍6m635sj:Ђu0f/J'jO2KJJH JbT2 >ʞ=8, m@u\bUTehdt IUY,Vxd93g~H6mٶ|1s,a<0eZ<ܩrru]RR+:jԕ{ouVpLׄXR]z5Mwm`V:RQQ|jS4-,|K+VTXl-| BIR])FmC< -5rA" ,Դ9Җ{P E%%NK4(Uo@%%%a{rrv*p={-xLhufٶusuZ0eEH m,D! m_,--mսmruNBz:8zbl 9CUU \]7gMBGe Yu7(~ cPGSw6&[yQ^fe֯Oҩ<={N;=qf#=l]Zzr(_ǻj%ߴ_mȍ{NuwcKQ@NpP* @q5w2#bH0u[ˋB4[mv`dƿKJJUEYQQQ@N$~zȉPhLw^US)>AL$QvIP(o L91Ph׉ q:󪣩S{QN۴D/”g N %_ ׉7W|9}M{b}h'e\ˎ+;޶{G)GǞf47џ7yH@һ-'0*uQ(4j82D5m aG.Qh]o* ycPO~_r |*q+5mڽ,s7^NX~/>|&İî;өOsrJMEǧwvjVn۱f' m ׅ@XHc.)pI ";r:n-?] Z8sϛ:Euy\|jϞYyQc +L'%iRv]q,S#ѳ~:r&Bm]0#/jlj[R~D/l9Db-Pn8ȭQhc{eSw 0 lQn#iVb7isq9uy묡. VSrq PSQ:`zayN:'ۑs%%'1' [^O)&)&.IT) A5UA;i mVLҠfBۛ₦@|])d AedYHƊ׭gG}TV-$\dBM3c=(G '|ƿ'fæ K#,xG}Q1sjkDYG}<8#*r C{ӄ[n;xȝKrlw@cVSBڬ} &jkk3eo^S|>Wm?v.> VtHo@O;eЯz{m~@DGmv*o U z|>/B$? ~9sڿP#IQAx6e%%e)({t{{{6M/16=c§z.%%8D]cKnþ>+߾z *Egٯ\~9eș?n =|X)G!q˧m ;$#WS.{OV'j BC+-+{_^UYQEEZ& l9ǢТ -3v >؂f^G.)As?AC{zYv4=]  |Q.]ONPI/H???ТI_ Qhc+//o.@+Q`sj[ /NRØTݻwqqqTIIb̙{nMMu֍?cƌIII!iɓ'߿_OO߯]lccs-[[[''ׯ7A m$zfTb}]v}u/:vPz1p^fԶ}hObC5kֈ?$[ooӧّ2$1cFbb}Ȯ]*VŜ2eʶmی,--ΝoĉO?F Zp(e>޾-p¢س:JhB#/??!C QSS[qС$^xAcbba˜9sf˖-O&鼼QF}_z5y;vlCl" mV^VVu%jI/ӅO8vͺ;.X`@`*9y93-:[\FwZ{񒯗HI֣ۏYu DNQ(3j.'ODwI A[)R I*0`@JJGIU/5k<}tΜ9" @CX(>x6&r>f#+3͢<1e_EZso%ߢ6o'*x 2vZ(N! x=<%%%G[[[)eH8ݻ\hK.ݴicddZY6EP.e(((^444wl?~Yj5sbkgG6wH;`#KzSZ/@ȄzGO˽b( }l^LC80p$0FS*|1&6+cR D{ mlnBӊh.4*mmm{{{*T ~B,;ǭ's:筸A !ܼbz-Ii ņ͑VÝ;ԁ3rZҲҲ2ձsVu M]55uG&Gn̼W'| fږAשKUn@ B3n̬=z˕nϝ ,OhY" t[=‰$DV]MN\RDsuho䭠~NRWhaCz۫(qNIYj5sj5MM49Y/ u59K5*++~‹EMU[Eժ|Tty<:KWr,\OhA_{?|9 G tlgȾqɏg_(%#h-O}HO{θ$gH/od1#K-h#O`J|h騼.(z#&U54tT_Іp9M݅G*_( msx]-=T?=zy-forze]<?)++pfڡlֺEsT/]|`*5h-Cu5W"ؔGIϑ4&f6/nFD~jUELzƵkל_B+tDb=.JOO755 4 DJy{s,jOI <+^c#xTA`{Sw.4Icۼ(++\tB/^(٠ayyyhhŋP#I9瞕էO tD?5!%%e̙=߸qЅlvN΋ثfzy.]~aO} bQC3ysJeybFGnIxQqiud$]*hQhcߚ lZ",--Ih"صkS(ƌC>?%6͛ٳg=ʻ( $$H̩Sɓ'[4Ku)))ICC#88֭[gΜYvqd9 HQZs_h/U̙|r???cժU+),,$ Ν;sؾ}];YSQ3a(/pK(B%dRlIϔ8 Qhcrޒ]r._zUZVJ<uѨd$~۽{7袣8P3Gһv풒I5kVXXؕ+W8{QQӧX[[ӛSLٶm #ITL>UmmmȧЕ+Wy摏ݲ:ڝpႻwٲcǎm߾…BC|r]TZl_|ۻҥ˫W|o((ܱ' B2xP ժ{HȦ={$kV}HIIeNӧOc0~|xРAA *+*s'A]3샥k/| , &:rM]]ݜ'M>z_Th/1/Fyfp4.g7E hjF.Ph(~~~$#I%I2 455%cHr#G pUR,G)yĉ?}[I2o!00Ǐ"A)>%D2ϟ111!ݳ%e˖IjL:aaaHOfUCCCk$j$@pp0嗵dso/CŶs箩S-]֠A.\8byM4[؇uy5 A_ q}KKKܹ˖}c``e˖,c)o41S 4{72]M%5%Nm@S# 4-zs:9s&(TMDt`` FSjE<|L'SKbl令oo襤dO7}}}o5@/fx$=IKg2s?3˹ k*aF.@3(^MzѣBB67kso}]ii)Pߦ{u۹Ǿ|FKMQOKlVSwjCvB[gqQɹ,))2'Gճ(aVvi[ >LL&*:O ?YԿu\My<.4QL(0bhBPfr[Tl Ж`,M#s.]e]7p0ɼtASK+3)I;9SUTTIP/r]IOXUso}BP¬},$}b&fmhiz9YF&fL[;:1L|#Bbkkjhkk׺,+8IƤfJަ*QǏ;HT'p<;wWOoS-Wsctv':[3UavC1avԑ޶LIN=1~fڗ{@ČVbѷ'$J{FGF_gX>ttuIfqqOMM͕T 4ut9zL[qw&*!%XQgUѪ2TŊ\̪ԗqcVQ ϨRQY\1`R >-U'. >Տ+U=gVy[fy!+[VQi&ù7>@kEb<ΐp;-x/ F5!ND$2WUVl!~:: 5*&&֩IYVV4IaA$8pX?5U5 e%11A s(2Q/-_Yz@VA(兀02)Q_p+ML*>dVpTt9pUj<+RgU˕\g~x4`:S:3u;m-ù7>@+FzzڌJyoq-?{Tou',>cDܽT}=}ݬzZe}lt'&Fz=~ugvHˤӬWqeάq"u' rW#om @ t& faHO"@#6 ~qfu{Gg55]ٓu6&fJJTɛ|MM'ijPl-(rH(l,L 1eOm~29SFOy*/^KzlMmzÓ=#ܟ?O}8ҺɒG/$Hn2GܭO tǡgV[۹[`q;)`+)@W^R-;d?^0ech J_t2uO'F_44Mhԏ5B[}C#+^te{.ׄk1$Ջ$1굫1/s5\k^IraNCc]K`"zZ[0#M03H~D۵8H(SIYPfk_ŕRW<ԨURYD3&y\]bKXLVG+&i+K:qF\OP\B61( M.E%:GBeRIՉ1-)ex|]Qf%"n O_Nk2"k'ev47VP?Ԉk+ -3cTfJhc;?u%>ժIZ!U8\G Pg fڌܚCP1q+BSڒ ڼK7:'^[fqŖQUVkpgceE Mo?x&xk<ԑ;^vbl(ׅ@`SQ(A\m]/3m+C i/#b? {:ZR7uH)WHRGOee|=M{s9idIz$d[4:3z"Fsv󼟶 Nʥg+)@WTOSG[meP pZ<5f./9xK]dƈR8ŬڦZB[-m'7LSSC[G,Vk[QWݾZr۵蒒Xr;޿w ;^l Ƨ6z,cuj~$. qMv%KKH-ֳgOYf]rḻ>}E ○$v%Q3Gvڵwsskh"OA=-³'QRT=z1QlXgϝ}|Jeؑof x5%W?mGt'No۵kB>o۶,Yo߾{BHHȈ#H-Չdrf--~12E]-3ST(C=."P0Ӡ*B>4J&?TNoH)v9B IM Ӥj]&Ks5_#K-FZ0 (O(S[[;44T(SFt"1b;wX#4!m@ȕ^Q(HBYC u5rC romooԽhaĎJ_AQ(ș:  _ϝUѡu S @Ѫ2TŊ\̪_o#I5ijЊ?X#Z.M؁⢢Zwp~@hk0 b5u/p9M݅Z !((0&}y;W:tJMM{TI™S} @"ƓZb$? fb#tv104Cb\YgˮM/:|P)BAԩS&NDmΝ._,Z}v &bFnbegoljFn)]%q1ѹ/rzx**)1=\J"iGW6[!>jqQW/~#I:Mv>PI(ʗ\zg`ً|`2557 B ;wnà CBB>|D]9XhGL;G'zSUM͝J'L413{!I;V0xH$^5б_ࠜl 3Nbt`vs'NM{M-̌$mL2IE{GN]d=Ho`y|;yd=Ct zt6~4ņ5B=%=PO֭Y*Ly c2 DUTM~ǥ%%LWb"oj.h-0E۬XaH~*M(]=c#/CՕAhDt9'yJLL\W*ĉȑɷ QQKQ!('$ -*˟h݁R%EOFf7"f"*ʜYu5om 2 t& fa8av}jPT*M4_h<+S.fޫث\VE%RG@X㋠ȶH :IYVV4IaAunw-8+WGY,Vzzi}&w_666l63f̤I%SSS۵k/P'FAGkH"Nu'UeEoMwLOGܭO tǡg>xa'#nO)6e;OXh ?JmDlh0#U#ra۷H(;+ĬCFC:΍E>g> ߽s[*$dG)JO{o@_y6&fdq=B\he$44tō qwa{ښ,[L$Knݺx9sV^b$@ =EeeqIu 9>.ݎ^LtDޔY7R*&ֲ ok7k^9&6+bIFjB[?v]n'|捒e.[Wc \=uDW^qQN*Ƨ6z,cc՘9ٚZZ.^TkB\쵘h(04['O޺uƏԩKPDAA}Ν:u 7/ѯHy(TA-)))ICC#88֭[gΜYvq?ٳg=z  122j' g˿ fׯ_?sL33.]l߾ 5AR̍y,r_I0-qOް7,XEAr,͙D^l\[C6Rch F&&G4_IIٻwLjŦk,`bjF/kQycRMyx ()+Ii^o Ƶ!SLٶm,--ΝK &瓈1//oرԥ 84Hϟ?֖=,tߧH>IOeoviכ*0ij+˙:{˛.2YUߛdw"ڈ*М_HgDH r#GR9.\:z#GΝ;aÆKicR$p}2q)wMMH&/@롧Ss4ԔTbԂCks, |=J̒/%5"Z!+==<9^^^ׯ_rܹs--- Iڻw򔕕UUUׯ_llkx0{{{- ! !(wUiZT ͇ P MAk0.zKnڴ{r =yUYUu^Y^5@SSUPVsH9TV̪Z B1, GeG@iw$q &ut&2+bv*:BjJScuۨr"Ϊ+7" (r ?i(u2? H5rۄÂƎSg2"KbyxɥYh8*(*])!hL`&LNv)W!%jzBN0 2 !hW^&fQ>_bqb?T¶|)UjUW-Sے͟PDm5`255ZL˜=W^>HO t ]ԥBP637g2yrF˗Dioȟo)8KI+Z{/o%%eIVJGQ̱ԨURYD3&y\%B[;:w%YFµ~;I7m4233UXEE/`uZBPist䛉&ff^~O>$i'Ww)]ycDܽ8t1if3i[3*N=}矨veedsLwpQ {?:XA@@@`-DcQ-)؀A #M+z^@8߽ٙY r7T2ݩ-W}6j\JV^GkvX#fAkލ\*Nt.͒k^srh!*\Ψ!ϥۆz/@$D(JLx;S̬OrĸXFc*IFM+/- 3rY~BIx#!&B|4&\. ɢ5gbj'g55~H$02l)>oٸM=TUU޻?|"4|9yV`^V3#?q["[Kb$-~)7Nvf˾#,"٘XO>$dl\3KGKMή%k${IAT]PPp.#zC#аQbjjUk!sUUщwՋ<$tIBݻOIa YE6lY]Cy ~{ˈ**99C9N]]hě`#і0.eJ u{Džx vWW9y?&qja #=ͿN7qwND8ٚ}i05c$w-Y#Kr.Nǣ<)k_mPxa4^˼rRff)Or 5}\\L@pT՚ޅ*9 RC'B^ۺrђG;io{62? -~51WD2n]?*&] ꨉ(  u: $f#;VQՃz}_H&M455Msݹs]#v w޽E:tCBB6lP@ R(8R.|%*\)u3j;rkGPD}W^!5iСӧO?ٳgM\ @qTWW8q!3l"@iUʺ &]9> ?*j޽yܸq䙤PNcǎ qqqϹ#GAqZq*)ډυ!Y@`Sd";*9t /UUUbD+EOj+~= \UUCdM/:b mڴmN]aeeebb")׏<3f֭i*}S>${QSS+ٳsNwww??S@{BŠݹqe˖xyy믤d7oQQQTn\~{=j|/rWXQ]]1t\hNLxji˼0mR =0SH)QIbF_ވ*TAOOoǎ7wh)iS'B;:Uȸt|`HhN 'SO vb\.g̫W55 qeF&>~*&Ŕ$}IeQaABL4au221^ XL鴑<E:\yo4[O^.(׺?Y:gx)@0B@uCzz* 0('+|6[TO^I% 9^x?n?=*ݹ# a|JN}|"{G#9hiz-_@9dBυv1GCY,?ËdZ^.֋LwφV)jljZZRm3'< D?4rn4ػ'yBYXfgihzVeF]yPOk!Vԉ?TjiM=.F tOҬ~tMeՃ #}yd_xr&so 17ѻ[#իׯYyH/g:!@_>K'΅ve EWӼkk PeS[S#֫߻2c=|ltlToХ5r(]Vڜ zghѧܲ?ba\$esGl=KY!iD}NSۼ'r}loU~:O7/mmtז1!ߛ(>i}z$_57qOG/y&)4!_?6%91E_Ock/|cr`>ᔦU6dOEUDPUԌ7ABUMO>6n4}MM-.t'ƿ Mjm($ژdqM:6u3-i_jl Nxv''Plz ]0ԅL_.*?_/ӯٓc{:yLO_{/g۬% sqN.I"Wn-%ɃrWRn?z\+unXc?Y)W.%Rϻ#G^{u|#3F&̝HrbGo\.kؘІFneXT_TX@)yij+)..1㙋5[ܫM-{ı.;I5OZGSCS[[[5(*=)4%&i^&bqÓG@Ǵ'L@Gr`MjL*22ܓq&BϜig!|\G.7;υta4^3>sḜ -%s"Nԕ7 hjiʪjj60hVC#c;@J[1O2^˭/[Uד͊ΪmZ+!O'gTdc>3TsGΥ2?-Լ:aϻ#K13]XCX1C^̒# 5сvGvE_;X/|zxǖ/^c4&Ͻ'/S5Erb c#<'N/O:JpZe,dxX9+DP'P>XńU3|+C./=Y4Fيya{{l[Ots8PxOȢ7۷fFV0~it3m)h d.Tn/Phk\|S"TZ?ZZ@*\ /j Vʱ#,ZM#GlioeSP,+ZKݒ=?mo6̭wAHDqE_HDoy`1&gHkRNEP鹳e .Zsiiy  ^ mhyyyZjJdl|R666&Eo.^ovV_~'ywz~kÆh<z֒K޵b_acp.' N=?쳉V"0/.27[G{}̹W^fF{+V̒gZt`q´Ծnn6leE|{}\(F_6@d\+R(υ_>#aRG['d0R~}q+ ߛ6{w+>XE"h7r,+":.*2ά|cǍj7g/*"(3?|8)7^h谌kS&O,UJEP} )k7 (R$t_(({SR3vkVQ) ~Vxcނ~2"lJnNNpPSWW'-X:|&+Vuޫs^n! );pp֭|ҽ*y&?I y*C :>-H ]V'y̌S&+$~bDU)*9 fϙWIL1sX03f5iZȗ`9l6[cs[[EÔBzC 6}{,_T ?5.7m\?fsf7lr?W  _򦦖W_~IR(w]]==f33]]]-?\i&ݻzzz>?od7G "&n57 E dɒ .?}O۵kײeJKK:k,?0΄$ȈsNKWΜ`G~dтǎ~øQat2}WYۋ-`6m& <ݲsDSN"m/~}? [ߜ/\h?@~J\=p;@w \\HN@I|?~|mmmm]]]r>z;%Qdz L.9] !Thhh3b6Wmlӯ_8RHMM'RSHWJ'Pe\۲ߥ-P(&u!v>T#osn$'$3jF+&,'g"v ˗/H\={]]]6Ӧh4 sɒ%}ݰaRRRf̘90e~DZj.bJpTfZIvVKQp.{!Zk;ʊ Uw直u'E 1Ѥ uNN??&Qcjkjb+JLL}UWܿks_ܐF64]KOxiGGU?~dzT[[S^Zjlj?S!\200p[nݴig}bŊ 'ݻ7""BEE/lyB;N6Cl=b'B[*= DȿUP\vAt56ךgC|zQm"2^ Rw`dק^&%ēXhfnAazJ2BIsoP\Xx~R+x%JK9NyiIԓ-rHgGuuap)5op 0aŐHT ]IKuu-*'ew:իW;88|t 3P\\lii.Ӆ%ZDKUR/fp7Y@w2)?l;yn:_5nuM݉[we5#v5rꓖ,Kۗ ElSecSҒb-mmSJSslKJz*/-\R\l@yx7$j^NOʊxV'Br52/_Qc609T zȠ|={~G47oIYYYJJ"񵪪jܹ|pԩv1V]*[(PiaRx#P(t VYq wݓ<3\m/(teEPY+¹A#kofݨ{J{ب&588D@j@@:{<|?pؕЃK԰ ?HjOXְbjk92J%=.LuzȠ|vZdIhh(?H k׮]t!CfϞ8[lۼyszzӧI4hPچυ!Y@`Sd";*9t9TѢOs'[3׭4BS p'Ѝ\hw_$޾f'Qjj$ܜqVl]=7FA+zlNÏj}}=Ɋ䙫"k55M\n0)a)dXjp)TTF v՟C݈*TAt-"e@@g[l[DЦms"}.|{9f/`l_A͌u?"f,({m@DZUU.V$QjKήnlbfFR(iCm25YXYPó˹iHz)>fk\(gnqꮎn^nNnvv`Hec @cY=#ȧ>E^fl̍X,fZ5/ĸښ^}Jo󤞔}ʩ4噧&'xƏV^IOD_I]Kɸ|M 7;,ս_|tTmmm_Ȧ]]o9GSߚj3E&:&|w`A4nB(\NF2-mȃ*;M UhR+Iѽ *g\.pUKH[ޤ60h"GWBN/,|f0X_ۚgJe )amn_AV.)Z#1tI23)es:@Қ"@ۋ)H,2$H+rorYظ7wSFCAt)oƌ'\{icSw0 $L![!od]+R( M ͞4xMuuƸ{z!mM9OwpA vUvJ |NGOk;w(ƇxV j[u {WXI"h{ J!:΅ 5+yP/:O>rt޼y5˘ѣ7nI)OPP:e^KN:"(P( Yx*.(ȥ("(ȼ")+10c^>=^ a^Ohd1{n(oJDODPڕc]pb6krW݌6%jl"V@鈳ʄ5r[=UQE|a-gZT Ra-幘_.{uuÓ'?YP\()lKxAO_U|R[FO+sS-}-``2j7_-NA!/%X9O>p:=3#wkP3mzfj6%6PbnF7*3+ ׾|&U?і Rk9pr)yTOxݘfzlVӻs/s%_fKU^*nqA]qР0\GϙΌ{2ع4Bg.ۼrﶜ23 lbSY`_ˬy !PȄ$5$/vsZKp5zoy.7Ue@Qp5Sc?lS]OW~{do@z Ь>CN<ܢ '[3ŦT9=pG#R[:g1!ߛ뾨U F6,&s)I(/ t.H]α=^;S]/hg)} Gsw;{5MIzjO0j$|mtV }styIn#(<7H -h;yy58j>f55N-Su=,JU&z(_?orW+ېPR_I* ===_uvGO!`ZPDe[D@mڸg㮹:5dT'[/M&|LS-1}EkE dH8\TFbU5QEbrE̥k+# ,[M̸]蟼6=;z Ж6׽QumMmc=6 s&lq|Y`R7kZ:/+"1T0WV,+Ŏ<$}z@g͢gRc->@&F-S (\p RP'-[M:ɂ @8d Fe@P߈|}}Ķ"v>&&$g#<>=U@`PNV){ kjjB_YRr+!&z8TA!& 943ss}Щ!v>&7n2e^vvt}IQɴz9p,,Bwʊ5h06M6̺6 ]Rh磭S]>գkkk6RDEE1eJDPVWWS-[v0 v>L&(e##דI $OQӗPUQ#鴦}[A Mvq45YXYښA[W*fvG+[ND VHglj=_q͏:0nʴV->濌˗z9H~PKwC jJm/V ~[W:88D(7hՑyT᣷gN9*?mTڹ_fTޮ mB|\IqQ}}瓻@oT2BOF&mE8B 6_GOڅ(!5z:|J)UA7=QyfG|=d;t!lێV{6sMBIظxQm[K^;M|r K|#=#/:ښߌli|%eoɲ{[6m,Lޜ3&d`?R0@H KC蔉a y b|by{rR'*y7۩'cIf[r)FN?omϾr߯\ϣWo俿c[>TRfٴB^b-гn/]GDaB@A^HB3)+烿Rݧ7Z,8*~ WWmܲ3ɍײ &,Ltg?NbgfN!U)g/$`ȡ;oMHB޴D)TaB@&3c}OW)٫$7~Ϝ]^A琀~\.GsYO~kҮ4; iť%_#zoNT6ȓfr9*ZΟHR\= BE 2=I{V>_0<ȋfj3-͌/^+*۸XbZ[BX{ɚ;Ug~=gJ{SVYE噷rb2ʪg'YlV 2qجAߝr%bǑBI`[H/*+7z';KSxFSiXGm-\vCq<~͂F(2I)E^z9X&_-I ʭ;6eG9LPR(DㅴݝHt4yDhݫCeN6ȃ*̞4TMt?.g>:Z<9CRP{dr˫CI {<<!v0MG닗fRf2  2lL];"Ћ9Gv](z8|e~D7n;qM~KMG{LǞSyEe$Rh|n/,kc'-|ӎ㫖LE^ݾ];OZ[=wo^Ԧޞڭ7 y& fb&()]Fh@,Sth/Km!Y?~y߻h֏%7|)k/ƄLԍ^(c r ʃ ]@ `d)ٿk'U`2z>>zTi3Z:TK]Q~9-ֶv=55Z:BQaAblSמg&R9y<4lRhCe*Pu#9!!xuީ<_ަfǏ_LH0ԢAb}[XhLdU@x vVL&>bX?\[Ss>&nog҅ͬ="ҊxD]wO/#ѽd^nRK[t#WB\lEY]P'uI%sPpbsvVʊ Uw}u4ΊdW43sstJ }idI-X]]m ljRHDJcc",}ѭjj$ңV_WméVSՒ&ݻw*@[A DJ]][$)؝L>|~~nN\TDظ [MLnp^VޮH:O]Z__Oy0ɟ/gaicoGd <0̢S3޵+JUN"8ҊسwlTك/&&juIquAnϼE:OG$[K[Rjh/Z%b@O^l`_=ѳ覐B 1\.׹]->濌˗z9p\*OrĸXjL>Rݻث#GBL̅8cSS-yJ="Q^>d58ӕf7e]<":麿y)4Z~GOB dd}(zrOU!B0fX1鲡1T< WUUUI_Pn )[?2On(: 9n?Gаn\.T>_ &f &,-uFтAj*\Ajf\mfQW{]RQկ5ŢJm)9O0٬Pu"(R(4#󟏉Z1k,YCa,&SqM^xkfhI C§ލܲ﷝"Iuoo !@3O N<Jjڬvf'GLs3e4Lۻgem;>2--`ω _;KyE'eF&f #"-I~FS]uB<r H /H(tM>?0}"g׹#RzX̟Dj3j*I_Q(׫;3cL ش+yzӃQ}zXc͝4跽Qwedb<'Pf[>EFCM>BI/uK%kL u>ZLS)^XH&#'N=={O[=_T1y>ydӢ^~F5ڬNRq?ڎ_hoVP!R((Rzp3v/ {,n| ,]˼Fz}OO  651=sLEEaN JR|NV;H7sffjKU +ؒr2hk:m5@ٺ?Y:gh%C }Q {o<&釟Rt// v.ܬ6n(_'?]yL8Ǎ?=w{쭯4ao8>xhk|>3@ꛝ_J- ýwZ%/X߿?bQI&=Ƀ*lǚgpڐ_9OK͇~Q1u΃v)7vǎ#qRg_Pn )TTUU[ZX~5ydd=~=tǫ>&B+ܹ#:SO_o珝ԦؓυpL&CR[BS EM]=Nt%gHezF3Fn;G](Kl9qX>6*|P~C"kH/)o/kƌC^%K/4$pȖ_a/duս*uuauz']Pҥ\H"ލܲ﷝"oooR4\I+ Y̧-qrOo';3]e_!5FZZS,vDތ<|z]$ kfh)2Oj^[ԙ *Mo.zS(?`DG5=rtMm/:4tESUU$v8Aom8y~NՉJ,_w\`wuU,q]su[ܱ02h<]9q]vߚj3E& .u57҄a^Ik0k4U邬8YJ tew (8lҷԭO"܌\?])y<8)~rPީ2wҠFmybԣͲ8ؘ.DX1C^̢*m=Qlwortrv~GN6f3I圉vE_;X/|zCMktA_3 3 .(5sPPe&ho_(9?*mL.,)ЧyBTATH_Klks7gUY4Ffż0/ SeC=IMeEy#t5|A=b2"  n(/H'u("Dn]{Pe&how t]< J#  )ęi?UT(*,v{O ۢM"vt(y ~2@-\(υv455pzsG U ?}SW]]sG Uy)@%B!ܰn=)4#k͍FZ))))):@bڶ%(Rh'Lʺo?;-Moek+uPd-o=sxh(EBT|b4HLb|, ׮\ϫ{(?Ҳ _j UL({  r"*GNO㟥s+g_HL՝;{;Jq1奥Ʀ>'H׭ /W zALdİyi$G~}}yYaOp~fjkjbcTI$yy_Fʞ>l6')!'`C$)̺#ɭE 1 &SGG1D7deEEj򅪻wUTU]ۋlm\lEY?i,k UtČ0w*i%*gש#PNƔǻb``d`d;jDJZ* :EFPȰ+Koݲwt,Ulnil{=*.242V it&RSlm\m\Uug1d_W/[Z UZZB"&%%$wvŅ$9Z4RNl%܂dBZRS/ZXYdeπfN^~e)OLR(s_7 ,M6ךlm??e7_ siC\meWV=׹2k!13%-LJzF&dT=xx﷝";";~#RO; +SYc{ڛuZ )}#39񎦖'ӋdH!*ͭ8\.܂tiH%%z7^t%‚ $R)V't>}9'R_z99[MMKKM.w'$IQ;h%En KD޵2ʪ*?gRز?ba\$esGJ6QwC%ɍ[+~ˁ Ku_ԪIKnB҆O*{8̝88J潑,a_x>HGoR#/|azE!qnO]jkdbr|@ WuȨ4> /DJRC'NʤFM]]jGDKR dMFFEq5'YS]}3FݻwTWmm Յ(2E!Gی%Kfp@OvP>_k _*Nt-ݴ}`72-ݗ25{d6+:eݻRh'str {vUUQ9nhE704̼zؘj#=-ÁC=SVR³PSS#1̡F$ԵU55>I͉7AV˸"y6}4>qLV3ݩOg.'@9;YVk6; b I37NZ/26&>Mn}IC|{?!@A d,mndf98Lad gnqꮎn^nNnvv`HXG3s )؞RToÁ~iPGR(yܴwp$ɶ͏.V$=)ښja!ZUUTRʺ 7njg&5G?xTMR( 'GLs3mŰNfF_0+:)321sM6$>|\|q!cU{N^@ 輐B;W~)O?*MxB;>1>DGW/@#l6̌`{B ܮ(k-z~q1/;!2<}'ŒO@LmKLWJs!>{O˳0Ow;ڳlܑs)=LO jŰs&lq|vT3/>?IKՐM~=9Y*]jY Ș^v7(P yPej%-UU5[ӑZ//O  呗BB!ϯSlrl6dJںw1&!||7;^RS(+//޽{jE3Ԍ.?|NRR5y#Bݻw ]ǂ[鰪.(L{y)8)ߋ!9(h@;0ZMiɊfG;>cu +nU,48ҧ3 ~jrr=*|X΀,--qT[ (BIjUUw[nB݂ySB\;wAy:a>#c'G0M999K9PxA2{-uQ]]]mm-\[[v߷_C',򎡡˥*kjjȷSCCC2{@{|SRnlbmMjBX5U]٧=Z﹜ФGs8/蠞^^II| TMmQ\ZIS~v:.%9^>y)Z7 o$ 6R]д4plҝɯ{9e\7nrwwlvwމ#r8ڐE]\zQ{xx\x bM*^Pא=B=WTT7Mq}c$-O?8vH~aQxٳg1s5kbcc}}Y'~^;rO綾 fLBR W׺;.J+=3* =zf>|ظf3~57eFƵ+?xX%0׮]Fnn˗/f4~aiL-.ni{F~v/*9^~+(({EҴgaE@T &;t&A"zS~ESQOC(,_ 8;3;,o驩4V}|tqD Ј]*0x1-i׮;wnw}%7X C -g͜9qDE HȨp{-j 5{D"[t'L DO$R`.z4ѬqṈMgkmfd`hA-Op; ߂L##;_F0ں ڵkٳgM8O>ſ}6<<ʕ+IivOL/7߸qSVΝȗuMMT==cΞ={[nAc/ooocccb5N~NV(S4b i[-j1^y ILecӼO^2;O?~>cF5St\x럩,1%Gnm~fRG4IsSH$} P1--iIIQNH#srחylZ ЃC y]/TKKI D"A*5 7\Hׯ_>?#}VMݸy㗟2C1Bz싪kGD-Wřgօ؞>sf?QB5X+({L8!ڸ}/ݹcZ/CWv9..&"iΤZD$=|5>|xa޻7yԩ:u*--]%Qk+̼{W\"8@&֪%s@œJza-ϟ?ZxQ5}sЮ?ki\=*W &x|ʀ={4i"SΰrYoyhe}$[B3 $ tYņ=VQdA۷Zzzݻ [vfAQQ֭-Z{ӧMLd%%rm-nm/=A}}V.\`\A]*|;vegg7k|VZ#W,-ZgOX,vuuQMŋ ,T #H 6:}®.>>-9&<'O /_ؘ~k׮k׬66n@IN5#,EmzyFݡߦM2>}2}N,INF9:|`aoE]ғU/u֝9{l}dCkQXXukllV7ۻ(HJJ @d=V~}{yy{yyBsa&"_xI߾}=^m"={#GT@vm6'==4 >(rr%]69t) bڠu;-PUjŀoUR5#SXF,%{)PP *Wp̳jP|=G JjeH᠖DҼF ŕ[B== w! P6#֭u JsseZZ m/-eBkIQoHL7xJ!wFT“\"ܸqkᵇ}=S0XpK:bZ--ѣ)RmROOe>?,>>>b JU3’J`+V89۞:L$_:;9%5qP}zF=jӦh;o@r*KG5k>杻wRRdV#'M|"6,0ع^ffJJAz¢6o%B)yyǏ;yʕPB9M7߿_ԙ"-t.ZD~k[wתիى?S7e?cl׶-((5Nu3@)LFTz3fwӡ;OLlj"|ҢKJ[ T4SQ]xJ}]"6FjD '[dB}'h{-];IU`^'qT2aY=-)@а9zD-O4wo܌73+>g7"x}4:|CcǎD___.:z˄ 4iJ񘘘o p%z~=…uԹxۨk{fxA$0:q-6FHc}W\С=_*Snn޶mME$'>]gD^xImicݺ} ƂpD1y?޿~> 1~8OO/|ׯBOIɃ:to>#_w'޽_ƒ!3JPƈ1}&W6G=z 3gzL<$n;wDk׮&QT1\v 2 Y1Q5 n>2嘖@9KFL(WH$J AӤe'[_,_Lޯ,Btme_-t[j~uwv[zQWBC},[VcJ 1<.E,YcI*%!b3IR*h;|pLt4ݢ ǍOR YX ׯ_V7u&z&sH ~m?vp$ N6WDQ/˟a咖-[{Hϝܲ帗.e6k3 :8$I5jҽ{i3ҼyƍM|B *9c{qqq^Kz3~v#墆e|fϞ /,GGG(.\0a„/~С8MÝ;w{W k֬!h 6mZd ! P(|x@@mZj Ԥk8&|NtLΠLLĭ[R>*j3yE^1\ZAKwFCQ)E;23޻78oذ!22Ϗf {cc.Z$:A2#`Z رޓ.Cptiv*o>r *:wu6CN"Xs#+m,@d̷/Ar-`7\$7\(+ڿ[iǎO;q\mD+hЀ{K *c:HV,HI˘r &X[T,,rssQMT&ĢI&yL9K [[lwXڏf)x$lOnzޞUTիW?dȤIh+tVx葀H:#ϪKOVW63ZwǞ5Z4YXxj(6,VonjЬJ)Tea[uSgg27?&LwJBGHe&Y۷fڄ9c{Y[[,Xy̙={hfLFQ8M6m׶ٳg]]]ӯyy.%œ]յGu\U.-9*0MlDGC$Y%sv"7mDLY`PJ\U}#+:Nn#ݬ co+o0E٩oΙj1zwA\DdQ-O|5.I HC/}x/xyEGD,m4˜9W4l_9bRPPߎ;-,>!ܥÆ a%%۷o1\,ܹsweefDDD`*@ qcᙖ֠-A"$$x&3Ј8.VnBaal^NtV 6>}PzvV(?B|pGe>q~}âBh2aÆo]^o6lYXAJ<pҭFC={PTY˖@y[B9{zzъ|/j^LEv0 W`jjt2 c풒"h:u ޽( POOKiG3Ųw ǾvoJ xpi[Q[eF{.zmO): wG)4ל<.@]idڊp33:gdjKʱh `J=yoʤIܵ{תU\XQ JV,[F̔ Uۊ:@ҩTWp;ڂf5n5~uI3Yufa.>߲L'P[#JxErZ$-,|N)rA11/_G47P"PUiߎ;2k-,,r)M,PI&Ann.\RvR<ȨbZOObgH aaC k"'OB;) E"aÆffML! SU]Xf{ݞo$T+dS`$-jkv1JbRt$mTc89(<woܹEBBТ8;[]^T$T.X(¥sfDhP(/ٽe)?Ϟ=lشj>q(sM Fa2\]]#V 0\]]QCݰ!00mBJKKNݫWbbc)+gz[⋯rm6[5k4a ?kno;O28vkP .=EE HhUZ޳GTFIVL 1Ap孺b!!S437Ɏ;֯ZVTPPԙ"[ZЂ;%h7 \(~]Aݻ=CL_~A!+W+,tFT7`2 mi׮]0:&f̙٤#S˜]յGu\UZ@!LjA鹹9ÆU'fMya~`ўaS"7#LafE.ȡ;At) neMuNڅ;SGh#zVDXe'0H"q/PUx.ĈQ&ef^b9GK,:uJvo_JVsX +l*|l",,lW=zHHH;w.6 Nҿ\"(Lܺz*(ޞnppЬY 7۶m[LL w}-WX顬eeE" IJ֖I$u+$İQ؇.(ư~.jrwDt2yɓKb6\:v=f,4 ;wԋ+!ɘ?o~au..ϛg͜5Ay ظFM2vLl٫/mզ5!A?&ڇx7Q/_F+F1|͛;7$$dȐ!'Ltʔ5k5n<}ڴ3gZ4b5$9VR1}]sζvww'e*LnLLfexpăZZZG0Ҝi'gg+<~lP~^B+OaG+Vq1?t=::z5Q Gw?]idڊ)CCCC1,dQ{TǥQe2To7o1|'(z˖rCur Uۊ:NH]Ƚu :mbNmb5o3lNk[LQa\KO>TUI3"eZzw̘[ݹsWGNΝׯ_GaF WV+y*۫Wp?~RÞ %oO6m:%[(**~b #2r2cԋzYKkk`,IS˴P B#Z9#Pd߼}R$ObXXw֭[1^D٪U={( % >%GXLn=:$dr(qzAepV)#""H,@CnW}^^^LbP.<j؎Ax?3gzA05flՅ&\C##5+/γJh{*0҇^R |pU~o~>O"/cYZ6goǼ-|.&_%ǹQJf7S ,+,5NaGWlRhgANa؇-@\π?"r 6uQ_LM%N$4P*P9q(ܘ2”DRIikkO 70ŋ_-z)_|yz!<2euoзIy"ªV$M|8brЖ7r6inLLfV5 A?HI$F t9uT.0YӥSgGzz+ᏈAtm[B[q0|!V.dj';j4LB*4۷U}5KOFϔ :K < NcWQqsDm\FaX7r KЧD8tt]1hq'o$Yn P)))c{;~ _XJP, -[19Ȝ)M WVj D Z /TQ(2mh Gۯ\^?R& ͚5>}ڙ3ݺuEl*UCk׮9sF6*$=rg..:-HQYTN̚9ax|у3-N2eI%%bdV&퍆+r3fprr qDnpϞ=߼ɂzarL>عNOUYSȊ"͆?i璏#R+9I 2 mj W.-C@K kPT$-//JU=EyշIP}a5iy|N>yo֭[ OM⳵Ϟ= 1O~JrbT,?E?<4 W%4]| <>"x[+Ej{i4##V(`E>&H0r\'g盏sOJMu%m-z=3߇sԧzsV3T,q$!PEJ PR"!҅WHEൾVB+>\% (9hYB?CFB+QM53`k2uhKE4uYX~8KCTI)߰s4pgA;p(SƲ(MO7e1pYp.R1]e[@q8+M c2XA@,wّ0=ghNf? \kNQ§*4JDgG% #dhp8&ZgL+<ř)|q)؀Mk~n_N~3U)ϛ"5 ҥ &tӇEK7mě+Z҇?!۰e և JI 8i *FA\!D,]_* &? O$T#&V'lN3'<= G緮ՙ F`3 c3%L3>\7('SsnB󉔒Nͥp8w2V233)--%F$I"??bpJ<%dN7ꏳWR7D@Mwt0.AZZZH&) ~Mg5>+fhhh2084|ɫTvJ~Y8נbzs LFҵ_GgeWSF$}rXDtŇ Ti )|mmmH)imm!Jkkk+a'4eIԬWs`t3r ;t4 5 NLX78_/ ȏב.@wۃ`|z܈~dÀ <eXԧtu钡;FS)XaԛӠhon ZvG/f.ݸ5Sƪ1?x/cU H4OEtqnV6dSXYN qy>wU{ 3ڡ!b1rs$ I$ĆbzI>6lSO~rlϙ  x0{Xmmw: ̜9Ko DՆK;?w}\~,nʡC[ vo~菾E^^C;WbA̫_;5!ho|ϷG<#,]o:O20e\yٳ-[o} {S?ob۶pvM?%Ѓ/~{}G2+_*cƌ ;jUF *Bݫzs1Ni ߐהª.~_([$\Jb +=)ƻF Ed_ ͆VHLZJPPɗ9o%x S\{6v? e.$A18TN2ύ0=fX;%h8-j4󾣳|ttwtww:;;!33ɈDl|)ɵL&fΜɉ'gL! Nml/{-gܚ_@+̌lj$q23*@g.O!Op{{Yd WرcYp!vϹ%_.(ԧ:u*Wŋq:_[YgsI֭[GmQؽ{7k׮%%';D"g?Y3+Wr)rE\{^pիb/^xyS}ys ٟeet~***Xp!7o _ܹs̛7*6m 7,aƍi;HC#;^_shbҧucfəF mB']^]*65oZ;%05)h'ЏUoO[|[cOLFuZ,R4O4L3\」D"A4%;o.1qeS'ݹ"L$|,ӷLb7ytKUcOO$F\pgi>Owy/0n88hԒ?^2 ]Cv705ȅAݸ|dNjtHN:Iq5\?> 3f HqFTUUef{ŽY`+lݺÇqwmٹs_W8~8---?VPgg'ǎK.##2~if^~9P¢"9^z%"[!ihh⥋roGر<쳬^5CZ.\*LSOiӦ㻧z;3_ǏON1;C,|ghb֭[ˡCLR/JOw7ׯ'v|spe_OkFcc#3w̺uk뮻 ?$B@XH2BH %dddf ɏ))()()TWD]1.H] 2e&)t?UW',4,]gq88 8[xW{e\lAqvh 9Sp9ᝫ(*rlrfyWq_v> qCA&W^T}J>sgDZW=%ɉHr2dHH#}F$Ipps %H 3$+tB I2CI2I_3C 2Òhh(A=JWB_uQ'*D]˄ ""^6\!W28$adK;aOXxO؀ I B"]F[!d%nCpq8_4?Y/??7^G$DLDHH$xm6n__#8 wsϽtvugw߽?~ 6zF 4%]oG.^ѻ_F_>T~%yצ57m:D0Ox D"AFFēU$dk- hiiDIqEݻBsoVDV:Ze\ŦFJ5U,v2|]Ɨu9Ϯvew&hϺ: BtwwF DQ"(HzLM>}+Çl2"ͼtuwd `x[-|An] !Nn~~B>ynz{{fѢE;q'?}8}s%{n/^UW]xon7oyj"'O`ҥ>}fΜ @ ܳ*8Oc9s8~_=θq8zkYHAA[nС;D";SuTWWsy̙innfϞ)((l}=E:WɤsZN&ЗL&H&$ Dd2L$ G&+\${Jtpd%RpCd){dd~:DȄNTd /t{4I+Xv^4\1;GHE] {za en,H-C%ՅDZVWB8 IZ6~\Ʉ['$?Idҹ>Ε0#QIG2fad𷥴+L},|Ʉ!NC?aӴ?Ru듂sc} ps+&F[ SK! 3QyRJƔ0}F5'O``pܜ\$͂PHP5qp|J#77و0q燭Z݄BL&(((X+P?W+?ߢ۔S],"Vf]Պ@k;[|i`|ccml> ~ɡW~ieLOiM/QL]Y0pU`> P5݃],~.tӇ;s}T3ۑ.>@$G-NLakAn@ޕ& lA"4_G ~F(ɖ/Prh9oh9ƥiSrZ&JE=*GUÍ;4Uдj s鑊RD|s zEU(cd/ K(|X2/C4'?Jr%"B&dddP^>p8d9QvN.MYI&KMB}̟EIF(LH ( $%]G@[hpet}9<&Mnx? bC9sɓ'DdH3IXLGHF'zɥ9Tʤ/>tJvS6Ӱ - Ly8lѶy+RJ 4VץQʴ&"˜mzNɎT _؍? ڏmfLWF)b'8iOsd"KSmÎzHtqg>A4aF7]3ywY~z8v 'BdeevO4CC1b1BB Dp#LD{[4&ÿ>Jk")+`n,eR5 gаT>Mc($'88| N܍B$?X Ǔ!V_!G],hpqmI?Ҍ'NA0:6G_ȨII1UBvm I :h!{ǘ#6|䇉BX#뗢{I5MqΤyjnMPw>QSo4Jl4ti.YѰڿnU^ xE@t|a6yIQtKXgGپiր2bp17} F>:*!KnۇR;y{0|HI 'k5y *&x`A%cY`HJ)BK+rO9 dN_+*ʹ#ʧǔhÜ\eG蕜 i:\v/JHtw BT˒&jJH\[u+  RlWق @F򍟶{Xx<];m^P<{vvy:t5KkM,>My,_O`,4y)ZΖGJjOhEc]gaoX60q+14<G;7s{ 2k)hB%mk*1g~*Id'ˍRtӄJ_iF#z= Ԛ'#^`emBp5(^{Ǐ󷡉|>pT4|4M:UO׊_4Cw;K[Pr!@-BQߐa.-U\R&ۜZ6W&K<DNɺTxw|L:wx(WDe=Su(B.5M%;8WAmʣ69[,>J~<å<c[`ҧW͋/[WJ3q.k4 'Rte_T|.~1HţX-m]a |{s5O=.8ڇ$MBj58wW|>Mz CDЂ͙hI'}rxcPg;.ҝp} U μwnӁ0<6Lʹi=t( ߔ~_)]9_X >>aLJsÞB [@U!=X=>(!_jteV:&118FLWW8B IDAT) ;ƽ|T}{z'_T˜ Pt:yj(E)EJJJ !\jnրRK5xuإ<H$;Q^6g NSuP!H,ÇvbeW iCPov06 jR4*M4evAGPQԝ]%lnWKzְ &I0*qlwSOfϮJJݥ;Q(G a/|.n-UpǪRu Ǜ1b`Q2{ LJP3?gG{}?>ikPJKooygϲv:߇DPAg8It H&H$xN=[/Sw/Ʀ&ǯ׾MCC=>?p$eMN<=tuuqErrr$p\Iw<5k lٺҳ̳ϒp'jUےH$hmkH)2n/;/RPJ6m /^$^ P; v+ǟ7>ģ!-RV\*>\y6LjtQ4M:̦l'W.lzma3-w) K><>{fȡ'):Z,C`Lt~vwSʴ[LJi<>wAM~J7<`p<40A}$qI Y5Ȁ944DOOӦM%#q1&&w-1%%|BWb„ K`ax9Z[K,cm?!'O;tǨڄD"|Fܹ… |scp җط?cƌa!xU˙<=Ūn|[{)5B;.j|;:W6ǡkSq -|+)0Bk{(ySxGz1໺BPVV;v,9.tQ,{vƚ^c׮]'cݺ}̬L]q_|Qrrs5[l!##.멬Ga̘1^8^ckq9?Wz7osT`m|k_ _{Gn^>(}74d]3;ɼsYx1,_ Kpp)җM^׿αcǘ0aHp(9t_W[۶n%H[{]<Ț^cƍ"33lJJK={6g…l߱'%޽\}QL@]]_Yz-*c,i^ss/~Aee%hJ߷\}rrr>}:=Ǐڵ LgݬX|{1n8qI& Ο?Okk+<,\xfddPPPHNn.EEE:(Hm{6|guNJJJ?>SrrsD"TTT'qȧF:tXNqjv&$1V)6 J?aa9kMnHRh(\:*>I\8qj}JuYzͯ)1TA[Ûft?sR4͉Kj 9 eC$r:ʓc)_7Ҏ)9~_%ɐCAuugc"4qx=h9~OƇۼ0q>ؼ ծxSm&p4$ ޤ?d\oG/aǡ!4upF@; ;XFFRRZ{!d]cǎ~O$)-+#7'R+-B$f7)(,#Gpo565?2={048HEemC!BndPpJW$A}}=x͛7L$'Rs(YYY3nظSRV^N[k+e8BՄ rijjjXlse'?ɼy\9^jTЙ8q"hGٴy3'Of츱466! ##\=zV8y$qWPXXȴSLl* .䟟|CPٳyw,vN〓`/tp8̢Ery;5V{<qIǂf=|=J[mpҥYoh$:VGkk; gg?~P(tR[Z+Y&A/Hɣ'-wTz?PJa.a&-H,$gn! 3nnsg,?N4gtO3ƨ2`-9l]뷵%H*°_'6S6˗l[c/i#k{xk j KզMLDկ++3)qK~LJeG=}{?X bn܏c--[;>O -J* M)1;t֋*OTiof.>˥K.QRRB2 &E:?rV&Iǫb1Iyy9gϦ>&L@AAcƐOYi)̛72~xkpڏ`$K,aܹTVT2L6jjX`dee}v23;n,H4%xDF cMMn׿BPAݵ(@<*/*AQMZGJoɀ҃Bt /(>B3 oʡ' _7 e E<(FV` uu^1d`IҕC3c8H=[:q&SC$NԭT23)-ǓK7q`=b|851 a&ʚ1=D$W/$4T 'k>Bʸ^cj.FW}S?}2_Ox߮3ƐKCCdffwBd_o.hE(Ϸ J1-Y&l`@:}V&l޼Yc),,d OrY;WWW3i$/5uGߔ[W_%s=o<Ȕ~ZkE^kɞo3+7 Gqq~th?0vEU!ݻtMnYPYz5˗/O+ \RJ^}UnVgΜaƍr]wU;P'Rr޲u$oVooJL*SN<ɩS9s8I7X_4B zFgRz#s=m#kbOS`,E1w5]'x{|k2Pǿ=vLܤU--~Hq$}IK7Y[w>xA3)2z,?5>d_hHTnIU/{.TMF-ʳ/F|Mp8}K„arrrT*Kqɚ+,_xKBLz" 3y0.a&W*9Ӫm@emalؓ5Qi]M>-p'kdl89FNhc QJ)2c$?hJliichKArzV "@n* ;thIzDnr't />STMLYًT~^ 97/ >94t3x0NԤԬ7}O˖x,{Wޱ+uߣ^zX}Le7G* =?'y]-f}mX~$Krk ̷O`Ot@H2Lb 6@d[i)RbP^vSxf 2ՙ'޴it-3iǥԞn8Gnnֱۆ΢ނQ4Fڤz!!i3Rp Ov_4' /6]_tcr8 /ůc,BL] aPЕFFWI2$ó{(Pa&Pi@(9p͡0q=RLxڥMxуѺdz'<9] 5yKM1Z?8~aմQ4ɇSCӝgGgwrjWЯxf,7'Jd,5 JNBwf]1<{6>~={on^7GNN(-.v)w4dȁϾd.=ҕ'fӳ%[>=Xz(Juʪ[P ZIQM,iHrhTX8j?N\De(@?lj GR@2Uo(_=|B*]TޔCӲk 'hEf-ҝ9ȧrTPވI¥Kz6Sǯ֍QWu`m , @跙`,{(AF|PabL՘n2~=Nbhg_豦H+0{>[Kj 5{c\C׀ţ3`-9\NbaY+nCb?36ruU}z~6L( er(sJAh2} '*VdlQ['D^Vص y%W)Ә"Ni.5}3 gՁVEf!M~l%ݹPqc`%J K"d"mֱp*=IOD!=Wyr>bңQKx~٬QnAWYwdᐆYzL~ťɻGKZŖ-8mwi&FGqou*U%1QR)  u΁x?*iEA^^hN*d:er #V\u4QիC'hF2Y?2P| 9x>B"Di~)3ʧaϚJF ߩ0NJ^nLTjgt^朔I&}CJPZVGQ|TJOO]]bC7TΎb!¡0#2```rsQ)!QXX#D"A{{;E/ kOڊK NqBvّGA'N:e&yIL`4*y37|;W6q(ڑpL(1%c Af䒛K^v.]{ܺR"YY 9f gL'#h 9u4/Mƍu'zZsƎ-' 7gg8<#d248@?-L<9GQmK__DQ8N\!q&Ӡ? NIww79R't/ ٿIǕy )1jN=O~4'!7BvEoV%Hc H\dKOy;@fB.^l{3ot rg0ٰ~-dIC[?cVOcp`0 h"3TkQ6mHIbp0!O2ߧΜ^~cǏo^N$M<coFyW.ĒLTПPl9?.- 5eMp|w?c|t3O?&u$jN SF/KߞItSx!F!D c鶰D2A2è3a"OKG\^_AJ ޏo 9M}$t<ğƇg~WL]]=v Yx8/lnr Ym}a3go O)%{577_k& /ÇRrQ~۷)%N?/^;0*)G3DC6]wFo@.I&;Xޔz'7F߷o;vH __axikkӰ*G?.]ٳ<쳬Z*.(Hw|.)%_m΁II,)8ȘSEeY!|Gx:::x뭷ؾ};UUUDQ~m6mĔ)S8q{ƍA8fժU۷xb^u222عs';wNJMM /"qF(--eÆ _JrssyٷommmL8_{0sGhlΝ;t)SXA7^gӦMdg;~VXA]I:=Μ9͍7Ŀ˿pM7oP(D~~>_;w2eu}k.ǜ9s uV̙éSx+)--eŊ$ܿY__ʕ+(--ɓTTTgXeOF% +++VŨL }}}X+-[ƚ5kkxg'>ANNYYY԰uVìY 5[lvS_SY<ի)..?55xb<+BAA;w?_~9h_|6&M«UVm6]ӧ+x|.]DVVǏVvNCC=ҥK?:Vz;w0vX;ƺu4iYYY>|/qu3ϰd6mΝ;illl{7ٲe3Hcu[?={000-[>}:XA*+'C,=}<Fu:88gx*++Y~=wq ;;Yf+,{ !hkk,[_|K.qeq7j*ONN޺_jk֬"6l8;  A,6Z[[yGXz5lݺ|^[,6JJXj{%bܹl޼u֑Euu5:3ٺu+7p6lkrb55NowZQ3RG_s?zzz{hɎSsIs8I3 QI d0dlk^}8 BM0 ,a͕B,h gr89;UU]K[ʭ,YDXdq1 /@ss3?n}Y&&&w;m}ϘeΝ /eEXb%b'O2??#p5%X\\$pQ6mLoo/窫Wd,[\.rn;Y O=o6wRĉO~sCۿ2;;$ɥKM dppVΝ;GT6bd2T*"B߹fd2w::;;_%GʭWR(D"|^9^=HX!cGxuy>f˦??7ફRn;z%C)$Ib|>O]]=d1 xwT,23=9K56~˴wΦywGG^ agK}]-E8YT:iN^FGG$$^ t.> gIwqxjc2eh]qF>]*aX#Jm7,|~|ClJlذp&x%cI[- ZJ|,Iuĸ(.VwqX Oоb9s|Gal-ASG2X@,Ŏ%iLGٺ,JJ$DIoG# "Bpv|lvѳXNSlP*p]:^F$IxXn=۶m|H<[k׮eƋصk֭'H$شi3suuu|ͬZ{w+D"N>'uޯdN"I&dXng[o㌌؀eY?;vc.VZwXŖ-b|+_V׿F}}X6Klذ={vfZyꩧعs'$IQww/~V8|_.=ԓ9r+Çx{n H|d2]wE{{;E,˿-dhiiGeŊXlݺ|r|cOxCҥK) Ut0]K}d0|w_s E`SWWu]!vșǎ9 xL]F21>F[{tcGPKtaYdG 05;s m-8Gkc)V"C]ĵWL:U!Ǹ| Rѐ6?'7yI { %g2t %n>rɯ9&yy~9LLz'؜_s Wf?oBrJ6yXȳJ }ωD:=U&)Ji ̭ ݻXj(: ÐSSS>|_1={|rt,;555ɇ>t{h| p9Iwg98~hnn>TjH믿_=O";3zgA4o+vp3+ՂrU>W\Ioo/ٶeY<>}T*Uuew]02M]6z{(?hIMOO11ؒ%Kq2ᵛr8u IR$IFFFRlfQ7`|90/gr9Ǐ[|kϳwxTcYJ承k#P E|);gx//~j_YB>vI.paI M M ІW&z(JmZm|mժضkH^}V Ջ^qAJƻfM {:ԩS h ^.?([>g*֤tqqWK. e]aڵƕXb ^X,FX[+rozzW_us-TYUtYk6J5 0_] [ nJZ{xɓM_U[ݷL<d^|2m&6!yq#OW&MGV!_ J< ɸ/{9XuO{ CF.SGI鹊J:X}=ńN"_/kK%uBF(ӳmI!>nVlUfa(|:_lU*J!6^Oߣ%hJ9>s)z參/Q5yȺ)+JoL 3"p!o(%h̵<.Od[Qof[yŋ٣lld]%)rcfM:ɟ~@t~_HyzyW~ |E?WB]OxdPyi~A4uǠۗIy'E@=vHGX\mtCg@38gkc0+eLj3<6eVWCUHel} SX,U>m>a$"_(<[xא4u&1K蹳XVVcUNՁ:Ad466MHj/SIv,F(,SZ0E8K%J"dt&mQVm?|Av~"ߦJ~R LOk8Ş23HPĶ-CŠ@>;G]*¦ +TI4sDDd2ӓL-ĊC^7x$mÉS,ZJz'e2w #671ƺ CtЬ=qfٹ,+*X,܎\eE e>=bH._VUNC0(3ONJ?7u9vLD"$ (Y5[=:]^E,hNm2ifP̻1VV#$sR|H}j.@RΝ;GX27+"sR4Lx,bn^|\,F{'yGu0.\ 39>ۻٸn]una2[TX^)J.b7$}+azZ׎ɤ.ݲh<'_%b]lQ6%GbA#\a#X7eͻstT>Yy~:aet#a& w nVm§>>,Vw}k=@o_~Iމ_{ms(w/o>o]-|5|{SgY,㓳h,?rCOӟeVm _C\vN6j~u;ώ}\K0hԏ{s/GAUvp.Q55lNYmna)uFWί ˝e ytPo(61)s.&HN{R`/ʹb{lQF$Vzj `~]}=:ImjQ%kseUN__ioS۽ pt&\ݕ=NV pUŢ|vv83/dt|m;ދ>'Nƥg_.ƫ67^'y>?p-'ώi `!v<"$q9.h]77"sE+"`|ۨϤ(mX57ei{3-Kw^ɵfy/W^eH*h[^`ӻnҢOBQ!T um 14׍D'raX5˯AV]J/ah'H .˜Ѡ2; | ˯+ZxJ]R,q96]\ &s&jgy,_2 %6C/eiƧCEE6a%\RVZE$.2KYfIOQ*BnC(ϱϲq;wIsd0w>ϲ,w'a3W0a/TJҘC%~/O=oZPRv,i'o?x4w|: ڦIo !_Xeoo/wĊ.Nz|ߕ clb}Uc&9V5[ Bx1*Oo>tDƧ#Qg~3L4|~!&Tσ޽{ٷogyY|9ːQڳsh122="NG+3 f8ɥ{ĤS~ $Y $LOM@Kpar9Ut^z\9u٥T,3X] hF6e~~bY UTKaR Z}%j}%0dY- ;_y2KE^gۿ|H`{.KV좾TS MM$Sud[)%"I(NS&?7Ç{z ; ۟uK½2vQY |6|=:HEVVa*jkQu޾V{K^Jhh4,h4miA<Pp轃`1'P("lRru"%\h"D,E.W Ǣqb ˲X{[ bYnh<T*Q,c,ds$1Onx^ ؂2AUR4Т1io({Y;3~I2@lEJEJ/"}}2<<_|1aQ*G' >h2{jiSK^Vdr3^Y_Pm] ivvGF#N}>`=wn'/aNbͭA#9LOtVrsR{嗩3V^%*U4q^PbZmx~JB,¿P9~^zVMhղ2+e´ [/8,H>W+ ,ZOwKQ(*EZ12K$(XS!TrDsYmV_ODdsuषGXQx "9XMd1PQX,+jdn(!R9]a"P(h'(D\45&9lGqq}Ei܁E˲#Armf M༚MMuZ&_<`1mǎdlb//mBNNCޥ y{Ws N pxK]B-2K"tPk% z M [)7@ZN7a `Lbw2]T&rw0vBܤ w}@d_ē;4<Q/~?Ē&QP^#FiniS4r)[Z8,=}>ybVX#-#t251AwoGBXK|.G/`{6pa| E?_4ET_I?*oqgEoNrC1 _ V SKօ :Ua3aV$o$I&Kchٙ,R BC)C1_d4BD]˶q 3!fO $uD"ͬc7g X8cS B{C0^M0ߑ0&r;zgL}e{S3f;^7]x1N]+ .9_?﫭%m7* /A-m8_FSg4|rߓ_s\.TM_젽g+i|\-E)8|`|Zv He>H:&IX 5+Ig2b1R4Bϝczzv!,J"d]=9ud2ގ >> CI*DZ2>dmѓ}C kjI(> Tg5 -Fϟ#/blE7`JYEyw1 aIs_UWvXT*-^)Ony{_{k'/# NH$X\17Iy8,vvڢ,'ڋeHcT2} hٕ'X*yXqǕH3dk` GRj\|cn [ EJ_Vz%yV$(lvceg}KpH]vP6_VSEx DF RՄH ѯzh贅D#QN%HNG7'9|e>UgNO$׹/L&nZHLML01>H$JHy<=`LjW=R#MiIm  c[ϙ@'+&9¶KBU+ZW=(_Єѯ.dBuT$h4M]]#vH"!Ӝ9;Jc}+6idQ~Yl"E9J%9t$Aw][DZaCo}=%(\?Вift&_Zޮܵ֯T*Kf~!Vccq~as9 Y~_G?}SI319ŵW^N4avn_A2gjflv1oQ[]E}=u~^T}^nK^==2da2e~+[_#esq(<.fG,1s<~сlIfXdqqq o dee݈2C)`KP'"@}7GW&f,OP] !hik oZlpȗmp zVtK._>Msk+r @MmZoJmۉr?Ҡù^jW:jlJVpUVZi!tW7D0`Uy7\N[ok-L^[XXzwɤ) ;g3{.#f52c'X}㵬imaϑ|WM$jcO\m gGimheX#'bYW'HRgٔJ?.X`]vCӿ,V0PUQimio~o<3'XA{;(GvF 4mc#\~FcuzZb֕K79{b)V-eIXyߧ;}Q>#IU߬d+S{?v!%Զ5?^Հ vw{|M8{<}?K;^#Sg⫯vJ&8\΍=6٪ʢY]rZK1ya'w ; G濊o4?(R"/Pچu!}t`BC+, u(fV9c](7 iW4%>䢗6JNi}m&|W4 I\&ĐP5gՑD(2H+4_xjcN|lJWWiףt0%j4 lV];>_%㚭%B{K3gόr+IE2L.riz;N/q,N1y8΍Xkl -KI7`9)!^Bl{+~In!0pX*wg +ՀXRG*=w ۻY%bc?%ϕ+[ˊ׾B h#dL|i)b xfVGqpo-"hh#UC*MK(>%t.2'L+SQ-ZpF˞| g[n]DOcp%me6YUwh;flۮL[xmu9(mt~`(0@=;}K{+=9lF8Nr[Sb7y뭷}xmذ￟D"]ww]>rsAoL&yygx7xgя~DSSgϞX,2::ʺu8rGķ-Yn]NPi(l'R~dГ}J/\6zJd]+A(zu Ru;&cGF'` ENZ3" Vf:V H`q_oMjM: _4aq^/mxd2i>)K-ni ~6鍅a Wܥ@DSPmPQ.j\1҃ i!8[d4|l٘r TEGx݋R1Itw|;_*! %hěA|eq1zzzxxxÇٿ?f$I(333s)~iԧ>__qwOm۶}vn*ZJG5[D?,?Wa "F4BKo"FΜLKT,1Qd~1[,_#945c~!ˁ,i#;}yDԲĖ.#8lNA (^r{fvLeJf |F"I E+L|~/i9ޣ{-L`M!Is3.B_i ȓF&X`:4M4P**TdeLON:Y6RYXX"%ѣ,f[6D"N~Ffjr<<CbBk|3u]˒IjmF.cժU_yd2,bӦMݻIZ*²,*׭[K/ ң7 8j%lkǫ%j; a< |-@cg7Gγ`Y?9ʚe㴤%A.> #?JMϽ~t#qsxŠ⼟T DIGHTc#?ײ6n>W+y2T;OHRGfw(0ouS j  Dysu^!Z%^|m>0i㤂r Cd:򦈟#=˦ ߲W(v WNc^ Xt+;B$I,ˢiKt P,8{:F#`5ػIZۉbe}.Dgp<?{$5kկ~bHSS~;455w^~p D"Y\\o&vIcc####sN뮻D"iy~Fcc#]wBf?ΓW~db$%rT)lV]dP`aI!MƞCyQfsE'(y>9{ik#=e1vtH&MSk/3ss}ލϼgĠ-nue{ȺunBqLҭCӇA]l{m֎LFs  3U6_,jcO :-D~s(%:!C@$@־ۊ[QP[u 2`/{ĂbSr14ɧt1 xeB^M>^HJuMm2&,kCuN?Ncc20#imx Ak[ͭD"Q'hB@va뮭 =[ɨz{2td;oCJ8lu=s* Dkɒ%h4J*B(YEKKO:ͧ}nmmEef455)Yna:^(P KhTta@F@VlADiegNOH1;Ãm*X۳1@*!2b1ϾcfW1[ȱYO"Dz-M\IG^),lשV1. VZu ڃW!G^$T[VN"Lp<.{jbшN Mݚ h zHO|WZK̶[v L썕 O}=jΖ픇z2TiZxLu>z`٪aEHJ <_zdK&) C]$Nghmkȡa:.%ӱ"!F76216F߲@'o ӹl6-]VA_razB.VCCExmq*,'z=m ʵJe>ɀi*\7!FK:!ٴ2%d8 +$j~r֮q!J6bf~V-_A5&rSLwHмj%3~"/;?Eצ]5=vu(cb+NBi^Ա5%HmSNp<.|a7.cxukx}tw4j`)ƧinpywP,88rlhm`jf +{x#V2y WOŞo:߽e?:2rth;d .*o4󍀟O,=A#<`E%*mn[%^K}uޓ˖@LS,wL1- 6B7Fl9gk,NK AB`rC%--4mѹ[{m!oYq d[;cLβRӪ%PV;y%l!8uăaG50 vpZI̜vml4*DG{Ҙ MTO˓wERI_a=ޤUЧj+J D2\"M6#f<@~uyh*My4ԯyR> $zDaɻRuW+,ܳɐH7u<):p%(Ć:)_`ziƲmX3ry b1 !E6mJ>@;Y$Ra̫J;ua"MsS "4ԥ8qfhĢ>́gvGNw/O 19u.w?v+=!RPku`FH}lgHyZSJZ79L9+d@d+_| o ;q3L@H(irR#eMl ]Hخ^v5LŔ H-a k׮3G޽:;ϩ;13Y+/yW䅷`GbHQi,dUp"5C43kձlhO$d1eSl8@ HQ'z? <'^RbOj ]$N^7'kl޵2=5XF_)ל=| : H__PpM2g*/eKF=;3AC)Y)aMM6Q?i+|6ّMC\G+`8~=3'snN{ݻ9A &*ڤd%+gYVhr=b)=R$K(P" ,ݽ9Lw=srUn~3gXpC/**”< )kAO LJ03r΁.nZ111eKRKxh\XS*ݎ}sevݮ4=6LuvNil)mb GU+vؑ'^e?rxw.=9-lk"٥MQ.F BD95)ۇ@HmlhIMdNlm y]1j4H`-V&Hq1N]V}c0mKK_&V111QLM͡jG j5Q<1@"$h=cvH 5vҐvJ[zN+m2ГB9|<͉Zc(ؠ15?=0}\=FJjE;׺IV'ʉ% Dj01 _T̵{G֢9ā>yfj*:VV mU9 VWV!P9VW!jVWMk]͍u\:slckssԪU,-.h5a?{sJޚn@"2Lt&R-s)fV&k؞T1jx IDAT2F槔)2KVrJ }t߉ݻwVcv6zIC݆.]Ǒ#oVW7<]"Eh5.UhR6n{X,ue.``V8F@/ @ %?Gg>>̭m෿^??{_I" ew+h}'PX O#.s&< ) 4ulmn^#_(`Ν~C##}_i2ffp=bzj bfSցy`aiqV =}@jj{8]ٞX7m;t<#FMc]&x֒6o ! 9}M޽##Tp}+BHdrz’P:ϻ j7AO)MY*)Y\ D(oժx+X=Na|wWFz0 ݷ ?x;{upysC?2r|/V8@xp;䈶fY4lEibxf{u rSOM|Bp1Ѣ4՜1c%1 0tW( f&nA<֖\H'KMFI@r=Q;s< g59ď6٤YL<.(P̣9 Sێ=ipfmbG]Az,ɨCE fQtfn@o_*1 @>G 0c b KUX0`~"jj{zl4Ѩ7P(Z\.;p8|+ 5uN!6OMQ88ubTVrQ8vJXL_h9޽ux800|8JN 1ތ tUh;Ig9x%;A#@=\˨"I/9w.0(cMVBqr/Jn!G4 oㅓ/:En^\B#ׇWD16IUհ4)XRˡIn&A'KCC<Pmmz⭓gwR[B Uf M0׏߅]{nŎ݇1Zs0|!W P*ћuWk/S]=8|#KJn?wW;aE&2mR66uD;v#x68XYBobʥ<`ni v2Gsgbz>z#ũy4-=7ؿ{C "Q Buꥪ?\YtKm6'X=%.7<"qQE?PS`GF AUh$N_cAg>p!,[X7*.-z A) L&34iO &=](e`'%T(b509FF> ]j(W*(K@..mtEр? 1a.xv^\X*a߁`X*!Oc bAՆ2kc E/f0V]Y?82R538L@Ʃ_tk؃R1!o*Z_=%o$ &y7wy04>vْ'"Y,,Xn,ɷ_מ:*x>Z k˘`ѝC5lP04x \_Jh~C%C zzxss Ux( i^|v;l6 6;uz6z4f|y8Og&;+{o8gN 7ݎgݣx59ԛ-ܘ]n90_?n ܕ/y\b^;uF 7.Yvhϼz\;LbJ^ LEwﭘ:G\ƕ9$e\{q8q"PS"5[~xB#N(AW/yA?]N~ bô}pD\FDPx'lh?EOeٓ;\@yyy#d}>~ ׈jMۣIIi E0yfCu|mx0И}m7V{R%hVLrfvВ؈lvdd} xQ;-_6Dio`y33ۗ ?ⵥ7@9X[xq~G0ԟCP)!lB{;'PJ|`{ :c 8_m:CNu8.1= (wj-&F1cػsWۊsk=XZDW1\.@W)rI>ܘ;or}w߲ h)<Nn0-J, o[iȪnr+P qW69|w` mM&G`82A']6Gy'0IƺLYYr/ L7;yQG3љ!n}]rb.NvN}KQŵPÒ:gGB@~7Ynjz͔T`0ўujEnVJ s,oL?=8oa,A=5Cݰu| =,^6P (=u&6J!v"6\:܉wG]#ÙK3_G&Qk4{|#>;ko_{> 9L `ay]z%FXE@C=FcަSDXurVdFsq5D.}Lf48Ac2&5QI]qWK|5_  ^B̀@3wt>1s#I y<8Se=NsfDf%+z*sT_'2'? &&W֮xIB0}9wQlfDгXpɗfFgoml磁s;`h!@^8^ǹW>=a;er Nmu3<:Z3Y:+LP\sVַO= 38a=nG'K%B>Jko&{/,Fa%ϙ "s! $󯒘?) 4'tc@dңUZ?3gk=BK\ZYLJ Y*B,iy[`2lӱ年-Zߩ9Q`3u,CbimGWpZdumkse%NJnJ4tzЉ ҮӒ+&zM$~/=^Z<~!-CCZa9 DZAۃ] J*-,.a K!`)hZBoa#l0>j[ mFW@;cO V6oY:KNƚ|30Vs 0Ʊzȷ6Y>/2[ v#vtnfʶ>l<:]14=H(@kc4~A)T|7©G;hy\⸙P-@t `ʫX)xEC܃R+4O KEAĹ3hf_!.MA.sAv,H3y-h05ѽh(~zI5UUMkz2 $:{z$CMD G$ qTFp,B״TȄ?@TȴbP( C|nbAK}]-$%;+)~ɜHT#* x d%vj%݄ WAIҒ[顖Lt,GZGOV=HI>Y'vxdAi@ (v6֡u31봏MY" izv /wdWP7iևmL',MS~q4oKs!>|CB. Z6r/lͭ:ZafJW/L`ie"4J'qҘXRVb3T\HLJ@CH~a)R #e{|_WmW8m8!3RzܤO-;uAuTxZ%׌^א,Z4$)\J'QLP4a0I*9L]|>Qk50CW g`מh8 -m,1&_R@\p@QpeSU2X#Ƀ;1T͙}i !!=lląQشmze%[ROl6[%_x6ZY`' u6y\8\ , \읥 ٩lr[tLE 8\~a+xu- VVQ@_=e,nZo\*>p7zm,nb066k]\CDW1*޹0vQ<{Vɘ4="@!Ǭ?̇f䧨0Tz_fR "~2ի@$XD4)03WRl!ˌv jwߨM7ӗI29jƚɇOA!JCP0K`T.,3O\|ԑ'qp aph(1jrX[]y؉\.#Gow q~ZyQMWLm~>J݉eYiA5 05 +{|]m8<~`Xݕ+W$*O:R}v믿pE?S6Zl 6mai}N;l~ith׵) ]ìK+۱g'$@vƸdMU(ͥN2Rfvbai2Vֶ轇Nc?Zۃ߼ϋdܪZowu UgW01ڏ0 k|+kh:FR .՗#Cu ow PŒyz=sEÕ0af뿾lO4=i=,QAhVtdZ0* LYf;`I}Mc#vI$"M 0fRzzׯs\`ƙ,lq2nm Fg,nObrrַ077ׯ#_'Ow~wO~(FGGq)bll 'OD;q5LOO}WfK&_iGZk;nLi`.z7Rh̚ [ ϚNhmׇRMn?Ͻ~"n=0߼b>α|O2*|9 UwaibGBr4-]!5P0H.ґsb#UwbCm0evܣ:y)ZRL}]h`@-y'6qM?n P94%ȶ=ӵBn*B _Pt 1 ؍GTH^eaGcS/-cuQ mz[иyI)MS*/iUھk焆m 䝺mZBxWsϡXXXG?Qܸq?8>ON|I菰w^/2~W~{Յ/~z$NXhI;Wt:.VXHCՖviEw.YMZ.[O۩m>:aě80g.ϠlcyuϾzjccm?>{R~_\ވyGuMDž ,}Vַ;WyM/arl C`UBadQNŘWYrmR0O Ёo#6uh(SUfk&M01Aj{:Frq3fV-.R/8$OnvWzf.d\kɱԵ'@Nwږnvo~~z+P*p |;j6cxx\qE<k׮d.|ǣ>\.C{ٳgq|ܜmJ=ap4v2bEe\[ފ P)uDQVyӟH˻mä6t\zܦ^A'N(DG1yz'I꩘>'QEtKsg (b悄\ TзyCpWVlY2f1@3=]aOU S,nQ?x(PL=u3/&6!cj8\\D'm[R2l+PPѷ mU܉wxX@ BcbFPXl^)!LIq=99bo-9! AUlbmm ǎy}X^^w܁__7Mj5|_O<K_ߏcǎIo|;wĮ]066QW1'H eC CyZ~P!|Ifqc}\/˜ƴxp:<1eky47Bs36ԾcpA`em +dco(A8]OMI3IKT;B# [Fe1a ll6l4h4ШU116}v}w5ƹO vb;:= xgcfj]Fm;0웯wsXXW)Rgphr5,oTqa4!fS. ]4\<Thl'z|` `cʓU\>g@Z kkn?*ݺFL {f!`yBe[DT^o퓧56:"Lfit)'&G=j6)) ^xw¹(W1<:U.cei c sj\F, Ǽf:,'ZPdzP'7Q>~ w3]|>Peɳ]peKꝞP:iy 9x 9^C_Ac | 0{ Gv bD?\;/ǭ{G07/" 9>!|C#q+%4Zm}0l6>پsv|5r7vZoٓWq۾Qol~w':JEf֧9WYsމ/tr}zD2kRL}T>)c6nBysImPX}$njv pc#{o')(ӟ%̈5DKbBhnvz!脋:Ð)`lEۭҲreXMA.-pαbrZ-T[XEVKt˪O눞%ƱhqvY<) G]Ғŗ/,,e@+fpy CTr@Ƶcm_\mB?{, 3Z ;D6Z8yq\©)Lͯa>\_po '>.;o^s 18qv[_#Xjczo_C(7P*(|Cت75;c/;Y/msgH`kU鈈<8ARȭ'sEm\I=7ܘgO-IۀZ.lG4LZZj"ufZY6d5M:FrsFΗU&0<8#s1XXAdNr4"y Fȓ&[~vaLFi,IMY8GTeLL Ĺ1TE" #%SRl $#&_ʴf]R%UcjtfN@0D_/Kzvf9'isuu J"0h6r^] 6+J}! qW?4NIln.ɱgIܶpQsM-\99{d>|8_Ւ4 _&zcW.bneq8sm^zg kn `亶Α-h[c+Z6ڜG\֠;2{F1 ^AKe8M'XXcڪe h1 :4pMY:"H/ & N Sz$0a9;EJ[z`wuA/='uuKsBF N2&`@ yL5T?z { R…sgq8\g`^Po\vU t/n,f';`gRj}m)`̟m.|IXZd1}߂T DhA] Ч@鸯 p\#4Cҏ/b,xS9I1w5cvm''j8 ⵳==`ml6}n/{2ąYMZ<8mio6|߇G'f/} 9j|藠 e0n3촛 DJ`%C|gLqX`o'Ȇ@+2̯`jf ="^|.\Ko]ā|[f 6ꘚ]F>1R1\c=Gp,6u<|<9 C,m|QډLgՃQ#Mbxv3cɅL| 1?f9Y$؉Lx*{Rl7yu4R ȡN-}qk\)&){2H4f Qwjn'. yON9iQ99ãѭXax[+kOU{wI0j"b5+NBā6?|r[[[M|Z-]Jf ` R O7;oX,1௾_p%|< Z*T*wakkb[(5utww^nT,^=arϗĦJi>c;q:R,i/KZ;#ƧTw" DJݩ3,YXQ*ؿk߾fWn, PY;Ch!.Nct+k[p;q-iZyQz.ѮBIё1=aw"WP ڕz#Y%j]̯0DQV8 zKFbb 9 tn(Ɛqfqhb"5e(AuTI1ZfУc1PtcIYHM`p̋Xaӊ-iv*G_3+u8u$?0fׇ˗.bxx/ew=%s=xgp<ȣxqU Lxrdʥ*.1WP/:s {v1U Uk|Kws]# Now S3-crl[cf~8b 8o V\☖H~zXE8W1^t>GcDg{FL"+IzeyC@Rg_˚xYNmCt4XA8Ţ (&{*U@ Fה __K G]8yFW{0j]<(6R\׃LJhMH(Z10mEQ-opa<|/SSo<5~ /><~A7?c|K'3w^-ܸ1 9^;~J}X[_3|{JDwB@ GIV8f4a}V[2c12mxeF+Gډ'6}rnV.@WBi&QSnĝ;^,jq"P0l'9J$ؓ3N en+pr`c6K髂VbE~'|4)n&9% qY?_'s! 0 O l$wSLWMk(<:8\&`a81蘴q.:-w@7M6,ɻ}6u 'y%d; @2|u4-GL1Bj3?3(Wd2w]kv櫳-j50P, j)4:\+˿.h] ,u}gpœs&wyz" "fLџ&rD>a+C"TdNl$,Y_IݩVƭ ʎ9>wPqZ` %"uT~M"r,_8̾Vq}ZV&;nyEtYjHNP"b2t=CLXGh`mucE.'~9](#gs3Z<nhb7Xi@yȮ<~KSm(QY53?x509244gIF5[ؒY@|fASNKc;'S6OY'+.3ee_5M/zz)0q6'qʺNY:Vԙ L@θ}鹝 ( 9srBF1'3=J k#:cE$uy%`Kuh j&e,3 q껊LFyZCHuD IDATS?c)fHJT<-'5I`}8?fvM4huQ5B67KWl| LArmc.T0 %T~wMC)j41'h`aqafgpQ٧hC.CwOaK͔H%u&8aLw|m,STIж́kQȡ*_|We#8~ChAD>[#6,DMyLz&%r;x2@@&&u9qM묯0 )>l& &I}XcF?SbN])Ov&2CO!2EuO?}N @$$ҏz|2d& %b9J.j1FU*~#x'-}M&>ޔEg9MT}te9BmIşkm!e\Q&w8 ]/fEsBҐdg Ǵ2H<1CCZ]o_? " c A={nX*YSVIgt 6.6%v;WQ 2 *,n-AU:)EI;Wm]&|?o[{^w si=Zlrm\`&g Y3m]b#|nmTQ5p LM/aqu']Vj@.1$;Zb^F# 綴 ɃYXm%o;0. FqɇkLydɉd]Cյ=ɟ5&(璉'@a$Z/K$ .' 7\P")gl߇yRWWkB|>bAQexOK554`FѦؖȤeڛȘ-$3+޴ 8fWbۡNU'tJ|QY]=̾D[sHLrcʵpk%6:eCMwLi:l24D֫ݣC>W1R!]c(tX߬! p<=R E3UH;rCIFULiLѲM_I8p ѩb\Yg0NE*3æ t2zgΈe`I[QL̸NsŦxdXҲ'JpNv&#Aec p'[-.Fi LJ:ҧh-@蚻>ҐoW톔ܹ7lݙ$}fJia[Nuκ~''i&JG[L`Y\!K4ylr]l5ִv}ew[I[Νk=eoߩΚ1wt*΄|A[=*$CdWuС!QSw;q>+:MdM60щ唯 cpAa~` jy$# 33X܇0T.=ѰK"F#RQ<&mFȺ]0B>4I%++EIK$苃[2XgyJd1KNdջt6S:ILַmw>;e0x饗P.0ӖL<4e#WZ Nj'%o8'v4c{fs.?"N@kegd#+v)~Mc\I:z0mRv].m?dbHUka+)NF}`@ cE/0uD\.q4 lmm! Cxr,ߙ[108lȐJiҠ0O:R4i@rp3` !wď6N'y0>vZb^Eݡ Nsn#ݬ9KimZj~??O|xW?ԧ>|+8z(~7[[[`Vl?//_Z$w'lT8ڧ[ GV4]mTVtympY~c]=sy^{%11!A*)"rSdL6ɂ8#C? BR,[[I~"/R?A 1ȋDzȼ/ؐ\$'< / ;и"xӸh(Yp L߸7pX\\:Jr_L:$s!R1HBJ5^2c,NR].VeXcA3MZAs\ jgqr$0E^! dF4FaD6taw|%1dz[,ԧ1Or94 r9߿vaٳ8~8nv7?Ϣ+s.wt')c pɒ&c'<̾&_S5-6YZJ`\fچx#sh>OGi䚎 D>6An+T΢Fnn6n l#jqF /1.$zaw#6"v 2;;19r}P.W:q&wňS! Z q)\Xݦ-PiEatŴX. n8 jժ./\/A:%$tnqc]F0";,I 3ӪeR%[,9rRdCjj1am&̘gj!n, B^._r,@bKPY'HYt]s`zmK[4 L~ۿٟYm  ٳ<CCCAR?f{ 4ۺȖM0 v4eIw׉vl:&.Pekz"`*mBHڮAnCR0uXKcUC.H޾cn7D"y%n QsCnFOr.NOTJx^ X-RH{r]`1^I'g9`̃ӸM "% J0WI@φmB< BwO"HGMp@їaey0s33܉.\}\8{\z{)2rgg184 ܘJw7GF |?fo`lbSWlb`hOĝއi0Þ}A\| č)5  vQ[9nӯbKBn8S b'1Mz{{D/& 1icqqҢty77[l+1ޛ[V¿9}y'I0DDJ4F7yc$ NADQ%@34tCwp>S}UUoj}{<{ZUTUw?UַU_tEznF+X=zASZO>W܎.u、U8e\ZA 0m.|`-\GҀBC`e|_ X@E ⇺!,LC(v?ͤ"pj}( `l%!F.9ћPfta2Y;ڮSW*9i:5w(ś`Gl"`83XꏁȒslW Z蓠Ps 8]+ tAJ,..hmkzz033N BWO::pzl_>@FGNbq~M--CKK+GG5>kPT@d]==E9|hıcZ] Cni5&̞V`8 $msE dgHC=}xG'}pqy|N^~e8p###8t &''qArW ]~5[ȼq_ku_m h Wj. ?|OyQm}hWޜK* 'cX,kOFP | Vstr3sKQ}GQ*qӕџ\q\_n1<딻:^տXUCi`CGו\ 0tL)RGPo ۺ@iɭD^XB H(u)ePME3CZFU)ȱm(VA=je+QX^Fo38v0Y$SI܌Ǐ㣣X;3ӨT*(K 1??LCccsssfA{G'Naqq]]''+ V!Hӡ#zx h;[W{3A0s*evZzJ u 8z(N8|7 LLL.qb޽XXX@6ݻ\wO?ƻ.ڵ f:t7|3^444`֭?s,LAջf\qb__WFY 8X)j>G'o-<8jT@xb댎ڌ*sčR=:b:jf0PM@4꼿ٸDB=XqLhl@@MʴasQi+0 E-!MHD"4!Ed=`Z-1_?@V!bih:>thdH$ih@Ss3d?F4 N\)#͢d}(J2 `lt/߇,7Jr&!؈A|`?ZZPɠ\.a)$I*1r@`i~^'l&q:qjW Uwdm߾BoĖ-[ׇ ݺ0;;}ѣT*⋱elٲV¾}p\uUشiSs`ry'6k*&s˦~. DurڹY$~.{ţ[PV\n_ᶊ?g%CBo䖋etendb ᧏B{^>9 ucc(Jj}'ҀZ8o %ԥSxjlǺU8|4rX;؍g_< {0rz!l=̦^BHk=&Ħ[l&J,Ɵ+s /j݇Ƞ4TKC-6A]PcNO !,[S9A)hM.a/FmtЄ^7PPd+ Tu`h6DT~<0ܨoJX|0ڴEcC $- ^3g 6n63@]]6nB q@2SuuIfzlz6 fB1m{'VMUBAANqZ_뗵|twwcAd2\~H$hooGgg'py /į~+\zx^;vꫯ7֭C]]ߏ\.͛7#⥗^ooxek}z\^LP`^B>Ƕm DՆ 066d2q.#>`mgOΏtG׹@ JpǏ]nmC?MGs|A.*ؐ1>TޘFoEՉMYjOl: FЍؘ \"Kc0`}8m|ձ_[(`m2M訜E*AE&XBGex%D:<&ff-WPbFՇJ4;Bqtb8hb)%*2  0Dx{FeGmCAS⋭J &|sss5?!ޮ3Mt+$rfff}g,R::'HN8k~>9|<8j3ױ z IDAT61n%zTӿf0>8Z~*:!P}yR!GBg}n a\ D04+jA/- 8'hzBg%!M.[5̌|Yab+Qi@'~Ĉ_zйIq9:AhQRJT⦐,0H(nmSs&$G:P.[C7fűj+t35^kRƵ5^a+^(sssGKK r\u\8kFu_ۄGRzT*-[dv7ߌ|֬YVo7 lذt "jp7V"|}ӨempS+qi}}]>fN?^M^t@v$p.a&FC%oКѵU2\dE^jG(a M,h!}O ueQ|b7NXEїL( 7 ]WnyXAб5`#MZI=Ԫ*94c]|r"L︝jyT"2cINU*<iWk܂.{ #?"GP,HC;of|H=Q;|\U}&C\xᅺG?PǵfLV0ܾ~Zj m)g%CB&HV-1^-\$H!aZ]CmeeoHh:`RW `|y hRˡv(j)) l U Ύ3I_m;atVm >L YX;ZpBo#6NT2Eh@,8QAF:]9;Oii:G8R{{1jȒYbh5]I07,ĚyCj,!$,R+Ta*eƑu Upr\ٜjZەǃl@<\Ń56V>js\ ugze+N5N9yp\A/>dL 6ͬy*賯n XY Oq(+%揌xJdx2gԗ=@kÈ/O5URFx(l;^Tv, {X(: {$뮅GR(1QZO`~9Ejf0ИE[{;|hmoÇ0?7 .= ^-(zH|Ed,`c+RWq6SN'C%!kЊabGP4ZI2>H5,--a-MW*M-P@"ʋh6f2b+߄qq of8āluqw<.DeiXkpUi` v CSR4i?H@nc:j+ -CC8}3X-as  $S)R)2P:852;:|)#ТB?dil!wdݠ⡜sNUq%.( &'ʗD\M;1lܸ ٿ?}.! "r!PζϨ~jsse\qחe4{'P[ۗ&ߙڊW;~%l⫯6qc?ݦƍ TAS2ESJ^lExq1ή6@#~P=$L.lla 1M> )ܢ,ׅ5bK=t$(=/*u Ki-a/yhf@ND%%ϵ19M·fOsrr8/ߏaLUhfqzlT+$LFI{0!!c giSLT E^ Q $xD䃻>pZts^z wU=|O܉瞋'v>H?'v/?ɏ+n(U'>hZJ~t*kqmhԱ3@j-9-7zx@ }\[*-"g3\UE+mo \ ku)$@ksxrtv9q\{hin =02 q%;qaɧ @I>荓h @32X>i[J!ew%tּ"mCUQ 1^JJq(F GP6.lE2V*hO]tJ!*0XaX)zuNꥣ-Ҡ3<r!}oSK 4fX3#BeA McPr)`®,#fȂVX10S@[rjA ǪzT:Ɓ eF𣧻x;c %پpo5,:= sN|\z#.@N-'+m>>YG&ŵmnyQoiT_~E+>dՆo>302>~2ߟWֳVs/Z7tO=xߍ|G? [ye<:o}-x';m8v4 ui,/1zz|hijo]{{,^m=B>'h@o:E^Syxޥ`OPas=Q{WUOg{X]e|:׍UKJ; NѢ%xxL N%al $\xA*FTFTBl>U]YǕrYA]R ><.Q)<a yte*KjSV}%W@ԕ"Oi*(9*Z_֣OE;GV*zn$D |qLh@aہXv,5oi^WJ܆%5FVGAk >8; `s} :Bx[){vTZl^^-0ٜt?/ `Ͼ}?BX7Q(i<~Eva*Ip\r$[G%'Tqy8^ynB$ F&q%RNOa4^wt JB{7o/}Zt6@Og+?g{bdba)H"6GsS.~f}uxf!;9N\w^/g"2|aܲk3ْlڔξji_e7M@h椏D^JHâ*kN8kkt4D~TQEzED1A #CĐo|\4[au(*pgRn B}}=3ÕttVle}V^ALj qm|˷r% ׹O8>TVyj(l6}s=\}kI/XB2p^/WhjC{s+2Uwjk)\mkW-Ӳ,ЙVx!ڵkى^!0>>Fٳ۶mC6}> Xne,RX-Wǫ?Ke >yj HԻh8tt-Y45bSH qx'G'1zz ChSs7qס.CF0ۉy)lZ uT$:nt7q,.呩CWG FƧJ&Q*1ۉ494Wa|rb qdDxr)z<@GP:{b'`չdOM-:+"*hA{OI_&'jlJJgvB__/Y%͡T*cƍ(him  ҍH՛cbDP#iVK]i[-0rzO_Wҩ [LM-5kT.#X3؇檻z`Pe% sN-y\FKK _>hhhg>|__n09<'n(:;;ڊf=z\pA .}\F\f+q\&~%<]t(3ѵZWjUACc+R Ѳ,PPo$ u$ KBA27ktH$c3IVw i n%cSxk.󘚛9l)#.9_OqQa}Ex/X}}hjjBXDX\U@Kk-B63He,czAT^tےc[iD(6l؀|V·m{xY|?RojF?yKnDeՖo'![-B>W~lGL4a>Y@8];)_ɐưI%%*;527_f[7( /7_/ =|cx;_㋸x5oM7 c'0;3c~/OV$JR_!]@[IvۆVN.yj5āNO|/tV o@KK :;;!Ď;1tttJwqgcll ?񶷽e܇}*|ij߸qՖZXl kDZ)`yAi(Q֙hGz*vesn-Cz_RNy79W},;2Ns%MQ^Qu𶊦bkL425?63hTƔؙ(CE k!Sm!WJ(i- yv@V0W\FPrwu7q oŁ/{>Gt9?x3x٥x57 _{?$*2JŒE_J=~q\~\+Pt8~x謔V5 L !{/>bzzdg4=~7w{A*¶m܌6lAW:Uji[ͶO8lULxD!0g"i{E ,8CviMAb VpЈh񢪰Pë^OG5Zh$nʇG3XDH1`EZ 9)ưڲ-1!`juj_JDhHު3l3]TM՗Q逜\LqѨ kJ"ηVBEV H`ǥ`p ::;G~gDXDEJ+k%T*44fqa NށUT"2|%W-P0&,>ّD"/V?x)%z__>Oio}[?ݍO|>ŵqsRM/n8Yx*]M]Mwz^mɯ70Sk~EHW]E=<HHۣ/˶Y My% :qHU<Bcb.@0ؚEh4yKyc o7Ԃ~^Dױ(`fQCT֖c)3ڇѼz1*!Lf =8x |~+HAJ䎐v ×m90%Ԅ!l9{ ~^UCdm-[7.>g lG_+"DcCejU}Eͭj.m8\Yj$-.F@Y Ф׮:\{] IDAT hgoqg]Yv«m߹/*B䖋r;wbe<)%r͟-o܆`P xW{тg矽GB$fO>,q@ӈ3 Tև ڰlE><җ)RF:_rljnXRdd4Q*89IgF,-P$I41c[>i0virZN NT* 3X|1唑TNxD@| ;E*zeL&??O"J!H 4=_ERA:o dD"`zQ`ŶNUV^ȱZKZ9cj$ P(`bbUс&=zU+hkkms&LJ-W:r p֚p?˅"b;~݆ޮVӧ~TCH&mCog BM [mGO7 G*R4Y67SK9s,UQw :T=-p 8l%Xy[Ha'mANiI)LӺ'IGՃe5U{ ry^%HTL*LW<\|WYTLySg2/OO`VR@w>CTDR" %twwy۫Rcu)15=R)%NOLbxxV_A6Ʀ"i#ujEHiZœPUd|0Z2OFEJ4g3 J"L>P_ 22r/qbtRGNL 64J2&J&Zn29n[)ݢU0*?ErK}>RZQ${=,^|c?Gy]wp饗bvv_p뭷B~XjN<~rm[o__//,t` TN☟+%*uuuho!w__F˥kq>C>sxd篰m)VH)Q(,#qXD.CRrRrRbi07Mk܃,A󸾮9Oтf ɤ_A.X,MY\yu}GO#SƑԥLc4d.ޮ8: {rv1"ov&F~oYύ j #>*D4ԭH Ze?o^. B |PҐp|pȨ8֌ S,!]dpzjp܄[RJ,..[R)AԹ{[W$!A_9"4898>}ލbn <8߶yz:{2ÝwމaG?C=. w⮻7ވxw:w0o"i7+be;LE=3Jrnr4goB apEG#Py8,$a! )[RjL2) /4@+0kKfsTazeCp,ʴxPueαH9'tt8CkbjKis&&{egZZ[-B͔&0Yj* ϜxYPev}_t ]4Wă=^Q,P,ϡ͢xwyxQWWz<Bk_㮻b۶mr-=\ve 2cbbԧyf\wuwy'"]?C'2V./jz>}t Ǐ֭g^6z-_"J֬Y'|{n۷ox䙘~8uv\zJ>Ö-[ @B7 r-G>ZEhelܸ\Zii\r_##>T* T^?}[-^JrYdf)Q*/B(nAlV>  O>]ϹK/T0&M\Z(xTl@ ϊH)VSHI (& 2K1@I5}/8ad3E,>NҺ"f%&٧x*vԦMD2HJ=Mqi X1k5uЕD;RB]8)RpXPfɝ_.Dlp9RXjH2C)=8] ErUle #k 2<<'Nf$ tvvСCn 3cp hhhkܹ֬Gѿݍt:>!܌a\s58#tqtVŕVFqΆ/ ɓZXXX@T4* 2 q1s9ؾ\ Elbrh_b.qM%TV#mrGig6qȇ_?Oɋ Xv`dY,--"N㩧Ĺ瞋t|^?sEyR)Qq[#WURL?lֳH2+iن2)n|nޏ~xO<ͅT5V܃:Nl 4N!#i 0<-=џʣyHIPkM$:TKH? =!ܖ̈HmPGqeY3'f֠ maoߎ7vfsܶݦ.7A/!%0;;~@n{vLLLD"Mp۸.=pb*kY#sۆ\sNhii"ۇ>]O?4Z[[q_MMH$/LӸked˻pxY\{6vnFat"2]%gjI;~ e) V1ǵ!"'G* )M" L+f!+lNʙ5fd" t@$K6, 'hԵX d 5L޶QRitvu$P(, B΢T dH455[ɗp^S`HǡN\5BPYpрOtTkqUK7(p":9mj@wZiājBT_ω؍k۷oА_JY=zĩc=+5o*W0lKy,8zH`V:t;.xa^X[Qr{ ;zKK^#[~?N%$hLA{cb e\;5_}׸ z]sRphj4uZ^ݗQpAPJ>@9Ӵu2>L?d\)dH>oR<Ȧ+%W~Z[PAiifLqCm>iسu1X4ؖTN2&!?6IG6TN;ֲR*]0nZ[[P,P*0539l<,Ǟ}Y jMNw.0fгv~LG`B9FwooyH)159޾~92WCщd*ƈZ" DIٴ"-<NV {-͢yƁ8ȳvJWbk%t"T.#X>5^ t3cx={~d7,*}_ r8u ;| b6m΃[st" $  Q0^:8 nWsZ@B؆Bqu$dyE,R-q7S-.snCTBW{3A2)0&gքrT<:ۛ2zROI ( -eDyO agrh@ex^ m[VfNP +6$ k^D'fY}<4"jS.@4EtQ%]݊AUB 6DzXPA憵PGV{G 6@mQ7FI ɳ \hkoSҼ(mhni"ꥣ!1E:LU(َ]"2( yp[ܣqqԙ!C9[rzIrQd|jqÕ!c v.69ΤH뺾 1Z[oE@+/[aՖ[eV/O0yCCPF')]quʤkmmyEzkq^Sz+ ֡&lYy/ 庶RZq~HioǸfcO]/CjE\,6I䖋ذsKXXcݪn$\v&,شKee Ed_.bמ#xadhnbT !pzjkm^$=:U֝0?k#jB@6Ĉܿ/)& *#<11B2< gȈh-攡A)OS:ss)!ekjV+@f@j/D:A5УrQX$h@K[+N?\n JX^#Ԅ)ttvP(X,(3T,"HR.#ϣ>!S {ޒڛəRzisq>[%s0u!swAO~)#l|LWk[+:VtWDju٢A`úLuCUz3h@\wfWRԄW[z˙Gtu=wQ+уQe^b w]H$X\ZrG܇RU}xxzQ\ x}8t4$26 9=f8:SX3Љ#''+)E82}l4*e+/ތ=Y+l4&`%WLDZtܸ󱶭گ W99- . #]DVl}6obg' s0wV;IKB!Ak;ZW`s.$9/ZG$UF͵3Z| HX5:YH$D:F*T*4f<P[ 'tL/ 5}cd c 5lv8q@0f![ĿGC{CY#kXF03OohzHDӟ4.i\7ު8|lu>>`Y1,ڙj%᪣x@[5Yh9Ȇ;t*hǏmg q+CxXێϞEcC^u Z! L$y4b)WغagPN\)^bc#jŶ161\`l׍NQb׊~޹.MBpkJjIM[5'#Ĩ2 ,fQ-aQ#C e=}M_`$ W+N=@GM7x3Zp JMV)QK!Ŭ#g Rp^ETpei\q?N~;LxsU\+>٪M_,$6Ū6:!z~A\ 8|rɄ=X.12>U@ضmBG[^:8 ;5xɱil]?Fsi i䖋loVV~X-yWX7`y(R Iyɨ(x`$eǂrƒ_6O;89&L"͙TxAp$"8RIS "I$EVO Mz+K56GDy8M;KK]k$ 6p stng: u 8^vٮ6D:b1kdRc+GMJs3WMWjYGMN@'VIl-\aLGMjɆ!s%>BةI#Os{"}N2prl'ǦޗOYB'0ӆL]Tf^i_.}}F@hCוݍT9商6gQ^84Zh}z3Y`i ǚ_"(Qo(Ao4E Y t`їlB M)I I&'PqQ:T6o '/Hbݵ[sdx~CcYqtiOڂV.~NGJALF;^'cAOXm)#`?@ k'"z\L6i*?x֢/ e|n( 'C̪@ye|eZwӺZyKnN.Y EۇY 7jCG/ R_Rsˎ5׈ ><ƒ eb+#UI3UJXG`Pa,Vu|so syhAr .y[^Ricw, V U$2v.)P+ vxTmAuq:"Y۸ap3dbtil6w,*.cAtZ@V-rt.QA뗢u]QS}r8tA^RG2=Fv攏GqkWeݭTZy ,W^)\gy7>F/딒%bDV<ЙZz PFm7jd;|JOѧ'F IDATl1-_<Vy4I9V(u.\D,F0S90q,H#M(f<$eiJ7% ' DZR)4Ұ:F6/03SPiA 9 B4I?.GFmq` ꤱύ~ܨŕ;g5 D@,гMR j=7H?jl4F%fZ2q}iūSZ-ĵZI+%<_qԞ8$TYߜ(JZ+'m">0J;*P$Ѭ" ʦ]զ7VnnJ+(Jٔ<<>eqؒQ$ItƆ% , ۩:MhnRa*r0l GW ͔+QT)dK栭K~ox%7 B@TSxyKzZ}5J0iLxcO`jbrSfц*0O-вIvlQPʙsV,m-@CP@Iaowq/bzgY?7㼠ےTd{X=ےmYֆ3ؼ(A vrm\8m*Cu\o<.|WՖƣZxds}iIuqBs+\_䚪n@6jseieƖ u6rU>Iؒ?% :t+iuPhk~أz1W1ha5>#5yh6J JJ]Ca^" M`= +ΚltѲv /qö8 >]xTY\\$֬ơ`Yq1R`qq fp14#PTz:Kezes9w Jbfj CtH&s8y8zH$pz| "z:b~nV#Dw$A?g* ^mŝ2!vx?36XԙQtzFC0é*z!,#DDiK)HeL֙ qv|'똏Ǔp]r6.l%Qo-sώ.z.Z^\t}2}ɲ*bVO!~z}Ouw}B?#T+d``IJF3 }hbBQMQ1LE ~ZC} p h?ܒ`hƉ綨RZ?ͣcʊ-%APeCPsR4,:^ѦDXJ)w;A&"ЈbYwZ035bUr>JL&LMN` h@sKA8v]==him N?!B.D"d*Ņy:Vfoi<`@+EmgmK BF}\(Ɨʃ8NzX( 2iVŒ k @ˢ(- 6)#89WFR ###Gb>Ykyv5.9HvLy@7p9>;:n On+N 4qYxר|y貃ONט=x /h6!Jj;!JNP? ~,]WlBvNK4D6|bh B@ d'kZ5|Hi!\@~qG^s9ēJ%t_g?e188Yar|={033OGOOrrfk5 851>}|@LvVK5q#rU5q<<(hqm8ӳC2BcsXXcjv_ cxش#3/1ێDBcز~GNN7,9qVeeV^]]Mo36#`c d&BBֈEx4!gx#@?cCnveUU{b8'"N9'~hm"sO`bz?LFK_TCf\5^T*5L.S#^2 VPۘ_^@o;^ :ۚT.ookͩiN5^OOq5ܻwMMMyJ%O//сÇc>S#Iw24?.Eiqui,-IޞڹNk+T~SNϵ9 (t[Z`viߜ;:V6q)/m\*DW+f1=p Ͽ|jOiG>˗n\*hvӋe7N6wHr9t6ڃ_"x佹U+'_#|]ģ׾}.sGQ.qַvݎݽ &gqjlx{݆r+8~jlnu0@nLNmmp '(kǻQ>c'@+)!&rpS`ё6jL&:` E왂: m8<@НE'pՉ6d[ 8$v@Fd02:f?zd?8V {G5~`hHqtvu9f#a}#m; E@+F1Z| gBB< Uy`OJ< $%ICo p/@7 ~?r ~x_WpUĉX__s=~X__ǝ;wСCN=lF>0҉^ŕ$Zi) ʹzSU}| R%tfbz \Wo͠ZXʭ!EGs&aBf06o+}~m-%Kylf1=fB L[D_w+ͭ`d7ae}=~{[{mG1@O;;P,䱸-y vaz~ \C=E[K[;5^t5g!a]jIk]y h '`<"Xb٨8!hiȔJ2:9EBw#Z?L '*pܐ}AZpo_Q}>G(rR'56@5_ Fo֕;ĈڽX iIb@/DKv>KRqr'WL&gyqi}Css3_w8wp<䓘"N8}sr ?q ZvpO4[q~:]89v~撑lU\DD-VX 0im.XN׶݊ͭ= wao[hk)abz C}ۯƝYtw!%v1ׁl&wfތ׮L"ɠehA6 Obuce0׉fqhomND.9arfG )e3(n: <$$:[PGuI?x.VVU5 ۊ&KC@ςBhk}P f`^$*75/945cUD4rkkk(45#_*J: X͐Gt|dA!ב3J|wA>&W"|A^P΢T8IȲV{ US10χݏhkYerL^GZARARAeoÃC8zt,VO~$}I#N&m+Ϛq%}M.Aۤ&iieJ aJ# *G'WfPnjGNd]_έ`k{-T.H[ιP10sj@WP4YhLՑLQejyOvYnbuc ѴCkDA}ہvL#8hT+xW1>>l&ZZpqԪU'XzBgN1a_@jBT\AO!uv+B{8T~1Lb_B0p iUBδ,L$b%r{N֞HE0OelF';Km 9}JW.GKi%I^9u#8|D%X_s>\ Of$q9|r%; qxL;,Hxm6cl?2FmhUvN<,:+h*ggl>xDܕ"6)m5_bE0RhKr(#|e_}Yhو]C t#?Pk2< s{bk.ߩA9 S-,\fЉEQk-!#)`0G`ϜŧK2 ї0 s$hA_"s.";8pcMZ#r 8;8>|t,qI( XR/-248qvNI>9ȼD>Iu܏\2'txFЀ 4$y\z$}X.Cflnl䙳79N浫B^o`ooއEܻ|\>=,-\oP(䙳rFpai=bl`D5or"}t>QZiN񠟜oA6|Y]hd|]l&ظ񲺺wPT;}xGrWz)Tk )4U.$OuKU9O$0V8:@ۻe͸v{aimMh*.^ W1ζf TBS *TMNKx ` -+#Fl/!`IzZ(M+< sA5Ԓ΃ (M; 6T"W.6ъ0%`c (Eq^Kݷ~4}3a/[ BhpXO3% q{<󆉔SOT̑15_VQ,!2#bwg'Ϝ4:OD&cǃm4ߏ>jٵZ 'NP(&dֶׇV4 ܝLC.CVCk[2AJ `EC_;4}eNl9t ϸ>ItQrd-~$y[ /|=X\\ݻSܾ}ַ} ;#<\T*T.~D6UG98Ut@76.f7[fZ[%[#o..l×Tkuܜ^2uܘǽ<lw^JW~0_ݽ ^x&cu}~*׶ +cdQXaZGfB*,Hpe<*aY2#CpFdך:2F~B xȰOomW;.`qWЎ`-˟yp,я~426N.yrm?dq%H/?166dYV'>B6 t8~KxH"sH}ʥK8y:N3 v8pѥG\V\.G08uK-%0w LϯZ߹;bmc#͗\2 soKx(_Tmedα !Ue?[2: K_~T|D@KXu?lhrz> ֵ m`dfcq効l<%OZ8x.)P'tQaS𖹖!=^+I ZQ]D/& c- :B!q߯?K2r6vXDP@WV?OzՍZjֶ6tvua] X,ϫ% [J -9S6$n߾[nV??__'> |_ǓO>)|@Jgn IDATyd>:KzTOWp__?M[[[cotuuԩe\x}}8vl=O=ŁNݣK>q,|.}<|s\>iѾ.P'҉ƞLNgTf22[8׮Lb}mxǏ^KTke3xcYBOG+2zç},mbz~ $ʳt_U/c u _C0$$9 QH[IX|U x &|fȢH_饸3> ls}x6_}`rbx(PiD@?E *R{sC6A"dr*99Cq? H } SWGg::;h耔=h`@BR'rNecĤk=c=JPc!c!Y=lJC]@{hIKŨڧPpu._  <'y9sF|/X;,k܉\]`T9V={V_ٳ}NٸL;;xWỴ#cGp]|˥ pG%GqڧG|߷aɧlcGQ԰ gFP,1ێp?xnܙC.ءt7au}N+80}8d \(4=#^ %_ T*ajj g#`~~CCChiiΝ;j{ ?<~'~bxq ,..mmmNۋ}C+LLLx`P[e+W~@(\'$6.+ۘ}1VWM-76U[o_L8=yXsi d \| hNKեmKԉ ŕMܚZ y\vW_|T;sY yZK91v yh2)3;?<$RJꤎ-1CFHaœ+9]녣Ysi(5U5K myO5E!$:F ܟC49hs3r}} r3rŢ-0'JxjX'H J q4Rl[X\[30AîZF(8: O(yiAF">iڥ$@}:~CI %韔U;6q#yѥP(/84z['ϏBIҬ$*MȊpS~h`+tg'I2k&opqzyAJt76\.bjV@yjyOu[tOH0 oXyQ l "wʃN kU8vlmUkVX N?jB,-ZH;dD"AȔТ_ !4ЮYܮPP: RMmE&V+(i`5{'c@T;vRB1KfP[T5/&`qnWݎ l Ͽ#.ht> \~n+ .KHe袕P?5СU$5OI`UJNf ob- WFq+U|cG9nQv|+ "*05P<hͩmxPdP1WvZ6LC醓:b -/SB)k4;;D>r]@VΔk"xr08#Y`;j"Q`qnYIѢOٚ$iڻ: eJlꦣ)č w}_#!AS>@*uI'K.+4%3N4<\$<(xPi#ZUÓchd'Z\69:`Hۙ!sSV*ŗϿP_҉H'B4F >CsPfcq"bʰ_AE|(svfp*Z+tm ;OhNO/1G){0Y!m0ZTyCɳsI tZYYF.GWOgg׋F}tvvBd2X[YASK <676Ԅ|{SV*j(77V\.X*aoobJE\+shm oE.8CiЕݤ-/I VL&D-d̊/JB( (B>’E *Bd?!:X+mB]$q`ip$dI:_d_[{{{Z>^f)H|공O$i}~Of>=|c"@D/H@(?<IZkA誾D}ɷ塭(h D$]aS"*ٴS$-g<~[qUEA mW4$` GBR6-B8bM‡ .ݦRT&pZ)UGdY% L"`g{++XC^ݻ݉;&ƎGGW:ggŅ`rN8v {{ŭ7SX[]ݩI,/-Zr̒5O"tHț̛>; @snH+R)9M/_;<+~ۉ0/ĞjgہQ5K#J84r驾Ɨ\SI+Nʹş2@-!T?\lb8iKpx.OZMj.e&7.jX.'ִ̚vݤKNIPckg8 Hb=dB=\IR8ja9 Y!k{x؂kDc+#5=R%GY'UAN Y頌 vIv*ˢOc¶ag2B`:H)Q?ZщBO(P($Zۇ{w12vY<khmkC ;`F$^#% ihf7N!!㻕H_zja2yB"Z8iEd'ށ =`ccxΠh`xPSu& I/1W8 1^ 1ZJxu Mt.Ƞ\ʣ^o`k{7'1ێL/*vqonWq|絛( a|絛hk)ܘCRp'ޜGCJܦ#4 ݄>Tq2>q6Ir*bHI1U2s):W%0Kfr7 T+Q\$;)T\EERATB6QT*(d|>l.CZatvvARZl6Rr|l6300\U ĝ<%2K p}<\}}4]c9?̓9vxb*e3(zCb{wKXZ:+UͣT#ɠh`qu]\ w#ˢŕ ,!^XCOg+:\.TW@_w^:{xC'1(䳡 =)Z!bqL)aUs р+vdL,,\]|v` WwUV@AKs.K̺|k+Fp\n$FX+Ru@vdA :\ /ӣBBXnj}3ȠY+$S~妦m]$ KT, `yybaa/|yK_" rLM&lQ/i+]!#kI/)F%* (M}OĝhM,kIt@MzMk4z@-$֦i4<Xi, cqeWf_awflRr 5f&f0!rL)ޭ E,m!ˢ- 9Tulnakg̗ث0Z-V߫8utgc5K͞^hLjDHQ '.aDqѝ؅uráYfe+A1F%˱ rVGItUp0b J ؋#B ?ЪVCu%>~{Ӎa~s7;̡\fRVɕO?{PliK!~?'K% >3xOB6B @6$r<?Οs\4Q,AmBPu飅f56To~]]]8xqa]# "ٳ;|cK<r\mvsǻl:L;ߥG=\2.}|z0NbΟD05g!_I+/:{\ fU P\>~a UU B]doV,tMOS&.i__?N<=K>??믿~}{cccxquq/Pɔ ]ŧDpy+FAR'&օhYOЯ܄ *)q>`z(V #1Da>D0VYL%͗1g4qgt3.̓%rBw8R<\|gB+WHƕ"j|/H)tbl\QHO%|~P/{6׊qhCQ:X,l݇nlj^$X)cwP塪u g>/}K'?W_}5“KN޾4>J|V'{j\:)yv}w ׃~O jץBJ`9L=URkKvA15/*B`)i"#q&א戎/&] 4'JI&776qwrLmm>,`~v}>O\.=yxGq o>ۋ>KW) X |Lõ}rp|q$'*g*FMm?@WYlsW%OX B}8CnE7q,ES([Ip,]sl&Ɋ>Ԅ\JIqk;<)].D ;FZ'(/ɔ\ IDATTI&K9kˈ Fs,vW;as0|dԉXFq5agg+82v+vqxt+X\G_{zI A..O_D$g zFgt,8]dMR Rt$4ӌaTF¶K'|R__pA{L xȝ?s?(h*iV|' i6@.@ 粕g+ HHܛ[ťVCX[ߙɱ~]w] ֢J{REEJjX HX]+ |)*®EK\5k&$9_r׼|ҧ+49-owwK/yX#K!gʬB", W'4^x5x}LJTB1^tXXcs{`#Cݘ^Bks ̢Ͼx'/`]F t,,ouݛ ֆ0R=8(q2H`G(C3HRk# 9/x~Pr3j x Q[r\AK2^EDY#H۽K(%R ~fɭvu?pqlϚ x)yH- tK X^\@Ooq = ͡@46[B7P;'TP@PJ*ZJk9Ҟ3? $2֎ws4'Wɫ6:q<|/mtoL&"XR_?թo{R{'}J6jKsIKړ=5` =yB pwR@O;W«7q.OR1q cz5Cz{Ŀ} Je38=>6|=LL/ސVX\.÷^FύKw{v`'|@e1aL]F(֝z y0%rCs\rRrrdg v(uDNFDOT1r(6X1]0$K4Ԑ"R7nwсݝ)T*֍8|drr<67ֱV'(pɥ@y&^d/{!6no76}B%]&qP9\-1ch wMjO[mL:K >U)75: i;/=uet 9zp?MI ֶ_8ǛK<>? 11+p.cS3h4_~}m7Z `z~|Y hm. v@'<^<&\ţ«7𓏟*n]D!E.P,T.hRXES54c]@\M YƤ>Y^reF/m5AE:!nqh0hA۶|gQ! 68] U r1SJznX-\xͿGJ/ F"N DP.nsaÇ04|-RmrS OңoKBUh+{tKt9=7h[w>>.y;b#DnCT@oW+fRF[Kw-\@OG ;SGQ*q,v+e38>ҏ"f1:܃f\3ǓcTjh n v`Rŵ۳kGF-cd+UAd !MssXTjg IJy1KC L4XΖŊ֟]s2+ ]m)wI@'?_z5 8>:2GB=ytАP*?5Z} y 财 @&*Hj'8I5RNhTOO=Zxx%LjW߅І2L. }&F0KedW"A%^h3*äZ^4ժ6xtOypM];X#U>:Q7.ٸƍ;PSJ4%@~Z?<!W '7lmn7oo>SV4W 4ՕUܝ;Ï#ϣ׮]EGg'74.]zRbogΜ kMJ x.آ%Iv]W)⋉.q sNc+*뤡-]8ɇ,@C:Xݮ_܀D0xMNQ!tS IW}+ D-%%[-dG.8|_uĝ, 3GF@ߐ4%=XP Jb绖TtWbq6U\;D{ils;wjJq zvw7/addA;~Jng>/bggxFRA_Ƈ?·1?79%|PV+&u58{>]_NӁ($?"q* ʏ"F"AYm4ÜS@eݼCg6Q±Q6,+RdBB#aMH+)j7[BŒn"BB>%<3\"h= K_*^u@!o}gH "$ fBLL%3 .%"˙=G=!"pu0n6&~R^  _#mnFm>p A" $zӗF";X~䢓wb\'Z#)lmh޼qwn/]/};8Tq<X__G>}/fֆ.bl(pdtl==cǑ;8w|( ҿp\H}}9,K9a1N@Hw"EA]& "'3YV$5&ʐ M4B!HKuMeP0E2Vadehy ж3(;M:-%lЏ7+IJAU:a؊2%&N84઱:; H@"PhY\qØd NX0fq!}bP BA›5U `% wY$> )o]Gia pγ` j`v*k*";͛]H)|O?/_W1<>~ ~ood'(T_שLѷ&x.>C>w;k{a4m8{}Br8} .8~$fgbmu ?8C=d601mQ[}ژs4̵F]qyWҜtqK0B:˰c.V$jC[HWvӴ@$P^KV*d稍 -ur2-1$Z r Oʎ R+xX5<TkiZB,ZaC&KO`cpA]Y-!Sɾ[88Sm Y􊷧\dB,.czj B]>'JH [U?upאh40~J岦5q9UiZK 5럦p;غr z#Y(zK'/9iܼyCa||/_8FGGՅ7xo{p%ŋoo뷕"Ƥ/J5 <2cǂ@8?ﶂSyTT䩓j bDNT4t%7K.`py\+]|.ɟoqz֭42:vJLiVR6|u\FbSR,_ -19P~c@$=4 H"@RPZLlq+Cs"cY|A"AO4`-b\O=P-=R J]SP !8wT),!Q>zbIZGH;?Xo1477cmMw{{#x{/CX[Zƺ~2N>E|x[3bc}ds8u>LM!52 Q,-w''Ppd(n߼Zή.9&Y$C:)'ASE O8t\|;z!w~{Fu<#8q^z%oE={=* N>#oe+aƁv$@Fi >q BQW b;%&Z!|K%4csgoc4}U_"o6 >~IJ`x"Ƌoiz,Kq<ΝT!DP(KV*BSK3f07;ֶ6}0?FѣXũ3geֽ]\rlmmaiaQTױ8?sDWw:vwMlnP,V+RqU|ӟF@ww7.\ɋ/#~7 ~SܼOR5.^wjA$Fp]uғ_88`A8J+F4ǁ}+Itx{qtJAR]6W4|R{ܜGV:7c#}^5>*A 2D|MؾmTAr**pE@jRE},jw 5γNAf!Ýu#G!{SdZ $q~:;>4;qh%lomX(booLr[[[2e:{#b{k BA`@P*3,T.L޹tViUpsбRv={_Wt?A!sYB/"g~g,ڼĝ:P 1:N4uy}>ζyh"&ywK-pZOc}t|c>Ul0io{nl w .9M<|nwg]qJJ(oLRFI~&VtȶUhT0KOJNM:u 'e$}yTPaCܱRga~#"pn%Sް۶ h;I)M_Z  ( Dwf>:vZ[` `X[1uܝ&=ndYE cyq`š#Zqjq<|7qrRT״Ʃq1d8<.?q}I,mQo.\~sM]!FKMj_*gIl\r!m2L&h1BBK5YPtҲyPtDf52D#6zbt@Syq,@H y,2̑'T5J0G:AL8RN]UF`}kDSsFɛ{z)% <8mhm nvu2 FFGLRNJMH@GW/#cG!@WO7~L[{-: +X *1W;lAӋ,t͎җ\K@V(J>NuhHJZ>yN.,j}&8@A7[qsr[%m|8$]rŁMqWgrD~ꏕmm*6woN^mA&i?ه?_.Z*J<*~#MH(3B@ӡ ʜI݇B+;êRA<{eF5J+uJX` EsY~YR6?e#,`LKK" 45-)#Iiͥ0C'1m`cG_a\-H^8Y4h1&ZH }wDzW x%sCXnkDǣ'7_2CSSqmhFiluA[[[ohr%%4`ƗWM](kJrzeCT\w@+Hǁz^\E%W>(]JKyaG%xeGv#<  'Ft@ z@6<- 9 LaP}3ЇJ@3Yo's 0ٖs'[vpdiF+maM2 A!!'r `5 IDAT3pH*sCi9PʻO. SwD﮺%}•FHKGE7(DԪjj́ mBiV'`T*U\t U(>۩.0eqmtsG\{\K~.ZIG<.GEp}lC%MGxɔKiO8$ &,F^,@, ĥ%4k KY!gIP`&.!sPNmy {u"[Vvb&J: dH 7Y~>c*xA"4-`m[|" @1RA$}/s]bqKhLHaRr% 2M:hBC EF J&mG(p4M" -bٝLQ/_̪ Pv~U]eħ =m MR3)pDS$ih%덺ԾICvZhIur~f!aeWՒ63PLX+Ʒ0zR|2ѓ:%@Tؕ:YA5Qh`*Y4HEJ!뛛QA]b+zzpLږSǃ< _e&%z_pcH%]UL!oSNׁЄHs&y|9Z]%lR9|6L%Cm<^ӰY1r\ة.D:锏R2uEFX+`ZX;<<9^r9L:Cq9ݫa&`1t&!7 =EYdō@NX;;An뱉Z⭁*fF&̤K_.*TxPm{v@N,>29'P$~)BԁGss?+H~$keHC%oW ppC")xg ^)4Rv2kM ,O[P>h/Bk*G T&fOep?J+[6 ȦHuXcbFgrPW~{O' !á}k(=[Ta=g݃et1  !R Xwv&xz8P&Vv{x(ps4\Rg?6P-+VKn''k(nnu1ݬ`Y=IеpFozSᦔ[Ieyz(E <2DsZD' 2+ymCp2|$]Ktݏ8` %W ԅt1/ndV3nDg3dpKvI߯1qHibE'6lݣ$C\ 8~xR95 >U  ?jF I4il 愖&yeA9BxH%78I8ʴ>tҜXqv%וmώAA4O}w'|帕OW:as/G>L;} Qm3 yRa7,&uNLڃ\ 6<:+ 4kexumgg(+.˷vy@Gkx.zhO]mT'+-|.NO5v6|Ioi7# |Hf2NTFT xMGpMYO1jɟ"[ db@ &&%8P zr%@!s"Ō 6qxy̟|ӻc๗o@k.DZYvpki>7aaf//V`njyGG#+y~8քd߃!`IlGPĿ$mxht[9k+HR)Bk׾p\1ώ"m$ycOiS8@~u4 ]+YG1@ xN_kKKo7W^;x Y0Y}%2|I6hccB]qQ#9 >O=-;O6ŵGϕ%āI[d~@{.m>%WZ,fPܭLC@5Ht< 3CdyTJ oZpV%0Q/C84VõM֜ iY㻎xW78:QVwCcU"a}t/߶Mznq_B!NMvXQK> o+m&ۄ,,~KTBg֌‡FF<pI4N)rlǀ[h7&TC6tE١xv+CFd-C9CZdq?ǂMGso0:RGi}s}orlvq<.^[<,or qyxŸ؆}QP ;>4~EIH$Ump6XnJ$+(r:JXt1N%Y 17 wwAi^bvC::?]qp@_g. (7;(|Hj@+>>ujIa;Pk>C~tCstqj::|J #:VnskCfԶ.O5 2&Bdy$҂ȘƁ eb|,ȷ$(c-Vz.|\ oKO*y;}y6KHՇj$.m ~94(N+_NƝ5x)]wo~#V8}l%lv=O>ve|\Md -c>/L1==ȣ36J~^땂RSyN)LδU?m7n&"n)ȅJL9<`"JX5NjEzՊt[sA=&77^g]J^0v焎9 b1w)Z{} ;2PRj F'M>osoԹglYP;xF6ҳ}V5VtP>4v1ÏHssT*AH |bI?oWH~f? m(!"'o-ď딷3-ZEEk'/DdbtOl)R|{ 7'q>Ьװs_9o>+7m]ZASϼxO?64޲`yX\Cن+s R !z؞{XKȁ9Dn}Ozi#YD<\~cq 0'\rpO;(! _eaK`[ݙ\[RY,&:n7*Pttמ>nd(c/F}xAd:#SW 9pZ;C  6&L%m8<1AE4c%zz.{mMy 7O]/BH~w+cIFlH,14vc@dKҿye2NC۴^}`||3t7 K/={Rd@RLz](hn?>)1ŀtB@Gl9HD dhы*fL:/SϋxZ_}>I6Yg*T R%_|{{lRe&5u`r /ڀ?)s@'#$*n"&?w6!۾u"2tW@Yf dVWmN1 P]8ƇeH|=d(e@_b/_E,&9]Hg~=IG&36g6*OI߼qiMh8:`D&mKSITZ\z+^"czN(TNv`vR}l懕 Ubee% t(#j+,?$9GCPZ$Q$V'</R@%!?>HפQ\@t '͹m%|糔c*Tv^mniN6R?JH/nC%:i<)o|K64/ S4AM>s@i1ܿ)S0og^) y=|ykh_%QG'4-)BQHnA&|myҬSl:FjYYtl.L 6> ffgq ܸy++8y4N:E%7 )1Dk\j$ނ v;0d-_r3vBW`tl /^ ZX_[űrz.TIa"vvqy lnC)#Gq%LL@۷p8gp-mLLNbvnGʊvXa/oJ[^ ns3O5mdŬx@q۳):*tPHB\%-Lk8Hm$~=W1yEkH>J?b iz>T: .3'fԷEOCvH;ֿWcs̋ ip򄗲ҩd&"R; |HxmaL`]0/^ՠ KfBDkϬXp?^}Fa,XW&W<[ 0H)\9#cX1R.nݸJVvŕ+Wqa[y^&I*eqZ T "Mik?R ؝^jݽ 28[ׯC j0;?5*J2vlnc9loc0`{k 3@&U +!DѩV\Fn( | =kjGh-}`)OHR)=OC!1h%x}ptYWS]tgܟgLO%sS/+~dt.Sπ]zims&A'~ TN鮽eA(s;igb3^3oqDCL@m\eݸ;TyV}c2?C*FeX|R9Z^ k1rܕb\_҆2T鹡 X[[&67666U +BL5NJd$sc@8dZQd%6:>h01涙 ^2;+^bN J&K hQ}eC!|)8jͯ1L'uW2Xv(w=+imŅ-xF ڴ1^]XxHsM^ge%Z4|LtsݴWԱ|@R~`ŧS۟ FVÁiz=LLN\*cbrW^O`FFGQ*Qo40$oj0p#r Tr\V8055e;FHU0R_dxЦ~ su2@ N@3$~Sh!/嵧ONRu095m-8v$KڜB:fIip7y(>#@[+[2MʢB89kI+ox ^ky;i_&*Sؒl A'>)} ,Hvl$><@)ٙWB2y:t|n6.E s4/<## Wx'Sq]]Ka0'QqM gb05Ξ>~WÏW'q{i n6I,n%o{ヸzy IDAT6ØŢ*b7ƀWXmp݂N%m=P]0w3-XfǛ,~d`YyeseU~MmcY3&w )C(139 R;!q@0ȹ"<;N@qҧ+n*3ND F%S(b4J7kwNDuӓ҃M ҿh "  o/~ӓ8011Ҭÿ~Yuܹtz/E/o8vx{;&wy7ASz5c6@Ӏand+iMm| ";}o /=hlsMz#lzbf%+%o&^]jcGiY bu֎O`eI_=$?*@tz`e_OriojghI7\_ʭRupvy/[0v;c2VpÜwlxIV@! b!o:%XD/gHJϋH4M"k΃xc،?;w_Wo};73QT+6g_8.鯼[Kygs]Oᱳ'07;KkOm\ME` #oG2(er4J.o-c>"/5gl#_|ĚaF EY@OKuB ȱ(qH6JvR1<rALRt'&X2e$" GBgLWƐ|)HJiB`L #%@˒Kyvd@oL.o#'>C2%!;c6Z_0|eC 闄@~ &F0)&wg^9|k+8wũ  =3/O*`nzF Z#/G?8~x_y-`YǗv Zz ;,sLB>0jm,Bvb>(`?~_ Ӎ%;hL`gHH&SŨmN%M,\ȭ9izl2aH34{< c& )P" dw"^'4a424?7\_]O΀vV4q1bDO L]1¹_BVs2}t:<āC5ZVyͤM0hO.u$ãU_1zy0 a +Kp5juFE4d"ځtt0>>j3v`s/hblb1Vt6ggqpwK (w06>6ffmp2Z.pdu8@nY Ζ,#"M&Z7|ϭl2so$$P"I4[(pŀq? ?')dG dHc#k^~"47ܷF{{+@c]$i8+v}@Ŵk 2 Q# \!;C4R%Ka(72~ T+6,w?Wz0Y9KJLVedeDZcYMPTa@Q:ʏ;-G <1 bJPjr8C;q#c~ j:5^9"zQ9\X7G[-\:zjqՋ8裨Vku::Qlnl`0҅8q4^~ Tk5`bd4cmuz{pҫ8rݽ LM`0czv{]t:{7~* N?0>1C`Cd4IQDRBc&PNKQ_b<8z^HhB6-)j_Q;$C><_|&g'-9(YH':KJݮO*SO;bʶOehm u4#x11;7#+Cg1=3k'O9v q`j;;ۘܜm{gÏ`g{+c{ bh<ގ@+\7-!PG u OVX2t$!;dK41BJޘ<=cv5h~)OH7>.YGĭiV+XZKi0XdJX;^sFCy`K+_R)2`p>Ѷ}JaC  5NhR--7ʹ7~J;'10ƇWqد\ ڦtqŊY]0/c@ WLIiR[>d 9S'yc'OCgl6nU@ڻ:PN9`]m?ʥ2!\Z&Za}m͓yҫ(+swgux AKǒM Y4)Xp)R%Pvy9<@@x!C<4%}%r S,~I[WLr;\IzI6F+wq]_ʤ'= GΈv2\L쥷 1ͅ* uPyLSJ_ 2(4Qg \(ߖ`7~wz}ap,pцv ßyd*C|8kzEƹwP s"C~V" ^PcoB /c'G0>1R񝙝CZv8xFFp%54Mʥ8XX\D9uD^|g|zu{McET5u,,.b|bW/J5|Hiȱhmn1y` o T[굛p«e_X$}jB*]^ z)iK $/rLesQ_/JeHB":VSP}v^c d'ߊ!Ɋ]n ݽ9Wm ,GO`mss|ck CSx J%s0>ZGQť075ѱ&>881<5\^)|[no;kQUwsx!Clnbvj rn [0yIDt}6ˋ)=&W(e2 /鎘}K|߃ ꭈ sw|8+n 52ܡɱL_FCqkI̓)&)f@'u3 m +/ %vQ)򜎉S ߱qIb'(RQ ӵ' fG21vxd9f|*S`2cvpmEJ*F"|bIVS 5/I`` wqhJ>ɼ_P*]dR_t6RH TbK7T֐NRs"4U+m11@I)L516|u8XZma|{-촻#Vʨתh^Ӌj+EVETxΣ?ULR.TR8vh/^7?rj%94)Zg~;H8(1&H:1&E|#:2a/7Vo9Hc(`x:n\W&gpGXȂ-]f5Łq0))V\beڽ^ phW(R c4|K6c:F&]HNȊɞ:P%y]W1P\D_J: U,*;fȂ@O6qhZ rk3[88[m PRʶX&!.L⵻mbaf7a(&Fp\{]lnbgGfѨid>V>0me3ѓ\絒f 1BGF>;,UI;f qhRΌ1X,_WLJ'fx&\a8_ޠz*)E)A1O@Ȉ$V"<b7pg/[V**FԚmW7Kbz=Ƌf1":L- Vqf% %J6E@<>Wy;1C %{&YtZd89ncY6khu.WLA^N٫;{i$<>NN˨Tjh5,$:>W$UII&k̒gou4' T*79ND1Gv, M^ʾ f SwҜ7; DQʃ`+ Ws+ ||yH2 c EՈ2I~VI~9RIz: (Zk PVR%(PWJyILZK8D(PKb̮Mhg$ 󍁲`6.iW){#oc1q,qV[?9ZҦeav61'8#Qv YOF׋ |SQ 3|2?_0U̷ۚ)0B3kfOP>/&S%@*CډÖ_NLX錃; 15iC C"v(Z6ʕS1LTl]^4٭(7T_t$$7/q$PI; cpP$@)@ Y!}%B|$91p'%[@19R ?C`8CqWV~%$!'9E[8$`1iK@Ɔ" } 2AeQ ={H$ dJ)HڸH2Ή3HOYe|(!tp@8>մBl$aeh0$xHAyF32:B/||kF{NFX._X*EWpU-vϐ 済?vfBmC만|`Ihbn|:S,y\[k5}@InH/TJҹ-!ʻv Ԥ=O_O9H;)f6h(4F eJ[`%)u78B%Qٯ3N'`Fz^R&2ad ``\x? ?$Cߛ)ZCGfIv\<$)X_!YEdvb i5Z~v҇TM 0TԎИtEKLX/Cz+WN b{*$C斧۩q;C3gkHN>2!nM]2v$i"vB57c /( Ku7N:4 @챱>F%{i`@Z#>V اv-yf5p`uXQz/X[qt07;ݏ+=_/?ݏoq-oç?i||&~~?V O~ j ??,~cϵ CPz=! @W(s:i@!f4qBq6%|%m!S`{팎1][LKNN b z3;BJF m^c5vqT3VUt0tx3~~_3?3p~mo_|p-|} ͮ IDATsO}Oqqx0~5|} +++?8ć]%Y%衿Ud!t@RV1 I2~@t>yzǒn@l8( c"%'&{?2lnoF-ׄ^#/hPH|&K>pˋՁ߯EE}!En|kNV>`5a'4^,Gnzi4*z5;[/үdE&mp6Jh4˿Ocnџw^H^xƓ|???O?i'?}-; 5FuY=?PBZgǗ]d&o!γN[̎__hC;CEgLƾc"c-$Ҹ+KT)nt}\rcuL5Ƴ…p8%K8|p _Q(≇^K+8=1a촻xk(Jl&1;5wסJ nӓxcx-oBYwɋY3woқ6GP a'(gYARAk{k}@"]+X;ҬlELME銖PoToTٯPbd@CtRDc깾y!}*RIl#xA}w7ƳШUQ.ҫPVXCs/_`O>u|{@\B?s\Xow]o}_|NEׇc# L5ÝM\7?rQ.Τ$y߼"`,i &[ @0/YaZ}ED">pb {{Xk2TVa:NMF/@Ԁ\ ^xZ~No|1@kLLNʟɬ j(G  +EA__|PZ׋PnJ=XݎfA[f:bޒP{-kvP" c^=/dGg>B2$scobT,MȧE|H} 4opRUЬp]ۇkn%6w2>sݾܥD(< ז2tL5q={~]#i:)0P'Z;z | $@etB["A-%v5yX xyc0֘6$#3'C2rKJ\0.,72H*ḱ)@`[vxy|{8YLO[(%PuibK!!"ȊHumRS'`:@\U 2scѝo8A3u΃tLq1ϋ7>|.7+U-],kI#_/ BGK{k O!qeǂ'CM ]byAڬWz40&vtavz.z..{mNy4R{>eRp&>og'f ̊0bO^‹K@`'# ֨tJDdL+.=pHǨU.=gR^>$24v81r0(BF #Ӛ L:qx|HLPB_Ȉ׀ #sNف\dNy*qk(Pߓb"B y}MZ',<ߖ"`$c`(0SJ.%s DZ$lɐx'%|DB'o%Atuy}zhIcI'o`|돐gl\`fؤNv\t`$ڻ*xIv4x+;@ @Y:& +2B|b҄tJH6%[H׼⶘R7BOh25o@C)8Ʒ$C맘yc6wqFD (ۖ)}KXӾ1vdctvݳ/z/$'i{5R9X/jxt/7a;pkRf0BɌ2se0I:BN|E(-Ӎ$Y_/Fy$2Le;oc۷ڻ!vwvbom675s/kZcsckkp-t:lnl`scZklomc=loW/6v.n\{KweIױMbc Қpl;Z3XNhI\/d(Ϋ ؊$i.GW$lΟ_ !9@U! OR_!YѶE#!1Y}yu /^\4@pbүr+(~9p̙m)@K3vkc}aIEv89&9to#C`6?|sj% ^3-WY;dyq yʃ-_ "JQ0Zo'n["74@FwnIgB3 #=.DVWuSgan CU4GFPTp-4G\_3g:>>vq1;7{PJa8bezg|v;ۨ똞W/_#jKwhXYRݝ⁳gȱƱ'~&!>t}N{+'zfw+ z` ZQBI>$#D鯷JI\1TWH~ȶP pyvKmhs-4vBds$ޱqP kȖ >6q_%j@㹗]ONm|uԪL5FVzkSK8s|/\Gfq*ƿt6:eOO>`4&10L[{naLI]:Z<P6M&1#Gpo./cue{V >װ{wbss-b|b0=3y4Mz??Is; 1vEsdrS33E(FFF[[*%=05v=ͳ]f=ɹ@l&K+Ó敼OI []J vX$]b(<%}oLW$puA!c!\%PPWƎH%]M c ,-9>RIu<o`}s?x.6x뗱[=O>`OHl޳&R젍!BJOXc΍T&cwFe<_Lk vz|y-YbCP79OPW*g|Ad蔎_+RB'*,IĒAhC_0sL(R=U_7؈%uO@JYj8yR n%fk8y fqq`jׯ^AksCT6@)mTɧVW r[K[áPnjOB&cVS7Ɓߴ8I _l均nPLN.O)qZD?^$0y@ #b;&4'yؑdR=ű6!|Pt:s _AT¡8e ^Z#:̛FUT+eT%,NbhK-kBk=6xQJ>vz8TD?Cd[N0;xaׁF驖Z]{I]yOVSHњOl鷁vv:h4T nj5]Tk5t!6r~RR nj]J%u }+ {rJ% 2m[Q7nQ)QvQ*QVPR Z .*jRPV'LDc;MC8u侓GHM<Z I^ۏL%~1ǿ8,Rb`B?kOOG7Wf.ĥP16FkJ r Z=F {Z^bFG`YGkrkKɍO%q7!;OA9h%z9{9??9'mc83>mf\whnKI"^șTINxRC,%e&9L eTkfeaV" ˇ- %J YYPy"6lPy0%1mkW8QŒOhpHE<}~thC%YlZҗ˖W!m1IvCWUȎd K& []՜ҭ"2hꗞv>ߔg׀|@N3b ɠ S3BИ< Wkh!פ:}$С< TNq3=sbS| C22CF; GC'U i#to&C{ dZ=@~y"GLd|3 .C ^՟I2buE@#O P@fkScйğuyvdy#Pp &G221" 0הԖQu*qRG!ȹɰ4)HƎ7 l+@Aja||toj2vj/bKn#oq=aQ\Q/۔|yƁ뙞K[`0@H\:K%Ԫ5kjQ IDAT+mJw2rC2ɠ>wD|j0113!_ɱNIl4O hhZ880#?b\Jd" 6:; (idan8nmmypG_*]}$$!!@1a;vӻ;;1333=m6c 0  d$ !$tTo>}%dމpFJ_f/_|2l% 捭0!*t$>n`0p̑rLMNRWcG0##** ÁO,ʚ10<<=d%V?%͕ptA`rrQQո\n^~n/nE{o%Zj%&&i22,5:2#55)ny'C᪓ xVyQ+J)I,}:÷`Z\.<.B4d>AR100"22 Zs #DEGll,333dee {zzO?яN* N\\vIOOGjF**+E/&餦ܹˍ7))*Vx( 6PnmmeppJt. G.}WT^'g*ܬ}/r]bbb464=ö623s ܜ:233C83s ň.Kt/YaUTVjժ C{.y{kϼMݽsÁ!@}}=%D Ym?\?*477sU|^/R{B `446,{I^>ϏDt:0tar;E쥽S\h0Ԕ hq>;qʁ=~i2c^oF2==CCC=\)=ddȆ xyuuw \^!!!>, Q ܤ9C1:>)c 1ikIKI%6.֠/!idd^4-_4uHڵkիWȐ[{9 S^؎Febb1ݎdro}v}~㟘ìM%R7?DQ.!y3&~|dE),㬟0;|JgrH7fc E=FTG{{QO&+ !) hDӡR cXh42;;Kll,v=:'u6XJF\.7b@v-[| :?ϙ3gbrr͆k_9p#s.^ǎEOTVVOqq,tz=Jkk+^3ٳ8ؾmgΜfq!|>::3yg)+/'/7Luq222B}}&m6~7---0ks7o~"##9|0/]#GsGgcj5AIMKc=ܻw,ܩ{r 111>| .>stZ-(244DNn.slڴ+&p OJr2{ݻs=;~NOvͪ*==ܹSfƶ64Z-׈HzZ8pQu*Gpǃ=rGLrr2޽{<|9ܾ}G̡e"##s. UYYgǏ} n˗/377nw9Epǎʼg2+PYYjcGr%֯_Ov)[^gg)z=۷mcڵ2~74xa|>]E/O__8 tuuQ{7mbdd}Jq7n+ě000@EE&0WݺwxwԲo>"""@y 7طo? q9q$Q7ºu먭e?!<| f3Ϝ!!!4._A_ Ȼヒ_IMIa``Vy ٻw/]]]ֆmjV#Q^z$;9l߾I'lݺիz*333UU4oJ3q,5U1a1P ]OH0b0AaHo]^~2H3@SۣH24HR_4|Ws.E7+nBΝ;)ܸjjjnVegMVVs^^JK]Eʯk.ydbڵTV^9v"kQPqg'fJJJxI!-5wU j*V100@AAoZVFNNSSS{mm- ǜtG:`}0i15= Ǐghh)V+ܹ{^&J (RV^.7CvVK455qInVU(((@VS@@Jjh(,,Y_gttYrRRRyR'&HKMСܸq#gvRXXHMM,,C[{;8JKKQմߖ?EADD$##7n܌9ތd2aff\.\vz/qmllL HyJx<)))ɓ\|4Zܪɓ'drrF__ׯ_gl߾]uRSS΅r1 hZ\.^&׮aHȋGuYʶ[ٶ};UU7e"J͛9qXt:7nvM 6l`KI o6rijjjY;It$-|_ƍx^ `0xÜSall\4 yyy8v\.V7xBbb"9WS^VFWW:^xFCOO&SMl6nó<ÆdYLyEN'CCC2M Ek)*> º:04yV#jc) BTz*'w??kLx,ǘ'33|'ߗ*//gϞr]ݻGSS="22R;g6, Ϥr0VFFT*K= a\A*רWUYLLN)W|ZK\Z+r}JKr!q\t sQQQw|LS__OUM*++p8!}wq-?~,SbddG?m ynUsv ݥv^GG~xN.^Hgg'ܻw;wt199̙3XVΟAΝ;LNN1BAzk(..+eWAtK[f|xNBg1Qj3zHOOgllLDJIs8tb&::H)LaST>t|ȷ-8@gg'1 3:6FMQrJ4LvFez?.tV .n $Ill,QQQr\p,ҔCR͛j%wҥqV u?:Z"@RfeS\& s|<111xnn3pa4GTt4>۷o333#[$A$ n@Nn%\PS9$T"<ETZ^uٿo֭cb|Z|,^xN}tlWJ^K"fAؽ{7gϞ%{ժ0^0n)t:lݺ~[T3 >yZ?. z=U7dy p. w_ S*I S13=p`w241!H<"l\Dll, ZRR"!(szzzؼyII'?!; eIf`訄N 9(gϝ;6Ɯ*jT:>7:opALW //){e*@FFJ 77o~3;; Ut:^dbbX/[Szmd;A.S^2 kh34{wTv1N_b^f*YeH Vm;E??:tj*&Oqq {;uw/37IY38swNL<1]\z12^zmb5[ˀлjLt~ȴX_~w^{h .z8qjZFIf3:L-%%\xv:saO,YYY?K/8huZ:㡬D4 IIhZlʇO32*8rE"""8z(111|ᇨ5֮]s`ZA:u{=|v2Qy ,դwnXSNeV rqy0ͬɡ|T*.b!))b)(( &&FIh4dZ,Hf +##jkk1DD0ajɄ` '''Odf\T{,FRYV=|HC&|8~<$1ba͚5|g\+FZZk B022ӧyW8sΑCTT[lᣏ>bm:Y@ sG%ommÇLLVKjJ(>|7nIp?s' %l(,fQR\ HHH`֭$&&RXX7j'OX$mNm6IOOիn܈!p{GG#۷o~v N Z;11G((( +++WFΪ?~LnntVKss3)֭]B oBc4"&:bތWȋOX,DEER|c0dz%%%RPsttÔ100;(((ʕlܸIJJbϞ=[477t:eYjz֬YАl...&33ߏ#::i&''IJJɂ%cII1k8t OmmmRPk4E0oZUEԘw[ohwn[5tx^*lsP2eRuDyJhN~шjtwNS4'ʵeTfohRG/ q(d5zw%B,"z+qRp | 1^6H{{;]]]X?@>iP:ܪ&lcpe{NSobcbغuPc9^ZVcccܺuǏX@9lX\DDnެJKK1~:8q"i~||ImݿJEQQ qn曨5jysPS?d86 t:cccAjj*DEE \DDh4F#"L&اtZ-6K.t:f`6(I177(F1 vX,nXz5 a6KADQ$--M h4x^z~/~KY1KMMW_WS/[gC!|/`TGF6q \ Bg&A CZsh;o~ǏRz hcbChj4MNU{[LziyĘb`Mh @B4/Qfa]Km,-!k(! d J!1,bbcAxm6Etq Ra```8ZBBJ%#%0DOjZʊ)FBK__ tX )uxpd똛EyKdd$:Z{ewV j%11aqw<ŎFq{1 B_dB@ Hڒo| Q`^+!RQ Ř/m;$_65)La4b2v]v`3/BD0]rmT0IooMLTEasR<.s<i l^`#x]R& FT?&&4͏R閔 qAP%]i'Hq>Qg9AxS?G̳" Ȟ/%Bp||918( J"%%%`t7g%Lbkn#Kjs}篡Lv;.TFU7)iݦXJHJ^f@N&>X^1K B]4OܕcBE4'pEX.-cx*8º BK .+)R~-Ӧr}[OWw~1 grsy.+JJ/G?AkK 7 P9{: TYTG8.|L +|^@ܭF7.vR9׋O~U*}J_W> x3Q g B./N?p;JS8? ~`-^O6VGٟo?VͥWkx/G=M_#޿ji> Oϰy ,m}~Q >˔߷ O7n/C d/u%eJi[P^Z=\+?] Ch#ʼ2ͣ}~j0´~VJڰ 7c]fzYs$333;<r0ak+2+'.X}yX+rokH‚Wb2_8}xKZ? _Ti]J~Jp WF lc%pZ=mZ r8s΅BK_ ˕6*^ &Sq ܓ_IENDB`liferay_4.ceb6ba1d6142e4ccd05b933809cb5c27.png000066400000000000000000005152501325274564300422540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR2 pHYs+ IDATxyTŵa@E@QF%&y%/4hsT0jUeYٷqޮu(|nWSN:u>'A{[;mmm (@QB( 2D)Q@kiH43ZzKa-G!NAS#,)_V>N^pTM1^.f˶$NXdH' /d_vHW$%mHZYChzX߁pÉFoӦA֥(RMKHʝ,΀W5o3FXVґdKWn,}hصiidtělF K_y OJ&3C}]+za`etT'^ǎwX|) qfo:^/^O*QWhݘXm1H4$(ޠ-LB)Q>BD(X;]CZS> {</JSSh%䤘,{D"9r PXPHnn.^W4-_4_nIrL\f %*Lq–ɡM/˓laL@NWx.OD&VPT'x3AiU!ӡLnh}Is1wC?p[lxe'd )~qx&r u>I$c^۩@&^2My3[ʼnm)㓔T5tvw9{Frb')bxqdeL@~2ha8VYISK Y岎YB!jjj8VYI{$BNv6]t!%%$ө.ڨXe%Pl:uDzzZ.YYY(͢ ACK{PSg,-<U]UE¢" B222,*0NlA3L ?Mk7ӇCz8crώL p:a_'-&ԉTNd7nt O$- GNIk/R?h CLN! y7 r;*IᥲXƏ8yZ:P;I!|pd@Q>'|2ǶMg*qy6DZ4q|vmygЎ#piwDՑśxxۍ/乵 ָ3uZ---;v EaQ464p1nSgzښ) MMm-UUddSPPMW#BıcHII?uuT;易M|srp?ÿ 2Y05t_!.vR3SHȰL\$LvJJxrrsȠGNCSSŤ)F+ẅ́GWI0))Â7 [gy/VwKʜ^+VN=,-IA7)ۂf}uoy@*w<]dSro6zɣ>JFFt||Ijjjׯ_a,^O6laءf|hss^MmD v"WDo|t 6Ix]x#<:[mtmM|D4 }lݎGw[AlmDxFQlhKsc퐷/A$UejDI m< [| _6ތeё( >.B RԩH v؈v[x ֶ6KQwˣSy *@ @sK ݺv%/?OTޔhll$--0_y~<4غfpj#CM%dLǕz?x|chKK  ^\.hin!vl)),\z{߻]F¸gH* )y_aNnV]`$ϧ|_|I,^Iw۷D`[.˯kʝwŭ.Vdʔ?5̼PStR&N려K,ެѼ><xq7qםwmmml߾&***:u*ſs[;Ɍ㔕_?zxWѯ_?0SNwG/S Xϊ=pB̟?s99>޽;?&??/)47|3r\Oޯ( "@ۿ(KlQhD GCV@KAI#hLx٩ңQ/ i#JecEPl /+H yHOD1כ,]bM%qiN'B̤cm("1Z- dLfMhㅙp;gxB_(xD\T1N)j=oт*:3䩘ElZL2k?`Iͩj 2+x’ŽK"bWqv,SlUㅍ8QkqVM g΄$'Jomʿ:(HhQtGqhi )~Չ x<#%sp$B~^0mmm466JNn.,l:|$<>pb̘1dff{nV,_]KFFꓥ;/^m>'55AΡw}R̞={Xv cǎ#';xdWHvV[[˾}xۿ?_|+VP(@ee%֭ݻse˨أ,.Θ1c=z4:us3uPW[ihaÆ մ{?18}t0%%%9͛7efR.XHMM {a\wuٳ5k)ߟ?m۶m6.2XRX/:p G E#VhtcFa bfxXdHaqL-(D&ziUˤNWlvs~ &&eZ a桒[:`H$ޚ՚&EåhsLa-즲9٪&:P5e8=wk v#a֤bL14ة)QI*ְ8ŤydWҦ%6!"+f'j)C9JfCKK1d0h$ʨ!SOgpΞE(nögai*~NP;yF"p8Rx>ҋTVVrOIRimm5, fQ|{{;c5Zi^/|ZA,ڎG GgJECj7!BuJہe|֜ͮcAV//Ai{ڷ(1M?BlݲddrGzv`0d)"^}U{X SN!;;Aa)ܹsYr%agSKhhl_|Pw "07o1nvfΜѣGgݺu/_NuU.kSSSNᢋӧfd̘1dee3{l}ol޼S0i$>x5< 7|[)govtn a[x.?N#vVVwvi&vBŞ xdRRRիsaݺu9윜@bQj;}?䦛ofEۗ x稪bĈҋTUU1ҵ ^GsG~s <ײ-~tH8 Tlm|4o-V76yMR&fڧZ- +aJitz9M~Lŕu:Gs{LV$I#~wr/_p<ɂT^מR ۝ #\ kְl\{ <{?Z tÍ`n@RԸ`0Ȣ٧ üY{ߏ̌j;mtBmܸq46Ŷinj;4˸\fH;,T}ȑ/P-؞֕w=PߔTWQ>Ԃ6*ortK$DDdjYf5H kRDUXhx8v444PXX9e;v,R\'owyW]u;v`9` |Ì55kPRZjعYbC/q R>`/"pwʬ7f?~<%̘>!^/?{gk}vCa>cRRR8t<'K/3lE|٧455qW;3s KP>`ƍ`F_|O=EVV69{6+>\ҥP,^?PTTIJx7:dr9w {d媕|٧?DVViii\tEKطo?%tvn0H~2u [o1c VZ5\ ncǎEqhii . /@EEq;=+Wd5yyyMÇQQQ@Ϟ=ڭ[  2f~hjj{e=zOp%#زe :7LD߲x<^MFJJ JZJhOxC:q\g5X 85xD'-8k54 %L/| A'o6nXOqnPNc,{lXqSXTĊ7f2ƛ̴_A|$ڎ;vuu1j(;<W-}X!#=]Z=ǠV~#u^\\;.ۀd<͛‹/vv뫆(ڵkܩfͤUVL8Fuv\.~m$ "B:ڍڮyӰ«(λ< Goߢd3#QQǽާFG.:5w.3K.M{{;m_~ !b %2e ?ϸ;c2n77xwq;&MK.19rI˥붪?MJѣ?HϞL~cA" ӧhL*x}>D"{+z}L<ZDcvٌ.n>O:R AD]!#^DDirkr;)ATFaV;}FN>,"@r0{=8O@X~A|119 lhj΃5ΑD]}PDZlJ*- ='EL͹^b7Od4{1WF1ɧ9iiA:wƍشiVИA( f5XzzAE˗/'//}{w[b^56nٰa=XAUuu,;N~?o9PwU5'P<S3mt5j~ IDAT=jJR~nvO~_ᣏV1x`/^?fѣG9kz̙3v8>ߺ={һwo/^LYY55\{u(BеkW=¶mx{ YYYsp~tF-F@NEzSli P]Ufh:ubȐ!l޼f }QFn: NJ=(/Ww!s&Na.0fgR׏_I~~.pιENN6wRQ mٲɓL޽ihl +3"yhmme۶m\{ 2U+Wߠ" =4Ç_իԹ3Cag",++{hETWW+pSy(Uh~Wl TƎ[>gq2(BYP]u\<^~ҎȨ0ef!]CdwK%E[ʄ>|}wN;]3Z(Bjj*UUU(z!Җ֧gyeY$pРAN+2az{wk~f͚&L@=d„ tTz͕W^\yŕ`ժUn{ל{;o°aHK ''j7n---DL0RRR0a]v#'' &_@ff+&L ###z^/eee?^w FMIIA': w5Ͼ}1n8EH:Ǖc`%w2F`n#GCaa!G#W^ů-^?no2c ~ hooupӳg/p]cΜg͉R3Q7jNUw㮽:Ä ܹ3L0\K.elܸ2tuC_7xCn.BztWa#ܼh 2!P,Gɦifl+5ocltI=R׹ٕ!'a+!O 茡]afON:)y7i(VY3B1ha|#vACM)oǵ:W6d*r 6و)vl)kF:uX}A760 B;IhLd)񐀱7AT+mŔ_%eWi\֭\'z< V~ztVLnBZZg%\nȈ9ZRRR dSyitxz455ѷJ%/ŰGRUy,Bdfe[S)I&\Ks3't0)]|TVn|fNQ~MfOV+ 3i fX hCSz:g6c0ʑL]^9f&`YaC&V d[O#[ao;i=̊J䴘Dx ,}$!6ANM={6cYHKC3wbK_YB\*rrIOOrDhllNddfi=FOG%o0SUU.] E`K(> ב~oP[SCN:a?+;[r1Urt2T/{ˍ"ԯ'q;S Φ .~8rmm=YYՋlڑ8BA?ïONɦ/i'F K&O;KmK:t/"x wu%%% U< tq" taݚr~prwxO⅍'SId|#c=^j>_ڳ[P:/߾C^Q)]6^ҡRR*pAvw@Rվ3BXws_calA2Fen ϠX3:uBhq4h\}ڈؖm^OdJl9\!rY_a-֫5bK'OAv`IՏ ƙ &ls֜Ss哬џg v{hiifϞ=q/K$vL⦺{܂C=ԥ3<uWACCHM.])^!q.KVvJSSFYdط3v v.$n=Kz}z3ns.}DC~FkkM {9N.dH>OG8ٲ,]%$~2" LI1mp;s29{NvlXia<ѤA[E$ g&&Eڀl $}v>4E4= E3h9jPŎ4vBX˥%4pcV%alGw\Z 78(H# zgPχWhZ[K ;'Q2ÑIbh$?j(EXyGI5v4 X}yKpHh?X2cSn~OUvpw:Q%i-6IjUwp&:d╔8viN XǎN7LmeqdwHgB;6zKPfsiNuN4i)8Exo%9(ʩm>nECK9y::n'#DkH_Giͮ?LY%s钣L6`tN3s\1ڭˬ(Kp;ʑTcer)(v|UH|Rc 3)V/)Bl* CJ<'_m͙N*j0%qz{Ƥ ɇGG yLT|M%3 fJ#[D29Md 5Nר?ENn2+@~vO.ޠβ,7tetm]QDs܆lg 3pN -@(zPŸSElW>0/^Z1gnptҐh(b6iI63Ҡ⣢Io{:liLFDuiHב4o76j7kRLK3Μ_XHn3,}K␷bHcgb՛Եjtpآ`̧RiLٽ(xӈ&Ye8>xTMCDoOXDpc$f:  ttv<6duݝeLtω-/,]@XnO MW'N W U3J>|FWɇj] ~, 1&siiHo)FcnY0Ll䜣qrpUt';Oԛp}:%M> ڕ8E2F#T@@3`8szS#;dGVdERXN';usӧOY<>8xT'If-x_M̐?d9\RRSqҹsgn7)))Ӄ1>aI3s w|,}=&?Vd;iosĉq Ǫ})((;n̙<#|'?hhho|3Cѷ ~ X#(--eذa^6 tLSS}Y~x֭_Ϩ#뮻xyg8tO?4MMM{S"n; |>'/7;CO=JKK w?{/aO~BsSlsjkkt|,]÷nrO7Gqm)̛7>Pk+#.k@0feV U~ZOƞG?: L>?6m:6n2v8|62wy' b,_ѣFqUW`>\ph"yiy9t]veΝsdy<+#Q;0`7t+;v`0H}]OO?a޼w8Zy̌ z-~=3|V}l۶> .O\tjUռ&?ݻG5uM73h UV2[[l - }O~ ؽkUR%;|ׯxO>ѣFJGjj*۶m.]:ˏ~Cٰa#.w^S^^Ύ;8x<0w3ɨ[`ɓټy3g~j5'ҷo_Ϙatу &ƫhkk~555o ۺn'سg3=+V0vX mOu=kkkܹp>yweɓ'o  kn&OL~^MM&>{%55.'8Vu<]޷~ogʹiG"dP\\3g}v !]32~=߄2]3ZJC,>"˗߷?@>}4#ΝHIMQFL!f6c]:bDQ#N!hlloY^ @E~Pb9bnZ8㡡nbum7J IDAT j9 D`eG].G . %ϹKJJ貂x={OJ ?222(//GQztݻ1;}:Dhj.>P(D~A>[lw>6ҪzVٳ' ( صkf͢{۩4Gvv6 [Q/'NV4>a#\~~u475u]9s w٪ it 4p|ip!?k{tk׬Y}=7|c,eu 2^~4n6Ν (R=ԩNn7MMMPPP@^^]"*[**Xh_=;$++r^/}Xe%ncUwC1|EKVvvTF8|-] ôPt,^#/r֕)xhkk y}z~b˴4.R2tnM1,|l у3f:9z^!}GrQWW.r|e!L\֯]X@=|FWɇ*]%uM))DMěq'߫gO|Ӟ K닢(x^؍гgOJ{FA=2 ( TTTЭ[7ڵ DoTa!xӍݛcǎxB%N/0߶_a'B˾}{ BݻFiڭ̥#F7ZmyJYRRBaֲ/ %ٳ#F<Ď;xyq!RRC!UU ikk]p\ҭ*t**ի'[lQ ==}ӥsg6o٢~Ȑ!tML! 2pdefF?Su1m4j '|ʼuݪ Hmm-;)~? ZAqq1#Fiok<ú/aݔbo^uF}}=˖/'o3 #=ښZޤ EE\s\3q"B --ۿQ]]͓O=ń'8.))aɒ :=|>9B~^[:mmx (y5'xb,]& >|۷SUU+455qEEEE|v˦M<_twѳ'GQkJSh_msuJ te/r_%f[ ~,J$껄PdefF_c'¨g|P\\.F^rKcfJƍ9s dgg3~x %2$jo6nÿ~1ΫȠ-^;#nF/˹[3r@}gR__[VV ŗ^"%%+misssq̈ޠyIOK'  W^qoΞͪUp 2>}zKHԑ&k2s,\|ŀBY>oeeكg~s9I25ӧv222DoN^5*C 0v8hvq(unF::t(o6 -$==6]Oi0A̚}B¸qdٲe=]xNoMqy),({tݑ,--eƍqhll`؅xyp\׏}طo/|P C߾}ٵ{7A:w9 x8Ƞ|Ŏ ,YGrs kOcx\~eLt=uukIǏgyѵk7n7G_&\wAA-Q9z(Խ6038 Nk.B~a-eeeѝ JkkHO{{;-- x<Ps\x<ZZ_t\TVVrfi<h% NoF^VT%Z U,]v+tRVVF8&##C!BFF>6Q4^/PEQ} B hii! xO?C{&Pu=p8b3uN2pC8|pv޽;455!D vskk+---nz)OAmjv cux'ikk#555z:fnEQHMMP]]C8Ncc#/ <0B;iii~.QtJEEڦRStԉTi}>sJ MMʹFP(NJJ^&Czz$@ ڛeeeDhll$2UWWLKs3x}>RS)++KnN9|CyR3J*y8te7T`wyFWDp8p@0H޽Dv<{ssN؉O8k䩂vRdÑ^W,:ʜ 77$FFcIM#|ڲ"%+r> fkȎ;!CBP30yVw=duֵ*@FFFЌT`0FNEK7Bwlj6C,UjYVpE!FuVyh 0#GFi˼ `FF,d"8r0uuN thvdN+VĤ]~݇pVNj5R' GֲN/:-?'庤R ~P˿UQu1I<9?w?4F؟ng/?0]V+'&$$` g! RCpPED1@he(r=HžCjE (+T䳐j_PD,ZE#bFn<Na-9*~;v NGaaapEI?Cr9<[?@Ϟ=HgǑ#GmQ20O FC}~V%tp6g/_g LSVG:NYu?~u'~FPs[ >>B=!űZLG:KzhB~Bhe%:|,#bѿCѴן@E HR`#EDBTg[L",0V$ebѬ* /o1k1Fm_thevtٵ{GhK&߅ޖ, z&ds*'gG>!?b=Ie1x6ԃ?%h)Yu֫WƫmTaA!>ܥ- l!L RU"FO >TeD[ud~ [-j n/% /'^Ka$!vؘ.œD0z+z?q\AR MH~)m޺nGӥ?ǒՏU4~z[r[*g|_$jYEGRp +Ev#/\†ia.~nx+ϕ)Tq& J!DnD$d\+& O}a?/.pbk5Xk#'+\9VܰZ$=<褣]'o0BH/xyX>n$I<sD':щNt JEbbroh<"Ih4űwb @ss3 //" f:8߃N#!!&x|*R#.OD@q}^TbCs, E(ETQYH7m#XZuJ(%-ۥ*{4ڒM=N^ Ft:)f;щNtD':I|TVVV2dEZEJJJu5ByIFcpHN%q1͝cNA$jIKKEl]-C #д؛$Za.p+bDVhYR$!Z+uBDžu^V,۠Rrg~1b>9)v Ԩ$F]3p(V#GL ј'1n񋢈J@t u:<!T;ے_Mm-W,~ZAo;n$ rrri7* AB%;*I$"@$DQd]}5D':щNt!~N8AccIITp\JK7`Zq65a0¶ ܜ[=ۏĤ\.ǎb2[R蟤TCE!TT0cSE z$I.sf{5e`хf*TZ퇑 ȺPDiw+BbrStAD%ۖ&I&zjA;|~ l%ڝw#c\N^-E_.7N 75>YFaQ]f:ʨ?;VVGѰs.r2vѰvdFII45:& Qbعh z"d$1HDIKł4\x<`,\tD':щ $ItLRRRx@V#4$f Z}>j5n@1dRHJJBo0p[ЧP>.tNi5~U 6BbMN3TTSYU#GAA!&)"l&99LH!bT*Wːazb-aQ;<5hLR[2"iHiPxHJ4Skwն=ـ*:9SZQ9eSv#PjD[saXdrG(!-sΆ0bZ1ԲÏ(6 cRMaZqxn9B!]WbCFʦ?F{ﻟ۷9ۧԲSZFQV+jd^Ѩ'>5_B%@!QR CtC:S)pvkj/M?eu&{$:eqtʪ8e%5MpTp*ID1;IbD/VE%:Dp^"tp8}8}ě5$iU4aGi7K<ߟ|^?w˙=nv܁J#> (_}s 33KcU0څAC()+!Eyd[%faҫq6w kG#[Ԓ 'E6O0x 6Czz3iaŋ.c,˨pAS$c-GbKEUr sV L1ʪ0v^$#GػbG$P^Z,èVcgn'{&5+ A|7І:\J`I$&&Z/@r!e A6U0dZ|"V^Z%ӪQ *A6 URJ$DZMnnn+T}ݺu1B(3lذF##V'S6`|z}ׯ.GAAљHNN!-- {ѣ;NwZYUgn83cǎh~O[X,TTTj+Oˊ^[P1IB%XMN N汱7ܓ|)j1G:dxjO]鵵5~?=N?$sv4$vY/ Ah5*# A SQ]{_4.Hp6n%󇗗͜jPllȦ-;y|澵2<NZkwV.,@n:{Wxq6(.)fuf1glz>|UVq!_|n7t6 }:+I~!կx7s~<裬_La;o2vox S6mb۶mTUUsO Qq:vhhhj`4*|a׃㡴r|D3-Eͼ? `ޟѻ^z z!ۯI9rݻ7?Eښo\ir:/NE/7|?=dZ?5Z*TKTX׎_HIɉS?[! {ͽ IDAT}!DSٸ8-\^T*K/~kYy*GEN^T7b61T ZbҵtIqa+/B~k.q73qDs=_+Baaa@[UU:rbMQ\_l)6ʅr~36 F3( ~C|5޽ixЊ"ű/70ˢWT·Q cߪs$'%r'$'0p@4jMpW [V$,iDJGH& *tz8 [# yA-haT%)blСCHDzz:*5k>jү_DQdŊ\v٥tO nȊffPVF>3CQ 0LRr9IMM#99GiI1Mdg紲h@tx^pݤ$IQWWV%##JV\^BaaQpe˖qQfx1k3k|ϴixE|'5YfqUݻG}m…oҵk>Ç`'??]vQ__$Iv0 ~ p8% ɓ'qݤ(ȃ:+*+;v,UUUtMS\| NHbb"]t9qIT* &rrr۝ 11`8pj&*++E1=v0MR[[KVlG!dffb)&xfSZ'[;w0m4>V\/~q5.%ǎ⽡@vvNqal8M8.ma!jjjlFٿ?~) Gt҅v~JKKIHH$!!!"ͦ&n7VAZJdBE|>uuuLؑe$p60<|AF]u6ɁeD9l~ijt3e|otږի(,~}ÌGg߾}T*>ǃBmyQ&e<"j5HJ[ n7~FBp7\+"5$&pdc.Fd AF,qVEq[~GR>! K7sN^ ӃNj$*oC:LYz9p0XRV%.ш? 3uMhj< N,^-׎Kj ZPfQ{.\r K.gM7n-҉q?৬D|>v[=7.7ճrEjm5OD!u4գUٽO23228NfGr4"i5hbkQ/7~13HJ}rh[nwF$?(gTu!Z%`W*ɏ$MFR ZC~Jqq1622ҹ9|0+W񸩪dLz'^shdРr˭l߾222z޽n ÇY>|ٳgc4lL2Acvyaժݻ˅`„ wy7onpSرc+5^Es=$$$0k}J&Nqq1r~Ō{x^|ʳ_|6ݻ3aD^{UFE {ddd_nWNn6m:fm۶qqrssٻw/7ޘfCղ|r6lq 23f 6-[J}=?yd1<_9 p:ӧ6Jƍ_Op ͛ 09z( ??In tb03g6>|GbX, 8kV/wLFׯ]hÆ \tޝoYf_\$Ass3F믿>"kJee%]S__mƅ^*UVGҫW/a?>"H /1=uq1/^O<ɞ={Xn]9r$YYٜ>!hXε^ǕW^ɂ hhhrj2e +W ??+yq%eɒy駉zHOϠL{1^|,I;w0avgŘ&~?SLKe7ű{nn@>p0h 4MD&L+I h=CJEsӧSTTJVfB"tHII'ˡCW_obsoxw:u*x:NY[_[Eewȑؾ R|^&ONW!m{eE` N:F&3o\|v￿ͼ((,d0xnN8A #ILL"+;Ǐ|>Y99$%%c)9qNC/Vp88F!5.NqgxlM|_z G9Xф(I4Q#!휺V|$Nu!poS1{ELf: yo@@fods2RF~hHMT^&Zhr9\$49]נȍ϶q-WlIII MzDIĔz8}rrogȐLz2ÿxbzO|8&o%KpG뮧G۷7\Q?y&.2N('k|<_|1+V,W矣i޽;#G+K,+:t'+222^Ǎ3fhHII=$rSV]466bՓ7 O<$|7Jن ĉh4w}t֍zKYj <ø\.}Yf,MMMT*222?>zn͛dԩ9r_~1cpUW1dPy?2̝;Gݮ];ڵ+w) I}}=iiitMdddP[[{&M^ƥ^Juu5O>$ĢE߿?9WPGnnVɓq̟ k\wu 2z.]_ͨQ;">>^x˵^GNN?ϸ\.6mH~~W&MG*fK,+.g8pwyb[Y;{ơC׿^En̜9qa4cXz>YYY̚52~Va/"fx?UUlذxÇX,,\W5:n={tR֬{QK.a<ojzO|5k>+dȑVywسg]v9Eѷo߈ͤIx衇INNS<ԟ())7.;'NO>,_?|֮VZo6&Lk2yd4iyyy̝;[2vӻwoV\Ɋ+x衇b*ΣGfAL:%33{./]v;2`,^wuw+^'..F\\\Ɉq˴NKfddo/Vpw0n8ju0\اGcbNjhZN[<̛7~fr\pAy=FY.[ }ԄZn#Muu5 gdpPZZBRR2e'KJ\\UTWGSVZBVv6 QYU(8jN?x>x>/qJT*Z44P\'GV 2;$>]2[wdl0Ġ@@TJZMHb.XI#*һ$蒄EdAGZu>2hr(˘KWJt"OlZx?Br-z[ve3V1MT7 6Yŗ%溇8FA|{[3cPk0V!T:=*I%Y\BA4ւ$FCa!-OVOM;L>zAcl,:g(*|pk{Ϳ O\6r8 le*k6lAAWٛ[DK$!^9 ELH+`Z1Lx˞={X ƌjpnWLʖ.'x`|z,^驨@ ##Obh4F܏ԧOT"t:Ӳ7"4,//E|׮JEMM5;^fN0GhZijrƬ$'2fĈt:z/-d>/c|i={ 3oy:t0_j5c"'馛@N|xl&##N%fΜ餦qj…or!nwt~B ɓl%gF% gE1@^^^G^tt֍ں6WX6n)78jGbb@lٲw}{9pf=֭[xQdfXr9 Xje<#O> Jۇ ȟGFf';; Ѩ]x|ddR uN~~~Dgy^(vwv{"†L,bkۉ -zYl_"? x俌޽e9уNn1LcG`i$%%G᭷ޢOuԀVׯJV(.h1LX,dffbۉ'55ͦ^}̜9IEE%͛ZI B~Ǐg׿&..o?Oh4rp7(v99vn݊}|~fx+W2wW}V+hZrrmĂZVݻEL~O݈GSTT|3@nw6Jz?YVNg+rGzOa15.MG$''?>Ųeޅ1cc=Il;WUtL 8lfDWElE?> ߰;7^tz=_0j5&II>RXxDŌV `1׎+Gf* tkWqøq\waلK&G ZKn$N/.g]ؾ`AJOi [ #=n,](p.@Z1 rs2hˉ &O+ _ՐJ(Qbp& JJ"5-$Ij{_f+7ry$ID{}ڲLS |z@EMENCF'RT8- 7|MUU{d2VY JJJp:ҳgLXnNO>=zO?t*?\¤I3o\&Nc162ټy3N-[!++K9Qƥ^͛;v{n@X`oÚzj+V@BSxy%lrw;}v***(--%##'d%jLII 555ٳ rrrظq#Nyϲ oߦ߱c;eee쳈=0z IDATemaݔ ;w >>ٳ㏓n_]wśo.|N/P__OuuuĊa{Xr3f`Μ1Qqq1MMN{yz)eQvv6۶mtyfǭo,_/dE"HfN'7n$/kٳgضmvkxo[>Q駟f·1㗊Bgl6N~˗/믿VE8 IYM^yU CZ?5kp8X|\0DvEZZv;%\'r_vZn;_~۷GfEZZ<,ݻwcϞ=@ {7o>3f> Ɇ _RTTtZk׮tz*M~Rc!d>RBm09)C(C~ȍo+8&$TxzCQ9:eGxe^o㕻ծ0kD1@?&)yG"1)2|>~_Q:=MF|>/v V$If`6[ybOMuU+Y$$$ەsGIHHMAllM Vc@\EO~B~?GcZpD6Z (]wlqWƨI|AOUa<3ZԤ8%*HFfx=:-Z.KC kG1h1^1;m[uQgdt޴8:E 26]گAHʊcKAho-6O^#.%UII9Bny< .J'%sȐ`{_C[ 'Z}IDmN@ %`l=.A]pȓ RGK}-HIF]DAC҇ ?O."^w(E@#"&MBrb2HLLCdff)'Ǎ^zT N'/檫FqpM71w$Iwʇ=V||iqhf;&E]Ĉ#$۷3{HIIa)@~~>g'ǏGү_? ϟw`64vƎg}ӧSPo~8'O瞣nݺswhx8NƎnݺl z!*>}>ytICd`O y֫=!+TWWq!T*7| ]w78_~ Ő!CQ|W\~H͛xW1Nj瞻IIIwkm(܌VCa2Q;K.ᥗ^nqׯ?_arssyILLd̞=Aɲ|ϟĉ21ikLvVUq63zh^|EONNNO>$ |嗙7o.scǎoQ̪m6ӦCm'կhy6BI]/yyWcZh4L0 dذa̙3{^z)m`Ԩ1c7pǏ[o矣gϞL|)Z;̔)S3g6 Iff&&MBVs-!I`01q_(\|pΝ;:t($:+/3|per@xs۶ۜsN6ۯjUJPƏϋ/n)hʔ̜ #Gl% ߏZ)+IIIf#R9l}hxj3,Z0b@<=-:)hW^W#Xpe- ==2ݳY3 L&&ލZ&;xU.(.f: 5y]9|e'O*+ (rͣee'ew8BJa+3EONN,;IuQn7 DF229z0?ŘkL0ބCJ瀡[.7m99V¤˜㕨ix|n4& :W]~1Mh/Zi9Ӭk5*4)*=VQo${$KzaRRh%V4۳hF7ףhQY6%}k -655QVQJɅ]{c$۶gߓxQޫ]HUG|dRfj퍔ڼTjj+qA"tkiE^y|c..ڵWm7SaʕqwŤ߿.2v؎ᢋ.uHO3ܹs?&eŪDN : /p˗/SR ~:x8,M)WDQ?7ȍ7M7Dmh"t:-Ɲ=\:p?B~G\v$IRNvt466R^^T"UUvC+| al6[ zƓO>mbSC#[p=d ஻,_ wɺlْs\#cbdۖfbRʜR}&u20>>^Tx||@JV`b# arޓp8L(ʺS{Jž@ 0'k,L;=22s$H݄jł){CCٺF4Xf5h4'o`Zb|l<=CXDJ_u]d2a6„졚p ,Ǐ!,Aji(2=K4t&I"6[𡁁n(%CyKFeQ%w-.ARRr%FPKɍ;Gl\Fb/LK"Z[3M]\s58m²ș,=B$hTKi0EwK(e~!u`|")uʊ=DaB)֭[dž  eRr"R& UUJA6ЙH$='e6gYChh xQi ܹsv:::xǣ(q#Iy'>Z̖lf06:JGG'vGcPH$]ɨ@BIBh6gQ/3sS_NaK RW|b3iU{RT"lg%T7_T9(5$)3WhY%.下'%oh.|dfr}25yIKC8!+rZe)7]{Y{l>wY6j&)sד,'Y]OԻd)]rn8wǎC" nLN2X>xtb䝜IRm*~ӷA08)G32w07KT+p)$ ,+ͽVBlO޵Ӵ"\<|SMjPl*]./s:^\+=[}Y(Qe5 9ojp=J0$O$fTQVkz %uWVi)$%^d77"=yKF!!@\ܓRΆO J VRKFK'#-*lk&9$(f07O܅p2eo(ݹʲ\kY+U_#>6s6~O'u+=j.3[/Rww7ʪzPV.g-{/+i*ߋ'Y/`(a^",c/M&4H7,I ,jYcC=cyUVX`*{Uڝ|UeTujٷXד,uǧn(”Reŏ,z:%èMw0L6?p4DqpW!C5E>#՗UvWTT%>=_˫XV>oʪVΗpW~/sww\/w[y]I:3?MjBnx\X-=cL#eB,;xo%+Wb2+Gݳe+VrU Ru=u2[RCӒ$Idd"`ߐ)O2@E""I,ro| 0B$@,9@(*]]=F0Ŗu=9(P_xuQ&&I SN;&L&IF&L{6rnݽu| !D)!DnG%<706k8yʦ-[OL_xJ"o|V un8'9]4]i:R˚ d9kP†Zѥٶu+>_ƈrF#4C}y(A*hRG#*^T[D##8v,N0 E# @OZ-RLm6cCa 8%=-~x 7P=x4jN;hm*DgV}(x=6.'<^7:y 1dYzz/fד,'% J3M7 'LŒY?H`?aGlm~#jwmmW'r#G'ͥ]( O&tN [~j=ws{/p?=a&p)z{:+6_M<3g]ڕK8?u1*Ͼk/]Wb\c~3w |77(aղ.7|gcǖռ(-֕is}>=S J K ) s31uVsF~.k^{TlSJ= -jo)E! הn~)gZtg6kN09'~DMwfV6VÝ_LlFU_'4"D/Nq zz/fד,g ~nL&/|=|c>4h;lJ`pd"άM/ȹQ%mqjں6?aRO MptzEHy]}~N~foj} &&C}n{m!7江˹4{]>VeNj:'DJ,_$D]J|736ٗU('7& IDATJ>;^ Ryދ){MLkyA٭z|ڡ@vMNY03=}Hw~PP7Jc%A2ˆyjPs\W,ϕF1f}SV];f/SV$ CgxC&H&4P@lrrb:d\2VXA,G8Y@ϒ]t-ǺFeE!I"cTTNwTss zJ9VIg0 Cuu]GuLjkK3YEսGw${?~GO36nh˶C.~+ٺi-&Z^v+Wo[K{sM'q<opmq6n~+IM㭷_Wl$OD^="8Mn:ZX\m;ޣsەΛۮ7t,f~^q;#1b|i9#?puke߳2󆗐L&я~9}t^1 bFѝ|#;H-ażf<i2ϙW)J~}x+ʤ3WkR,juWV~Q>Ul]$a3ZaOA"ΈϽL  "=E8tQ LwF:PB8RxNvlXu`8|$N˹jVVv U:L ́(==PUhkguw''&HFF8w A#x@pҷLJ+"J%PW7y;Gs7^&/~~ cr[݊m~/8&'BLe_LV_} O@M'2z= &fDBr;;|8w.H hyzkH LI|M.4y|J}* ) b~'fLE{.j:/<ocm|bQ6nLGg;]b$n."HEh1,A5 ^nXœ/'('O9o%,\.ϋ wS2mî?dt<- ỏ=wh4Nh* wݴ~d{n ݚ*JD|k: >`({o[U޽{dݺ"89)FGH;rZRje#LoWR|H3y5x+*D"E?2g^״ى$Z2er"LL$H8n-pՀH$B"ȞPñb4e#^sh)@S$c;$EjXx5rIGV66ӃtbڱX؝M$tj!YIb OFhxTDCS;G gc|`҉]w\_fGScZvtKd{ڿ:{Opݻ4N\0xYaQԙ&^bLK+ٕ`*,3Y$OGgMg0svh 3钖2z擑ɗX,Ư5.%H)I&WbŊ g5g O)w%~=Gٺb|޽j\=Byq9JȹTb1"CVݎbVJ\3|,CQf*JKuh$\YVQE7ğo:Gl>ynn/LbnY`]JtCAH꠪d&em8 R(6 +:Nau`H&b2I0HU6+Qtzd85;LjbULnhH8đc}["]eGO`EQR&Xw.l\,sb*9-{HL 6^?6*j4cU;S^ U<-ނzj;r˭hZ2P8iΠ2~񆥩l)rV k)~PAϜ:I3dƺ oFFLLVhNp]S7Dٻ5_uueZQR|f9E:Y[hB)y{BS/-LJ2NM4P2}aFIi486CEcBCh"MVtECMH"THJJ8QHIv 6Dp)^zi7۷mK}چ)@"s3{"@~{(8w75=yg7w'b]Yް=:ge\uϋR' TD"{¨))%`ɻZMZ=/JB=Vеd x qh>B ܌;raV\Euv[,mڿB,c^Ξ9M<c mvΝ9˩VY~墽+S'f^+.!R[k,Ҽȹ,ӚS~? ,~zܗ2U)MH)I,GIK@ dD"F"EMH\mŢ!!T)$R1@HMIESTBBɊndV0R&-̀B~ pg~n#I1БR/3UQ ӝ#Ӱe|=2|0}' Zݳ {U:D } ͥ7P1 ֢<i)-vl)2~D5ՍXŬWf}b| Dp2?N;%˖s~^Μ:lfe[8|&Xىbadx; 2ߴs<~01>Ʀ˶28ωcGxeLz:V+dG NWwYϙS'sZ |E_ySrz &Ն$IMCA Z"H4`2ٱZUdR(B"5ٌb#"!"qIp2̙Sg!nh)fդUl8&'̈́$"Ջ=|7HhNʀb{g !RLu֍9Ͽ9rD\<-^lY>k4+]ge\K)ba4M/I SPD:rѰC8PLî`0<5ńL4%INA,EJIxj@k6=uZ;|TUkFu.7M^jbtxBUUz-gdx126}, NDUł@.t*Ɋ %X#q(aAنdst:zd/_{dkQt@&8{IX,m "aeٽu L/re/N)%"ej/SOI5`6 6^RJ['@ksQR{ʋl?u9b`N7PZZBn,!L¯7Y#ĩs"AvtuF @Jvj$pOeʤr{XlgOf՚5~6lqW4-/ӯZqj.<ٰC!]q ;Ο vL8S }0I&ÌNFbTnel^+/݂ŬI}'܋19hY6aQ%f=SヒW_7z3#cc?{Ŵ/XwRh!\|^iEQen,d2}l,/ۇ, jQCe.U J%TA1ʨ _4ֆ._!Vx-6 i۱my}x}9wч@KRÙkIό*8xTo!l.%f  {W-D8SSA\nCls]>@'N+o0k-ì,iscut65"’6nL"aTÇՑ.8L:M7c -L&5ObzPRӤf $EW\(7o`(T~d `X={yɟuF~ohٳ;9;0Uoext4 ;O}˥\{U2#"ϽвO0|~ ;+J͠ 3!23)Qfb-RP%D[e y-W3X5_k"7UTHUX*)3 ;F԰C8i 2V$6mX `I[ n;M.dTg\?;6asq+{uxY_nd5K'm*N"vru;r0H!sd7Bz:eAB-tY _OT˷лl)Gvh(qm}^'#45T8T8LG[+v͆laZpUB(}aqz!gs(L7E?vN=O~4iI3csu;y_ӧ x!G8z`0ī{;}>ӊ\Ŗޑgm!\\~&B EӯC1 BE"+crYtʍFTa>,Yb=*^(S:E1疝XHhQVZơGtjMbpBAkL cJD,>,fBbSQtU(noK7&XQ_!94Jc;Yw =w܊kAaY }ܣǔZCe #eUSM8T Y[]|)ʾ lpQnw*2?b*nAa\W@. <̒/VnVR*QLi3000xrkkE?PM'(Z[H~.˪XXEt#L~LNo-wo5 QtLJf8Mg9| zhh#5 lиuXюB3ۉ&h^M'"$&gVZ7,hBDAĤnɣ:H͐;@AG/"\ߍj;nMތ1~> IDAT&}9ŦO |uWlJa*o$r) R'oese\gPՏq̒Ql!O19M'XFx2}%l ϗbUB^_ >WJVn!r _|O~nΝ;}G?Ѳ |cV??x;Ys< ״9!A VVcpm  r-}A*S8BQh8Yr)"܏ij5#1h LSI;|u76~?݆crlI&$M&ՆL T CH x2((s/\ߐ]܌z:$3 {U:Da@,leZ2jУrabt%d*|2 /S\*h%3gڙGdGALy/K^{5{9Ξ=K<'}_Wڵo|#tvvOVwO?#<]z> ;v`޽\y啼bdg^u}Cȗ%`0ȍ7>9~o244.L]X|$dfSck:MkwIq,f>ϋ.T(f4T?0k"͆R8ydChr:{ sz,H8a7' Ƙ M:ɍچօhezcZ țY#zpp U.CL&WoNrѲXh4s ;CYB:ԒєѕEpŐ[7,M-vKB ֬1F w%;~1x'3ߏ+W9}$Bo<Om!mmmx^n6z&''ٽ{w6EQJr}klݺ5g1GFfЁ7'b,mC> =P1Z8&Ѕ9=8F8:Ehʆ"*"0$. Mqcdž8s"J(n\A4ڗ97gH&u6oLwk !aÌDhXT]W&3艔"QPxU6VSN9ze1lP4/pa\:eU?Jz+eٹ-vm!:=9` Y/)$I<^/V !-m0p,ko L=\} CxH)ټu<]LKUʋ NLlپ{2>6h$K/95l+3oke|ӟ~}Gy{%Kk.R7D"׾5?z{{WC=n@Qvڅ]vaZiڵe˖yf^x:::g>3<ʕ+YjK,k`ӦM477m6V+{R/N>/J96P@ FW};046̑_f<1̹87lީ1lJRє(+g:Ul4{}x\4L)4y|;y߾0:n9o50mHFQ3KFuQyБBϪJvHg|w!Xvm=zeO~C8FٰC8PL.cP"Q  H)XAB!Iò: b2[8wuSz흝vVHÁ5(*g25BJ!T\n7C0E zfe hD5|1R,juWV~ԧ>UpϺ|o½˽ޛo~3o~se­\n(;s M'?WW<<@6p@luF{grg/3pۈkqlc<c|2̑y85DB+bLf FPZ{hmCAU1z2q6t`:@`(JNBQt=eDR#ӣ#Ü;}&/8BcP3=WL߹!Z$HT4K3X:ndb"mX),:լ bi\%mcph1YlMR- YҋL/l{5Ϩm'|+?ivrneSVk|/=o_2R 3VzX19Wj^ ?q|֟OQ{SDfat ~Y\nrb|C9t.Y6BR$p@h|~?gO"11:$ B2$*̛"+Uq 1Wn!< W=sUv=tg^9ěo%jpoP6N?azyCcAX-&;lV3OsvN10[JU6"Z7J|ԌQa5 7˶.$? TMA@~U%{Ȏ層 (@h4wO"C%e˖FFZXXyac%ƌ1t=#[kْ%mYA$&}FO7P@?P* jv}#~˗Ktlr\ךO 达<,׻!X $@Zlzzh._[ֺps{~vwoB=a֮ [;Bnb ФZ"AF `X1>E ,Tj(U4q[e4c_jdjAo}/׫uĠ.ݶ7>_gN6o8B$?;zgGKgk=/" G( Wn;5<ɍ bxl`P8J8CSEk*јftmo.b-n"\×-F TBkST.XKExV1ţkJP+Lӫ͔?yyV(ߕ^Ƨ52Y~~ myS 5Q|v|Jgvח,w V͗rWozl|!EG]؝{:عoG=:8&Q}{^zN[XNj(8"a lK7Fsin;q93슒2&e"{Rt>Y[ t퍼|A0A¥ür"1aә[7sm`:=CQv6rب:jg!tMKm=]M"3Jֵ e6@edk I[ ȿekygIEǷLju@^9_W6.įPuQNyY+6Zόmo 1UC5 @(ξWLuWGv읍(u|8rk9Maz~Ne&-F'8d"0îm}NP@EOKħX|TQR2S앁g,wuqpW3tZ6 |47rc;ػC<ؾd- 54$ex]GKa.8)NKz>Tt1D4QCI0\_| {VW'ӈbl:K>[z\H~!XYO1GʣZ*2R!TBSW_KWw;O،1MlNvt8T` yk 5"^A-Fv4<r8FצF0\\Q 9Kd2mp5R*ޣq[CN>4 =/gRjd!\_NYwLWdT]FkR3ɑ0J93"+\ö%u3O:^pQg μv|i7 xɼ8 r@$EJi[>.~ں8Kvp%Q$o QT'.{ƥ8%n|^6DhDyg&\@vZžzj05k׸xc4X&LK*k_\~.%YZVTdZK7³[otl;h[U/;OKq"E@Q!,OJlzfכX%;9%~xB䈙_&?S̆)"3}__>)+Ոt>y: ǘ糟ȲqǕTJU9Qy*>QQUwvZimigtSV^)ֲuS3[iji_(h*7xe&6lvaI˒ECb`a)IQPX v`yAq$斫$T^Zxg,v[V!|aj2pxHHV8iuTW[p:k)W~]9@H, T`*mDu\ir-^ϱw(Mc{=N5T+Jtțg JZ։MU0b]qWЊ!RQ@ECL UO*%Cd60>ING)Biכm-ZO*]qq !\[5]F;\BDRf뒒dٶN7n̐Z8_6|c(P(y%'(g[sYx4ffz"p %ӐYjH@4!_Z*hzr9ΞWOrjj=z@`ӧpin'k컼EN̯ڇxS\p}2ՀzۃbR+$(">xIj@QA-~ytָ⯞>q7޶NTj(cC?x W92(p=xMX܅\OJ@T:66!\5r|;ٽ{ ǎcjjZ/{lann!&3ΫC$JXK7Ywou-w$`&4q:^ &oVB3d`*^8D;TU#P5x9gp\?d©B0li!-+P,3$]0)D FrZW>ǽr YRRC*Dk3 z[|ד5UɖP1(d opvG8ꥋg/Οss7ZsyZ64673:% !fgk^=u뙟0>6ƞ}azzvZQטOM:'?aʁٷo+}|K_'Ǖl[C?<ػw^CX";JX1 qz_h4&^C)4xji?DoQ&]îڙe.]g=KK}4,L"n$7زkۈC(4P˲\;L#>F]/^Uq}Fcί.8)NW ] a_/bA`v[zMtuwsefۻ639>wBa$0?Osk+^1F-XHٙiba ;TU%0 jjkp8NfgQ5 EcS3N _M Ο6ʹe6㘌t=.$T~ CCW~MSg?3b|#|3kf"8M$+q؝B_ uJb hIX su&. =Ɂ671h(MubP6쪝C۶qq1LBSm4-4 B!BqR5l5N;@O&ma^Z b b)@ PȄè i7f-UuKSZY+qWǗS֝'U!Ci:BNҜLk_kx\؄azjKqCS[Ws.x|P5+Rq*e`鶺>v) (BA  dg?.^cn6>4\en%.mA @hN;f&&'ZlXffC -U]WTH. +D0TTƬX 8ǧpcoM7tdO6bvLKA*:PJ"t ;qW95HvӒ|-u|䱽8w߷l3 6X8͌O@J&ghm碣Ep-a\;{m=NKmcMɍI&tS_!4M%5ӊiX@4z:,/X^ֵqXVZ~=hPAJQGEzZ4̺Tʴ4E:q^ /defTE里IHmS;ccNSs AwV&F1Mtmf|l0h059=pEQaY|5H 9q8I=.Wr'PNSK vÎe1 ˅np$]q\K| p88].6o!07G{G'NSmVCGbעR;V˭FwDI>[1V7.oa`jiL]A"jbĦZhP~Q]B OL`J%$uP5u L$BQa(N )jn210Ǖ-^~C-{x~6nï$&M P$%w3XEMeYLNiqhwg 04:Ko^C[>k[y\6DcôbrfNS|]Y`k\vi>]?4zkGm{/穇w102L ƍI{g6EQᯫ[MսeDpZZ0Qи3WW/;d7.34IJ2*F;2{Rn;|=zgrb_KbBH2l`WWcd A$t0CsCnlMT桦Ά03Ąrb7T, `XQtMt9) 94ܺ LBDH*0(S`pȽM[{Q EjXxNyp׽2Zd_`IxSH2Z4GiGpcp[Z9m aZkyؙ~v⛓c~4m4HML+φlqZ)J糡,aR!˯Rki!8"mll9Ϩ<'@ybbRjU8S/|떯mN~e7xjzR8k2c2µۼpu^>3bB 6lQn0ũI> Z|86aT@Ģۋmc&h:BFh XB`x^11^,L%LTD1a*1Pq]`P敩y.^ϡ=kem+$]6"j\bgOQ]S (s N/ ˩7=+i: LsC{8`8ʃzKmYlL H(0[:hpD"û10o7>HQe]s~=Jq>W×SV(HT&d>!ޮd Z5-4NHRR:ȺoS|If9!(YvM˚v]pWϯ^5Wa}ײFRr]gΰwb8)$* 0hf :7K<m._l !|N " UA6\vl`x](0"1)IUI6-U,`J9b3RCM_t4{$ɂe"5S8?{TtK A&,-1ա8r$=<V%mRTXr}ZF_yJ2UQxp}޼@}߷Ӱi;[8uxb"qWN˛㿼tN]IrMnL P<>/f{w 2RJ~ a,kW/qrFDY959?P5-e:iM2ZmpbNfOLL2 $ Yҗ\r,^kUN\Z>5jt/C-IJ,L4 ſX #rqBʌJ|uxOrG]Ò&:!;?014.Q4 iHa"`6񋵭jj:1tWKDY,BeHK2% &0iZu?Ytؼ*Q%|tyab),U"TPt Kט .`F#xmv!c^SS;A^-??$>^ОUCt75cS3v7Xt !rUmn36Psђdd=ٍSǁCȑverut_і5eY /3G6Ӭ/JgǸr]QmD빆aŨeA}]}Œ33!̓1 9.O%B| {VWɧ\ں8w*vB/Upj6ɂ W*Oa|# 㓎)\f~ǧz3])-}1| H1ޢm2r5azǕ8W~Wʉr1@AA1g_?cGTD8jwbb,}aFUTPKC(N)cr5htqS T"0 1=;Wgf\`tRc4P,L cjyGw Є&!@( & aDB17͹0QCfTE-7:ޝoqWϗS֝'ӥ)aM\ĝQC_!L)"G̼,2:d6Ly IYF̦ Ew!LǪx\iP* !iM÷{-,pfrXuէm%^7fQ &M0M =ƣ8<>v)1MIL.,0 ;¯ˎEs\0iĈ1Ub*`, %)@RQ,ii[XDGE.T\oܾӵ(M$r s̰|5a W˯g^CCbB N_j‚r%i1 /|^>Ch.s9KDҌd!~t6`!2U"mWW QUq=S3g7]<ø͎tP ,V(X(v"l (HE ,a&$ʔNC hDF a'*J(G氘i RB]n FUQlZ`RtKb!@h*)"ei*a ,Ą $ACfC,,Sb)|=RjҮ7(>[ڵ곳cT 3PC|)cş@r0qO1|$! ( Nf# bQ50OYB*dBJeW(>E~5ap׽bV!(i26{7>w/˰"60@ hB&R MoAU HILHAJ @Vb!1AU£ l:1B XSUq5!-*Rxe$KDLD 0U;s""TA~ϫ*!ޝr\ Zv8l!\fpŒ5!*eC2m6>c2ʡBZ" 2>skZZ}A<^a`w8صg/Οc뎝xxBp5q#iO :$:,bjUcbmlF딺Ƣxqbpt4 l8"JX!hڱ; Sʸ&t  !Tdr2 3"Nw% GM_$b⛾*)PMá(QteA†@`J-&jPj 1!u݆! ̘ijHK'jTRUTF,}ttو8RRC*D5a|-l [G8˴uCtnBKk+r҂W091NSS {zls^bvm/Υ]ܕ_kįmNJk%|dY9aE$f b!@MHi0*eI Kĝ@7!?.Sqtq X`p J)q0@QLSBDA1U@|ɢ"0e *ZHCX`XXiHb(İbi!l ZYCxNP#K{mY oJ lyE=D[x H9m;\ $5]:4hހEtk"fL i׍w^zEc/̳cn=yZ?s2=1=3=54u(h[^֊|j+:W%TE–_h5*Rwwlt?V:n ~pm!L!8+ο<q :pp(栄B!\.v!F,F$nwtolzt--(#5y=˓OtX ~Q|(R D%:^S6m UTQ/9y&vnbfTg ,3;r%I_8)NVP&¬S3nsvU0UҖMl0 ~HZf$֛Ȅ8eIZH)1:FDan\mSG ǙN,T.__$qׯ|xڪxu*o0ū\F ʩp;6 GQa'˔hB,ftEbJ"ev,$p492 vىLGoD{]UF 똆E0AaCSGqWWnL2 )i ,m*#$gq#,hvIE&G1MH8_GBZ!nw!uv''qqj~w%9icmr:"tZ||RVB|tݯqӱA^JÝ轄EV㮴UG0M|އiއͦ;?~ndl2@W[}7xSE IDAT 5䗞:|({`07u6]gn>Mx@÷ xUo'q-Fp<~vNɩh'ܹ|̐['|ul~3!!Y^ۧ/ex>2<7%49RNĉ˕gb-}!"7܌ݻ!][;x}54bhpvсp{<蚎㡳FGWMM3t P"\b}5'-`|e*e:3rBTmUEU }9:Bx<}g#'ع<.^TEM~ƧY<2>@Z[v0O9rݤ"fD*ŧȯ!\S0::<޹sk ʕ+vE! }4FCCCZ444 vJcdd{]YjQUlUD_*Ƨ4yv`Yvi0,4Maplӎmgp  ~R!˒Dcs,[kp9mJ*,+>Ԕ{S#"`( 9w-Mi~3p|6T]Y3Qp0z+gVԘei\6||WSfGZ" 2>ͫlHu ?('5c98pϰ~{9hs| ܷ-8Pڻbʾ|֧ZKK˚򉾟ŏDӦ {uK8ôl#g9+o ZƩ 7x=ko_fb*KA [Be]3~˲82}˕xHéNʢ)Rͽl >㊼& a\or|B C1ה"XPHI M%#de6O/kew]=z}׈_]p8?cԧ>E[[۷;O?ͧ?iyЗΆǏs-moA8&w}۷o ַo\??`<*'[oWΥK+_ RJs }m۶"۔5xqW.W.';.Jt6t,\~nF'5ֲ)bIgk}޲*Z8eKߣz5F눅~i/K/GG<3|Sk_O>${ǡC3 N{{;ǎc۶m84p.?~_ ׿u.]+—eLLLK/}~Ǯ]OJ{{;<\v x9~z{{9y$\vs[[X+tD/3ʞr򥬋QjbloGw~t& ^#Kaܾ}zW_}_7|'O211Qz](':[HC~f_$V߬ȥMd2/J<<-WBr\.>.pI/}  BUPT^JTCd|B!45lir > *.3y< 5%isɶ3u'DP!r§'3lsiX,x>)+^쓃_8eeו+K?~)-ea&6]uy\//?0@}C~fggyguH$'> 6mڄaOrؽ{77oz ~>ۋaΧ?iZZZ8}4mmmr)Z[[C/>*Glݺ08z([n_"sNW_}v/Ғ(7^-ԲE /N{s!ӆ%ĤFPit"ߤ3s7$4u o{|cP$kc>PΥ!65֢X011kp8 iX ǵkزe po`yk\ÌT[eh#CK\SҥEfWrK'!/{dLJlƼeXiaD].:GhUT"|}="p%h6~N3X/N_ޢ/W|kz/gy{G=880xD"|>N>͇?x9_o1Z<|ݸz/^'p7~W?n]!)L7V2OĸN&wL~_t&ϑverut_іu eY /+Jrʪd~f|+Wu=Tf0bݍ$x(; @C(XHZ]!, "=BeYDaN'RJ¡bw8@J PX "$-,p=:)dl|ƲTˮBQ|j* ஋[V!XݥUtU %(VZ5jP̎~>y͑/Gsi6P)N\=M^w<#SliƏNPbn!o~ ߽|Zܞ?G NЮv~m˜4ot9" A$̙3\z5-mGgƃ;?ա)Fy~^'~*[3ãW8vaD cMON|䁭<s7]{^EP JҰp%oZ *"6ke6&QÌ#M7cax4ݨ¶z}(}]O?gb&Ho[=OA(6M{y`g;/#{GymmLs9sǏs 0dEkP$?<}#1~V8|=mu\5zvt6'vT:e/w:_IlDœ|Rhiv~K5!)N3QCX(dWU N$0b$@_M-H$miInݼ̊7 GDɲtm^ִ$z~ mddѵS5itvvz*=^e&,393\0}`ϸ)6.NC]m]vV?XR28NxWZ뤵C4Ӂݭ~,)T`mm" :cccܾ}; S&QƦJ)"1ɾx[Z3A>ph >Gvw*_Il uЂ w'mjFJ&f=YC(v;Py[2=5,:f, q%VCAcCF9eķH~ 0$uhI6vL8ܙlUd*ˮͼ8KYϹ.ˆe5\Y/-,2W$`˒L;ZsMQ#8\i'ʕkiƞ~=\7qQR>25aZQB@~6(zBwhjk&! , 26 =?@36t \|3zB+4E\]NR no[PξcJYjuE21=说e2v\I5aIuԚ 3x@وbлm;ANI1S(.,O>Fga-E] y/_*_f8|^/e0eꉥB&eɓNZV"KNBe_/4Kb/y]ɧp"MIB‰* -wvsgybHL#n"5 NkGw&_~t'{4s=HgS _E\KA[޽ D-XŭYy:lRC˲ Qvp6&iؾ.|n;|b7' T#{;q9ZÇr8F~{yuB_}|w9_E#G#c&ontԕ, 詫L̳:ZSf1I^Bpu|9e |2]qS$h@EYfk,ȫJ`E˸>縼RUT0͙y!p8] vf/t$wZD]Cnߢ{KzeQ/3^LYçI0ת\v+ke rNJQ#TubJIMޛUS{U/ޝNY:{K$ 333.~qFqt9.3⮣#*"K(@ $dN:t:ݝޗZu{nuUuU䕾9ysOs{G(zGS V"z/T~.tTϥHZIq\D[6+eD$Ϩ3I^X-QN4 SG!ͦ|`}8|ORRoWdo&DR_QH]G߾O`ێd}D~ԮTtO B dJJJ!LOOfAv=.¡^%cOzGw'ݜm&17B;_;…gn˘X,x\,ԌP(i"> ?OIbCgy`Vh/@ cY񸜄a ᰄfvڣy8V\Nmw?C ) ~<},, ؽ6u&x6y\S$8[R Oc<BY!AH"jd (?.jꅢFqJPW3Y;yf؇Ik^L`Lr>)ڤ4M4y"Y-#a(Ӧ!۲(uTSSjeǎG|m333͛9sFhbvprhjo VSSUʓ/`fOkc5-5<:_ϑIN׾ I]u9cQ^֭lyɩj*8oc|K810S0:8pu[hmW[ME-oZKmUYbY9U>QƖfLplpyKR32|Hf{ us?T& u)Z[RT1i婽7CzS(\w\t]KUUU E X7`ZZm6V+K;l\ggte 7{6|k:m|){9 ̑lbd%BO9HJFЌsW#9jz6@!+8؇h1 f$*WЂ&!UP]bg] O%6kY-1 ӊaMl .^, $QVVFKK H%@M;Usκ%]^sC[׌}5TC[X"yP![CA6C:EXo-T\.L?!Yb*EކR4 ǂ?"~sNerzkN6cW7%_{g+nsYgqUWM{/cc-oSLb˨3!yW(y-'+1I;Km$#߾})`X^%8<1innu 2Zp266?5ev @+.\G{s5AJ=(/4TqƪE}ZDyB(,[Y#ABa@0%Ӂn#:/Wy=Nӽ{yG~~Gf [4/U:qpT-j$*AhFxHYKSLH`1@p_zQMftp^2 .)S]dπR }ghp[隣6{GMm-x=\fřgŊ+yx}>֬]ǺٹE|~:Wŋr~?t3կ?ob|>??(l>UMe*E>wBF +B8vY622B(g~+Vğ+l|.55qoqA{{;|6p!7FAMm-e477ğD(‹.m[ٳ{uuul۶%K;ͯMkk+O= w߽جVN9J6um}/P87}Ͷȑ#X;NÔsrsyOb2<r<~Zdn^H F9A"ap)֑4335$xxҊɅ..W>ҜQ")7t,afh?fMk ˰*P5VR>/;x P?׬l0'(R42pb۱X,,  TUUpPڸ{ дcnc;3 bVѬQc&C ͐]%w>I<)a4=>xsE:Klmmw]^ghh! tvvFܩ"AN[Zqyʝ@D.=a48I;i҂::HE*R)Zss5c}q B)r_C\Y! "nG|LGfla’Xe3|ʠM8_C8Gm3!~NĢDa& /xya\os9uT vM6Q}`Ze@E] B7_…C/vj.\~sWV-i]|$g6ՠΈ)fbY+k!TqMvzW3ye4{'z)-+' r\VQ3bY:tFf|,Y,oPE?B4:xdW^| ;1_OP3 da*WOEy Q+H TN4lJqeBuQeN _M ZBBB%~S]xd)!_3ɫq-C%>Ln7(뭹XMfҤW7P0JH ־V~?/> P21>+J` :t>kXlN+ DBP4˔*\˛#<2/KsElA/2KAhReʪcUYhxF%R"Ń}{If)]!$K$K$K>pNϷo[W*k@y*n!xR4â\oflE?sD&fb_\FGmLLL)-GSھ[QbzzY ijnIKlӭ9ƕY,w I -P  Ĵ)43,Emv9iWe9*E*R;#Xs,Kax6y<%!|L>}0 PiP Jn= ;6c?IFvlv;V &ƨoZN}Z/0/ٗ*N$x%'3 c$ETųH, j.StoSE5%!\rDVhHo$2hy,A[C5VqbUGWa[$)YE?5p9^l.̬-Pt+3z dx3H'?Mzqy.,LDiNnln5c,V+pI cV;:;MoOKh:TO ֦- zQحq7[en4¥i/ґo.BU6^lRQWiD It䦫x=hgjƏ"𸜼zv36A(fjڇˁj-~npIH~|"!E˜$u]T{䵿UeѕN6lO1HFWeݾ;LT~A]Wd&HwΠVuc$kMqFMr~&pur$ FNj⭨֨qp> P2L72 ɔR2/d.??*`&x>:m&ޗTx㡄9BD RQWiAhg 8Vzq?!ǟKOi uJ0R.Jc҈3S{oQB1^[_bF4NB!~BҊ p8,`ltfj 5 >/ ZYRQWE*R2"@-xaO7n젭q#cS<~&&}<~꫽85LME3~?6oXn3WƸQ?/\/nl=\3Qg0Cs!#0M|ejaW jU+*+yx+*Mf,A3p,/)\臰H)h-1+*<ueL%\xr5U󵣄pNI0f$k3Bx|y鵣6V9~30weRʓF -jL53ZLdT0,\50~{Z<@fW~UY%LKe\E"xry\ k-$I: @\$K$K$KNhOiڄ4UP[Yq:yO/{شf%n^O<ȓ/8D0qSyI?R䪼aFq uV)1U(՝0&k)60lC Bx(eٌAͤIY(({ny<|u$,来*\]VU2 ԧfg.o h⾼|d\ IB IB I|rfYV6taZxs'mV5UP㥦_M ښ9P4TW0x{d˼jcZC8GJ ګ|2 lӭ9ƕ(rB)crJjkkپ};֭!#?8 2::jT"%!!ն粖Ŧϱ,E?UxYYg1̄9 g/NsrGaDгS!LY*<]; 䊼$$aד!xLdH7x B) eNwe~_s 7dxgxG /`ڵYHEZdmVnB0fn^CM^"O& EvcZW0JBuT5:DFYE?*uqĦؗMOˆ LBnYsL.ٸ BMocCCC;'OsG?իۨI(yǃ>X44_B!ЂU}G}'<ߺ*!&!frb\a㡾f\.WIlDIh(ɩ/7h IN>tZ Sy6~__ \T,E?fPާoK.o~tuuqUWqwRWWÇ|:Gjkk$9.I|+8#mi}e+v̐g0x}l~sWp!'N`޽t\IHD(& 22:?yR )ʗ}h,Y7i4uɐfc婽7CzSɝ zyR}Ki*< L𑌀hTxqUxHʆ]Otq ^ kBԕ@"[V, ~;-P@ )--eɒ%y睜x^ AAYY\HEzc|ZX%ss&j%TƸ!TR&B0ֶ6V+QPVZ`00$jc&]?<Չ60ϴSH%# x Y#%Ibfzё&ƣӿM5 1>6OϦNb)B!&&#D~Z񰆏of`0`(PIЅld@s))sdB&E]Ν;X|9---lݺ>1^yN:Ŗ-[X,a.z{{H3>::}GUU_|񜖯XGӂU}7\+(V-iŖk*.$\Kz~Ta4VO&CDVU^+/MMM"Z&/IX+pk\ْjSxTFAFG9=4H0뭈׀ة,Nu SS|>***x> B9^&'pp:I_z9ZZ{~JJK J-qYUcq/%:9F +B8v1\|RF~PO|%`ӦM>}Qn=\6n|\~wya=5\_N `ڵlذAsbb .&n7CCC|K/;($d)tdhʒRvqA'ȀϧbS.}{"/v O'n$qzrՑ1ra(R ?>v-M^K ###seg|ASFIxmLf7(qeLfҤW7P0JH>2|RڗvK.j' q`^&Y|V=/0#ìZ`(ѹz-P4EOLL"ch[{_crre+:VTs12>6(+VrҲ2FF8^ K-1WwB>Qi_!3{=Giln ѣTTVRRZJSKk} 3%P}0nHiߜ\+}y)Q/^իW/YO ?' W e]Ttd}$K$K>pS0[o>U1$q OUǓ{6e[D1#\P"Xbt$꿄ꝪHCpQKe@"_d݆3@Ôyt].Jhk_̙ϥsZ:VANgtxaOt;Acs 8#H0X,V-^‘ÇhnmcV61>NIYnJuM K/";Wӹf ^KK"*X~NQ][Mgr8@@9:28)dp`3:2djU)Ǹ^|sD_l\ve\veܹ.g}V7r9WOO7x#{!ַc=_~9eXpK̊T"qɠ9d iF&'ζg|b:e?S!6Ϳ`9ERIK_ł"‚%jZE1;32C()@QC8DЛ~M⇰ !,fC]cc7xft\U :4mm22)v??O~{sWpws%$t:9x lܸ[oP(mk- IDAT>Joo/vv|擟$pOSn&oG??LNoe2O NW&P8̫sE멩*~$ v=3/B$5հ#ﺀ'^xllcvϾrj?E| ?C'pTWRSQOH s7t NrIϾr8rEˌMLWryzAv߲!b&)k*]/!^C?\ TDӧetMMMp njjjk +.s:9{bIKm\;pgsVR]Q=\s&>f| 7\~6en9QXШ!`|)OTQ3I>2gC,F\o\#"w"њKg}VK~Mnrlf;#(OҋJ\e崴p8(zYܱ ! jj먪)}1BZZ)--&l6;;ѶŊU-/‹u7aXhikSZ jx1,]*;K-[Y 򕝸\nj\6JKkU{Y lv; M͔{xJJhlnpDQV餥 EIi vӨΊ~!&r-qGCZV!r 7M-ZZ)D܅"g'B/O~466R[[;.-ȁmTQ (+u1틴S3~f|,;89vRqr*<CajXӣ,i_ڪr*)/MMUX-gLL )[jhEii[榦LF<:Œ#33#%\Ch&szWųCTK.p3U^O2/-ONBu1 e^ʼ*-XWR77EV+NSՋQEe|-b(愗))SR"U|%hhlRVT ʽ4]011AMm-.'\'UN:Nyڵk -ZYg֭[X,|`ժU_5_Wq\[2nf;Fuu5r tttĄikk"3<_Xt)g}a (GMM N9B(ra&''iii!s)xv ʉ[C'X\Ciq<.ճzהEDq'vQQa18}}f<ٹX,暔e.:sC0⏑<^1g<qw|p8̉iUx7Cww7{]ﺞ`O0"   %4]t@p82+_[[!TN&3&ʸhR*!Q@&xZ<02g;KiUdfg.o h⾼|dSP(//xg?Kee%ԧB /088({B!l۶;K/UY^H:^H;E@0ķ19|H*+zCGO]r$pS򳽇N zN g<M S/엱}MqǶ$UMË~CK|.7!)36C !)KUR0Wypa&1M.W^೔aNxdnE1O@sLH6}k|؈exx~m~~_poor.>̏c6oի "HiN`Y+|ScYT&пO pH9&yK IaAVB869/biǿʌT'jT8sECx.sLķ'IǤ5 ד'brIFi222"3c$Ex*zxR!^(|!˅' t:ٳgO=?Oxؽ{wBW\svڅߟxqTY<3ptd6c%#%;.BgPll2(MMMqYgo}'N0>>.7222Boo/79Eҧ,yZpʠȷo[WE?5)gb06Vuc_61r"FD4q̪ =3"SqFM1=CQ\n/'9ExBD|%'ʠm+R2//F~%?.4/H)O2]}HABbFc٩f,K֋lzG2:x+h+3oH'FK:-&om\)1vYgz;ؾ};7x#^D__ѓ'^?yއ4̫HZPN ue40g@a:1C]%BhGRU"Ax.⧊Q_p\o #PW^g!T}r*+4km]JRǤg QBDr\O54ϓFB xPDo3HQLU8h uL妬 ͖XnSȝ0k+ܵ?TГF e EO䡜AN-gVHkdjjjVtM}[[rkN'gwT4(Zss5cuq $%Bh*F}iE?iRB"M*P*2hfE0+," ^X*}0zB4jli b<HhoD$F$ (_(Rd*,[ڗue2 | ݎڷ\ףܕU~Pu(?%hL0Q|즃kf)U&;9#e-MAzt{6, ^E͈YxFq\:+O nK /,YA?~ KYz.[JC.4dV 3U2L pԳzhgg#CHx"DlyT,jE\m)eYc_wd, jp ފ ,A_kXV<u BaU5յ,^a^`/OU4I;Kd%. .e=7xύQr^|ylV+֬aF^x9*+x?PUUM_ nx{{e[\ˣ0nF(U#E:iDŽBHT");!Y9&I?S^c D!NDf6x6y\Q!) U[(CZ\eap)NvC&t ٴ_p|xҊɅ..))#ç9݅+r|w ÄV;:Xz 3SSC[ R슰 W:^|$_2U[tTp:n8F?gXTIӬaW졧(7TTVRW_{FGGYAXؘ * j F<cB/jUEؑ̊% 3cLzPD)*gFN&ue2; L\eg8&Q*,p )~$X(n>p48 c* g &Isʨ^1H^2*7SGj\cPX'9tF/T" Sœu4c\ ݔupbSd 8rdyÇ?ه@8oH#>B4l)p;,f(IіUFeHtpixB}#+sIB^VW"F.}[+`]\yU\~W]XEl*I4eeL>R䷯0Hh8ʚ)ueF""NUMpyM֛rDglrC,F\o"㮈&8XFuޕ%0TV)0|Jƈ3S{o⧒;q`E*b':UTVڊbTVUthi[夤 ÁdqGfZZ TBghx[ =Ay~<4==FbtBpczx#E5̾.2{x(NCeCַkRby_UJ#?9W(&Rgۓ8-R=J IwB kd}n.po×).rk0x(g$E?:ʘt)0urfQSS*gjb:.**VTY*#r6X0G_SaIm[QﺞrjjjLn}ŋ)--mVfη`۶E۟xa⪗|FaEľWR":`B8G0vUX$-8qPQWi*IJo)5U^>2:/n",iCcT ~vM 3xz Fmc}r8f|~&  qhMuUyҖ7¢yu;Q,pEH3M3xذD<|B*uFYπxrŗ$4VGd665_]%l^/~ |_SYQHlb|Ly)5< WYrc0[O^lQ;R*5?zk)V4UM9H+J%0 dO+_" yg) WULid \Pݴ5ɞOu!I@ {)19GQ^Po-ﻖ"1ڡ’BB%h Qv!o3-o?]֯Z4Oqg|I _X?o+h)}䷏;(/ar?|/rppTwZ%|=[8t|+g>΅go`<³Krp/I֮lgKimzۛ(+q+=0=2|ȝP9ұ mZz +-7If4RY/ zT,e[Qs+sLcCQuU% F1&`aQ$geXD.!z.-RT9#! -O=*[>vR^y0ꏩ(/j_1/s1Ͼ:;^~e<==?}ݴ6ֲIX,X,F&yf>&9|335x.g|(_u?䙝xs5;oɗ;ϼ2~fӺJ &4ǁʵ1u2ھЛN5);[< 1~UآYo|>f&'R"-T. KapV@X-XNGJlǖ>:JzO&!V1^[jÑK$cZ v/R$6/ >x3Vc>ώ_glmK|QSU΍|3|a<׿8i Gu tDKߴΎ}u~vc6ۭBFe>Ccr.zx{PWS?*<\y2֍D<5K03<'wCHB\)5 o7R)qŜO21=Z")Wdj ++DxIS$jKNRʼnm IDATR SF(E#hDATXDyZOķJg"@yPZtF/_6+tjEM_ƺJK=U{eh{۬86g2UZi2,J#@0?KMeM@ [GI#|B03>?()qSq3#%[NX|?IU \R|V%C^cndc+iWp;W=ʢ+l5ped@cOU~Ielb6Cm<=ʠN?Yoˆ3Rhʸ4Ն[[[hliHͯHE1YDC^@aՊfV=GOL'gTr-5hϐ2O RW) ;CO(cK,]Ϣ:6+/q͊bQVEϱy S"6V\_~a _x}{8smãX  p'^dղ6OyKIuK_/,f%9J\Rb._ciGS=HOB!AC2|"‚F,a(>>'/U}r*`44km]JRǤg  \"%`0D(NjbFYxl򭭭%`X23_(' f\.NsN*R?٬|=[]Wkz |;?W>OMe9~eˇѯ9w9 fJ\/0 \BPZ=|k0FނjGo>- lRjM9d2 5,ᠶ2otjLE*y O23nةt8*) YR8fϞ=@ 륱PSSCCC*n!E.^8yZ( Z51.P( pՂ !>ɩ$ItPv" v9'015M(bjPV8R 0%جV S!(Y-Òv2=vbi,Aiѱ)J8vSS> .ۉpR$Ibhx a<+g22j25۰b|5 skV6ɿSU$zTcl^BJٳg<[V瑺O}zzO- P)&a2[8y6nH0@ Am]}lFĄ ;#^7K *~VdS% (17eaIh)FFجٮBϺq֩B!/_͛ijjbϞ='?cݺuU77|3/_|u%߹s'\r /2k׮-Nt)vtJ?Օ2MQ-[+ +݊,͊*.SdmVeTUVجfje3 ڻQN3LIl:7Y% )}% B0Ό)8v#R/kjlAuJZ/~Zk!Kkk+wy'̚5]v??\zz<=͝ʼ #E W{5Ơq(! ,7]#PYYg>iwBdطw|{<,b^S|/d_ )gBbQ˹Qbƿ˿vK7o=Ȳ=0_*.W}[[ .;d|nr/s 7~!zzzx#' YJ(~d3U_:?źSt`&L&$0誃UJ~2NL W,ʫo{|K-sl}IX}ᅬ[1&_AKww79l˥,Z< o7Hi_dYF -].Fëu*f|3aڵ_oQUTTDgggBK>gϲ`ed2100>8555,[[r1ZZZXt锔%3'Mx$䕄/!̍ϧG "LJq TrQ3#5ޒ4y*;~eb-sf}p(7@{??d y8Una[;x?cF zs~}0~Vl%pCe’`tl,m|wy$!Rד<8~Sc] qY*++q+ff3ngDOu:,X۷F^RA~я~Áo}qb?8/z/#λ2Dd)/oJ~s)k:J<=e4K,^˔jҔ/%,:  ԕyxp:ltuu۳gzﳟbʗ/~+V.G%g5f:n{ˉ,\ؽk_W9rw8lG pp8{8m]XB0$_Bd2qM7;|5kp׳|rN<{w^z*n&p8tM̝;{wG}:n&wˉ'Yz5uuutttټyskp(p^UO𭗃FK~ȋ©!f*Fb34(!L+.Nj!`bUٽV`0$444<ݮ, |Mz!/Y rXR8lG p"Dy'AD}Y BBdb' `m(X,+/\šyUd2!ͪ,2#V{?}sݬ[I$z0Y6޷ƕ!z j8 ڵkYx1Vۍ՚x,=9λJ58R]˲[:筽y%1œ[b|$ēefgeKʽx/W,SthҔ09e4D8ٌ$KNbC8%Ũkbҥbe*0ã>zdU;}ш#% 3vбs4x$?$3w{!Ir'+_UAz(ռhER5K)s[p\̙dg\ҥ0 OYk AV"6jZ}٣XLd#W73{@\A?YVVƍ7ukqf#fYFޚG\W,>̞3ɊU`1iӧqYM4k3eT _<Ɏ*t*-EW02޷~ #>~']RTRC;OYӅ,UBT|T$z{qb3*i0F =J}3و 䟂<"ʵaj:>ד1Of⼬Os8]ueUmRuTe'o2ՒQKZSb4QbjD1#~xR2+)~=22J(i+jeef6adYKZm6[p'4I4/&sg᭬B!1x*=egw-wb2 FN$]ba /u{rne;G"n8,α  ,j~ea|v 'ݣ9x9F&ȗStc&Ah2acz "LDUu5'6Ci$XS¨RoI L.}H?U1KK\N+cA:z3L\eVl6ư<addFEE`~*++狀Z9$ Cee%[$zp8۩1::0uuuAa0Mxjkk2̧>8~/eqvŜiuu'H0{}~ZygΜfhh ٳg'Ntpa 4qqox׸ >z--[>1x窫/f\t>䓬]6کKQ:ԩS}whlAM/?_O8M:::?>`̞֭=m۶)3;wd``|s;l߾o|uUC!4fI @q%0*fo 9#tVOls"tYTLC7$q'jVayJ{' i5cUo6&[2i< If0fP?EkF'{`#|||z.r{R)"  BIZ̙3۷cVZs=GWW/?wE]1>1gVX ?=' |K_ıX,8q~AW¢E/-?Az/prh"oPUUŒ%K>/~ ֯_:9!I?xZ-|<ֱgn֬%K>1~z)$Io_0{lN=y/ ||VX{?_~hQ'z/]tG:s=? Y1~,1L_`ݺutwwpIkƑ#G#|-}x 46oG$`|󟧫 W\%RrK.FaBrMs`IDfq&nh]E1֛.PWpB C!  جg2gZYn=PK/{N طo˚5kݻwsWr'~Wرc\}455%Y|9?Ofdd!~?K,aժUA/_΂ d||_~ /A:^oVelH|kZ`RA%kػw/[nehhkO~|8>?\y9q#ٴi6l`ǎlݺk"S[[?9N>;XN$>ٵkWZ0qIL}}-'8y$wy' 4pnF^{U lnn7^ۙ;`0SO=źup8 rrK\KcBNo3wSWWͨ2 KQA*-#0v{Ìr㇏пB!Lg| Ka3>ׄ'1uqzEA&i{Z@^{K'fg^v2it.߉ڲjsw~7ŋ9}4MMM̘1Cl6‘#Gx79sɫ {TVVr200`VIq\{n~j Θn1=fiooN͡+hپ?!Y^ihh  |25K%I"ۋ磾^ 3gfKr^y-[M IDAT̚׾U.sŖC~d75_Wټic447mMO{{;GYl9mvzzzW:N~?`ݑd.2ջ׾{*2aIl;͔988=fH!}[HynIA`=;_J8E;zٱ-~_#ø\NC0b տ1qRAK^ȕq`=TWUg.#Lmtq64d?hVWU H2:;CXݱTUVεwD亦*CnMfflOnh}$ 7i^xႎ0reF.s.wr{Ō?ʃb`0LDžaCA($vy+$J]$?8I q:(gϡSlpc~*ʰ 16S?lbpdq?. OY~䇰p:j2)PCFw+Gݭ XR0삕 q~q{,Zp((aXb[^#7@)fpBZWktʵ.L}\eTVV*tTvK ŅXST}WGY'+H%'~q<,^"m)mU/dEZ6ߘX[m.`޼ySGXo2hkk+|1R(?VIűӑU:BPk.[3/# c5EӬ]ʹaZhnfcHU)jށ%ϟ="glee?s)DPg 쪪i( 2I\i":M9)+*_E$jjik_%2̞;zGܹZaB o:`Y: zˤgQڲjɠya>w}'!4͘-o݁I."MvzO+`9@,gJD8d_l*Hz?tIbIBۊb}=XBMӜ:7ek;xf^Nw .srŋBFw0{n?6,%v`mΒv[d7ieTslfm3Y:?*w]w`BPz+F @1+݆o->=ﰨC*vB,8U,h\* Lr+1Y/YȘff50%!칭qцfE.ްI>Y-41rdU3Q'W+$?!n"t Jx13B´1f,n9 N*9o}?m5nv+&sj;LW_Lks-]C Z9~7vK%^aq_gz8&a62`Ռ)|߆`}X,f6 {o6,a`x'6,ʛdTSkM`=]ܢ=˃jP3BU_C83`da68x͏IxL N^6KOF:L%U]d{N~|! ~*J;H8\nGhgB+JTZg`B)kBg%PB ƺ >ož8+[پ].*4[(_Ŝe̎}93%sh8y>;&B8btcT\dM/!̍ϧ#f><t3 &>H3Wͱ7/ ;>^8}~dƗ޲-d"w?D;U9>T1%’ꂋH j߆A106KN V?լvP鎿]]e7X-&޼/Ϋ6/grZͬ_Șˁl+V18<$ɼ2*elp`JQȡ5WnX&!p9oF|Ò\\> ŌngtS]QdbӅ Dqڸbbǐde;ã>32ib|1с,CQ,T (!Q>8Fz덶3$O$> z }T4"hfK+Ƨ_jo`HjCJ>Z>Pj шDZxKhI~FbEu6"ebq8Orp>5W*sul}ֆ%g^7M)>rTyFI%?yET1$!DVJn+c'J΍wzy60 uؖPB Cqj6'پL| ɉFq'6uc{P (YChStcXV Lr4RbgjR H M[TH>}fWޠAj8:C>|uQÖ́y]e}C ׺' ʈK!?O7h%F+曈\qPII$OuXѥ4I}IJIɮM_L;_L;_LL*T"4;CtIC(G>c\V!UEB9Q>FSLB "مMYk RZvjxmY5dО;L-ݤvf~160~ J4Z/c_ U.f<:qrhDY_\abҥbҥ|+_hzӉ/t]Exv.g,7?y fY%)0>z2C]2*4BLq_d*UҤk!)"t2}e AvlT7J։-|w }P'\<*YʑJ<]„Jh?%YM7"> MTB % w+v~uQ/T }R- p5L5=x,!TjPZZ#BcVGpB}*ǯ1]FisVϜg@w|lnz{3Kf'ؑ#HWdqN?F0Lo ^6K !=W>KȖO'߈Rč42T 9!T[KuROF&P1u I_m"@}¨:j "vL^kfA68nJ(aҐkߑOY%DJ*k|le:?Buu$.Mϧ+=6Q (!$At2*ILڐQu,}TbY9r % !0 `Ʉ,ج6RP0jl2GҘᰄjl6SQY,UV+I/IǎaF#oW(B X,f$)d !LY@0D$f3%^Fwx:z0%rCUMubo&ya!J#D%Č$V_,FI D̵N,P\V-Zɪd.']C_zR]er; [o:񅮫|ʚ:h|q4yh2J@_On73&dػGXd۞3g8AΝ9ngdd7pz{6!յ  ᭨cRŢ8t`?,148Dǒ%G0 Ǐeˑ$w7>NEe%-s[9}OnwXd22{~Ʉ`n[7I}&L!|B;mw~czC]Zm}Q17+SM\.\l-*cT0*8ΧR]e󲮒 <3bQod׿+0_CG^N7C[TKFSK(a})KI1#.Y(~:xvy224 -fZz9w4KR?Q᭨`+Yj5Ξ$h= .\COwaڋXY᭬d+Yj5ΜOֶv%]OW7e+WYF˒P^fll?,Kmkyl,Vq"S>5'SyTR?؍TΖ2Gu 1S $МRІhMH{{RgSy9-#o>:]}C QfS'뻎r\?o?egⲟ9x?%7u=üO<3?;o|ށ8'$y]i3D>w2x@M˕PC8|=H]2'A9fŒ^-e1aْ36LxŌ+`Պd2t:Bq:]壠Gdddp8 k8m8q8s[۰l#222LX0OOǃbAe$);fdFLٻgY /5ϸJ2Q56pgT7h'&!ƳLNgT,#BUrF҅.NHHy %ܙ@e:ď~#:D8Cv[Vr;E]~3lU?p-]]eȲ[:筽y%1g׳m&$10<ظۉaoD8,QSYxSP(̂3w~Sda\vF|Ty0M (wq9̞Yo˕JoehdOq|d`xdg̪op#gXL2ݽÄU2lV #!\vn'c~ǰZT1=#a*<.6+C9mT]MaַJ&%~Sy#!4dclx(y52ܗaB4KB y՛y=L;M ;LOwirQjZt{3p8exhHI}i(r:p8̙o"綵᭨wjf"A'Pvz۳n\-LrMPzdU;}Q,!| sfڮ#:v/$\g{x7<~dW%s8=Ȋewv!@eZ!`Yj@ IDATpmx fr0<ꣾKӌJv?q_ٹ3VJYUx_ _ L,j9q*7?O:,foDej*Yȿk4xMl^'ۉ/㢭rsL1|W.ÏB%+ۙT]ҵ[Ov63[h>jA3@U̼f jOGHBA& 4M7N.JSRŻ=^jؽ-$I g̜ɮora2GVWUWÔazŊlmHѬNWsAN?F} N|3'Oξfτ#5/4ad2h^]Is@&<+a#Uax NfΜIKK ^{-O+_ ?}/|S~<#|`ٲeܻロSNf/ロfn:TŤKŤK!xSE;3aڋiL VY<v䯯P8̮'ؼf!Ͻyu즹 gޤpjo9d xu֭hoh!=mh6ϨBlw`u6G+gق&=bS\հ'S]馾 "2k7<就e".{% 9tͤmv=?EtvcٹEfpn'f lE}$v.h^ ltgE[?Ib) =DVLF{KFU`6a4ee|3$13dqV3|UVNk[;xX Vfu7p{˸\u=/[ j3W޾Cl*䥫1) )q9W1T"rq8ܹs\q۷gy[h"jjj0.R{穧{~_v狀~.\W_,}/2A Ia>~?jcL0~lV ã>HQ#2LJˎiKg1gz U]6 )7.sY0Mxʝw%s<ӫ탣e CK:l bԩa2+yW?·?aot:8/^,Vl6+k2hhh̙38p@;k,/_Νw#t ݊4TZĘY6^muxݳ 342%e!n vi:?;Qx~"~ɆG)k: Գ&&YR!ާFLD?UJ6 5ƌ0~ш8 1kV# UZ}~dB' eʖ7 iUZOVoٖu2 lq6./2Lu]?SP;#cWW\.q8XV)++㮻zv;s??ʕ+Yb]]e7X,&֯jG C ذeq9LBmU9eN6cYp,|q9l\{*ʝv6Ϙ/܉f Kn~U;v6++6)w"2cՖh2 *=ebCWSʍ$VV. %֮$E[&6\8YpyA6+6ĬJ]}fV1fWq026+lct̏n]`y"PWb1!*.l3W1<HdʩO/!̝ϧ+Dtj]fT7땻ΝCe,v;vWCW"+daړA>|tlTV*g{zO^4SK ˔5B|x% oX'iX#='HNjȁ?$Bb @2+/HR]PB ;SWdNۗaw/Ln%hq5kq9Ig\k>pt,0ϝ4o$q;:-!5$w}{vsM7sX0,pqiN:ņ  aP[__2Feu^LG>3'4q2'L̮Jr||EtKd넦23=9Kz{Lǂ#wRLr5|ԅQJN(U %}bۜȧ3D?|DtGM+b^hfEtk/:6B$"w96h9)o^VSIhMN>H7Gf^h T|qS5]+UȈW{~{5|6ej0r[TTW㼫oBSEj8[ֻx=UEz6|>eM>SFc&+!LOij>ZQ=2#&w@|*Bë=4Hge×挩 V}{F\u2UXhdYĉI+n7e1C|M _LaJ>;_ ]J~㟹U|5}ϐB#"مMYk RZvjxmY5dО;55LdeuJR/Y9~8en/v-XB]ҩ2]ɑtrM&AXml..^::xxltu%ƢY92n1l~EMUYf cRCHziN4ą6~J~:lV#yFd^|J)F9[Ny޲,39pʪ/(t?VRC;OYӅAĕcN=H YSA33ِarz`td83MOǏ elt!ϝJ/9rZtFd.|LTm-N_(Fëu*Va^@Rt‹/df@P_3'+% &[~"~Ցl4>W B I)O,Rj0Q-I:jI&+ZߥJraD`٨ctw9r\GfdCA>P`0#`_K1>W / oBΝ9C_/6WYY)kG0<$˄A|q~?$n* * hYՠsƻ"G>d|0xC6M%_uǏ~#|:gI{.MD :O{=:6#ýP,8*Crd,yM](k"PcGX_ȪOYӁA&fLsUde1,X5SXz20Z>6`jʍ}f*z x*+gfs $(/wΜ 洶RU[+^a<^s54־W^|bEkk/mc@2^%**+r삕<ֶvNI+?yN|؎c+ڎl.").$@}l3H`OԩSUu,f^>Bk Z2*tF_h8.w_ 4g2⏧w.9rrr7Sr^\}:VwvryB>Fu ߴAjٸF\Fm}HKFjذy c##J\n^MMnW~GGYa#jr i]\j%,/rR<\A%>uxJHDq`PZyRW[nѣ޽[n}\õkWַ'O9p`?=\<_d,(܅:q1^=pw8^_dlbF3n={  e(_4㥏gZXsldr)i\, ##P2,JY >hzʨ2 $>C8)tzWH5)#X{hg~30;;Cnn.$13=J+Eő<QoˤXl[ J=/022$I޹sgNsB7n9OYQXQh H ٙpa ?Naa!555=zj*++SreIZ[[9u]]]l߾Wm6Gww7=r->455 q:I[PPG?|>===|MYYsOm:-0/ū3<6U]t$r\:L}u ;6lUjʋs{/q#zloS\o^Cߵ1z2k\L0lz"#o?Lz37"#?FUa6WO'2G/a A6!! ahE!Ch 7Y ü tWy$L*-/,,btxI|> SyDB0~ 9~V 4c##}zt$)T6!olb;Y{+B{φ[ihnܙrÍRg-&Yo~۷+?ix ߝw#<0ׯ۳$[4eDeee P\\2s7r}aɧ| ֯_7/?#۷HR G?GgʊrjB♤K㙤˂vʹ20}XES86^7%z5wjwo}c\Ul\,3s[eJJ<<8eEL83"֎?0uX\4P ]տě7pжaS?z%RGC7B(S¬ťxJB++9r@|VwV0, 廬vQZ^N0(EzɓlBGƞV`b|sg-&:N 'A6<<̷mvΝ;G?3< y8x O<owqJ.zzz{fggg> = ;w>n''?˿eQʾx2BXf >6ڊ$^\.vg?OyD#''XƼgId:IWZ-7E@ r%IyI>SJa~SslYˁ!j(%&k#G\ܵ]"D @lS^'+obX5S j|1J7.3! Q!d4SDIHvo⯬:"&h|EJVqmQ|o@}CMT-[@Cs3 ͚r갸B$lNI%K'Fڿ?EEEl޼lذO}Sw}tvvկ~~|?~\yx}k<<<쳼|ooyꩧҗ>9yy>_<.%f).GĿm6>?IAκFT8KYZd06m p8ll?~팶qo| syuifq+Y|=s9$.6<秮iFƧnb8r֯u/.Y?㩔u2C0tP əL4''T/('p.IzUf005EݘDc%W'+ ˺ĪD'/0SOr?_?1\.8qB1 w˵k4NƷl“O>bsE._333{7k ^?455E.Fc#cߘd)K E&}ohvz+x@Eilnc ni5c36Tqi`ڨ*+䎛9}ell8V4T!RF2_)!CtC(T7OI2m6lR0oSN;1F5kvB x3I'<]zyT?mYō<[fhYHQr`0H?'O /}'|@ Jkk+;v_2LII ix<)y|';HBh ˗SSSCcc#իkbR$)hc1?22-d"-06A~=_{Rw;n׏Q'^Wkʼ*MHH!P6dHwD97xAhPO JY.lf*fe1*.P|TaY$FJqEm-lڼ%*ay2LOY$)E&ђ+Gxp:<{IѼ1g 察3!L!.~ BF{t].\ynbuVJ>vP?8􌒁i>pj'OŃ1%$d)S  ߏ_{xظqC YR$Ibll fU;R SWheHeR]WRabY&ϑW3I U5aR IDAT W]*|fx*ee2cG?hh.q\9{~v܉wn@ *Шl f m>p07uoXF1VC8䖘͓L^ޭSӒ$ƆLw3"ӎPV%)H0(!HR ma >HT.?P"Hz_xVDU"Am}Xg`S"xaҴX+0sL%L%L%~MqEޥ˔&]~IO)4M+2_tP<%Y&xBD"\[VM2 &'In ,YZ0r8444PPPee&Id:IWx 'j_2KBKB2hr >OEF*6|fffKPRRCu'hJ&ף9gV&JNcQi b4>%3۪Jluo ``i-RrH8"Cra6$$^/N͆U|8`dd'NPDđ#t[GNرI0aە>N$t8΅p Ӆ@}aY9n fᨺ257 \y\=0Chٰ]kI1$[)-T #TLVBtGUIYSPZIƠ|U]I"g $VKdBϻ`В;# ֕uZruؑnz!LOG%"_U(!̫CR>*#=F{-F;IcΡfFH燐ʪjMIiTU/34X=|>W_xe,_KKs ]ev&ƹxk7lgKK[E%;8ne556j %HDgZd*/a#%GQ}ڭr նFHDTXWqJvfگvʗA$)/Ok`j uOZB٥Td)[WiI֕#1ҍg*;À=⋙Wqͅ B!SFPt:kdJ1BFE\aQԲO~>Aυn׬c:.* TVUG$ B'8 cc氢m%@imHQq1׮$bwijiezzUz 'RIE<\]GB9 RqYna ?Ub GucG]Sj/0I~{G"Pw/򧟤:ڳ5|>Ucyˆц %J !!4_ #KEUPV8IojL%4,AҾ$([WiUcKPruRϞ9M5A3:jKw<@A0̻Y_y7윗\ݗˇ ܵ?~zBZعy%yKר,m86~!|;7dȩKz}] 0穭,af];:o29=Ǎk[u"sSRhakH0Cã^Y'*̈^#\Ri8  _0R8xAa!C׮z\Z)`zju6211Ι'yhr9V#IAS]S nc}vzR9!?| 7l(O禛w7V[U{Pg'A&V>>{^sgϰ9^}\axhҲKzihl@ ~S ﹗ejʣy$d.*vy7H/.Pr_OP \ AL%L%LeAqepWuL*󱪹n]7 L庨,+ɞ7Ѿ?ȋ{OPUOӯщiZXIo0׆Yi~\76Ws6|~ffKo,fbz]q9)ٱOC\:#6Feu5v;vi|~ΜHכorκҋ/м|9׿?,raJKxn G6lIJw=ƬhT,FCeĜ,e)KISA^d/mM<~W Aϕ!QWU4s^%zN6ʲb%.?/)(QU^-Zr\LN295˥m:p-ՌMLs\Vse<MP[U_Օ|_9uN~HwLS3sAzG.hx֭7D$8f!L. 12<$]@$n7<=yͩQJ$D c'9.%%Jp<1 'gƫ\=&$wSLp:Ya/n".   ~χœƍLFTU/t7]9)@HeSPe-DK*4P6U֝a=#KF͍A-1ZB@O~_RYYM7۷K.w; _~<}݌GQQ/[R_Bse.=T-dXs"3xùZ\.R U5[0ʵkd)y #y1<< q^ehh[nى;G @UUY?<]pLC_!Flb8zGY#'rtdd.4?1$LXad)W?#~է4n*Ѥ6=ELQuG7?0/"|R9KYZzHwL XcBAHRY?Tʺ~pc? B,;j1p]1uNV;I7Q n(>>;(7\p9XK*?WRZ<.6(3!U'Qm}{,:34EqK?t1JPHr ]F*Շfy|#z|ʕ+};vw^}CƇ>!.]g?Yo>/100ygy;ɝw?y-7F,Ӓ$otSj:'^y3דB0'R+R $E6  O#RR=fDR,\Rv]?W(YKBҶx*ڠu޶Ś)˭⺲Kb\CE*$(JPQ)*6T"0|nEPPle|YJnNhZCe-""W9GU~Nٴ DƯ[÷V"ʁҗĉ'׾@^^'pb| ww<#TUU)Oss37xW,Id:I,(Luuޥ˔&]Jf6C(W^I੔u= I/r\Դ^D~AV zZxBeEoZ -&z/LX-YF&frNDt'"E_Hfը@4 |>\.QA ~z:;;q:tMs1/zyygk_/^䡇$fBWW JC=ę3gؿ??jkk-띥,-)Jؑq,E%0y<`0¬To–ץp`hp˽=(!ͥqrNݤ.,[Zϐ33J-{#Xo#F:,.. Ǔ(B*++ d3Up=Ӎ/ Bm<ϽKNN?{JUU'yd=466ėeV\c=Ç_2lݺg}ayAY:9ILʚ'CJYa4$IX=v '8Dnk%i5Ĩ<]X].*n'fB',F\z)dRk <7v;$AN'fI#(\XGiFVuЪۉd&Da跘x&U*^TRR+ L ?\xtv9"FW-q95Ϗ&p: 9.@0'7ǥ9w1uU/9n'6!}?UC4ۉW. Tv;a8SQx*ee*v;qKsNs! Tˊԧ9зGݯ0T:cci6SAMj+z NQyyKJ(.)z j?G<ȑC8gNhכ\`}_ȡ8zߧǂBtlqpEjT0Nէ_ɾRU&\E9]pDKRӒ+9j,%z0Ov/Ovx W'T_q0 Op(}'9r{>OPtꍫkY GUTG 1`MoNR|1J7.s62!"7K3gSD1 'Y hęmQTDlS]u6ˑ˽=Y r1**B$^.j뙚$//UEqf '៑p9:r4~pp5Q\$g-<)[WYR9sk9 rڹ40KN[b ^w+Wز"Wynqf|t ì304g.|ܸ_>&$+CllĹ+Ou] /v='p8xD@+4Et쥡¼ju1o܈wl#\7Q{2Qv!!G6Ո:~‡DbC0\8F4qʲ2Wn$X)YJJK)DH0;;Cnn.$13=333Á7b!9M}TTUSRZ> ?*XUU=)“)Bd) gGhV]WTRR̊Ɠ-X`l姿ƚ2Z(᧿{׵0423#?M0(sJ8Mgk- S/wNig̊Ɛ˘߽rO|}mWgxt׻1;cEc'_9̆&&fy.|~UG/7̜O'G[ <췈ȴCO܈U\=t\MB&mDﲑ}Gg*6ắD AfZNV IDATXǏS^YInnyDJGKk+nwCC؄`|jں[xxq8Fh5H,P)U5:%[ē:(%t$QoMӭP\Z'/;Zh‘$wFЅLDt2#UդJ^G4/FI{jR@^[7 1E<)ݺ,(.@fٸ{G'S H76W05M'۷S[Y NuPW]8ӳ^&fڄ2Pޫ pŅ\N;E{r40Ba|T34:EGk-@ TRW' ӗшn3,kH_̼Txo?=!i/ o0!n0&RV&ڱU/F^^2}Њ:#Mb!+iLӢS<%M4w#V`JBDU3s(Ɨ ֦*~>&gjֵY^_AeY![.sMMw .Ng v^XWRW]}LMvg8c~ax*e]/ - ڗh:av;?. 622i*%? Z 6ce6#b} &Ug2CAHJ]ض`رuqZhZq6#  +pXƼEyS)z56r;^#[PwyH-/zŬ; ޱӅ7*W,Z2.nE _Or ?xBޙuwL1zXO_\D#]+Ƨ_/$ 3Fo* ̇:g]O2*:y/Cr—lO٧P7bϞ)㏡0N8ΑÇyT%U_o?_RǞDwFom'@:?C ]"ChI֕#LmBPSFIѼxq+^H.oąjEX ^7^0<ap3 (,yKFu! ߔBؓ8LtEQ; lc:fJ[MHPȓ@A| %9Y'er]%'[طwyp\>ů~s;:kh^cǎ>kvqYΟ?NJ{;to x;mٰq#/(?C QQY';VZ}1pu>r9 v>'=~d#sQ!yCF?{YZD-bf\p#ޅpGk`\G= H:g-F1ߒшb 0uq-n:Yzna2|,zew^MZpMu Zc%\M/R'RƳb̒I0=9sg[Ё}>~w{trU\]>'O>ěo:?ٓ  q ?{ ;oJM۶3c׹뮷αe9~~Ǔϝww6}OEF*6||MLOO355şٟ},ȑ#|qF~HAADKT$)(ILMM?$Nt2?(?{'q&'q8446RTXnsP\T())eu{;no~t?}NKzioom|+_f)+/'77ǿ>??K/?Srrr}<>"#!\RT2_d]~S)z8I-7~餦0ϞyEX˗vuECS3y̞ 3<4H^(ghni57|cXlWxMC|_^ԩSs/ss~_Ë/Wسgz+?!;wdǎYC-H0 !lJSTB4w/z|mԑ/t/D\M(au? uXR2Qc(m䔘Hr&r9!WLr~ZBS<&[Tꛮ g/Y&~33=Cqi B{'JF|7SI&|dY}+U C\FGͥ¢"Μ<z΂Ass$`0s~%N|>/@׋$~i z|>*q)levuҲ2OOr+D.O&1DU_#PfjʮshC1 B ʯh+(r59P TjL;( 8A I^'g Ssqtz;x#s^YR˗,gĕ3R k>"TԿo:pt8#oPRSRNqPݪB]Qi7.y(*.ƝsHH\zK=z}8oµΜ:INN9j`llǎrq簼ugObUG'}\-[ p$&okWTPZ^@swNr <t: 0ΞI:REJJ((*쩓LOMRDMm=G[ogtd 7Pl=^@Nn.\Ӹx_;7?s/aroill`՜<z9|rE͛ٶm=?0uz-kWAv!-4 DU0y W}ێ7^e:>EfВ+GO|C〠 ۶O;o_ڕu _og N"0;wPg)3!xa qCXxIg ucgrl.D#xuU.?w}/!IUIvYމdݡ7SҶY4NU}8N<&'a3;3 ?{՝'z ph\eڵ;ܥ|p::;ҥ1etҹn=^72)py:֮#(I?EղeRZ^N}c33 ;Ca=/ԧ>9z(ׯppY,ɹp]]]tttEoo/VZ`32ub+0:*jI`ŒM!힕/¤y fNU:yB"Es5PxlG޵zq9팎OqmxY/d|razz'wNs3Ev<JKE庘`ko.ffh[ xY g&E!~Ogg'Ŭ]Eyy9k׮UVSZZʷ-9x-ZrK̒$I'@3T :r4 yfHz/RǭVDMB+HYf[ 4R!zϬRA6j\]YvoǑ$J<8vǦ:4A}u)9VӸrciƳ~S)pp×jy^ǰ&4K0#Rx՘iv]f|bJ;.rrs MBxsQSؼWVRđC$/ÕKRPXĹ3(+nafz`0Hnn^^O>S~`.;k<@[;LNL0;;CEeоv-Q&FYށ7himCYNn*L-n x$(雒$Q__W(?AT9>>y> w]z$!\tmmednvpYx^Jʣ:$34"< Zp//:KA[[6[jP}$YP*TH~/0 NLh)"aЄ?ႥBfZr#]/SPp;(eϿL)KYRb 0$I>vglrMlYϟ= {Np<4sGrӺnX݀á$,]LHBR)U!oq #?`+(U|Td\vbnv64  H8u v܉wn@ *rLDԞWBsb~B=`QIzUf02.tfů xx [yIg1IcH6O~x) L)KYR&? rD.z m/o]Gq/޷mWpRgkyѥqX G+@੔u=J$R"O'b{U1Fςrm6 x3I'<]zyT^0}~X%ڲe%[57@Uyrr2Y%Zօ-R2._L___ʖ&K6Z***,1zSiw˷zRIٺNK, 6Z睅&qz/rEB %C:>tQ~cAG^6vRR$)Fp"bD!$P,Z;`qj|> (zS }?Wd+H,JAC9tl2lBhڊF >[aȧGb;SGhINl,]llKnĻPchmKY?x*e]?x$eP a\SHфD_ oyuF.N4Meh+v^'}~i&>c,IvKV1E'''1yΜ=q˭ _rK2nue\]Ś <.θR:~F+zgnDF~lN ee:! PJCGfYĤ#h@:\ G7 I -b2% .mYp=Y]X%I Dt], nTtUS [`c6u[gERD5wwp к>I@ { IDATt2$]2$]0.RӤKa JYKFQeµ4q} *4;*5t?e|TO$nkjq5(Upn'~73.4|gΞ _"S4445R"g」$OU9 XHA $GZtd~e[{|,Kdd%h3vbsٝٝg:w?:UUWuW٩93U~wU]uuïsjśUrx3bvL7.s|cm%ֶ*O_,bxAr~3˪5q">2h!L;io=sDBV; '\u!6_ uSN_UR?r^-Z}IR#/_J&~*$U$^^a|[emVU>O mQ^_qP";FGO~/p8lyɂ LboMc(O.<;Xٯ-'$Vla2!TxV²/K/$IUWp۽jsl2r8AV=nf1M`a=6M[y%,-Le5ή"O#3 F4ɝ*[Uj #*jKuVեZ(k\EIkkkwTnF,X}QQjRM^Rx9*U&o!Idz3' ZqѸF)Ra #@ǡ`_m^ZrfAC&@(D.gB4m9m<;j$l$$k I@'%D!un9D$Ib=$H[%_;(׮(#mE ^JJoܜR^>/n)Ms\bZ}P|p\|(ZBE9RKϊCX=&,[Ƕ'II"K$pmc%$h$O5 pvúlEފCXo!$el J!&frdyԭY֩31ռ~J$r8εG$zNszvKwlt 8l!.^Ep=FO?L,cs|ѻbrkkkm /8l~,`1^+ɤrdja=ԟC(h5H&+a)H%@Ǔ<3BRF>6M?H".w-`v-!Icc~=b8 .ZǷ/.g,dS|"zV\}䮿$'z+SӿI:U:*F"=zp8\0o<n!f&ewleVGt")΁v;y,֙q>y zڛH$%;$OESOQ{w姎e8V­ģ:n!4AϧkUlPgC<-lSMrs7 ,'sಥu~'_zw6m捛kyfI*mV׫k҂Zg Mm8!DmFS \1cSa2pf켙t1;o&]jPn%E86 jUV1.vՂFJ7W.[.VܶKy=UFG8Uť|GWG:IHʍ&|xz'fy{|bW߾Gv40)ѫVfb$2=v_=܁)r|2H+.vKerRԱcmuy9 }SӲmՎCt:יE:df048X5O%aҹfիL7.fͤK-T&t6Ʃ1=4hmf4>Ífm#p:\@;{pȐ.[V/vd+7Zq5f>+] !Ba0Wɪ5#0d$#^@~\_&%uq*HL<\6-I^[kH !$·MRs -@`)Wkp1$^ d B%^F_Y$^fdlvY(Պ䶙:TWסR[Tj)9`QfeF,X`,hZm6>o\Evv5LKcϿv6쥫<IpMB^=M\t5/OAx^>0hHۊd]&/<5px Zqs#T'`!nx)8B*J.#s2s=m-;NctoϦdiY8z!,ft̨ , G<ϟ@p5旂2_{Mr?z 7N-0sibK98H{A]@5yJTV2ͧC%|*_BMˬ 霗j:_؆)RHlT{N|B|'׳'R~:A8eڢZ!,%m8+ampׯleVﳂX-"ܵcUin)%-[q83/&\gLPUrdyԭY֩ !$ݸO̤Y k/gd,Ң|Yl͆4vvkleVz c^loR~^Ve|1z|F7V*pTJ~qr<1/ZAɗ-as/'NLh[=3H9IuW2k)^#Q3^6۲V&,[Y`"l喱jNR7Wȕuۭ ZΡnB 2mI?N`!Ԭ^YSNmhB$DD2N" Oljcc1}^jnI`8,[ǶUQbmlmbe[f`!ܸfoTGTxB@*MUZ(ͫz)~Q5BT[=ZpT|:)\0g1UJA'kI|%*xV'!GJID>1#zmV9V­ÛIf켙tE¢|)y;_#]8m%|5eWz BA$ɤΘi!5))%4m2ΗTeW\4 s7++w Y8/:fիL7.fͤK-K :xG0w󵶕v~P0bxA͢Z76ED"dX j! zt+Fx4z *jӼQ7%oRq8u`PwGcf7Y+a|5e$z=sEZZ[RӈIk!IֶV ɠaKiR ,/NgW Z>|=J sx<ނ //-LJ4hPFrkGFx-6R/!xQ' ,X([um+U&o!,m|>&%gنGcSSH&C 'c&rP$W.^'tb1N'hAH$8l6[ߚ C" c۱lH$Dp=HD2$/AR1DE7M^bV~#'dv ys_n]77+WMX2mg ZʊCX9_MYf%I"p8W1wf!"D"LPUrdy}+H//^y>NX0 ޚfHC##4rX]}r)8̭[446rm*kzGX]Ys4R'hnm׏F0`tnZۙ6<(b9xaʫ?exA^oAlz|WA\'Q֗b| ͆4vvkleVz vF浥WZ58U+F`K.p8E1ks8X&8(Uj; M-,ϳJߟ3u:{bL!^}uf2~?ݽڻevߍ ٱklǮdiqx<2Issk0G}(gLzjy*Z*ʫFxb[6ʄe+ ,T26[Zzn/^+ɊCh—ԖH H9B=I0"n!t(-VJ^'1)e@Pl zKեHx+@gP#]J|?5{XTyM j v3$}Q;X_R9jwA͎ݷw;zt MbQ| Z5aijn7dyy A W垫1iR Xqkk0^ͤy3bvLԂϾtxAkKnwֶ[E@{X^3:7y%YVyv ٙlN'NOZj>T.lzz~z4E$/beL&,-EYQy|^' $DD|Wa!UW4N,XPO]P{NMI w?kG˟')Ͼ|g,2C~//=׿ ǿ_&& tɶ3i+/h[c>LpefO\Q;Lrsn.Z),3sEelgDEemVªo$sQ aKUX$ւAB wvWУZF] ]Rft̨ , gw.W<$\ώn?z7nz/~/D^?͙9#9(} Ʀ#{u[[ƭyu>{iioN:Y7‘;vt:[WNܱ+Kx8u=|ζ&oчiǯ|:}ufhmz>[v+)k+|i ml!˾ Y .c`? ]URyFx/cU*VBCy77۽~Մe+v*nvsc]Nܱwo_3W7|ŗ(x\Nbq? <c+|!DnN/O?/ŗ,uB(˫k_Iz87nP_G`m_|?FۓOqt?>=wrD$Z ~~UgyxՓ~E38v|^7M>pvli!,֎擵) CJk 9:zB/y||/=}uB"|V܈Z`P/TJa8 4<`bj_z:wܑu`9n_5˞>{N~_|V_^}|:J{q7ɬ-9&&g?K+AC?.w#}|=v9ß?qyX+R`i%Hï~ v{Oˤem+ayٰE-WofYĄCFq&(acЉ+SoZ(^Ba^O UB6]\REw%tf1>s8u(٪r meJxa[6ʄe+ ,T w|%߽ǕQ~^&&gyn|O? K+w3SV~sSp؈ڊpMN!AagWBDB|γP^\C=G"L$I$bl~A_._y{6Um9&xz7Q|5em8!#!8Ue_auPz! *ʦq]XCSN:V//謹_I7:m 3qX2mg"~Qy;o_z $N˗#g?K+AoN'dPoS~|C>v vt:=Lߋ頩ѯ19;}wR=Sh۰mbu>[x{}m"Mݣ||xm4`^8~'N]ʖ__>!V|9Eޣۤt١5Ix-h!2^[*2;bsJ]dH&TP:*(|!aߥRDGLK-7{@P/@EK1g(/뚽:Ujj`mV9x-:XM͸'̠y3bvLl(~g@ښs(hVN_< 课 >~3{/={wsϡ /g_ORGe={?˯s]ܘ͹\U{^{&O g?S*>~Qv s{^cr _9{?opkvziUyb"rTNZi!a䮊,j20vbzӉWWoкo9a= z 3ʵmbS3` C7 sfE"ddƮOsߝYݵX<Չix<.r9H&%'[XL# IDATDQ$319*.>n.r =Yf9r.BMH$ngbj.o9zطƆ:k!ݘfm=L}C=8a ܚ]3:Mם ]0~ P8wŹtm:>ݣ}8v])`VLЂ<섅 ;!bj::89Ȩ@^J9[)ۦ!4p.3d `$&v+vMP\ՔefފCmQZ_w\T Lݸᠻ&&ށҐZ0H0#73n᫫a\ 9;=Z0H<g=0-zcĢ1Z%*ӺEbKk3:^fɂ JF5 v2&1Q2y+ae|5em>8bH.X1T"kՅysU童I:2d]=]%`ey C \p&I$HHc1dj?'FI$8N|uuU"(p8◔jD"U\xƦf</Ο' &'DabRN~4B$F94^ֶT$X4G$b1RJX4G"N,MO&"%$7XY^rz:%r:IruY.(˸ WbckRLތ&nUc٪w$Hg6WׯR^-WتBYRnIgzykl[ ?ʫIWgɜBG4T,zS jl^F\ncW;(ׯr{҅ttuseB5Fvt:ad.^zY: Vw~p(墻ެ>׮^edN[Z$ ݎH_cyq(6W^x.UG wyGY]^ 8nMN~sehd y=]Lh}fuuLNLFؽ'.#IZ_cpxkc,-,rsC0J=Xq7/m6XqkTJa8Z[YU31V*:\)J,. өu^Qq8ܚdee%A2$Hp8p],gxH&dt7Bv+WnZ-Lm`8,[ǶU3*i3*SEYfPJB1' u~[W APmU5(rmb޹a@6<# X],BŢy= 2vLa|*Hp(ĥ Y]Yf!l07:Ye>@@h$Z_g}mp(,;-u vz1ɜ'Dlv;X,5YDkVv+)~]xi:gW/YR'fk#y3bvL7. }!iV=qїU+~-旟AlPJmN/xPr'%c ^Քx52 gHB ߷ASbdS|~olcs Μban.X54o{dЗ@`+ڭP%|8)Û_AΜn#It9]\L,IKc|0_d|jdp'Ao߼?zK3x6Ƨsw?WN35Dg ?x΍}n_k M\'?xU^y2k#1Whqkn LZnC(;][qK)̼0 I&h]3 )MxE"Fat" PF/^ zh42(2cjZ:lѥ-JM_*_C2N,XP2 (T))I`=5_S(hv:o'Kzx߽\Ak(z>rd7 o] ׅHI5ѣwq,O^}Gt )f Zi^ h1fgWWSVdmB_*+LCW^ >ey+=iy:L YYr#j>{@JyAuE)֗b| j,6 ۩²qlK[5JFyˑKnG9sqlU9uвm%-ke² *B`E;4Bkc*Tk#wXG/Poxܴ'w;:DV!xsK>]8vF۳e=;mCf<|O' |O3N1BK ].OG/in}^l"?&nTcz{\^+FxUGN+>Ij2^^}eMT._MY[/- Xq5dR@r%մN-m$rTl8Os3qX2mgBt:)I|E~r9Y g-ctg_=Hy>;s 9SbڊCh!J!*Cwv!RA騠k z|PCL3Zp/Y㵸Z$ca660.fͤy3鲡y,/_ Rk~-آb~3˒y@x)G5^BIuRΫQB+ᆢ:O]հ`VY8/:fիL7.fͤK-lG7[ulVVYVyP~D*m,9,k;Ud0_5'G\h`8LRJkmԹ8l[ՆsQ' ,X( 64|*u󛬋"G޵n,_BWo!,dn[ b=aq-%:̮kwoJ2N,XP2'C5 rT*LފCX_MY[W8R:",mF|nY[J+6UWu T`Z׸R[M/ij)ick5co6WMX2mg+ID:C |cm%ֶVWSVdmB2 #cSRJH&NÜxs8x ˵!Ivdp l6x2b@ٰNNuc[J!bU VG y+ay6̲j̓#Yis37{T"-Ik_Y|u>>ۿ۴IH&$tIJFc[6ʄe+ ,TRjn6žVV^A'Frtq*Xs7/;%Da=rUǴ>0T^Bƭ_ʗw[|[_gvv^~e9'_5:mm0ǎq>'/їصk;ɳ?oo/~CU68d2;tقòqX2mgBt:)ILZR#Dښ8vfWio/x.*H$BGDxμu^4yYXXf ʯEڊCX9BkSEYfPP>0O*:BTwy hY@BJ}AAʦZڸ:v|+l6Oݻv3vmrq߽o|,-/qI89=˗8{,7&o/{nZ[Z~_|U p9{9z!zecHͿ*ꛫԴ62iLfm+{%iIH$ܚ[QѿdBymuPtyfSכZKzp͠Ff켙t1;o&]6}Зcwۗso Gb|rtW޺O8k?zHo4>M4/*&|5%/3^^3𠼇NJrsՔx52 g!D?<_ʗ~oWǮr%{ C3OD"o&mm,.-2?׿uQ ?& r%XZY "K£{Q5`Nb1$Sw~P4;`w#B!hͻY8/:fիL7.fͤK-lG~>f|ldv1Ro=6G3:x=.{>B(}ӗ'n艋%/7r |!E5D}=/~np՜6ft̨ , NE<`yu \1[ptgkxN6c$]\fjfz [Ls\3aH(K THKc=LN/dIpmܱ:;F_W3>bRx+U&o!ge:#iRC )bfu3'3- R~;+ _K_&çȕ+  #I {{xG8 w3[?y!^=*^;Fcc#^EkK+ "!)dexL=2sf.H)|uY&_H>zc`_<O2R=RlJo3y3ڪWMX2mg+I' VqsZr}ŮN^wz<ɥk3tYXe5f%;ϯ%x=l^q96<4qpg/$I&"J 7UExjoęky|im]}"'\C.ښz}֦:*ýLZԅܵg~;v(Z;;x ;8v>Aڛk3M{]C]\C}n4{i }$%13CŎfVxw駱ޛ*4ԽeV*meՊWJ>d/4;=؜N^_z7 Fʪ>Ւr:ik-175Z` } q=1ssXǖhml{{` .dh+X Hů5$725H_{ѼDə%ښ5/+T0>}nzNFW鎎tefLv_xerJcWffnO&ܜ˫)|pisp8RS9qb--- $hjխN|^9Z n' #H D"I$ߦ,l,e+v*8J6~굉=8V­ċ(92`B6/fS5BT[=jfZ<' lH˃f/XêR~)!Nz+̠ff켙t1;o&]6 { i{3Gk~-آb~3˒u6,B;_ӐUNJƏPF,3v5ڈhCm`!6GհppPϨC͚gƨ1K IDATyyI7^f켙t1;o&]jg[>r|cm%ֶV̲j~9DD2g!qM.]ɥZ؎Ȯ*%W+M&s?_m4C)L%H-hTRFyT[N*R3n^Ft/h2 |)_IJ=$[R+ӒW׭j~fLYdlU ^mQ /͆4vvkleVZm,W$&K٪Pfl[nm:U\99rTofYE| ɃC(NPxƲe*m镩7|gXI} OaSHb|ܐ>7,T(j< 7\AUxͺ[ MmX+,XPJy˻ټj;w#TLT1/l V_)_MY[/-2;%DaXo:(KGB[F$V-áϜ9qfN_:V7`N$~m}D<՗sԆ9ϝULp3n1;3G$3be':y>RF<U/tJ5-ĶbfXv7VƱle'[ 09j0*i-&(YE6Lfm kFI8lZqLV/!_iTwV&fgE#}`˗.NRJ2}&P.^ s1¡M76Bopۇ(L\rQЈ 8kkk@,nL$qI(`MD2͈465S؈(,.,FX ށAs3Ӭ΍qnMM<.f0 ssHgW7KKNb~nfM¡]x}>cw "Y _S4)ѸW+at^f켙tE¤$q$g.O"n߷F_Z(:|Yo-rܻgCׄ.VܶΫQt ىVZ2fY\X!H`w8y#;w1;}d"gff4m,-,z3s]==\tvcٲ宮,㯯GEVWWxܜ$ň{n\rSؕ˴u l?s<4573?;"=}}_J]EQ@`ezX$|uu8.'W.^d}imo'ĵ1:{Ο9Mos3VVp{<\g=Lbmmή..__߀%A*j\ +f͇3㼼ZϤzÛIf켙t})Ȳ%IN^CW[g.OƙZGO8ԇ艋8vfWo40}x-$]FX__'S3y}Qq=ܜwrSEJ7c۱lL]$ILݸ(C\ã `xt"0f}zsqO݁due;hlnxp\tt8x]45Z0HcSN:?+ofz^ m?8H_ .|kuՂ7)8ˌ:Y`BYx%0}͸p.;ܜ]r_ o_b|rfWy\Q.Ogf@'uIfy]G zg7^}w 3ʹ7uu-VS񛬋Mc=<5pc*ʕ3G+ ThQ&N|#2t ,/q=G8|>fS?<)I 2a&p8,-.tbQVY ͖0PF ÄC[E6AH]%6]#6nr|9K ( I\|7X׶k&!dL{lŽ\/*] _LoRq3d %C Dc @8#jfpAp:6ل$( w Iʹ5م馥ۙ}>AnZ2;: &R.RYe_NB9KՔl>)(jXqI5]=YK $q&  p= @cc3]ݴwgqcbV:{ G$y::ع{/;w糧9Zڙ4HD(t5qkjN\n7@TpX,8ua~s'_$KCC+K,/-xN2|W.(īw*Tи~Vv_5a8 6;;yS3KVhpM9?Aog d?BˇbnY- Rj ٲB(WoqǞ~>7Q=_=["o!,֎9VB5Қeq.uzA5Tߡj[6ZZz"sܳ ށVJ{gˍDSK =46C\zTEQ+{A%Bh}Թ-mY笭3z{GgmrNls _]>_n}=/]fۋW'OFF~:_: qcbMp\HH4`w8+ayfVt;a;յRX2mi+6] wg-cgj:7X\Y3ߎ堫Fu:?r:joLJ>ȻsmjL;M{K=D(eh7;( ܽPWW8U !,R~3˪5P;!H  E+XWDUw׵E]wۦ?+ +i!zBHL~L |>ɛν{n;s5蛥z@Ѡ Y ۆu)T 11fV?.fO M ӑqB{"N]"7鷌=DQqqq\.N'a >kB/B/ˏՒBxxo= .e8vyIn.w3;?%U[Ej;d]/%@eejڿnEll x< xO|$;I ]/S⺄I4IN>.w.C 3|we:S!_,@1y{kBKr9ʪ_'*[g\ h?|d7A{'^Ԏ|Agv&t s i]ΠF{!>d$*S ,MHz:an2S#{aG轇9r/'^{A *"Gt>ntx齇y1KIrǷ{/wINm95$.%kP4 Ox-/p㾼+_9r/'^.w ^ga߷z!t2H]Y뽇W]% [4.%ܙl.%p2ˑ^^orDZ{/%-{ Cx)I ;h2'uu5)3~>]_RWq\ >}|,.)\{{.Gzz..X~)h] ^.%J]ѿXZ{/%?\==¶RpMh~WB==K+.b["i]r|],"u{0蝼wfO&]XA;پyw'6Ir'})Sw B>o?;`wx/wWŅo!;?O^,ʪ_)t݀w]K{!!.72M`ũN1gOto2*{xftSnhp;c;|$Ѳ.\Otm >5/]|:kozLRY+^^(.5r{o1˖@t{./|vл=BBVH/t B؍ɶzᒁCKM"*wW.y˗K+JCV.Yx|ɷ)QDTvQ/AE< ti rە$DHӧ=GI{z?zt/B/t P( 7I Q fzuåR2[&=( on)+tb3Ed")) Qج6{^^^denR)9w( TjBBBh< $lf#!Zͭ.Qdger9qݘL( 45(a2ͯj|C֯^1R(z0ZhԨjo5HhZڗnGPPWWGddvLAxx8.͊(zd+Q$cW{ӲHjZB!܌NCCYYY]] 4tׄ:d +>竧οz==൫]VKlLtesy|ѣde0p8HJJ?襓eűJCP_F/'^wق` ==y}BQ(ޅ(@P(b1c?WעUk(ݴ&O>DEEܬGڔTNxx8I#r޽'ٱ7WkFdؠTt:-j\zVkR ] pbîjՑp}n\.vÁfce_^.juh4j N%Itfn#I'OLrr8|v|‚'gG9&nW8t: CXۍZ!&&ɓX/'nPQQH=@Av8eˈbbhhh@R-Ng $$R `> O>ijd+ ܬ'))łF%}ODYr%8t0--F9;B;wzd˖#6l(f͌>C$?nʩS O?eAXW^Ett6E˖-g֭l6P*U7bJ!::(n7\Jll, JJJ=t=UUU={Nx:MwOCYn"(3g9q$))m{ >_#Ow߾^VLf ׯ]r=.IvbT"5%АyGذa#qqqg}ӧ}m*}w1U R]7PZ9sBBB$XvKKZdff"[uDGGE˲ ]ee>t lJNN6-F#rs z0aJ"E#|t)55HOK>|e˖ajm%;+G.Ғe xʿoߎJ""";孳׮#9%Zv#zD.8J֗R\<ʆغe+qqqDEE YPPD4 gϞhjC`X$yT`niM܂FRS9BIJJhlPVVk@Q^d#9( <ϫ(Jpii)j 6o>̊ٵkNV^l&##/A?mضmv"77Jݻ)))ɨjXs֭[Ǚ3gHOOGRu]VH5466z-j$IB7ł(@j88"T:J% zmo!}JFwVO/V.eJ6dee}̙$&&IW2xh4Ν%22k!taZb1mW_ÇѨyX='===vc5*MHdskү8k*մG8lَ[r_u=F:zKnzDQDrbbbՊZFb߭_ml6#I\zf @KV3zI'yZ[ 0Lm5f6]ᗝ`th[b4Zı vލ`lbpܸFCKjl㷶< ~:zWi||,'t+JSq81Zh5 ^Zzd ZZp|2ɲ5`0ހd,S쯧r~ (>|#G%J@)mK )l[ VEd1ݻXT6m[_@b}8jt. QVVC9r$;vdD}}=EETVVҦuZ 0=3|gxݙlZ-9999u43o #фIj!RP&_=FGMi΋ԩS,_477Sałw/_^?7}ggn& -ёozoomm>|y ?D IDAT.;wr Z[Dkk=n4j;o;Zr6Y6?raZTO,+V5"##innYOssbP}^xITTTp!s̙M]i-ͮe;xp:ۆo=[ 89FeiabtNJ4aauPOo]{IM 7hE ,_ī,E멩fժQ\\ÇZ-<uxX,w6Fp8 x'N_aÆHOӹ+xLhh(3oT-[Fuu5~̙3'h=?Ozy}םw"|)gϜA$jn";wdu@BBӦNe˖-߿sםwsWWxWf#Fɓ'1X,nAQs)l$$&PPPng-}0ÆkiSqTUU1dѢE5j |)scFttRN:(I3I&…\}U $z)^}~iSZ ̟Ozzx2dϘOMD9B6o̪իrB]}=!!!ܻ`vkr9#F0k,x L&{50}4v;0[P(zcΝYVlaȐ!̽6l;qe̟?E͹s~ (..bԧsΕ-,=8ft:-w)M?CNBBBp !^ ADD8j@7Zocv5eyǝddظaECvq9degs#i?Nߣt{Po>C*UbY9"(tyۗӧOk1|N:Ŗ-[dxVa:$GSLd,T8u4Ç %K@7zM|G4Ef͚ňÂxjk2>|!0l0222Y3ob̵\{[xNǍg„WVU /?/ưC1cZ&@Xh(W_s grY^|%sj?s 6ÆsN8AMM CX|9nrK;xG)4s$''pu2e26mbϞt̛7hI DSS3FE @>}8<QXX@hh7`4q\'-- IDGGSTTDmm-e5 8}b4 瓖妲s  jN %.K?!++$ ]C/zy=q#WÙ$ǭ{(Hǭ{Hn!Em?lRyR=EKNɮj0P{2veP)Uډ 1fI T*&MD߾}/ٹswuw=3 eҤ@PǏհ;#E 2x$(RS]MjJ (P_OaaѺ\.m󉉉ŋ9qV:t(7|3w.!(qqqyɻ~'pr2kLͤ1yd+`… UcӐ?ǃw+U*9lټ>nEEt:ZM&233Ȗ(bbbx73gX3= @hh(g&++={bxGijjwa@/X;v,#`Y>N:ER>~z222h|ZΝKnn.|!ͼs歷ޢwy''Ooy#3f [o1|X{$"U(^y啌=ݻYr%= II3JKKٹs'3f 22|3qqq,^ӧOc9s,?~A7l" [ZH5'OYx1?^'::kL_d|@f̘Acc#/"ӧO#<" !,,~z~znvrs]^]wo 8[P__ϜoBBB9xٷo&L AB),(`ƍlܸ; yÁSOrY;WCwiiij??3gDRR@ 1!_ώ?NŠ+OVY)..ka},[~fwSUUEIIwB0w\l{… HJJ>DGS}~cys8pUcټy3Υq ʕ+1}:Hg=pY|!#vty0ͤF_UUITt۷mGѐAii)ÆɃdjj*> ϟ睅 Mx,XaѢEzd+"U'?!=-Vƌo&*233iiiW^a\;nGb`Y1Xf 3oj*vŔɓOڢJ'22'|gyF7LB||Mc!:'L`hz:V IX,8\.$OgK7"##ٵsn혐Ce#+ /?!FCTVTUoA^xW((((@ՐρAxpݜyL&-F#W]u JLL N͛9~Bcc# 8N4 t!!""(,,6t:~pAv1qDt:6 FfC=%kƍijj7 ;;hZGu틴ˣv`;hGP`)**fF||}C`]3gpys…xyJ[zDoݗWS>É;]z5?f)))aĈOOXXYYvo===x$ŬUŶ*o?7 E_|Aѣ?N:xgΦMJb{U}RI߾qHH*XeWV?M&Z[[ZTDhhqx1sn{[GXX}`2bJƎCfFgϲcJJJx<444?O ̮]p\ݷ} }nJAh5r,))餌d(֧v:5M>t[{IgV)=Cq©9ZvԒ3AH")PװݸNΟb`6eq̹q,WIС5ӧO\ (*n"ͯR"**ynfr >` ?3fZ'&&ƻSubb9u4V,N:j%99%H&PRR¤In0tzMYnbbbIIIE`444ϒHXX^OIR5j4w=Kl2rr>a %Iნ9}Sn&N[>rykE|v{p8~>###[H|Q( RĄ ꫭ A2zOJ r:u#G Rk֬aƌILLpk׮0Q*U[֞hi1O~ %o&ϟG͍7ވB!_)`ڨ]ypLDQ'~#cFرWK/ĉבԩP*LF˙;w!^계 "}$Jbb(M6rDss 2m4cwQ)Th5ڀ/?Waxg%oGۙp/cg!Ijs#Dќwb :hFDN/11&z6 D- Q(`ewjor>bll7/VNIKhss#9s搟W۶uV@".>屧$c!##ӧO':*_&rq>l'5+k qqTT:JV|O<ɡC:[}yg !6.tJׯ$.͛8a;vd@P H^^;v?Wf2&]72s:w3b=_ n=BcirY0L[[i5$ 6&T>SѨdwY#F 55iӧs 7 "'$*Sx)(VjIDAT( --ݻ˹+ػo߻@ry5z_" pAƏOMMM(O ''Iطo/ǏW1kl9BllL,K ޶ٖnk0ٱCE+jZPMZ-j<|Cc͚c~DFFzˡ-- @DDW7$Iܹsl%JZE||>!>>+l߱=~mpIJgN 6'4f~1zW{͜ğct5eQ`TšVh}]b|XZ!uTWt4\Jݢ #zDxL%_.d”Q-h4jۙqTܒ$S|WC0˙{лη:J$,t<}U-yDZMFXzZMfjC8tp?Cq|u3EC;BoVA+s~iyE9z'~8$裏HHLGȃػwT*:D~~+u\}5׿pAn߾T*rss_JxDΥOR1<Ķ[v #<3jf\l3Yp!9Ò%q!zAJJJ8QvǛ0~<pݤ1o<9r$6qǓLhh(v◿ hZJE1|)"""TWChvټ%ĠH"//Ç~JfiT*FE(]̠+v|ry8ty^HL:͌3fv)..8l~ʯ?)B y7q9] 6JUW}ڵӟ(9jbbb)u:55ycNKOv4h`nS!#3M_~O?CRrVYJ鷦L:Exj$رcyMF23-_GttR+V1lvuP[n%KHӇp>(*BEEO> ~kĉm=ͷޢ|~c ajme{@dd)Syŗ(--%5%fbٿyٰa#!:?:{ohɧBRqw̫Jbuױd[HII_^?⋄1w\)4 ̟7 7xO>+&cǎdQ*3!CvTTT/Ixx׋Ab̙;^ȈLɓ'@0s ӦM$n~-"""T*]w8f?Iزu K,OR.XI^chZzNeo>RSIKK嫯B˺N;Z}->zE%=h4*Ζ`cS2&[njHTY4}_u󃦝\m92]2Hr@$7n#GװcGY'Bu͖-y뭷CzAdHKK###^xA&N$""lDFFpBMՃDQɓ 6F֯_۷d"?Dӿ_/ V}nZ233yG;ϢE q^W׎ ;LA8~ {Z;u\4N>4bxON#1172i$֮]G~y.b}l6J%&L_g$$$tٙ]WP\<$&Lș3;ec* Zhnnr9v:^Odd$ ۷S[[l&**TV(--픟,n<6m"jFaذa;!ٳJESS.s8BkS=- NCӑƉ'X,5DZ3"rcߢՉܕ[Ԃ ct7y!3~Fh]ls;(~&CsOtucr,kxfW oiQq?k~̐A<66LeⒺ?rWs:C ^}.- 6 G}=!4c3P{>~Yf?~{䮳𝹄$\giwҤ.ҖcZ3f, Qill$66M6qu7O[>~Ohߗ/_AAA},.B޻ēO_*͟o?Wn࿥NDDD҃<ЃMn݇$o_iϽWQtߦ6l$y7od sejjJ%SNGVqzoo7>Pٵ~?0\|Ao)L&߂RɇHV[^cwp:bp^u˂"&6ѯ(D I_EO% {(q էU[ $DH(KY>}ŁJfo( r8`U Nuȵ]`_ځ~KK ˗ >믿pF#>'NgȑԜGA$y&O̘1cعsw\>W^ᡇ_!&inÿ/~cٿ?YY̙sSkss3oJ^Ϝ9s(**LFq|gclcpl&[RBHQ"AJҧ>P*IB`>-5R3P @>c|8 w>savggvqw!<;|ͷscݺuqN:EQm60Ъ;nknnft0R[;v $I8<.\N۷`/_G}7܄7b||gΜA$rؼy $_2~lݺe.^x<WUULn޼X,%K:EgJNa& .qK zXGobUU; ݶT5^/dS9>߻-@}дv-~/ng߮#}v^-^X,WbIGR4Eit*c̩vhNӳ5-= ˻w^qM=4zarN.p.x`l:TȂPh@ȣ*Ξ=Kݣ'عcCYZ=:IY|"BF:LN>1ju@y%wJƟ.&N:Ј 6_^rnּcafO `%ry+K F<%6eu\Y{k8q&'i&=b[J@#0ׯi?~\uB/g\Hϝ;۷o,k[gKMM38X0W<+$/4oD?7޸WW qRDt1!<6ۻգ3>7"Y0wT-sGw/w_̜ۈA#b|LBسKpQ^MzS6Y0uuu 3͸x"FFFpAVoo/V6 s3STӅƷl٢BB!oo_męΝkuf`:!]<DBvh;oc>A55Iv_-&mm\Gx9 Qn|\_M3SI;1$hJZVw`$u8ކP?!@ge"Lp [?F{Z4:ŋs3h9 TD!@X? !S iWOn^'X x`---^.;+?GX. =ʦñ1|hñ(}Ŷ]X 3$^jj/1ͳVpbс:^m=?O4Xq.j|m"jzҽ^R8/IBn*$ILJ$^C端g'IE\c.oXn= M_}2a'7ee}'tŧù&-ޖtvZm+$ܵx $Ep HJN4U|ygZ'Ҽi2 oР`l8Ӵ7<>9(),x5 TU . . . 'HL~ǍN;9oSZ2!1$æ!f kk'XXsƁȃ''"v~V;3[Ʌ?3ɂ*bO/m-3m(Bȉ\jUxv++WV^lY9 documentation:liferay_4.png [LemonLDAP::NG] />

documentation:liferay_4.png

liferay_4.png

liferay_4.png

Date:
2016/07/19 12:15
Filename:
liferay_4.png
Format:
PNG
Size:
112KB
Width:
1239
Height:
574


Back to documentation:1.9:applications:liferay

liferay_5.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000003022141325274564300420270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{SIDATx]|=+I@pmq/kB8 ^IVP܊ԐʿX'~sr{<ξ;;33zNO cf222>д04M32GT\pr7Mdp1#r;wHm+{XUK0bP [hjĖ2'C%,t V\jJ 0[FAE2lh k4(#JFX 6@l'a)dnd#ΚJT=TMP{-YNK͑UCNIYRtŗ(ydf- )%`6S\3 cpFcodP;L&Si S' bU^aQbSlhg61KMPbnPU X:rc2r R>ht:?ʅx a>?_()zFI '# ] e0ݠG H4$ۍDʅ0xUpT|YP- 5R\\*b>)ԥڞE@x 5qNXew㔗)#U[֬Y5 B&$RY!Ze=y4MI7uV?V"%Rf)ѧ$ #i.)RJ"fC)(0RzL&HjcظA` /^XG!^rESSS % xO!~bl2 0RuMEh&###grtɓ'1/_&&'x{˗/$$#gr' IM),/ a6RXK`c`e/^A P1L^$(2|;HrQ#wDuƮ0{JO# %p Ud*ddfzzyZm0݋ XwRRb+W *Q$Pm@@ |x=iR\" -r\FPJʣB'H #JJC˸1I),feg9^Wk4|c zU@f%,JT3/ M4(lx(JNaj(Enjd|) [ B{ET`sB{1\ h^> )ؖځ2f"tFCЖ5zBe 4Dta X̔{ogd ;8*2Met~p^‘ce *IQ|ܘVTӓJEKIIy;wJ(RrGd:9GIC!YP?rj thԫ`0&9C-;7lI*6.#L*<"/F”dE&]mHb-Jͤ<6Så,A &h+^=Nh&H Yb)v0v-Y#FtPdZ(\E xeWUH-2p^tý:RмY< L/^ ɛ7Ȑ>]VXؘ萐w"Zbpv;]bNZx˗/ Uu.Ђ˅CȅCIT~nZ ASxP"SNT E{~+U/uS32M~mJ H5C)'Bbb_rR NKiMFSRR2qqsfϺxbZZWpp{*OI\9ը4,Y2V6m̚5FN2СCT02!lj;+BYj>A!lηC=Sݎ.AY!ˠ2p=U'Rb1KD t=RA02iF\vz 6p\bpA!Puf&# P\lbI JzoVfG!_#Phy6&<#ȵ|(Ib@V 'ԙ)| 1KΆP$fdPNL_p (Cr ǃ؋+> @7-{_#HUNAN$STٽkwΝ *^z{:%KQ }}bQEnyƥ&zrJό_T^Q[1N)1qKŷl6}|^^I@^4O:˗/S{_NYy dIbcd>ǎ8``~ 8fNqYYa2SF3cf@r46" &6ML11 4YS i4[nh0n$kHm 7_=I=TiZ%x¢MI^a%U<`H+H b1bD\R"WJSB+V4%ı[ /JXvE_~ EQXxp~;ZaGŢ0) -ޅKA.Ϸ J8E%vl5H.pO-5f95FdvA׀E+ p7;82+ЙF,Sǘx1FbL(5ФVX<2(eCV2)V #5-jm|yKB zQo޼t6D2vXhfiѠ?1zvr&IwƦ+\e( j0:VJ(|d Q1 y"8+W?^,Oam/^Dvҥwo@χ`pQ#fh+6qҤkř;wcGAMC˗/EFDܾshRZ{Se۪rED۷m[h!(Qʃ_ر_|^}?h2 ~UrphѢcƎϗ/JeXhʣ׌e I}]w}W)ٳ3u >w/BE'>_Gc)Жu0 p?ʃm䤤}{vi5tHc#r<M]Bo }r-q0fsao?z( 00 PDJ,4ݠ \@hyT'?^+zdAz.cbԏJHOu). NA0ѝyBa2ku:]ժ_6m4i˗ٳ? F/^y+Rn:txK^;g:9|̘ظ+W֩dQ:|P+|l͛(+rڵnP˗_=7G~paĈ&q͚5s(GBeͫP )8pvsPSnil!grȖo\b*ʉ8 2o^޿Wӄ,بi3'O$޷mA(BWyǷ m (H}Xuݺ㞗՘|JpRy%X( @K}||K CSҧw]:w^Hڵk v:n].X bұƃw׬Y .y{y%%'?}$8F%o۶mРA7mD/9>}˖)c+^/ݹ}U(&v,P̌UQM(XgPj4%/}ٺ`[Æ ٺe i߾{ia+ 145P(T޼r y!!nڣG[~P` ҖQxa.5 2aջsq1 oWP;ꝣN3-̔A\Lޏ.B12A2 ܟA0!>ٌp##&Vq-7#Cbč Seid\NO e [a$S{> 68t0>RdF;y*InfF%8>TX|D_ѫ9,>yk*#.‘j_%/]Amb矧uN{!y u^5d*@w_֯@-zwVe@ԇG]n+v=4ZrT[P=))CACӽ}|NOeqFϯM֣Ga/uu 6psu\J]拖_PǨ^~sMB Ю]K^Pr1FO¡- Ec4^l^V-E j֪bРf#[m ҧORff&8rBe+Wys礤X"00p9z1K:⫳ (d  SK|* 乴A}{SȗPC19.pk hBW%l3hY1![|50a`aR6H0\3Bjh.CJb ے$4Ĥlj"Xe#ƃ\8ZX;B h\L n8P^N?+`VUNp8d)F8``D Dp|̅4]i6_'"F(0.481Z~ĭbHoR PXvf8H=2%kJF# x Ƀ  nSA%SL+hRey[96Mhs?˖Wܥ'3ڂ^&CR5cRtitm:a@0j.H=+SRP/XO>9x\˗OK-*CznY].\,gҼrC ' }xo S(+$hZ%q c .wP"'Pw^tas5[(ly m;>;I zUTℑ"0<~>`y 3qp~r^ !6ELܰ%y,0mۨ,Vk4a)%e<ѤJ!2dRHy<.ZZ AT`"^*>{" &q(lǺ9--5wt: yߕ빶*'gى-NaNsR 7\hhtL{a{F+aX2SJ&#?K6KRKvOQJBbދBƒK L5jW}||A PӧjB-+ '0Bnyz?\ys?yZeR0F֭~T/^ĿySTi???%>cz0@>׭|IӦZ"6.n9cNZ_x? (}5 EQS(#\e`DD%lW@%rb{WBrY|ɡ-9'(ٽ?כkײϤbJ.9Z )zI¤T]gW$&/\jrQFJL8RXo4BQZ:*,-^*VJ(rk/INK)<(W)'*òP%CWcYD~OL [1YO]9T&9g_WHݜR 8Ν;h6LzV/^R*^@QClӁ_`{ i>M#s0R("0BOBFOa B-qKJIĵӁWp!V$kI:tX q#3‚G=xeEB/|`}ʦ%fQTNTQEݰ*X~&b+ ZApˀL`II  p#&BԍmFET '㉀}SxP"SNT `Dhإ˝Uopk׌ X'.Un 4y|I5kfddVruuhF!=-Y6[wӠ<- y#^pnq 3:HG>%cj .[8 /'3fZ caGR'Fn ABgaf_^*x-HFnEm5Z[_rO[~saǑZ"7c8*oLp&)UFfAr܏Cpɚm%1'ZT(6aGQzc$B ˑ|҆eΐ8L%pQI\L-8)Y뎳Z 8Hf hވƹ770ȣE+[.doŒ0KC@4%4T%)(@qJAP ` 45y;]:/" CAL&KBe{<|p UdYʰi?RJ40Lxxx˯* TIMIjZd4fffq >$<wh)!ɠ6?u`|FȽKb.ʹ쉤Bq۬(;eӓ=1~P6%w4,#T >VCa蕚st]h'( G9&Lc"؇ f~k0+ mo*ЅB=uۍ64Ơ =#OwDr7 |_P!bڡ3#(mK_,_syv 2RŢŖ ZRSpmW U kTRSx[e -"#c4lThRlsW* ,>7X]sű#x3 - \EcuqUmjZc,x]JG=xtM@2A7.rQ)3H9]0gd:A|QI!áM{hAhnETG:7~!)O!*!f%(&0Ic[4Fxsr\&hK?, V>#d8Dj)ZBko+n+h*P=.lƁ3HªaF0$F&bh]`UF%nBd05,9O1 ~Җ*䌖K0"5"u''/#2x4+Vbm#0; Z{"FzP@Mw8*2Mex*Ċ)!M D ̈#S uRbmx8 ap*$ŶahT shADsټ^WS*e HFQBϢF"TR1',ʉAK!~=Nu|#A5OiDa.k679KTRm0g[, xg?nxkFC } xTOd򄓔Z(P·iL؞ 8D"5(6[!)nlXQ; 傫?Ċl 7[# ˆ^¢ Gb8M&&D Ct0Ea%8dޭ@* K7;nc߅A1 %_Mt 34,?5Fhi&(dzAZ"kT. 2KT9щ1fTI"Ce'EٓT$i_عTLHf[V)4%U#;sT=^ءi2M@@@ [WDT),ި(4ɳ+,Ӕ(yT KNJSeZ2o/A VVˮr2NZxw NI@ةcd % 9|h,ѣGC ۾mϻօǃhh"*?4tٲɓ& J@@@@@@@@@@@HIMMLJ;gժW巔"#"ޕbjp%K&&& 9,""!!Do ٓF@@@@@@@Z|F>Hoߺe63իWSW- z ycoχ4JS)Ǽ̶mۂ`ʨիV-[VZņWޘr ɓ'-p°ߋjz8w܊+?~RpaCխ[WY>g3/]PP77O/^Z ɓ' +P'˚V;k]VbE62..XFޭYZk%|u;T}g͚=gʕ+S&/Y|Sx.?NG@@@@@@@VQ\w,X=.%J(ߛph4._Fo>ݺuc~occc/1cҥˠwLUW M4;6Ņ8ahѣ_j0ԑXjY0gO/^z%TF3fsG`nݺ._BFh .D.|qPPP>}~ , &Vő˕WRҪժPL,2Ɔ?`Xv풥KgϚe1Iǎ3fDjĉ6mڄ ׯ K *)%ԱQZ4oz׺ufΜo%9c%\={@zZ@JTr1!]z5xZ^+V(ȫM$;#* WM9_r!n߹n4Hi4קj˔.-,T4ck֬ɗ7/5 Ə7ztq,CE\5l]@nȑYW?Ӷ];$&%߯MNJt4 Tng&" K<hZ%Y/"իn`,/[!Ʈtԉ՚ft6((HVv5| fÒz 7~ ^R.]=zUVl՗Jꐩ^b[oQf`00H6skD=_'-Za{zz3~D,/6b!aah1642k|>^qsVcPS} NLLd@bBwvT)*rVD@@@@@@@ޡLҙ SPN5ժU[u`Y(%?{Vpa  ܲys^J[EP/:PH?\yo|?O(?p)uV "H!S0~uLMMߠ2z"`AK|H?^l)hی3O Z^ayy'V͚ 'rKjc *7O@J65է+;wukJ,WTQvtFjʱ 0LtΝ" . ٳ" RN Ղ NʾU@fϙ36<<p6m4o+#F͖.Y:m[ѣGED?ڴi ?}4^xqfM'h1yl欙'Nеk{25>Q`<(e 'ۧPB$nG-/WťJںm[,YXGY>gf%4o\Y%GdGϞdžW\իQ/Z\j tFjʱ wի޽{Bj5ݿ\7nTZU^'GFF~ ۧʻzAbcc)ҧOk"^̙EV=ztpvWnݻu떚) [pA6m@Iƃ ξG 3iղEK԰PL=uʔ%KԽ{SNmٲ%0Hס6jD"EكU)'Ϙ9{ Ay̏?LI,3jWRCNiii74hJ6SJ*N4iᢅ?.\ɓ7iJ-=!(WM9_r-f&==C2 UZbE޸qjY1:n 4M޽݌! V=quu:e*SQwڅTZmXRaL ߥsg66kL>$}He! >ʖ), 3 %x]k2D>@@@@@@@@@ AX(A΁Pa9B r % 9J]Y\ gA%PY % ad0zVK9J@APg"114 8NHHH{nŋ{2F13Ӑ?~??Z||...:N5;*,1 %p>|TP!__w 4=z\XQ///4d2%'Ph4$%%IӳT27ojjn! %\BG:oo0flcǗShM1Nssu-PG%f f7~p$HYfL˘ҥK{hl QgEA?KTCjj*?ʀy(h4U&iΗ?~~~jna%w7W*.T9% f]$ӢVӝ,fl2KбsqSN-XD*UT ЭMC?4Ȥy.nСLHHW3gXI7o޻s|Կ?M\Y8,'Nw5OXe'Ŀ|PO+[-3cn c6ӖcSP9H@@@@@@@˫WHٜXB$|㴴wn{hUgzgħ"H,nPGڪ^U{ ;AҢ :bBhŸLc۪aB!0d4.t@WJ~>qM'3m*^OHp @Avu<…s ݳݽ.v^>=$ū8;qb=! ݻAh޼Ch7o/|iCᑘOs ƴT*_AL: E\˗ Π->*8<‹hCn^' x/C(R+R<lC,c6gdd{Ɋw{A ߌXpJ\.紳(F@^狗 ^WZ=8XRܳu&)sΎ.:M\]6(PꟺxI}Eu_p? 9[%S۷钒m/ hiFYuƾMNK,ZP>KAu0ܹӫV}ګy>9u7sfݺGQ&G۽%˄2j];Mf}5GYN.%S5tj&E\5nz˨at:73S<,N@@@@@@@ի ,X]# -o&sR!`hhow+_.~|?/]ZД?PPO2{Y_w,sq"*3)FcfJ\֒ĸ[.%j5sLֺޯfN" 8uԿoi6==r{pys\b%wwwp+()5I=TPڎWӻ/+V0 ˲eu~Zan`RUd:r份{K+޻Obw;vVJ5BjKO7a0M3c Fp]   flw &pJ pT|ֱpgM-9b.40&ںyKSr! p._LNNҹNJ׮?|mh*SwPvo2,hb6|?q][LNKg1d,$&; ~'_P͗o;]g?|̫aKG߿IAyӳz}@](/ _wxY@Asgӏ_wW>ѣ=Mۿuk#=v_GRM3u,w{~Ta2 21b޼5j 3Wٜ?ۇW-S@[v{U%=%LbƬ=)A۷ϟ^¹ żqc/^µjZ`a%(k9s֭kɬީ{W u}/~hNQ}o)lV" `9s:2r_ѰaÙ3g* ~;6<%%%<|R*?mڴoZf!BӡPِr۠^'jK ;MF# Kf/ݽ{(O?f o)x={b&~&}~]@ Ъq@j_0ޞ⻲&&&޽}͛f׷XD$>l\~E/b+4͏bS psi_FM\/,1kܯYƯhOz&fF~\]$eK7233vQBK_uэQ;OvjO֣CBێ 'h:fͺ݂JNN^hd2u}z˖oWE5,KO$'gbb<~zV(T)'3hSyX cV58McǝZY谳K\رc۷?o_ѣmGc#G93Wmyv]{֬ZeـIʬ\>n„/c?0&veMt__4!nch]un.t,dm}|E7UQ}A8ۅ p^=o޽{,SD{8^n]R:4`@7nX Xʕ+{]Po} b˖EԮ]{yQ)kEQFNfr>oN$Ϲ^xz+Y@57_W)dt|MS{,48q)ݔ;}d; 饗xI/],S棊+%&$=%w{򍗧׋YKNe߻r;vi/:95C7tzD=H˯=j0&>Acn>}ߏ͇Gt"ba=>ke[lC6_Њz[9KBnhP5kDȁ,mժնSJ}'L<QwL`vw~מ=)5aD*(ɓ'ٺc'HsjG@4eǰ? J ;ܽ ݲesdd$eʔ]rEXߑ=G,vʆMҶmŋHӧL*?~AΟ?ڞ`9sƞ={mуGN8s^QQĻQ̙3ӧO~G 6q1Qܽ{gĈl1<(;{I&D@ʣ&JOO=z^^^1c4YW^qrE[V52(7j+@NsV!eԟ;w.pSR[h>OoASD&LjUPoen݊ZW^ӠA,.'/WݒgO>}[RRU#33P_ 0嚲0(%ٰ\[BBBŊvkݶl˶mY uʔR_|q32K͙]5lN&#O8rkr(nV-v٫h;JM6X^wMKwwQFC2h*n֩iUKiJɄ$T{ ϭ#G>}}B&@`Ȑ! {wXbΜ9/.] j?,l0x`/\<OOEl :`\`>k֬[\@x%͚5]xpDO5k| k4mڔV\ WT fM5P211aÆer察9 Ph 54kI+dd8Em9ZdcիM|||&Le!{W)Ϟ=[Aۇt}[Rs kȐPկo\}ժUA?\ тڳ\`[Wϟ?JϜ9 nD1AUŲf *K}vJ ,bUdly EP朗cEɛ7ϟ={0;U`Aӌ`g){#J*;r F&Jcl_rܾ^g%\7 e~g@Ƶ/!!N|;:I^C`p'S2LU Qﲈb64sczJr^^^^"Ji4fMf`4ge%i1eSn_p%m7kq}^sdt37poO\L^g6KݼP I"!!~.Ns_*TlٲgvZg-Py(:^04dFeTe] =3mbőGBǐ/~ܞiǘхG>ҠnHĊZ4\r-յgh%$Ur@PW޾(Π  Ocӌ/Q5ۏ_uP)…KYXpi.̮W_?N>ݠAv C߿#l"I a ٳ~377ɓ@nAr|x!!!v;g/hFHY?=///va? 'fϞ}$;fffZC4BF*O6Ֆ\UH*c{gK7ua-PǎѩS#Άnj^u4w9yVжk׮SL#}[R} 8еj^e%@1nX* 믿;m)n߾ . JwAi4М;ZĕM{LfPU3gTl*V~:ր_8it`S(\z'M+ @e>|XHGoQ`VؘOdzk%(2 = l]Ì~^Vmv]_;6_D?IvyļċqALϟ%,=B|qq8hbx}=.ѯS0/_2YK> >~Ծ+its7:hZջZ& :^ZݧD?ǧ n9w4Ytx7ߧ鬵' j9mC|ߔm.ߎ۾t?P8m%z4jDUۆh<>qc(})>}d1{/k]N\T{`pl4}}GFڥxnu;KA-+_}upN@AwHԚV\ul☦MT}i#[VlԈ5v x[Fmc3..nhmt#e{[6r_Ikm7欻 n4USpaaèzɦMƆAa̙3]~[OHD.-[]x… }ΝӬYsqhq ܹA)6x߾ ?z{[pۘ򨉀=CB aCUb)Mf-i|T6l-BT,::OP)0ԇ&0 Ss4w9yV($KB-hpޤk:Ô|XjJ, lбc]v:taϲPG . Jw"EVZȑm۶;ٳ%$lc:5ʪ)֭[Ϛ5Dh/^;vΝ;YI"uwEC| g7HHHDyy~E{2\ ki>q$fxyےMf[UڬĊS<[^|u+f'䢙ܷP ]ʆɯwT=;~ߧ^N.E<}8Y]V\=kڙu盳LJO (j0HM&03LFpWr}le6v:Llі&k:oéX}lӻ?|@V~aÇ;w^)C|^'J1 JQo?}tM̃.EhgSsw׺Y2ǧ2baCʭZHA a*4 w" h!/\P~Wn.wNkSEګ4 z{w|b ōJBa|ؾˆ)'o8qŊ'O1j0Ξ=hb,y|<)EY>}9sVF N: {__Oj97Iiz5ilظ˫reYQRms?o}Tk+TxtZJiuYmUX8~@~gӼ}co~Ώd8wiZbAx%Kz;ZN8:SRmnO=B}(h5ӎMF}׹ѕP̭y]}]h zI3];GCvkzj$GCnܲ|KZ(l+V޼iA.Gw _b "MDjM΃FcوkL@_by3\]fà_GW __{,YR~lir˞I&o߁@%hE۶m'Nb|vl_ҥ"#N,p}ro̘1Q~ZJeʔ0իW}ܹܹs6oBY? yD@)S,[Ѣ+Wnʕŏ4i"zְ%/ĭEʐlo^z9ryܹsgٲoW]N^y+iIkPq0>kk1bd_~r5lsreV#GXd+x*yM6JBq-$*ETVM׃us r˚)B2 .ļ j?|ǎb#KtqbU+1 mɒ.]h h4osBJ ?7SBE? )rʕwwwONNJUQ RE;wmkܨizhz|έ/^(UdeE.9|DԱ.:?u)jry-e5T./w(5r>ahUpء}Ͼ%޻-gFuo{+),1m(Fca^ǺSk=LZ3LF)ixS5dcb~S> UV 4\t%<Ɗ5*U~޽9#qyyv<܈Zv;EŰ}R*=r,33RT. N/[7n|޽&vΝ;S K9sLwI ˺{wߝюʴYM>wݻQN}6Wo{yyQc6ol 1AˉtܳjjZך5kO|r*o~͚W^y51&w_zr,͜9kʔ)aammm)ݻL/ĉ,YLĉnʯbojGy˱5S-g4i}"W<}SՔ>nc|Kr@yʔaaaԼǮY :ݿQ޸|c>}[1,YBoZN)?tc 'N3gUrGU2a? mojj17uR5SXފ2 o!ZO?>u֗u˿kQ&Uz ~jW]OQQH;r&=&%Lٙ*\/ǖwh!oW z}פo 񲖕斒h0Qmoۮ˗ѤMr6x>~m^4qāYݨ1{Jʼnllm3PP =udZ'GgOO~ѡOD=5,%F'f<ӯ}V3vz)ȤRcؘq+<2^ ~bf?]~nJoAige7ُnU쩒DrƳP"TI^^"X$EZ?gi Ν53.:;6WԺUUǏ[_~+W4~̼]{tM\\3LzrVjqar$-C -P%]j~8@) -ϼr'#!U% \qn,(` WOzPON&Ʉ ajkvG-.Cb1=6B/p޼O[9H5jLS{ygW2uٴiSݛL2WKLEVVV/WgK mݛkeOMoF|S;car=[7IGKokݵ/rYnjyS1e.d2ʕ_у?~%{1i27sdccTU 7ԇba/L6l8}4uR5SXފc7Z _xS/T ~Ս_1J ͛`ݿ~eҬǦPPupn0÷}GY r/vųC ?c(zzBZh<-ohrso.9y_ Уbح#|ŲV={v_f꯾|>n?-ǿ65b<{Өp`:0HLokX~cyS[NlIH:T,d Q(2q8/[PD9r wE1K̜禽zj.prA8~ŢԴVqTV(BSQ͢˴qNIUk T[?9wJ24MCq6M(q,#;Y3磞˱ :ލ9pP?fM~~K>~zw?=h]J?B*8:;;:9mپ?|⩡fefeYY|bHTr1T|P(`o'6Zx<f~wh2(mdi*/UmVnN|jFY'Z^-[_x-Wk[=wfγ㎗o5p)򔠀FV@ k{"?Yܟ6V_12xtE7j+WV6;?b$%$U N.־}۽6U#t6=T TT]6C]F *T"TʏH P@ S(mjPFgV{Q-tիW̍G&_|L{PzzРzI*ezT)qs}Ý#ɼj/>?nszNEZm%QT"+H2DɧVp#'''P*-5&X[[_x<++yffƥ˗%pLO)H 0z/U,Htdoo`wfMX _vÃֵ@ KP_Y~ Pv#oooPg*6^ӓ֭'HORٹ3rr[[[\^;BX&(Z-.֎ P!BEW/'U-q )R(TP>)ǠjP nAh10T"a E BP eru9M6{ r k TPHۚnB ߻woݺu;wrvv6'C.69@S N-ԹXT(-YR*Hĸu0S8RݐB)U;Iə9fo/,tDU6jSYBO8?'N];y6U6MnU._EkzU]P*_HII@`$sեm۶b 5 DФ;~H.'(R,`闟;8zVC Kj2L @XBCб(s0:QuSՏ]&kkkCU ' wB'4Ze]˒/'@݂SbOP1zix|LU˒l@D"@Bݕ/Ӻvݺ`K\ACҷVVVF} 7[I\ NUF ;T_(w8JƢ*Qh@CSU@zVf[*_˺zؿwEP*}{dfa_2-ߠTg -_F!!!3{k׮ ҥK7mڴqSԒӷH*X~#LܩYrO.Z.V}zVT1ǎ>?Çr{ ǿ9q [r5$Wo,\x͖Z-|i-%oL l4ḿOgT,9wq[BEe ´baAڄ]9$5_qɷF gL;|7G-vꕱotCJrϮRO۳gϮ]Ra6_7Yb̚iߏΡg4khfL߹wl(N#jChe>?hݕMF,|wco'Okj B'u d܊dtل[6%tH mnoɊff T:SgY^0 z}8+S6U-xyvJwK/2Ï-TW}v'{ \e˨aϏs>;w}ta =ٳww={ߘG;kǾkz~?gGs?W/^:,lP>׮^d6m O2aRT<1ڟh+M?rl߶W_3gYYY?͛SRR}|>}t'ӮXrƻ}.7ojK.QՊ/L<Ȣ"~\CݻSk4vBfŞeƄI5fɔBcc6s=iV(nݲ9ukz2=JR1Bu@U&q>rFæc0q宆(vR[;W(ѱ{. LrrrzqnfbŋݻcNkkkߙ:?3j?yΝիV}i3@-T}־-"$B@(TCW*]H|۩967.~f޼_Hv5ssGVoyW˱Cr5|*(ԁ؛m&>!=툥_J&@ݍp}|9ߡɈfoboEٕp~H(l:?Ky'pj!BqnՒ8ֳ}:p˴7n20v7M|6?7 0P%BP"3I0k/@oCߣW޹/vDkIKoȗ2mgCPX\ %)TSwLw"NDDWX\˖/0~ N>p~/06p|zz;WfBEOnֿk_给C:"Dż^\\ѩ#(tM)~x쵔czO_~7}0C)>Մ6.fH\Rg:W|?vWX>fE·(V-_;mpNA+vaTNcxPtXW^zwȾ#JQ}{7L;u!*uw-JIZ];|v ˕hyE}:^Մ齺ؔd%';:YOU+4oYFi wFok[*.c.ǖJ9mt;;;zJׯKFjtR{V233 Pt_>0_כi j"O-J$BqkpRatJkgR(v/3\0ז#Wb.&Ѭ|vhr'm|5Ӟgn!~M)TvNj@D5k{A'MƶS\Ki<-N-1rk7۽bEL&d_+1lZymڶy}[ZƁ}3dV!O|{8,c.>j4uNFߚ0QovOor56}dΝBi ӿ6m; qݛb1<<|̙ФIzCfҝ;z<_'N綾Xf?|xC%~sEX$RBSk6r\z~wĔ_:C˰t.}d;O^:[sS[)cs?g Wd 0yjF'>39k\2Ϲ>w{^eVE/+}Rn>ei⛣*5{zL" #wƬJ:s̤3cXiш2"ήKn~hyfMvE/o7O9t#?٫OϗzׯN6=2ޒw-tѓݷvδ}?\$7~އs췬e:3sNS^]?ۓ&3zUv~-=[;rR_ٚ\, rZג󃂼^LeG[9?7,=ݓzooJ7yx܂=oP ohxKL\XWH.!~~(ZcnHw/ =-]_JlNS2rʎ-}&>N kl\ _t+m~ú6閂bȥZ6(uBlmm)4|$(\JH 7_1bQ 6 nn~gZXoraf_ޣWܴF^=K/btGe-N68_h  MHHh޼ՇǔiӦQ?UP5~uJ:{`R{LE,xS7Y͇ L/g0n@)WtSk:{&k/ mC!=VwvM=?{w;bN*Qо1Տ-&=02_x(P3Juf^3^Ds_tC~.j׀rACW;@ٙnH$zzW9r\(-ܐ;ZrP] R X|E\ /`f-LVTTD$\"zا{)~IB!5a:KO6fG4z^x:۬芝Zndp&'}#=L!=*thL<1 m$EoIjۂ85pjtmCeZ˚nJZR -ė^AkU]֭[o&&UvkXC69)֍۞%ך7)vx/o$-II$$$ 55(}PRJMY )R(P9HPBeg qF.T뿪&@ƈj P6HP*4R(T&w E  a]h=P%voqAɄׯ_>fVF@_'vΎUP4&t&Zdef]p% _,+_ZGc7NƜ3wշ9S)|?'Rhz?Nj]]zvɍ>op?A@cSd3'Rɤڄ6JEcT"-WBsxjJ[THDBIa}n.@`[!An);T}/)I)^FWTT7ptr&O=*q٥gSqgϷnj`t+P%E64n'd*Zyy66&3)quwI0SoUR wAB]* \E*)/y)LQ}D"Qׯ^*)gG>>r7ۃECsĕ WnU*7O>zZX[6}hKNzv=ne5tYx]к07'7?7ўtvu-,,LOKwp[;VOQaQٙY|%mA*RAn%dQ7qe~;=tȾ#GNN޺|V+[7n#zOdܬU1rqqu' qE% -ȑ^^ZnknR&) Q%%RɐaFae>U*խ4QǏ/*Rh՜ >xKC%%_ǣONwҁlS1GS9kd%*h5 L|]zu+ݽu0r<=-L`az,%-+,(BCԴ/f<"(6\[ܦT*znH9P8 Hu w'_Ltpr0E7\9EɀFtߣowJ 2kzX蕱#"߶;Z4kܴQP޾^e7EаB/qؙ};ɥVI|_Ύ;ڹ}gr)tA7zs*z$&׊9|g|<5 ?՟ )P6GDؾ9}"9)YT:8GtlW) yx?m{N=(=uȈ':v+fG/z Ͽ:R阳B˱_˝ K)4/7x2qnH_1~-\FuۇrSA_hԳC+*csrq|a#^~Bܥѧ)߭gim{;ťPoGbrjZQ];ʅ3-{} n|t0Z9ٹܤcjʃk>ŭ".n)߾yQ@s3p-ߟ[wˮV ` ڭuې'V/Y;lS-d2كoI4 iզYKoWN_}͒ oݼٲU/mѲ%WO`;Ӹ7WƾOѭ{~F gL; K4-nK_폍:DFi#{P(N#r'STpox[\E 5n&&s-ƍܴr2JϏs>;w}tn ڽo7̋.eV\fA+߬^B- *G:F ˖\Ao-auM4/2ZņLTj87z0iFL歷z-OϗzׯN6=2~K~r>`vNNNnN.<1ɢfb]~hn B_(T[繹_܁O ań& Hݹ A nj/LkRhuCwT_(4( RVTףhhkKN MPlfz:ۋDBKWٴ ǂ+5Jn5';%q&BGkIUjRH(vv@24PlǟTjȭޖeZL(\Rhu5Jк`ҥ5sV|uCyxi5h4jFRUj5TS),ύFNz}PO Ux4?t.R(ȅVBaUuRh]}?b8tуG;vM{Ӄ׍G3Դsg32inmc]ֶ_!zI SPԩ*LnW.ήΙV>Tw箫+=s)]\ʷYZc=< N;E jTduXDi-v ҳ V6Bsush4wSrha*gL..δB9|<5%-kD"Ȋ9r\&Eug^p9M+.FD;wˠ"ȆԔn#2)Y irڅ0{\6*EʹD)/Rh#yƗԛk+fdFW1)Lxˍ`%bHy&"n1+ǝإgnP9a}QQQ%;3g(IyT̬ w5k֌=+oW6fzYJMM}lْѢE *>M7̙36ۻw|*ϝ;5QfCrJ-'dd®HY;*mjVզ\ݙmM ߈4WaKCΈcʹ2AVd{NJ[RCǚq2Y-{D p<#ZHuSvV6&_߽{BYG't[D'lWzeRcld{w"hk~.-B" Ǧ`f6luYV9999]v|27;bĈ/x&LؔUֳgOI@]a+5&C#QK: LELJIDu(q] *#hxp]h}' 2Hr/[TCYtM[6dxtM򺟽\;-PS"h-nBQ,gҾSjb1RFjUiܸ}\\\y޽Tsu~+W3&&&FRZCL?Ι3 [vm֭pB;v_Fcǎ>OgϞФIO>gMm޺r\bŇ~@{5_ٳ_lڴuYRRv2Cm"kJ!{5s`l 79*)RXuZϚq5 F%>6^&qhҳٓgT2d`ÿ=dg:8.7^ҽFMqii'dnJ"(=KKZhРPO7oo3}^xy P;uW>}zz‰'hkǏ[௿/.srswr)ŋÇgo&Lƒhݛ۶m˔UkD^5}+SP 1ٓr]el"ShM4Yʤ>V a8[g6..gSNuMzxeef;8߼qFBb~=q]{uI뭷G[Y{䞊9M5,/d[7n{z{i}E J`f2 PQ?) ѳH$BOٹcem?Ӧ`f2P'vu޽W\nݺ-s[޽6Zr)YGb/1u$(9::RnX}=':;\RTLVش ZK;Ŝ&KQNj[w\X2@|ڈr4`"x՗>TC_hcem5gtkRnL.몽dT7m|aXO[ާѭ;ZX)^tOFգz'/^FٳgW䥍=zZl;m۶G,z]$WWסCr/*AmFL+V*\Uʁ]z[)(}N{&6+J.f{M]exE{^R(1WA |r'r8w ʨk߾=+Vc_3fضm[NNNhhY(qs.._ܤI>W^yiƍdooo2N$*9[fkfcXl[oEToJ-Hs_Ktt&n|Yl0H|5}&wKO')m$VD{GPJJ j')*qZ\Y(֭&Z2ˋBio廖d 6uwwfjJ(ƛziZ6>6z- Ӳe+Y5޳^*!V4cOh/a;ToΎN$0R.P P{ޱceHwF EJg̕AQ{:aTkS R(\BILͅ t^bXL)S$2AvLBN/w.n[eiڵcP,Iz J!yꙔj!PՆsD^k@\eO ~reXgbQF{h1N&pD"{.j!ܩGm8T5(^U;Zw{l@uґjHK F09 *g+`OM.40I+bJR6*P p`\LΏu\PUk4y|eVᣗpsg M߬m2'%@]jʝ^cvmr)T(xSj;4aT5 J=kWhбT)c @ r *H1-.f<"^zLp #-89[h=!.x}PjP(V@H؛sk;HӝHaG.2&K8IE梠=e@UfEL*2vwB)J-`bQ(щ n kO O<|0Js^[]wbfugiI,fഔP-(arlޓ ȗ&S `ضtdKJmm*ԌL {oTR>5_+&3j+f(b Rȸə# 2Dkzm$';BmAS!4 m̖t;DIShNv%!yfJJNAHd͕SYY?DUk"69UŌLƤ+ I4uE!(PT8YRlv>52i0I!V&hG0 ~5T_/*ݱ#QA mXى9v(f؋ФHs$&5偛kdHLJ9Ŕ{l!<>-J%"F"(\WF.P=BH";[m egI$Rm }X[T?.g k>+܉\=UXtZ%&vaNڎQ~&yD 7FnTHnXrRϭ=KtqsNdgow<wR.k@+-vpy捄ĞzZOBqr/pnVVVx6vg㍮NkEn=yld>|▐d"F&ad_Nړc"A^Q@=P,֞*)KMJfdKIcݒu'K;M- KGВ=L>L3{Z ջ_hXPtp܁ >pTP؞UH$ZYǣONw̒m Om3Gocem'j]"9^ThѪ=h4/ MPitB5nMPR{Mq)"Ξl@ rR71{cO}4I5 bwNZ~V&uMB'Pj冽%AMnh6޵WW3˝{t|%WлÊ*- Ltpr6 ?h5#Fkp]?ɍ$ЎA+TkO;j/ʅ;M@aGb]3K6u Zׅ@BxM__ߒ;b#00>k׮]|y ;w.=[ M֔+W6o~LKL:u֭Z嗹sk72 Y*hGպھjY{lD-E2ރc-{K瞱Κ5DSN=r۷l6 .\POEŜTIƎQ1*{*=V'Gg$ݷoߡC[CbѣGgeegffv}Ĉ/HO܅8#ǘ1cH@^xqǎTI:[^w9Iz,Ϟ=an޼YW}333xׯ_= #4 D%;F$KUkɩ[n۶m6lX_HЩSyѫ}􉊊"!Cٟ8r2;;[MM; 5DGb&B !(BzU'R(^vΝ;O>y+W6m]r̬w[Y;; BxzzR餤$l֬ս|||9bggGbZ.RR'J@gff*((Nj/߿O}Z(PAp#F[HH̙3srrT45q/[)(( 8QQS]̧U̅@3fO?DWZtҭ[J_}׮]l6{ɒ%yyy^BM<ͅ~ŋ sϞ=k׮?~=ƎK'yf5}'nܸQv)Sz]XXH.ЇqUuGM =J%2*m|jFfУ-e_ cÛ'>XpDtBQqy sFX5rijGW<1jIy Q# ǦN*#ÇkP ڴi#x9/CA%$rs贚ھ2%MLLq%k~J?IH΃:|8|ܼү~̛% GŖ$y`oq@!^ɒl`Q/&9Qh#sȉx)~ANNNε['== KWӡ9|z<|cW,:4mw.e}siUI/Ȫ}(4Sj(A\(M4nh.pw]|oާ}-QuC׷sAA>WR_i^h܏=KlzCq#\ڐ%dۺ.5sM]#S6mJer9܂.yԔT\K܅J3{𱶎\5^)4LJʪ \&rrKW$ʏK?*+j}~H^N ,PP-K|9`%w&!0ˏ+)sOU>{i У.B _w^%4pzRo^ `0MyA.3O'#Mk,\@]UE UsK OTTU"DҙqfGFә<쬷jI I q=ӡKPF' DJ؅0v8K9lȗͽ bH^bQVA&]D"{goPq]T`|yrK.ݱ;$P1?8?ֶwo<~mamAkc.njj.n΂vbP %2~v!LrN3mҸ&-% *ODP(f]b_m'.JWbjl0_V4pgX.@!/j! mZ;5`oYy""R|M`]lX BijlƟO\tqִ)~\ͷc!U']i"n1- ([VucsСJaj(sPHÒS'oپó4XpTEmIՐl71+G}Q٬hQhe !;0j~,IzR҈3?oav-MM[bc>\{pKbb544.^:x0O;v1me+;R~Ve*Td4dGYJY#lڲgdhܠC](/[[6 JGy(ΉǶm#%9y]̞IZ{vFFF.^>eRIIy6[j~JZ@0)\@ȉPW _G$$fBټ( 2|ܼq}тy>'OLg#sg[GtQÆ:gΜ3oޙSxyTp"V/"@!d[lw>pȏ˖XXݱk͛7Λ{ڍfϝrL aC IBͧ ˖,t>7m\xGgM:`o$9қAaP!HG7_l6J咪>'N:ӧ"##߾&0pQ$ =~5fMb\!Ԓ̆0DN2'Oqq'9w6mުշ!9'|3Q.SĔi3<{cikAA;w%M<թYl/]hwo >@݉`>N`j8,vyŲ%ĸ1cF좯ߣ122$K^^^;'B%Y$רJ{2|67eɑXdHoFŠt?{0 AW[X}UVa'4e1߈FB/Py8vtvs9(u".]*66ƪ];CCV bM(MR"GqsRObݻvN6}Nqq&OnoQng]6+*er?..v:}۝J=ճ{sO9KYE巵kFR=>@Mm(j4-nDDI~nz93g57353ۼu>bM1Tf~?,]:` )'BU3DѣOY3ضn-$gկkΞm斦o.YxY,|mկkfN}f[;Mu߸`ޜ&ɶIWWvqI1gV+~,v0 ρ4nnrYϞ+Ə'?o '?muZz5@VonyF8l(SkD>Tkϝgs7|̿}U >W1 {)"oF}f\ :'dVus}U0Fs445;khI\ GyxjMhbE, c<WĪ&^`DRm>@=r*+**Jxϣ{ 5!V )bJ I7V@ E0ґ&=rejQ#j ?e+]=W#.(&//#˚rpqgM:y$3UY\.κ l*%1ţp)~AJ]]^GWh(J[y"Ğ)*m'dVq72`ލ >@e6VʼnqjĽ1uw@ҎeRbfvqsVI}2gDED0癑AH֐PpS""(PTR]dii)i$g+UFUEK:SpKT8!9ke}2dBe }i(a`K%:2-I$)!x4)>gtM[ya_`H4:}/?Z5ן<˳K/iy-#vlogLHjn=_ϣVr QhC]bt\ض |7DWOYB&d8]CsX.'٬81\]8e7)py bY\o_Z`gt* 3+̬YrLU%5Zq!gQfi|"L0^~3n7ԃl:vޏ0)x}mc€S["ݲɬ$ TSQ|w+IZEI!C7tt52^fu0MR7BU޲hÞnɛ4 ᨭ$q;\HHhӝ;a`{I޾j3m bQ~;zڃ iMՒ$fѸ7zٶLY3G,>MG߽y'P_ @R煖T@f8q[tw$?_sǏ7ͷly}Jmj(<;kp5pgۓqZt~P-nGɶ?)zCnn.Q(4}jajt?ٻ;$hޢ%P&'0mo"dO!#h hckKBw/@FTyeB/_?|-[Z[()+I(|ȉ6V{#c# 3Ą8#Wdefw7#364kmV.u[w'%wG@v$:*ΊD$QDfʼ ;+XY05XPPt'eK]=]l6l:I|\3敖/[Iu+[u@ս$!hZzF}wdLNҫWvZԱV!$&4''DEDٴoJNvT~~~g_~A#0v)^]]\J=Mo۶@=ۓ'OjE8$\hYn:66 ȪGU霤$(LJD I/X`={.Ə_@-q}w>sW'?T·Ç8ض#1=<zwNEE>{nQ1\.wot=ynphx+UG. Qh#МominfnJmr_`ie!\ƾ]/\ܜ%좝uНǑֶVtD{'þ}:T\\ߒ{R͛gccceeO?>|xʕdu֬Y$x:u#Gn߾f;v옗w);Lm۶їRqww߿?MOT> ve``@ԙ3ghPttt255QR|  8u]escLJy&?vNΎl\TTb6[~]Ԧ߶Xp_(TB o%$Ą#}H8d["VZ1At·jhԺttQSQټwǏQ=>)č&FQ1$Q\q.RXէJR!(`d=g|?A\(PhӧOS1b4|||9bggשS][;vxK.)**U^@W^%;uMMMMUUOJJ"f͚U5C2A~5vVKfM۰}Z쥫歼{'%XZ>rqيs<]-HSRG[,߾{W@!6l޼bM>xĉ7n֮]HٸcwvGC^ia>q-HSؙs{.U}]DP<:L%#k׮Im1 _QF sVGJU$~i3aLgTGG Cn2I+-I\yH(OiU2--jF4>)2Q(RQV^6gEDEw_@ƈ>w'h8Μ=? 5 s cTUU.]@ q< GzBaggW*}QчƢOB`. 5M-ee6[0&^,ڬ5_Q-ڧP˫6`~K}EWղ.g~=+/>ʧt! |m…=w y޳R"Fa+ii[K(p&2: z o?,'/D3) %LCz "ԠΤ"fQ|E;{N5g/zץ,9>\Z"K:PT6kF)>45 }W_3mmjfnJ=wUw *!''^CKZ<ʻF:I"^)!pLq0E";-&V₻U/7Qʎ5i\-_0Q̇SX(ibg?ԇQω}Bu~ (]eZqYIن(zπ,t( Nˌz:lD}utefʼD\.κIi~$2<, /EH25o(Qy ^fqqup9vz +[V7:-?44׺>h\b"p?3ؘa#M Bf$+BIDLY3*?#RSIީƆ$Zs3m2-%-4 מ2CBIgONLH}C}atDHXqqqb\q/gs4;:)ó{i0v։T[hh*bw G5}A7@F<2fqOv/2^8tIHfhGל~/ fe|LY Q^X,Ex ;G_J:ؖ7nhKj2*"JN <Ùn`K:`ae/|vNfAHE5uka FK*4`GohB1c{*|Z]T煊6GM]-/7/]·jjt~~~ORigW@L9f7Z2Ŀ~E)N-..xtܢ\]yyy.oJN ,߼|*CCC݅SL@n㫲/HCjM SҨ%ɸ|iSr̷c}%G{SyF?+]ե'QjȹP$6J:ёZ:ZyqiEEE 7{ԤM+箖Wp H JE;&''oO.dIZ2EǏIPJB_/ 9.#uc"V$ULո9MUې!?TJ̜:yHݘ>@}x[h N'iU;#I *^Ǜ9#(Zg(v^"M :jrd/;nȓ9eթ%Ƨk(3I#b%w{1<*orJ}HEQS)ғyɇQiQv<쬷jI I q=Lڑb\{Kbc544.^:x=cjfN@0upޜǖvv)))gnٶP^%ˊ ׮Ye}%HdtAVݏh\^l\Xx RZ)Y۹ƨІ=]1:ݼIeX^F:AN} nJ1 I92Yzq,o^ \T` H^{!=w uMN3]ƾ]/\ܜLaA$ݡLVtֶwo<~mamA?PEEeڶ |7DWOYNLb`k[+:Sd)2Pڻ A>V2 Pۄ4j&͋ s̷:-s708y?Rt,x԰a$ ?`3gϝw>^^Pv}7oܘ?oK/t%:rr9O2~\AB)&&&?2FRaqv)@ƾ%lp(T@v6ie"_T\qJٳ,2]|e<}^8*GѮ|Ӏ'y*||(@tKBFnyp{iՆ5 %ZښTaڹGgںidbD~9p9ܻĸDK;0Rk.씿6ݕJlRB.BP雫Y3ZFU7O_NN[$|NLYYLy*d\jʜ ދS\q4{j{"ܝ-nHzjzaacG4&LljgzLJ<zWIQdH?ITJ3fĨN{tu#FFFΞkgc_H`;ɱJ7UJTTwV/ Poqv}X@rHʒc]u@C o>!|}* A7$TX5</}hBͅJ(jGT~-,\ybƔroW2 B0RFG2BVUFUEK:SpKT8!9ke}23̅@!@5S" HHy/ͽj(}/`(jGgIGL+RߓƦjLنh[.@ H# ,)zu}wF(!hj! md9A%445;khiPGyk>.@4%bPOKue2[GEDg{bXOfzi**zz漺<4F,܌Q۵W2a֣FJfK-/nz>}tf_ؔqݣ_Wa|"q6>3‚[Z_fIB^^̼<\Es_M{WM-ͺ{ Pq 2}c*@tˑk$LuUk umߊ,k< aү !(4̅6%rrrtIp9$%K6Mer9܂% \|%'$߼۫z >IJB̓k&dfl;z#.Es#zSSB.ԝL%8Tif ILKl4T_dFx;4fu}K~qCJ"yݎ7㟽hi;mdOM{z\[CXK.j)K0 wF 6PT\\LG _@/=5לBT* 1.ѬhsW۶H Ib;OzS#.a=ڷ5I~s$ ɶ}õ} >J}wâ 45W PNSm% I{u٠]n[2{$`BI;>+w^nd1 ~F%ܝ eXZ:Z.ڶ |7DWOՙʴ{;h k yvaA$ݡKQOGtb l]6Qw?MJ}!WJ#X&p/WXr~ׁ&l6K5ot"NܥPsIIf;IΣ'SFvW]lLɏW)«-ɡ{4pBP \n^]rn]G!1.S[W }H| soTD[.[k+-͇S%Ey*J-UR i A 5ԩSd9pSe-[/$|rZ6z̊| бIݸVZ _]bE||޽{mllHfllIw,,O{v#GnTΆ V^q̙۷8&۵H3r;wn۶ŋ$MrK_3gΤ͘1MRrСCw?[p/e;vDţGb9"44jO>nVm7n|{nGGGqNeHZBk\3OfK$ =uRuK,QUU]p!)aX$%QC۶mIO ٳW^%?Vu⚭R̙hllL9P1cPwx*,,vH3reSz@ҩ)i:v36d&ƥ^:~Q$NЄsjuCEߗ2I efJTE $''GӅ133#K.˟I:6s_دfڷ͛ T῭jtzd8#Wiheef)I4f:dIE$_[W$$ęA d>{,44&88x۷o_j ~bihhg޹sg…7n\v-FrܸqTm!YMAAA=2*v ?#NB]7Y<}ręѹ;'byo`Pmq׬Yh"*_~Y|9'QW6x$߿?5aT·FB,+++VSS{mFF~U^t7 uO\YK245UUIZDYmڶ86MTW_9 իW2ddU^^~ٲeMq SN&q&w?ViժկڣGJ;|'&$$t֍%2 nZ/^&n@`7cjѱ]zus?p+"݇vUU g&?/*7 c_(TVhjk.=LK}nԜG&S(mv@'O{&Q5)eYm]F&FٳFǍ(ƿڥK>ӛ>ϗЬpu)&i]?****yzB ?+OܙMуT&SBe)/xѥGgCbQ߫۶p—uyYPPXT\z]efvζ7_47О:z-GOc=o1.3z:Y=w gH3[~A7Ю=mWRx:6eӒt{nMdy8Egk_WԒ>piDEIaho.N :Q,Վ r$%d VE]b"9utLOQT?yǽ ^b|ھՄ]C%>ap2H}}feXUYtmoT>D_4$`)\BDBWS-/󬰰PQ6"PRo;a՚U'%0kތ}iZCyeZJ ~%4 3DF mdGp89X脞~3B5C% $Zswwg-/=t Y1;ie\ !ap;ڛ¨~gu '7tjjuz:;7yD7 U9(&MkQ6>7_YK>KcP>_1_ݷPcHu~7m7/g1}dOi.{~:S9cTjp1;z.ޜZ.W#{Ðy=;Xr:7uKwƥimx>갯>/'Þt1 PCÖgƃ$i̡Cs>n"*?/@Swwu0jSPBP999MmMTt[G[&ټqTDt32TTU xyuchzgDN&$9$##|̜93$$?F$gΝ={\v)  ZQ8nE<%dƄ' gnoe /XL^ᰅB Vq EPA~& yyyW9/?Sͽf-v>|xcbb ]]] Do%ܹs{vyR8..޽{ԮEdLc<0иNIITQR۹Sk''_yg/ZNSGS%a#JJz pߎ Q}:[̀Ntn|?ˡ8\ :|fvζ7H$G_W]`mLkmY,U5 gg'6|bD׭B-p9䀏d*pIYTTn_o$4qo߾6mӧI}۶m $>z(l.+00ヒ]~QĵI(T" #م3>|xEMMu֍9Rdjiiiiddmm-<3{n$A2 +#eٳg?~I"?CYYɓ'!Ԓf@/=$}|{:okg^77,ÅnY?L^.$$5j1{Z>knX4&5U\\ի׮ߒ? zLL55HI999Be^zj:9/9!EPwxW]mΒg#QkgL:ȑ#of;v˻p ᅨ(?~]H`nn>sL Q(sBCCUq͛gccceeO?>|xʕdu֬Y$ ٫Z}dޢE~iر'On׮ǏeN[;tcߒ{ݻG}rV$g@/S^Le.&+l,gRit|olW7;Lr{9ƒp^Pl4d2&-.LY鯲6'EE$3i|Y,9w ´99oUUUeeB\ED2¶M!z.9 &]갃gKOҥ zM$Y8tɋ/ر233%9fTZ[^9K^z%W%-[֭[ݻw}H1dmvi޼yVpJ{_ȵ̏?تU_? EW>>3Թ=??Sbk'' t, OGKCUmMK "u;޺Y@w=l!$ $WV Wmgڍ-/=t Y1NB02gRȟ2$ѡP#^^^>>>G#c׮]}}}uvӧO%4kLf===QQBڴiKr[M6iҤ;w̙3F255@*EEE؈#ȒץKΞ=j67жimL yrr==Rkck|-(* 7nZwnZֶj]Y" SA(Gr ~'y"7ngءKL& JJ yu;N#,|H7T p]L1vP]Xʈnچ yonXaP8o޼?]vȐ!cƌ=z4-[65kpʇ~_SUjHaĮ]MFL^A_~v䃱n:###UUΝ;ZKxL2yA}G̋1s̒Uҳ].qK!֧F_O,1]1kdgwRdvaG}qaH/.OF!~g% /^iӦۓK.iժ,!2먠&ϟ?/Q|?-% [J1 mmm/qټy39 wUbS i۶m .\`@ aL3f@HPQ@A  u)R((Qi뻺V f_iTc@ Zk`-<G?%xьYz1C5,k.ߖH>-[DU|ʚR^xo1a-_|+nW{xU/)>@BVhij訫Ib]j?|l_^ު.yCuX<P=;|.=I%[,TBŗS6zTTxi2ؘaQRh#TydK+N];ݻB=;zlFJ-$'e=ʲ}XUU'$3 3R'B< fi tѕ,\IJrnFZYvpYO]=]觚Z^=e鞙c]Yd$IWȘ;uKJH9"T5XٯICi̧_>,8tە;klOf˩kv٧ֿ#9C_Hg޼:|BX`eaYwx6Af֣Y3eii ߿%5E Dk]PtAY1OʥR%مX8ϚHbǑ7{{G7!)TV]-](3k #m,9x"Y>WdߑFF%H߸3I @VfE |9Co<^ź]a~ 򏿚qo9FRhcܦmZ743iFffdZZ[Sy2Ȫk9bbfrM{'{NNN+64y{?~Ԯ̵$GGkhz*!")3=$e}]VwS[iuʍ*Pʯ ,IfJqQ2?裿v>vz{M s,̚1}9݃oܸ>rC?~1s?B.HƢ@ 8y!̙IƧ`\u7lB5O9㱶DLrꞎL'[UU̧9nۢbR`BE]s?ٛgQ|[\8/Ur^&ɓBJH˔osGtU }mn%߅x:^Ű'T~+DP0F)b'ɯHs;;G365hEۑG`faH)415tZ&:xF;بǙOtt}:InȠ+jjjmZ.GǨs_αKż2P agI>Hں\!oc>IR辽VII?={zyz|/kiiiӷO^rTOJa~\%y  mtߏطiif9{`c\Mr/}JSԷss+kia;m'I@ `e0<|ɏD.W]qM1㤹MsC-Ӈ1&:v36sKs#]3=$e}]"5Q@X,Nj`h gHP88&..NHlhTv:d?>rY'RҼys}}srv\JyHpeV-ҟ%_@Oq>*t\>B=|ncP_k \-͎Mz$ ?p }(Y22e%@|_֖ޖiFf̗QiW^ŸldGPBU/9P*ұ.XɑP%=8=0yQ_jjj> f 'a6D'[y1]1k7j )j/fe%|^ʃUV6bL}~'jw{w︺Y5oNWheKK,4ͭ}W1?Fz ʚcJg_ZϸQƒm0cm]  Elk>6zȟV,֩@T*^شnNSPː@BvyW2*~٘sϗً]%A5&ڜh,B֔c\C!DvGU3(*?T5 >79r!@˹?M{rd)y6&ǭU_OȬf0`9oF g2HPu^,Ɣ<']T/ s+hbBvȾ2Pv % B=}.ؐ{6i05@!@WX͗D=iv(vNTn} )O__/.}!⹺`@>dߎ҅FH)1'!*ٗs^䨩\[ڐ‚ Ѥ>)!J1<>OOO˭Q}Pew!,-5=9!JWYuꗔL$նuˇ/ۣF MR2'Z 3!:Z~]EEޱݽBS㬇Y:ڦ֖3\hBA~^r_<~AD"Nzy_< aЄ!*(66۟<U&R+(( AdT EEEi)igzӐB U999nޮ:WЅVwS[iu dNV"5Q~~>[g)Txu/whG:8FpvTeK;t@(I|YhsZDj Xz7؈@C UZQqF={8~Fm*bnfJwhį~񲣻j-9[z}{>b)3.uwAAᅃ?jx5 r)*3m?J&~(yçbm;Esuʩc?.t͟=mgmN'%gH=f7!^K"A֒UۓoZX,bsrB8wI{:P& )*eTĥ/Zp߶8َ0M囩~kyƿgZ<̈ijnz>U~v+]E_b,߽6lZ1ϳ}m $ @%;EA߯ۻyzՖ='EE]h{cP__}~!՛)xl%irV.Vss㡓8E}'$R^ٲ+jÎN_~b>io?LEA JM{DǏ襥>nhf q'ɕW/]HyZE86--/L/⒕yaSVDXIy^z&US,s&Vڵnp̝4xĹ] B k:t!TCz?'ɏĪ1^eƷN}7E~YO^t?˚o'qƐ !9i.ԂWoz,kkzfr`B ?9q ֵ/qF.4hj:5gS .`ĈVRSSSv|ɣ4~$jiILL6mZ\\~F?GO8@ʄQ%zw.((}Iu䡕v8oV|y܇ѫ狗m;m~PgYRx=|f'On K\o;gY[7[;#@ kk񮰨"P -**g̘p4규DN_}Æ yfLLLaa߿_{h:$n3?G \5/uVàyt9[fZSaB 5PmEE2_TtTM^fsKCU͢'O>x Y ZvyJJ III;w;v[W\9rJ;#F )tϞ=H J?èVZm۶ˋ|+V`P~|H>$Xd''˗/;;;{zzǓUd|tttNJV?~~ qرa׮]vvv^>}}H$ZZZu(#к DP]M/GMnK#lިI&s Ž;;v@vJ|'6l diӦqIDf%Ȩ56a„g={$M"UfAI$'B՜9ssYp!Icƌ!I&ɺE-.jNB) _ۿ+?ShݻuF&O&t˖-WqqG}D*l޼5ZאBe?޾J4nnT{"*a.]gΜ!$f]|ׯ_OZԱp^Mi# SO _h 6nܸuVYlfڵkY&zzzÇRhhhj $|ӧOO-DEEǽ{J?䑤P)BsW6UUĥڪ?T999t!5Nz^]Fv̽gϞ֭[+lPkt fɄ mF2$GY?-bJ-^|/_>o޼>oYbEZZڔ)S45˾A(ҭXodE>TJQɼj)͍ǣӱê_VUԅ ʯFmBdm ٳgώ;\]]}}}?~o5[˘T!C(vؠXdɌ|Kbb"Unݺ/^ڵ̙3]vem۶nݢXK%Ke#ܷoN#ۺu+tÇ Rh]tG䇗/_:yjΌXe+TỊF 6y矤|ڵ$73fѼO˖-?5k-CCӧ/X@k#F۷ow*H %qPO,մiH5^A_~,޽; I={J'F>SL!Ə?VURh]u}Y~X~-Z̞7WHOԆ<޿kʄlccr+k{w͛5Dm[o+@c%V +ڕ% /^i&LHwX7_&1/SHU}Y?-̒w=}4==/]T92SWWwK).#.B}8*B^G΅GLt| Lضk[ta%g9:9J6u´ɗW̘:t)FjZm۶-\pǧJ?d!ёQ[[[{8PRhbcւ^ܝZX`oZgמ_BG}4r׋[XS=LV9yW6^;55n^/c>% da8y> @ˌA k999+|ξ΢*'%|S<SI MJJb3t|--M6>6><,Bd7+dt0HQBux%q_7ˎs>3e:.4Iwl?=eT.׎qBnސ.'@&"mssrKJ|f?6-l$_-dV$ 0X`)R-_><Ֆ~ZPB<@%"o&)#кVu҅%'sݸ~)qrqfD g?ASޣVA߹ׅ*%1OLs/\?~B|4.c+j>(o=qްwe=}aٟIqTo~7cPvΫ;=674P7333q ZZBQKGaK-Z31+6ekeŒ0/-;c}ZgiWWK+kIy?Ilǧ?ma]]7{V{/.Uf8'tj.p!!Τpߑm9V%WS?0w\HʹM.wݮ0?_Mٸ7|՜a%\Z{ՃZתq,4{m톔{)-g̞GW_:H5YKw{yxZURrѰ'T~+Z¥4"H {ջ*g0zz{;uTAV U,HHBHIJϲ϶/YRWC>2>cf@R%7)cU^7eAfwal/?OKt AEPpݒѺ*#-Lo>^dK`{.j!5~P/Tig?Psrv%O\֪|5QUNUk6nh"o Pv3P6}[ jt Sf'".{sɔ~sVѯp8'Kj )@ Q,VFٵn<6EV]L ύr>(KKulh;|o1&\AJiNw9`Hɸ]6=ni?udw.A k8 MG[ 3C]ʙJ}繤b?ڻs~uQndE ` 9C%UsK4Z4/UH}5t +CM uLtŬ!KF)@h<ɻ&ɪ::jz Gs2+!,IE5uk_ Ҩ{`f]hBBU#?UjO&uN.GWe5|@B뚺z?/rdù3'r*jܜ}pHa@кO?Yj˾CkaWe RȂٯ6к_EEECaaQEbauhTJzw&RCa=!z9fdC Z2PQ!{wv* /8c,5 {Bfߢ*)Y"7R(Ԋ{ʸnBHqáD!T`@P|PŸldU39o0 R(>_Wuȋ\eYExKYs f"FfߎF/h[xgcr;VfsBf/ lZ7W)A :ƶ^Yv6{TUT#^(a"W@<23#b[3c#RXPPs>IcS}B!𩃋Czjzp VQrqq;V Pol,0(25{#]q8mNJ0^8J+|<P{B ^[>L!gJUgw']=݌ JUUV+˹?M{rd)y6&ǭU_OȬf0`9oF gxb1/JCQ=66q8ƃ[|­N'>Ν:S'_yރ-Oѭ{yjΰi͍& 43c߫GKG8e.0wQqq3\C]8iDHQ}:݌ASqj+SښћH@ AqP~"#D CRujV @-X+p !ܫcCmkؤנXtDz,wo< qnr.z:>圡8aěi]4/.^햮?D¤KTTUNf!>$^R=Kw6Hxֿ#gҋu[Z_MO so=nՔ{WVKoHC md _zM=}a H,**JKI<;4 E>|qk+WX͗D=iv(vNTn}e4jFWθ!]>]]EBUOEE{F *!.SN w!%tkw^FRQHl?"N˓VUU%i[{k@@L tUO/Yr!! BLPD9N!62.ݼ\޼ys%񊳛;[ZYDTy΋\==)Sv 萵@ Eϟ@`DB=I/(7*b޺%uemƢѹo eO0jSqdmnK§ǫ$Q9!!62&$+=u.mS3f*脘XܡUH2jlT'z>;dmPC=7}&mKcg OjҔNf'".{sɔ~uz?Բ r}+_t~?No:t[sVѯp8PX"?FF=Ku,Q8hdVU5f`u;]jee2axW:cuZ[̍ZGS]7u3dy8.hzw't'V yu;N|*ZPjHȩcbd2O~vz:Y/^~RGWDطc+wT>UmMRh#,;BWGf&͞d=ܭbR0Ip5Z@ӄܼzܔ,Yw'%~޼C. .DDȱwOJHaKqPMMֆjxovcie+?I{ D$DM>}lbf%_L&!SNeώ6D\\m[|0B|Ͼ=n"EsoMY,TBK|(MNL))((-R駖V’9}]EEޱݽ՛I t+RGEUF*jJ~$OTӻWJI:tp]˻xIYu&/sTo~7cPvΫ;F͍̌/25߯t V5B)Q+xG|555zP(D:y'UUK}t$RN e9y&/{P'!qNS]hCCIֿ#Zr8,&]+=(r.zBrH{roo({#)yR.տ‰}l?=<ćd9]ek}5ɫ;p>q!76;hbu[5eUsIpiUhBfe<)?63#HW#a䨩H8Reܜml.1!Ց{ I%BPNjj$|EeTP"n_rn_DQ݇SGuWtu>x"k 2 UB\|* w!%$ &=}Zԥ;3y[ڏL'[UU̧9nj2mµ;/]+;YE 톅x?"s\0.Rh#Ρ]dxi'YO&] faez7UVׯܠ srrܼ]u$^Ჭtcw bW8OܔcKk˴t3 y(ʒ4ϗS99ފ*aR&~5>'_}yR3>#̒<>,]"@J"m}۴43Ӝ=Jw>s]eқ&떌~J&Sv%\Zq|8FF@ϫgRBrnnt5GǨs_αu(3626?ޡm[oQO e#>.qqw>gljSz^Y<|b"cr>\F@߯ \׮]{MTDa!޽\$XKuo'U% |-+ڶ4;v6O6-MY7 e3C}/pˤiav"rhψ7L7gAj #yRK Rhcbf]yZ( ȟ욧M/lnӜPvvҭejW`/W.rG/w<;y堠kך 6,)))44tΝH=v[n\rȑ\񉍍MNNvrr|g||4f{tr$?TI.N<);Y]1{Pˤ.܏ٕYCpiBW}@ҡCc.eϜ9CI$ (..k?~|T2$ cC 5k ˖-#zzz$P)44ej yUUK_3+J:99EDDxyy]vСXg_crzo[/HPGKALzv޽{̽Hg}7+VHKK2e&u ֑@-&RK 駧N:8pիWߺuKAyrg)HHHȞ={vpq4޾}K׼>y411&&L|%K*Iz-FeƯ8!{!m\?~ҥKS:uwܹiӦٽzW9_~MM()j Tb By򂃃ORv!C3fѼkrJqlٲY[477 ݽ{7 tyttn:u$P7Ȍ4裏>b^bŊYfO]wމ'ٳ~"qܹ$nڴIgY R((Wo߾]yaaaiii$Woߞ~^%߽{t~.]ʬГœzŲw߼y30?S 666wrfPO۶m[p O5tuuI!!g˖-~~~ $4|}F)ӀR ye)MATP2 R(P;HxZ&}Q)X (jii <1я%ybq3g4cVxP ˚%;߷ -ۈ?K/WWK+kIy?Ilǧ.^e-_|+nW{xU/)>@BgO]Ns44{De?hd(Y}%iij訫Ib]j?|l_^ު.yCuX<P=;|.=I%[,TBŗS6zTTxi">zdK]<~;bu3JUܤUz9ߔg^UO{ܸvՁ$Oj5*Dߺɓts{UEs ]eV)*ޱ əndl֖Ijbj0K[G”szxgjZF5j0 P2w?&)4ݽ'dل<%W$a)+_21uZ_WMroH CB#PCKRM`]lפ!@c<<YxcKMu}KwpiEBcut;.$fڦ}gMK*G]ʹG9^v7?jƽ ݕ Իׯ_հ>Rh#clb(Q-Ϟ<+?U&*,,$< EU;J3VO 闄Ϣ3QCCj.qvs}DWOקS<|b"c(jpJZrS\(c ԔF~5PZl>oGg?$,iav"rhψ7LG W~そth]r9+UCMx)oo4Ԥ;;ҜHw"QȼW-r,,[jc˥+_2΁%fdlԣO0kePze ׻0I K,0JǾ͎.s},屃:u-SFt*Ge޳v SGvir7Xt'@c yeByKq9C'0}=)샲268fJL uL\Kp/%v6+f 3YH7 /8c,5 {Bfߢ*)Y"7eEJ+4vHP+JCw(TP:Jd_XBf@ !@˽?Mzd{&;p<Ưiwy}9BvC^o/*#Xʚc0\7 !@n*w?Z䞍PXe U,iݜ_)j'hxe!٘rM#SVnPx_fM R(ԚoCv4ۡjE凪}fcF9g@.Pb9|[y,e2丵J U܌>(T)jUkݔe]٘Ýke}nM R(Wn!ZSḧ^5ro[&f(=P 2Hs' !62v42T~IegDGĈb@kjnn 狋r?@=|HPDи},,_̤Y} %WW>ȇ<#<>OOO˭),((9$뉱1IBWz!=5=wݜ"(YϞfS)Y &2IScf>~>"5Xz,tsQO_cjj MR նuˇ/ۃ^IAݼʎQhhjl.JinӜ|1TԵӽ;)dٳ!e>z+:!&2W)BQcbg>-璣BXqOŬv4#Y&?"<ڞ|#Xlb#۵Oy mogֶy[9q/cuv~]_&p 2}aSt"EE!޿8ZywRRo!MX/|H&~Xr|NNy8mo[lwYK\uZv;m?v77Sמo%2_odGaӊi~'^&p$B ߾q(S@yOWJ|b@) +o.0w@Q-GLVђw2_~ornG.>*9[fqoT(Ǘ |R<==W...(P;RX,.**z];PYVo6)f#ՠ~ocM<==sssutԵH*S3zyyouuullɎOӋ/Oxzv r~bnnɱ.CWKrf yڵcvUe?6ގG qׯ^J7|>MMMU_}T8Ծ0?%%a۷r:hk sYab?JRkqqq^^^N΋D0444N Gp{_3{ }歪*3!^jbHV/g~'OaZzJ I`. ;úwAB}BF֭wuuϙ3zTL_UyyϷ-ɥq$::Rvwwx;lO W% ߕdӧOɎ-).,:D ߼ys9PXׯ_T%XS޸q}޼r%eX"1),̙3ښW2y2utU-R~ zĉM7XKZP 'ól`?֭W\btܿgϝ]7oߎ#FT{Hx Wy+]\lU:&Kx80_l*0&hwFC]]\rn^Tݱf&PtoOWjbg`Yi$vvXu}}\ť<Ζ-[~ʙܹ K۶mMJJ5kDww7Ӌ/˷oZzubbIDZOY* Hc+6@@,}dEXimduի\Nǿ$޿_9^>H˃h=L%JMڜDo; |+X_MRy'kju(7G7o^>}*~R]$*U>gfj\0$$ΝkYR@ +(xGRs;w,{^C){/QQ&{ETP jQc~֟[LQP_/;{{W̛7y73;4bυ:d&b*nŮB 2{0&f!!'09snܸgrs׮K刈VZ;99޽{o}ݻ4R2i&MjӦZNMM _ ^_-ٳ'3>>055eadY] z>JLfObsRk{-[|u*)-j-5Y =z;I# T_K 4/;Q"&˶]Vwvk qvo dPx`!ɒ% Ϗ>?77wРN6 ;vXB;; /D5)))ӦMM Wdl4f3N1bSAf.-{*x0 ^39AQ2 XzMl֭[˝TAkr27o1eR*dK\%"o.&ܴiZ܈K9wSP|ݭRM2Dm@(Z:?_ rOڝ;CC=6;~%޽aӦ6NNjLoeeeT.(]V/z(|~]RLL3dhn!b媔Txa?ϟiӚ& ncG+AS?~r!{;;+04t%_|Nզof (%:zŲe˺w&w&jR/{tw߾qcǂ>ܽ[7fʏF |8^z`RRZzĿ jkռC]]8FQf>GAf獰9U0={~{СCN6nLܲ%"] @.?㏧O|y$X' yӦgS70;oUn\ض-Ŀ~}X;!_݁cB heK"dMSՊ#M)ܿ~٧p7==S$%'/fGtt4YDUΟ?oydAdnh"̃˝_8֠޺ Q mt0jH?Jv)Z7&}QiYMR,MpD',,[/?g(`|xy$tR5jOiiiݗ/_+]&o߾=8 ,P_g`(l: }R6g޽̙ۡ>,G3Y˯*44aÆǏIh"|;7͛dFPiE#~icǎ)^zFEE3|߿w ܬmD4 9gtŕ-'O?:aDYٳM^^\G1%m[U˗/'$O1LZ9M򂂆 D `]vҘyQs*xMۢL53Xx\vOmoǶъGߎ:pk$>J󌖆0DygZ<$r_e \(:|FNN#Bn "/T; %B]^Jf凹[qzAo ZBm0*==(/vNnn*!5?rA|Wo0&`;#W@$6_wo×.uqqw\ ӧ>.]ܱCd0ӧOC,YxhT}ɔ/mE(1o{GAO f*;~x֭vdЖfӦMe 4pMQxݺnUKgKϦnna0=w\prIr?mߞ, Mw"X c];8|/éO>sٿ`Si7nI? dLf qnQYҩZғW[X,k\J /87M*޽{ĉ$*5f$h4wNRu-:a}c]Y"sL3Ljܸ Y*UXpʕ c6#9ro]eHÆ JMM0"qFh}\׮]. V4ĦŋL>X]t"88… cǎ/ ~СÌotQ?~ۺu)YMɓ8\ ?Q(++7mڴvuq&//_&+GEE1oVAuO?jωW߹# J"ܹYY:[Q7jhڵN#߿ojᛴw?| #"fn޼b29yۼyDn,sIII ,Gq{{;0J ==}5kրFT߶m[dGGGEDO1ر㣊m2jขJKJ3e ܿ "@(M@-;>y~9%k„TN}? oD?nȼ}D>|l~6T>>Thjk|l6%l$R򀏏L&Ca⥋+S$ry] "u+@ww- R+V <{v ݁cXXh *Aʕ+ <~<> Å"F$ɳ + K+䌀qWZ(|,49U02;cC6O͚XP4IIjU--=9ae#,OIWb5y˜ëV޹E)) 9s$&&9{SNըNU+66zhܹsAAA7.dLf qnQYҩZғcV6۷o/nHJ缼/oHؼ d[mN+wsULGv3M=OM-I/>zepH͗罬]P:(fiU/,/Co} :thѢs0|9If 2cÇcHbƤp޽wVX F p۶6lبP(ݻGÕJV\Pp3V^~Y++!dkG{Й7o>ZzȐԼ47ndT.l<8tWk;`$>B~tG+ 1եəRRRLM0l}{ #Æ Ci..ni4j>mt \>tZܹƌ$iEXX2 %HŜ`%`o^'RRRqP#KeĉX&H 662˛p++m [,h%L_tx |ɡˠw R֪7\(xn+Sq^+:I;5+geC'dVNg;5Z56AUNCaHO𜓓XoU& ѡdf1hFT=TU*${A=) U--=95s,gLWv%{5Y ]%[;*))qvvˆq0cҲ2UlJal80-mٲ%z1Yܢ=STOfVzCǏns}+WXBFl$lU`kPI& 3E.O /'th-קqMU~9=sX? _oilj"C axJ?o)?$P7~ipss9sFϞ=ɼ(g__(\n]2L&Km˷UMWk;;1 M&lo-9УGmZ[[ ^lB _z.;8wn]UTgo2NQ */4xw$8~;WK FߑPwV Xp6()NHyҌ Q!\~/a:eebhӕB]I|پUPpAGVDP0IDrXӳg/7X m$u6$Fa*ů\ a!C߰!:::jƁS8lD,[_|Q3VR ;vX ?k`Fo <(y[_aê&"`rsBHUZ&8CxjSъf>[Al0KU*F #Eo4##cVnc3T6Ttl[F0,oő[ O} gkG{^|2[~_߾ըNU+606-ZXD[}yy5%-#:K:UV@KV=Qc7odÆ5'fMѣƍX? 4uVd [߯/j7v 6Y΄~RycNP`ɼwwlWnXQ,*$\41b*mKD22Ȯ]#G |5Z`I[x_P>|I&5q |nbƍ.]L:.;us箙3gVg! iiprr2xTڵKPͲeCB> 'CrȐaU38whD*X çh#vR)]{9c…Æ Enժ%J) J%ubuv..oyOicFY)cww >>^ ++%pI$U9AZz65  ^Әv-{ٲÏ-11q,qԐ(2:**~/xzzN8ql#lL5"6fݷ-[⃃GfffW#G~D|i"5sfllǍw*jQVڹӳN)'=wb5Zьg+ b56α[Ŕ)̘93///?$$zPPҳ[cR7 go! ._l6 FY:ݽGe2Xn-u4ǵ&>ٯ_Yʿ۵k8?~AS]7m4s%{{;*}􉌌x06C}4ի#JUn]/Dl4{mV@LL [T[B5 EhŊ(~@QGV2e #mݺ;w@(e&MHBLǑB>#tXDDMk$Mӌ5*6v%a>Ĵ.ԭ  @O,CnWBl.:99=rڎe> B"\pAwWhBuD=y$ #GfY;?ţF̐~=~N8 D5,A1cllEy3^0-|& ߡK [F2R˭8fT-.ν?iji0L])}TCf*(VӉ0 {ݳAː]&OLOft"y 5LMckk tEEc>F"l?Z$TOe(iyGRzbŗGc#[՚6}}'2l5Zьg+B㱐mUF{3kK++O?E*[gS7E`,a)"lllV@&N6mLڑ]dr#cЁ$3-lJal8+vdfGttlz^ݱm۶?Ea05x*5ha&_1]صXy-mhN]&o;CܬH Ԯ[Ŋn5CKȭaS9\;w?;''e*a>qq СCc#Ru]J2"@KKiܸɫ55j|~}13BfDPc\" ɄϏhժ8ડM\jͮ];<=L2#/i۷o b(?dQFWL̤E0 )ĜEʴi cǎ2Sf'N0^P"1߻wϰ04ŰfMLUK0ɓ` r|CgyzY?J? З$<`SQ#25 jưQNUAh? s/)th,qlro6ߑ[C=z1̃"ߴSZ͛;(MI&ҐDlllE(6ʌy|~AG4M]/&E._⫽#G@݃ͭ8DC44Р1̖zS Y{yUɶ|)B3@TRtĈ`Fݣg3m`>R7 \0גq¼XNê%a!ޖPH݋1ruWs}X#N"? cq6/ϿfPFagU#wXFK> I:Æ| `ck9Ww_r_`/(@{әБ5`Yf]PCS>wR& ,r4%@^iZ3Ѝ5A (%ɘ@mxoDmS}Ffy=ȶe8*4|]n组`9)TPK b G`eG5MƖIf]'o?,0 ҆~Zs~pi`rbYB}gv=z~"ejlUJhyxi``h0j8F`0Dì˗9uQ*:IR;"deeJҲ2RXX L&H$HVZZ/w&.mmEE"H.K$99RӧxvggPFdh_z 2ww AZmee ]\\FA_p*%mll pppx%(??R999?6#|UÈcd/HjꗔCbBjA8V&@vE{ii5~8#t:C N =Ӗ]VV(N =<<=777BrO: s|>GG\}pɹT+GG' rssyyA/((S {^^~ݺuAPԴ\PY/+MPʠ``V*Gg<͐z{CLq1փj Ix0S j݁BIc@J!=2hr1$*A :`v8* rurriy H5XPX rqp]ĠyS\\JPL.xĨA(&A XP@]PJ"1(eXa*[-ٳ TCKUCqTR WIP4ۛ0$w!NUH20?yC3F]AJ(;!x$H>'; P^ϗA&l2ԖI** 0KPqh@#$l87C*0bVoo+z=܅h`4fgTuM73$H ÉZJV A HNvGH@zޡ@SŕP%*@? tz=4.-ΠtquUA`e Ɔi\\> lTCfDžբB,𤐔p?%u,Sԑys_r#ԪR*};:mz3Z 0W/_T*u)a.@)AV_ȀF Hmlm_<(S@mH Z(l ~JKJTƆ:yhP rRe2(ѽ.ԖA/^d EB Tt9G"8ܳT:\D?m냵j *6^&cL_2ܭ ʇVnu TJ,n@à?|]CS_Ƨ_/¥-Dd1>?a.ۏϟTL)-ÁNO,83V,=,ÀIJ) tMY \ cv#G}}-}}%gk k,yJk0?p Fw0gedd-mmWN~H$,̉ϘJVx3R!śU}uOA7)D ӥJ&tӇEK76mƛ+Z҇?!۠mǁ +) hjFA\@SX!nR d?OTC] -/,z?JϜ!yg0:g6p0 J1f1c3P}raiayw9? cMs>RNkk+yyyFCNRtuuLVVeeeb!PsT*Ekk+PRRB4 ܼ<"A ׷ړ8I2Pi{{;mmΣ&RT@q~ۛI/KYع*^̪^ѥ)|!JL&6:;;5>+@" wk"NJq~SSRJ[ZH&P%Ӂ)ijn+zhjn&L"\Œa%*Q)M 9|48gPvt4 5 OLX79&~ظK/^(?j_Gӓ&0W.?KW gs:rS>+~s{ T̀PF,3ŭq=U!4LCi(>=4x D32'//OWuRJysE1{la,\Y.bҤ᳖:~X\0p؊! JDb;#dp}5ݤgః9-oR!X Ly  94 ½OiWa&knLN]~aE3xiC .C AuHAu}"7ČͰX51s[{;I=OdddI^nɒRINnhoo'77l!#!;g!T:;:innbʔ)8pFh\2.Jݭm)_kaXwCm譿ƂBR_һDLDd2AVV^+kGEwl޼Jͻ sn6^zw='L:X,O~cBp=ł !++~EH);w.SN +(K?ljkjJΝ;GOOGf{ˌqm}v/_NMM > uuuttt0gݳӦ!e#Grmݺ\x1d}{7hkFƄDrܴjdqqΟq>'Od &H&fdJFTf=W\y%"s ,]>~VlO"$33ӧO}s,_*#+GH$̴8w:Oߛ,Aڹn*@v}@&"ј5H>̩'5j5n?M0V̹xַя??cѢETUU3~xn&.b*++;())a|k_cٲe<ձ|rR2o<~q7o'`ikk,#FN-?lذoٳ8z(/2B@2q?pwsϒe+v())t)..kN8~ .~wߨgH$[oeJd9?cʔ)L2E_᭷?x5k˯H&|g?ʸ;?= /~'^}UoT \BčjU+[h}g?tam :"CI()"8J'GֹF?DhQ9`v#(0/j2&FG]2:̅$2P a+Š@A <cvb=^l]w›34#S!~-" 4B B+aUB]On# (,Tooosgض#j;<|͂gil\M"" f 33T*E,so):bhBIXI jIܝ.л\20ͺ4L$t%&Kv(_z^?aWZ8 1¥y X ih FR{I=dt9fPF)h(ĢnXB3}3*` w I,U&1A:Jp >ȕ-҉+D5;<O;Za Me kbh6M0EqNA2W_ìL&ȈyT2E2b%0Y455H2$lŋrS$--ʹ55r7q6Ba1вL591Nݲ Z69JCED3YYYO,#DJIo_YΘ Q֮]g%33]wSVZJnnWj6lI&q7;oS]]͌3b\xD"fƌ<3-ZD$!JIYYgF֮=0eg".rNFAA> %wXbNz4#GL3fѣ]C^^v 3Ew֬YDQ.J}Q;JYY9L:̙K}}=/<ѣG8Yf5&NDvԟgΎU*Du[P}0sd&,/|+ ى `$ eWrʸ'm%3,a;ynQŴ1kL;dM H^.c'P!"GrOs P,H_N'{b}X7KMaaA&eU\\^%Cab01 v?tceŔy]?UUviFZ-ic3CyEz=%Ф; FIII<g}#`\ED"!b̺x.BFr$x 27Ƞ;Ռ3'OǥyUZV٫^ {k =JrȬ"x?Wb)F" HR\\MҪ6]9z懕@_?Wȑ#ttsYXS4“vuttO\vetM6N K-,+D‘F/4 bHo)C?*|jJW[ ɠ銙bha!nS7 $mu wC&V+0a} &\͇Yi>H;d >^A$>F2(~=}GfO,F1_XrIw_.N_|Y3߯c$4~2{v) 9T!8'4 B_ Νe``肨XFTSS{& 8**+o.5ZŘ?SgϞ##"d*EFFDsr-RV*4H R}uh$q&޾^L2b)[Xx*+Uw)SSN1vX2228x? {:<4Ȱ?.siw Vt3냜MCZ;[U_&[3 sQϦA/?owQ?O~'f6aH{ͼQf.H|k+>C+`4j KS׆S\I?!mj|6&x?Pq04'ݢ(}IHB h4@x! O"$uHhmiݡ)"nŨL 8OIIj&sK8 I~a 8cwM!AZ iL駛FʡԠ'tn҃Nʁiz`cL~QSnU k۔A z>J(FAPH7n+oC|2$a06PIGOyp1ܝxH)4,~BA'CH˃w_V--m> :[wC\"»8KW=3{8M6P| qLO7qh@2HV~])^- h#ޅiKr ~"Ų[iz?OB7j|X T4|4M:嫞=nM#l]iF"g*bB[0>*~f4h]$ '7ߗAoJWW#i(LJ:>,*&AneW%`u|(sQbOWf3aqA_YŇ4r'2?Mi(-1> xȱjO5D2eI/ BDyj(M)YJKK !gΜ\c XXϫ;.`d2ɻ˞{y8vAr!qb7 S9h< Sr,P7Χ7vR @C?qrկ0xːO%9إoդeԛ `Z F鏦Loގ0h*3MMj [P(^~zdT뭡.]iRq$ ▨R_7V';x4-u>0t/=ތՎaG+'k.Nڭ\Z[.M&~tkۡ}Xϯ\Ȳa\r*i׺2lll ִ1taƇJ%;?u7iNlNjX,jv̘Ҿ|£C:<d<Z~RSZԩب.l@K?oee$d*I*D"ٴiDL˞={)Z.L2$JE2c455if$p)]<*\4 IDAT֜4NpBÍq:H{{͛7@8pyQ A; NMvzZ%cLv@;~V|I?o.}ʣGCZBU|f!l`vQ4M:̦l'W.lzC ʹ?3a݇g 9TdC?#_Š=ƔKj8A|7?DW#˷=<>wAMqJ7A|v0EKް8 mj<Ƒ]]]L8X!i#&xXjnDi)_^"/"GE/W[F`o}=xODn_qq-0bcTmBb1r`>ls=lٺ&8}-[2bkwyQPP@{{;~;ƍsC:SU +W۶̹sؿ?_~9ɓxi"(.oNk[_hkokikkk`׮]!+_@COOO/}}lٺ"^{59s&[m͛9r$_~;︓W^y<ƍG$ĉعǎu<#,[č7ҥK!++e &%mm455w^"Bk.nJkkͼt)\2wcHӧOs50rH&Oo~[nfHR| _`„ ? rs'|ޯGqzzyhinfM|[u6j{\3F[nu(_*z<] /L?|8ΡCehok x?>}rw;n l\߀=:EЮd0=M «0YB܍36Ь] SklI?-#bl{{v[py440)u2dWRɧd@m7_v/QtןFW?ϲ!Nn?c}Ͳ$ w|xz4կTRkPw_ o Cӟ8tmj AےϞ^ HTMaŀ@Ayy9 Φf˝ ]T Wٰa;wo"+;3g"7/O3f222TWW2bdJpɫW9y$盏=M7ިag̘wAnֵ7-կiӇCq?'|FVFss!rAf>^z)w~;W]y%_8r0_Q]wwطoG&Dغu+;v&__h4ʙ3ghim#ضYꫬXRCdeeCiYӦM /dΜ9[?(_{!6n@n^=ǏСC?&(|vN!m*!4O|:l˜7FG&n!6&5Wס+5f0W ' 7)9/@l5v4&O?~j^Bxw\fdàeT$ee{Ͻ={Kmm~D\mM4rrs)++c֭"\1!gb׺LaQEEEڵ]v1LC#G`LM 6mb*ZZ[F"D̠ЕHǏL$Xz5dǏسw/ـ3UX &P^QAKs3eel߾7~}l*>l#R$մwt/4uZ~~{a]Ԍ@?GaϞ=tML?|1c#K͕J:cƌ!33.={jjƍGJΜi@AFFyyy9s{inn&??MOO̜IQQ'L H0 ̙3yIv'6mW+U'C~)zf;G4Kr)ųg;ELqO<Ȗ-[5{67mbٻ2mruױdj2cͥZupﭯpIFW#G~"3g뮣 E$hVb*I=i;{P@ 6k,# 'H y77ə[F`¬(W9czx3'3ː]clTڒC尚0׉͔%VeS4 v~m|MhWBšTmɔ+T>2 8Э'7>,;{`),>b[Zwb=}*,Z M wqEP[Wj47+hŹs(--%JpѣGE:?xN22SQQiѣGSXXHوP^VFaa!3f2n8KJK%MvڵSNQ\\i8x cǎeWTP;SSSCqI ^pN$;'gF{ B'54-ə&N3f ))9(+-R:u*#GRRRB,#.3b&NөQFq饗r!PSSj5bxaf!dff2qD*9tL0l:uE9}X,%\Bo,Sr 6}:gƌ0qDoNYY:1vTTT8疤+C0iDjƌ}I&h9byG90hЧ/:c y8qֻ[HX,hii=]$;:;A\BQAMT@FEjV꛰ \a[;EZQQsяرc۷Oeر^kJ)_yD"wAAA ZaQ̝kзY nBIIuh0vEU!ƍ^s5nYX{嗹ʬW^ynMѣ\"nV*p1Y|DJ?n:qW*7Ʈ !>괃ra}Nf חa7)iill:?C3^zmdMix %hQ:$ooS5Ym4OoM_׉h(җTtÑ~uۏ'4eLYaرU|Ȁ_v4t?X* =й=ȷ ۰VuU3QΧԉFڷD$J4%77WXSY2  ޒ/C},̺Kŕ*δ*}[j5PY[m6IٚKMwi]M>-p&kbl09Nhc QJ1 Gt|% 6 gڸ m?{&7a /Bt^w_1TqaM5XhMV䜮L>EД^uhx, o&+^*ד}rhtg`l87}O˖ٸ-{Wޱ+?@ L [>z&OiTY&G"Ϡ /p)^nm3a6L)gsA1 媒k@(~\t9wtL[ZvlZ;z F(jꅌ|iN _m80&#-z W[L9 +iSWB8T20tQѕzҸ }lf7`* x* %JN3owرX0rh6+GFƒ9֟жw.D.59b #׵~p`3ikoi407BӝgGgwrjWЯxf.7'Jd,5LJΐBwf]6·<#_%ޛ0SG7zo"J@.+>>S 9P<|w̥'hi*]yrx~jZ>=[ZރCQJp(rnBM/ii&]Fk*aIMCJC{H `D;6'*C=,)[+j 5{1b\C׀ţ3`-9\NaaĬ7աpX՟L9>iz~kzY +9P0ǯ<7lP0yR[W-5qsa GZSd~my9Ƈ"u[SeT Q:4@YIid.~Y׹P4-:G}^92 j=8bB .:aDWx9e=.)猤! zR{bEM`JIƶuԉȫ}2-CW<6*zS);ե/<`S@ÿ:j sh 4,0үX;=4i|0V%ƄVB2yRXz8$ 6WURĐKVѮӃLw/%G/G fc C❩QZ@* ]7! m]؈Tƣ%-b˖_kwi&F#;/@UޜggC-zMU k-X܉+LG: ZPwC}[ʠAq8 `l'K炿2WG! jḣBx"AGG'mZ,cq3 S@2 H!ѽ5!df-3X(0 kť\(U=tfCqXEY5+ D?;좧PVPIDE=xQd==w:̱ơS]nnm&?/,TjWt^園)ZZZ!}!mJ(+i}>myN A *G?U&x|h$u 룳lMJH$r$?XK&RRR>io,fX{Vw/]Jq{ܑĢ]82Ŭ'/'Uśq۹iCywɮӻxi"2Q:4h$WyYyq>nk*+#Mw7Y#F0n$2b1Gx8a"Wj:YkyN ӗ`G3|sb咙!(b^Z7n\?mOۧI[OOdffH$8|g2 A_p ,:;;Uz*%3G9nZ>.ȟÈXѣw=yr$qFH_;{,7e+cK} $1Im>CBMWw9%!;@VBΞ=CQI)7dIz-5aa=-dɉb/P;~!RUU@^ξrsN~'N)IΜ`|IӧPLGݢEzmۿ-K_LU8m}OWr뵓`dƔfQ;ތ^ΡvrO .Ky!\\oηq0??2d5i6֯0 8m8cIt~ƚ|>Jg;՟H$```LDQ23c鶰d*I*è3Qb{囹-5gil8/WTQrz/;E& 3OPi|<zc|8bhg}4OJ:SS9cһz{PQqꐆ>}8H#l=WW|fFX~ +lHmŞR=w 匒?N IJJ9֡I^49ڽ\ʈbZ4U 9}H/dBR$g+P8z4Y$dRbȾY1uK zqB^N-1cYx1}~E$*J_/~v2+Q inyCa_M6K:f83gxX|~g/~ѣGy͛7[ygx㴶ϳsNݻ_ٲe RJ>oΞ= S8yİݻPÒjCC'N No߷)|GaKꟿR){=ϟOwww` >wwwg}˖-_>SO~q*b5<=zT?֭[yW9fa`֭$IZǑ_tzR~_Ylj*H)Yzo`Mcaꢾ~/]]]?S0&Sm~yN5 /tqikk# ҖBPcmFOO===>|ḟ̍Hz=I[@!D,xQ@kK+gϝ -\8~(]gXqw~t, vO#?ϊ~<y~O8ޖP[w%'϶i۹ o<*н(شG)+&ˠ?ܒh.}/d;pm6ظq#ټ曬_1cƐC]]ӦMcժUl߾ɓk9|0 .L.]ʪU+cҥګ̜9ױfZƎ˲eؼy3gX~=^z6.]ʺu먩!33zUV1~x8MrJjk'F~/[K/^{ >9y${a„ ٳ CII +W$;;2VXe˨&//z-[˜1cxؼyS|. ٹsgΜ>ܹFƏo%_UVw``Frss?>_R6m:qeH$8s _~9> 'Nb̙3+W2sL~)*?dӦMرSZo_|9ׯcZ]*?kW\AGGuuuTTTPTTćnd!H$qF,\rvɥ^Yh }׮=z*vɓyꩧʫtY 6rI&L8tR֬YM$Қ}]V^Mqq17C23ʲeXrcǎ'࣏0w\x)**40:uwy={pivĉg…dffJff&ObI)ٹs'ׯUV@cǎhQ|ٳgdowˋ/:>RPPHWWmmmԌfoY3p _j gʕ8pf B333RE9yyy H$BAa!_)3)%Λpn/y7Mmt8,fU$ΜȨ*={v禮D#ODz{M! Nt7NZ818y=sIBBͭ5rL+n`]z,|0~{<,z%~SVVJRo8ᓇȌ1b|=v#q-TCi G{s3-ǏqtzD2InYQ H$Wqw3~bQ"2ET@f,2;U0`mc AnVO,vt# g ik櫓 }==ۃdӧS^^7owus^R.};W^yx<ΓO>I2ȑÌ3ƍ رc;'No'~:jkkyuo*Jqwˋٹs'MMM\r%K޻9c1gͻٳgOWbM]CWW&MbԨ*:;;),,◿hT*Eaa!UUUs`~;ʼyxꫯ#n:ٷ뮻 J4a׮R)fϾ<3</L?k׮W8Rnfؼy3>Ww!;;Z^{5jkvZ*VXك7xc)wuϨԮ]Xhsg?Iɍ .B~_O[[+cǎeԩBooAܸq#6l`ȑ]Ûoi%˗s饗r1H7ĬY7rMur-,\cǎ}ye8p{9Zw ئFK%K^رc\p̝{ SE{{;;vlgժU̜9.nݦRo/;Mwwe===\n,͛WrMMM]G]]s^7 rssy_2H__ /ɓ'ϝ;ٳ 455꫎>3,Y\˚5kK[W\kξ}TW_:,]#F0fL K.eѢ:̙YZٻw/9ܹVx .ꬳPl!_X`eÇ8uuvVsCwwQy:rt_e˖'>͛G*e}IOWD ~ą]ȗ|O`ʑ#innNzQZS'innr BΜ>řӧ/($##6ɥ---Wp!w?n yWA,#dfr23UE,|LH[ښBu 5RzhlO<&p$KOo?q$Dh뮚OVyʹʫַ_~̘1)S3ɠ1v6ZO<ಉ2<_q?3Fܩ*YIZf4HIwc#%Gog'O5*ڱX}7-o?Ϳ^ƎebgK{젱JZJD$ˆJ Q@ff&g$Zzz=᜖ɵTWWקys]" HRu֡}v^Ftww B>(nY󼦹d S뮻_6m(сD}}Xz5~z:::|牨B:A[[ZZZZGmmmzy@[[QX 1662LMMbbbee <8ӐHz̒%KՅ~VVV͘r7LA%㥗^w܁fLOOuuuBYY}QtA(++CEEؗ_mO0***pソeرηLL&с6}yVR竪R)viѹ yW-ݲe nhy3yɒ%hjj?dz>d2={vczzZ;Jtvv}/^"nÙg~}Xj}Yall^_g>sݸ;F֮wvvUZY']Ndr9 ozzt:Wo~ g78g^^WNYV?nB>_@AJ-wW]#MEE.B|3~FqL333H<|>l6d*yE:F:0 {P]]<~},vpm]]]cu]zٍJ73*{>N$ʑJ988s [e\v٥d2oـ8`7t#=8H)Q(8:xbp {>Ri$Iwyl}YA>C,Cyy9(bֱx{N46mڈr4Z6O<8֮]!uY曑:}_)tI8qUW FذegX̘wƁd˱ 0ltB,QQMMhiiA>sߎg_h7ⶻGnNh;kͼ{7!v}EԄ۶9Y23l ؄T*mlAAށH$M7RcݫlͪQƒjkK w aP,YȦ IL BO. F4EuA_Crb|CJ+o(xwkc9M up 6ɬz;H"Ls8ҩdp.$T6 & YT6։#b'C?|7 ߊ-' 6!w<oa)j0<܇J bʮco +æQtw-BT*\6d2zObsdž^ 3<|G: \|8 z,\wjCA?aXRu֡ xk֬6oބ}%߼y3N=Tڵ/`}]CpCCCr6oބ3x陼/۞%lNa44J`jj N&3NnЁ@mm-***3Ai_s=xB;v8Ax?ϭg2֖cjj =(*+rPw誄͇*x)G=񙝝ŪUpgC^---Bq78r9LNN"L[nuN LMOlJsF`pйץ\U=l_ۇuGKdz'QvBA5 XvЌQP(Oml;;amaQ8hNLtᅵkڎ~iG4 ADDd Q!H!н ߃ЍFy708}m,G,(x$fH :T¡MoÞ1G$14VD,EFhX}#Cȕ!VWɠCqĉ']zC@R3=wc`R⾧V㶗z8v߸ }g}I4CN.BYd4W'oۿGЂr aABjjjˠP__h4i3Ac544 B1޽'t/XTc8 eeeNJ= NMaN>oܸk֬];Јŋ;PŶ)雷_:1::OvډBAw2,=,vډ 1Yyl?vډva~<ԓصk'$]LXooV\i!w\###xa׮fXbm];J#2lsFgX`y VatD6oނvDMM g}/vډ:lEIŤH$3]eee( z3HB^pjffڱCdP( Hz'߳ۇ7#߃adj1pn-ˀ2ʈ(XN\roæSSSD0Z9d(֗ö-^܉:rȡ)Da \rr||d@M-NWLL+Q&8MLln/މ^EXPӁDDrP x/Νu/E$$&&]SC}-dL>B$lkSs |/e̗׿H }OY(`Νhjn/|;@]]-)\DP(99|>7[ &Ba7_MPtX 4:j_ZxE.^Y bcu5ާDNb"GOEe> h^qQ:Cǹ|}%Rrw1Rm@ 7NacKX$˕AQ)ReR?kWıq0Y;z = IDAT^S/՛yDsu\4 4xUnb0hSZU BMtggU?YWA:]ꯂD`*U1-tפ!1]twsCxQ@1t(M`TwP}G' >V1&8\B}rveirNĘ0u:p`WІ|2ֿJNR1|+C/+XxQ@rnAOv$@kKLfʛTs" ) $0<9xkTV]V([i_bon(D~c03Į2ѥ[jcӅ`_j zm0:o*Ȍp>PTA]y)i}X,h4?{Cjl'Z{@k914KatMS5OL'ʘSbPms+o |> YJ&lܴ;{J2_|/e̗tn4:#EXLJݿyuH|/e̗2_S.9u Y3uPH\NR`zA9w*:Ixe̗2_|{o7{t>ǨwEBTJ_ ʻ}7| _;N,an2u^a`^~ե32`tX_WF{T^-o8O͠cg\/:^6# 7%TXAJo)*?j|R0z oa[AKLJ NZ+8xUī\2zC0J[ׯy矏UO?n,^f̷Bq^LgtdDCA@UMбK?:_L&Ld̗2_|)Rb"BqėΉ~#(=gnCb$X1pwXB0ْG>Q,XEapp-a-BBJb ? #\Ab2+N|yX3@1AP6sAtXHW|cxƀ'O݅PKM6O&ΜB@Ha3_G2ޞ̾ Yel fp6||U9;L_%fe .+BmCeeϾ9,*8-Y4j^THF@lQycG[,xWcEP@P@WW7"JI ѱv9,U7 Xeee Ɩ\.|`mƢEE!aN  EK`b3@ 5hmRo82{`GÀMaEAq>*F/m `LͧSzAu4lb8|,2͹9:a 9\.+ B(jm>7fF36A¥6ΧTz.Rͷb+V#ϓ8\żGoj~^/TtvߡL===8|oE0͗Z' KLMADx+F#,+3`i3gSiΙo M` 0Mlkb] \cWS ${n ԢI Ky! RZ }p7#cƗqW/Dyg||##D"Z_2003wASSW. _7:ވ23=Yy+҆ŖIRJn3z;;4V4xAiϲeH0 φӫiU0l΁ELo-CA4Ͼf'|^iS1|J7 t4Kâ  &KAG!P2de`Sj>,AuΪW.C牬?Q _;[7֭DBaAjjAy=-A0(bm Ǻ #X4D"|,,~zO.EMm-}AR % ( FGJPSSl6==@Mm=66͇!t˭fggq U߇d2D&'&@6;_>}D"ohxS+\}8c CJ./"?!zZyФv}Xi:RJ[qW1 Wuӌm>(͆Ua ̹ѐ0J¡#|R\A-d}pe9M.W*/6q4+R. (TS&[)5( sI|>Gw]Y8C#>At>,v[JW"BJtxOJ>?{?)f Vu^ټs1o u(K ^|PW[+--M6 ۴qٿ?\ccc(N8,_B3X曱e> fqƦKA099=}֛f8hB,>^y%[Z099^$сu?43 2NW/r>Fa\x@wPr Rg'^掖4)@>Jڍ рb"8k ć?ϡ1YH 8LVsW6>ls^LV6:c.LV3 ņ8i' Y]~3H'(!JNJF,]Py~+jBuܙWxz.D#9r 2]B sp3X M\4нqqԔ=(Gk[+vl߁E,7XsAee%rN6Sw{/<ۇ3 xxнd z;SSB࿾w1>p~/~FA03=f\~p~ 5\6m;v߽_:H`l~_wnVLOOo׿uqtuv]~;7cʕ\vhmkc׮]袋 ZNOѠmITvA~g[&ƇC rܝQgfm'bRtzEV P1<GC2iW Z gӸSBCU ذKj"/[Wk9SMӎ\~G׊*C7:[q/Awk _`Y=5^QY'2_F&d:ښ77/B۶UO=;w~iDcqtȡo0c>p&rlܸގxI^?JN/ٜNU'-|jt .[[V TgZ3"LT<0:eAu#qXeి6tud%_G| `i ^H`7h֍>FqFW}7 B`6&Y  Nalj<3j2X8_>ǭ@&G&GM77ʓIw=~xx_t;0R)En JYڎg$Ƌ HG:Pnq֬+HFY>Ή (L@(p\ i}쌀" S0͖рԐa@>9 &⃍}l40 r^x7:*+?(™w'(E1iAkUACԽO ; Dk}VߥmIuEe:$6BgK5yi[k Ep܊ Hg2,GL\^_)YA?:U!BT6wy@&cH#WWL?_^F002 \X,9ch[_Wp 7`Ŋ8蠃9*߹x1|صk>p~{z/_ كŋ}a&gv]s8 Cpn`1~cEleUid8m^;~^U}|mA`py WV/x03sڄ :L`GOHeÂ7N/ذ84CX>eΔ,+ejڂ?"##ͽb|R]ʪu,ӕ^tcQu2z+$0x&x_N\B[K'qSϮƊyu6o_q2X#%+RJVh4L3bh.a6Dut8:٨sڲVXj]V>S$1e7f %3eo@eFC(yeJ7'j #7 J \m|Ԣze¢Aɔ.'jJ⃎s#CA@!/^>lCmCЗVrT}xѱFRe~ ?o_PKx{X4ކF{v#t+#2X@.o89}\|Nq 4TfUz{w?:_ܥ %dm|B>Xa{h((§3 09N7h = >C"K#FtA9@YXP}kiPM#dmams6FU@uvk${_Tahq^#4Fem3"oW_@F+fp6>"`_Cd* YXY6ybBHƧ!Q#+P .͍8"yvGF+ Gx[Zpt2.Ѧt4zaChff1;;YxœMfвV6jla0ѷ*Hmz <`20ye_Z=wT}.k<[ +Y_"/GqYe lqLM;w9e[P(ٜkɆz? D/L,AۏːϲZOu(hΙK[[ -sXAYUg,> ~at ae ph k/6MP%E  )]3jt;HgtШ7cGASSvZԼR9P'lE˕aE70Q`fuv|)N)( YcSfmtD0|1)lc`q Xɀ~./?X+-,8msOe7!K eeMlY]Xsi} ar VdwQ`ضq) *q̥{N}m^sb*D1K9(|ǜpZb0WRJȷp<޳y,>syX9o LVEc#غe|֮yKںZ]wabbM-ƞ=h_؎i bAG;&&&л`tdG]Cl-mxṵ8mBkd)to]alpX P(eH4fDQPph48 .獤 +U$xqR8q#m$z!I#IwOR$ IYL ˡ/ SA$Uyefsb$r9[ә 2.OY31!M'0q z' JbT1I!Ph6LdTPw'wH>-%Iqsp9RDXߠR m d`ó"( KŲ6Zl`\hdU+nV`xl&gRA 8 90~`iW'gT:ZQIabbcc( DB2DDC"8`}PQQQTx AJKX_n8]XU]ɉI2iD"D@}CŰu6TWfXܽ_,FEe^^m-rXv=f ;?0>6Leuy`GxI5+xMh9A*N7Т"! Rg)0Tp*bgxJi=,x}.&+OڋY69 Y/JG0~ at񱶀&_cYbsDϛq}a|JWS8homaFOؼ޹b:50Џ_B_/*++Q(c6,XuP#G~Tb(3:ς:!ܨDc13i!`akhjr~0IB-hEۂVBhsӧ͠U'#p**q*RQ.O(FdٍAZZ?07xK X 2ȯ5+bF6TC3s 90‚0suVlh ⃷R?|[0[sp\)>97NWP  ؽ`?Ԅ\6l6mv,_4*0:1Rd(lԅ8h8P$ Ux)2޷HT?TW)VYleV|ioN*J?Eb(‡ZNh/cjI ߑ3 r& ?8_Ȯ 60U,\3b Ō4?t>0"^vQ!t/IRRJ(;b^+W[ltaRue  mlx7-~ppsœ!0Ih- j lx^q>8b4Lf`"l޼>[ɰX}P铛Sϊ.otHfYp.N2g^pYHD184 QM#ZJO)b/֠L&እbc-٤_`0gРQս*e((JŤ24Y.ZGihW}I`ElK0MLE-E[* k̗2_S+kp(mA$d$bd"U&CҾnA7b.L}pwP~S~mܲ0(ͭ]$mP(6z 88Tq@9&,˿| 9ZxkV;eè :> jlA+4m&  J-GgW*8;,v*+*gM/ݣ` b4x#tW ^TU)~3\y| 2'Md\0+uVXd$ArCM-s>LJo> s ow%6ڶ5mQYswv ^TkGrp텩/^c-9Ѳ3kDɓZ6;6μ׉T"Ϲs&4X]sЄ&;t C&'&].n^89H;"" `,c(AjMҨ-"kAd,~bo16(A}v1R8oSQgsT0I$/79LVUEV2e՚ǰJu ~ӋU91qdʭ3 -!<+-'KmN ̻0*/zzh 4)^a7I5uS- .a Ųs2&=j_6O;k>&FoDNߜzfg+q}et}0w q͟ذ7PP >=|%@Fb( (h %4Ҷ΅!+9Dń/]W6H**P`-+&e}:"`̀&@H^&"XRx/]HHiA.̟քןE'])<ŷ^q޽.CJ5 hyG`:XcJjdY`3'K]Fbxp##=@u.$P][}됝bӆ΢sI'1<4B݋sNb1,\V?D -r(xϬdk( X h@Cz{ wOR9%Z0"nPЁmQڭ8 E3 o%p%CΩ^Nlm\bsZ^4 i6JmcT:BcG.ʈz.OaA2cË #|hd,#(utK@@Z״0\X`d7\Y|cj(Mg ,޵>MJmN)"(!ec@2N22G,=<ۜ϶Q  ) \xIq@Dek *EB@qbTNUu1;3ņu҄XTue'Ԧ`y}4nv|㟿M9fNjkWwnX?%5Êތ>զ%oO2fFړ"fRHrxL%KQVNI #=B,=W)nzJeDSw$#>f-x>ɲ:[a=#KgiᶹAo *B 0 Zw| <>TIZ.mڽG0ɲ1lLhJ4_Li$ RP(H'в72XUiG=.ňgHǂr6%u+]-t:==Q[_xYc㨭pZd%ɉI Q 4 <([QU]H$vKvm>Y>ݏΛ8z _9+{c#K[0Z y)ŃaU8ޫ{-Ibѳc7>/)xбož^8ftl|izv)#?|gn8T8)Q][ )%\y(d L.c,o R V\{nq*#Z# l f2QbTjNXtb8 t&T:D T8 7;"B[B'ˑN D*B*Bb144a+Q,׸Fk߰:})>(L(3Ь|x +Vw .h#oDN}""³k܍3kpZL٬ErO/ |ϯ^O'+oDUu%mM(/OඛE}kebq/H ,WkYomn 23VBmu U131^I4<隆w4_t}ȶ9Yz^lfl`~g& P(2*`0Jw٦q oKA@peۨF/{Ҁg"BAbŘo<~6 &b $oC6Tf`}XWqjD,S1Ab0NB Y/9蜛[ T]?f*2UJ$R"NDбÁ!%,^h4EmjiBSK?jx%ᗀn 4NpU^?eh f^Bk>8x)wCG?}6}f kQ]S7! Khteqoi$?~#cw~a9|3s3OerY?"n\Reȓe hU&8$H\gJ GI3hCø Asz~Kֵi}6޻TW26~Yb=F:7ܱe2&z Ϟ1DzNF#=>|=p#u$K2.[ .Bx)ZRGye.VްC=2 Ksa"6N^>\]6ևüv꾡:-UM:F.$aȤY-ͩB$tXYQ)<=֬R$MRR vJ, &w4`u"B)izߚfT}|/ ڥ~)1C SP^~K_9-"<ޥƇ HJ-2tF,/iAg܈'<z}УMA|ඇgy‘FzWF.3 ؕϰqS ?Nj/t)aN- ?  pj8Y5lmg5; x)ZUatpʇgJHTuS om q߅:44 ]FoOBuMкhɳFO|HNC,?x*zvF_G?{е3180w׈?.n'8x_):p҇$xvI(t{PCfZc90dZ<%F"E2( Q3,8@U26Juu VA̗7KqxWo{v*̿B5Xyl| D]K:ݯp.Zc8愣GP,aGxTrD"wRC ]j[iz4.H}Pvv[NättVδZʽ(>V' nNVaeG e̳LXWB3~A͗7bll(H D3)$bw1wt$FI T4vZ| QS[tv7u^Uot<(vK.+טᡆ##9ryAĨe!8t5_G~9t!R7#BqSoC Gb{=4iaJ $9G^E`< ʃ g%OFЁ'}~Y;"ғ ݕ_7*-WvoxØ|)غ/[Wc$d\=z}fYW6XҥXt@N`U;X*?؎Lʍe ^ȵX2WJE-hE,gs9lܶ d>mIjH֦!|ІU<P>=:;R[tu_9xi-9$N6y JCR @P: H|Ë`P_zsęF#ۤyq0_9 IDATf;^ߟb/1d{Ь40{ᅢp"䋉a)\a8]!^4#SߵM%Ȏ 6%WDq73'ú^laBKN7pC4s  < ޅ0K}Lsm|%E[acƳ>]Cڏ&*!y-x,`^D@:-ou6nFS IY*FEzB;hǯۿO+:r\rpޗM7_ކq,/X|?O.E6FGGχ$Q[WG_p/~oL7P( ~Sh XׄDYOC|/ek7<δW!1ǡa QAO`g R|ʻP]S~FI%cێu7Zۿsl xJZG|?E 'V1{ \_ ;V1֍F>`pلj.4sA8xy-P̅|xj jdu3 S|^_ n0>.(fhJP?S o,kIl@qY9!|^33V!q?0ccWmӉFiY,?kRU]M77\cyև׋ݍ9'r W.@Mm"BwFsK+[hbteeeX)|EJ>G ];B> 權¢{Ac_qe$>Amak5,,朂`e.Օ0ۂ+<01 XGF3,AB H-RWoUյWf{q㾈Y-dc"/{2in}zVrhm0#[}|kb? LY-ZtW¿ހ~Cf283?3V2QDTB=VjS<177z޾^@4zzzPTPT0n݋r$ u%8-Xq:bdN / /wgs?[=\JD=Ƶ$^JviTk+wx'>KExhR pdn|cn[]3>`{llT-~Z~˱Y;`}{cv|!&i,BxP 5-A~lMbA2Ʋ3LcH6ʝƓO>Ԣ֔ٳ{#N)/켔-IRmKI;\zx}E-uӊJ_Vl-zTt T rܟ/.XꢯQ4vmi[!ɊN#<܃Ж*z| m-w$I &-4"_+rAQԿ E~.FѼw`u|MZ~8Kr?~:ظ~} FhĝG2mbK!k=4rFvKnK7\@':N+IJ×}(__[pjqIeV $+a9OǑ>$ IC$$r ΃rdj+b3 evWlh)x&bA|HTҦl0s$jךT>AzMu#E2FFQZhP ]'"勍Mup7F}&:{,iI)Q[})za8"F22vn`a+S-uSŸgJ-2Öh|T$L-9hE. \A-)( Y'InUZxR<.t1?ZgGJ T}:_KvB\ W%u϶`Ӄێ@xh(%O+d_ |wYsbz8+㙐0ζi CF8<])2衎u%ÃP*inP5H:kYkBz"rx֌x?]$h/tWK_{(Ϳu P|21t,"RKD,Hc8 d/LsNʥ}-5SHcGy&| J2o\ɉIlFVGugf 8GG3Sg}vnEEcx L‰'%'gzN ϽTVVܮii`Lc>-xZ]rAZ6NhIkAg `@<߼J)cmo{z!H)qwmo{%,>-SSS?=я~g%q|?j^w!ɉ(wREXs݅.HkH66[&j3T:=!0c jTKN6Z@1>>Dgg'.bu]xꩧpw// ox~g~ /G\Fww7>?\.A7MяbuuGŻnqxGi&<wpwcƍkp\r%]w݅-˿KE/>̙3 zh6Ї>z AǗ%p [[[r8r~~ \r>tkzH ڔ&oK<: I49v2>vl]ǻקN1=g6K6svˡRoB2!(8ShD~,UIF>o4%@_^t5 %u35`(P3e]N3Իj /+J'zx^] $S[&:/ 6 _`mf!2c b@cقLuKi8F lܡzDt9¼GSRhpt{ ROTDqҡ=~NGBttt`~nzz{8L&!tv A 9ӅcG83=rfτowMSֆ:ŎRV+6hvq8?.dDlB-p9r͒R[S^kKq!{ク⋑6m֭[|x0;;{6mEi:<~7ۿ?? n6ݻgG>r??E]ݻwk׿u4 ݻqbll ?81;;ӧ} \r :::pea||7//{˰k.‡>!ݻ6;;[ڇcv<[:.Im> OhKVduي^|~I:72p$٢VY׼W=7\u>sn>݃b 5_FVu !Pk5 BdU @''PL %P6_wNֶa,4(ZӗרlZ.E[[!wLZQ Ym#Vgz( DuV_!uP%e]E6+`=\f >.C7?؄dz6+SCa5XT/q"ˡYw#Ja0r<{u`0AWW7elڈt:M[7A-شurP,QĦ !i&ZRQX\b٘iCMidlz+ :ѾDҪ_oo/ك??ě&iH)www؁__zzzp;v FYr-Xn|;7J۷o?|Mɓxߌm۶i{/..bbbBٳgh788Ul߾۷o͛jzo}[Q*077_%ERq'e]|WG+`\}1pe;000.o|yf޽[*p\җsehPm\v<.MLllE|'Ϯ.'Q_NuK߻ug+/!Qh_^td(:"Nö@Jf jUdUBVa]Y`&Q JSH=3wJ6DiOل7> %mD eApD4EtUڕ~tx8kLȂtqA)`"ct`:$gr\+@<%f-eRGbC)p~1MN@6f槭'ʖHB͚!JRP2BC; $8vj^lE|^4qx#mf177Ylذ'OĖ-[o>ݻ'O2 :;;199͛7ĉعs'|RbzzXXX͛SOaϞ=+q5GߎoF\FV155۷^Gww70::CaxxNž={ǏcÆ s=TU,..lW;n?2MIldO΢эHAex"╻z0>~hc3x#s6YWj>Hpil-/' Нj|tnȇe rD5byZ>K,<۟C>ҰgU(c33x>KJg}FFvb %.)\!H:Ǟn݂n4 45̜"vڅDgWT%b\ 1+a اG XᲩIB* k'Qjg {Vu\cg. IDATNb8N ^B-Hjס= 3҃ܪ$}GyO;?{'lK6UۇQ|ӟƶm033{+;/J}U?_ 2)c$SWj2r^3%~"H6 -ۖTϘDΒE6c2ech4^)\a>, `x% @Q+GV A`ZFrdRBZez)S;IhQɐum-VrXd"]:jQn'. FEϕ|[hI мw^? 媫>k1~Z~Z^=ol__z#@&%0>WC %8Sg0Rnl=vo'.H++Oc$Áޞ #̪a5&PA` ,5 TH@`i+}KOsȾH*9&|x O⁒̫3 LBԗP[ Vt9-;tb X-u'ʦ2,Ό1p@h 6)PgZIcZ.P!x&ƪ-8>d uO^3oS4ɐ.]A@cnjCD1 ]/RAEvplc-Kmַ[ڡۊG'܀5E+~QZ|-]llk{[>9weֽ[:/ϝN/RG/Ϗmx🾍k/=r˫LYN/U)KPBHSҔ85 lHt:n,7HH?^t7>5ǡ E NXG] K*xIW3]KH@Fa:uCv:2W4}3"hC qf>\icAR04LZfJNGCP? ~DB8}@zJV+[KA6ETD6m<x! D^ QoŨ2˾o;X%SnG__ #&K?!H|}Wiwۯ>\P:>`eRZqɑtYܶk] IڥunwWI6װd)lͣK㿾z/p3ͽ6^̥! `"-v!thJ !wcR"hZF`-]WM)љX ۄ@. BJ`5 LM8?M%t*yc4@6tC[FNW@O3IE@ߜØkC͒j;'o8/rа@JL/VCS7l /]D R* ڔ(A_J$\h*`bx0 ũ6V@)3m M)AsD]iggjUl%s{8^A,8֨`G%  XH-UC|mC*{%eFX,q!C"(EIt3ܹ&]˙S zzh4pr$F7}},W059LMNAJBjXY^ԩ)lD F6m Gc9(u Nannݽݘ>=Ø>=޾^LO`qani6w㳧FBqp#3햵Zw6gr%3D݀nZv+Ӯ۱%oll~\snƆb)` >FA NftJ уl.z4MV-aCpX^" uˊRԬ4,7`^ Dx3a- EIGu|*[* ݂j]+)_#׉z%*cl\]ex0v'&( B8eU* t)B0RJm,ZVo8yP9HO}H ]j4b5utuwAtt!͢]G38v~[{Wwr٬adM-{X. B`vf'NBJt:rWFjBڭmw*'JhJOzy xוgZvN0^KG+RJh=ؓڸ2 >9\£Kc+:e+*o8]WU2:@:B.GG6Wyt&jX4052l'I[]7u| 3FL\PX44 DMic=B cȝ@_"w2*l!m͋ & g`T d 5] iZ!Q.?A8)"+j:P\pwtmRJ#B1컅( ]OZ#BJpl8N@GGbb9#h4xGOcd(:%YkJT tDG1wf^RJL&T*PT ':tXHި#J!E^Q*Qošl{Obnw270 -Zh;S@t*o$D|:m[֒xoΆ֒uqh]Y`@LDIƴGpvٔJ^^\kM@U;g$[SKj٧\Z#gД@@&G6 aS*9Q ^1)ARn>C( fod'n+EY#NQ#Yx1@)RED>X82G$쵠NWFK0V1AÄ )l q`Ua";MB2PRQ~H" b DzYȎ6}ΏHC¢JBa`pBmAJ=]R#F;H=+̏jٕ"`E{j\lhЄ:%(ǰr FjDh'bɀZ*>p S+kѪl/6şםI`շ]f ,.hs$ҿ]>E~h\vv/2@ i4@ @&BFHL\:|W$|H??V >a+FL5 uoaXX6`IvӋF5>bP H&; *[(GBXB09&"z뵥d%`]hT</ fffaK)l6177 ΊF8tGx/>?X]\P\ch\`֫q?mLVR$3r9&M) LNJ a K /t۴[4S@ԀI =v!maWQ $m?bEXJ"ڮRb> (;dXLRQ#%dEtPJT'Iĕm]s) E眨@+.- cN" /tbkU7z@yq9J{e<DlbA0c|iTzeKd@m R|?nfy=:u |I<3ÁpiT*߿AֹKvuqѧsw>T}O=yz!w}s={}{111Gbff8}4ۇy\~qxxgq뭷R8uj<`||}k111(3ήlk< A+0tXlg>\skF>yI-,7U)rtZ?fb {,Zsj&XҤ^h?9HNM gh U__XH"Ӧynale%i7=϶~l<_%lUHY\SyT?u-6QO6 V p3eՇwڷUb2Z*a/O|8pc*EN*Q$&BMZLDF)J׾VPѣ"&j2FT..8:&ԣ ,m'V뉶ȑ#HRq[n> o/}.)k< $ |+7 >|?xwJ9xquᩧ訶7-333?'?I=z?s?k^߿ ֲq]u .D8$ $Uxv ڱmkd:<5k~BB y iJ>m/㇔ҷ"R㬃Ď }Pnˆ#4Z&;bR`X`Cܥ Z À,%+[ m/ ֘X?|r@Cb)w:7Qrrg G|y?_6 nZ` !J `,R5yR~R2b8]K/uR}*'Ћ_Jc`EK{$hǴ#/i2yCWm&GUhGe 762$Ӿ ]]*esW qA-r9P([oEZM7݄G'ݻq7"h%$ϯv Kk122>~5+ h4xp V?wq\{@T…^YdYd2l۶͒ ־` oOҭ:1;khu\/_Ppk;GiS[$|K?|P"~8=3/||Je5x(9PzlQHh6,`z,E.lLo !؊׊k oX/MaSBekm17#ɵ4*A=Y| :)MyS;2.cg8#ͷXtIoZ }'7| ?A)x[IpP-v;O\*6qL66Nv`-F0N*_^z)QVh4c\( x+^N\yxߎ|+h4kjV$OB/I~NEr-W_a bhh۶mC&u033s=R Z ;v_j@X5\N/[Vs=yݻ?0:::w^}=ܣ3`IvȒP.{v\i}+=|LڪNI$޼O\RB9Cw` zw5lč׿ [nE{>il߲"|`cx‰i\rv.,cv~ o{uۏ$@Vr>o 0ÕQ| 1O&<2"Tx빂T91| &N* gj,糙+j|Қq.]|&I>Irc|txrjY;I()? ]em{zO}  !>ҽ;g~/ӯw?qE;q)o?N.)L_^{q'-R6ڽ-8h)-8'X4Jx;LCm 6c33˕ ,,(Lܢ1LK1Ml,@w!mZ'ѲEPcWsmr6@lul'P I}Cʪ$zcgYCc-pٸ,N7)ZwJiCؾ)ٔr ,vl݈}ư\2vn(rxubi!6/][pyzDB`!P2r瑄8\vճ{HTm2rv^,Iu> Ea9Q/ARz;4!`Mc 0 $ξl3j9\@_R/c}T*[X,:Dq ŗbŶcᨍbǯ b`` !\H;v uh Ȇ9 `wދwm|م;1:9민 d2i4M9>3,]jt_8~aV'y! 쒎W([h["dRv7ȒP;P"((`(V'?`HK&֮`I%!J }P7hT1&V tF f8Ҵ EjtOたdƈ2"FDܧL:L6t'0qb\A/Vk|!>ZZ;˨8yb|2igJ|BT | Z} njntBlpιМ(G$:}9U $ZJD }}@l9xH?y= Q88i8r^W$S oFdMfG|_$YBze-LmkqXu><8WV8/>.z.9htez@ VTY. =]%3)2*UtuPgտ0_5: 0vlBj IDAT!3FyEf b+&T&/X?|V"a+~'1-@Jlо n17_:i)`I$EEe >`I_cDBc;Vt5Řy9yDpNP+'},R苲PDZ~r:J%L``#8y$i;݉cǐ/y&TV*8ybCf݉BήN,-,Z[B ϡ]]xN8XO-x`ﺢA-S2 2hGa Ul_'rNiO}.`%1/V ^I*@rHS .bP ȃ,TguD͓Lz`XhzC9tx*%eknu:?_wǟV8v/~Ig+J{OW *:5r^c=dtwtSgp񦟹22jroVVårh?b+P Oy -V4Kay#4Sm9I (1qXJsu@1:AdEeEQona'M`Y(󋚢vƣAA)r;QZ/C}fzm kWgW#Υrه6 O)%uh6٩uJpήs,~Ŏ"v٥iJ%ko~㦍z`īYFukͱbă&qjӒӟEmy3?FnFAMc9&#G:@2)]ڷ}ccs AE) U$4қoN_X}-yAUo':@4Ⱥy[(iPWK[|돮?~?xQկKO  [R" X"/-,Zed[יHrX NSfRj6d??ж2 R"h6lxB4FDBC8 ص) (TGz1ZLi!NM09=gD g_/{~ҟJP(1"Bqv-ehk;ń>(硚zӋ^6)ظN◤k /| ,P` BG(j{Mo%\yUƑ#c7zO7 cn$TfƧ+чv]+A;=]lވ!{Il۾UaA8v؁gG~ ]vcaq==ꫭw7b8f'f\ 0瞇:"qƻx#oKiTz,}oJy4LV 6{q"3f<Y#ⅈNdfܰ:0k/&g氥KDj+f4'}%Xq_#t_P 9[ jXFW<R: 䴠#ӎ *Ll|NS]ll,2Y|4vQƝ.=tF7jK ^" ؉8JT1f#MotS]K @#O}(((: rbz OOP(X,"N /nF8+!ޗU^k(1uwbz Z:Æ#8r۸k+˸;>8o|7033j.etvvArl_.\ze?܁џTCڣXc^dGk OA%i*Ems\t\uqင,<҇S=h L~>\6kyRAHa#:HsdsHIO"HA'?"v@)?_ ٚFۄ'-B&:qQhZ\'L]:GԟKKMڝs@ME@c|A衒ҥ4PRm׳ҚbZu5:l1=T0 n [jC! эd}nXnkR/}ɉ2Y'x9I&EhZBBAh]Dz\>GUXjY2=S ΢!ܡ l& [z0z#/-kȉHڹڭq@!O|RoF3voB}sJ+|I)4Ѓ``p=Ne~]B:W\׽UNPf;.}-~"UمfZ 4%x(;zjII}}oC:h;OV/$hK׆IGk־$}2U#tEFluIǚhJoq_`l1\KfC0A>ՇŴ"XF,axOC]:X{}ew*+Ǟi\^tO fX8 ^`kU\ӆ?rNqH)Z-|MV^xA**P[aiq FA`yiOWWRGh4l6eiE$8WJMk \8ɒ:t=~P?I8PvFu4Jv-9T6w:#'t?i Mz}dmV@Q[0vbT?3u"N5* #h}0'.lr׭kCA/{ي/.a.P{!*ncBRڜ<1_TRJ&KCxر+n?=\h="ړ<@tz'F2Hi e0D Vu+EcD^ՀiKy˟LDL0{`Eb?QB]kRL'g|B57HT /Ŏ" EVW1wfssְ{~ |D6׋EttZYІA̞\xم{.VWqqsBo_/Y|eW8 FH^tHv>A !6?QSpmwRZJON(m,`{;Ap̠I|җEJoB*Ջe*QRJ6csB䨄d9y Y90 I(ΞgvUI]rr@*IǸxP}@p}%_7̳U%KR3FJ N74$d"ҚG8'ꢾ$!gFN=,XD<3Fk%m"Gq{16JI@cWl[X^EIG:h28hd)qI|!2=XQ x@?)b7Arr.p*`[XAo^Gjy)~x== c8&N:s?BmƳO-p( HRTR r gP(T. j!@h(8k} LW1Q=3h1|%iiZ-iSEL \2q7[<i>lV|rj-O:\r9N.֕LЙ}{Ќ124Lx!4H{0F ͑dKL潜 33~1ғHȑZx+1$(ʹ)z4[%8lBC[O7,c4𚜘DWwVg<8*OHS [Cfd2}]-;x#6sJ-T*9խڥ˾h+c;:7I}jͭUV}ەӧk|q.xuWom\}yTk]_CgocOƒykFt;䮯ah|_O<_zyw|'k^uEx_G\~Nai]''g/܇7g˳{dhWWW]ZiF<#iA=ώW\^q)>ۿC]GP7ԙ:KE7i/?a^4M91%Rgp`x`M}W\~> phlj xŻqzf_1_/?U{7GMt=ӚFbL#En )FK/`- :X$S4O|,j ]s76MKXTZ>PrmBj3 XЩ0V 7 ]U|' C6UJ D -*?[Dk% ?G_@g3GR;)X$fnT<$c,y/@Ҫk-') &w}e\KVj'{'/ݓgk+>olk _nGA.pffq痿-,ظ _]z]d8gMwna[Gcdx++?5F HRxuǝ uh|Ã7b旰g&Uz|4ѧ?in1MB7Vڜwxrhյ>"IyIIGťW}M $x42>> d'2` PbHXir)-6pd!ʁ9oWA]Cr:!p=pkZBèOF#(#<'Km&ucm-B^6_qvJc\(If_;.=Z>ڮIi%KTc\.^kЎoGv۹sΜZ y6𳯻 ?n n~}OJ|Q_; |ѧ[FsGq^/u,m uZ))L+?cԵ wHKg@ !o^ѩ0~sZILm4B SP gbu2z+p%qj⤜C؁L>wF6ZJx|cpQLssn)K;z+y,셹mXnWM<6?_)%ZZZ 6oĶΒM_]?W< 4xQZ>]<|}||\+9|*>>]4|I|6rmGVz?݊cs(vt##R24XZCB.X\\A*Bgj A Qcfv}=e/ K+|gfPoYߏ-XXZF*B@o'ff XZ"@1rL:zRX 24TW!<ʲ; [.v Rg#b9A2@?J.Gxv> @}}=J2ʥ0==͛6\*H 3⎐]!Nx6z$,\Ib4cj<8N/J5Gg ŖDnM9$p[2:-g$ q4 hm.}>>nIxГf/Z2Ϲx@rl+q~%˞hj]S['G¾gZ]j#YǁȤӸhW/QQJ^oD~$P3lot`L:&$㜾@xXTUL4@A5BħofO?u}sѵx🜦[ժ4Y1Rݨ V;Rkph%CM6.J{VIܙ!EK1H)16:SOZ8q&'PVqY9yBc8a,,,btx E#Fh=kxb}sG3!7l%wg*ު*}jJKb#ZVIh-rNj$,Vq]k.? ãcEQq3_F.wZ8rxP9?BA7)@fVɅRv:0$ B[PԹiڞ,O݁#e(mMzQ_@gW oT*UEk[+F066 q @uu9* zzpiH)~{d =IAYB%F KcR@Sp Kʩl XLOOlhllԙRBj&i@JFdX],333hii>}הftotjwDl]v7_:7077祙fNQ(QVzdApBYR btAcs /M7Gld6N7F}K+9jO_"Y>>z~K{y=2)̠ bKKK\.r,,,P(l{Ƨ>)|#?N\wu;k, nh qZ}v5^mswnÛ&|M7݄JI<䓸{//غu+lق; i`||T \R MMMB`vvmmmۿ[}HRT* \H@Vˎv')]z@塾H .ۨj'=}Xaܒ5xM fÏRJu`y@46#T@:B6RlTW[n_^zb` "< QX:iw-+6` Rj2?^*EcPh(xve@O|e>*>@_ӵIq H29R]h"J-|;b XFxYR DK&7 ҾqMR8A44cŪV+B NLzVaa~t\eцSOca~{W,T*dMDv`@ԑZ4 5p(4Tw@.I՚^Cy<󘞞ssshii155K./~T*׽ux{Nq7??iA/8xq-ΆHpY]h}&~ӟb``cccg?={;~M6x>w{;0[K$rt+|>gtRWm V ݗ_#OA: W_6"⅗^)9qLMϠ v]r.غ?D:A4 0X $CPյFh}%J[zڟ읈 6>Dcd o4P_2Ex*փ4n!(u Qxg7)c>smGpt9qm);1ay(%ۅK)B׻ / ~+w܁׿xꩧ׼gΜs=G]zV\ !P.Q(m5\i<>ҳV`e/h ppGes\.Aٳ;wD\155I\KKKhnnc=/gϞ /.u]ػw/n6|3A:F>Ǒ#ov؁\.'O:OZ8 P$9 t}*N<>+\;?@Dx}"qɅq5115ī?26G] ,Z[qqON h T˧@3OT|F3xK KJAmA< jت-CH_A6PPثC!!3i(|Z#354m1< ,ϔFH&d`K'v: 2 ڼ>3"M҅$vVژ;Ej'BP2҅'#mPyȈ1 %e ԥqW\ ~tuua͚5?3{?5k`ŊGgg'֯_. o~q!@JkRJ;v o}[sN|c d;q7cݸ Յ6l۶ ===Ė-[_^xk_nv 7|3mۆFl޼---ذaߏ~\p5kӣ.ڥwKv?W?ߚk'VKZrӃ38{q巡20?yIסo >~ӟ#Nӳs&g+ !15RR"Lbtlȣ׽쮥\ [//ÃՇge ;j:6KLD$U _jWb0Q&P?(\S=a0zp`gm#m:'klB}S\,X?L:G?(qh̐ꮮAĽ-j.SD*Eooj:.=|S\$f>iz'Kt}3A.sAg+ m||<}<8M_y;19!}0.#ƍP&MPdb[tXA2+ ix`ӐX!/Mcu46HGU:riyX |ތ\}}MN/-󘜝&9֐;î]qC:T%8oטZ ƧZ-I{ ` #r=T!P GfƆZgE@G4kȚ G|SY:jH<'V`K\+,0],;,\Vc.8ttz VZTtf4;F$GQIȾc6W__t:8Gٵ.b2dȇ%k l8e\lt{TRB ȵDR>qsչO:T]}]v:lzSfO-+xԹ_JǞ+tM>d F(4 }!q"QB,r"5 kMA {)y\::%+Z KV+V V ;˱MezU+su:MkZ+܇8fdcB>S߈՝ms"_,aDrz%P("Q~X#OŢca9Fa^ VGJ23[h1Q83xf'/3ʤpaXS)J!rmkci@Ь R\Aag}cxA鲍 E\,159NLONR`fgѷs9?Fc /crb 3ShnmU8y$pѥ;0:<9GCcd+f8vBH \b~~]ݝX/AXjΞ>KwSs!fwOc4xZMӮ7~Lu2rOP5Zx R]đ!K머3UGJhE"p.S2CvZ-ѹr!5m~mw(~lǑ9xGm1q8:NZRjM_,+6ھ~a8/r7"[DH>:!Ѷbvr&9w%+RׄyVLCfB#=;e:mBs1+iV< }:PrQܵD((L 'I)a4Sbt);&J)Z3ˈvnakG|@#GSܙ!3 BJP.J\::݉iLO-MRܙsH83t<?b9u4N$״.)Wqi}GgK@PX O) rן1X*'0>Sť8xFy\KTQ@sMi=_A$1mzu}](Xn-,::թy9l߱M[&.T>-3 oXMG׾̽xmcir^9sd["4\X0mXAZ^ƘDnhtSRE{]Ӄ+Z F74,n8}m8~8.{xض};}AZE\F}}=R r֖Vtvvaz8p ǕP;^Tה2F:ԲU-pۤr_?'pr|sH# :vV`^I3T&7kbd@܎mq' YaCIzMDhY2Jcq c^/  D{,YZ<+,APbma. iK IDATۍM@(z 2SȌ)C:M MMþt=:儑yk<[" }b3Ѐ[76W<,kZwFe;VѤuf#h*[i#:s|h:pU@/r a} BA'鴺|'@ =N9RJ>>mK F_W^uR4:::mx=?5^ !v_s-&p͵aÆ ؾB!̾qoǿj.۰⭷ފtvvMPvz~lfO̮+ \[j7 vG΀!)c]VܱΖeK>p\4ڀ^8Z17GT_^1oSXgtѹZ,U=Ng5ls 0 +"/9 oCb)>5sWm~ɈQ~ e·(n:wR"$C jjThljDRE&A*$THa92mxD {qh#[$'`~K$H  dj6"ꡇ˨̵ߖuй\b K |cĀWT*477ڼ E7Hc"7A+[k_AGw}4])CYϤqd, #;@q6yPw (1Ͳdl:? zJ?e2U(V̺tV$Szk\gꢾ&x%䤠ǕbXF0JM1Fm'e` )D!cEE'":bO6$xI!|FІ l/nb|SӨgMDJ`)ֶVTU#N ssB)~2gB|!iSfBdjw%۝$u掽y]})-_ұF qQ|A/iI7Ԕq9<:.ꒄgyx]ܚś+W$@%Em;}?{|㔇؞&S4?hK*%:IqLU+>f# 4l7`@ؓS~N?atxSSS/qa!wK^QU紕ʜql5O^ Б1I&cnqݷ\7'nk%O\r&Wԁf|)h=דIi2:>Y|}\׵e C>m O]W_nrxڬ5}^642C'cznQ #X>l[@HQ*u?%ժi+U, Z6dy Dwf>Rg=c Zm?&<HmWPiLXㄐWV}d aQ՟em%kk`%Gε=qqmnq3/jw gvZ44mmX\X:!GC>n^ܮtyan}qA!֕`k\ v\!΁B 1>|吗峂:'G-[d\8OFZ\.=dVOX*15&04:aiC'qnd Ï<s K~aHx,&py ͠R8|rS茒#3=(+8tbg'Q(qP0ZšØY@TƑS#Y,8p|&ad|KF'p8΍LaqGarz*ckӆI֭=bIo _yXJc5(QDa]hABAX@6^tQ/xPKy(劶֛(v萡2L=mlő"*O r)d=3Bl5R\jpI8s ::;PJ:q (WH?_̣+:`!4RXcQ~r5$`O0C':,ds}6'NҤKrQ}@k#ΎU% N.kLdvt|2O^䎳U-q9|r% ?A35\+PQ =EXAo 2VI_{n`^#wprhP+u A&*8SR  3wKtvw)<_Ufn:,-ڌLMN\{WRM`Ӻ"v0uNtqU@H<}ɞ-ZW|z$ \_NKqvNxԚ#-wP6ZX]'Ko_Z2ᕠSX,G߼.GI;n\̵h:uP:S.W14:l6.4գmM[DwG+? `v>Et6-W(/ou0uіX\*blrk䗊8zz͍[c7`x|׭u04:JUqjh3sy\s#0<6%l<[ͤ19K,R-kq8v*%f,Fzl6>&:϶L>FbkQ0&#"*g#`z0L@PVHuXB1+QP  CTae2wT:\]߇:._.Fpkv&>vKJ#f-?u4]r'\۵_;ikn}ťVn f2ʯ47?}V`*<1+d3i6<)l]߃ 7{we;;Q*U061UmxZ'ejNOLu뻬օ|SwxIj֘"r񫖊xv>^6JeK%RbM(hkkw] YDS-YY1$3^MDY@,}b@+>ۑYy+1< z {9~rسg~---Ccc#.~a\}ػw/^ѣ } ~;LF@m>ӧOoCZe]f٪P({tZsEٳg1::뮻βrYZ7w dsKvk?q>GN7<Ւӵ|4A_C\~ 5qԯ b/Irs hյ*w&K@PY4-_R"dH[54走GCxYޘۺZ2Kۀ67N:tbc`hq:r-niPoƠ@邤LI?m<*#!]1w:JADbulѲ)!ذ2lR O`||j͛7#<|>gæM'Oě&۷=nF!gEyܹsؽ{7|I|cG?Q\qx؈78pN8Y 󘛛Î;~o4x`۶mxހn 7|3>(:D]]򕯠Bo}["6vv#9UoN]9$zčG8Z7j`˝8l dh^*6i!k0^Y G~HW۷l%⢐ 0<sFу -.5n VGgLG>o~B_%o>K.woOYLLLZǏ}݇l6&LOO\Ng?Y|D>+`֑ #Kz:P 3W:U5V_40 ]aP/D6Hx Bɦxc&Ƃ,8fgfQV1>6 %G'ܴu z-i&80 `_<@y}TKNt?x)ZE/xyg166#G?1fgg3_2n~7O{ .mݦ{1=7駟|'?ILMMKKKhnnl̳155k}}}x;߉?8o|#>я"J;_;_~9܌R)%Ӄ۷o{/} =`׿K/t99t9rr9Gt\\|duW}$Da>_)!tvPew]isDmp$#< V X DUQRкKJT?e/!cA`AT [P`Z8ʚ}6N"]}qcxFه5x"J}rgtxJz.4ku *FZG20z2ݟ9$- hji(ruY]RT*Itwwa|lRVIQ`N`Ɇ+߶^zcWr̢J`" oI2Z}#mId$n7`VA[jܞ={p饗b۶mxw^޽w}7ۇm۶+'> Rǃ>뮾j.^|E+9رc7ףJ+?GUW]~LJ>!,--QU//'8qj hnn`˖-]v<9nJP__ >[X^G0-`Б= sZOZ\}|/ԃP(PTJ Al1(+0@ 2!e-JlUU@*"%QTNPTͤTUH S64+j- cOml л#ۥhI ,23d֠MLkOs1G֖f"{*EyC]{̒CW4CBt0S)&$;b`TԢ̸LkhOB6 hnlD:Rq K ! .X\X@]}ຣSZщXLHȓ >ՆPA]x (\Й aڗ:O(g: |Y+=%T_:JBkjjJJ֖Vttt:y!q]wᦛnΝ;Ѐ Lo۱sN\wuW_. \r pעXi# IDATk֬;sNoD:ƻ.\qhnnƖ-[Ӄ\s5شi^/ƍi&_ب6oތ^^^z)z{{}v\~Xr%nf-oASSߏ&lڴI߸q#n݊믿mmmx߭fصk6oތ vBOO>WE]Vl߾]^ |+l1_明_q.‘ϖq௖Ҥs4U_B6[!|ӳ)l[X @L&]RJT bb %g/bۆOᡟgXt:kBBL6|8HRTTX*PT^DvF}.|s#8;<:TBj8#*/oЬ|GR dbF[PWc0!fF@i6džFk͕e, ,@66Z` l,TXtdDrm$QP:T+T*,.. J ju5 64!SWgp L|n;(J0JyȤӘXRbnvǏ̓X_l߱ GסGm6KgT?n#;H,;瑔'%OS빱pV! xo$kq9ケk$Mq6K jUjG93g/˵YR9^κL2I4: TB ;sNZ:؃^8Uݭغ8L&Ha*,JXՊ#ͤ14:=X̗˦11=߸xg/WmF]./lcEUJ\e-6cThknD}]'ύc1_MW_cGT,aPƊ@J<1Nmhom4i~z@d8PNkOz>뗪@kt\8v@?zdVR)N;ݷhkkկiG& ^P,]Lr$.}:Hq`5f@zO ׍Ԉ:LNL"I#W<~ :}z0c0rRЧHE"=hꈑP*B98 iMF NCMDsT.+_ juuuyeݻq׺i|75Fpv} ^[KzVou24NǪ X[bdb?yvlS/@ߪN47cjfd?z-.AzTV>R k1X+1>5Y4ؐCT56#TYՁFQˡTbv>5:y`*4P>2=26ñn";FۃP,b!η֍+&ք@ ;Jpܩ:VWǔ_Z:0$ !RHDL/BgXJ6GK_K, ߜ "2 9 $}^Rgl۱Twll< n@  @뾹^4a_ Ѐkţ^eޛKt>ku;qt O :SsO.=yF_Xpu璧^qنdՖãVq4棓tL-zf_ΘZrBZT}H3Avܦ[qxjoF[K#ښQb`M7 2ŪVCsc=$K/C?{RJ\}Z҈ޕm/}c}4p~:~RJ4"Ju NGKs=&ˤҀ\6R**O ]E56 CiEAJד"LUAbbŁ~D| 07xadQ'OiMxRB,\dYix蜜F z-TD9wU@KWk@'^ĉS s8ӡ-Ht-'YnybCYoZ.:)o}9)޿8Zj;_,w(ֺpu2g-,I+)#秭[&ޕxEwY|0Q!yo-!j(V&ggm@ @1#BCdnvwuRR ##Hg2[P;wRWЊ.+);Y( T~=6\*E eI4u 6h3ZȠ*p?@/M;@ NՒѧ88}| h$7Ix}4#l]k.A :ͺ 8%s@OR~0Ԗ? {d:a?A%y/F llLEoWuu[qUEı\]o@ /w,!+C@ 膒Wtdl0D7 g/2j;h Cӏ Jol-SF9|4\.]O$\$Y_)I&ՇsG5 >[ ';%G-I|4\c *Z4hHk$'!_7Q~O+auUqa\wȏ) h=|T'0Bzk \c>aJ5&{;*% GFp_UV6:M!RH~{tGH"(=Ƀ#tA\mlCmblgOE6CWw'V7K)qqH l2k<@ 踉ԇFpqQ+S,T_'Dg{[rp|oMM- W#%) s sJO@xG躈ݡb]a4-%IB>~B e԰dʾTv6uGˆIf,,-{91@DMD-?mKl9%6vѹg 7f*ȌD'Ղ 07>1&:.]_)Wv`-6l q8vX,T,\*㙽`nvΒܸ,6]/t8 jkCnq% Ѩq .{- Mo_u}&҃U|r~-[@Kh7'֧Oޤ,.P}yR&/CgDZT(P(1C'Ύg!@X$S3=B 83<PdVR7U_)m< BD{M+uЪU Ҿ6[,JShniF_au8s,U!L&N$T6V4׵G' Q:IKIB ?~a|_tAך/|_Ʒ-w}puaxxc/͗/>&iSV뮻҂L&j+/"1??OXj wQ(]w]T=-܂[nũ`+ƾ6_c{(V> םң=\2$.}|z: bÏU;7Z-]cǖX*p0g0fE>1E,.q> u98>=U[py,,//AXi܇SCeP*UuC?t*DZ7_(@V@3lnҘ41,i2Vb f{G*2j <4YOϗda劕O> y4Zt2Dl +:xZd],>0 2f}`:tvE\a)q8t=ѧ/u}B}Li mmpܑKvK I #X,b~~^G>Yio>9LNN;x z뭘BO=Q.} =nVr kO :[0;GTFg[NGT<\6UU|g/P`XBgkx^:>Wm_-<֭ƑS#J1XO> !J zWG3c(U*]3{(ˎL;fޜ*S5H*U ! I6X͂xv[~qm<,`_7ݍ0ZFƈI2J` %4T*I5d YY9Ow㜈رώsn VH;"ι綕 7("+(rjdcM\1(5a}Llm,oO,%oewWolr x"<8_b,1ڿo ZgLNNGE@>Ql<ю.#I"aDvڙA\&cm_B34Otbl~h9(}Cz)8[GW'+pL_Ar9  bpx<:׫t4تw; b?ꀼ,H_X5uc. R~xo8YMR%-)O|<-@'/̙3v \},z)yo&OZcll Z-<4AA6.q~)fJ) y.#8Owy'[oݻq |_ǎ;= SRcC|C!*SB,A߷ 7jףP*l7gyM*rxǍWk\>dBbܮ!49f% vhbBÃ=h4x%cMun3/BѴbf@Ҋ1{OV)P2X.;]"[P[ڃm /' N%AD"i=qI>V1R^/nxLD-#31gC'@VӨT*عs'&.;vDXٳSh\c@A/)Mdݔ/8<3}bxɃ%2\񨎴3Fgg*v a l~r r 6=( L?"ѝS,a:v1.P1QsbfR1[rWGZ^$`sD<E ?C|k_>>}gΜ 7܀CèV8uP.n(8|0Q.Յ|>NET*^~etdNKlZkJ!8q^z)111[n[w8}4?O|):tK//[o<'Ob||7p9b(?#Ciu?x,N_Z x$(zpy8jE,j]f3nkl[w|3Ʒm®#0;F0mƷmz-z] xb=X^]ǞW12؃K&F15յ*Ը1M]cMh+am8?\[Ʈ;& 3]\ 6!>2M.O}ZboX9S+TpE6d_*|pq[QE>N'v!O|B 8 (CƏТzgiozzSSpi4 ;Sz~~r7~ @Ie'EQiA.ߕQI66fdp|Kr5 qFJEm''dQ/JK'q}fa[Hl@dTNR<8y$~{M+-Y>(:$a[:BGGZ PkzY7SݍJtM)oEf Fl㲕TYWzPO DqgBKS-;Y/ 6:?0lsu3x04MލVuD_pt[8JǘA53 ޲sT‚ttzϚ3=9^1x_ܾ0g~RSױ4c~) EqLz$4ubPNOgtLԈОD&ʛe7|Ljă؀9HIKV~1^$?r=0Y![mov~VBi0.-,~(ĝ)VmJVȲm_odJ@_B%[ea+Bيoi*`;$6ЎX!Y_I>s@.ީXN (G#q]1 `ʍqsA/9~zGi{ǣ`R;^hlB(Ҵ?Q3J ' ,`eb*FSﺆv"\0NyPDd W0Zd*1>P‹yzxm0N.!u1r9[HZܾZŶf'XQWl 8h9P~fY!Nj.){}RJ+^Zf߽(P.:ZVWWe)( 0x{տUZڿGR ]ѷL2O$01)c[t)АLsQs3d 9 =WÒ~VmexϞ0: "@΁;V iK΃@[8RIBAJLSΓv,1Fb߹}0DKO>Kn j-kh +D)fSa! IDATl&GysμF."7sxf_A4LpNLN^I,BWbrëG+ mT*>?| B?)i6ب 9߇!~2z t2$V>^3 y$ ;`dX*(4`p)fOd1%lcELwbGs9Hh$ %Fr̗럔NTŸx^U͍=U}%@Ɨ.{\s訠T.aeyz(hqG͙g7÷S_=F4d`k47S6}PHIz$⫡@. fI <6F.-qz=QG#/zBqγg?qьq/vtD$I`N72~@ yM ^hc,;$tjŋO!ђhJ/CrTƫx-9[EbYCom"'~wKS)ˑL A0HkuԣO ϋX;jE$p1yI[/ڋ ;P/W\SSIEpR `MQbQtCY= % r"CD7^RrJ/`eySSز}K,3uG;1y~<*h6X[_GsӸh9bUaV1ifXY^sӸY/Sgl61s~wnӨ-#>IUG>D叆 JK<+@Ń;% 0H;'O`'Nž{ 1IpHg h~ɹBmO9)S;K1fQm"<:Ƴ_gȈApI3᠀T9kE^'? (q=m»}hb bio~SX\Y'?}W1}"*m%=q7`ͱN.n]_:kسsRY*Xymn7cY>&Nhxix_$*9# ~>6Ÿd|rN`|NEpjrwկC!Y޽ǎ۶aIul۶J;18 rp_n sa]ݼg NʭVN͝X]p1PZ!~un ~csy-`LbKP # q : Uӏe+4i veي4߱_œنǑS bÃ=[\ oƙsҘ[/8waGMNĶ!s9<$^opvz ˸xX[a~ib7bZcSlV< J3/BXk}0~# ePE|:bKJ{;wĭ_JV"VdL)~ \5f'؃q )xD6 ZIu1ϕ7  !?7q@ZUQSq4VWVQ*ۍϢtQ]byi##XZZFWwϐATzzihdG 3X[[¢_Vqa׽JoN|BR++8{lmEu2 Idk <XB&[ٝdC_9 Z#@AaO?E<`nn?㳟 ~)|^QJ_*ӟƗ>Wx'<5 +4#%|^(4DuR#Ύ<'pwmH.g*"9ͩQE RۥȤXδcZ)v+`Ji'fiizsZGB!NtZkv 㥓Q7Qqi.DtV.b~y,ΞȦ v`{v`vaw][1%h4ZEG[ &kB)S(( O.Ȁi+u5ZWfY`er`[ -SFnJRh%{  {:La vTU3/r[ά*fYj%B)RDrA-c#0C^Jzjдpp''_T:*6zm墯 w[.߇R˯Dž_Oiv I'nbOc:A_W||m"&f1ۉdCO4S FzylyJr9>7h/[<D]r]ssvg:5fQH82C:{j3o{(<6pi1y$B`b(;֜H=E%\ i3d@WZlpV DVOKA B}L$GmmmQ{}\.2 ((a (].Q~m^CGP,XR 'aZk@rL!)GvTz܎LT>4@~?+_K/( W<k^T5 h`qc``WuR{wweh/൯ 'O"c˖-6(xݤ_ׯ9 !{] dC{P14 I讟^ vLVIH@iJ~Zi`'t\E*܀y[79.Xcva k(@D['V0<zytuh"A)Zk`ieBZE y,-cJͣ;ڜ 9M y4kW(0YE k߯|ZqpƘ\:=dogu@) hҧIlhG<2yz<ʵ4O~t{p8†>(4kL 0DP44/S$Ʃ8I 1~1wà%xHfVO Ϝ0(_ 9Iʇ>0~{[FXG_Bc877F(_G &HB`.eԬKR_+ @`v*W,(o'l4~4R#C^VZ5\Ӿ$MN$ 1DdfVNxXq\vm?(pnolG7.䊁'_4DZQp@]SXqnN,5{uFCɑuN>xi Ub2SO&%vT),!Q>vb})ti;?J8 ݣ&2a {$J[*IJj\Ivtv`SJJίO~+IyZ$]]f" IkP ,:\9 O#e*P BA\D\8˙O4]#7ĉUXZ6nm>G?yqy!d93:3 k76[{@RCso~+by^*wY|~*.FЯ(ȠQq~v QZBBq LCuM1TgoRv9 ߨS~\`v| r#BZ !)o+%AF cl{1zz'sx7B,o"昹2[G{ҚCY'&1'υJvi׶'(DlS_8H~qich]Fm 28xT`y˓80a26t! Z$Rgۓ>EN("ny$ }܆$gP&Fʳ=]r1r̙F&jmb^>5 0XaC]2a~s#w!cysMԯ W:ﶡ㋟0'Mx%v/l ,۾± XK' [$O\$*xBEE:%y@dCWDL^lURV}+E:͍Z AJZ*7͍"%ɖ y/b1ɦi/9 Ϳ/#3Cd KQ'H)a HD'sMYףLJHY ЇjB5W6w|kV. .!P-6*i5BVnӦJU,+RTAװq'C?R‚y.uq<ȜZYoR1x5 됤**"P&R g)PӓgPhGO_/Ν= 30uJ^:ax m6J%g$CNp^BE٥KAUAy 8u." :T"%4=LrJ(4Ap@Ok Of4@Ai-K9a8]Z?G1T4ݳd})V\3xje A:a4=>tɉ2jV^ilLve}_,% (-ʴt2B;5>6{"4[֞V/OUOy#,|U⑦'5OYtBmX%?'DE)?aǝlqgMZW[n~K&jT&dlgIDwmv& *@lS}4Tے5:{]Z8 K8Xf<ᑴa)d:m`{vN@NJ@KY\P F ! P,硵F>GD{{6oٌϿ4MAkT*1=h[d'M_ԡo" C] O9a==}vG"jzMJ$0%?FZ ɖ9t .8ﴹhO+> H_i EӒFVMCGx ɔK(rCpv9V$.x< Eh&m\Y9'D e7Hդ? MR'):xb4(-T1A44.(0{.ޅh4rزu|/ލ.]]I0Yз<-O8[tBXv}2z/*:6D DΙJȇxұ}}%uo!褁 J&C._)m4g=Vi2 }C$WB$xɣ~Eѓe;Ld||>7aqi!wJcSXZ^g4ǙsG|f/A#F68}$΂^] C1s9/jsW;c([6\r!8cfs!mX·~lmv t(6das5vMdKĪ^,ga#E/Y?5 Sn) pk4Z9[NEBCXF+:@mǧY2IIr'G4Z4L IDAT/MbdSN:_zjZog^{~zKǰk(pǻނ_DwWgn'/!*me=?7r} g]|Cx# 翁_D{=[zw~6< x<:?+H15fPDGy?:УIp.왞Ia8iJk.cbڥbqf=ȫ1GAyOΊŹH(*?lh!T4AvJ!a|Pߏ Ӕ 1zrESLƵǣZUS PΟwXP]BQ7>k+6؈_r%:!irJk ?[:ՠ}(J[/JC"V4I )O(Ko,9724?L/a(cߡ\*3m'/z}=O|~l<2.ٽ\%6o3GsnjBs}@onǐ|LE(/&8-:w{mȁ+ch7J0Hڃm@Pʝ^R/Hdb$Ջ!!%?p,w\,2JGݴi yЎL\_[|GtbBʗw$@*ByKCCr#%4INJ+-qS _ZxKsDh}%I$g)fd@YKOOisfO Nes Kr.f߹,W عm {~=NO@¶͛M}`|{anW߅M}hj}W@`%LllO_Xm7R ӳ ԇk_{)n Ο%,Ѷ|:d\j> '9nawyS s+Cqx1DڬxXde9%!U0t alD%i;A(Mm<3 E~RǏӸkl?*g__~ΝéSPV׿۷oO  +k,/uRڛ&PT0<w] fffcᦛnJ1 l2FZYr*WHl8mT*K&?W֟ gQ.udvaVL؋|>uPہc[p_[;{'ګ.Spy wcemkUO~< ݇=b[9Sx14b$ zҌgL_.ZzFY.<oRR)MT/m1ç:{Cҁb[$NC:;)g83$UiwLyp|U,-fIj'(fs h6P>SAG['::txrW J);v ׽7o8p&''\.;_O} > //я~=Іu %VNhH_7ih3?G}X^^"?sՅo}[Q /+8~wzشiZ4 @/1 q SG؝M;# /‚wi1P%1Ʒ{ fർ0g~Rө^c}}/ǿVAFwLqog%4AnSkf=c/+زiX<u9HG*`޽G?smo{p]wy%\s5xlr*?߬HuÎ;066Þ={p!@?|I:nq]zp7C)oGpAtuuc1??)o}݇1;v{ߋw8wyxVvd,y_uJѶl%dJ;Y J!%Y1tZ 0_o~رsvLjqho+;P5S <S?33;?8ՅR9w}(JV")%*$,z0PB Յ\^GZ(j033Y|UW]l۶ ݖ?Aٟ~~O=:155QLOOC)M6ayy :Jx,bV6I%y#cZK+q0`eC 5&,;8Ѐ{ʂ'ƒeӐᡠbph`dEL ThOj(EDTW$P,;9q$ZkeUxt o!@%H^ `I p(*1KK :}7yOlpLO}"zzqSKyov_2'N_ (xo?[0c,aW\tzGk182\|xgGG8x >СC155oۘΝ;Ӹ+{n}PT011{W L> ԤAio=ߴE*ָ0>>/ 8x >a֭X\\]w݅/}K8x`;p=~7n:tkۋ.2<|I|%\}{qW+v;Zsm@uܘmU.i\ՃCE*=1ӳ%^'@͏{73:1ekh8!=0ez(ËT穢>ATFzc K_mu(4`i}˔n@ Ȣ/Kӣպ4^9}˲Q+ M JC#{ٌ||@!-$ߋdUb?f6`W@;OLڊs],>_kk?_(&vCy?lk$e!`|'Hn|BLtؘpt oi걑 "$B[`JZC:rjLB@)zeVP(p c$JmqN:L\4?8s Ν;r4uy_-Vט_XwX\CC7ZԪ5Tk^ZQJlA>Yg|>}^J*~} Zk#<>pELhN<}`F3t7g'7GQo4pCOԸ'qu!Akh\*`VP,PաBGVGN)yWk((S/uA=0mP(N!<v1 pE < 굃Q4e=+ι ^2(ԑ0YB:YOsdW,1-&wtJ0T%ƞ佄):L`gv[*?z=эR ć7ֆ! rӝSOĭեXwLJB a|}Z*yGpA=a|r.-գ/gQs& 8>/\_$"x0lҽm; AGGk}}}_@k?gE_?&&1?]{wc-5j&sM9{J|D N&noT|':B('XHpt8KH' l>ty(K 옠Qr&J2cՕe1v:" uiDG '4dj# PT^3t m VW]? &>0o|Yl܏nLM/`ftUڰw|3&6oĶMPJEyTK2܇gfYOct I: ܦnͱvhO2m?f~*-̱t}#wj{RItE4&KGZ'l :L 4kC%]5}^8>]ۇ_|=H,`| wKfǀNlȂrLVJ<i* \y}C-aveg#?Ӽdhv@/e,^iA84ӕ@Xh߷ 0d؈:X^w`d&rp}vKi*цG.Ӄ&z|tg(gWO;s1Y{)xA1_I-ݷ"ъGWlܨQɤK^},\:| (Y'Mda%{ n2*QOq~c+OL׎I&ZX6͕ v:yXĕt_L<]BtIEAI?{1 QITvѸϊL(N>-%CN!%,E ]J-nKh[&zɈ%i&Uyw)K]Yʂ`:S{QlMQLP=k&гv~ktAڀ?9jsBa|"tp*D2'N B}&FyH`9?h2$$4v? =Wǜxd;FM^/Ek[̠԰6@$K2k& ՍCQIZʋ\V:ɵt7SJʛGCkw,&9YO8I ӒKЀh `F3`L?sRkclOMbڸf֎t fgNʪc%okH۫Ily; ZvY5gs& \B80+m,T<}Yh[nJ1~K4D |Jzj{QפŅj,b}-3 7)iMܞ9ϭ SHio>c^ IDAT$ā DޥbQ'C:!FttDÅd׃x☤-aʬ3?nkgMӡ6r[&'y@MHůHI냂D(C\g) ӟ0/ t)14[AFN0N&R𗒉U%MF`>+(kuKZl> SwQs9l:|ͥqEQ&Uo:َ}~> n|N 䢇+M! eIy zesi=WAW1vƺU(B9Q&]10$I DcƨkܓzjC N;|>ӓg׋.ttuhVFXL%`~Y*C8o3g``oOָG>0} sQROfڭ8Fmt)l ,yHڜ]'>gC'n66A7X]^ep+{&;dGi>A]Z( PDLneGY)tv G]l ݧV<~v~|O@xW?.ʷS3عe+kUh^@7:2 •*V֪?Rx݁ 05B>59\{ ֪5?5*q*ß8y^gGh|vtti>}I>} JGgw:uSF>+ N 6@ۋW6 P/ ͣ&c Q14GsՎ" ץZ}JH~bF:"qp6݉u=r##(PJa4P,pcEXDWO7?Bk|c8g+B.CP>hh yKs0ã62o|0c | 7wA>t=a~qkUtc`X ~.v& 'CZ~✑H&,ړc#ˆorc8 B _N#ԟ l_**ߖ*+#IW*FR^J+Y~<[.W[-ӳhˑqFOMS M\k.cVi$fDM\N7^L6$Gz5bscr);&XI&utr H#7d쉙 iG7+kk]X @<=UskK.QB}ʌ}H?+"Jo:,٧AT|^c}}/'y`$T8* c!БhJhUzo DLYW` }[i2Oxz#ȣ,H9Tl$ZghML VƧΝ! z.0Yi]lNʁAH~I,WƆd m"Bv>Y$0d3I^ HsɯBsmhǙ L (>[-5V-m*+OıT{L=br 2`,=, 3u\8ikiHvd>{ωσ6zp`P,˂)o9&\;yk˫ݥh^Yk'Q;M b+ʣKIĄ"18&O4Ti:~R5 kXJٶ$<G6j ΞJZlт" ;O{ '$;_ bOOI9‰Y/}}yo@d dYC@( @`HYxHyr]B@7$c5>vޒi>6>M.if49D5" 0"Iu99fxh>HWOe 2Y|/q>yƘ{ThU.e@oFFٱf^xr=lu)f0xw'׎I6o1uO~9j oB3AwaXp ʿ6%80 xj<9@VXZ\: AGY\ɡB2g"V7_LAcBE 8QGK>ϔrR&CDʞh{jF;!bkW\+aK-6GdGO , h&zk'ɀ?~|R e%po\XaߔGÀFs/,Øt`!@ڏ*i"',_* (OF]T!k^>2P0k'VWVQ(PP(P(Rb5f]X_@yl>尶ήN~9{ۃ%T:*֠Fgg'ְή. y_Wd#vbSNi(s9J@OD/!,_ ɔT xnH?.dN?nҸ4`v޼ J- TI|yL;j#ȷBv6NәϏ2Bi KkvmvWh-( ?ɀOyB&af.Nc]f痨,bqj_pv/h/ Bo;ґͤp7Mێ \z/c,2mnfT (G<`(ʽb5^(8HTbG{hͣH`{DERxf稭+)Ο k>Lt*EuM5d,=  hher|F&VTJ&L_ALD6ezr F0^ !H\0YuT-Xz۝=]ZBnסAw&sI#Ԉ׋1]AG-' p^XPJfn1y VろWS1=JX}d ȼl5.lU yQ yEVW_6O2SWqzO\6i, u,Ɠ$Rij*LLP{/[w"m>&gI_AnLPSD7x:u_` GN[~۾GyY9k_L0DGK3):[똝OhkatbÀ|>Q`Gg|vH69 T^)*C\Bhv𻹺5oqELaAֹ ّE[@LVNgceGI4Eht c+[S|cHBXNDbNuݏ@HX4UĬ4ъ(wq ΏA767ۊPUSR|Vƙ[ NSYYA$!6c~~5Ws%**+hjm&Nfiima||ښnEc1dlZ~\Md[{نi-<{hZ;/+W[*vs -Z)UWf5tY~J)*W1ڶ*/́bXG_W1\ ^l᫡g%!?<~*R, E7k}rsct,3|~?TVU$:.#JQYYL4#)U!v\i^[ι`5슬 +>q=tJp6R+Ua;TEˑ#G-N>7MկX6IW-X^Y$ݧu}fٹsCCv)Ϳ7:,Y* l奇 SkyA^tOx]_tP뼀Nsvp Gt\9*j+Ƀ%6n#RUQ[{XJ`~1!-<&~VRW% xRj?=xkC8?Ll>h-ZB<ɦ56Ty}lLI9cN*m]ӵ Dto38]Mhw7HGYn'&@^a48kgim6q D6FpZL"wփpݑ^ 6:,NaEPqTT jlAR WD6  1 `=,--֌gŪTTV@}C=xea** N~p5dY/  Gʝk$-//cii L }iKB1yVKcj)z lhmվ{"Zrsƒ>s=9s <.\4MnVnvN8A$_o1<<̽ʕ+ٰa/"H555^gR"M/jQm ÇwיyXZZ__oo'? >[<|K_Ow~w/սޥdv{)/Efd^X/^ͽvvŊ 6.|g|(kWpy`a OT^Xn)+[[-Gbd;)~u#1Fc\w?ėR[dbINKcSWxB099e!ݶzlXeG^y|" '̖k~yjHw oXa cFΝS)m1G35"C(-T ̜O[K5 `+x~*@+*nm?/ww,ju2(l_!YNW7T$32 ]˗φ xG|}v~q 6o){SC'??xټy3 zfY #(f"hVu^kعn|"~Zjkk~իy%FboaDQSOGGB!Oz>^vrF IDATrK=^\Y?^z2,Wb*FCG׮\&]x7^8~ q:6Ͻx}ܟ3R"jhJ@@seB Fm!#J[ȯp kW>Q.`S٥~BuYE糳, (r8DI3A<7po XP$JzHEk¾n6t:Q`SNlN6F]a496C" L 9`x@L-觅73(zPR SPm͛\Rv!085ţ+ꥺť%f抋R|"{oǞ#,pAyBf#wRdtmJhXR_OXS7-'.Ef?:R`~ntDckE >ۉ:Ͳ磢ܽ'{ˬsssxYnf/0y]t'W$xu%277Gyy9eee ^^mn\. ,֙O͂(#V:e汤01L:}WFE[7mOҺF렌˻ઉg;VR}]YrsZu73>iSWHʠI|~nKE(-tװ"[Iic՗Uh,h5Q ޛ^̪8؊Du(Nl봷ݓ.H A^>M/^1=5M[{+Ჰ}I vrD+SY1=d9H55 |)%#" >%!o|zHrHme.;;aP=IUHQS4B-'hf.K. =udte+g(^$XomG&nA~9b9u GCl%WX參 qf5fƂs(.Ng\u@D.|۾X PE+w~,RJ! H$rNP(H::r **_!LLƑ`0(3:*++Qh+YClLqNvy'DY{{ (.kIÚ3ːV ` ^Li Cl z`Siʟkɢt>W?2&:hMi);eR΀ H|6T:D`B/N :p)ӋW<逋]ᥫJK䡣O+ @3u`2R #1߾7fs;r"GNH@uʊ2Ƨ穪(g͊S3 |x n)1|B{VFIkS*"HSDܨ-SSRPW_nBC+i֙s[t&L"#Z06)>QOat@?Su `9J DObpbGqpWh#9<aicHmmUUi.R@ @"ʥimo#dN~qr?`@jo> pEmm)D95&.6zF;C#6< me;+{$: Q s,p- =,imvR53#PU͂LսmP.`uMW @ |AqZNr`7pvRgC~C[8zeqQ5~::tl,r@*JS:VLg_`|:&)/ Xö ݬlda)LP00a,Te}7WZOEJWjYS-tAp9\8*ʧԋ@JmHbH(rld&ߐi_WhT:ӹnMc'aq5FϪlYLv-d٩X}"='E 콁!:~KJ;D.Թԉ6-t(1ʨ]LEg/9E^KCWmMTE` @Y8ȥk7X-eVnL_LZ˕qVv4I3tՓɚ}yNrK6Ӵ}n+. 1ǾcdExl.! NMg,p J6*cO;}6RѭAQK4Kg |\TN  H`+%3UZыAf[C8*1RZ 4N@quEԁStꁍ@%UqtēD9úfmǣaJ BY>1mY $1 &=06qVfPMZ(D"m˖N11Ls a=%{{l^"}̗8Qf죗LjR@̔NR<@\i]@aə&SR,,.%FD#Q-# UJgl#b Vt40s9 2 \ &_GУ0NDnA?]m"} lhXkW}D^n&J ˶nN|5MbNE7@" o6YE}^0ٻH"4Ea' P8Rз=<&ٞhƙ霋-)Gw.Rx48!^\DoA)CRBEɭ1 yH5_)ҰL42Ҽm&q'd" 9t8b8ysiKǖY|>$n˴7.y\ s*.oȉ -L7~2 d'%X:&K4222nx ,UErQy-X|j3RFnduL},*c+.Hac#`Q6e!pbFZI6}Tڦ}ϾV*d+𚷼^Zyt:*ł-e{Y`Gi ۩ ӛn_ۉ8LM1=1q?ITk}ߑ0Kږm7@ujm /p:diJWڪX{n tJuqN7wtru[f] kt=?p\~Dn/DOP0 Vvtc`Jo}QpM>:Eib\HtDu>ȶ*3aHxBZ*MCy@0@TN6ಲdAqr/+=,POrጽskVt!b|OChg_Q;y B^8 y1@%8!ю*=I7>x` _1S+⛯unkt6d8G 瓨c̪^ԣX&JWCb r_zlXGgRzKfRkˤ(p^{?ۊHZe2? /\__O$/[rJVVL "n|zCK7Φ)M Mn{u;6Z(sQ&_v}@Ar:ҥCsAEkZ~S~jjk>Bm}-rGg) 6c kzVDedJg%詠R1X<qr;rN>h$85Ľ?ϒJ-ȤհG/@@ $銂 oУ^h,4r.@'^6MYP+N<5r6pOMgii+l=l7,)l\9 ܾ.蠴7SnFk9)ҼB(q@a?(5&AK>E ~*^ԜtMA1!P&J?҃Ȭ ..q)lLƙL\.ǩOijm,(h:.굫VD>[֒H\N|q !ZZb66罹he+CZ`81g!VtǮ/HwK;Gum0si67e\N(,sD"D"~^H7Ontd]oB)V@{{g{O [{ (5N>7*&X> Χd<+% Tdj~6@ᦂe])E^Ea5v0% a;8 xt O C]F:A!ȧnQ+;iPԤ.m I;uhA @(iJv8m?kZ՘ZQ0h(6T bvtBbu4?MC0!)sH\|L FF0 4MjjkVD1 G,̍ \$kzI&dfYllG6%d=cUnϞ.VвAa99ClcPx"͛׋}~-h-:bu$fTcX`(FC׶X;WyE[ thrtEg+/=Ds3BtRŦ5O1Icd7_o*"Sr)Ȣr!^ Ҙ2K˴Bvf5! T[եp(61m {lS C3nvPi^M~5hZ^s`ɕl<^I}K7tAT 5M_7-'6l&c΍͍ BPYU syӓD*dy Z?cNRfI,%>W224B<q&-tQtO:-e7(Tg?݂Nޱ霪C:: eC]_{K[)ˑ['W;ٸXzקN^/F>:=lnt*?/tzdPm5^nT'@'ֈ; ܊s.oEmwL-)vLIQ8D+nhɢ'ԛUA[I>Wr>N;qXx-%p֦G9͈4蜞1 B"I$h2CR1-ږ iRQYA*ʥ>Z[ ~&& roBWT[| V]@>V07;O hmk!PYUI*yih% 1>:N"ꁤafRAKP#W窭 iy.oUHTG,%oȤk_K[Wk_*(^zx{@[1jUJ7JKR "R};w`ht瞸Ǯ7?*ʺU\c=L!r904neht&s=le6(ֹV/W\kIz|`P D3A8 = IDATS2u~Fn1לiEC#z!w%h>U%MMߙ5uvY?SBι!ӯa@CSf^_(r+Wtֹ޻zsg2Y{Vv5e![d u,E٘ab)/r t=(xm_ t,r?:[K9b/x,.4ryf:Vn9cIAǖW};Ól"ZΟleՊ6R R _z~!^}3I2=ds<ɥo kV!%WW)vg)q2{jw>zA@ǣ<jwUU"!pl](-OeMyPĎVd2$S)"DJ kmo))khqۺzƋ=1aroSbnnNDt:M"ܹvۦ< )D|Ɨ<'Tx)A5AxXfPIcNJ*.pe(7*AU|>###D"$I "~? d†Az~cTDydk}``ʊ v"Hmm-9sa1 SN D"ٻ7 HpA8|m[ 4R$pٳge޽lݺղd O{sXbnaqW_}GPUUE4ݻ9yVbrr h`aa]^=,-9z(]]]?Gf{vr9pY1ftl{Yb< `dd#Gj* Ud{ڑÇ㔕qq:;;%)EacZӧy7f2;?,Mk8q$--Np!^y߾L8s,o&###tttpu^y8 맻VGf ׯY_;-TpވkD]eee$ "rNocھyռ Gj&͐Jd|GA9q8#\6@GG;?_rdNT*M:?ݵsα{K\| R4|?bzz6kOr2]]]+^R4 Z#HLA*$ 115IMZJumY;UDN 4szucc~.L;֮՛oIGGCO=/W'Kt D",..RSSC2`~~'N1<2B:fϞ=X @&f_oɍ^E]xvp,CN-mp0 ~',q?ܻd A_gmf"=("Ut##o>pT*E("u:i{ztwc45aF"C!|~?1:;&@uu5C0l3oDE"<ș3g}v^ B ?/"f|3!DcC,_pvi8KKK)^|EbO<8lK/S[W=[oͪ^bsb<9s8u1K%/K~Fy'x׈OryxN&mm<;vn>ą y üTUUO+8p7l/H(4Mnܸ^q._mFSS5\`7?~\.GKs3=G… twws}P>`p`˙.\  LӤa}QLd|>zI|m24&ԓOr%N>Mss3?0ǎ,%|_+>#.]-[hnngN4#f}V'P[<{g(/8svrrT2믿.G wy'gΞuVmM _ػw/)֯_wi8A.O|W6ehhwᑇfhh{RS[ˣ>Ν;طo6m7ޠ7022ž={Gf?ؿ__?a^عs'`\z?À;ñ={$J>Ku=3g[8tK87x"ϟ:~444oAY8̧?i%6?!9Ӥ~a~?`CƍpYΞ=Ǐc(T{ swH$zo5Wo2??,կ~5{4ybrY3D t&C}}=I0tvR{mg n-@02ed2WY/n-E{]L&MS`3s(\ȯ*Lr΢K_"a| _`ml۶۷og`p6qA< \v== \Fww7XyժUܱw᝷{&gpcP8O= 4_2O'ruQWWǶmxge``V9~#V06>Ί+Xj###[ͷbʕFss3ss _LI=kZ,]]]L;px22:ʵkKdp8LYY`d2I&Sq~b <'y߲- v>u|1@͛7~jB6m⣃[پm7n䭷ޢd*ũSxr /kl&=,O<#ɰnZ8wccc~e`rr^V"_$L288/tvvȇ~Țիy' #SO`~uí/O~$ nܸ>04?!.dWGnLP7rˣ 2/#&Sfss7y1v~Z:8p2ӶIf˖?~m|!6nGرc:uK.9??%"e_omm%L022ʕN;E [oҥK133êU[Me5a݊ngY` (ڶ -X?;'gnnT*kK/Ğ={ȑü=s?~>x{%ݏ;ƱcGٿ?W\q,1>>˗ؽ{7,`r9?G=zGrE˗yᅟǫJ__{رc>|Db4/"2.\̌m\$12:J[[k[@CClvd70&K6*u_?=~w7[*wE>_o6;F/?g7&)> p;_9UIw-+P_oJ__U119IyY3,55m `qq1^_2w[5FTWWSQQ<,gE[[8TMZnixi Ä̇nVzHR8y Y)Dl.G}q3IF^I gܾ};+{WcpCaks WVgjjw[nazj AxG?fnnΙ-$A}^ǃ3M֬YC]]v0 ~vEϊZyuMV^M}}G*%ɐH$~v;>kvm}}R8`,..:]f ƚcuTWWJ9u,,664P@`ݻ͛P]]a~m`6oFc9Fhw77#i&&&XUg/}L>|\Ʉ+f+\"D/`3}7MO?%4˵k>AGG||_ehhd9|\h4 Rcccs=0<< O{M.'ul`w[\#soY 0Ήl>p;Egj<ϫGf`ӄB!^z%jjkyᇩoVLN02oΏ<7m0 _e66˶m[޽۶L$gεk׸{fݤR){9x <iz-olojj$ y'?a|"Feڱc*0gΜaQ"4^hnnW^o+?w.]bΝ֓N:]nQIDATܹG?Ϧ>o@6eS_ap7Xd [6ofҥlذ#Gnw0[6o_-[²e8;lwW<N|i{v_~A֮]wCwqEz{{QsYvxÇeff}7;}4B[tNJhnj>IOc"N{yƥ+B!jmy&kC!sy._ա!sɓ,b?Nve}Y yQOjjrr'Nk.Μ9äͱ~:z{{H;i(B}ڲT#GfݞZVʪ>8_n[OFQ6nHN0r ?|iTM4L?@sSSXߔɺt󇅿jP*"'TF}6*?\O_b0v 'O\=;< uW^j(4 ȑ8A,}5kٰq+pдҫ(ضʮCE'/_|U0spW}{+Oæf,_X,$$I9p$SSShF* bvLOOȈl2t]'[oQ(hiiaݺutvvA&qzzzRd̘GJI*bjjFGG1 t:ad2VZE.cllwwt}/nwwwie'pl}rJ~{xѽOg~ga6 UƷA2k )-g~18Q3R@R4/#`Jal}ܸxEŲj-->ŋiJ<Ám#!-2 N>jnj:VtlnлOL36:Ϫ:PˇXt)DzֽHIH$@BZ&ikkvl7@+og)>\6tNCՋ:aw[<|F'8xdDL@mOvWNj+)YXBHDmFGOjWX./DVMo~aXdhhw@W<</0a_ b"7 *YIa9gς^4dVU d 0sZի000@OOsss:;Ja:aDZ2J C}!Jm[/4 D%0PTlN܂[p n-܂`S-z"$O\U Y\WyC[\]+*Q_6QmֺF'K-njtY-BdU[Ϫ˳=g/u魇?zi "֧N/l\nCF,6R&hM+]6W#-Z lC?/6"LjldID}|pݴZ GWmQVMSnrQ wqfvvTXQ&Ѽ 4Zzץ}s\m P^F!&_)qj^ENi7@)#mFؽPr;8[@(Ӳ4p=?>0tKS1LM?PP:`oLMMOĉ˟7Iǰۉ{IENDB`liferay_5.5fd9c45ea82c6be12767c12baba4733e.png000066400000000000000000004766051325274564300422770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR2 pHYs+ IDATxwő'mx6krH !@$@D l~ }3>9w J0$`r\<1g ڕ>]UթgF{{Nq<:;:!@Ӽ4#_i`P7Mi&Ix0饴YB/g )a!) ;k7Rw 3+qȕ+c+/js/.}uN)Cv^y&_Q.%}+eeycF)/ל#5Aio^un3iR9k~TuNPtqmxENTs[Ff:ij_PɚoY/_y si}t2?k,e^kkrE8c.5]z*kxZ24>+~L7vOKPxK`JyY/i8a Y^gJ{{m ìވ@:;;M0 AdP >Br"4ܓ'/j0[ے{Pʎ,#Hv$٬ QjR6?NOH /6i`N>9h@WuO7/sLxYֵίtcI_:4^$yqN&8U^s=d 9h蘃 Zk>4<-%Cū22h5K]S]\*^]e:!Lb_.|t9$>&tW@]eWꗎsxÑ6:j]{ӽ:t(S 555z/uJX ޝ֪SH$hnnf߾}444MYińakt-|F.++v,,( *bk+#'Sieyҭ7mHuz/eWdy)}%.v-RUQAeuzPI[th̹JE3c|(~s\LdKiK3UPhIS&YuN dcJ]Ro仨Ґŏgiz8ʪ']dBϼ$̨S零~cqseq9^ 瞴:^VA<BA~>eeekR6jjj8xE׏l̓L[WtttP[[hmk Ѩ_@fQ_W `Z1dP B١2 U]UE[{;e8x픕ZP`J?x,zç*#O Lᚉ3w4X}ﵙpد&nڎi®<ÑfP4^E=7dm?d$#GnO|A擟^ŵ~$%WCT'U ^s ;Bo23+> Ե-]w1=MKNuV ;^4@˦(ީW&7ɤ?xGdqw^5}[sKK $PV^N4 (--:OuuuԐR^QA(*򢔖 <'9|B8x ٔE]]=UEii)H^EE:sAGgջZ:B: L?Ҋǩ"7Kaa!V+hFUUyڎfE0dw- TgZ>!O= ee\ 4Iϔ+z@ >bi<#dsWʚիyWZO,#$i 8ە J23%KX ~_1|>S~3󨮮3LyS-|7yw'9T!;w駟;3A9K}KW^R'[ʐS6-JYgzYٞ}AeͣY<|ђS8}$,)ܧӾmk]T/|$6§?{Ȗ9QrL&)yd &NkOkN :xѤ˱CY{ѵeƀ|uNgѐFj~c a+QW_OqQ\}~1&M POSc%%%IFimkptvvRRRB@BZZZhljHVDCt9<`-}ZO~ ԔDn@j* ȫM g񠅌:BFɦap@ @II --$ wo%~!O(bQ{z}B $c+/˯svnVxלqh;55rVڬ,EU6{3݁ߵkh ~OQQ]"6\4tա$?_yg۹@xg~eĈwk+GZ԰qF[ŋUVsNfu㈮(qСCD"M694jJ{̙O~z~зo_wy͓* ]0Io:@rN:.'_ a?*'#xX΂SC^,zU7T 4'Lk5*Քക[4\pr\#Q|9 :e2yHSتHxoy5r]qt jh1da֖L~ ?3.v?PCN l#s28 GZ{;.|횱:R iN*Lzi@zmؐ+d}<3N&MJ{cP$VWRU x>2uU>G> -<#/UtZ:mՎlN 5]Ub1ZZP!;K3Bx5槎}JJ蠱v Dkk+999nTH$hO{]*++6mlݺwy|=&F~!=m8FB87P.uhw/LVK{Υ5 M!g 7es5D"F0$ ]ˬZGyh4I+Vo^**riҥKٻw/3g37ߤ_:.9l Ƴpc1~xB6mbŊп."ҥ8$ pdJ477S[[kO2H$ҥKٲe  bxձb jkk4h0~:Y|-vi~M7Sw/K,!ӯ_?&O̚5kصk'\s-,XQF1a:[,~z-:l xl߾2N>CvA~~&J~dѢE/}#G2}6w;-K?M7Odȑ S]]̙2d՗Gͥ^Bҥ|gnZ/_N]]-Og˖-|jZy>}:p˗q**rꩧ"D~c1h {= cKH:%q#( 5BO`jKH@\C"aq6A"Y .NۏL\T'Wds<3pE!P<(,ϙ3:hD.zɨ~Rr.~~uyT贠T.jqf w%9f)P,lst[V2Y؁z/8$ s'if54j g"P C'GB΃+Assql84P@*4FsˡvŰ}u}AeyUZ(zxxŖjh*wi魃l.ԵË7󭻏^̤9ɵ/oYr(6ZGZQskSs8@@@*D(;w0p rrrnXɛx'Hx\obO<78_Æ,73wl2wvv 6m՗_SO=YfPϙgI>}hCzZT6FD[9mTr{ 7`+Z{5:ε 9LA mm[)Æ>B'|J677Q[[N;)Sδ 1w\VX_ /<ϖ[ӧ`0ѣ)((` 00w\_WB0r ׭_=D?0dΝqǍ‹.̶m۹ؿ?+V .pnmXHwNxR<3H"FMcc#`'Yx1 _Kv4441yĉ'?;zhBgfu\xE<<6mȍ7Ě5kx׸/?~rh hjMvp5r6t'5c'鮧ĪwL]_hI뮍 ʼ4+I&(}dԠOjcYgT[˛ Y )![^nG@Ca/)d$ù9TEKy5I]^x_e|y|DYTxdCVW*Ef󨲏NU{!ʞR?JM|mF7zHȶJ  ˖d[\u~/x\su|)+!! `s,|5>^xg>;ﺇ|\&>!%ۥ^JcC~mXOsSrÇG$ڪ 4sZ4*bUANi#<YQmшjV,[J"IT@ hATWWǚ5kx7ȋF'SO=OiY̙=n EHGG;͌;#G:vV\ @^^&Lp-V8Æ''' Lºu?>7tD"Y<9w./!C׾F8C[Xp!/Y`sΡ,N?t;|˗/ 77'p*rrrʫ;wP3<˲&=MEE_F]wW_)&1qD{5?5X5iN,ˆ#=g6[nca.G??#ڧOf̘AVV կ| "s>%^FII P#SLy殻fpWPSS̙3׾w|{G~ט4T=*xm GJ'rVYsLi\uWf<NW}]d2Ukλ4L}M]9m)z'wLb݉M8ǴQ15qt1И2jβNs{yvm{ڡ2)%)8xK4<9z2# \,4^L`ٕtrJ(Z%-hY< bb''(CR)<x5;mU+8`Ǎ9D{GttY7Yb^ze?yO8ӧ\r)uu1ܩtI!knN_Cxm Zj) AuR>htVνL#|@!!YE40_MsUPT\f<; {wޔ 0bݬY_ꗬ]?D"A]][lfa|_w4]$ydeeJ3\0ĺj7?+V,gY{KEo~7tVaȑH$8x [l3Π j`0H8+ ͛7q3|pBSSPR]0`3N?>}εb<|p^z%CL^Ǐ''']vqKI$M#[v zb {+H1%!?j̧e.gϏ2bp+8Ca[yd.gN Gl|_xC՟qrYӎo-2c "@V;6Pstvv YJ: TK>}߿?H?zL9!>BoJ~ DQMC}=㟤 <#FpBFJ O>sϝI'ªU? g}zu<UUmB BPVZj<&6m^5)kFMM-۶m/~>%}'1kl:Ship1'~es:$׵굌 |td^12)G]O91[އO*/:y:6mP(Ɖ'-nL>1YaFrp'0p .Ly4;< i… yG(+/i"`ҤSinnlv'~/+W 3:t(B(..ih"N:d=4: njq?qcAӨŸ~!_!?/ PRȐ~cH,sΡ3Rnh'|2C2Ϙ1z~ /[Ԅ/cB̥h M_QQaG(/+ uzbVk s)0}tRm|{e̘Ba:ƌc" !waOySoC}jnvQR_za;*j95T  _9+6ίc3{G^)a <uuU,) +U)tR!OԲaK0f>tM oTJW;#qf8RԗNV7# H$3ఏX2Y;xMj o2pv I7D<Nz*c>8>XZQ&?9%S$^I$OntvJq*g`=CLen<$1C`;9 u3sTZ[zuhHR\l*cvRs % dee@4%o@ b6+͓a@f@ ^vvXx¬l8Sun 4551q 9>8\#W@[{;͒}!qE9r)^K\6žL8qqafOvvC+kkk)*.ġ!1ф`ʕ 6j~p9|DZz80{c/#'*>w3am]?)+/~B47W==!]G .^ /u/]MJᡛ:[w8kq2YA7!es&V^kRқ#C\Rr#Yڋ#=H\o؝,CI$NDь?ԞTd!Ԍt嵊)ue{ԟ4&c$;WҐ1O8ɛ2^9W2k3c͜j~K1b0|WVƁ%gyV leH衯 > upT\%Wygc&ӵJdلL5=vUI8uqh&Hb/^ijFjJ=gv Tk_m#NWv?r~)ͣG;N^4?ՆOy~l'ӫYH$xhmmncذaƠIa3%v-MdmLAsLܙyu)~fi˖^))GiY5"R37mk(BGzzEu޾PWNb'3KF7J[8,=T(^캝65OowrWI^$G[(}\r:('Qbړ-/o詠gV:t>S}o"ʑa?dVa Vf:6/D+C+-PbKuK#C\2|ɠ{z2!ZZٶmuuut㄂A)e#aoyUر! BE+nBֱsv'CAA C$555z#K mhnnb'۲Dp2 e `H$I\?a~ rt/ȓ|^ ~rfzݲuҹͲwxJJ 87@]yVeoxi; ,bwmK;uN"XΡt$H{C}4w %30;> |C9HIUv^r8FQ뱓YvVw8̬,%p{&It^3YQ.55H:\y+FNWځH)0m0h!3tWKՏLvBRtpeZo{+t?8QO[z 0pXOc^AG{ u4RA=$?4!2_hH:ۭ!AaQ!ZScw,d]Z 4HX5"< x^P 213=rː{ᒫ6KiӤIr[ pzz$~Yy<^tǒ! &4麕6n)tVNՑk$,z?/Viyto q%9(4V[YT +lպsy6: 0LdPh;uBS |X/>I5ɛ|^=׫ҥvG#zh;}@a9 eMu_i ΚfǸ3KepxMZF.QpB/n & #B?*)n/iBU|T8a9^_O'?5%"_ !yQVDͬWLY@%+<\ɲ9,밝Y~\\ nZeJ\;RmNN%ζޚ^^^Y9&E\O{h &h?\_;/n^5Vͳ*whRNZiNZ٧pO.#TG͔txn)CDtHdH[%;kN}Ƨ:fBB,:V_S򘑝dQĩOݚOlۻtߜZ(vLӄ" ۛɔ ݽ "O !My>.՛Dw@#ub*O2!"OKsk7C7{e3Z"YߓԵ~?OIfW7bSO 2&̰V< uC?.{uv]o"Wn7E&9͒Y &\?6*~@ j㴧az.Ïo1ETM錥6?Sxc輓7oWX\DkKU2 KI;hvxJv]ctzPlх<2']M'VO/ت;Ӈ4^[U^[>ڶ*==If9SX\Y- F߮V(K.gWyy\UQ-d}%Yu [N+_I̪#i:druOt*P~F|FvmRO%w#}Q G8ANllۯ*󕗿hu|O͟p!k̅N&TOo qRȖe,vi;Xh1q>N|~?Z4dH ]T| JWm Lꑄ: ύ2%dhcOvtqG݄^|r|OM]8ҋdPc~#۽҈FP7dMRZ.'uu2r(<w٣h9 oy!O$8p 6m}yI5TVV^i6[m4\.v -LB*k4mmT3J;Ҷ読tKK`%mmAkkUUU=VGƴ:$zZZÍIatR[(#[4/kd LI|{GXx'5558p6WyGF5~+W0{\fϙ9s˪90@ijj⃥K1Kߊ Y@;=g޻̙;WKhEՔE"`ժUTWW[r.~sDZ^HP86n+j_y_yݻwc`vņ |\U;Lf&#_Jx|HXvܻkxm,^ؙ)Go&;tdIWwWjkkY O8t_5~^~{͛7kزZuֱg>S^}UwK/d|TH$x뭷tL-[x?~ҭx[J$Yzz'=IIQw̨$8pM6((($??mΎ;\guqHT.y=jG"77>}IqCJ!pr[=Y13OS^6qKѣF,14MxZ]z(Z %Üsٷo?V|ߦ"nyy)C]=v1Ehnn;wdʕC!m/J,3LmͨGTfŊV{]}ɑH;>|d ̜9h^\bVh4"gҥ̞3۷[ctƲt=x[Jgr>O^[U^[>ڶJ沩X$Y# %%ۗ`0HNv+͟! 'N#X~=|g_z +`U~Ǯ(--[J8橧'իyihh`ر|hnn?G"ǽ@+0Çsg8jjj9k۷o_߾455o,ZĊ+zvm<{R_WXf5wСC7qĉ޳> 6#F0ygMy'صk799=c 4,u70a8/},^~e>h5-͜r)|k4_|+S\\'|¬)'rqF N0@l Bcc#MMMQ3 9&3w.Es 7r_a p>|8=k֬aҤI,k1cxghjFE^^vN؃D ڃgoB {x;d͚5lܴɢ=z4]x!ӦMc{y.b8t/~^np8̥\ʨQxYjÆ}l۶mNwusOJKK{ \.\[oȑ#ηUغeR#,X/^{-cƌa3ϰzΛzHɯ}JJaÆ 465ү__rss=`7Aaa!V}Ă^c]w;v,6mbOe%466_BQs3Oy"0W^qGf9\ɓ`\~ttt0wڵvjjj~`_?۶m_䤉'λr%pg)kmm-}VX9^GWiŸWrʖ[SRBSs'uǎds_?X+\wu?f͚E ;v,1x n݊YటF"K9sSڇuk1bHa첺5 B(X2 iN$a˖-̛7f-_*UU >\3B@cv6|oN yvO>&l\}ռKqM7Xk+99 ˄=L29Sx'mnƝm!=G|2s 7H66u5 2i$b&h._Wx饗>}s{XQQ'`&(--Wal6۷ 줴_WTVVrۭRPPرc Ì= :X/~ dʔɴbR\\LAa!#쭬ŋYr%xN| +h{'pt(үH8b>8Ƕ{\r90k,yQ/)7-d Ǝ5Yb%ӦMڿ'L ++©ҥKBo>uuu&Qaj]?wi{,==kӽJ?kGVfR3(i?cz݈:dD"w 14p8l (io!C0|4#}v @]]Cm:N9e]#GB!h]R8hѣG9JKټy3ƍeÆ 9 wٞ'0Yv-}4FwͦinnEhll$7'Al# h@ee%EŴi.Yv wK<׿ BٹsmmرF=h?`'r }6xxsޮԜÆ LZjjkgM gM6SOimm#;;,Z8XUE"kPQ^g72t֮]2@4e]ۗO׮l?i$zF,L# >O ?xLŬY>}:&g5:e`Pù( l=޽tvv}vZ9쳙6nHUU.X&&Olїg3f4|Uɓ馛%% SNe,_| _r1wP\rɥh".Rʌn _jSO=_y,hgQRRR__'0o3|c=ok4.RgYd P3N?&2M͔sIʢ>h $G}ͥ3N?W^yƌѣعsO=mm8F͖[ *VmSΜ{`ذaD͎Yx1# 0iV˹/PWW'N*hL2=3H8LGg  ry?K[{\}Uq 墖jlt|2ۮMo^|G"I/ߓd9:ӧUUUӧO ZP@kjluu*DxgMʲ^2$C<D"A4%@AA{L$aby<8---tttX(CHAmmm cL(1X,Fkk+HxPX,FKK yyyD":::hnnF4rss ôiYY  F$?~!:466!n3](xwRVVVw^ 4bBE"p#iii! wAͣf4Ekk&ᰣ;ﺋ7tttcz0D4rrrBTWw3grߏLNNmmb3deeYm!0xgevo߮)|nNYYYTTTCSFH$?䜝MSS3FfIvvS("ZO*bA"csPAADF%Suu5Mʹ47I8!7'QFo~?LjJzm>*} Z*J7`ݻwkme$c;`ȢiBgo{ 9o/*}Upmu^8 mBw@wZ;r}ysnT5XQ齂tTCOݩNONUt-,C7^]zw٢ i+(>B6a [tO{,==ݓd$KOOd'$Yz>4B!(,*" 50C[4DU>`ᄐx I~;O8E)KV.}zKk^C6\IlNH節Ҽjݑkw5en=:MkiPXKӅZ.=tlw,/Yz<^`B^=S^?[BAM2^#ҚDl$WMS(jpknbe=T$ǫp锶QKO6-2IJ|*[t^ټ-#m=!N'VGhL$KOO*tO*ѶeBmhEɲuM0?Uh0(,w`MBTLxn#xUOWճY=K^-\2McLɞnZ? ݈HM*|Idp`?9R(=V*aV?1>_y~X$Yz(C$FaXe$baN5>r@ԏtO7?Q42!A*")]TѦpˡ۴tQHnlEpYh{Ӈn^?7>x-+F) ^a/ߓdx̳haz)VAՇR)^~bgM|[,[+w`9\CIJ(ֳ͛Tx!ɦ$%KCm఍~?>iĈ!rMw{MGW2g6 VG6}KWlG/mn[d#[OK = Ñ28Ӛ5zr4 N/|@D(**"2>;vv|^fy|>'J w HL3y(?-gY+>wDJ岣W4?tW6 hGvvHV^^^^AAGG~ö˚H$iFܳ% AMM --1h ;B訢uzl.;xp>Ch\J@c5PwQUy;L !!)dUA]6wׂkYPA՟JT:"=td2Lǔd y;ss9[Q5Ӻ󅶯MBfIDjۙBuq!кh6[ k>B: (,Sj~8; Ƥ5QPsTuq$JM$a-+/ H.JZ}|<ƘQꫮaUً^zPmIo}L*UP&""(" BeY$I#2$q]} p p p p !|>Ξ=KC9 l<^/~hM&v;ZY@M~Al}La!{">|Ar p!p:>}ۃ^kJCp7 aiXe,˜,aoZ23=T&(5 MᇑȦbS\jb@NAPsUhUVt [~%|̶љtɈRaY[v&NޚLuu5Ad8y3lj5Fg*)C^)Zh.@uu } ǏBTj ưfͯNlN`CkK2fjIj֡7%Xht:q] Yh\\%\%\%\Ȳᠱ( zYht:Q(x_XH'>>>FR rG ,~$4JBjl1[_$ .?6GIX jHæo+Yvhi-_`yhX.!*ƷCw;ӹSlBAD!D$TԹjOFk87 G#Ē %L>NeРjԱkARRR锓Zhfm}-Pb1k1B,)=º_1KB9Dye9nظ8:d1#]"i)2ZN#G/+CVqաAP`X8S$#8Gw] ̜9xRRSQ(|T* u[E;6!"*4*2^ A^@!ʨU B`uXQƆ:B%(,,DP*|PM1b?Bem޼aÆ5 fDD~_}0~l?7oZF~Q\\%77gE>W__Ϯ]t :UUUEmm-]vko~ \ +-d4)//R.ހb= Bh7i4k5yo=+לQ467b]ɑSr9+[s џj~DCM9}>E3sT >J>99{FCrR9*~96{$ֳbx>lF\v+Z1xC/y}J*el~]Yjz }LUE)nˍJDPTZZ m?ewmOZwpúCk(=8y& ~;f>g̹>Tz*z=SAh_I}>n7X:l6N9z0ʪj[ύ7N'5- NA YVZ͎v޶[Jr۴~̻y 3;Ɠ%;AR3 j;AЪ۷{Vu\!Lgǎ/`lܸ_5`Ce+?=$IQ+V, O Xw,[,\QQ߸X]v;,SVV?ǓO>Yhя%.tw߹(uc@^Iشi{E8s|XTUUbŊVöm{Oׯ'`x^dYO3PTTOeQz .r7_w,u8qu+++yxسgOx饗?AQQ"j՗:uyYhEFqq1~?ݻyG.,$I8V+c2tTWWcX\Pyg ٺe nǍ͛70F^|r^~;K҇%suhTyOpѰs,CA)~*I5=rÇR^^L~>|酧=f~j?Gzk?$bKWWW!*T@T2o]r@e!+>U2FvJDя( =ۍ/aÎ#̙48@y@ZN:CJj*Vlrrs4`i0h5TZBO7r04{7x#3g/Iع;^}U:t kfH:ʪJN@))xH4%{)rPs:ʄ2 >u:£n}%AӢYQAҠA&%Q"I(jkޝF$48޺cǴ 6}QyV߼H0'fW$ק7J2hŕIMtIh2iK8%5- ęF ƠAzw@ɠ$Tfq!r88qYIIIAE6lD^$իW3fhڷcZqfZPm)|[F4S__^'33Qt:HJJ&!!Kqq  z222[WVVbգR x<JKKq\$%%,R[[J"55 ֬Yѣ!/_|Gs8z(wy'+V,端bܸqlkhEbb"HDYY֢VE0Sy߰a("2555b2IMMCjkkFՒZRjEPb j H}:*[h$99?\WS]]?Ngڴi8Nn7FH'559$IԩS*]k׎s*F eǎݻȲLUUVKVV "؏Jp8$&&!J pc۩OP IDATDVKe\.x< w!55QF+!//5k0fX,ώ45v#EYYnxL&~ݎJ"++ƈ<ڵk2d:vJ m*96-zCXp%99JT vbbbի7,SPPJb ==JW\Ayy9Km.]FbP]]^oAYYY. QIOOCm=6n7@@3;:y$* Crr2ZUv\.W ;bccH륶^:CAIؼe K,{e3rQ> yЌiϷaҹujwԪ[^ ];FY~?l%%ȶde\\tɿK>@1\qtm{Ȳ̠l11؇Pd V%j eeހ˨B#!# MBt~z XHMMaҤ9y$k֬v`ʕ̝;#Gѷo_l.ӧ`޽dff!?ӦMvs np[crrrtO3`@;=zছnFEmc=ٳgYd1 jcѢEȲq0a_IHH@RSYYh4q 7 I<Æ #77A.g…h4j̜9/p1K.̜9SNn:_~̚5 GP`0bԨL:,[ fΜ[#v;UUtЁ뮻1cPQQ`$662*&MĊ˩ϭJ\\gb8dYf{䓏ihca޼ۨd…ai&6o©SXhj[nøq$}Xw^ĉ;vl۲e I>}c2f̘J///g<쳬Z͛7ѣG39\[xL<sk6'пn7[lr$ ͸q"hCtбcGfΜ`eǴiӸcy$%%I?ǣ>ơCZP( t9K,AlzOٷoキHNN9sngy'x?0(L1cz.޽{Q(D̙XZz1ͻ˩SǤ}6uY=Q(q*{n]wE^^VmvZn݊h@1uT>fC!++>ZIII)|^ne7o+Wrxw8 ʔ)S6|D^^5xxٳgcXxQR$LH7y6ݴi/Ol6[͢"͛ȑ#ill}lfϾ%,6njә8q"eee1cM+-j5. UIp\Q[8bȑ#ٷw/o6(ra>Ĝ[oEք(oo< ɰhF;RM7?ƒŋ|GXV6$CyrqY~/~D| ;gϜA^/陙 XV.>٫wiF3(J~?YY٘ʥ|Ys%kO5S~ nr;,cwIѠ@Fhś|dη!pQ0x$F$q=448SRYGǥ}eYfkJzjv IFu6'qvj4=̛~USY͊Vl6{ۍ|>&_=sA^eJn] uV'~I'k xqʋ%o 6dJMvd–R,XP;]XYW!e䨀jld6|ıEdeu5IfړҮ(z1vh+( (4;ȭ.tjtH@' &z9[a\.K,H>+mlϞ=t:?>>_>sѱcmW\ɟ wԙ]@3L<72bRSS)(( ..DOjj*: FUW#??˗seb fΜEϞ=S >^Ç0`@3Lt=]tȑ#ی7?{w<.]ʎ=z gӾ}n+իWoAVѩS'FO? 355kPVi2|AJ%t+/~zuF^^G>JBѠVqe/d>|8| 11&zYYs=HĿ/jjj"G;11s瑐oɑ#G1b9p9#F0h ^ye WO4f̜9={Q]] ػwo緼+$$$pM7?RKrr;LBzz:9sn%--뿢#eII~TVVsK޽a~_}Ǝ>G`q~J%?<$ҥ3舘gn1wߍB7eeetܹUDnvV+!F!&&:bbb"VF 1A1xw4׺/#>֮]f[2e "9`n8U=PVNxYd1}>x0_ۮve7=zbQ(Dr;trQp8TUUGJj 6E ݞ* bcRQY|_-?%=&&CR*;(gPD;=GI°Na=acg9RPJl|qf0dtZ)& Q(5-6ܥ v 2 *J*j&Jk%aw8mńї-I`ybO>>jO^즅ϨBT`jFNU֢M鼃 RM8\rؘbhZ4J ̥U)QAe7m[I5~Q_SLbr2:Pb9Xu]3݁cn]Hi| woh7N/k70fpz=lӈ<-hMfj JA@FG Y9T@ً٨WnAב Wd2q]aM>|k׮a#Mee%^{jݻS^^@YYݺuCV?ngφ]Ht BAzz:Ng ^:Vɡ8.][om1cF֭[ԩ# ,[L0Ɉf#11Պ??<Ӈ~o3fϋZY4Μ9lh4"GѣG`ԒZ&77b#B1g$OϞE*F k]]999j:tȣ1KT*222QFFF 6bĈjv233n_BPoMai^/'O ڵ+jr'N駟FR"IW_=!jӦ݈ hZ23wTj5Ng1ڹ{}Y5aFbbbL^/vw}'Nr9v(X,t9BZII1۶me…He<$Ovvv[j:vHMM[[Kt46].yl6٨T*t%>>{m!ws᷿in_ӨT*~?>׋JDE6mȑ;v 8qSH'11YQ*ǛîIIeeZIҥKziCѠA3z Faa!IIILV [~6nD1|;̨r(tx}7STxPk4 x9ǎ YB^ d1<L1Bb$Ѽ_k姦gefy&ucv XN>ǩusuh·z2X[8a!@N)<2{+c߉s"vUPxt% <>? 4 e*AhB֐ hbwJ&ԔB\[GZ)SP*D6G R֑f9Jڷ0q峣xz#ÔY@;u2X~<>/ZcJ;rEjڳ3OzV.`RVCĘ(/c jQ&)9;^=.3Z۶|! 2VNS ^ hƀN Mf CTE0V(p8_OEEzBƍߒLqq1ƍfQ\\fZBcӦMaÆ0ֵkW֯_Ϙ1c8|0O>f֬ۇ5kֲaÆ z4vص{z^Q});v`Ȑ!Ao> ztb0),lw֭ofa 66` 48 ޽{i߾= 2gBd"!!Auu5G}LmƠAع;:w(8bJ%{!//}RZZfCgnEغu;wnݺ֚1Q2"ڥ)''-[˷~8  XZ-}.\ʇ0h@zѣIX,TUU] k֬'$;=/G^|_TUU{fڵB`={bȐL8F;J"--A۰aC$;UwɤISVVh<ٳ.]q& íz<wj` $jjj(** Ph4L1;vq1tP?2vtؑcǎq!Μ)v[65dffrt!]=vJzEW_}رcWO$''p8ص{*++q8a1ctuÇIIInGm_ZE^^|t֍&Ov+.++ ?.{.s'}a߾}aBKׯgl޼cSr Sxz—_G!777B[dff1~y(ʰCYYGiUo[9Vg%:hlldӦML> vW̝;FCll,ƍc֬Ydffw^L&)))l۶1cFs|޽;Ç੧yHII`4i"%%!;vS1 ܳ Bn{Ht(0xs<7LV Mt%TtzTqQZ_|vXS@\[@$ONiBLLLPƿ6!l4 ikz=Y5,7b0`)*vh4TEckK5h*>q!-@ZrS]qWCk u7!Ba؉4~92r!lWѩc@PY|;OFF RH2DŽkjzdA@P0ɸ\n77޽zaoPow5g0"<0 I@}ujWhq(P|QF/8:Rԩ@HTZBG^O(X LŶT͔Ѹt P[Q9_!Z[t.zR" )^EWIC# K( ) ZadHiB6h5tA</g*jF'%6@-('0"uwi~? IDATb kqq7aÆ /0tPnzɜ9݀~?SHĬYh4>|XNzx3'O --== ?e @c=ܹxXr%ǏcL6ŋ!2ݻw3\bc\C9[noΛoСC1b,w^.|D̹AeY,XgΜARѫW/J.]œ9`05k6]w- ,஻"77G} 9s/bر~Jɓ'[Kq8\wD:vȗ_~CE=zpz!ڵKF}J7D|̙ëʢE =z :Ç裏`FVѨI;ng 2dff"&L`%}]tҕΝ;3|Ξ=m#>> Wb} ν￟xs0qD^{5nmGh$ԩHLLo~ڵ;3N:ϖVP:7x? ))Yf8t%[p?0a3*222 @ׅ-Zᅬp#Frt!6bb,^^7t:޽'p;wmS@efΜZnGP`2̙3Yh^XZ--X!y8qF֭ӟ@~-ZJdĈtNǠAy鿡h=z=W^y%UUU~pM7 $I4e a햐$+VP^^ΕW^CMElPa4&MIJe2ݨjMG/>O^$$$`28}4?)))̟**u1uT>+޽:so`[o5\K׮5j4?|\q?~}Z2k֬_caaTk߿? /#<@\s=baajҥKػw?^{-=JţVIJJb̙k <tK9r=:jZmS,NaRGlݺ&//DQQJ`OR?0_{P|cccoYKoxF+B]]}gϞ 4ϟOVVg̘o߆pe>u55KYf)ɲīJ}}=&M [["|@BPr0 'F$=H00t:]ĵV<_C'9;Sh1\=8J!9WCf~ɈRFRZZ ;V^s(?RAFf Ю] g 9/jܠR%+='$l!ܮYY$GR3jsaB@))-.8$8u$뾒̈́k,! [W$!R>!*xVp_l&ӵ`6?995,+l5lXKuѪՌ{Je(Whv߹5:? 2kl֢L1i5tjDO+!ȠqkHRH*4v$$Ҫ[Md`Z9r!FFjATM&| Q@^ @>f+p픖wb:M_ uw mu)N'..Y6P\ATA;l\֚5k(++e޼ۢ=z"Fþ}{q :GKbдx"Nf&_7/" 9HHHwԩ2229t˖˿߻`fUVQYYܹs*(O<s,o,\ 7L?h./9s3ɓ'Yd13"0xV+,zIHH@V@YYYP(#%**+޽;ktk[O1[>>MdT*5j]8e1p'Xz{{աIh ݲ-TF7g ٫(.\߇\Z ]vȻhw_¹rX+CRReeeaS yظm'F Ah|ؔCv*w|%lϖqDDÅ6c49TpYitC./ݼH|.cՅZ f(ɋ hm~4e:I0 *1.n={Q} DEA@z3x]Hs(D쾿Q> Z% Jz TTp=e$)Ne^7 ""m&bh$R* . ]v 0'k.Mv{Yf56[cƌaJNNfԩ?ܟn1c/sc}K8q?[n?jVN:?qk_;x'"Vlz0}1'ӰqF/^zG]׆n (T4,Z(bO޽{q]w_GAA ,M`t8v= DhZfn7,GXZZqܼN\+f ˞=.[Րz4W0$J;oh"\W nK w?V1qw3]2fXddF^}iu]Qbݛ ~a)`! }#I2$vki ~*b#Lp8S OI(0MMwCnN6Zaws0!Z[6S@:;,T*Y{_Aa:ݍ-EՄℚ ]=p.}UE[=mdYjظzzTaK$!%AW^R v[AW^yZ9!j@| CrrCb#F27״nfSMRR2(P(P(a젲$un/I5JU\¦stH%$k@F#*' 2/>F-n#*h9rP(L$;T*UgcccD< ŃBn\!W $$$s?_^7b_B5@q:\#WxBN`Z[R״Tha# E%…y[RgjF펈?؃BZTWW梨]:r"-;4 k5h!"h$T*Q)8N [J z8j!A`d% g }>hŇ*++q:?l ("вظx[ Q9iy GR"ː.B!ZȲ1rjmv[T$YdIOF$tuAoɶ5DܤjىCB!@8%f7~, z TVط.RXnN.Anѣg6ot"J2.B6#y~把2t=%\%\%\%߁㡮΂7);ȒLyy9:Դ~.GqG!11-y2OfJk~.Y,kcK YiltݰC $;Nyv/'K$cA #deKHB²_K- +'K"  DNppwv6==33;wyv{>O=骮 P=W& Yn5jOgKUVՠeofog>xu"'aJK h$}R/I ~`rݲ)´T/cOZQlƺoY$pZZ|سM.xN[+Z1r9Dy[it.M6_ *ek1|={(PҎϬ9! !P~_Iʇ|:'[̻}C5dO0|~X|_f[cDZ^P8*vؽur VެP䁷?H5Y85mO 1/Uکn"IIۙ_/#=̼e?'`~R*hWi;FYj{D,b>c-FP P=ҙ=)om2Ag.=^`RY.>Χ 5T'y}UX Qgi\-q'[|?#FJO='% @ p/N$K0[#)3ndt#E(,%Pq#pw  @pB`*E$}.4=XeZt=<8;_ @83,>QBau=3LiJ˚5ϜC؁Loe,$*^KcCK_T% @H6㨃(#<Ʒ⪫\iN([KgKI8T"g2VBY8#Cۿf^OO;Q:._}z\'9Zq/@XKgɜ㤳y`dR//FWWT}sD":A>\:T #[]3=B"c2pUNbQJ} 泵~-TJnIO6_oޏ*%#wrJJiڠ>/ٟgE'(`Pc=ƒ%KkٹcO=$T)3Q0)wN'MY>EUtNq:I?#~FM]Kфc"wWQn_kTS¤6YR ԓO2mZ;W_s *aZ g-*x_M"_8Mm$FD"CH P*H xwջH44-AWRI4M45ʐtDQr賈*i{9˨ۍovڦN-WT2sS܄rR:+zM댴'/.n}%QF>}y'7uX|Uߏv>UA*$^b*֚Kև)t P&4:rk"1 d BilNGEU'laJ(]e `3e4`$,-Z$IRT^H$B$ $  guܠrB8jDѱ. oX{U eɸ:y2ʥ_y:KvYd+Q@(K֏#85|:7.VBԪ=zn&ҞrRiZNT*}{)imkcԩkB` L}d2E,+?Lhl'M;Hj: =޼*Rr*my*rw^El1G`}zڕU֗T&q0F 388H*6X,J]]o8#@fĨ*`VAs,Un|ҨlFa떍ӟkn[0S&!zzfƴ6O򚦱e^BDrn7\ q\;տ~Jɫu,-~_ |ul ^JK0%FI֏^={c022֭8x e蜊c,Yf!0 nߺPUU;ٲ02:&X‘J8 @cSPEB(ȑ#!/dmJEE<(R-Xg+5Yn~MpxȲUeXzKg1*!o綖|j)^[&K qm+*NVhRZ D]W^s&]C!kjXt)3g)8et3} '2Gtkx srhy@: s֊ x}t:yU|}%rlsz>ŏeWϮi Bk?k@&I"~:·Em$5TːѪ*1B̙˧?9Rzm?kee&N. ^|k̳晧޻?ooy橧80klC|5Ws\re465eԓOP[WOKpﺓ}{A?=8׹+yطo/}#̙;Ϭo|_ohM7r\H a,>ͰC:%84t׿v' QSS)cs7jF %_xG[ꪫ|Wov7BMEA:wmȟ`>R/rw^Em;ܶWQn[Ϋ(-9l7x}C@!r#}E$ JctA~$O؏X,f>G:/&Hf*̙K4{ヒ5V1{838̳ܴ{ヒo~xcZ~?% q}DoඟSyd-|+PQQaZ1<<=/!O<8'mNZZ[N)RaN^].~I4M?1<Ge=ttt˓g4cRJF+>m% L+"BdnmMMMH$ػw/O?4[nA}}?pakj:u*x>ƉDfz{{OunʦN̟O?{atth$B[[ g3`j>ޮ/[^2x7>|W&$px{L'~q_7 wڠw|oN`VwiW|]˸DھUz>>'YmOΟl#ԘTޱZo[;y2 IDAT~f_1׫: I(Q'ҾPPPQYŔ$&Ţ: 2H222Lmm-/͵opBxr{M.**+]G8!ԗ/|s?ԗL6% @5L◿q͕am8,pWɏ~>4+C>_{u W_C$Nsajkkҗd^*;^Cg_c'v]G;?_|U}=9ZWzw|X$ĮÝ<Bt4,d^IfLoSǾ(o9̶zy}#K1uѹ9SX2aGl=9}q;M5[ Bf2kJ/Cw;Y6 /vc^F}e3zٰH8ۖ䑗;ibł~7{h%Ir^hf3?z`5v%9{YӏC(B (\LN% ,zmUu53g MՒ2o41yMOU,`_??k?AsɧJ<gT GGGM5)bsyb1"(dT*Ewwsͧ9ssĨY/)%WqAꕫXt==̝7Οgz&EOUehO-M_L}I_s@2|rCP(DMu-F4>TKh@yal6n$ w~S.\ȧ?o~Kp:X,ƗE6o̧>i.R9nv>O1}:կr=pE1{ԫ \$}7Nv:%Kj|˽/y2zW`B7Hr|4V5Z7HHwGR;3w j>y}~x*w4;`۾^^esۈG#  5T#!h_kY6</>O`&S4 5QqQ]>y1<^Dž+f34h_ĕ.6eaj+ T]7q܁!2N Zia'0w/FmEQBI'|&.N8$***ͺ>rU+WD}>̯U+_.ēN/ϴ}P8(XmRFFaDn3 oBwCoo/ÙuCT6~޽P(DUUv7x#oI$|窫bӦMg?{F_Xx1g~:^=w/︃C/B7Noy~N~uU*my =o $F]FAUDQY'ύ^'Q~}.ϬgǁGuuZ_e=N\||$08!!X+Rޱ^d}f@ zRR?/}/XlIi>c|价~YBʌyHWRדٿ32Yy{ 4/N71a煓t`ԟǞBKTNȬU UUUg6aͪ Oִ)FEE%}Ŭ8TRJ(MC&%}/Դ;~IMmRJN; n륺ֶ6B);{l~1u43K/Nc_ 2::BEe%=^ɹ矇o]} RJ>ś^N)SsF ?>Y{Bb ]Vfjp Fܖ?X|9gwNʖ-[ؽk:tP(D[[ERިo/^7߹rޜ :e˽d_>Ui}Uㆳ{Yg Uxuڪ8v_x wL:wf~Fiq܌&*bj+ȩqSuN˯=+;?q 8y6睠Rr¼6>eCmS0eTWxe̷WTy;,轙MMH$m!Lmfc̙s5WTTpqYv+49&S) @f 7++WÇ6e W^y&h੧bƌ9>鳧1NtZ8@<ޮ|L|+|+W_T}?tOgNbG؅Yy6X>~əT ygfl N'xםH:mBp3"9Yr|l&#{,9>Mj9BA&)3Qꖿ~%ن@FGaR`_:NtX̗nNJN"IooFHɚ77pC+"MkʚVKp{QMS:l|",||5ӻ>aϥCJ*+c16gnVy QUYIm]FGG LŨ!"J@<!H$JADTVVC4Ç0-ʲ-n>N;|j<ҁށcc2*%>ۊXH$a>v-ehM7~͛7F Yd2I"aƦQ<$ ųT"&gݯ'krӥorB!ǃ @zjmC-[ )fg^s2R9sjr&?eKyKP.uq ΂u_/P *W從k׫;K7N';<0u3!8߲!ԕHШmec˜虾'a@.O|p٣ݯZom̿SڸKl/zwp y;|'*P'O}c-WYR/C z}`}~";x4#@;%YP`tVH,3cg Fd$6=l70*9bkE^Wdwie3m"S-ڙJH%S$I/$1{¬sNo^n}9*%om-7ܶWQn[Ϋ(-~UCt%/3$@Q7:@]ط+LUa]֜yFeF|DZ812v:fyҖvw۪UDkג,+PF('O[BZA'b7*D +v[^u7ĉsZTBx)eRjmW_۾L k}?z2,%Go2=vjbFHgߕf |͘ƵX]ŠX_ǧ N-xs Ƒw Iky_yX!hW˱5q_-1eWWGJ)Ǵughy2|n>P(Dn9g~!k;:`k[iҊ;bk6.S@t]]]7~ nξ) O n@7VԺg_WQn[Ϋ(-~U*my%{Z^7|C¾!L9)%H&Ko1dƝ@e#Gzظn#-DcQ:$Ҙ2H4B8fppʪJd>z280DK[ Ab48xi  "B!*++B0::xxEMT"I217?!4-EuM5CClߺ+cK؊dm Q# QUY9 @!*;z dC9i|CII*DB+)j**gTUWQPuYba_oSMa  SYUɶ-8} Cl۲PH0|qmXYsfJزi+'r]twfμټKt젯$I饵tu~9]E~ !cwnA(Ǜ|C'j? a.M.J~?l;'[ۓO֏1"+;eB}w+@>:M*~[ٽ}7pjل}}̘=Qv1}tv|rFGGطgΦ2N,M,9a1;HjkdygN8y9uJAaճZBhE'j?c6~O-~dy?wOCm)Ҡ!D7wA%X{BfR勏O{ ̙94ґ}m]-;DJ7ӹChln4OaˑG4MF2*FGGTO !pp$Յe0ƆKS9QP؄C ? @ptMF>qGD{$d2e$!;c:1NZw^peGۃDǩ6gKYQQ@Xؿ3MrE̜=6 X#>|sgQQYI_OTVo #ã(#:pp$e M YzAa)r? @ לL|eE9ʗ"]J]~OرTJcpp۶q0Ie8n]miii!`>ԆFgT=]WRb)r |z](LSKU*bDcQ@JF^?)R{iƢ0<<Ԏ} oƬl߶{ !UhFsk3DQ+RHDQ$a3v_@ER$oJc q򝓥L{>bD~U)qׯ|#wW2\ϵn˼l~YipK/fqX`UlX7x3SN).z{zdww߁)ko_{3wp2=mֺ1fϝțciJ)i)RTz@d"Ib4IuU5'8yBmyG  @Nw8cvnk}BZ*E*$H $I@f;ijjJO*Kq$1JwOO*s P[]C"_%7Y[ICc=M›i_麺#+x@cB!{G`-H*wpV*^#r)5"dRVpP(,z}O*JD!29=S>.O.7c<a>"WV0eڔr’.?fw[+=Yyޏ}@ D߽#wW'_C<& NnU@"$$RDHdga֜F/!*R.&Xd=A?߾?KP 6Of:P}"y_4Jax/7Qǻq-Mw~kLӮ f3$ԑWY7, l[7Z~vMUu l\e+ AZ8d"I$FKbN4M22<sihj`ۖ 12k: uuDHDhikeƬev$j#i8]Ɗ :=!ߦCɃ"',/7 y/`sx׺^Yy%-Lo=m< (mb̫nBdpz!R:Li fCmN|>x)39N͍t&JQ[[Co?ͭ޾p8LUM5lB]<ij!oH$yf PU]0/=[H$I%S>ғs}{QP03fϠao:cECYWe(޷w?k׼;4frgPS[Dk׼IN.RFGGy% 8~"Vg}o6 (wL>޼/+(3ʩ@/z2|p!\~gLɻF)aK/ ?Slz ^! E΅$2mͥolݜ>E&ߞs$ijiUĈƢ@z(n@DAcnʪJo"WĩoW^#Q[WC8/"pB꿬&Lqܒ; h9akzf- _bKYdi֛fwJIEXb))TWW֛/bU/={w#ٹk7FGlۼ|~r6?=$_~FFFY>t7؃O̟':E%'ݿ^[1{M["H).uCP4-m?Pd`^^o>岥X_N,o e孾o[ۗ>ٲJ-mδd}!U( 5x#=cyǖɋZ`BOK/Wx25{LS8C2h$B_#/p+Ҋ?**YbE{2"^cF;=445 hNv܅spnjfԯ!X]Cq~I]] bƜ,<~>O~c gx˯5N*₋3WnG]}Hwz<9uaVO?˯W^\I^}uXEvs~C8|L׋;:yt-zz<7yd}`‰u8*5xH!s8z{moJJZd<qn'>W 5$ 465 oJi445䵼H%y_I7@8TWqGb]lZɆ77q}'k^~+?x9Sۧ}x·R[m9xS\AEe O*B4:o;jN=d탺!֛9r9+?bruT@~Oǘ:oe8:yl`~jg~|Ў&*O3uR,-ƛ/s;|б;9tKo+[3j !iB)!PzJz)- Yu H2߳ITsͅGX'T/#T_:cA1>Пy}Zp|(GJ̗GS;Up$̛׿5ڬ4ԜJPǽ[^9YU]ECc=^Ki{m=D¬ ODVywU1N;Wޒ.?_u&/ai_p=o>^Yy%-Lo=m<:g^͇t>Ae!Xp2A(zb1C8V9V~OskP#Gp2~wo `} m^['>͊OlV,ӏ=utftVutb#a:9^g7"y l,<uO*_9C!4do#no.I:X4!ԙ41Xp3Hww5@a"08ūxٿc.1³inm췟ECc Mx^I.TTƹ쪋ټq+B@sk/`J{ 7ͩ`ETTS]S>[سk/'~"HPHᏽ]|IηKs~e.Wb>2r]t|g{&4y|EF |5Wvs>XЉ/АB` GvuB (xL='<9^6z&w0CdBp)~"]* oȔgcS+_/sEڒw<#l߳vĊ9s~'/eYn\޿w+S!ɂ}"29~mV & GԎ#Om*{;J82쎹㬋Zi>xRmIR0[neK.exhd2JJ%-[sCPlC(J 8WgJ At)'>[yWzO%YYUE8o[c=6܀znD:i1Ұv3,H$yu兕TVV048Lk[ٻ)/l !zM&>X;C!<׫`R EI#-̤22C)#i-L& $0<Kp^935gg64ʏ1]Bߖ˒pu,v"xdMUf:5YxPg?B'Nʴ1L: DS0zeTUTs+dkU./꒯ cu/3o!+eM|V[(7_vfoW~w<s:ڑ߮W\gr3CK~:Ё 2z"FN!=Y usLVfEc%mƗ 9RJv_^z^,vk׾eW^ wv.J9<ҋt9i_ \77~3uor/;ijj+6oIn 9Ȥ%{]cDKiص??A.Rn'/9 @+ i~>kK|)uM@b˧igM\<o.xJi/)2$GC(Di턄`_|p8M_d2{}1W%Kݗя6_&555,[իV~g~0vbddO~泜z{M:%KinnaY~#hZxpـ.xلc1xC]c,M$G_G{JW;_yG}vӲP9;u4bVGT|)uMޔK9i=JAPjM%LcX_0 Nbܱ۷SW_]jjj曉bH)qSϬ9sp8L(JD#TUW3w\*BPWWg-:~11IHC\h~p hj睛xm:1„BECX`ous%[h±Tb;_yG+(TPwޕ) ?*7 B`HL$U )֬o'䧨G`]O)6uF=[]+#-bV9a/]!7ɄW[觑-s ,/I 0{=KBBC)XgpYg;9͇}?gWAE%,]D"qQЀ[!㝬_&;o_=p:yE74P[[qDH:Oghh#ضD2I7gRiDT@eO6]QFGG(֡yeXA#w?ʾvdu;n ).*BM !̊ؔ]HHnz)SL!x,Fkc޽xJA+TW=97Ȩ&incvW~+8Q)F^nɮF2_~lo#G4=Z7EW^f֭]^z9}}RIR) -Dl۾~;@Uã AuUpQxÏm  @|8uf;1uGCp7_K"!$F17W×Rd 0c`h2*$B1d,U{ o.z{g+zb"K2JL& Gj $IR; #8}'g_G{JWqJ}'ly"7>=rJ"D6R8˖oZu5)_J]7pQFsQr$K/=[ٴ3>[-5}i-1u̞}A0Y8Z,_y1+w(yd'w(#+$v#_Le{hcTUb^Njd&1Pڨ!̫&'y g$r'jg IDAT)xdoz_{Wț{ޡlRJH%d2I2$wqrTmfz'6$$!% "\*xA?^_sDU _DD("Lyezzfz%=ԩSuBxs.qe|YG3tTa皷3瓷 + ^[֋ڊ9[C8Bka,(ڙdK&2L2#)v)ɘMs iZҷVLYe´ɘ7 Km<~2*}c//qܕU2( 2;?e%;yRHӾ.61 ;?vEHWʹAPPVmh132mA2`h*$LaӼD~PT1D&?.i}h #QdCTL'oo(l$K$K$ˬjTzi=y $EHK´q:sq}ϔfZ ՛zcUFs@)zNk kL-xCy g8K( IZ7 cs5.ͷL\+>du>du>d^o4|yNր2H6im~.ohKӽSԎݼ$ͺ k ¼uJJKXqSSz}7ӶG%|R226Ng{5i=І t:;jӅ,A|m!֑W.ʔGy1mBܣ*~bJ?2ADqRrRD,y !DGNpZ"0B8̜$KNuw<(ja5=c()ɄC9m*,Z/fcK; ;<"ڥ'2pfg3kX[;0C(BcFF V UT3| zifoMM&*T9d?atOM16:FBo!ccRVQ@zFFD"LORP餢嫖!ɉI*}n' 140HG{'>:;WSP(@ uLNL07@8P! RFM] N\oI4+8]>G1:'%O?7~W~ӟqF6n܈x|K_p qgD"z뭼}o駟ctt438p@u=|68Ʊ~}DqWV3(Z=4//>{.l6~w̞'qG!"RqϭTvyU{F jjz(jU-P xѼPS VbyQԸ27TbjJ5"Aef^YW v%R_Eg׎ 'C( ⳙBcѝ9Xaa| a䜎b tJJKe:>sI'k|3l޼Q|AnVmۆgƍ}vm|k_cϞ=?K/6>OoPPPW_͍7HWWM>1~=#<8AY"fJiL‡% k:@SuBaHP ,ud"]Չ0:5dU)Mֻ-^Bs`*ނղ2*=Ch.EKOg5([H$tRRVJyeEEEtuvG$-FšP^UAVz{hB8FAEe9Cv)(*e( c8\Njj()-arbRjH$MƼaOXm31_G|'ɳ>˦Mxl)**b֭|W^OQQg}6=n~[gy:nV=?Coo/*=_yLy=l۶o뮻3^QJu/穧b͚5'/2ַxioo^ѻ￟/| ?_y9sx'l_H8J]YP.z߻뎯p_s-~~9?y96TrʒAװ)zGsz߾S&! NS GWsX@J\jPokg?ӊK:K:K̙(~AS\{w; 2x\ nBhi'a+>"]R|~_P{FU[%#0QX챠d*,fX|]~;cEJvBfg39f4D1;AtL'<²Kq\q jj_Xq(BYE,_^_Ţ%Mx҉,[ ǣh"NXee!7SXTHqI1'،ֲxbN'.KPXTH}c=BVYIuov KX"8K/={kq饗??}{Bxx<Ë466ȽK__{/q8?׿h"=(p$lAii)N^xW^ys:9r.~鸸җO?=.xAY__OWW*O>$%%%&cLa}K:KNT.9?s8T$cΫ)XTHީ!y"uѠ:(USCDªQXPJXH[uHܚ9"ex!;r%]as%x ~.o>Y< INDzag=02ʗizp8477yf0֭7է%\Bss3H6۴5z*K.a͔r׳uV}Q7vVsxW]u6m"i\p?0wy'^}qb .Bn&S|3-[p ';vPPP~}֭[9 M=yGpIijTkԕ@HSL$P{NY kH4:Ig6-՘C>,# 1A%^%.$l[0qJnxC83>i-  CbxhPӅ3Lzxї"O0ꨲ X\4 .@&ۧiR]ٖLJ]D"Pp(L(R@o'ofK|*?JKKR222mUW]RlXVı~}DRVZ܊ûA!+vi*<<Ǝ1\X]FB6Fp:s6]HKB2R*U]B ԅ]<{& E$mN߁1b֚)P q`G]eU^Y\VG'E$,+V]`tײ3*RH$ag׆.G4#v`$́n.[N>H^S1a|6U>⟢ݻ#ÄBa"0pŁ~7PHD|㫮IaЍk}LC)Ԕrg^6~C2C}v92~?7oԕT \PEu8u`uTPє.@[R0iq(ՕxIAmӊ^k"IPV2Q̌@Vy<W\qW\q|Gy1#ԕٸH8(t BSaUmሺ%[VW֣X1|uhgHMF:ӨSPpaa{5``Srj64 &/dUJ.dW{iF: -!]S;t0ʚ#jqd4PzMbΎLt5u--kI 7H;b[1->#C>dق0m2 {%n!:2}d'{Yap 15=}*}weB9j(wS_mG$e`x;ne >\sQ'rA\sI\sIYXeEpD2=` 7L2ԟF@}"x8yL(l8~`2D4_G2Nw3峙V.qFS-@CoY&V,҉3tag $ $d';!.f_G)\+[|.ɒ|.ɒ|.2|{4Ș?b20ҁ @(f{ 8*MxI1^ȧ&:DUXt!"}3OVge^QȼʨJSݝZR &.hX;K7-!%-Z9v pJ_%'،[:Gv ?=gLS]M'kotˬIeEy\Ε2GydhN3|ƄIMH$ ".3WxDH=H 0R8BE 3R T?N uXi 5Vq+PTɐN_=V8b6,BY~gg3n!8t#k iZ܄oy]3)?Z!ph:q\EiY)@&'(,*CԈP}=}QSWCUuct p8-]gђ&JJK^ثOOCM] evBGWA]C}=}~&'UPbM!Orք\4rQ<#ià?L$8H0_( M IJ-j6A5<5BWQ{\Fi 'jځj 8"KQ(B5uUn<KqeVE8NM]ζag!ʹo{c =RgT6#o H/=KD|0rN'$ OJ+V`+;hkiD8rkVv IoOU Q-~n7n=ª5+fltjZRVV&'&eOɉIf:;ꥦ@ HAaAko?UN^ ,[ΣH)k  Q髢? IDATKow 5Cɗ~ӗ/0{8֯/ȗU87(Z`0 _lWbTu@T%^),)@H@}E=xa]&41OqZ*X]ʬ|ƕI'B/Q𡐚E۱6}14 ̈́>cyjt >i-;Zxpjo!FbZʹ C;*2O,Iiq 7NNqI fɐP}91Aj^W^++V/-:ajkQ p$}  SZV_jSSB!**x<\aQUJ&Vgyj|S-bltɉIU`||ߏؾĢu 09^ȳ|kXH%O:S*}eex(@QQ!#z)%S!!"L"P TOEK9Q{jf`iʒփ(Ɩ5Ψ_鋻HC:’!=.1rJiZQ&!"/~`5!s?RCh s34^ֺ*)LF\<.|2JueD}F@Dx{+GM=6ѥg58biPcojJJE 7}BsEnK75YNEvqYc,ԗNtKmZXy?qe|YGBbl焺^vX (@aNPUxP3;8ћJxUMoW#>.UM*&['J真T9˃fĊ^-~@Ly?bzÃCLNLv5'F `0DM] Jblt'r݋( иQOhTTztZW@[uT$dhpH)0;:zP\Rb.ydxǍ6Z^5>vN^oEݬgcgL}9Nq 1=}*}we@90CI8sT *# urh?ån:O}!1 D#iP&cvyY#Drҏ5Sqy;޵ pQ1K-PKvH6*ŐP[045N O )l B"E?U^󁲊2>X*,[¡C(²ːXJ6|5ո=n db P pTT+j|x((, MbIJJ (++? z]VP{7{ihpPYUA(z=9z(`e+8|_7eB֋"pd!Yr%Yr%Y"RDG5r8 p9$dDŷ)1NbhX^K j JS(@2eLY~cۙL+yh.֊n%#kTNęNư ? U*|U\Aak֮;w‰a嚕0N߯V?..)DWQY_v)'Jx- MTVU紞M¢8Y#TVt:ͼb⼼)re,,,[5+*Nhjs'8&8*,Kœ!/mGO_k1"^v{6Tu<);s~.o>0:0!Wt{,`Usbcf^Fcrq8W.ʔGyuB5PTLy%S_˒C8s>i-$>m?Q(bݑxhVZS4[ҙm_2GS i-ތBc9%Ps&v? E(p HU+A|kj2ATiJ5i4cWvϮ]#iLgN(98?wGy0CCCtww3< <O<RJFFFappH$B(bppnӟ4R^DՅ\qWVƯ&EK=p8\.\.CrvpG@Q 0zb\^Ҳ z}3x)dʄ"2\q-= *kⳙBuX! ?N@LC Z9#sDo\rg^6˶l3/C*SSSڵ{w]v!.'tx;(--Üy8p!wu?8۶mtdկRWW\`!?O({z!:;;yWimmg۶muYzꩼs]wvַ83,Y¾}' qWC<}ۿVv"p\U"#g])T/"af)T35r!"/~Wac >>ǧ}IqJKdЫg%@&F\<.|jS=mi+gi19adcs\.8p.^Lee%/===\|tMݻ͛7۷wѣG୷ޚK b+`ӦMtttop6ng-W_ ezRUUŋ/ ƒ>޽{[߿6nȚ5k+G<fkVNZgW{iSF:Ή/f8 gg3k[<ߐ%a0i"l[Y?$mli;[R$܊i2KC6=a͐M829NVXA{{;~k_cʕae_]]My&<\~zoƍLnYBtRwK]]+Vk'(,,ԏfdd={[X|XQ[[UW]ϧ<99RN'\ve;g}gz^(X%JCL|o0|uA?W+Z2y^:o7eg3ʝKv~">>i-ފXS-8S"7.)D\'̓ Bb{6ͯTy "HbzMsi E!M3sD ҝ&`Ŋ^)((@YC3 kx嗹дȔ s mγ\ Z]vl2-ZFDG,^ŋs=PVV;BzJ.F'ZZZ/Iyy9MMMZKO~.˙$K$ˬIB(~x)k(G?`qC [#:{3}oͯ3SS~uHc#kI8?R5.Q\\~0LlxHmu((6R):5$Sz648IZ S$4OnNw?{7n׿5~|l۶uQVVƶmشim۶q'̾},[ŋOKꇰ[o~PSSe۶m}nnV}]GiF$'sI'cՓk|̈́B!@]eO<tuuQQQ D"u]tvvren:\.=P;e7?|.22%6<2y/ '_dxt5w`׾C(Bp LM?^kß>E t8߳nb&&yetd%>0|:9?y766A,B\ֈYhqӸI2٦ICVS[_(qai;x7_912-.c +WdʕlذA?eS;Pھ<g1&Dap:_hŜzqccc\zꉆM6~vZpu%uֱn:SN;-#chsٴnw[]NDJ~tx=sI]p%n|Sy(_V~yv>Hw $`:?DzΜfZ z%D{ ^By&c=PdU ؒlCZecO7aP(Wh.M(^{),*aQ#!:))-rRSW(GtѴd{6l]ݔWe"o l/%vV,vѲO15RXTH.jjqX:;\C:6E+e#<6f|(@Q!ݛׯpP'< u!"ҦZEAkLV'&oLʐz)S!۰gШYb&|6ZAPq=)f"Ӛ]¦vIxKO4ҳOTig3wXB2&Pa׭m5qP;EEx駦G:pUq*j؋GwgjM}cglxNˢ%M8]NF맺Gq{<:rʪ ێbrbr\.ݽj|  Q[_uJ0{IP۬Y9[Kq/ikXp:=vR gYю\.wzJ’0\usj"IRe_SAJK/S#\,ғ-d+LLN'~yzժ ZX8A I>?}9'}> f^[qcB7/]NHMVwq'}w[Qz9㔵<8㔵ƎVޮc,"px4/*cc=O"U(vo/kk]J&M#.Qt`>u2{^Qb rbZj |,ju &tͱtXǐ!TcrRٴ25f_&hKX.YAY6]biݮ^C.M JSv9F~y˸_>F0djY߼ᓜz oI4րE}-CaV,g߼ᓜ6֎_~XP 5(.U(/-!h|vaEKL'o>9|9[C866`^00:0Po`a0iʖBk-ͬ]jsij&6.T̑*|"&ۅxQ:Kd|lJ=UͫX0pr'TUWӛPz^&'1> !f[YjRS*%J6C?udHm~e^~۱9{l0vs3 +M5=Al{,.GΥz26668C[TDA^۞Ipׁnm+,^JΫl`zXq7d4rt٧򗞷%z(}[T5( o)Ro+Ӣj[m{O5$MsI]_[ܹ]Q$էÓŴr7B2 ?EF#0d4iyU)^J@S4a&see8NnU>b @yENojiZDQqnv)rQ =rC]CzkkRٍf p:TWs7Q\Z㡺M,Y6K/1qESTT2|RE3sh{)L+&{PB-XA0&]ڲgCM:HPD{n"ha,FB CuiQjB4a g on}5M#^]ʴ&^k&V7k =af+kϣڎJ&Hĥi88=ŗ~(rf^e|.2Py6(}wɱg,;?O>s7E"?Rjh,ۙL+y'@CQBF0~)UCvcMUlF:q1aQ1ee1: /@]إc#yFiRZVj(445d^ErҴ).|C|27OXm q-[iX0+ u$HqQj`醠%n Χ״5'UsZ2!]'@gwMQzgq d勄\FU&iv6ad\}ωrL}Ֆ2"g^}_H|.2奖 z FfOUǓ320T8\ֵĽmL-SHs/ c߇2ٺ=n6l;~'N;Ғٕ7 l>df%화U&z{{B,wBw211=B !#- V?g Qel֖ji ytnEXݻv =~4EGgg|>T,5D&RlӍFJ'+\S4嘽f)z\-m>f\h $+^ʡCx{>|55N=kUW+^|!`XĪYھwP[PFo4XX2cP|1o_ |˭=ۻG|?0oΝ;8}.RyAýri?yRqWobW^ ^Ao)_P#)걛owۋ*gTRva|‚9㲬4T9B̔˼B !hp$vuH%@"ҏilQ31iOvS˝*f+gC[i1$&C#CLMwwuuuя} P@ii$H5kٽk|SN=MO2.Yj'F n[jk, !b.?K5IK Ka:HW,_ݻw_a.EE\re_7RR\8INVhbJJKᬳ]\;trlj'mZW`̰C7ԩcCO1'9!fGM'oftd !W$n{R.8o+?vK.P ‹.=gϞ]W˖sK/Ukp9o|Ŋ\Qݎ|)ӻOɗTCq:۾f!$˱Iv&|6eƏC6afk]!WTWT]MͣDy/#D"¡0PP(!"/O<9l4MƢ5C;O\mFGKZ."e(B D;Xt%a?tZڰ=K~:.H}v|ۆ#wW:d;cZG䊢 eehT#atwfok HQ*")Z[[ٳ{O2G(&x 9ѣG9w TjV|`Š؉dvNn| )i9[(( ZV6Ly< E8G?w~mAؗM@oذ 6ZX2UkUE4Q;G@lH0uz0":ͼ-6\41hKd(К90G1Ƶ61 ]km;-$<0M˗GT/鼔fֱϱ,&w  (|rq:߂aQ)T&TC\ms I,!uY؅TxA]n2{Rs5![=6T iFEGcu3"edvZ/xs솪˙ga{af)cSG12*/.7Oh,qcA}F͓7^v->i->5{`LQEa<1.}@5Cys.޻ Q][Mm} ccHah`ʪJk CAxQoC(ꩭEM@ q\.&((P][`dxgђ&\n5 2665+}}DazBD"ς֗5uB膠hLmi=Ƥ-XvM-fR?l$VDi(:a^c!ԋ\qYVA[>0,(Y25Mn2!6dT5f& qZȠW|$)LF\<.|2Juef~4.j$ ;<Ţ$6H )J,r\#[7K^[|8qXN(ʖ$EE5EM$A-cwfgfg~==9{s朗}/W`*"37;(޼\,bMN' rV;9MK~_?\5u56FF8umNUI|_et'VCSKv Ę&ҁgIӘg^`LF\ӁvP02~]^%J#9Y^HȘ1ӽVci#dq !ZKrFaE+9<|nļ5G&PH;w !DmPF+MQ`GxLi&Rnf,u-?59(zû3?$\B %p8 pz|.@jC445{> t9Yټ#oe|lx<9>1?]UIL^q<*~H˚yRGl:Rc2/H'}!ׯ7]efc G}Wf;n$_K 3{(ǩ,z+yߍy R?'_x|o!вnQ3NWWqWusQ/x#!4REz:<Lj0lRCKĘ > x,#&lxdy- 즳 &$.FGhhbPV^F<CUp)(*n Ff*+i saYihj`hpҲR%\5u5jRYUjqesss4L;9?k֯Y>ePEaHeʚ`Tfˌ2՚͠ 7,"o&YS{< ^fן0HptO鰳s:ʊ O5 tv ji٬\`tbWM0(c:o3<{;o_<ƺ ojz5 NPSYj^w7̩ܼk \< 182o`t|+҈j),ȥUUl^n+てN Y?BLef^3! 4MA!ԶF)&H٩"8餫kYVkpSCX5{%ѸԯrQ]vGUf+*ZF]c z7і4e} +-b3ق)QfFÛIf켙dY^y2 xWٴ/}#~uUt1g;{٬+_iPU^8!) WokpYeMS-_wu/? q=_R qn oj|f>8Ϳ|G'p: 1:>Cc;IsS ohYU77i~b/sTO_onȱӝ丝 T_>[ uL"ӚYYJ%dmfP6մElw$V&?Q˰F*JZzU.x) S40f),b^Pugg:zXYWIYq奅| w] {?~ Rj 7+Opu[Э;h)WoTN_?Mi]8 z66L柄O7"{4'~3 8f\cF",҆nv9 `STQsٴvUeELM019Ľ+(/)xC"C& 182TfU-/?  aCsجVڻ|g(*2 %~M16>ŮXQ_IUY} PSW108LCox~WGCg.dZ$#G$p d& < $Mq Hק& ,Tx JEJ8cϬY#^H'%By"'L^q6.72|DVWӕ{77Nϟ jJWgP\UniX(/)d;# C;t'0v;Fv͓/i^Q@Q {5FbnB/>Nruf۴;`˺_ .u|e JPdѐbU 쿬xyb\C^2_y3/slZ% D,!/p!?p)u*u\ҿ \On_ 7_>r5_?޹-VjRҥۯxkC qM#E}_=?]b&~LQIzشn{^;_g*`E}%j|urC7hi~3ȧ0QKr Ʈ!ԿɌPEP2Y?I#Sb$+YbR"^CqU"JWR<,@3`m*,Xkg4TRݜn?nc&s=XVq6k |k JnW0pj;Xkos\] SWUJ>ƽWA{nFEi!`5M5?݉nSmVv:YQW7sVWnd놕ON|wa9<|?gƨrFaEgfjt~:d.&6b&D@  J}> au8qzrIP_CGrZL)p:*׾^&'‘yp5ŀHP` H0$s<9nٺܢ.;.9e&AV#qꑓi,+ǡv% ӌ/]NvbXB\m߸ϣk. }3\gJW eP{f=!Z}>޿J$߯g"i֣4;C[[ǎG?  CN|>v,@*e0=T31vF xi¦@c<Pu (!g +֌Pj][2y3j@Klzq-.o&YΛIf%Sxw;NW-'$IMeg| NP^RU |>fx(,.a,r$%lSΪ>UuFjZ90}*qIWĦ2Uw^~!yxC.j=-Lx5L7,f$bm]{EΝIv)Q u`r Œym5TG*k_ʼ?)|^O(Wb A^^Ȧ2jcP!4 $P(DϹ7\g73J9kx?=tMx:Yc;Q," =BG>w1rɴ.!4e4jH`B!gL3%!L>BFnZ\9.z{M0h]LMS]_Cn_?TVbz|LO0+cxh7ʚ*fg#!M/xa`Q," 5 {Zy*O0Qb] BрRtPVi˓gCIuFj4Rj1L*IZ%H'oFUMU'é5݃夰sH@ }qٙYǛeb<(#gtd)zK4P/iɈ@ ʛ͉"&|ܲRxb0z5Kq*ѰVaBMh@`Zl?064R7 )t!u_sݸ.:ۻA ɉIVJqi 6[ P[_pycZB Bz{Z$üyՙE9ΓG/~IK>|AFFFyg _}\ytwwfn7os<>,v??pRh֭vm|ߤk^_*~2*^|E$I۳gw஻{|0f^V^ /K_lذ|+s=۷f:::ꢷpovnv֭[=Cmm-wq8~8w}/2lMygx;u+Wc6ߧ^~@ @} e𣏳qm 7]-~W}m?7]-MM{%,'#  7ߟPfLdP5MfP!~4^ fP%hƟGg*D *R">Χ !@?Y^ILOMҊ'X׮ftdwZ$ jjJfggBPRZB6 )(,`|l:\.'%E36:F]C-r<9bKJ'w3 fj#22!S~?> ׿uxGimm妛n"//_igsss|>֬Yw/Kzz[/.3Qf켙dI?ug \<|ګy5l+6_FJ{8r=^w>uͫ ߛGn{vaX'!P(^Gk)\yy9###TWWSYY9r,K/\7oz<Ν' 1;;Rv^/~z$Ix<V\n&,bn333_SS. au8dYw\?':Gs=95 E" ֱ<|ɩ)6mX8wY6;O.o 冟A\~FO-EvmVYLBZ.Y?KCXRd&CP]>B3CP-x) S4}lݺ۷s}q>O*ADd͚5\s5G?P-3On:صk477p8ضmUUU|>|>eeeTU%.chH^]vyf.2*JKKٹs'|;)++cر۷Ν;aΝ]PQQ7ob$I(vvyf6l؀+jeҵkײcj4yP]]͎;5 Ydj<>} `<;iDn'ߤ\7}\NN|3Νturr_~ɩ)8u,޾~9>δʶG/ur-~C$-%敪%Iblj*f"ʨjg2-R0}}>֮]T Iج6'ƙP0! xrsCRX؇g* 51|f{JQ_2BJYa/  ?\O[ڌ>#RfU&q/*u\ z ~q-)^FK4.,&C |WAʧCAZ}>no/NC(Կ>i#o}~`P0nصksAMet~ u8zIƖ;/)nhtkd.$Y(`!~sm6\H.&\Je](JlV+lOyժFtR6,&@TT'*k!Rܼ."pkhei,OC4d2"Y.|IqY$a<*]I$$3V&x3_/,fDx Xt~6~/!Y:=L-0$yՙE9ΓG/~IKcn.,L !Nx@H"Kbfr[Eɨ !-Vr~N{繅bY0u2]]Fjc0pYxH~mLJIǫf)cqDЄMo,eA䭋 b`[12VMԺ  \oF u "nݜt6˄`rYPO~~r/QKrS6"߅ 6~_?UQ\RL(9j?w!,4650?@< 0Jl:DmvfC6$1}$z|r+qh1<}YxX^YԝflgtKGD2CM&5xRjdϗdZ a$^ݩ_Zw豉k~i\jc0߭ vRM_'\, 4E%E 26:q"rrrjBN9&/?3g:8ٍb֓g$>&'piom346*33) 03=T8agtxaJJ3118CÔ2<8 333%/02< ]]051@`\*U'i%y"A~t}> <`1Kvz˟3ȇrFB.(%mRQ9y*5/9/uE4}A2IA<+r"%6DQ 9è gݓQ:.Ouk}6J`Ѷo0LLKNWꟍ~%i%J{Bː糁_$sA D :Q$IFMYy]-,5Ɣv>'z.ۍި&_ >i]Nÿ(>*0^x& spJEŅ 22<7߫ϝgr|@ @OwB()+ap`)V67(-+adh V67TOQIY$Ibxh++`tx&'&bV`7[ođA$&'p)-/j2==M~A>+os~y^KfQZ^JyeۍG~Q>e5y]C +(;AR|vd wC3 BeF>iIm&K]ۑ*0 IW>N%~+4`$QOF~`Ii^4:k`,MxySQ:JV2-p:CQTR 5Wj!kr124̩ٸu#յ6F8y[oGǰ;섂N\yjv FҊ .ۺ@ CG`b|ζ.{4 `ѽPveCtwv#TȾ t%At@1 3F'M( =\ge 6I~^BP iZB :3s]NHl|׾ :;y?cw.fqEķkPEE46m]eS+ՓM@[F=C'4 3B6d 8 F[oV'$zsn0'h RW~.8loMV>'HSZO9-Pfͳ~&g%ZjIH7ZX,-vS++?gjV8<1WN21#Ɏ'LIml1eU4<'[iu0&G6i@v " WkT ChXLb,X>adxgdO)FG*ԉyq8}E^A$`tdOP0g"ett awq(-+l3;;HdP`xs7@0ĝfvfOqixrfo>jkLl6fgfJFuИcԧybEMMMz<3;3ͮA}C|-?a]Y},q:;;xM7u|"/˪fuvQSWw6VPK/7UPtT<+oLUJ04"XYiV?c!w8: QQhedRb]c!-Mv,N3Aʭ cdwmwR+8ʶv Ih'cu xl+FB s SPSbslYa? 9x36۵e3(ܢeE}wbhE!\^ '1T ZΣ3 9#Hx,e GMZzEUM%u  199E]c +gdh k@:ffH  ѸjB*02LO_?W_s x#uq;kwNJM?v.&&&()-Q~g>|K?OSn^ؿoXp]wO#T. *@3Sn1(/6O$%$'z"}REҺy3ɲr)=g8đ=!TpP\vY7⵵v=' IDATJXLLKⲃ6˭ *p;L][kej%^ 3~226v5oDFcl* -Z p%m?^ %ˈWWP'sj,5zDx)0EGQULZ/ɴ.^MĬ4z.eőOD!<?l^Z>ǓCˆ=Śu1VY맷Oiy)vBeu%Օ1.6nh(wEU9Uʵb>֍; @TҩHnߏBw UkTG`\^BԴ*Id/:X"6haTTv|.p $$)+pPYYŷGZ  x/(D$*$85(,*RafN'?u뮡 A0Bkp\ļ֗3!mA2 dP6dcP.j֥TuV!Pn w|NVzuXm6v76pÍ$// _i^~i/-kQU] @EE%555\Bal|ٙ>z]<3A>O B?q-Ax3wʛ(\f!ekPm,V _\E`L ʹũ $ [Sm&&-[ $.osyS-[ &xFH"/?Vs[cv3\.$x.5V`U:tEe,E^Y?q LB!cD46DEI |8z);%Ta圐KF/Sd1o2>2= B7f|edRALr_ۚNV/iXtcUPKJsOPZ~0Gg4U?:YBUPqtgěys[heeOW45I ΛnVWZ15_<@hX"~,7x ;v=}/HWNVI?QݯE0'ۥAc .V``6\r2.%Q@Fq]KK&jdQcQRqy=fzVI"s`u]BLu!J83Q͟EFMNVPUL`3p.NkKk F\<껭8o3XT\\=fL/jytXB~䨿oX;rkJ\ y=WLh HFrKrAF`B g. ?re[S 'FՋKN,qg2 ~c9mY?i+sHBTL0׋ͫV1yo#c9TcD]B~Jz̞VWZ'(a])"l QC P?֓%k d(V%rU] չR7TꗜZ/R7љc'F:gg %D?9ֻe:Yx/6VԬՐtŸЇS%($V'M71tJXo6#&l;MyWnbjIJ~v(Q]Pi72DPp"骚@~G7wA҃lHCy-xC*S-Aم,q#k6\%QCi ([]ϗ2D|[n!"ܦ4ը?y|.Ik/dL,*i v؍EŞ' {-Fpii<?D{oGr[V^d?.dZfM$`B]gc"RN6:L xJ=Rw7o t@bE*@‹SH7$M6)ݴҔDd"A @s/Y]N3ӌ)!^du  r;/0sk E9PZ$AWFSD[ZVtI'QBkyĕg$) 1̐$ζqY\.'bkVrpnYǛRR*#R?2Lfb.[n1"D TҺ%%o? šG!\d^KXT_"#0'Ӎ,XZ6ikVFBz"B@C#\N ETTU`'dICIÌe",H'Fëд˾4Ɩϟ|mv9pYA)zS(*KU Xw"Aꁦ@J-)J<1;a||Vo  49?3LORX\yxpJJb|l lbrbq;))+ab|3.<#^HmkIw@ޤHѯ/ZdkȷuI\$J$՟݋KSML gqa&f I!ôyMN2 E!gYffRUsiP(d'%Ibrz6B1FW"2KH,B\v9xt<J$\j3օPf% ))DH8UUY$5~T Qg` GJPܘ"eyL3r?Γ@3`m*,XTƚ>9Q^%7 yX;w^PHWOgbj-庙XTőSz\DZ)>_θPHC9uvmkn󇘝 p&g+9;Luy!<1'f\ռ~cԖX,_x`(ȍW0Û;8͚lZS4/8I-MUz C47V?vqٲo1`YyǕ-亝tzJq+jS33h8x ykD'!T?Ia\'Lg oBR 5,d]hH6SSO7Wys7Dx&Гaff>z,V+sʴs:ͫ΄.Jq<*ތfɬ>13 zOY]KNWGѡe-albS/aۆF n[3j# OoIʊX뷷yc/'/őSxr[x. 78Ɩul륲4d୓]Mu;0sgMcny|[UP{t+[hgz8v'nxIvlYx8qb\g_>$>mlr.-J siYYˇN=Y$[o??IHz0B#Ug2 Jf2zĮ!Ј3WRPqDЄMz~L+2;EExr맢Os } OrrرYCށQ֯fuC93sBDCu uWOqkV%d|o?)|T\fAۆ~eU[LB`O ¬B~uѭ++T''fzjZTTRYSV\*C.j=-Lx5L7,f$rʓ)NdF f*cpje͌OpHB@8cy[vnSn# 14:?brzB8;CgYJ _UMEiu`M&$-kq;Z'P("/ϼ|B/#\{j^z4om m86r=Nrsgtbo)yR6uAB /֎>X# :|\i%=C~nݹə9V$IZ#lYWٮ>9ˆ5Q]^_S- BtkWB3ٯC3qanLOM C$gΜabrk݅nP Jh c\mHd{1N<Wȧ ,||p0Y38&u .Pv2B ˜)򥂘/ڌqȷuI\$J݋<, ;ZZ.a{)K:'4(".1*G-}C(c/dZ@ 2 13ꪲj llW 6I8N`Sn`:$yc3R3LBu<:]zׯ$I>|GRWWǎ;X,1|Irssk5{{{yg[㥗^ $[oqW";ePg\~崴(y'馛(//'W_}nrrr4|<,̙3\~sWԔ0M/Y]KRWE Obx7ͧ%N?/ޗV21[<·$U^0@_Bky$IRs!f2*SOECYr㴥/Oy*0 \d!$Y8 E02m#7JWu3O5bbb㮻k_կrډ'{83SKvk_o`0`YB% Lf?Lr@Bx&73 %Bga&w' L B`c Y-YRk߻^GwWWUW.eWOZ[g;9}"/ȝwI0;-r;y饗y?~(^7Sb裏J^yy\3εc5'gD c} ȫ9~|6e->4h! - 4>!Ԑ5䟒VH>&m^'l΅N]]]޽믿'NpD^|IV^͟-3 +W'<ȃ>޽{뮻hkk{ロVnC ׿5.aժU\z8pW_}M6ѣGyVrf6l[o=~p!^z{{ٿ?imm_"R뮻z ?yVZ??,[~@ @{{;~; _{<#r{n|AG `Ϟ=r-rw \%,Y]wzq olr$k%yugSB$ȯC@fN !GxS?16,7]0DM_0U$X;vկ"w^ |{㥗^K.ߖ½^/nb.nv֬Y{1~i6o̡C7^`ƍ@ȉܲe ۶m_gUJY1==geƍtww裏n:ill;  8p͛7s~qhkkk!v9z(|;ߡ#<立FO yVZE]].oÁ288C=ġCؼy3O?4۷o`llǏrE__~92ϯ;|.|.2*xIhq;<_lZ|ΠQ"_0eTĤQpjERA2iR +p Y IDATSId`n@PuSE8/k׮eҥ\uULNNr xGxGihhҼk\y|{K_RL7 wϳuV~rsN%__ؾ};+W|3l۶B|>qf߱}v.bhnnf͚5|borw}v>K+++yGx0BC|A>1>>C='?Iq޼y3wqNQf˖-ZK~ vCCC;|_elݺU]]?xzk~sI\sI7n(/]|*fDqsW ASCMU~.o"!@tByQ1Ba$$s5( '}$s1,$3C|>/wCxٷk?=]= 0?*ZwdAox099ɳ>˫ʒ%Kx'yGA0   J|>.?66R(++fIϤo}[-GpD_/2JKK)((Ѝ9@ QUPP˱ZRX]]4Mcc#s 7իzvZiEץ.<ީ:9ER =/yHV7fϦËŠi!4IRSrLeQˏUrfZ9Fs{$6X,gfGE JKKX-&|>_HT0H"Eo:kvٮڵ {fn7/y뭷Xv- w}غu+?ϸ$jX,L&7?nʓO>}ǎ;+Ξbxx$033p43"_\\͛я~ÇyW鵜c=9r~?r}?Maa!K/۷~|M>ŜC#T>}ie*+d/^Le 5ZqGK#\L:ٔx)HPXBWDӘ1TJnEEk=$ ҫ:JÍ&#Vi **)01>8e 21 twv31>flYfV0%B;EwHQU; ڵ5kx\s5ڵK.o}[ QXXҥK)--+_ ۶m\{8NL&6 Ӊ``˖-\{ 9sr)}'> vE]ķ-*++!$9)O¹s7StRXX8?ٌb`Z%K=7ndΝz<@Yo6:J-x7x#W]u _޽}sv^|E&''q:l6*++G֯_Ν; Zssy5ym<DOPg|~gxG &.Y|pZ:g/f|ҫL>?Nv/~ٞA>cةnvl'ĝ ^DZ`/~X/|x@EoBQv->Bֱ[E%  }n7(bZڑZ"a1T??B;.0:-E3@Vy䑇;MKJWd}~~Wx3wYJθ1^Rˌ?Ѷs"4֔S]YBo(]CTT[QThށ1Z뫘Lyiao(#X\qrF=\{*>h;gE &Duy1G>?";m'NQPXdT**SPXHWK.)әP:B䣆6MϚ{䑷Uyd눢bOb6LLyh[7ksx=Nay]TsY mf|CW06WKW־TslkI97o^hMLLMaIc5Z]=$F#_;QSUJg0kڬx}Lygw4UجvFiC!)-G> Ӗ+8I^'l暭2ӧ #׵p3\za+agX,&Afk.QA 3*L|x}tYPP@ HmYVGSm}.E6t%Nu382d-u+v(/!\D;|zFV6P;4m&|6e2 ̯2##HXCTLTjy F-Ke!eU#E~ᰒҒ24f[XP_uSEJ]w5wGy'uJԹg&NLF7](6F'i)YQJaTsÕ(e1,KxxuQ.gY܏bV]g^:e*ˊ5)k/_o^ޏ\~,DG/}`E=?{m7]ua̹X-4KhǨt3MOys ٴJӖB/)/Ey_č |k<.9?y7pCINmMMm<zA~ n^Бhx|JD 9g+B~EA;|x@:}uLlZ"n1׍:s"ѤY"Fo} ZƙtF+DBWW:%,r̶iQs. 2##,,*)Qg?S> jkxzjB!:f-^o\ݒZJ [!%^/yٞJMGO:*^Fy>vOy[%Egx#1>X.Aϻ%_O_\\ZPzuTlZ|&FEZ>Ɨiٸ*-N b<^JtTPM^E?Q^Qv! # Sa9v&2s #:%XeU>x_󭓨ꢟKz&K:K:K*/Th$.v>y%fB1G8C1Z)k!rF)T#\T@~܇  P? > k3ap8.ousUt\%\%\e>xͤ-z o[!?y7D!t ?h!4R# рtg䶠FQS'۱٬Z'KW,U4GG?Ɖefze |3MfsGrsQ<#襔K+SY;?Ǻ(@{?QܜQwrsh&]> ȺC_0'SMYV[I]CwrAΞdjC}CE%E1=z,K g5ȯCMĽxNyG)c98s|U3I^Le!̌ϦGU4r  b`0Z*1 H)T A:HTWKu%3J'hGzl2!z89}`0V_KGi**+N۟"nG\Sh'F '+*HQIIxbb't<>>N__`QO7|M!"~' (266F__}}}LNN #|?Ifbbb6O-kB.cJUXx߼Mw(7~63~L_OqTC緯KvVOCe$W_/ bdË,l|?rw>xT<#(;MY W 9F:ch;:ֽHEXgd+-wq&d[ݳL:Ʊ<Ϥ3y 8qV W/GEhYBQqg: & {wggMut)+,Qn,n:2YW;۷ogtt4~zn6OO;w6OZ^2XH`g߾}vL&<_~9s䡇v~`C=DSSS|ۿ[oY&r5,J[U<`PdߑZp9˞+լYVGOfQ74& M2s &'6ϸ,e|˛{vY rapt +qV74ƴG>&<8+J*/l>F'tQU^LgV;.=LWE67;)Zn>Q&^jshǝ ^e "M;Lvgns| `6[`EB̌FS1 5*(/)YѪ)A'I'N/O~2ЊFL/]'z Z,Z0z-k$Ȟ c6M}8?|¬/j>RWWs=;#ᠣGEvrNahBՂg+55>&m^'l^+_ ˗/_2{k_+WzSNƦMx衇x74 ~?n;w"".ĉ䜞|"&Egx#YMnd208:I NQƚ &8ؤfOߴOߴ+/+qrGe;SlX5V() tkh4"4ʱ8snz*K uZFǧ8;Cy̰I\)i[!F~ dR*(*g5KZYb%UU.a yJJ)..P3XS_ ҄`["JJX-8kY7!@?16,7]0DM_0U;griVZŦM룫뮻UB_qKJJW^arrIHAA8q;a֭[ 6mbŊ 3>>%p7c%uF#y1 |Sb_XX4SSS zx<E[oŒ%K馛蠣Ç 7܀꺶k.9s gFmmBsI\sIZP};h)`liR;(--.B6+#NxZj*xuQ܃c"A.5KxDb6iJ d wYM .p _:>*ET9|R|xwIird*+ >!ٔ }}n n/RJJq>FFBC~cCtEU8" 4ȍ|@EeeDUԚ4jJKW{ҲR**+0 8kp W/ffA+ytǻUWWp8ضmN1߿6Μ9SO=c4ihh={3ϰeE՗7Wu;{ZL~GWU)uN{GϰcIg|6T1GNt!00">mۨ|l6N>֭[ٹs'w//ظq4!L p8~ݻ{馛8x _~9g)++駟f߾}p \}8‹.k87UW]G?Q:;߱m66nȞ={fqWs=py.">O9y1w818NCR2Pl/``x:W#cS6T1=㧹%4Ub/EQ @}c,ovI 5U|KYj13陦 ULyfX$m)glK8V.LFKi)gzO83βfWe)KTU0:ᡬ&'b4Mج渶Hd|6e-7!"E&'YUY({͡etle;5FQ\^׉ (-- "A -,Р`kTezg"$5l$kX*+8`/8S(uWWb7^hgD}}v'O\ђGyGrxD7-N,gN!s|ՍVit$-r'Q˅c/֕r0 r_qjSdrMY~<ɓb61 G0C(//?T9YFe \}y`ĽmAI'@rTu~8r{߀l xquȅl4ەm2峬s/, ; ׯ6F\.|BKe2qg?[.&JV 7^ua / +driʟo[ ;kf82KLyQuHO]&|6e-^'SăL*Y-=.֚yٔx~:fsOXL)JVzsc@VH*G:Y:sŃ#YO*}*'#7εt8<@+~b{aSu5YpQIGf+IG4ee-MmGyd\3εc571ui_U ȫ9~|6e->DX@h4$uB%EWi}L[P#2>d?%ݭ-&vyٞJMG5:O={,纻1y(L&-&? {n&A9;2K.o5f aVrX>Giq.J˷jsׂ:ZЫ“EYk!Q+|BSThxt e1pq|!U5/u#yNPr[qf=E2B+0LJ^H Z~QD>@!b>>#%i"/pw~ZAKx?棔2\괲TE~ZЗ\0'aJ셅1bQG[7- 겎/u>}EED%3,_heoU5(G4vv6\eVy׭xXgu#dgSBxD-2hH.h)8\r5MZN2"Tӫ:JAj♚\/+׬@q98s,fRG)-KrVRQYetx!ve|tiΞ`0P^QFۉS@:J9׋FYE]g"L&Z5EGO:MqqE%ŜLO{xတ4:6L3:2?.prATa\}3>1Pڱ$w%n{m<Aof?Ϗy]ʏz8|)/Ͽq(O^&d+ltx1:}#zqGCE(YdgSBx2;Q^TuUy&,Td@ >:OkV0?@8qFQqF4E% PP(#,_B{!cx&jKPuO4!&,o g_PkI׻bHAH2{e ^CV++)JQf5Ql14:f'Y䤱ȸ${bˡ\Jt踇7g:,hz~7+[jtA+6 c`XnT9uI]e,ov 9vKCCKzFaYT$]جfVPX`UBϯCE>qle^!\xQAy 8M"VŸN 8I:qzyV6JtfQ~h`kzt^v{!ΞSS_KNg9f` (1B3/e,+S#AF#)],YL&cq3<4b&*,ݑE%a+dr!zަGtsnV"x5L&^`}X[w֏ÛƟ}2PvG)#Gwb ^ݳk(%Fc^ڑ3=TPTg%mP:F/oTKBʱz hT (_^1etY@viwt /^`Z=zKkv(=?>  ^qĊmr:v8IV;Ͽq| (*ЉN@ dtvϿ~Ч) >kh;GBGg?+Z]#l\Ɏ;8 qqUB:ֱ&WЉ;TRCZcrW!?pvtFCR]Pݟ -Mt(MۈڪeD =n^dA=}}4SF#@BaaI% `+Bs&UՕB #R8K.;|,4!0<4LIiqaJa2clt Ռjq ./";ss[ 諻l9)RTEx\}5.᳟׿bz˚knw/'ǎ 7ѣp/cZΛH[oImm~kqI>ˉ E[A6ܣZ##E:x@iݏ|O}\-.Rl1_NqY%*ưb )/ Bt A{IAfEgx#٥]*7m7mb6knudek i>7%MռQ\\y5B+Jp*,d97+q5qمKhmd2r{X*VwVm: +/Yf'cP^j h"x:zJWZ >r^XFfB1G8V;)wN MY W#R{Ď!T5ƫʯt*ɤIE<=!iM9F#KarJcPh/deY?F#-*aP:=@iY)erXMT91 91>AqIU3US^ȟHw^7AӅWܳBA8~##|ؿ/4A}C=)/QVr9e9>\xyiOoUSfrA!j( bl4mĬa9Z,Վb!SZV1݉dc &KUxjttC_[o~\e>xki7\Q7uMU)ϿqjGŽVv U|Cj+7hY̌}w覿hU#O<6f?ټ wR;3V ee/Wams)Mxqd٥iIu'㲙s|@c`*Ρ_pV"BK.L:~6Z 6_~u.֮Q>9~ؿ7g>CYY9 ɅSOrK(+/tQ^Qɶo*_c?ʨoh`Hz[wp#?o馏Q^Qob+_>>PPLvB=F"}+DDF'9OHpu<`(SeB4HК*)%RV4'Ô'9PcΡq/MQՆ-ovaYTWjIh WF?_iOf <9>st`E9=ȟnBbEKrKH+ʊغe@Gq!ۮ _za+|b3>x^>r*yԻ8|"Ł *cUJ|6e%!̜Ϧ§3Pz bbDd^6L!BR3@ΖNrV2s2HYR՚bUb  ~c/ ϏUI0&$Ri?yO=/S }SOo| -N.oi^:ao]{",?BH4F9 u9>> TTݷЌƂ`@F $Z #2C"D׍b(;ZK@[GyHνzJ؄2b>eyZyfs'ivbЫ?A^qxt%^ZGK}ev9p4p`/ .e`6[WFOI A+N]mL)+׏G8yCfK|@H K +=ĽmAI'SLkdIs\^ iqvgץv*dhP6Y9[Vo#'@NJLAFҿ}Z%﹇ʪj)ލ7}"sE:E6$ uY/4y QIsncŝwQm!+kD=1䮕 IheUe-FK[3!s> ,> rZ!,:[/]Al7^ua / btl#kAb> mDjb<|?rEw>:$Zԙٔx)NʯC3|dqk)$)˦&L! !ZewTz7(b)#/- IDATp8"t(uR;Qe[y_;ѭ ^PzF*AFϋu2KrnTqTNrb̦a{rO_0|f˼Gdm)r᡺?T|ڏ:ג.qT'NCTEƪ(N?!*6il&M92]%m鿶d]yʨhEj&A>ŵV&c- "RYb9O8 #W+qe?uJYtQиNRxJ!jAAy%|1< fkjNT6y !c} ȫ9~|6e->a5uQU˚~OIJlx1K#Q9I^n8٦#s'Sd65-v<8e9+.|=:AhTcy@*,,ҵECq"VE1 瀞Q^aLBR;N0])"}/(>D!NB8?=+dnm lr~VBu_0=>F*Bx qU%˾TuTP;~@g9ilWy l ̀E {($dƻL&f|~)|3Fݸj";WPl&BC$ N;Jg8Tǧ czz¸ya1fLN& C.[Qz M*IU-7W FփKB. PT.v~tYԈiŝo> uEQ1Z;j6>rW#1I mrb%I\yvU҇  P? > k32W$3ޭ@ ?ڕ\Af fŢ ,_sZUdVq'|қI7Z?߶R܉Pyqfe^̓n_P izݹF#W\YLv1 Ud]Z)sQ\D*KTGD/T^Z:9%a|6e->1iDR&rG/fT؟ˊm}jb[+[~?}} STd}؋((ssaJJKi_dwB5;\C㕋:GyinW$y92&iT8[يkŝ!tȩ98ٔx)^.QˬL!&5+b )&-'t$ W ѓ3J'hGَ124JAGǍ3]xh;MEe3izS΂(%B;Qe3GYs8y]So.\U6q_6UXte?A#&wye [e(+^D #zqGCE(#)k!R<(/: *S & {wggMut)+,1l::[;XH',sy[%Ei+G$!LϯCE>qle^uuT'NCTɺ8N32<Ƌ|= ǷX,,_Jzq'l!*6iDm%Hܦ#H&#<2BڱUdz+v*:a|6e->a5uQU˚~OIg%ST\o&lVHs=L{i͊j3啚eb6/TIbi\ sQ\Ţb#=ym<UZVBʯCMY W#u3]Pӂ "n^E?Q^1t!W)//Q`ld(q240DA ٌjYݏja9v& FȆN=^nIk=['lKKˬ1N/ M"b#']b!C[~Ӌ <(a#>֧gSBՈ+!1.JwPTeS+ I\9.k bkVDKN0**1 8kp W/fwA}s!Ls5.ͷNz3^sI\sI\sI7~G<]1}$d1<:O?S/o>?m3RϯI>gĹKN"B\uoLkyvɃ: QDp |3D"e{Gr;8sgVfȲl ۘb!RBH-$8}y&^Mr^ `c],nݝmڕ9] SMyI&G ;3!3,+l2EմDʊ!O cM,w=&'}d!OF+uҡCxx孝waxtܬtE.,OƗŅ9LM9c9| op?ko|RR,}/~k͟8 oc2럮/bǞ&?ȉNRRKZhnoDrمx;Ȓ ǞOyq-'NQチe cPxeM!OKvze}t|BɊOоF% :*$kډl/!^?}tDBU8l%5.7i39Y|7"q<s-Wٱwdy6I.߰%+#rç?q$[&7;.^s?0)8r^y[oKן[>DUo}/QSQĖXYWMCAei`!~* o})~s"?-:z2`N!;^'s}CU>8Zz.Ǥ3<:(x<ʢSNOII nҢLtx<ݻWq6(**Rp}}}Q__\"r&233cֻ" kjjfQQQs3 F3VQ4&7\}!O|# gKy]c fϷ\%<('+t;7~tuva^(;C=LM9xG#3͎`{/ZS?_΋od2r7@!T\a\8eM7,WU{GkAkB-Sj“@Yk!R/|w0,b5sTiAūz)Tb!;׊h$UppLp:p?Étt8GKvpzz(?c63 *窫bӦMXVMtٹs'_җؿ?O=E,RZZJvv6\p&E]c?[|2|22SSĔRUVR YRSVS^kd*,.eù1 ONf"ʋq{6qnXDJv~r|~@ SRR̔zSN 4EfjD꺜K$Vl)<4>Ĕsr5q:# 108IF MnN)Mrr'? CC9N{~bQYYO?믿NNN<+}ݬY|37y'xG9z(?xW8p@\կ]k᪫`hho|ڵ;3G_|6oo[Ҹ뮻HII_:-bժU|_7 _ٱcFSNSOqO}~[Ann.SUU{Yg]wEaa!7n$33uq-088ȣ>ʑ#GYz5+V'K^8AuA!(H|@2JkIztzܴtuq]my<:zz2/{ijjl6c0$[;rp:dSQ^F }g(J{/1aˢv{h>qa{3ʼn_~ロo|| _>wO;kƱcogҥel6hnnNgc˖-pA>3ϰh"O?۹;>Gss3O/K^{5nnFI馛+g?Kee%= '7n=r ss6m[o+nrq _җ曥t԰ӝe]})k>I(ztPGtˉL Sw"zB4y\vevg՜ONEE\իWf/;9:īJII EEE['NS^^NmmL&n̷08l%յbo8e%W_C8=^߇px)W?U`QQ`2a2{荨D8E`g-¢3#tdC -Ll4b}t5G?ᘢ~ryWXa=  ﳷq/n\O|ǎoVZѣGٰa .ɓ\r%ƍVn&6nꢱ\veS__Ϛ5kʴ! YqJZTF0EUXzq"ǝu^'d+u Pt[q"ox2K3xrxPQ#^>/*^2("XT 0Z,X1)DW"I,2C=OŻ肋Yv .1퓌pB~˟x">yͧ8vT;Yk'ɻ{HIёQ ο|k?y-+p=w{;1 +4{ipNQN jݻyGpݴf~inGCǹ;9~8k׮.ssiQ1|c%\cFEj\J'RO\JC9ʈbB2|򉔕̼|Q;ogQ>$EeD/*!ԺD;ҒAz<7p\1 Ɍ㞽˗/ 47: _L:ff#  ۇr>@/㌳UƑ*p%'3 Ť5>jvS=CC8x-IJ4C $J:PM@C^(q|!c(>Qp8LLL]="\2228Ck>AUu%47011㡼U `yz7w=|g )))8&'.m=t/8=x( v#L05VvU,h$33SscD3^߇0ypq I\ɤKɤˌy2kio4rŅYJ{K;ã\|^$;߾ab:m9E5{IQPwyΈIQ;46z|"e%;e }ưD C4ib+螽D`0hXѰo~>TVnmy#`ZIM5)l6O N^D)ovS]Y|gef04:A0`0f[gg']]]1#9`2P ={ tml<|22QEs4伳rh'+}))"=ƩA^ڲ%EMLL:0} IDATV /nMwʒ\6,)a%v}dp%Hխ8Džk-%U|ܥd׾s@KMo@ |4{ ?y5ʏHz}g*??5v1W]s?8/(-+w'J7 "EseW/}̬L^ꗿFUu== JEZ#!L~NpT.s0>0hg-ud5_ljx2rdCet=cZ7::FN~v{⋯\?)HZZPyŵwYbxRokhCryDzzrUH~h؜Aa:zCGOg׷j-{Kt8"'+ g/wDEG|5mj774~6E,(}ǰY͌NP[knclp9NP[ӽ$ ?˺'R|} US-/E Zw Q_E(7H&Y}jx~X\9X,-_q{߽lXx=*"ki#Z[rDc*zD_916JHOJ+D͆0ANG8AD@^D Q>>$U |$mf[Eh$=-ͻ t|4 ŋNӀB LdVc 2EpsFJFENtq)襴(L6rRYXUԔ xxg/f8\nG')(?[J.2RX,D1kcW4y}DʚagV&)X/uB󾗣JQ'6n{C:YH򉫯 JOeW\thw}f*\5[xx>QHlQEwӃw蟈(c   F#EIz/ r _ZXEAtIՠOAJ׵u]rޣL(x9,'^tĞH|ۀ ??dCogd×} Pt/ RL1&ʇ)֮R^<!TCt04GHggܢ2/1kbv{h SIW0GOPY$y[ >{\WpQ^&]N?4F8g-SAKk7hm*d4PO042ɩ!V/sHZ$\U=BWHΖe]çw.xT<ڞZNN"DʚO֦9Ɨok( nBUXgThD~(:tĂIV)bL: 5ra|Nzʼ?!A6K )UU'bl{ޥA(}JgoΠR>АV;X^(jߟH \  =gE98?*[jR,U.F`^$iP G V/h0PZ94,x'WrqlV3m †KxAq~&}LIA + L395;Y|Tfr2SY4e]{+-Y 8} ZF|!v^^'6Sl5<(?6Pjʨ,B=be)RJE(PyϣV`p@ U#?G#V C(>k{hbO C$ֽT8%S? "&@4H Г/5Eah銰uQH=砭:JI-IɎ##i]hJES'CRGu ~V_{[*>Gԡc!|GNq)}PUp=Mus ol;wtW\{͏w>ly nאкHfQ\TgZsWAC8 +ZB>B Tm%XPFU:hgBF|^% [LGY;a@FG ` iK>HSQk5?ZʏJѲו@%  9սw0!!QXE8^V%jȞj1څ*tytA_ \U yA=7 2q 8MF7^u|=\}絠C8sF^B}2Z9yֶKա#vfR] $^ym SJf;ZΠWdK ʣ bJP>m ;|As}CU0_!ܥz-(挊Bިg|L"rE=U!Fj<y# o{7"4S$;L> nKx 5k|{s~t! )+y5} ϵ:Qڴqqܸn\.7+|)* \~T7j B6C%|qyrՈe]'DnNc)\¹㌠+G՛*eRR e<3܀R4ӰהPl9C\?'.sa8~֛O\*q{* ld#ll5<(?"6P  YQL$2RhDc: a[t["Ɋ 'h_kwvm1 XW!nGXN`x$E,RXޅcsS?%!p]F?RXmR«pWe5*QDWzGw䎆ȜY0HF(ݞ"ۗQϋT?D篩˫ҎbTK@W?'{ 4hf^Hڲ8l%kxQ;僫x5^D ," ps ([mxDʚO|؇ПJZd PAK[l8/B(s4y ablޮ/+縷 gR]be$kxCt({Ze@AE_ [\SRբ?ѯ/ev*̛eϘ/Y"I_.jf*Ak*{$cޔ$i+ M^Cyq:d [ &nb؍i Q! #^~6k\'< C(EP v;^ŠzLlj <hŏw~A|"_>j$0m6:=DB*S/Hi)U'd> ޓDY]:=${V ATC_ (k u>(q R_ + F>}$ OMX c'[XͅfKrZ^DL/ V<^> CB6~Yj9^Gz$6k(,*`tdq{X\,>xrr(6JJKyPyegxyd̗1~sE5/alMžrfA4klO^PF3?-@}ΊOrUg /@~?>֋g5|a9im6ܰ7kaæ&<61O~vY/**~:_Kl}}}Џ\C,OuR,E8|gvi>BVV’*+e0j>$n(-\BH W)D65C!r!#H7e–l$_@D1S Ȑ U%¯@Pdj}Cw5)CEGAx{ A]ևy Z "j ɶ^3L>Ysh`cO88M{ 5Tr,ɥ;XդXP+#,)8?S"?́#3trxsV,t`b6226I~N:݃4V0:>^:YY[A̡c8=B 2ٵ)V3KkJh>E{,)f1h("n>b}xDJv^X! U*CTZXMX>ݠ\ _#{f9Ξ]{F`[wᶝvJ`0pBZd{ʈyǡ@tEݤhr R Wje7AIOE+\l2oN-0" FR j~,f;A #;D `k˛>'4^UP-I! .#zs'.sd6Nta1x]tt(6mgISN^^>:J(6-~ŹLL:m~)vmw|-fׁ8nZ;őn2Rx!v@(/)V3M'88Y'*n2!og3A'}HjZ*##]n F}"v/<w6r'5NÚ<㓲^]-fο<ط{?/`n/յ>S3p:'f{m("G#6)O_BZ2)pLdtbXn4$LF c ,Yk=L-ZWga枕<<ݴ'ԍ8n:ÿ~ ,|=.\Tw7Bq~&Z#fqELNo3prńWk19nRT]_͎ƣdQY>,f#\^h46eU礱bI9S,(8f[$ͼD}'R|?P.MP΃ D1Jpպ2ǚXsscdfI u1\N'yWVp@Sdd1?"+;3{LGĊVAL#LcqU!r7z ,#㌌N2<:AEqv/6.onǠ+#-UEnpc< ޯ6t4̴RkXϭes;6A0.9-,xDV-Ծi~-x95M޿i}:aC(oéMHY;"W43 qX뼅H'@tTC;*dq"I&*GEl6ELO񈤦<)=@Ú|/ xD)).Q(D'XD_4c?|EV 0yv V㌳V( &X')VJhi7* (%5Ho~"^|s7}e^B- Bmu1; c2_T}-`O`4 V ɀ`j6fcpx>jaHխ{P#hQ+䫥b!/;[ wu2:1Tu&>{Lj7&VQ49q{x<]n.7.ttHzYkV:twP]P` J5dܢ?j5f*ۼvGdXq- NH3mIi㡣cffwjT˅$''rn7_XC(S} G;R Qy&Dg0Π8LGf+&5t'zҡCG\8D՜VL"qsUCDL/ ӟC(hr\߇p$=G!&>Q>L"(eg*Iq<2~H tGV㌳U=KF>k}絠C8sz ^&P9<=TCI՜pRAPA7B~:O PQཧkIvm?䓪ٻ,~k5Z'Q&^3'.'.'.3 ԭE-^E3ct ڇP~.TdAYNrs5x5PuQ*1l5MXjyʧ;ʋA]6}cli7L`u '^IdIdI7Sh>dNw~m]E˽g6n2xX6~~6kP;gV EQz 3VQiƵH Խ}q$VMXQ':t"UgTw:YE>R =/y]}әCh|R#bCTg(4D'T>䟨}]>?3crB o ~w&s{Q':t"_JDJ.5[1r+ke}Cŝ ~:͡.&5x)lpC(\q+j6T뼅H'@Ez41DHB ]n0|"]DK ~&^>I~3N޸XM>mHH越g_Cյ(Qǝu^}B|Vqʚ/"MáBC(‰)k>R<~?*_@*Aj6@-e",:RH+FE#g[XhKhx"/ꓛ/ p1g3E|}yϣVzGN'-[frb3p0W6lN,/DaڼL!L6iB:B,UH'[XͅqxfH䴾a|"e^߇0R*Yj9D_Kc4YR`T)N=⥋[ˡіc311sWN\N*TQ^UoGFV&UQGN`2ٷ{SSS-Cx8y IUI3H IDATM5.^?$ii.6eo*8vS]].&u˗dggQ{=p!M%/;TI3_FbF3n߲$n*zq 8ROtk;,,T`+c_XJ^l5sxpڈy=܆~Mb+7]!96oh/mͧ7Mui"Yͬ^V7Ͼ]xT'^ Ch Fe* 9-HqxY/enq|!jGk.01>=Վhrsi?4ve+fxh+p4jEQI! VRAfV^qNk%'7nVYd﮽:NhvY-(كfch:nz8IeLNLro,@ l+-ⓩRVO$>>sSɠld%d%deFyXXYm,(D{/{q G}LFǧ(`ϡlow)'HKaoS}尨9|4UuX-&vjehtbrNW0%YVaC>H K D;׼!l o򉔕0% U*CTZXMX>$`0HKOC&&&HKO# O_PϩSnrd`O`<ORZ^-FAQmSSX,WgdaZlddeΞ (!>b%㼼IP O&'.'.'.sKo&Uq&4V0698cQ #=0cSu zD> `YHز GNvql3a^~CO0̛tHMoo_̿=W~9{182֝MN⛻pq:{L93A;0馡 F&b۞#M&N{6vj?mSN&lq݇Z%jfˎCuۍmiDWXQM>M=\)ͼm<I(c082fἳjX ]\vA=,եd @۩jpY Z MPRy Yj,_\Orlۮw| WnXAW5M&SeNtrw`zFI܆,)KΫ5$1+SE67]͜(?즡\YYC{fShHM'R|3P߇0GC2rp!8%e%Aąw@_?==M36:JVZqW#}WgZ9yexplRS4`A*#r[10[w]w]#UL7x#կSxyijjI./>b[oQ_?8ӡCpCyO#no4֎~z RVNnM"xe}Slfzdl4"H$[5˫YQo4 Nx¹ 5dF9.:{,jhkV :^b5sT?vj.:;>+0pQ]el~S^ŏwv"nugݪE\J XbDP\4[y}Dʚ/5ʮh g.XDF+p GL2N*hʼnsX>$)b4\P8(b0Y$ d>%uKd6SJF~ްʊU˥Bd۩]V$fR|6+6杇pbKa6).-ZPhd4n1[P&ΕsEQ* s/U1'_&33|+,[_w^x ~SRR$kÆ ,]}{\y\tE\uUx\}?&9:::⋹ٳg/fƍ|s#==='d^kb <w}M'[n!''[o~ng?˷mN8C= eڵLMM144OS:;;WC=ą^8]$sYH6qR_\nO^IϿY⛻)bzuH[ZXLFNIMRW7Mnu mV35u/yS,MF fO_ʜrAr SS,T涃HZ0|S.F\Ԁlflr޼lZZy坽gb6`"^~%ES,جH).>^ɔy+kZLX[iv2>'\{* |{w3ꪰZL }Rr3ӂyj T %Lw.xߢAQV.ƊOKdmp?*_bB_ RO،1rBTwȆwJ? '.qѼ\vd4HHܟsDvHYX]b1cZ[3-=U?OM324۳G5uy2L@o ;*jQljjjͲex[xgk8ַŹ]wEQQ<԰qư?KC,\|׳b 9B__w/'Neȑ# ?͛7o~n&}Q|AN:::׾^{-[ne۶mUkj\$H[jx2&lv;.E)/foS6 K#-`2ߞ?k!#cfSV TuwGq~&sW.4)@oqm=fV042.鱸QNct>{L W`+Y)d{yx';/xNIA%Y>v 9+5428K>|7>IAN:9ޏGۺNf1+VC^;h/΋,*ru!^~6kɢ! ,iiiaҥ444zjjkkggް-((AGGYYY^ZZ@ /#-nn7=LLLɶZTUU!uuuK466O~:tj:fFm yXс5Х ȦUW_'R|3P߇PCV4h|WC"(eg*Iq<2~H6fϞ=!tRjkk?l6%K3ϰ~ٶm_K6leя~ėeJK_y}Y|9\q\.FGGygYt)UUUA2ϸ!f E֯_ /O>:t{> سg[lnСCcؽ{7^nv/`Z馛4:ݡq*\6 .X8W4CZa_0N[",W}œ@Yk!R/aIZ}uƇG=i>dt|-!w[n<i&~ӟe/^ 7^x!øn233Yt)dӦMs]wrJE>vM6QWW(XVVX'>!Fvl6?0K,G瞣x+VhJa.ns``ӦMZ/| TWWpwn:֮]btrm@CCYYYtwws7RQQu]\s5xϧQERrssKH [$L$;L>aXC8'RVj2P;;A0Z,XS^uPb"$Vl)jLvqܸ\.Ӆ"՞UgEootvv288ƍsoϱcǸ;o[oe``|Insz͛7~ig:^8AuA!_O, dVגix;L&UUUdddDk.ґbsƌW_ST({|Nw~mC?HYɜp!TyF,{-y%NzEj0W{yf 甓rY N߇0*ij`2ɤGJUHHZ_QTY /LWxRҵP6OKnO}w̎An~!v]Yl jkė#T-EųR-\9Q*:Jt)g(NSG^E G}%(gЏP ʽ⒕p_V7akPmp}UyZԾF;wxu  *F `AJzfɲ(ےlJ.cǟ-%7ɉq"ڲ%j$UH]boA]b{- $(<0;s=s9>p ĹO#+'!}qivwvIfV)V$#G~ S~/ F222h4H!G:ڧ !z+!7Bf|b-+|N$Z#vGZb1c6[o!K8)Min~-E!|$ 1~.[yG`e `hL$(jÅTrS҄x t0zSg'V\65VQJm L!LAkc`81ϊ9ZfFFA`N223ؽ}79YXο;/tQ][E2v[).-! y)^{Yv2}݇%ÂJa4jrA3h4,j>K.εcX=^l6Q=k&y9yM+(I1Q/~=ifdtZזO6ecM9 ! +|A$!7UaE!84~o T Z*_Vz~:cH["PGN16)[򌹠8#/ė_SuUaY9mJ9 ~nVް 8k)f[w`~meѸ?Mv7>TMwV2vҚx*t*Jޡ!LKP4[S! viZ'p8v'vGsGyIo=ɻO1p7'?Gٶ=M: KBI~*GpNBK.N|M|Z>^Lr@w*\ P҃A,ȗ!ÈDu`$2% v|^P 8u47x>}4JiqX)~I]H0TCNx%3JYq ]ܼq%ŜVψqb|2G 9Q~uBXQdDε фR ԢCl <* Q f$wIZʼnQJ;PNJ0#K"fT.R-0F|9'4Nm7( /.32.zzaIH=~ua‡NVu|i\YLF=HP_esyl|(rse~A0-/x}Q!k8u Qϙnͯbn.2V޸Zūrռ:YS~džCxp b(CLsD(¤s%FCOJݒa[%L[ՒDž b$,VThuZy贱oaza)?#!ĭJ#! qs(@L n0\j25pI/kWݑ&/HI喒R$!I%G$_HyQ/& |(eNrZ {[Z_ɑS\5˃Z%i$+'Zm@A}m)=6/I^TJzduѴNt2xX^;ɁC8xaC$HeT&֡ƵK~](:4u)-ňKf@=\)WJSJ="*-)1f< YI -1Q`d 4ʁ/%2pP~V&HW[MU|lE*Du2YۈaORv$i"VPD]H?@I)&׬Bho$)p%Uˡ𞈓/6>:6%hO3?)L`a ZҚ8/} O 3rIe%$DIzRکĶR Oe_8o"KP>nJ{B=S gR0Tn!c\>m'y(c0\!pRJ_W&)"]uD1CDVƻi~A>tJ9 ^Us&{u`k" J8pjXʌ$ӅF3?)ۥ5;F JY8񓪎a"x? JW3i&5τA]B_D$ F-\ C/2(G$wIyE!bR /ԸXŠ #VqeL9qULQv D¨z)ާN2_i]C%:v|./S~7~"i] DH0W_[FoI ]ea+PGO#89@HCj*PɔRN_OpF@9y?AP2cJ 6_3S [%>ΔaN%Ս3O=I ;EmzHH@Wܯ$dr50m>vi}[VKRZÅ /eZ NH\%)b}E?$Mg *cu0S"˅xtl ~x:,]G_ e ȊcHi qqv# if\z d30Sp`Ai4xi}񗘗)?O$+?3*KJr,tS)\lqO'sr4\Fѕ.KZ{ 9jyaAPRJk}mh W` Č|Ik,x )vⰗw=]ˁ'ĝcK00^DҺp {1 !S~jʰ(¤sA jل IDo?tg!O`j&&^C82BZy #R#:x`:nBʟ#>4ؗ!y0 ը 2MQu8l#)4M`y,c25(KF#NJdz5`2IS~'?sl )Ӻx`Lg×:Ը:*W$i0UE6ą.#Tᇗp9{{u&A@ h:u uʧNNtqI223C!NۘS?; ἦ_\Cfx T*?0ܕ?`0FAFsYYY)0EmpTa4a/^$'sFۯ ƽ%huPSJ9p'Fo'Mʹc0詪hA'F έ!;'c +'bNetwxCzz8~,kAOx"v7.lyܼ\?s`2ht~ =a>rC'a'o+}2*N! 5`0N. eyʙ̭CEu-GkKH(,FQI!r T*:wr Ckf$ asղŘL&;A@ G"Q13hkijf^C{@d#ӥ vzR/7OᔿɃLLvdeee!O$+S~?@t+V][/] jc}@233n3Y8.zzȰ1 :E NddfO˧'3;GVj*9sFEK&J$AHS~G ʧ5x~]de'//o+ e5p2ˍ#>C8p2$ay}!DQo%Z`2X~5̚;#l1ke4\$w$4FD۹6f ۃlBëT*TjP?V@  : >׋daaP;C_TQAl/s FLL`l4xi}񗘗)?O$+?3qV}͙$nYLLP@b%:dOYrrq:]?zh4TϪF;q:]rs^>'ydf2ɪ:~C#"JJz ='@RS^9ZJGF#[F""+y ۃ? 7eǴU9('1.sd5q&ٞLLa$HҚ^&*0^ZcGV)C( {9cCJ4cO$+ hj$L<%2oi 02z {D)ŒKj5̨F$ -5xDQl1!F$IdW.YJ]`40~.nŌFAEj(,i+tQz& w8ݙ2%HC'+eG/{qWA[? ?:on7OԐ!W/˿G~8~ѿxI/=/lFJ$Ĝ'x@Ķؙ̺DҺp b(PՔ„4G"h&F2%ZMfVf,VA]PȰ!o#MŕNC}ST )MF[&J0[>Nf:T& :>J:AS^ S9|(eFhj<-lزn $2 F x|~h6ldZLF=(r^0 8q+̙9Q HZ9x~k`A҅0yg45~; (AQ30n/U3(AR!IM:8ډgJ3, :yw1V,C~nGO-&n2L,]0 >H LX~C8cXKC$He?0!+/rK 3) D5ocV$#ģN&ŏ1BP*kc)YMLxÙ tZ:̪*slqy +on`C/7lKO}fNi ̪._{3֮WR<{o:5#lyߵ`4cw~;~;gWeZ^k?nڎg,?[;1tBU!Q EVvAb2p8#%*Ab6jp{}=hz&Q I0$FL Z0hC uZ5Qp\n/@ܬ \/?ha\7,LzA$ŷ(WEc` ( !r. 1P*.P[!))):D. '6_(o$+QT*UfjzSƥ2?U&juCdAQ)8ߟNLYn9FLd# ;0(8E Aŧ` ` ` CvN 5zh4j.\ ;; </шNMi q15p:lddRYQA ' p V鴈1HQQ!@ qzuuTɰs,+ߏ(@բ _*788ȴiKֿ'OI[E%qW6LzzUWi{zzȰR~rrr$7^o$ ߏFEh2?>1_/z>pwhܥO/ x<zz^qp׻"|ii 1-[JPACltZZm:6m ERYo^/*.23c"5<|h||X, Q Ȝ9s!+hoabg۔/vJ4xP1bCEEE~˓2-HiA2mِ=QKLH{N ʌ3FkՅN'7';}hٌ7.1eɓ'_A|Ewvv~ٳg;щӍԄݷ |'/ta٘>l />^h6PT,@  RrzPBWG3ڙFZ[EƴideeoE}>/K=ϲ6oo47,y z&L+Z^j5@x2GF?}86h x^|>y^?eч z:v,ac\D~?YYn$vtg=ֲ`~=N233jAᥫIN.g>̿߇lF`0V"''ٳ7l o"YYH=@A|ds/zzzh}KFF~?0Mj^Jt@o.W ~+EEŸ\.B (o +W# lܴٳ E;<9AΎ| G2cv۶m,ꪤ IRdKH4 ;w/ˉd! ,{ dgg)_` عCQQj6n{Ovv6YYYA{=6#Gb29ºudƌhg:hkk(Aj C +Jϵ|,%%12x ~Ϝi7`҆4`v(tuVjjji/ƻ$I۷͛7tҒҤ_'m{$E&+W5d&~hljdQޭVc֭RRRhD7d֭]nΝ;y7&77q2}s1f̘;'?)v;53gFo(w-܂:fJv-_`ztt:Y>~ׯ18HeEMu& P<߻wFȷ"_6|7).)A ~C"YYYN-[аͶsNRVB(+9NG[[[xFnd2r$J0(rU8=T9+^JK̤?njRTT>3ϼ\w9,-`|:O^V((+[n%77V6R\\ٸqPPP͛q:̘1=VDO8ƍٹs'`iBlnggٵkvBQXX( qF~m%%%oh4iӏ IZmr(/Ic@VR[ϲtZ-Ҧ56~:jkk#qhmmcڴ 'P(N…233+n7y8.$8q_>Gqy}vJQ$tPEHnqQӪ1-cӾqnR/*;͆$B!'znwDevEӧNql6N'. F $ a;e,d%ipЉ%tyt"y|\.w?_~ >]IVζ38f`bija #6f?~V#LNgGWDQc,]X~]SˋRr۔tWVV$yXDޱi&$b.v:qc0~H߿ǎtR#Gny]-\Hcc#1Vh4@ޞYqуשd멪bpI˹swDjEW`Nʴ Pe(Ӌ׊n')ya64mۈL'.\}w nw`d~d=Xm6~pp^{h;-˙fD`߈=yrz}r|@ɫZrnOx̬Fˍ㧤Lra?Z-^Nرc̚=ָEK3'ZC~mAMhkmTHKZ?|~nN`Уh<,+wR#NL Z`Æx8^#Bs!DQXVΟoW6Q[[KCCǏv%l  r3><wtKWҤKرVX;˾}{Vk?+WrJ***5k0L,_f$ {=4 _=^^xI@ և1UH> CxP>@0ȅ 9q.7srWl~43HBIC $ȫDWW'?Wʯ~+%GCE1[o5"N={$}oootR:R>=5xwQͦj .N'p]~鎛U)t9^JLSsT?TF6[63 !NJvm""!GKK ?yزu+^=8Nz~Ӕw~6,EΟo''7Lss3hDQd֭ڵ *|4 l1Lwt~zq̜Ƀ>W4'N Vي~2m]m޽{ys˛@AAw}#رÇ=}Y^r,Y\Ξ=ar?0:s--tuux(V@]]^;v !>҆Z2~ӟijj{辰huZ:`Obr|~_|ꋬ]DI+y~:7O~??S\RFPxbjO/f bvoJk妛ndl޼A/(UPT|oȒ% l6ryOnz7… x}>,痿%AGTٽX.{PT|={:N ,`m6K0fL>яzj/\*O<$ \. YjbB30sLN'fT/=FG0tKz ddXj4qxM7۷D^^v[Sܲ}30 m ' dٲĺ/x{`0D(}RQQTo^z%N>h$p}t钸*_dfu5Ν'?)W;v zͯn1?ȝwnzx JKٿ?-qż?fUGO=݄D￟%W-)d8~8 /`̘1p}r(**ne&y>nfn$MMIC?ϰ 0L\ ~C| Z-:/\x"Z|{졹Ȫ9z6l  êN,~k͛DžQ *Ο?mʝw;dpЁ`Gȑ#t:^uf3o_c͜>}ABPOGGyy|̞3͛7 ؃^o@|G9G?N溺LV4|~?(QWWGf%.O?43kjm⤼?$ "@|3Tv ݡRxuH@cquz3…{iի|\sw M[DD^]j ӈ[$ FG0(t<*]j>o3pIs@kk?JJwyꙜ9M7݄B2ota$IΫn箻[v[4nII)ظq#>:ZVz$)Qh(/#P .DqE-:=yuVCxA~?ׯO~lذ|f5N4j {n 8x˅f8qQ蠮h=z^f̘Y[cp8صk^sRXXP%9~8(2g ԩSf0b';;;n={eeeHDccΝ#;; CټUΝCuu5v#:q1klJ 466rrss;w.9n9̝;,F#FhŊN`0x0M"uJ_57J}2DDABRjJ5 g=i(2Ӵ c}L I \cJrpO4.25XKqO-J1s/8*XٴyO10[{J|ʄ=)^B3 SG&%y*F.)rGByO+u3fp_?sUe%wkogJYn.>m^?!r0AְjqLa|DG͒!*h4~TWWw>q9·->L&~H*Ao~m۶c5kXڰQ9NiI (M}}}vիw/@ss3SOj]h=>׬'.yyyqwP[SW/kD 뮽6FXP(D Vk4|>*+ٱ};o Yp!A0 J@vV999Wn'Nd2я~ ?x׾5sJ/^kYrvͫ?9ZZZ(*,d2e}Qf̘A oU9s&/ dgeȪUdgg_/cqO}4-[ʊ+_׬^is KY(^}լXi&oHqQ׮Xhd֭ݻロL>C /p9<^/mm W" (Ξ=KeEW^d2|-[P[[Ö-[ԣ/ǁvΝ;F#w/ŗ8trKU*7|3uul{-zk>hyÑ#G0 o|sm=*w6l{%>GYYYHng p}GQQ! 0 ZΜ9`dM<x'do~z_cghjjbݺuaphXj;v~5kO~"^|EΜ9CNv6m<7#Zo#Vt gӦMܽr%tuuť ?'xֶ6K,jqxm֝g~鲁.755]tϘ֭[YX$KKKS_׬I0c}C~~>O>$PXVd+"M|K_bzYX~MXf quQ^^?{xM,ϗW_}9s+w>oH[h4s=dff_:AyΝ;ٹs'>?Ƞ># g(_EUUԧ=Rm?O;yXd ^{-Ndƍ|_W_e3k,? \iokC^ye3{BoGFP喛W֭[y뭷X9… ;9r(6駿T!^YITe\L/p{}xv#uzߊ[hq[cI ]VD/>{&?cwy)-⫮_|$5}t ;vh4RG?"33n9sF?Nww7_x<<,\ lj'bٸkA^235k[n!33;~֮]~߮aٲګ8Pݻ8pռ+H̝;GDSSMMj#D\ ɓ''>/3ȴiTVVF;vkr-`9p`?< gΜaڵ̚5Çpؙ9sf߁@͛_apIVV&N硇>>ƍ={}}%2s̉SB inwGnN6f9nh0RY~=u@#\m1~eڼ'J9{-Ia-\,{.(e缯t~qDb7o~$,Ͼ/ &L,3䕾3%A.p]|iZL%w_U[D?rpp+ YwJyy,}aegG8<wi"tӹ&> ¯ {ʼnM+j% j.Z=¢q{<=|I"`oH3w@333ٷw@Yg'0!?—GVT 5k PRZSs-*uuu:fϞ͑ÇAxgmik[KlL>=,磬 ZM~~>NJl&?//zrq!_Evq,o `(cp"f  ]wŦMo}Y<,˅F7 o],_Ag͛N>lJdffRYY 8)(( ##>++6GE^G3o\ym$ ?xwD,RU:RlV+555t:hog\ 3v3GCng@VSTXȠá0hB\__+6?Yv;]wQ%''ߏb9s&ZۋJ"//gR^^NFF999b6Cjӧ|᭍2̝;}x۰B 300 TfR]U  {..\o:;:[#dX,جVRT*̞="`٘>c:*222謩s`0/}#G~nV N%k[o}+_H=G3?O)}ZS5BE{ݍJpB<& sΎY׬.kyٹsgx Wjљ>Ϗ>ln޼?٬[%Kp1jΚ5 LyE)-O>|$ɬجb,Fᡇ3`b"@'o6Nq̙ETwQZ[gw}#h4Vޕ|V5VgFc*pN'; hBL&3yx·`fM f fΜIff&BxL1MϘA{[{aݺuB! Rzzz5{O͟Ͼ}>!\8𱭩#?# G#uqiZ7+Mp7ܕd:(28 I2RFR^o`Џ;l*g;'ロxkٰ$;JWW?yJQ5vۭ?_VkG>㦹_#_E4MF9NYɦn ZZZpĩ:cP(DSSk׾n @ffwq' _ӟ~+*믿{?ƳZ[f9j֮]RGKT2$1Z`#Ǐzr{g_Uq/&&EHIJ*lŽZi`[>jӶn V "(PHHa {! Inr8瞻Bs~393[FlXL&Y֊M5ە/fN4fm&$6Tj23nZƍݻ2$oo-F2eT4ۤI>|ӂ1ܲe SN#""#JiRS2k,j ˗/JL]]-=*eޥ5k*t@[[;.\Pns:V {2j(n^{'IQ 2gg[p<>>>XV"66eܨ\&*fL&5 ҂ТHAp;:7~Y4Y^4QI'FJ8>*?GQy-;5A Ke?b 7h& L$=hA#x!bh2^"%%/h&[Wp M%7xs>D`a*VJ__SƄ]}p$7+j$ Vpxj)Sx[>j^ިτ KF-&AͶy~j6/޴İ? cІd0 8m0P:ݤw) |V+&w,PtD]wtttjQttt ԬV+ ɡAI]Fa7%zmUSIDAT(J@",,TT*:gϞ%44j^BuM ^#C@?---Q]]ގj%!>C$'FYY3d)J|\EEEr/& %%455Q[[N RvѣN 5jEENŖHpPKKINN$8rsDYiGV%}γ]~:}H+*)/m3(*u3f M#>R(Sh^{}MXX'O7:Wb*Oe{;FȒŋx Ŕڵk={6޳]v"aa+-%--B""[kl)++S\ fRRSS9}}VdgSr 11]I$ m#N̙3>|-}bb"ee!$48m <|qػwHNIT@]]iiiݷ}!>B`'Ldl3VB 豣tSTq$py1 \jiz{#!Dİ~G  /(`Сǡ bbb$rss=z4XTTJ1"4hعG@?lJJJƦ{_h IHHtHcJÁK{0d|}}0 blmjuk1iXC۟ dPbVjD40dYG198wq  $VC3.\o߾4]t󣹹:NTr/dȑh4j(BkLBJ4_R\\̨Qxyy))_FMtt'OHmm<EY~=qqqxyiɾ?*^uMP $XDTʷvgmfNI %$m;;;)ojdK27*Z-C3466Q耤 "IHqN:Ɍ39z(!!|Ki/޽jeĈ.uUjŬаmZ-`Kj%GP͖-I} {Y6*6HEE|Z08q"ر$Cv@ҕH 88ooo-) ȇZfΘAjjmKNpz6;q5W JoP`e?"7h&#TƺSXE KhBRihmhć`OF.Ƙ a^1x :.*f h"i4 b,xE䒹ɍ\ES% 'pfNQz_V#j&s-拼t4\Ǚ ]YEF,MMbb8SV'MI"sM MwB<"BWzk>>tdea6ٽdKN;E="rg HgƱjJ;S5(kn֮]KxD?O#=n8(bPFÇޯ"ѣy0 :Ԕ4 {DF /@p]ݲ;߿?Geɒ%xyy1u4I"+hll$9%Yf-Gflذ%uqc+`69wyX,ƌKTT̳ϢR6l(Z,JK…ragΜɟ>\`t:KD8r/}Ƌ)'h9r$+Vd֭$$$8]\YO6}B``3>L,I&0L:$LFt lG}sީOT*ƏϻヒDVv6)$/= >&^^^,tLL ?v4QXuզ\!>!;wx""i%]}mފ>Zy>y$V\bA^'//UާLbB;w$44@6mDm]-$;+˥|k.֮YCD>d\l0dH&,|i""i1 Ell~6o1؋Nc۶m#&:G)3fdhmk%9)s` ifСC|v|t:~_eOh*\~h8ak֬8~Mߴ3gad W۶}wXG1~<6ls=Z&/7M'4h<3zYAd鼷=>۲43g@hll`"ZEfΜW ]c*=NE}{Y=:-3K><<%K}())aG>}:k׭cUD3c /[9y/p|||l2$3ښv8D~p6lȑ#:u ^;Zoo&O 9N/_MFa„ ttvFrR0vDQ]߲fDF2G$U\,pmDnͩ(؂ٓI:hhjsZۙ8q"_|%}II(vI{{;jqo3`hzR Ndd$ƍܹ2d8qǏ'KWo29~8:PTK.HLLl۶mXDvMZZ;vPZ-MMMMvv6yy IQQ/b23gVJƔ͆qLjYt),\/غu+G&;;qaO' H>Oy%h9o<ƩNyu|Ykדm|ZWD.dZ<@䙓S, hUS[.*PkzS9 y΋/(hI?d wp9\I.gupYU{;| u[DKK [P&E`>oFmfIx!|nbxq*/_A64GQ%˒nٳйؼ3j5&qGX`umj|ͮy_=?pv) "A>h4o_b(u=yފJge5͓iGOkV.$Pxkď)z5jcQ9r45F~ϏNwSUQəhjdfeC'( lQK/ojjO6qP 999L6 ^Oss3|ObᔗWO "K,o'77}ɇ撯_ (WI6vX,[̛7v֭[ILL`컜_YljfϞ͐!C())`0p뭷RZZڵk1̚5Lظq#}dmaaa ݢ"F|eܹg}_MBB?0zL4 &PYYLYY:u `LONPPjVqJ"t>ض+eC8~8^zent:oѣ3N͖rzٵ'7G3ntUz =n kyyyVkǡ_s\G[]MBn/_L2vU*5hUƮbAX- DV}Vhj6cZDcw*c[SOnGYW<}衇\ MmL݄\=ZGbSY2vJcXذa{C^)9[ǼW*/ϓS^^ȏꢄϵLfZXE+cƌy~3VȒn 1c͛7+)L:xx=yy+o8PT`v^>|4WVc r|rjjk>>ɩӳ7n,zxh4RQQɣtݦgǯ;(+44Q-ķϟG47P$u>>33QaSA@/0 Ȑ߳~HLL?<цPVRE?!(ʙ]Jbbb@FDH;:T8'v\]]wu{$C]]-z"""{=w޹s9DEG;*t7W;I#00JDDc=qpmw[jhkm%8$DĴEJ9 h4LZY%@'xq%N!<, Ʈj^Wwit:loYScw.}^-='gϞˋ>})x3@V**]| I'R~Y{Ƹ8||ֻw[w3ډWUNc_Zݮqb= w^.kX.*J_(v 8_jm\ FvIOfFж4Yxܶ{u?}>Hيy jOq`5_s5H?åVs8mhKF]K /9]I윛}JEjjnGpRzrٗ8q11c'¢<sckru\QRa5TVFŦO1glw闔Dĉh#"PTnbԱAC=wGbbUUU9}añLX}^D>vQZBdWܡBp-]j  kSWk׃Vw< -.:y ?sxu\ 7W͂tr"m rM]?pT΃EъZq`zu^}ιwr=votb'h:?u])8nrtt\߽ZDEE9x>QjVS6JDd!EBh+ />

documentation:liferay_5.png

liferay_5.png

liferay_5.png

Date:
2016/07/19 12:15
Filename:
liferay_5.png
Format:
PNG
Size:
97KB
Width:
1239
Height:
574


Back to documentation:1.9:applications:liferay

liferay_6.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000002751311325274564300420370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{z IDATx]|=+I@pmq/kB8 ^IVP܊ԐʿX'~sr{<ξ;;33zNO cf222>д04M32GT\pr7Mdp1#r;wHm+{XUK0bP [hjĖ2'C%,t V\jJ 0[FAE2lh k4(#JFX 6@l'a)dnd#ΚJT=TMP{-YNK͑UCNIYRtŗ(ydf- )%`6S\3 cpFcodP;L&Si S' bU^aQbSlhg61KMPbnPU X:rc2r R>ht:?ʅx a>?_()zFI '# ] e0ݠG H4$ۍDʅ0xUpT|YP- 5R\\*b>)ԥڞE@x 5qNXew㔗)#U[֬Y5 B&$RY!Ze=y4MI7uV?V"%Rf)ѧ$ #i.)RJ"fC)(0RzL&HjcظA` /^XG!^rESSS % xO!~bl2 0RuMEh&###grtɓ'1/_&&'x{˗/$$#gr' IM),/ a6RXK`c`e/^A P1L^$(2|;HrQ#wDuƮ0{JO# %p Ud*ddfzzyZm0݋ XwRRb+W *Q$Pm@@ |x=iR\" -r\FPJʣB'H #JJC˸1I),feg9^Wk4|c zU@f%,JT3/ M4(lx(JNaj(Enjd|) [ B{ET`sB{1\ h^> )ؖځ2f"tFCЖ5zBe 4Dta X̔{ogd ;8*2Met~p^‘ce *IQ|ܘVTӓJEKIIy;wJ(RrGd:9GIC!YP?rj thԫ`0&9C-;7lI*6.#L*<"/F”dE&]mHb-Jͤ<6Så,A &h+^=Nh&H Yb)v0v-Y#FtPdZ(\E xeWUH-2p^tý:RмY< L/^ ɛ7Ȑ>]VXؘ萐w"Zbpv;]bNZx˗/ Uu.Ђ˅CȅCIT~nZ ASxP"SNT E{~+U/uS32M~mJ H5C)'Bbb_rR NKiMFSRR2qqsfϺxbZZWpp{*OI\9ը4,Y2V6m̚5FN2СCT02!lj;+BYj>A!lηC=Sݎ.AY!ˠ2p=U'Rb1KD t=RA02iF\vz 6p\bpA!Puf&# P\lbI JzoVfG!_#Phy6&<#ȵ|(Ib@V 'ԙ)| 1KΆP$fdPNL_p (Cr ǃ؋+> @7-{_#HUNAN$STٽkwΝ *^z{:%KQ }}bQEnyƥ&zrJό_T^Q[1N)1qKŷl6}|^^I@^4O:˗/S{_NYy dIbcd>ǎ8``~ 8fNqYYa2SF3cf@r46" &6ML11 4YS i4[nh0n$kHm 7_=I=TiZ%x¢MI^a%U<`H+H b1bD\R"WJSB+V4%ı[ /JXvE_~ EQXxp~;ZaGŢ0) -ޅKA.Ϸ J8E%vl5H.pO-5f95FdvA׀E+ p7;82+ЙF,Sǘx1FbL(5ФVX<2(eCV2)V #5-jm|yKB zQo޼t6D2vXhfiѠ?1zvr&IwƦ+\e( j0:VJ(|d Q1 y"8+W?^,Oam/^Dvҥwo@χ`pQ#fh+6qҤkř;wcGAMC˗/EFDܾshRZ{Se۪rED۷m[h!(Qʃ_ر_|^}?h2 ~UrphѢcƎϗ/JeXhʣ׌e I}]w}W)ٳ3u >w/BE'>_Gc)Жu0 p?ʃm䤤}{vi5tHc#r<M]Bo }r-q0fsao?z( 00 PDJ,4ݠ \@hyT'?^+zdAz.cbԏJHOu). NA0ѝyBa2ku:]ժ_6m4i˗ٳ? F/^y+Rn:txK^;g:9|̘ظ+W֩dQ:|P+|l͛(+rڵnP˗_=7G~paĈ&q͚5s(GBeͫP )8pvsPSnil!grȖo\b*ʉ8 2o^޿Wӄ,بi3'O$޷mA(BWyǷ m (H}Xuݺ㞗՘|JpRy%X( @K}||K CSҧw]:w^Hڵk v:n].X bұƃw׬Y .y{y%%'?}$8F%o۶mРA7mD/9>}˖)c+^/ݹ}U(&v,P̌UQM(XgPj4%/}ٺ`[Æ ٺe i߾{ia+ 145P(T޼r y!!nڣG[~P` ҖQxa.5 2aջsq1 oWP;ꝣN3-̔A\Lޏ.B12A2 ܟA0!>ٌp##&Vq-7#Cbč Seid\NO e [a$S{> 68t0>RdF;y*InfF%8>TX|D_ѫ9,>yk*#.‘j_%/]Amb矧uN{!y u^5d*@w_֯@-zwVe@ԇG]n+v=4ZrT[P=))CACӽ}|NOeqFϯM֣Ga/uu 6psu\J]拖_PǨ^~sMB Ю]K^Pr1FO¡- Ec4^l^V-E j֪bРf#[m ҧORff&8rBe+Wys礤X"00p9z1K:⫳ (d  SK|* 乴A}{SȗPC19.pk hBW%l3hY1![|50a`aR6H0\3Bjh.CJb ے$4Ĥlj"Xe#ƃ\8ZX;B h\L n8P^N?+`VUNp8d)F8``D Dp|̅4]i6_'"F(0.481Z~ĭbHoR PXvf8H=2%kJF# x Ƀ  nSA%SL+hRey[96Mhs?˖Wܥ'3ڂ^&CR5cRtitm:a@0j.H=+SRP/XO>9x\˗OK-*CznY].\,gҼrC ' }xo S(+$hZ%q c .wP"'Pw^tas5[(ly m;>;I zUTℑ"0<~>`y 3qp~r^ !6ELܰ%y,0mۨ,Vk4a)%e<ѤJ!2dRHy<.ZZ AT`"^*>{" &q(lǺ9--5wt: yߕ빶*'gى-NaNsR 7\hhtL{a{F+aX2SJ&#?K6KRKvOQJBbދBƒK L5jW}||A PӧjB-+ '0Bnyz?\ys?yZeR0F֭~T/^ĿySTi???%>cz0@>׭|IӦZ"6.n9cNZ_x? (}5 EQS(#\e`DD%lW@%rb{WBrY|ɡ-9'(ٽ?כkײϤbJ.9Z )zI¤T]gW$&/\jrQFJL8RXo4BQZ:*,-^*VJ(rk/INK)<(W)'*òP%CWcYD~OL [1YO]9T&9g_WHݜR 8Ν;h6LzV/^R*^@QClӁ_`{ i>M#s0R("0BOBFOa B-qKJIĵӁWp!V$kI:tX q#3‚G=xeEB/|`}ʦ%fQTNTQEݰ*X~&b+ ZApˀL`II  p#&BԍmFET '㉀}SxP"SNT `Dhإ˝Uopk׌ X'.Un 4y|I5kfddVruuhF!=-Y6[wӠ<- y#^pnq 3:HG>%cj .[8 /'3fZ caGR'Fn ABgaf_^*x-HFnEm5Z[_rO[~saǑZ"7c8*oLp&)UFfAr܏Cpɚm%1'ZT(6aGQzc$B ˑ|҆eΐ8L%pQI\L-8)Y뎳Z 8Hf hވƹ770ȣE+[.doŒ0KC@4%4T%)(@qJAP ` 45y;]:/" CAL&KBe{<|p UdYʰi?RJ40Lxxx˯* TIMIjZd4fffq >$<wh)!ɠ6?u`|FȽKb.ʹ쉤Bq۬(;eӓ=1~P6%w4,#T >VCa蕚st]h'( G9&Lc"؇ f~k0+ mo*ЅB=uۍ64Ơ =#OwDr7 |_P!bڡ3#(mK_,_syv 2RŢŖ ZRSpmW U kTRSx[e -"#c4lThRlsW* ,>7X]sű#x3 - \EcuqUmjZc,x]JG=xtM@2A7.rQ)3H9]0gd:A|QI!áM{hAhnETG:7~!)O!*!f%(&0Ic[4Fxsr\&hK?, V>#d8Dj)ZBko+n+h*P=.lƁ3HªaF0$F&bh]`UF%nBd05,9O1 ~Җ*䌖K0"5"u''/#2x4+Vbm#0; Z{"FzP@Mw8*2Mex*Ċ)!M D ̈#S uRbmx8 ap*$ŶahT shADsټ^WS*e HFQBϢF"TR1',ʉAK!~=Nu|#A5OiDa.k679KTRm0g[, xg?nxkFC } xTOd򄓔Z(P·iL؞ 8D"5(6[!)nlXQ; 傫?Ċl 7[# ˆ^¢ Gb8M&&D Ct0Ea%8dޭ@* K7;nc߅A1 %_Mt 34,?5Fhi&(dzAZ"kT. 2KT9щ1fTI"Ce'EٓT$i_عTLHf[V)4%U#;sT=^ءi2M@@@ [WDT),ި(4ɳ+,Ӕ(yT KNJSeZ2o/A VVˮr2NZxw NI@ةcd % 9|h,ѣGC ۾mϻօǃhh"*?4tٲɓ& J@@@@@@@@@@@HIMMLJ;gժW巔"#"ޕbjp%K&&& 9,""!!Do ٓF@@@@@@@Z|F>Hoߺe63իWSW- z ycoχ4JS)Ǽ̶mۂ`ʨիV-[VZņWޘr ɓ'-p°ߋjz8w܊+?~RpaCխ[WY>g3/]PP77O/^Z ɓ' +P'˚V;k]VbE62..XFޭYZk%|u;T}g͚=gʕ+S&/Y|Sx.?NG@@@@@@@VQ\w,X=.%J(ߛph4._Fo>ݺuc~occc/1cҥˠwLUW M4;6Ņ8ahѣ_j0ԑXjY0gO/^z%TF3fsG`nݺ._BFh .D.|qPPP>}~ , &Vő˕WRҪժPL,2Ɔ?`Xv풥KgϚe1Iǎ3fDjĉ6mڄ ׯ K *)%ԱQZ4oz׺ufΜo%9c%\={@zZ@JTr1!]z5xZ^+V(ȫM$;#* WM9_r!n߹n4Hi4קj˔.-,T4ck֬ɗ7/5 Ə7ztq,CE\5l]@nȑYW?Ӷ];$&%߯MNJt4 Tng&" K<hZ%Y/"իn`,/[!Ʈtԉ՚ft6((HVv5| fÒz 7~ ^R.]=zUVl՗Jꐩ^b[oQf`00H6skD=_'-Za{zz3~D,/6b!aah1642k|>^qsVcPS} NLLd@bBwvT)*rVD@@@@@@@ޡLҙ SPN5ժU[u`Y(%?{Vpa  ܲys^J[EP/:PH?\yo|?O(?p)uV "H!S0~uLMMߠ2z"`AK|H?^l)hی3O Z^ayy'V͚ 'rKjc *7O@J65է+;wukJ,WTQvtFjʱ 0LtΝ" . ٳ" RN Ղ NʾU@fϙ36<<p6m4o+#F͖.Y:m[ѣGED?ڴi ?}4^xqfM'h1yl欙'Nеk{25>Q`<(e 'ۧPB$nG-/WťJںm[,YXGY>gf%4o\Y%GdGϞdžW\իQ/Z\j tFjʱ wի޽{Bj5ݿ\7nTZU^'GFF~ ۧʻzAbcc)ҧOk"^̙EV=ztpvWnݻu떚) [pA6m@Iƃ ξG 3iղEK԰PL=uʔ%KԽ{SNmٲ%0Hס6jD"EكU)'Ϙ9{ Ay̏?LI,3jWRCNiii74hJ6SJ*N4iᢅ?.\ɓ7iJ-=!(WM9_r-f&==C2 UZbE޸qjY1:n 4M޽݌! V=quu:e*SQwڅTZmXRaL ߥsg66kL>$}He! >ʖ), 3 %x]k2D>@@@@@@@@@ AX(A΁Pa9B r % 9J]Y\ gA%PY % ad0zVK9J@APg"114 8NHHH{nŋ{2F13Ӑ?~??Z||...:N5;*,1 %p>|TP!__w 4=z\XQ///4d2%'Ph4$%%IӳT27ojjn! %\BG:oo0flcǗShM1Nssu-PG%f f7~p$HYfL˘ҥK{hl QgEA?KTCjj*?ʀy(h4U&iΗ?~~~jna%w7W*.T9% f]$ӢVӝ,fl2KбsqSN-XD*UT ЭMC?4Ȥy.nСLHHW3gXI7o޻s|Կ?M\Y8,'Nw5OXe'Ŀ|PO+[-3cn c6ӖcSP9H@@@@@@@˫WHٜXB$|㴴wn{hUgzgħ"H,nPGڪ^U{ ;AҢ :bBhŸLc۪aB!0d4.t@WJ~>qM'3m*^OHp @Avu<…s ݳݽ.v^>=$ū8;qb=! ݻAh޼Ch7o/|iCᑘOs ƴT*_AL: E\˗ Π->*8<‹hCn^' x/C(R+R<lC,c6gdd{Ɋw{A ߌXpJ\.紳(F@^狗 ^WZ=8XRܳu&)sΎ.:M\]6(PꟺxI}Eu_p? 9[%S۷钒m/ hiFYuƾMNK,ZP>KAu0ܹӫV}ګy>9u7sfݺGQ&G۽%˄2j];Mf}5GYN.%S5tj&E\5nz˨at:73S<,N@@@@@@@ի ,X]# -o&sR!`hhow+_.~|?/]ZД?PPO2{Y_w,sq"*3)FcfJ\֒ĸ[.%j5sLֺޯfN" 8uԿoi6==r{pys\b%wwwp+()5I=TPڎWӻ/+V0 ˲eu~Zan`RUd:r份{K+޻Obw;vVJ5BjKO7a0M3c Fp]   flw &pJ pT|ֱpgM-9b.40&ںyKSr! p._LNNҹNJ׮?|mh*SwPvo2,hb6|?q][LNKg1d,$&; ~'_P͗o;]g?|̫aKG߿IAyӳz}@](/ _wxY@Asgӏ_wW>ѣ=Mۿuk#=v_GRM3u,w{~Ta2 21b޼5j 3Wٜ?ۇW-S@[v{U%=%LbƬ=)A۷ϟ^¹ żqc/^µjZ`a%(k9s֭kɬީ{W u}/~hNQ}o)lV" `9s:2r_ѰaÙ3g* ~;6<%%%<|R*?mڴoZf!BӡPِr۠^'jK ;MF# Kf/ݽ{(O?f o)x={b&~&}~]@ Ъq@j_0ޞ⻲&&&޽}͛f׷XD$>l\~E/b+4͏bS psi_FM\/,1kܯYƯhOz&fF~\]$eK7233vQBK_uэQ;OvjO֣CBێ 'h:fͺ݂JNN^hd2u}z˖oWE5,KO$'gbb<~zV(T)'3hSyX cV58McǝZY谳K\رc۷?o_ѣmGc#G93Wmyv]{֬ZeـIʬ\>n„/c?0&veMt__4!nch]un.t,dm}|E7UQ}A8ۅ p^=o޽{,SD{8^n]R:4`@7nX Xʕ+{]Po} b˖EԮ]{yQ)kEQFNfr>oN$Ϲ^xz+Y@57_W)dt|MS{,48q)ݔ;}d; 饗xI/],S棊+%&$=%w{򍗧׋YKNe߻r;vi/:95C7tzD=H˯=j0&>Acn>}ߏ͇Gt"ba=>ke[lC6_Њz[9KBnhP5kDȁ,mժնSJ}'L<QwL`vw~מ=)5aD*(ɓ'ٺc'HsjG@4eǰ? J ;ܽ ݲesdd$eʔ]rEXߑ=G,vʆMҶmŋHӧL*?~AΟ?ڞ`9sƞ={mуGN8s^QQĻQ̙3ӧO~G 6q1Qܽ{gĈl1<(;{I&D@ʣ&JOO=z^^^1c4YW^qrE[V52(7j+@NsV!eԟ;w.pSR[h>OoASD&LjUPoen݊ZW^ӠA,.'/WݒgO>}[RRU#33P_ 0嚲0(%ٰ\[BBBŊvkݶl˶mY uʔR_|q32K͙]5lN&#O8rkr(nV-v٫h;JM6X^wMKwwQFC2h*n֩iUKiJɄ$T{ ϭ#G>}}B&@`Ȑ! {wXbΜ9/.] j?,l0x`/\<OOEl :`\`>k֬[\@x%͚5]xpDO5k| k4mڔV\ WT fM5P211aÆer察9 Ph 54kI+dd8Em9ZdcիM|||&Le!{W)Ϟ=[Aۇt}[Rs kȐPկo\}ժUA?\ тڳ\`[Wϟ?JϜ9 nD1AUŲf *K}vJ ,bUdly EP朗cEɛ7ϟ={0;U`Aӌ`g){#J*;r F&Jcl_rܾ^g%\7 e~g@Ƶ/!!N|;:I^C`p'S2LU Qﲈb64sczJr^^^^"Ji4fMf`4ge%i1eSn_p%m7kq}^sdt37poO\L^g6KݼP I"!!~.Ns_*TlٲgvZg-Py(:^04dFeTe] =3mbőGBǐ/~ܞiǘхG>ҠnHĊZ4\r-յgh%$Ur@PW޾(Π  Ocӌ/Q5ۏ_uP)…KYXpi.̮W_?N>ݠAv C߿#l"I a ٳ~377ɓ@nAr|x!!!v;g/hFHY?=///va? 'fϞ}$;fffZC4BF*O6Ֆ\UH*c{gK7ua-PǎѩS#Άnj^u4w9yVжk׮SL#}[R} 8еj^e%@1nX* 믿;m)n߾ . JwAi4М;ZĕM{LfPU3gTl*V~:ր_8it`S(\z'M+ @e>|XHGoQ`VؘOdzk%(2 = l]Ì~^Vmv]_;6_D?IvyļċqALϟ%,=B|qq8hbx}=.ѯS0/_2YK> >~Ծ+its7:hZջZ& :^ZݧD?ǧ n9w4Ytx7ߧ鬵' j9mC|ߔm.ߎ۾t?P8m%z4jDUۆh<>qc(})>}d1{/k]N\T{`pl4}}GFڥxnu;KA-+_}upN@AwHԚV\ul☦MT}i#[VlԈ5v x[Fmc3..nhmt#e{[6r_Ikm7欻 n4USpaaèzɦMƆAa̙3]~[OHD.-[]x… }ΝӬYsqhq ܹA)6x߾ ?z{[pۘ򨉀=CB aCUb)Mf-i|T6l-BT,::OP)0ԇ&0 Ss4w9yV($KB-hpޤk:Ô|XjJ, lбc]v:taϲPG . Jw"EVZȑm۶;ٳ%$lc:5ʪ)֭[Ϛ5Dh/^;vΝ;YI"uwEC| g7HHHDyy~E{2\ ki>q$fxyےMf[UڬĊS<[^|u+f'䢙ܷP ]ʆɯwT=;~ߧ^N.E<}8Y]V\=kڙu盳LJO (j0HM&03LFpWr}le6v:Llі&k:oéX}lӻ?|@V~aÇ;w^)C|^'J1 JQo?}tM̃.EhgSsw׺Y2ǧ2baCʭZHA a*4 w" h!/\P~Wn.wNkSEګ4 z{w|b ōJBa|ؾˆ)'o8qŊ'O1j0Ξ=hb,y|<)EY>}9sVF N: {__Oj97Iiz5ilظ˫reYQRms?o}Tk+TxtZJiuYmUX8~@~gӼ}co~Ώd8wiZbAx%Kz;ZN8:SRmnO=B}(h5ӎMF}׹ѕP̭y]}]h zI3];GCvkzj$GCnܲ|KZ(l+V޼iA.Gw _b "MDjM΃FcوkL@_by3\]fà_GW __{,YR~lir˞I&o߁@%hE۶m'Nb|vl_ҥ"#N,p}ro̘1Q~ZJeʔ0իW}ܹܹs6oBY? yD@)S,[Ѣ+Wnʕŏ4i"zְ%/ĭEʐlo^z9ryܹsgٲoW]N^y+iIkPq0>kk1bd_~r5lsreV#GXd+x*yM6JBq-$*ETVM׃us r˚)B2 .ļ j?|ǎb#KtqbU+1 mɒ.]h h4osBJ ?7SBE? )rʕwwwONNJUQ RE;wmkܨizhz|έ/^(UdeE.9|DԱ.:?u)jry-e5T./w(5r>ahUpء}Ͼ%޻-gFuo{+),1m(Fca^ǺSk=LZ3LF)ixS5dcb~S> UV 4\t%<Ɗ5*U~޽[!OCaURCr,X(h&k,O7W_o6l)!Rzݻ6ꫯ dT+2?›o1jHg})$m_ևqss#ٶm[3wVLpgݺFZ׆ ^x?LGEEmSpCט3ߊޢ_~e塡!VVV$8EFeO^y-[_~jz6ڐ)fVfT˿O=DH &|y+L}QMiM-o6Z=[$(/_Lhh(iO<\t:.nҒ%_tv̭(?GW&oСCH~5jM-{7 pQ>?8g|ۺZ|u9TM-ֺIFj*[w%ljiͿi,̒0 ~W]\.iGΤde...IuG()>VZnd6kQW.]L?FfmR2˼¹#&ϥ>YGRq")삥U~4$IMK=y*5*2qvq0O?jzZ3fOyo2FH(p[>i5?p_7w'ܔ߂ZbƼJJ|4!V\W%{j+I<]K͞2-p ;F߇g},u?Yģ^k֓ f}|kҕ{899rrp I$dN]KQU z,C^"t_hnTC(eJ؆hmM*.Av6)T~f:Cr;\-כ%|rym޽HӺː1yUBvpwCp;dL툩̚5< vS΀ݛLWM--I$uZ}Ǘ+}4k!̽I-f ?5M}Mm_f6|/[ YrMnM-o6ZMP&$ Hv>ēيOsa#KKOfaV->?8}lΜFki^yKOb{+Z3waZO43a7 ~nzH#)TP\v4ߟ| ȯ,R}}})=1 s]h;;4).>rշ+kyu.ZъxdkkkkXNޢ mg3CB)mquUUNNZ׷yŤRۑ#/$?b,((P*3j݆u2fpWuV^go?oV7ǎ3+b3rx"mU[k,J} _έi*^Jej"<|Ug,dz&p2A<*GV9ҷ߿% ˣ{*5j-@2F' o}0G(|Jba)d-umF(÷^}?+V=lM߱贏:SEEźunmݖsŭtjkke2@ hKFG#s쐅髴 =gkL//@V_pť9?A/쉧 T*mm[zK~2Ϝi8[$?\1WwSǵWm~%,j$Z(Uk+$t 4\ʶNeYMm\If!#Gj^ GCpmc>_~QG~7KJ}lZv_. E)(Tt]E7oȡjݿqjL9rFQR\[yRĚc-j=wLLt]]ݤI_z}N Gi`=BG|'{{MvTN5y{u䭃fϋ)M"xza*Jʢo-~v6wŦ$Tjgnt*Dzu֒y}rS(#<8`7|i]THb ;j}ZZ yoMAi< eY#RZGG?qO|p+6ABGWu;W=kw[1u??t#(qP/]ssL/p閧%nt0qo`i!ybg} ;7&]MȸQ^p-^r4yxy4sL9NEP Rwg#a ++JK\مU*Uͼk9OYOJx<^Nj Kr@@|2mUB]!tC)tﺤ"/#N&\\.@^r쒲h5zS ytq ͜7s6S t={TVVP!W$=N  $K2UP$K.:8Ŏ07'$CI6aQN@|s#-tGgBYBeENΎ7 yͿ)sqs5Jʺek'efO$y^I;ƤtWwcLcWL?s.dp&z.//&4,*̯͜))=Ъ45 YfE_hq% dQL!IkShUKCo`Mo/%4xd^d I p8Ҙ$)rqw!{$;qj)42x1Ywtϟ]2?vPB{M]m]UeUMU Shh_YYUWWWR\⨷ sbyP$) DIFeLӻY$Rg]?0mv9~aѡ3OwqwV)U2oa3 [~'~tH=Cs{'NԿ')aVVgoW喝7l[yYTjs-ZVFI! ^BP.`(Fy^v?ҧ=,)*s4MPؠ{+^̜pL7K5>#~75JH$|V嬍kh7eV܏e׭'S*ɫCFŐZz #9:;jGgƅDb#rlaPhP‘ g/4|=Bƒ m61ãˆ)O&'1Wpq)3%CBg7g>U+>.).ef7XVzlo}VW[G&%?8./O-tBYAY,Low(ʙ󧷢Fns{';QVR&bP³'y04"'"i6;#S$;Bou'v|#fZ肏D[v ۷2#442E.O?}pϑƳLj՛5}HmwgݧSoA}{aJ㯳3k%;A_z>}SSήCǑBZQD_h8!jHT+IN8T*#DKXnҾO%;HqSlf'<߿5硹$J:CRh+xyndb]H f/T tCwb5X,"uQ!3h{%Cۚc̳sp>v1%BQSݚjkč" 痾VmݫUsfUEV|N]k37_pprXb_5 @p8ɃҞLs/B5#ԵnC|rjZ#+3I>޺6.P<zOV\\YQ9ʊ>.3.nqp\χ޸kL qwoڟBR(tAIN_͜3DEE;!/ߗY&0lС=G~f#7o-pI 㧭KW]REy%y),*?I Iwťx|!36gk'}؟r PTKCM1ɥ/ܖ_v^9c޴̫Lk?et-}'nt |G3ʳgUWVHcGM;Rh竨Stu{g}i´qajV*W+1e$XjdhzgB;ؘnPO<\xɇdžT.ԧ_n@+ 8Xuu:I|EC@ :{R6 ٌ + E}s 7x^;{ 'JD"3Ov~wܡgC TnUTZYÕ@0޻ȣpA lHnܔXZDh*2xu\H ):ZIiy }}<ŧx{v\HMVwuy/'t:s3r]eO-v6]U+Nֺ65]G0gNv1.S}t#mܲ5:&En38ي m|6q}ŵl<` fϯ[3Yx7_|n 1jԺ>3/?OW'L0ˬr-yb݆IS:p`ɢ__BS *]9fPd§}^#J5˚U>n„^=eO 3vߵVt.{3??)nJ۴e䓮n9k>Rhg{f]siJV̓RO'h%B @S((rj )ڇZr tH=m4V"8s3H␑CW 8Z&ZRjKjŧDFQQ:JģF^ek(WUJ䔙rP.uJ-(*JFw7kM& $_&%E%66R[ԚL;tZRPy)̹6Rd:<*Yqǒp~&="6B"YQXd(;kai5$N;F13f[r8.PL(TxHs( YPB§ĥ[.R6C I*+<[>0h#T̬̞={H˕k;s&W3fL6I| ۱*J!#kLFBmUBC[YZIթqJGR[~%aVehО EB\!˓;,zhߡA"3,[~,;X;YF?D$ EPHIKŜR4P(4HS 0 2۶m#ϳfڦŦPnCv-c]x1**o߿?S믿[+W뮻tWxPVHHaz"曥sϺu˗.]RHytttUm޼yŊ{wfϞmmt!kM'UVGiA]5ILJEIX OY˂r%[xs)1H%LH=CzjH$b.$ 3)g`i#ѕ;ؑiKNNyy|)cW*$gPМj!Weǎkv]vi+Ԋ&MTQAw;vG9|0?@zz:y1b'N<淲iӦ%K0}d;y$S~ԩz&kڻwyDz…s޽{Am=`%wL_h*S)hC%<*>)QD'R6V+tJ^RU},r%Ά;XddD 88?^Ixq&zwI9g}dwyG7 K-;((*;k- 2]I8%D#o䆿t\{-I|=^ZPGDI* _P B{'KILJ:e\_h#.68:| 1zu;EFID1/!79ᄓS̰h4Qwl9Y`f矔t\Kj#u2se&H,++ $6662LU=I_kQ% AF,W\Uu.^Ij|N8{TU+B)m -Q4z5ujH RYUY1vhXD'Fi9b($(ѣ6FE vä)S%3gdRᄏaÆEz{{k=g}㏗3q}?ի$?&f痔[UUUgG=zʕ+I&z͚5f"tƍdK.;vlmd?N܅C?% xgN ^mͧZg2R*ب=W;ݓ4NzF[XhF+*;#@aO^e}uMpBvz; KovͬeǴj9rjVmMGGGY {{<WU(¼,/Y;qܫ?TD!ӊOOk#;ToNNPB.`>Ka:Bo6yFPhqʴTlkAݍz!1 .7q ZCwJHM4hZHj#0,/R*sedW T_k*!2VWv] @ϣM87 Cg_@KusT`!22Qqu*\>'s_],JiQQ+e^}^_.^yt^m䰔$@]jʜ^cV=6sF&B``%-\*YKY v j:b\VS(~v9$BnqU}ERe)\4{7.W+)u&jV_@Ԋ,KRmM9:4B++;P LPBj.wzDP19'TDw" {F>rQ NHEA >eVղf-x\mdG9.%-/2q(0̏Ylu ;pbGxxyta{ o%s[-4*tC!opWݒ/ξ8 f%NJ2APJ:t^IhضȖ+6ʒO_PS";KUHU5tm*rSr:Z$duʯI{܆AȺ:э$ut(el6֒HHC3B{6HHR*VVT6g1$OLN_,B9}"6}j0XN:՞SUUODTΐdZ5H6@HV[ym jIjΩF;dZ&Lvj# v9Պt|cIGMR^/ʡn{‚GlD0cԍ)PѤ9& ")>PV3,F(ªʪÉuuQC#<ܨ}Sϑ鈘>RW'gZ EұBY)ZAaAW2kkj%to.S^Z,bǐg윉&>q\$]@Q3R M4UhFGߋDu #3k#s`vYH"БϚOk!t"Qf $6guTXL%˷=[(XB* :yƕ̊Ғ2ݵ++JK\ǥ4ethmjTj mܛ%J"K [J4Lh\bZs;Ɂ[[g4c[3AUƶNe1xޤ5H=[nN. "R$< n%ODMA*>g[K{;J% _?ǯϠA{w{4 CL7Mh߷&Ϙr'7@Owk)=-+tu+w=ז%qjc)P=Bzur \YNn-7 Zֵ1GZO-36R3J1v,OnjyyyxtUt'E5U|}} R4@io?OD(4$nqYJȣD:{K&hg=9OT!Ks|5h*U-6s^JJ4<ʟl.e.4cmA3F {P9~,M'aLapxpDB5$)!?$CѐBMh3Kc2fE/`d\10p-::;h47 MntB5$nݚa*.Sdy̝=IezIoЦ71{k'H֬#eí#u;gEbѨ# IhtWn ḬD?jNGa1CfлÊ*}<Ƀo@YINjaiahv 3j5jÌvמ*V쁧xB-6Y h `Ԋ A F-HN`{z0ǐuIMY$Btg7q'Llx{;D\-^Qc4 c S(ltqG}pPL_(tt~O0jv_ɾ40ƔpU=uzۛfeiIj۹omm;;:=E;x4['{cϾ Y`J޷ 9zL9f6,(pphwnlc{DJKʏ$t5UGM2;xi0/Ps6GmB)@7p.g7K<^xH;zK")ʙ~~'M{^6p%@݅ǏOqӤAweU踱ܹgN|<EQAfMᇟ"&r,\0h>8;9tYQ_/ Ik{c:9eIf---\]ӣ$5 gd]#K}a!:R\RJ$&ɔݺD,+(P*Ud:yh{S#;t+^E7_oBc"<2}F.ysb##H"͙>l?+lHtwu)(*&\IiYРAF<>nNBS h~ۙap(_/Zo66wM`ϾLFߥG/qX/6*??]$*z5qabH uU?t&L xR ێ]qOΎ1l%ʕ?4lf[?&+<=7vrL;r|L]+h@2++dd餓8~ťmߢ}LLןJ=kf?kFQIIo`Hۥ;9.N¢x~}]Ƹ8;Wiqi>k.6*b葛8vognJi/~>ަG i#6Zhma;{V=2e߉cFff_۸uF묬y㦶 e&!3S&}\Y9vhZ߯~wroXK,Z3 B}[ꘓW4qdvͮ@0:dzخ_?w]Oy''3/<5e{~%P3K?!$ׅLW$t֊i)TWР:J&]<~62Bnu㴅Z/^z9#͕<&uZJ6w#C+)- ..=.-w8=Ѱg7^LJ9#lO~>Kj2JU{Eoяnmќ=2e_oO?S__rzht:fe1=zwcd\?^.Os7y&qkL+_YÎߌ eW| wOAK:{7z떹 tFssrM"_&oC]\,̶K =UP*>Ld:0)嚇Sc%Pr&p?>>.1`wɛOO/w^Ϗ9vuvrrpwL%R_@'-zr3_ffy97s-^^)TVPXPT8+ ࠹emaf<=d'T*:p8v[d dha?nNaQ1{D2F[.su";=G?+?,sOMtS=G*2y+՜9KW^- 1) Э|p"Ph 'RbGxxy\I ";C z{,DPb2JW-;b/bzpAK8ry)6*Q# 'cϾꚘpDLR?,(*r$oK7k]|uт-HHN!o߻d$Ǎa楻&O$A$GIL 40$hϿonrݏ{GlLwL,_D>wSV|xoؒ Y ,{X$pujD ڴuɜO_TY[ҿgF28:<=CrDLKIL3q>q\$! HR80h`TRP$K.:8Mbš8E%L 5 CF' h J80qĎ}[<9?M qJ<Öޗ+jU}t54gɼJlg-]н ;,srק]tE?p$ST\rl-/_3[߶aF^cKr&Z}g&ek$<?eX;[iqi)f^y;ؽIxl& ۶GlLw,;Ɠ,u_}59n@@v%_~ڠJwٞe_l\<⪗ZS vͼIJj_w;n6B,eSfLΧ'˗8)$y^I;DtWwcLcI?s.dp&z. 1Ǔ6 CXHƍ~!/7_7e.n@=NZW?|! ,qlKVԫbyhS54ggD3s.i KHV/%$g ]K:.+,ڶcWn]wZZu.]0zΖWMbu];e#6j@[ȍ[dfޗg+ً;s}baL?bv̜|up8$o t{B{A|=147t*pxIf SxA24f0K#T^ յ׆șNT0d~/w$EkShSbx7yZ[^9nĮn܉LM'"T<\9fV!W$'ʊ\cEBSK.C#B6eptx>B^A KJN8S^V. Â|P윉&֙{hI6aQN@g1jQОJVggd̦J#qWtHS. ffeyi|~} ڻce$OJu=zisioЦ['^BV3.g::;2.n.׳rH:5S˼NãìϤYLNV IX.v/+U~U5}]}`jmpΣ_7v_Z6pRRxvj9ɩO"_!Ǐ%%'prqmA!!gO1vx[LOYLNҰ'Iy수)tں0+G^̸VxxʘV eK>Zw]4N8#b. 5$FٜBWgЫgO`Y72Ѿma~+=O;ȩϿ~faϘ0")g?~}ɚ+dʉ!3S!R+ybbyo}\~7Vv1K̂>ult[a(R(пgkOF_jW+xٻ 0-SQQ,Np[W]QjuNVU^UAD@ADPPeko&"~hzrr pG3E%|m sE䇙eynN]~)NJnZ,yE*/m`:5t҄n)޹Od宓;ܽPh0Om'󷟸f^=l :C~+7>r&lӂɭof_;RIqE3 XRE{Y@ÛHUVyA8u=&P}BCyEվAqWOH)IJlک( pVRRVtS1(.VTp,h~ r:o?FKw\VV^ -mظ}PWFzZi>lJw/{Lb׉b>ͯtЭ|mmVTʊ )1ѷLNNE~Clyv`+O>LЖ ufίx~u_ӣ]:-Ghƶ`wOy,R WT\Wx]E*|.;+%#d{Z'NUY{o @!PIBÌq>DS<b<ØQCBK:L/Ӎz?p":>$&>Kt_UU~CeUeł7E_g?9ӾnvHKo @[k@QkEl=|¢NF3' %a% .ܺu+;bŊiӦ ΕwaϞ=B 0a`G騜J.\۲ )ͻoWXXmdlءS^Eq{EEŤIɓ'Gݻ;wǭR.ٷoi J^Ű\//c^v> YTTtOKz!#;~ep2K0.3f̰u9WWEq˽yV~^|7{k3 S%%%ׯF>~ӣ+锪imor?M!UU,.zvlw{7nixOB{:DÇ[ tIgΜٻwoDD,ٓ/8Txx8ۗ9%؄:;;gÆ GJ RՁise'#6aӦMLZhذa?FfǯV~ј=ϦS 0ݒ%K(_zU__mP}|Yϲ;sreeo?Ƶ(ʄFiqqB)46|5Tvpgꋋ? +3+"߸Tw󶉙[^q1qlwq+)+ylSݠu{n7oݮYfΜ9ϧоP6R4<j/xLŋ)Y;vѣϟ_v{1|Pw_ΎlA%$$P 9rE n-kDό-MWT$jBk*/Ш EhGd4h}kQ>CR9,8ܠiWۮO^('/7}ɟPTT,--;$ DG[۫ d^?ژYMt@ ̞9 kr5n:`jj^w^ #j5>S2e\5-sE 0Dt:I#dcnυS|t0hu@s%1v 0VaaJJ: P P~~j;[ ^>LMNGaC K;Ilu-"AîLyRhn~}sL >뭯6aa .m& O~W3rUGx8wjmOSKé#E/_*((t`i<)2,Ī[ю69YOj|Ww S8X_y7>ڮ[)yph+aΣW4[I[_2`UW-8h|3wt4=3H-B[#ooڞ2SPPT[ `YNNˍ)Jڎ ޟg$~17;q'I>)j@1v;'s"_D.K6vvdt+],|7uuu{{^]s۸GRD+Q}'3s:F%G&}=gh_me05TWwZ#m 4(Ph.**rrRyVVA?` l6fJI 4 mJ|;3.{yRRǗz;B˓BJI-?!ҕx AOjW*iSKח6sYxǓ]o44CoyTU:whU4GEEuj5쪽h=DsiBgkߦG}2em[Sz? BN^P%}C ^ l:4彨M3z%LZ^mm- ''gfft_Bw=ٳ絟R{V?ci[n>|k@Pm}N~HJJJ/HNRuLw$rP|^(4'bdFMe;vرϽܔCI[[ku˖-]˲ؼyott/{=4vXq;0srrbbbK3gΠ4_3gDw֍СCSNMaeee]V\ѣGO ӟOЀ5[l:d.͆t=hp!T ~XN~~~GϏ92x`*؞I Ǐ7RBffܹs/^㓐0bpJtAJ|>РAT={U.Zۻ#G׭[4!gϞ… ?p*-… ɓ')SB9w7Dk$f[j'G?yHF͛7=s欛[ ?OTUU/_NS6 ~ټ->1twľ&?/^=S3Y9ٲҲ;Io^iE cbyyyꃴahAw^ڵoyj`d@ښl -))~5iS=]3g<N 55g7oܢ ´jkTiemKΉDo K;w.zt]\Mz8H7niO"^-;2#.F>x{BŒjL C^pz 󣢪.d``pIEk255;}P$4gAde7BMՉZm]rU)[wlSٞ*);[t4$Zϐ*o\g`x}C}w/8vHKNb݅gdl N=MJzUyحO/Ž̹~hnjyѣ6lefgu/z!ڈ}Vd))gs'"QlLutdS3ԣz%g%{,Eh8o?+.)S$L$=59L,;';E2!.A¤PVř$юХ[{)͍?î]%M h+q˫u%zTo=̪Sʿo:⃏>R=THZ}B[U7|u>[_\\|dQ~]\F]I視!8~˼yEEEU**)JHsnEEEvfv.eݕۡ&0P\Qp~]И$[yk߿yk)AmWKaZ.??Tbd;)PƾVHK[+1>QS[S0 {_(*(*PN+//OOMʰp]m?ymwy %O$곁S^A"(L%礂KKK)RQTV潔STD5ZZ}P :"xPJۻ/} Gdw!"*f}{T] -9Cw%O$"^ۏU3zedbAT¤B0塅Eҝ)Ϲ~hVե+_-k^'++ñ{WAQ ْj8Ҭ`c+$[h܈aB'տRz'rr6laZ>xo1ÇpEŲO7}>'`c}/?'#EP^=6RIf"GVhPc&2<2')_]GED_rޮ*{8EEXuŽimgr.]#cM$9/TFFFS[b*F\~CGOE¤l_ lm׍\?4+׬ǩGO6֪*ʧ/^~$+P_S Ij=:6 vtݶj/O1vdɶqRUEEhVN퉿Ć̟7l޺kuGeq]W2UuCJtMjC_}xH- sʥS`kS[urV7mO_Ly>=+++Ӓ/9'Z(ϣ6 ''Ɣ;KX?45-`vՇϋۮ]J#$tg2oCyU3>]TXT$exoٳ ۺs),C٨. $7giji:tl@xt嫪mgiii]۵ğ~DwzN?Ʈ[=jf^KSm_\R /\RZ9w\z8=|$\VPw?٭,=3I[jvZѱ)N;ShDTL] a_(ԟK/^ԇMec7oԱ nh8 vmtaC6\=;_yyQ $njΡv?ԉL}Tsϓ4Z>;~HEEŤny>"WrP>y/^ԾKaO N_LCo?r5b!pӮ߅f[Ynw =w } pv,,*ڶw?{.ya_(@edyDde7Zy!fښєMh}|^(e**K?.p{\BbͭꢞBy oh!?5}aQMhUjs/M5vTkVݞBڐ~5= V~Z)UcrڊABGN hs'jݩW-^]-,,B zAL̑B[AGSP⠮kҥdkgGH-6S]8x@S(}=T]RXX߉#T_R\r=g:zڮn LЈ|+kX&>xz]JZJMo젭ODl͙з]8qcAl69";3;zeQAVVLeQQ|աJlK7kaäy}{jhj46 Tr3!)##S+(dn#)oqAStnzC,QܺwO&]к69% _VVFnJy ElKE -=5=r~zF?JJJ5ƜyI?GeSIr 6컔cbXo}5f=)wQ?fd*+qp$K-3]j0[Y)e1q[g }L3$* &9c>)s(ZūM.<1o3{\_m ]JZ0v1CQ =̢ ho!@##r[{)ںBzzYLS3(2ii:uȶ<BW+#c# R-VyyyAkk7o^KnN||v222BքRyyvڿg/JJzʗCER ܽizf')r&E/u83Nj_]5PWf0WвMǖs@)Zw2Z<*/6hazMxʊ].ѡ'-jGL]@=AIՉXul&RhÜJ45] =j6:z:n.LuxȵXw{MQ"ܣk#n@{ꕁE756.+-SRVTWWoV Lx{Щ Oѥu-!惇)>zF9-u9r>Z:zc-BM֋ ,3I$dQ}~/ܹ8`}~{E"vT{4?"0'JWPT!|iiZ:ZQh+*^|iccM%?JuKj:dQӘGpV8j\_SMyO*9H"U媑_*+)P{SxaŹ٣m>)Xcf-:h ۅǓUG a]&HmBdYeeeZN=z9 0=CF237ԋJ!suQ~B#3T壬Fښ*N\cj(RwK%%ׅ*QѵIz[q1ƺc:4оJ rܠ&yШ{c|]c=qP P+q؟ dGݭ, Nǽeq?%n{Kt(Q Y{jHmK/^;WY%wދ/-Z8w\@\FHZo:sj?=x<a1:tQQ^nsGlˮ|w5SF8JSJZƲd?w6cۡ+I31X?˵GǽCt@њ#7tƺzUͯtS_G-F mPD*!@Q)%/W/t *y ]M׳w?-[S񰑪Ә~P:_*-MV.%yؼjheE_9h_H[CuY`IE.Peʢ h*F. )Zxp=˖}ehh PW̎Ph|QhPB}~}=𡃷o*++M>ӪKfMPYR(?!!a W.+++oϞ*n A18"ڂ}}<$$ye`A>?8=AC@^ mOSK; `]; ^>ԔYԗ}Ʃ?N{x+))RqƓk!zz426E^#-DkޝH&gΚ5iyyy5lV Ō;6---$$c̘1u_FF;w\7f?HHKoA~c_h롩dOGOuITE 9ݻvĈG=s挸ZPP0ÇSP9ruTTT y怀7o@C}SN-YlϞ=mfffٳg;vÆ 4'b 4UPSNwΪUƏ/NM7 )66]vsν}6=?q&0a|Axxx,Bokddd\\ MjkkK˸qFmw#i~~~.Vp­[bŊ?Pzo9^pnjzqqPRR244%7VUU7ק^ a_h+"HYO{u*Ǝ굴4vx&I***sžPh4%%%[z5EK.QhK1۷o+**&MD5NO1-M¢"ɍUUUq8.}Τ2?_]S]SxN10Ԝ={3xo/R*xjv޼ytf͚9s̟?ʜB)yEJ2ǎ;z׮]{=&|9sͬA%$$P$9rur@ lYAA6qYjHYw̙}ʕ+WVV岙2JH{ŋjځYKH+m;$)THhԽˑ >r|#WM.&u7) j֎+ȗdgf{xʢ;u,#dx[RRR\Ts9sfΝ[n1cOJ_yyTUjؤۼ<ٳgo߾͛R")TWW梇>|V߸q#$$BeAAU뭯u;OXOc0m&, 0gHT>s'3Sz݆?[6!P/^m ;}J·OFݩ׿aٸ"D?gP2Uvy r=DHU@ vrqIM j/-%9(ѳo|MN`+8~mFBNvvd6_ mMƍa?-җ>/jt ^ nbd.@ШȤ ]C+)@ .nb_(rNUz@CND.R(40  fG@þPhz8/ފ $8/ *Rh s8So EEp^hkʔQף +6 B֚ZF2wKK]LGOU^Aץ:ZdhU'RhW^^z?U]Cgdl N=8{Qwh$IԵ(!qR-]}[7/./Y)>ys+T)v]0Gh1^nu  meSCy|6j^l 2у\+*XoZ|n~} MBA6J+ȗQ[9y9q(|]Ϸ1 -W'c)ݺѻTx0s陹?';BGB;-{b Nq%{FI <,zd/ù?ynb ːY0x޿Rन{ŴGiҷeIY4!56煶]zzYLS3 ]m(7 @Rdddj.zSY)_or!]Ko %#uD5obzQ^r| /tŦA0qX-5D2\m,dee;t5xT̈́Gj?)vuz=2fݣ"ERGoF\ UVVVRRQu%Ҫ3jn2ee@ ˑwM yo1W8j\_SMyO7,ƫ~J9PW~Z=}={XBk01 $aov|UEq"'JUjC\c5pw<66eo޼XUUߩvTeddg,W6ꟷ^ik;qu6?}%npfѪC;k w N_()v?뺌NfBo t J LzPI!m40^204ZeeJzYꍰ<A:L l;5sYxǓNMqߥnv0֝5֋jvƠys|wvWV~ӁuZTM/Z@G}θ@ $/_}SRAMVR\}^R;>_F8Cw=si5ݭ鋩a.)yWOaz+YӪKfMP )U*ǟP |岲{٫LBxIHM|?~LYYq{"@s!x|jd3g<]VuV/LM((Kh|E K:t;X#@##KII616)+-a+۷o?(:dSPF\#@CB maEٳovpv8"zޮFF{ ͻoWXXmdlءSq_|M*11 fffR*CФ onz!bqp .ׯߙ3gt/j|FDDL>=))^۶msvvwnkjj( QNrr MMMkK3'}_|2lۜ8rI%%î?~* 8S2d:]vUTU(1^<׀>Z:Z B/]4'Y[Y2uH~W#f?ӡp+'''Ą$kn]vnScǎMKK 3fLrrrs>ߎܹsݬIn !nR.^6@C] H-훷5554jfb⌌ܽSR٩e3Vp+*6;3"';GVViSJToOmn\i`ǦA%e%Uo1oݡW?NLeg{=~fdUVQvĔEG\ymWJbi^nqi{n1bѣGϜ9#.̟?Õ#G\n7o@ͣhN*|ԩ%KPyϞ=mfffٳg;vÆ 4'b 4皅V95ѣGSha(lRW(l---qZmjj*-)66o߾}D; MN-^p֭[i+Vx,M2eVVVС-fԩYj9A_?y߿OCBBˮS+4pݖIW!4->\6aaݽ?ϨS_j Vrsl#wՎ 6~{#P;CB[_DSqusUT'YvNv613NKoVBvґnt$ˢ=ʞI90:⟷AFƆ̩O288F&F1qB)XA^s^q#Q|߷u)1h%%%[z5tˋ<}bҤITCoNʹz!#F("rYh찜SS"zl8Eys-HJL (.3[03}[ϝ;h"۷h[(7?lٲ򤥥%eHJ Jɯ< aj,n)]&9sf޽ڳgOffS؄:;;gÆ GJJ t|2ӓّٓ皅ڰrNMφTg/fWKiC?(괽TxhGz)0O6m$Xz-jJɯ<l}ɒ%%| $WLO6c#=~?~m 򊊃gn|4,9=gW3rUGx8wrPog&<}jӾ)rwI{W*KvlMnmMRWX- ` BhkR )VDNK"4&slQm_hH-C'6HVNZYZY&ܾS:-++wxt+'/WFFFM]-^uWTO&#[ufQXpA{S Ӯ]ϟ ڝw?::3;=,甐} qdɏrnclm4h|>_15gϞLU޽O-ͣ5k̙3gTJx"cǎ=zk׮w|Pw`ZΎ[ǹfqrN͜G)9kZ.]- l38wt JHH8rHVsVJ~y`+ O{QM;0k 9rTIo! ^)^1R@]M3s d+oܸB!Ҳ*}}}<doo߫W/OOO rvdQ>yunn"᜺O>N:~W\,ss8s.UpTe;Em %wN\N.UCn'VsVJ~y5={AŒ5M tzՄ㕗Wf!]Ko %]Lkaf ajS(Oڬ\m-6 ӎjl̘r| /tAAB[[ؘ'ӏH]]d6:z:n.B(D ٝs+2eJ-j!6l5j-`ĉR(8`6O5;w:;;oܸ._|ɒ%ׯgpn5gO-^H+mأ .tժŸJP@+FP='ͫݡ"tkYmaP4Uvy =j6>w- .G%TC>oϸPmVjh ~I{Ŕݘ h޽%ȀD9Gf$y^W߳2-9MCSC’I̯ܹ30^Mpii鼼YMt՘u똂){9޽SgGj]5sjI111LbιT.ZW_}uҥt]vQh:Z_F7Vc~\MVsVJ~y`AY^!-oFښ*N\M>n)w6?}%np̫PN!uab2D 4,:4оJ r@ GB[~#qVYYSOǦ^@gϞe˖-]㹺 =/\Vz;(k9YSzo>pK7u.@hYDMЕAIWVY8" ^MӬv ȓ6glPa5}15>6lw-;w`3}%O,`` ":ToQb HPm>k]z!@ @ 1{=yp^(4\#wڬxH.R(ƃ BD#%('?{<$MXeLY 7ֽjں:{:BTQTARp#*s|%/. )zu╜uu5GֶMl|X$u:veRkzZzd@OOبkJ9OІhGGsKԔ8&ƛYǎڤ磻Z{U@T(RhC׵gӠôgwD[޹u415μcjajimY;@w侻 nyvQCCS:~*\Eg`hP @Ѕ0olnĦsc3jEEED)#5)|&\K%@k侻rrrܼ\utuijV)I)M7RM]P)յ)[@υ<}<£ []ZG=ꍖ[_>mݣ#c.GQۧw K"682_ئ10m{{W^^~؈z-Ӹ2-V~2x pͻL­9 70S yt.m NBSZVr߅BFnLԕHDBۮٴ-Vs{]hx.naMr)]eǞ>Ga51@iI]/jlnJ)Zb[*NIC ij@AU^0>}t'Ozd$n97,iVm/;3nߩ90h}GFR(+ٱj/݂ojSr sjwM߅|U^^I^;E˸)?3oڐ߶ͻKޕV P9g"e4r<첕yA]DlO~]I`F2晥e1Woz >>];GnW,wD"/-,,A10y,~d Z&oX?Qv>tuYeHtͮݙDG3/y󩭣ӼFYF P*3*M޵BkZ^ޑ uFmP+΅>|y.MMM=w]9zX-+nuW76O ?E"Ѹa%=:{;w3q`3i˶={@2w{r/ ;M0sėKx|G}nCs 晅r钹WrZ'T`(zoTКuWqIiQQ2#bU|*υ Ά:c N9{m۶+g$d.30a};Iկ{;r 7:ӏܻx֒)bצ"̴Je EW'¹к)޶{Y9ϵ4:@_OYs~ r׬Yս[iii=_oRhMC "DK׳$Iljs.Ш7n=??ࡃ6eצB 4Px۲>kᕞiG[76z{UԦ[124M78D$-+U(: CBkZYYYm XUT^U“rti``2 2oG M?(Z(P(nEFF|n|?.nat^x(O6mϞ=4{gmmmss={Y~?~z(>>ԠzjJ_k)k1h:J)3gΤ1&LڱcGii)B un۶ٳbbRAA_%}n=ϟyx[ܽf }̙#Gt֍ꉋHn12{'3#Or n^pIgx=(Re"ԴP o$ވmfffnfFk\&\#޳1e]0?/j9v׎].U:`ܯfe=0gp7#yHIIE?[|&1 8@qRϝ;{wgKKJ2tI޺ukdd$͛7?x 44Tz]''0OOO+V 8Pn1~~~̙FUEBBBTU+2| -)1%Iyر#sԩS+Yfe>*(DrQwU/w]n 9xݜ9s聠C+" %ዂ:::x;.[ZrrrЫ]]v͘5{LiS>L)466Vz; ,,,b;>}HjHUe1PO1I'w!L7˥٬`Z 00Wbz9"7RH`tww)ShtI&M62BO8Ad߾}{=vزen޼ɝ}k(,_zIZ l'ӦCDFG^ˠa2-FUY?Ϻ{xq@+d8TDijRԨl2v MZI:4;lƚ?FBq:E]]ĔN楞[ѣ"[Ͳ7lo?3e?yL@}U^D5Ǘ>~2kWÇ :z(\v1cݻ7EV+^Fl'''_N m߾ŋϞ=Ky>??_P՞={ECfwĄ~Jzk׮m۶kϏ{qW ,8p{ʕ+2-w3][G^F @M1WtrH",{3ZMR}/kA iU~.ҡeLtO4k5RH555f=V\C%?;0wQ`_s RIxz?۷xyy17w߻w;_~D&Mg&%KP^NNs c27Iw?~|nh:K *ϝΚ5_,X0gΜ˗׿bŊ|ÇT^ᆖWųg.((qFraI&wYV2dH^Zjs'[ wg䯟.nܽXpg뱱{I;Ev2-#k?fg،5z='Y9+LJ}hmn4ap^Ք__AVhbQfZ;.*@,* ^EZ3R讝GPyjtffdyŚInn^ o?yqjsz3##VfV٢E 6rd}%-_XͱH_b11b]S+IHo_ez{ih[YY?/r]]]mmhEКMՅ &,`PPN^ilɆ]im͎] x:*qޤ3ئwgWMu)Or{YCEEEGG~^uEPT*-+/^IPHvZ?sVs>+}!΄!~Gwq{ mjǩC77֟43q3+"T<eZn[m>>2"4nMWG|QLE\Z ON~,9PGKCmOciߋd#&y{Kot=ʬR(@Ccd4vzg7)i6cMmP uRhM&uyUxPBkFՃ(Кt.+jhȐBk\%+/+--+-+c%%+-).e.bV5@ 5)jR(P9HPsB=Pg!@A m8#.TzzWOFFYTTt\ԣGƦ>bcZJZP@kH6c -UTQ;:hL=[SSɷ={_ۃpPttm~zu+לݝ$ztjy254vR x傘#~1mLqt8H qL]S SKkK ^cSXUZݔڔ]umPQ8Xs\?0+w.N~νv'@g،5ڜp#'3&+'I͍& 03֓~ާ#}irt[ݫlዴ2 H6!0$Pֳ> L mĬ" ;BR%szS*B@?C,,,c^faޚh-;&' >wŹM?GX斳;q3Fu1BE3;fF_IL]񽤷ѳ巫SHU˷=8؛%eꏸO_u;}rI_fB%}zAoRh=o,s3//_ou uɩ"zSXM3t!w-fuG &Vp---7dL(]&}B=옄/'2wDB)hgz vj*xZ(2[&\Ti (Ơ`?ňaA} PBGyjkk?Vy` EX467:{1QEafɾ#ZyEsR0ѿpc|)157xقNL1wvEmL'eԨ$z^ ] a()))**6337 ̌jjjxuZؙu&ӱLinkv4jHWQ&2mH}5š[ſѯDfU5smzwvT:|)&֊~FF]{tkw :4-4?Yk'Ғϟі4oq->^WWG$>~O^nje2np'cF_ԡכOY- ^v]~vBCML4=nxQġkTvYo { o)|y.MM_~ݻ^=zC˖jPwR(4d  ;?uD#"Ν>sJKK۶mWHHРU OHfͪݺ߿OKKͭMnI}9& @CƜ 54j4~č;x蠆M**5 )rshć#7mXRR>hP5)TYY_ uUT400 y׷LDHO̸KWrt%.mML}=Ntkm"Ν$k+"qơ*S,Vt֪EHL֓=z42i(QTU,&)Z.pppq5mmm">Q퓞>}z-/ Fu>7` kI;ѣG'&&_&֭|թ0--kW \bZ^~~|| S 3ݜL+¬xz *OE:8,*,:!6&ɇY._Bjm0kvm}֝_t𶴲sV8dH?1xf"BGMyz)(=-=2@E'qtkdlTޒ(&#G7fԩSLj:{(U~)%<8%%ٳ;v4hPRR+.$DѣllNHxĄ֢u_-26iֿ' Ҕ{k4,~dnr)Of])}+mV6^o7BǏ{7-ͯ_ Z:,>62jN1qGSL54KMIcR(),,#(ABxK'+R߹Lm&kfvG֫k-M6me~{aE)4??ڴi{Xgmmmss={Y~?~nzxxDWWwW^繁O?4df#v{nڠ2@ͨ8'''ڦ3rE3cd lΜ9sȑnݺQ=Hd&[S#íÿ\Ynf)%:::~W[n?>ݜ:u*?f@Νhw(<;vLge(￧)-ydtg{l%~'tӼNed g,O?ҰUOݹЮ!P6/1yh'ۤԇw61WGUIݿv*-+ur7氞mF'~IhVN'iRks#*X/jʯN_QVhbQf7z zF]M Ei/{{oaAaҭ;9O)70JK._mױ-+Yd1Rg"/_aU*~|;ǁ$qpp~s#wM˾}Ғ^M&>|^IGFFs)::z< ^),,Ŋ[sj*U; *5pӇHen{D毸ǏK2Ӡ?ϼGfӧO߱cǂ hG8ܐNUc3gIhf/&L*߇RfyGMqNKCMkj;S;5&+0nģLGX斳;q3FuNʢnܣQkv&AkMoe/R}1,|7 OSw{u5*Jb=grI_fB%}zAezB g6z7fgPwUSԙiVέKKKSSOE V*sV+1=GBٯ~{ΧLB˥KN4iڴiԖ9z zmo߾{;vlٲe7oNٵ{ "/=@GJh)BvNND"Q}%Q2߁N$E{We7`CS# /߁\uyPbD° ֋>$GzcƦe>{ߗ;<%)ioRUGW'J2sUIAr@׋ >aSsSǖ֖if|zMQ%^XX3ꑇ>|8((ѣtsڵcƌ;wYgiib$s_vrr8q/_,ऎ_xٳO={!3ݻwiibRŗSTҋ/]vm۶յ]v~~~܋ʭ%]ܽfƩǏ{...W\b{'=#u5p{mx1!]sC硗LPr7DY)6?q}垇\JΫh0rȑK--Sj1൱{U_J{YvPֲ@{'7&±~8ۡ֌w)Y*GSrSoԹrpοQ0ѿpc|=ʬAB}=϶1qij>wXkg_ze'EGTxTaacKe梈,/A:th-P

|2\zX,={vAA7rW[K0{ZjՐ!CzժU;b{'=#u5p{… O@8K|><2x J"##7lrJyܹ¹]<W`HTn }PGEߕyx+ܛ-pàEs-:vr'ƲPzd45;v5GXtyz\o`ޝ]5Ż_ټy_| Booo% T D)Q Z! eô4ϪN&r{ҝF*/ÒΐLkt$Lnd{d6%jMdқ1]8}2k4 H W;.[IN>zm @~!@A  5)jR(P9HPsB B+++ ]EHϓGO^䩵CZZ]K{:43HZdge?ffnŋK+͛v]'@ gn$$vuIm]6 ZTXt!£Ʀ}g#ݽcQwUUQ肂BޖVJ`$ۦFkwnyHZJEEEE=|dljLb1uG]$njh}ˉ |Sz:ͬg_sٜ!2 fOc7ˌY#nB gg>vpaojiky`q( dj{ӟ43d&%X6ڳkfF&ſ! WaaaA2dFJOVkN=Izj:<]Vttm~}zu#̡L/&BlB}21j,zܻppQQQ<;:RZ48٩?)+?@.-E"eFʙnTe8&g10~jCwR -(*g.UXTJtoVN'RZM`fGkB CL %WS~v^5X ٰۢk3'ER Rh=NATCS)**VSxkQaNWUUUjKBKJ]b9xRSSSl{6eQE7T8Mg`h843?uhm`{]}7=q֨+k; O_u_㤯{Q~ג>x AP܍]zAfv'\罬jj%%%Ai)̥K:پ*gH62(R,--MMN ?#$khP$WצllkS26^PZZv!N wjK^NvO&ܾ9kE+U]M5e-tsPbD° ֋>'rB{&fy1.QsiFzejrEeczZ:mzuW\ani'|79)S?u;x%,,,569yӜg6f+OG$42傕Kt4WQUa(IZJyl`?)rBC#6Wsss՛;7oyBTEcSco_ѧwdX*=op_)*"a#ĻxGG\bf94X^mٞͷ1f?ۚ #,:tTIsGGk6:ѱYf.7Mή]/REEE:w#V)14n_2תۮr%{ ܦ͛()_v2=T[2u$w@Fyyyiiiqq10X,d>Dvnlο.P?G_ԡכOҙ0wJt4&տgVn[E2yhv;ZNz?nD*P!u%%%EEfffrDJ9S2;;;3ず*~Cu26(]m߿11̛[^C?}61]8}w6]JKKvr*--y`mIuuuD"QT[Uj\C -,)x%%ZZffk::^s?իojtԩAmÏ>ŋO<խzj Doh)))gϞرA^iu[T"Jt=Ζ턄7LMLh-Zx{BP?)/:~*\Eg`h@ Gq歹o/*,bڪVSKIe -oݺE܉'۷ځ+V077WW6mpNmqw1gϞEFF,[C̙s zmvݺuvvv]ȑ#o޼illhѢC2f̘vZ?@.99 پ};SJdϭ֖GiӦٳ矵7oƍiI7ѷo*g93fddtUggg/raړ&Mڰa2زEQl;w{W~~~̹Ae999yzzXb[;|UPDYZ'OhI}Ȏᩄݾݻw3SВR(dS%cǎ5kJٹ 0}t ,e'E%>}0x!.γ{Ihh(.j<~X;8""Bd)[nrGwΜ9TgMM UYP3BSs ogN-xQ~JoȘ I5*"a#Ļϖ+΂0J`pqw|Ƌ/ta{޴iMQZZ*Ν;X>B]qQ7o@zzZJ>4,Gg0v*SǑ} ڽ*!`,az|9__,̴\c>[j R(E?c~P.={vAAAPPƍiz#c…ӧOpV9իe޽{ݺu+))TL5kEklmsY|9 @ >\]JxaI/f鸕3fɒ%#;u$ .7̕l].ss󐐐;wRvtx&}|h ,44f(۩LrG^;z&MOG܋p )Eg\] }Χo߾ͭ[ʌtӦLJL٦ %}}}fY666匮ĴG!}Jm;;;'OLMM\~In%2ʭGna\2Es1H{{ ?~FioG@O_m<4܉4i>OT &qyp˗ee•gmZF^A=iOT޼ղm7 UM>̈й>7mo;p͇366Qv.˜xk܍Ҳr{[yuni˖7Z6i͌[4i\,G^?S?4:w`7~) PWy/? J$ Q cw}}}k \ˡiYZZ6ngڑn͝gRș06?wV)_i'mm߮v5nY콟|WSeתG'|柊>:څS|=ZE JB /[ҟk{';PJKPحSE)4#ZoܖMK-x`R]/]6i =Tʸ~;-d삻maQLLtB6EؐPB^*S%!Bfm#/HIKg{FSdw|ͭ{ D7iM,ViulO*K{dDlDLÕ _NٵcJEE%UXqZ"gB)^KLXV'U$PPH$ukCoMe:yDBEdXcF/]pQ[Ng-4'_kN-uȁA g;ZVƂK\YR~(+>J2U&T )R :/X$PMUq 6s\7r<첕yA]Dn]2wta?-umw'-ai rSg\꽳&_(0:"#b[/߸_%!B<J8_ڜ$NW㿿*V6э7kg,xyݙƇP'Ůwxh?1r>u4>j롹S3Kg_%sGLkKOFqk',t=sޛpc JB 9/#4?0mPE.鶅&^~׽.pkܻpo*EPւFm6n2B @A glmD"C6>mt%:66m뭦&=¹GM}:xӺԙvA"ӓz526CUR$w'_T\Ͽk}۹ofl_x`;N-n߿"Q"|Vo<ࢸ><[.UĀ"( HQ!b1DDc5Q4 %`55~֘X@QP Vjml"';g93wfršF>sM]BAA 3PQs[X@EWhFJR&qͤ'C|=̍9K#K﷈DlVkQ!>*T*%R73'9B ?YA6U(  +ۉ>׏|ӈ7 Bjg& #=ϻUZ7g+ }w2pzȮJ+ՁY:ZŒUvֲKNύt>_o/`<UPyWocg#;m9iP"  !_ɣ{ٔ0 Ju栗wccL{{1c/»wRZݬ 1κ_\{9xM0N?-Nu1Vf`fmli'061QZZ756?saP@@Q&$[v"|"l lWvuռ ABWQQqPPPCC)bEAf>gff CvQWWg$;<(o0c߸[hc)L QQbѽPz#ySXwmEd=Sc <L#`]PPP֖=@ACC#HJ P 9xjQ~]L ZywgjYsJg@7y֜W'"9---B LM{gfKNaDXSSkmm @idd=4\k[ VB*UL+Mg0 s PЫP.f~$(ܹ~cAAD-}ZZZA"鄢]svabl63-&UYNI. ((P9s!p8s’M2]Hʴh׮ի᫋&*:!IZdS(򷪪 >HErI2Tۏ9Rσ3>8qJZL s PШPTrY,Vss3C{Zkjw΀g]|7]v8FAA^(|> م&אÑMgߟ`a>\ƈ`ajni'NCo]]\^H<<<%!vRԩSƎקOrٳۼyKEEERڌ >_ F&DF~ڧ^4îlCCðsZZZ*Q3w[&&&<=66VG@L} <.,U)U|K۱蘬jTN^:w޽] %7roTVTO{ќFǒ)':9=/X y&iu|>Ȉ{玸aj67`2\ncU/a0XUUL33IyҪ!Cݻ:F2Lgg'>/ZH#u~*}Oܛv?sy^kDA" t B]wrXG yC|QXRGtƌO֮]ݧOLM6p ܹ+22B/-3ٻww.lٲ`ZhgΜ "399n˖ͱtIʸ~-Mg4pX,^ˀ|K<ǕEmmqq̙FDDV;Unӣ~ K%7JJ/^{byD"Z4o5HuϤ̪<[د;K-F 'f0LCCAmyHs+׮56vvb.WPY)a1u H'*Zu.\p-A3T2#%%_}Yd)쁁=fϞW,6={v/^{l6{ŊĐ~VUhѢ;vTVVzzv/;kX  2-R/hȡcn*.@LL\\\tj)|P8;vl\\2!z ڵŋBCC/^.]MR Yf&Pf~ ?>=ÇǮ_ 28qֶp֬zd,͜9o߾6%6o֯_WTT?yĈt+@A>]lYxi|',bMF7A; V҆ |+W_89kh5I ҮKe§Ok:<_ L¤իWL.1d1@ cVz3o=cܯ?:Tl gjjlҌճg0CB>X]fj1)ګGG'Up5>NJw~VLMMʻa [_zvf}!5'OOqMMFR]kɉYr_]w!+t n}||6nwqvTiӦ#G@$K.IIIYظ;wVOO…s#Gef^Pg$##[7?ZQNCN0|[̕+ xb333 >-[hѢ޽#kn:r$ %th SCגo'"iU<uݖ;ϘIϞP_h,%T`̞=ȑ" abyg _E-EҪx.۷ڮ~y<aozzNB!c=?ݳ w dǎ‡N~B6˗v:ʄx.uZo1L.0[WWW`iiQ[[awD!%WUBI M"r+Fz 0~N^:4d7bp6"Va (v󲿸?Ld5fn6WO+QTDqq]P\lr )ZϱNTNpb_U%e-\@"rx%UUw 211WWKmuFJ7JU+{G.C^YJ{4A \;;;(ɧcƌ5Yf[XXPN20x˗MWZ8,|sss(MAiΕG]3#~K,fC½oabf>p߬?"BɛzPO>bdl8nzzot8t(I^e_\ܸ[.FޱcG;;-[6;88@tv͛7Or.<< {{{jKԩ[n͟:uY3@Q9ܹsٳF ta:u̝;6nܨTS ?󓒒jjGuM6vR2[+G3 b}wVc?$seY#h=PNwِp-MєUC$hoъXu c]Լ-awŭ9]փ ռYDbZ$ jj*+ӛ?C,P uKSVV_{qss$(N&ԽHn[yUVTT=/w`Pmll433I6[_ ,\CUа"AA+tB17R.f=ٶxrC#gL=XMj-=[E91էl4.!%o]_6@7?/۷oΜ3Ywh'm}4566VӷZ C?js l߾Q ߚ'''\]] 5krĕÇx7n~ɀXlk\\\չe2Y V\5jT2p>t& (.s$'[n166;w˙@O~r^=PkcGthjj7666a?spLaÆu`-UT+y$5;]#s)KN58+WNaѠlKE,pŦB%WLdgF1=ir8T]0Բ}ʟZ% XPTCȜmu'w%*Pȯ57fKBɫWJUYYl0 v}}}L}|~kj*Ŕ͗;A, 9h}vn7Ӿr99S ;vIʙ)Ȳ[e!JFu`7줞jhQ!PЁoZ3m]s3ͶJ#͝DJ)%jldx0(}TcQpH~uƸQstt,((ؾ}U+(PV7BAi.tJҕ {sՖ1\gơC,8MY]۷pL8QJ'!0ϏKNYNWK233E'H{[@U=f8###I:gx ~䇄"cB!)Q*t_3rUu;w9Qjkk;v(v.RZZJ(++VoeeEUxKKGέa|̒jJ sT_]>p o1/_T%ckʍƄ|6X, NfE"x\')o7_Yz?ݰ6z4}TMa,EoZ]KE ic'7cbb>x:iHJJ nz<4d<є FFGGG'$$Oo l؇FxŋH:QQ}͛ =11Q&hn .i;7Ȟ *zDe/( O@'BNN))`5w3~ INy]pLUCӐFr:88ɻFR%QlR($''8PBv0`i:t5k.]F>#4a(MshjjZ\\FVV…>p+1 ͭI===@l6X.rSNݕṚYe-Iɾc;f;}G\ 닧ڝ{䤎S-57@D44ʽ{WҢ'qB(`iTI㠺 Ӿ 'c@ eՂ+BA"jvam^ƞa;sGArV5p|qaq M/+++/8߷orHRR _*G US'o7yS?jS~:iHKQυ =H*m+zD$$bU??fjBA`w*%KrEj6[['g`mpp2x׮]}kQFx~M6Mi.adY|LڧL~w}viQq\.lVuGE;w.yaŊG^u='Mٛfdz*//B.n5Nקx ;o\q㺢yqU(YDՊ#]]uh:ǯD~s3Ri-ªQ(5JQ"(APa AACHh#R.~ PvOHlDO?2E<ȣOG^eG:>1_:z%&* |gg Ҽ#LHT(v-{GŸj5o{qqYGQ蜚T+Bb0hC;~x:}}%r*Xٳ琯R6ru'ɡ&:nMMM{◔CcZ[[ؘٙU98[sñl -m0bѯgohԭ&RUc.NճAAǑ;,XD E:u}4ǃPQA8uw(?S^&X>DDFyZOu9-uҌ{% SwBҚ.\999`gYzzz7ohjj! PǶs475׮W= e}8D*~ 8PO&FBAyje\oмi|( 'j[kX9p3OMti]{Œ72{Wq UTx ;99}$---`eV$s{#4 ZU'O (U+;8d`c٧V .To'   dJhѐ]E>ϐ3dM<\ХmB]Zd 3IXSf\#E@k::v|tqqE¤NĪR >  3#\2x>6+J]{ PQrIM\B1L)'}_(  3 kϜ]su[]]{i f<1"   ḑ{_˯fT diw    AOAAMME 6S"Tom5{'   :@> ZW_jj !ۈ-勅U9w J0$`r\<1g ڕ>]UթgF{{Nq<:;:!@Ӽ4#_i`P7Mi&Ix0饴YB/g )a!) ;k7Rw 3+qȕ+c+/js/.}uN)Cv^y&_Q.%}+eeycF)/ל#5Aio^un3iR9k~TuNPtqmxENTs[Ff:ij_PɚoY/_y si}t2?k,e^kkrE8c.5]z*kxZ24>+~L7vOKPxK`JyY/i8a Y^gJ{{m ìވ@:;;M0 AdP >Br"4ܓ'/j0[ے{Pʎ,#Hv$٬ QjR6?NOH /6i`N>9h@WuO7/sLxYֵίtcI_:4^$yqN&8U^s=d 9h蘃 Zk>4<-%Cū22h5K]S]\*^]e:!Lb_.|t9$>&tW@]eWꗎsxÑ6:j]{ӽ:t(S 555z/uJX ޝ֪SH$hnnf߾}444MYińakt-|F.++v,,( *bk+#'Sieyҭ7mHuz/eWdy)}%.v-RUQAeuzPI[th̹JE3c|(~s\LdKiK3UPhIS&YuN dcJ]Ro仨Ґŏgiz8ʪ']dBϼ$̨S零~cqseq9^ 瞴:^VA<BA~>eeekR6jjj8xE׏l̓L[WtttP[[hmk Ѩ_@fQ_W `Z1dP B١2 U]UE[{;e8x픕ZP`J?x,zç*#O Lᚉ3w4X}ﵙpد&nڎi®<ÑfP4^E=7dm?d$#GnO|A擟^ŵ~$%WCT'U ^s ;Bo23+> Ե-]w1=MKNuV ;^4@˦(ީW&7ɤ?xGdqw^5}[sKK $PV^N4 (--:OuuuԐR^QA(*򢔖 <'9|B8x ٔE]]=UEii)H^EE:sAGgջZ:B: L?Ҋǩ"7Kaa!V+hFUUyڎfE0dw- TgZ>!O= ee\ 4Iϔ+z@ >bi<#dsWʚիyWZO,#$i 8ە J23%KX ~_1|>S~3󨮮3LyS-|7yw'9T!;w駟;3A9K}KW^R'[ʐS6-JYgzYٞ}AeͣY<|ђS8}$,)ܧӾmk]T/|$6§?{Ȗ9QrL&)yd &NkOkN :xѤ˱CY{ѵeƀ|uNgѐFj~c a+QW_OqQ\}~1&M POSc%%%IFimkptvvRRRB@BZZZhljHVDCt9<`-}ZO~ ԔDn@j* ȫM g񠅌:BFɦap@ @II --$ wo%~!O(bQ{z}B $c+/˯svnVxלqh;55rVڬ,EU6{3݁ߵkh ~OQQ]"6\4tա$?_yg۹@xg~eĈwk+GZ԰qF[ŋUVsNfu㈮(qСCD"M694jJ{̙O~z~зo_wy͓* ]0Io:@rN:.'_ a?*'#xX΂SC^,zU7T 4'Lk5*Քക[4\pr\#Q|9 :e2yHSتHxoy5r]qt jh1da֖L~ ?3.v?PCN l#s28 GZ{;.|횱:R iN*Lzi@zmؐ+d}<3N&MJ{cP$VWRU x>2uU>G> -<#/UtZ:mՎlN 5]Ub1ZZP!;K3Bx5槎}JJ蠱v Dkk+999nTH$hO{]*++6mlݺwy|=&F~!=m8FB87P.uhw/LVK{Υ5 M!g 7es5D"F0$ ]ˬZGyh4I+Vo^**riҥKٻw/3g37ߤ_:.9l Ƴpc1~xB6mbŊп."ҥ8$ pdJ477S[[kO2H$ҥKٲe  bxձb jkk4h0~:Y|-vi~M7Sw/K,!ӯ_?&O̚5kصk'\s-,XQF1a:[,~z-:l xl߾2N>CvA~~&J~dѢE/}#G2}6w;-K?M7Odȑ S]]̙2d՗Gͥ^Bҥ|gnZ/_N]]-Og˖-|jZy>}:p˗q**rꩧ"D~c1h {= cKH:%q#( 5BO`jKH@\C"aq6A"Y .NۏL\T'Wds<3pE!P<(,ϙ3:hD.zɨ~Rr.~~uyT贠T.jqf w%9f)P,lst[V2Y؁z/8$ s'if54j g"P C'GB΃+Assql84P@*4FsˡvŰ}u}AeyUZ(zxxŖjh*wi魃l.ԵË7󭻏^̤9ɵ/oYr(6ZGZQskSs8@@@*D(;w0p rrrnXɛx'Hx\obO<78_Æ,73wl2wvv 6m՗_SO=YfPϙgI>}hCzZT6FD[9mTr{ 7`+Z{5:ε 9LA mm[)Æ>B'|J677Q[[N;)Sδ 1w\VX_ /<ϖ[ӧ`0ѣ)((` 00w\_WB0r ׭_=D?0dΝqǍ‹.̶m۹ؿ?+V .pnmXHwNxR<3H"FMcc#`'Yx1 _Kv4441yĉ'?;zhBgfu\xE<<6mȍ7Ě5kx׸/?~rh hjMvp5r6t'5c'鮧ĪwL]_hI뮍 ʼ4+I&(}dԠOjcYgT[˛ Y )![^nG@Ca/)d$ù9TEKy5I]^x_e|y|DYTxdCVW*Ef󨲏NU{!ʞR?JM|mF7zHȶJ  ˖d[\u~/x\su|)+!! `s,|5>^xg>;ﺇ|\&>!%ۥ^JcC~mXOsSrÇG$ڪ 4sZ4*bUANi#<YQmшjV,[J"IT@ hATWWǚ5kx7ȋF'SO=OiY̙=n EHGG;͌;#G:vV\ @^^&Lp-V8Æ''' Lºu?>7tD"Y<9w./!C׾F8C[Xp!/Y`sΡ,N?t;|˗/ 77'p*rrrʫ;wP3<˲&=MEE_F]wW_)&1qD{5?5X5iN,ˆ#=g6[nca.G??#ڧOf̘AVV կ| "s>%^FII P#SLy殻fpWPSS̙3׾w|{G~ט4T=*xm GJ'rVYsLi\uWf<NW}]d2Ukλ4L}M]9m)z'wLb݉M8ǴQ15qt1И2jβNs{yvm{ڡ2)%)8xK4<9z2# \,4^L`ٕtrJ(Z%-hY< bb''(CR)<x5;mU+8`Ǎ9D{GttY7Yb^ze?yO8ӧ\r)uu1ܩtI!knN_Cxm Zj) AuR>htVνL#|@!!YE40_MsUPT\f<; {wޔ 0bݬY_ꗬ]?D"A]][lfa|_w4]$ydeeJ3\0ĺj7?+V,gY{KEo~7tVaȑH$8x [l3Π j`0H8+ ͛7q3|pBSSPR]0`3N?>}εb<|p^z%CL^Ǐ''']vqKI$M#[v zb {+H1%!?j̧e.gϏ2bp+8Ca[yd.gN Gl|_xC՟qrYӎo-2c "@V;6Pstvv YJ: TK>}߿?H?zL9!>BoJ~ DQMC}=㟤 <#FpBFJ O>sϝI'ªU? g}zu<UUmB BPVZj<&6m^5)kFMM-۶m/~>%}'1kl:Ship1'~es:$׵굌 |td^12)G]O91[އO*/:y:6mP(Ɖ'-nL>1YaFrp'0p .Ly4;< i… yG(+/i"`ҤSinnlv'~/+W 3:t(B(..ih"N:d=4: njq?qcAӨŸ~!_!?/ PRȐ~cH,sΡ3Rnh'|2C2Ϙ1z~ /[Ԅ/cB̥h M_QQaG(/+ uzbVk s)0}tRm|{e̘Ba:ƌc" !waOySoC}jnvQR_za;*j95T  _9+6ίc3{G^)a <uuU,) +U)tR!OԲaK0f>tM oTJW;#qf8RԗNV7# H$3ఏX2Y;xMj o2pv I7D<Nz*c>8>XZQ&?9%S$^I$OntvJq*g`=CLen<$1C`;9 u3sTZ[zuhHR\l*cvRs % dee@4%o@ b6+͓a@f@ ^vvXx¬l8Sun 4551q 9>8\#W@[{;͒}!qE9r)^K\6žL8qqafOvvC+kkk)*.ġ!1ф`ʕ 6j~p9|DZz80{c/#'*>w3am]?)+/~B47W==!]G .^ /u/]MJᡛ:[w8kq2YA7!es&V^kRқ#C\Rr#Yڋ#=H\o؝,CI$NDь?ԞTd!Ԍt嵊)ue{ԟ4&c$;WҐ1O8ɛ2^9W2k3c͜j~K1b0|WVƁ%gyV leH衯 > upT\%Wygc&ӵJdلL5=vUI8uqh&Hb/^ijFjJ=gv Tk_m#NWv?r~)ͣG;N^4?ՆOy~l'ӫYH$xhmmncذaƠIa3%v-MdmLAsLܙyu)~fi˖^))GiY5"R37mk(BGzzEu޾PWNb'3KF7J[8,=T(^캝65OowrWI^$G[(}\r:('Qbړ-/o詠gV:t>S}o"ʑa?dVa Vf:6/D+C+-PbKuK#C\2|ɠ{z2!ZZٶmuuut㄂A)e#aoyUر! BE+nBֱsv'CAA C$555z#K mhnnb'۲Dp2 e `H$I\?a~ rt/ȓ|^ ~rfzݲuҹͲwxJJ 87@]yVeoxi; ,bwmK;uN"XΡt$H{C}4w %30;> |C9HIUv^r8FQ뱓YvVw8̬,%p{&It^3YQ.55H:\y+FNWځH)0m0h!3tWKՏLvBRtpeZo{+t?8QO[z 0pXOc^AG{ u4RA=$?4!2_hH:ۭ!AaQ!ZScw,d]Z 4HX5"< x^P 213=rː{ᒫ6KiӤIr[ pzz$~Yy<^tǒ! &4麕6n)tVNՑk$,z?/Viyto q%9(4V[YT +lպsy6: 0LdPh;uBS |X/>I5ɛ|^=׫ҥvG#zh;}@a9 eMu_i ΚfǸ3KepxMZF.QpB/n & #B?*)n/iBU|T8a9^_O'?5%"_ !yQVDͬWLY@%+<\ɲ9,밝Y~\\ nZeJ\;RmNN%ζޚ^^^Y9&E\O{h &h?\_;/n^5Vͳ*whRNZiNZ٧pO.#TG͔txn)CDtHdH[%;kN}Ƨ:fBB,:V_S򘑝dQĩOݚOlۻtߜZ(vLӄ" ۛɔ ݽ "O !My>.՛Dw@#ub*O2!"OKsk7C7{e3Z"YߓԵ~?OIfW7bSO 2&̰V< uC?.{uv]o"Wn7E&9͒Y &\?6*~@ j㴧az.Ïo1ETM錥6?Sxc輓7oWX\DkKU2 KI;hvxJv]ctzPlх<2']M'VO/ت;Ӈ4^[U^[>ڶ*==If9SX\Y- F߮V(K.gWyy\UQ-d}%Yu [N+_I̪#i:druOt*P~F|FvmRO%w#}Q G8ANllۯ*󕗿hu|O͟p!k̅N&TOo qRȖe,vi;Xh1q>N|~?Z4dH ]T| JWm Lꑄ: ύ2%dhcOvtqG݄^|r|OM]8ҋdPc~#۽҈FP7dMRZ.'uu2r(<w٣h9 oy!O$8p 6m}yI5TVV^i6[m4\.v -LB*k4mmT3J;Ҷ読tKK`%mmAkkUUU=VGƴ:$zZZÍIatR[(#[4/kd LI|{GXx'5558p6WyGF5~+W0{\fϙ9s˪90@ijj⃥K1Kߊ Y@;=g޻̙;WKhEՔE"`ժUTWW[r.~sDZ^HP86n+j_y_yݻwc`vņ |\U;Lf&#_Jx|HXvܻkxm,^ؙ)Go&;tdIWwWjkkY O8t_5~^~{͛7kزZuֱg>S^}UwK/d|TH$x뭷tL-[x?~ҭx[J$Yzz'=IIQw̨$8pM6((($??mΎ;\guqHT.y=jG"77>}IqCJ!pr[=Y13OS^6qKѣF,14MxZ]z(Z %Üsٷo?V|ߦ"nyy)C]=v1Ehnn;wdʕC!m/J,3LmͨGTfŊV{]}ɑH;>|d ̜9h^\bVh4"gҥ̞3۷[ctƲt=x[Jgr>O^[U^[>ڶJ沩X$Y# %%ۗ`0HNv+͟! 'N#X~=|g_z +`U~Ǯ(--[J8橧'իyihh`ر|hnn?G"ǽ@+0Çsg8jjj9k۷o_߾455o,ZĊ+zvm<{R_WXf5wСC7qĉ޳> 6#F0ygMy'صk799=c 4,u70a8/},^~e>h5-͜r)|k4_|+S\\'|¬)'rqF N0@l Bcc#MMMQ3 9&3w.Es 7r_a p>|8=k֬aҤI,k1cxghjFE^^vN؃D ڃgoB {x;d͚5lܴɢ=z4]x!ӦMc{y.b8t/~^np8̥\ʨQxYjÆ}l۶mNwusOJKK{ \.\[oȑ#ηUغeR#,X/^{-cƌa3ϰzΛzHɯ}JJaÆ 465ү__rss=`7Aaa!V}Ă^c]w;v,6mbOe%466_BQs3Oy"0W^qGf9\ɓ`\~ttt0wڵvjjj~`_?۶m_䤉'λr%pg)kmm-}VX9^GWiŸWrʖ[SRBSs'uǎds_?X+\wu?f͚E ;v,1x n݊YటF"K9sSڇuk1bHa첺5 B(X2 iN$a˖-̛7f-_*UU >\3B@cv6|oN yvO>&l\}ռKqM7Xk+99 ˄=L29Sx'mnƝm!=G|2s 7H66u5 2i$b&h._Wx饗>}s{XQQ'`&(--Wal6۷ 줴_WTVVrۭRPPرc Ì= :X/~ dʔɴbR\\LAa!#쭬ŋYr%xN| +h{'pt(үH8b>8Ƕ{\r90k,yQ/)7-d Ǝ5Yb%ӦMڿ'L ++©ҥKBo>uuu&Qaj]?wi{,==kӽJ?kGVfR3(i?cz݈:dD"w 14p8l (io!C0|4#}v @]]Cm:N9e]#GB!h]R8hѣG9JKټy3ƍeÆ 9 wٞ'0Yv-}4FwͦinnEhll$7'Al# h@ee%EŴi.Yv wK<׿ BٹsmmرF=h?`'r }6xxsޮԜÆ LZjjkgM gM6SOimm#;;,Z8XUE"kPQ^g72t֮]2@4e]ۗO׮l?i$zF,L# >O ?xLŬY>}:&g5:e`Pù( l=޽tvv}vZ9쳙6nHUU.X&&Olїg3f4|Uɓ馛%% SNe,_| _r1wP\rɥh".Rʌn _jSO=_y,hgQRRR__'0o3|c=ok4.RgYd P3N?&2M͔sIʢ>h $G}ͥ3N?W^yƌѣعsO=mm8F͖[ *VmSΜ{`ذaD͎Yx1# 0iV˹/PWW'N*hL2=3H8LGg  ry?K[{\}Uq 墖jlt|2ۮMo^|G"I/ߓd9:ӧUUUӧO ZP@kjluu*DxgMʲ^2$C<D"A4%@AA{L$aby<8---tttX(CHAmmm cL(1X,Fkk+HxPX,FKK yyyD":::hnnF4rss ôiYY  F$?~!:466!n3](xwRVVVw^ 4bBE"p#iii! wAͣf4Ekk&ᰣ;ﺋ7tttcz0D4rrrBTWw3grߏLNNmmb3deeYm!0xgevo߮)|nNYYYTTTCSFH$?䜝MSS3FfIvvS("ZO*bA"csPAADF%Suu5Mʹ47I8!7'QFo~?LjJzm>*} Z*J7`ݻwkme$c;`ȢiBgo{ 9o/*}Upmu^8 mBw@wZ;r}ysnT5XQ齂tTCOݩNONUt-,C7^]zw٢ i+(>B6a [tO{,==ݓd$KOOd'$Yz>4B!(,*" 50C[4DU>`ᄐx I~;O8E)KV.}zKk^C6\IlNH節Ҽjݑkw5en=:MkiPXKӅZ.=tlw,/Yz<^`B^=S^?[BAM2^#ҚDl$WMS(jpknbe=T$ǫp锶QKO6-2IJ|*[t^ټ-#m=!N'VGhL$KOO*tO*ѶeBmhEɲuM0?Uh0(,w`MBTLxn#xUOWճY=K^-\2McLɞnZ? ݈HM*|Idp`?9R(=V*aV?1>_y~X$Yz(C$FaXe$baN5>r@ԏtO7?Q42!A*")]TѦpˡ۴tQHnlEpYh{Ӈn^?7>x-+F) ^a/ߓdx̳haz)VAՇR)^~bgM|[,[+w`9\CIJ(ֳ͛Tx!ɦ$%KCm఍~?>iĈ!rMw{MGW2g6 VG6}KWlG/mn[d#[OK = Ñ28Ӛ5zr4 N/|@D(**"2>;vv|^fy|>'J w HL3y(?-gY+>wDJ岣W4?tW6 hGvvHV^^^^AAGG~ö˚H$iFܳ% AMM --1h ;B訢uzl.;xp>Ch\J@c5PwQUy;L !!)dUA]6wׂkYPA՟JT:"=td2Lǔd y;ss9[Q5Ӻ󅶯MBfIDjۙBuq!кh6[ k>B: (,Sj~8; Ƥ5QPsTuq$JM$a-+/ H.JZ}|<ƘQꫮaUً^zPmIo}L*UP&""(" BeY$I#2$q]} p p p p !|>Ξ=KC9 l<^/~hM&v;ZY@M~Al}La!{">|Ar p!p:>}ۃ^kJCp7 aiXe,˜,aoZ23=T&(5 MᇑȦbS\jb@NAPsUhUVt [~%|̶љtɈRaY[v&NޚLuu5Ad8y3lj5Fg*)C^)Zh.@uu } ǏBTj ưfͯNlN`CkK2fjIj֡7%Xht:q] Yh\\%\%\%\Ȳᠱ( zYht:Q(x_XH'>>>FR rG ,~$4JBjl1[_$ .?6GIX jHæo+Yvhi-_`yhX.!*ƷCw;ӹSlBAD!D$TԹjOFk87 G#Ē %L>NeРjԱkARRR锓Zhfm}-Pb1k1B,)=º_1KB9Dye9nظ8:d1#]"i)2ZN#G/+CVqաAP`X8S$#8Gw] ̜9xRRSQ(|T* u[E;6!"*4*2^ A^@!ʨU B`uXQƆ:B%(,,DP*|PM1b?Bem޼aÆ5 fDD~_}0~l?7oZF~Q\\%77gE>W__Ϯ]t :UUUEmm-]vko~ \ +-d4)//R.ހb= Bh7i4k5yo=+לQ467b]ɑSr9+[s џj~DCM9}>E3sT >J>99{FCrR9*~96{$ֳbx>lF\v+Z1xC/y}J*el~]Yjz }LUE)nˍJDPTZZ m?ewmOZwpúCk(=8y& ~;f>g̹>Tz*z=SAh_I}>n7X:l6N9z0ʪj[ύ7N'5- NA YVZ͎v޶[Jr۴~̻y 3;Ɠ%;AR3 j;AЪ۷{Vu\!Lgǎ/`lܸ_5`Ce+?=$IQ+V, O Xw,[,\QQ߸X]v;,SVV?ǓO>Yhя%.tw߹(uc@^Iشi{E8s|XTUUbŊVöm{Oׯ'`x^dYO3PTTOeQz .r7_w,u8qu+++yxسgOx饗?AQQ"j՗:uyYhEFqq1~?ݻyG.,$I8V+c2tTWWcX\Pyg ٺe nǍ͛70F^|r^~;K҇%suhTyOpѰs,CA)~*I5=rÇR^^L~>|酧=f~j?Gzk?$bKWWW!*T@T2o]r@e!+>U2FvJDя( =ۍ/aÎ#̙48@y@ZN:CJj*Vlrrs4`i0h5TZBO7r04{7x#3g/Iع;^}U:t kfH:ʪJN@))xH4%{)rPs:ʄ2 >u:£n}%AӢYQAҠA&%Q"I(jkޝF$48޺cǴ 6}QyV߼H0'fW$ק7J2hŕIMtIh2iK8%5- ęF ƠAzw@ɠ$Tfq!r88qYIIIAE6lD^$իW3fhڷcZqfZPm)|[F4S__^'33Qt:HJJ&!!Kqq  z222[WVVbգR x<JKKq\$%%,R[[J"55 ֬Yѣ!/_|Gs8z(wy'+V,端bܸqlkhEbb"HDYY֢VE0Sy߰a("2555b2IMMCjkkFՒZRjEPb j H}:*[h$99?\WS]]?Ngڴi8Nn7FH'559$IԩS*]k׎s*F eǎݻȲLUUVKVV "؏Jp8$&&!J pc۩OP IDATDVKe\.x< w!55QF+!//5k0fX,ώ45v#EYYnxL&~ݎJ"++ƈ<ڵk2d:vJ m*96-zCXp%99JT vbbbի7,SPPJb ==JW\Ayy9Km.]FbP]]^oAYYY. QIOOCm=6n7@@3;:y$* Crr2ZUv\.W ;bccH륶^:CAIؼe K,{e3rQ> yЌiϷaҹujwԪ[^ ];FY~?l%%ȶde\\tɿK>@1\qtm{Ȳ̠l11؇Pd V%j eeހ˨B#!# MBt~z XHMMaҤ9y$k֬v`ʕ̝;#Gѷo_l.ӧ`޽dff!?ӦMvs np[crrrtO3`@;=zছnFEmc=ٳgYd1 jcѢEȲq0a_IHH@RSYYh4q 7 I<Æ #77A.g…h4j̜9/p1K.̜9SNn:_~̚5 GP`0bԨL:,[ fΜ[#v;UUtЁ뮻1cPQQ`$662*&MĊ˩ϭJ\\gb8dYf{䓏ihca޼ۨd…ai&6o©SXhj[nøq$}Xw^ĉ;vl۲e I>}c2f̘J///g<쳬Z͛7ѣG39\[xL<sk6'пn7[lr$ ͸q"hCtбcGfΜ`eǴiӸcy$%%I?ǣ>ơCZP( t9K,AlzOٷoキHNN9sngy'x?0(L1cz.޽{Q(D̙XZz1ͻ˩SǤ}6uY=Q(q*{n]wE^^VmvZn݊h@1uT>fC!++>ZIII)|^ne7o+Wrxw8 ʔ)S6|D^^5xxٳgcXxQR$LH7y6ݴi/Ol6[͢"͛ȑ#ill}lfϾ%,6njә8q"eee1cM+-j5. UIp\Q[8bȑ#ٷw/o6(ra>Ĝ[oEք(oo< ɰhF;RM7?ƒŋ|GXV6$CyrqY~/~D| ;gϜA^/陙 XV.>٫wiF3(J~?YY٘ʥ|Ys%kO5S~ nr;,cwIѠ@Fhś|dη!pQ0x$F$q=448SRYGǥ}eYfkJzjv IFu6'qvj4=̛~USY͊Vl6{ۍ|>&_=sA^eJn] uV'~I'k xqʋ%o 6dJMvd–R,XP;]XYW!e䨀jld6|ıEdeu5IfړҮ(z1vh+( (4;ȭ.tjtH@' &z9[a\.K,H>+mlϞ=t:?>>_>sѱcmW\ɟ wԙ]@3L<72bRSS)(( ..DOjj*: FUW#??˗seb fΜEϞ=S >^Ç0`@3Lt=]tȑ#ی7?{w<.]ʎ=z gӾ}n+իWoAVѩS'FO? 355kPVi2|AJ%t+/~zuF^^G>JBѠVqe/d>|8| 11&zYYs=HĿ/jjj"G;11s瑐oɑ#G1b9p9#F0h ^ye WO4f̜9={Q]] ػwo緼+$$$pM7?RKrr;LBzz:9sn%--뿢#eII~TVVsK޽a~_}Ǝ>G`q~J%?<$ҥ3舘gn1wߍB7eeetܹUDnvV+!F!&&:bbb"VF 1A1xw4׺/#>֮]f[2e "9`n8U=PVNxYd1}>x0_ۮve7=zbQ(Dr;trQp8TUUGJj 6E ݞ* bcRQY|_-?%=&&CR*;(gPD;=GI°Na=acg9RPJl|qf0dtZ)& Q(5-6ܥ v 2 *J*j&Jk%aw8mńї-I`ybO>>jO^즅ϨBT`jFNU֢M鼃 RM8\rؘbhZ4J ̥U)QAe7m[I5~Q_SLbr2:Pb9Xu]3݁cn]Hi| woh7N/k70fpz=lӈ<-hMfj JA@FG Y9T@ً٨WnAב Wd2q]aM>|k׮a#Mee%^{jݻS^^@YYݺuCV?ngφ]Ht BAzz:Ng ^:Vɡ8.][om1cF֭[ԩ# ,[L0Ɉf#11Պ??<Ӈ~o3fϋZY4Μ9lh4"GѣG`ԒZ&77b#B1g$OϞE*F k]]999j:tȣ1KT*222QFFF 6bĈjv233n_BPoMai^/'O ڵ+jr'N駟FR"IW_=!jӦ݈ hZ23wTj5Ng1ڹ{}Y5aFbbbL^/vw}'Nr9v(X,t9BZII1۶me…He<$Ovvv[j:vHMM[[Kt46].yl6٨T*t%>>{m!ws᷿in_ӨT*~?>׋JDE6mȑ;v 8qSH'11YQ*ǛîIIeeZIҥKziCѠA3z Faa!IIILV [~6nD1|;̨r(tx}7STxPk4 x9ǎ YB^ d1<L1Bb$Ѽ_k姦gefy&ucv XN>ǩusuh·z2X[8a!@N)<2{+c߉s"vUPxt% <>? 4 e*AhB֐ hbwJ&ԔB\[GZ)SP*D6G R֑f9Jڷ0q峣xz#ÔY@;u2X~<>/ZcJ;rEjڳ3OzV.`RVCĘ(/c jQ&)9;^=.3Z۶|! 2VNS ^ hƀN Mf CTE0V(p8_OEEzBƍߒLqq1ƍfQ\\fZBcӦMaÆ0ֵkW֯_Ϙ1c8|0O>f֬ۇ5kֲaÆ z4vص{z^Q});v`Ȑ!Ao> ztb0),lw֭ofa 66` 48 ޽{i߾= 2gBd"!!Auu5G}LmƠAع;:w(8bJ%{!//}RZZfCgnEغu;wnݺ֚1Q2"ڥ)''-[˷~8  XZ-}.\ʇ0h@zѣIX,TUU] k֬'$;=/G^|_TUU{fڵB`={bȐL8F;J"--A۰aC$;UwɤISVVh<ٳ.]q& íz<wj` $jjj(** Ph4L1;vq1tP?2vtؑcǎq!Μ)v[65dffrt!]=vJzEW_}رcWO$''p8ص{*++q8a1ctuÇIIInGm_ZE^^|t֍&Ov+.++ ?.{.s'}a߾}aBKׯgl޼cSr Sxz—_G!777B[dff1~y(ʰCYYGiUo[9Vg%:hlldӦML> vW̝;FCll,ƍc֬Ydffw^L&)))l۶1cFs|޽;Ç੧yHII`4i"%%!;vS1 ܳ Bn{Ht(0xs<7LV Mt%TtzTqQZ_|vXS@\[@$ONiBLLLPƿ6!l4 ikz=Y5,7b0`)*vh4TEckK5h*>q!-@ZrS]qWCk u7!Ba؉4~92r!lWѩc@PY|;OFF RH2DŽkjzdA@P0ɸ\n77޽zaoPow5g0"<0 I@}ujWhq(P|QF/8:Rԩ@HTZBG^O(X LŶT͔Ѹt P[Q9_!Z[t.zR" )^EWIC# K( ) ZadHiB6h5tA</g*jF'%6@-('0"uwi~? IDATb kqq7aÆ /0tPnzɜ9݀~?SHĬYh4>|XNzx3'O --== ?e @c=ܹxXr%ǏcL6ŋ!2ݻw3\bc\C9[noΛoСC1b,w^.|D̹AeY,XgΜARѫW/J.]œ9`05k6]w- ,஻"77G} 9s/bر~Jɓ'[Kq8\wD:vȗ_~CE=zpz!ڵKF}J7D|̙ëʢE =z :Ç裏`FVѨI;ng 2dff"&L`%}]tҕΝ;3|Ξ=m#>> Wb} ν￟xs0qD^{5nmGh$ԩHLLo~ڵ;3N:ϖVP:7x? ))Yf8t%[p?0a3*222 @ׅ-Zᅬp#Frt!6bb,^^7t:޽'p;wmS@efΜZnGP`2̙3Yh^XZ--X!y8qF֭ӟ@~-ZJdĈtNǠAy鿡h=z=W^y%UUU~pM7 $I4e a햐$+VP^^ΕW^CMElPa4&MIJe2ݨjMG/>O^$$$`28}4?)))̟**u1uT>+޽:so`[o5\K׮5j4?|\q?~}Z2k֬_caaTk߿? /#<@\s=baajҥKػw?^{-=JţVIJJb̙k <tK9r=:jZmS,NaRGlݺ&//DQQJ`OR?0_{P|cccoYKoxF+B]]}gϞ 4ϟOVVg̘o߆pe>u55KYf)ɲīJ}}=&M [["|@BPr0 'F$=H00t:]ĵV<_C'9;Sh1\=8J!9WCf~ɈRFRZZ ;V^s(?RAFf Ю] g 9/jܠR%+='$l!ܮYY$GR3jsaB@))-.8$8u$뾒̈́k,! [W$!R>!*xVp_l&ӵ`6?995,+l5lXKuѪՌ{Je(Whv߹5:? 2kl֢L1i5tjDO+!ȠqkHRH*4v$$Ҫ[Md`Z9r!FFjATM&| Q@^ @>f+p픖wb:M_ uw mu)N'..Y6P\ATA;l\֚5k(++e޼ۢ=z"Fþ}{q :GKbдx"Nf&_7/" 9HHHwԩ2229t˖˿߻`fUVQYYܹs*(O<s,o,\ 7L?h./9s3ɓ'Yd13"0xV+,zIHH@V@YYYP(#%**+޽;ktk[O1[>>MdT*5j]8e1p'Xz{{աIh ݲ-TF7g ٫(.\߇\Z ]vȻhw_¹rX+CRReeeaS yظm'F Ah|ؔCv*w|%lϖqDDÅ6c49TpYitC./ݼH|.cՅZ f(ɋ hm~4e:I0 *1.n={Q} DEA@z3x]Hs(D쾿Q> Z% Jz TTp=e$)Ne^7 ""m&bh$R* . ]v 0'k.Mv{Yf56[cƌaJNNfԩ?ܟn1c/sc}K8q?[n?jVN:?qk_;x'"Vlz0}1'ӰqF/^zG]׆n (T4,Z(bO޽{q]w_GAA ,M`t8v= DhZfn7,GXZZqܼN\+f ˞=.[Րz4W0$J;oh"\W nK w?V1qw3]2fXddF^}iu]Qbݛ ~a)`! }#I2$vki ~*b#Lp8S OI(0MMwCnN6Zaws0!Z[6S@:;,T*Y{_Aa:ݍ-EՄℚ ]=p.}UE[=mdYjظzzTaK$!%AW^R v[AW^yZ9!j@| CrrCb#F27״nfSMRR2(P(P(a젲$un/I5JU\¦stH%$k@F#*' 2/>F-n#*h9rP(L$;T*UgcccD< ŃBn\!W $$$s?_^7b_B5@q:\#WxBN`Z[R״Tha# E%…y[RgjF펈?؃BZTWW梨]:r"-;4 k5h!"h$T*Q)8N [J z8j!A`d% g }>hŇ*++q:?l ("вظx[ Q9iy GR"ː.B!ZȲ1rjmv[T$YdIOF$tuAoɶ5DܤjىCB!@8%f7~, z TVط.RXnN.Anѣg6ot"J2.B6#y~把2t=%\%\%\%߁㡮΂7);ȒLyy9:Դ~.GqG!11-y2OfJk~.Y,kcK YiltݰC $i =zV>R 33>|rMJc\TX"⁔#{]m<4N3Ȝ PX-*,Y)NF䗅)? Wt~&s6m6,k޳z޳neZ ;/zh2'@5R*TR3Е몚F{ų8]/$Y,AI$wؾj B" ] K8ѳd }}ctqU6&A PW.0#.{ 96!B{:Vs_s|\ E3ec9̿t}G9C='JJ馜켴]߱.-Urm!`}毋PWd]x.-Up\i2,֌*B"mq38]夐xa"((-%,0bCX2ꄄR"c<\%B isdG9·UXY?n~u*.~}T#ޢa{EYꪔi۟.j矃ֽaTi;ojN]2\UzC]>t%,a1k t(!+iʡ\, -LX+,˜4f:ioYZܼ`qUԆMAOXd[Х S.L>x@pQ9;}Uva'tR _N5]ynu+ϣԴyl' Օ mqB;pxQʉvv<_nUWѺdb;$Ysa#F,SJ)ߦ䳫.;әeyor}GUVO}륀rな AJB^44BRqð4>o} z/6ྕeCދ,Өs/߾C!*-Wp=rvzQ&&I65GYv%S3tvw248MP]9 fy2:;y[i\x"kH\GOLH&8z([oiFYfO>;.l|)Z0DDutMCS5TUCSUTUCdijnfDž;ؐXohT!B"ĹY{4Zs{Q"!sab0¥3YF9 ፩Y+!4fFP,dY;R=r QH$ 9tO0;;4Bttw095En,[lްymçѥ$LKĘ&ͪ "$Ӗj!9sz+XIÇ P%jΗ*dnNbDG::R%݄s<_~ʩQue \$K t>H,A &T7P%O,;ok SBc nݚF1 zOĉƢ>IZZ[Xֻt:虳d3YDcR]MA8۾kȟ(3_~ʩgTe*W5|d :$YIA%|d)FR-L&o|ɴ!k6E|B`U B˽gΒH& BdR;h k['dhH*ssLNNҞjck+/gi7GESUVc'oE/0;3#G9u)oQu$m5+x!ٺz#2"DVxX-F>pmu-?(u7a o0|طw/woISlFLT>7d\- !]=]D""Lj*GiOH&JϒnS whV3<4ºE%X\ܥeiRSH)Y~ S^h4J$a$IzzX~2M!kdZ \gz bDB"D+8A|9'O=WeΜ9i| 5pXg''&Qf:;; 1?a_R9tSSUdd!1~s\%ݮ|άVP |J\[蚎hQ˪d3*--\r%L=A|}"AU-qׯu#ԕ4ZWB)% Q; :ݿ?*˼mo 7H$b,Uvt~dvnc!0e0^ &2ey~qc- u3S:7kyW@r9EN*_9Y͟K8:_PW?JV !Fb⥔ >.D嘝塟>I*_~>ljoGSGeD^!Xղ *a91 mqGx[:*W3ܞiێ 9\]=UǢ+饓ܰ  Q]Lu"D!LxAZR+:sU2G"6lȆ@Jɍoh,Jk=VSzLCqzQf+b8{BR~mQ.bEE|U|Ija;T[΄#IѱJ ĥ|Ewu#ԕIW,֥kn[6)}><5+<fP"ݷi10[nٍ躎*'˒*(!usBK|xsI# )UtA}QNZ/6C?G.b|+sU#ԕ mk=_˪5%kYNe"pM …B^ h姴 <޾Qˡ,tWwޢh:U(ξbAxsŬC.'QU-iC]TW"<cr^Ey}!=/$N333&hoo/Ԍ0a+ș+ҬaG>tʶ(WWYEb=c0!%JZ, P,y ̗h ߬K^JxX/9t⼏Y|lEy`<_URo>쯊~+,ky{f*ׂe^A3 ddd]׋ niH$lݺn[Q0ohSJ $*#9{)ۗ|8J.{2)T'蚆b1G_o&"%t>H$B4-jnVd [#u#jXZR4NXJ#"ۏHV)D]W9`Y^7 7Jrap 3deCy-KuY_3h>쯬:e:C^qB^v5̗e^A3 333|[il &˒QkgXXP/y(3|6ʥ3LK79G8zNpˈD#{).WyYU?K- d R3jZE'8.vTWfXެK~kB6ס`Rc?/ vex[pY͗֕|a7]5VnW,%4,甉^SԆ_ȲKiLqEWUUBzQE I,"fYJzٔʥ9}5rn\ LON359E"`rllVı:R$;rUkVD7]ݝ\vեW191I2d çiika՚U$ ΌA<q|6֬_(8vOڍhjJV2uTO<ϓ/_$3p _aQq)Dp|qR; ,vx2d{[Z's IDATcĸk)CX-n绗t32|ۙ]UO3X"r\94Y a^AXN2/#KY "1, u6C[\6q⃂ήMm9=JwO7/?2hֶ"} GEQ",[F3( grbl6QEcg5N" N#``K/'Jv* <(1591cvS'Ro)xWp%XR(1'/>'߼vs嵗O|ywS{E o 7 ]x*h _UIDZasbf~x7cakWy'J/f߮&|P ~ToA,)gIDh,3  }Ko151šiiml6>QM,#ՑBhni`3|jCiVg V z\$౟?c?˯_[Ȥ3<3?{I7.HlN;j@U5OBh^rN鋅,K$/Kě'YXK~úxJW zL7 ?7W%x3t978-Ż^?آ4;~!j4vͨ-(ca]amm=sD2a\%˖rpAZZtm<$ fN3E"'q:qTg;Huѳd2xYCDQf s-$!( Yꭝ#zCVjQdYO_GzHu_9MH&Z QSm$=(<{J@tuE,E?jV2QD9s`æJ,c5љZׅ: LB.&'ֿǓ'$_]?3O˯m]C?{_dž ֳoK/a \ƫy&0=tGO׳d~C b|lf-729>jw$M(d2,JֵnQŸ,JxL95+DzuAFYֻ̖{:LMNўjgyr:S:Sx7P "CXC-モ~!HusMpFxgؼm7zTU(O? ѳMXnK/_J"WH6%y}Y_`vfMxQ~]+8vBW>\f.[%q@!?h^C?B?kb,w)HYn(f7R r+G\Nj/SϼD"ΎKvT%SmP˼r9E@=F#.r;aq/AQg8y|'~*+8FXՏ(:sdn.4֮$bhEQxᅮg|o-z{yoVB臰|z-&>✼p>j1=Ÿd揅2qBU!qmCˬU[lJR4t[:*Wo|I.|ŵZH<]I/̏ L nO?/d7dxgOc\\WܨQ,4#DŢLNL0=9\.fE[x}? i͵@ ֓w© EMyg\ \:ygyz΂$K-L3B?|-Z,|e{g`vDx~E܋uP|fW3_^/L>y˙p$):VcF*GqBrϘ1ɱI.u1xW]Y޿d-~hnj3oZˎK( S3S'`A4{n9tvwп Hg|t: i|Oѿ]]No29l8<`]sA. G~ m3Jy|Q[O;7vlW垡w}Ku>W^cF wCYB2=+ bQ2KB]yT'%-:ыLƹQ Ӆtt] %)_Cxus)^~{ItvupWp;@n~ˍ|_׾u^+`OSϳXOsn|uy=Kk7yz_C>ӏ>C{G;7zbqsWcԉź͟*k*h _UIpatl?72?C2NCXH!\LO=;h1BP"Z2|&q~]odzj44wi~If>~~gto[7dMI6?t5/0/K)+V pӿMM|D!o| lsiMM,[J$ɭ:{ś'yQ2,^C?`=frn^q[w˽~|pxuB4h\f5+VZRC(RgRB$$IVNz:ɓ=N{sdʾ<߳%,]<[w\WO}+-7ԣYfB[Y~2BSP迊WcH2wݣ_]t|ѳJw2 u+8!tkbC(Q|'bbv:ZB:e[`\:C:.8!f, lc$!K4!K009哖{sMW#/~)n6\ވ|P="+io5/{% ,tykɳx3lɋV],|y |_VX < B0T_˼qB1Yh[ "TRj7tHl_Y┊i 4cLMO^ JG{[0BEǰ|[{+o}ǯpFUUb1GzY$k臰|z-&>CC? Οe^dzߥPhF*!CXΰx&'D*16>I"e;k'3P! CXfpXs^-!&^u֟o__3\ !]-gpS&nx!\(=V;6$²yd a"M M f^l͍'SX:?O/@|`;pt;R;Σ;v$YχhWe9!tkbCk_C?e*ei0\s-}ߟfڪyWʗ??S?z۷?WGC꟯\ܲe}Kg=#yxUru\ T]wֽ~f\7/j iΜ*hMxgD^S; Z5Fma0x7%4WdB#Gv%`A,J]fI+oYdi ы>|o?1b%>&/#G(nx+V@UUO .`+iO+O=og Oo-媫&y?.W^!_y3{~ή.b8ߥ1_/*uDJt!q<'EB>dnYY.-x~/aa]Uџԛ+;o |B7ywK )-~#Y2!-q o7X~ߒHC[[RJN8kyi7x#O'>Q/_4t+vOy>J_Щ!R){y>ʪիȓ?ν_ne7n7~g<#}~Ϊի?|>/K`AuTM^S EoL$Hi߈ttgA)D!Bwc?.W2W4ƿhQn"CXuUIEדMMLMO1>>N<_fnxK.YTM\>-eŪUN:F4$:58ȑÇy)Xw~e˗Ju0;3C:F]]  huEr].fU2̂D+2"DV1>|Z|<=aX2e-#~#R1"D"Eׯܵﻏ3S/}nɤ3055I<%bkjn&UEQM{zXV~]=D#Q֮[ljhkk#(D"EaffS,u;Dc!@t6]'oJ9;1y!6ٸJ[H>%#ԕFw[~c_X45y->2( KƒQD i3n٠)]q[Ӈ\˖c:U*ZMw{afggYr%)%7M<3z@D"Xuj)OJ,R'F$SxW pJ6nL" ǏW^;w^̒KQUww\Ě5k9r/<TU%:ŝ. =%Ed‡sjY0S1D&&ioJМN365*#Ś lR#ԕPW]YΥnb;J)912:P#u3qٖ \]]]?U5z!)5]"!KE|=3űOyrh?Ak!Dd :$YIZF83 b>j̹jA}|- :D{kVY j '?i*w!V,3w9sZ2ҟ2k:Fd RYM&$r`Vq_^e*7$rUIA%|d'mmDs (A"ҤZKV/dY:\ ET߁3i5j5_iFho.?PٛBe "Drim&\Vn`SnCRy|d"r<}2n1]6cS_n!U_-_˼ _&wB?,߃ۿ_}! K|a "DcFG-,?zRM+ola?6G2ͪ{8kb rcKBܕK>KR1l ?.u]TMP5 MUQm-I#Cc8qȷ|uUK%B]y+/&JYrqwѺH(4-a)6=gZu7;d>|-Z |>eLQ B?2}cS{[+Jywq0UqAjʷN ]3|jjjeUfccwQ\pаK8:_RW^YG"]YSABM^fˠU_RV2=F`Iw"s @%_u[AgіHԕŒTt"g=:MBzrIE'*UtM3tA4`EDdNiRM+nxa#^W2;CDl6?*ˠDv[OJuyr@S5HiG)IWO33  7[@0>6A&H!RJO r(K0zzgYvU UgWl!"C<>rUIA%|dio2%Io@b6#9>1frp.f6FӅ5 J _d/dY SQ 1e臰D#$ fdtt$216NWW'sss~uΌw֬`|ly[Yn5D06oDsK3ggffؿg?Sr ?虳hJ$m{@BϲO 3x6m\#?{.شeç93|G3rn2BGH)9x fٲel۶ E)csxx9V\(< W^y%gjjǏyf6lXYf*.G%Zģ>\h,2-, Gg@`u aReI ,cϬ4t n[hf4N@[TiƳ09L\fYCslIX"@WNs\6wn臰|%/K)Kt5[a2fSAGe?~=ILOEOnVojrT;gFFiko.CUUguɖF(!%Ld~ r "<1H.ώj[{+h4il޲[6k:4_}] OĹ`f6l0.Fgw1(K륂?Zcаzzݻws3::Z6__/^xݻwկ~o0/Htw~w{r"Duh{퇏mvnv~84/q&fբ,u= Q>GYHQ2F21QD;6 ?08!tF3/ѦD!5 C1/oZFƩMWn?^?։9 {m_B:nS5|-Z |>^Y_]rVd_ uVkyߌ#37\^JuA<' =kW_}=tM|;AUU&&&g>5\ 7Wyϳc^O044߰k.o>|`ǎ|ӟnx(Rqs~D+8te}ZrHWjUVWY*aNi>IZՌ|6Ҹ65fg5X\!~["X4$#fVKa2f}o-_z0/yn6}l^~N-Zt^A4E"y-ňD"ܢfRO r0Tj!#EUW]ӧy4oX,FOO۷o͛7Orwo~|ꫯftt??fnn_|Ǐs3pw~Nؾ};⋜>}I>g;ٴi~VZK/O~/_0۷o{|+_ W\qַ8u_җ8s /"_Yn?O?>,w/eOu/B]ykX\dd|KQfZڒ" rlׁi՘!B@sd .7f5`ZX:5f$ S^a !!_f['xB(/,;Gc1,\=}]nE1t[rQȤ3,!P.8H.&Sq̟}{:^-EQe)}Y8ŠU!Xֻ^勴Ro8'$hJæMG>[8{,W|'X{oVoۼoy{~3>)կvZ5r`"Dڢ%uΛvz#vc+[YY92ɞ_.ߘWÐiL1f4Ux-BŌQYK/%sP5PK^B'/;!/djjaԩAf+r ƢJ"V S~΍w\s{RJ6ͫ/OĹ+ ٺ kRgmXD#Q"Y޻ ]Q" H`TMPŘAֶV.غ]׉Ƣ(šk7;m4qV^I@OnpƲ . %a;T[΄#IѱJ _6R9mq}144Į]|1>>n,?&e~(_L[[sssͱo>$k֬_=tvv-m"Dgg'[n./~ؖFUբd2hjj"f2p R)tB}H ;B]y2}''Ƌ#:hI(d4{59qxd,{/* C̔3>2ca gkbTnj}#sld2fZ30Ψ0֌=^ta^ *:|֣S npC z%<5+<#9v(t:LJI"~r w.p~RY[}qc^re5A,7, ٔHw Xh,G ELjsĶ0J `| 5ز1P%?6rZWPd;W.jv բꩧbff1::~y䑼L&Ç_L&<|_e͚56\wuH,A$K]y&iҖ=FTǛ̒t[* Hb~\F5`jdu 'f 1LJ cOi/?d9kWy!N krᶭ,] )u4MGT'طogGr5W:~aѷ0.O>/6C?ԠG?}}}|`͚5 3<<ߟ˽_f./O~3w<3|K_BmFSSmyz+g>î]ڸ;;x衇˿K6l3g[ַ[VvϮ]ظq#wuׯ|Q/sN>G6~%K t>H4w2 <E떓ljA(DDЖ]ӓŘkMDPUWpF SӛWT .* 1DDR lnVR.0lyV~kȗk% YV#y)u }}H2&2cSn;>>.GϜ6(J`::tk:UfTZ[KT . L!BQ {5K؍:GUkQ)92@p[Q'T^:1JZո_o߸/ٛ942h)ForCN[L@1#f$ ?\BX4IwQ7O:v g ~״),e VW|~9ynu`]҄GY]̹*jZd^Oqa^zj55w4^t믿"u$aM/{I=Zq4hxQ!BF1t83ex2;G{ՖU;duà$6 hKfZF3˜C>+镾\:k1yQX5(sh3c^B ]3)WfwDtFthn~ĩOw%wJkۋֿ-.tf|eLC>?^#iCq%uZfYEA)BG&A9KT% d.?1 _:8gnYi͘h6f f sc=LsGdUw)9Xȕ[^39vnF#n#x.c#j_M?ThUe3#U!|c&:5"Lfɨ:sY!GumMZj.ԃR=~ⲭsШci5x0-s: ao0frguc-!h@~aZfa(;CXC^~GPn7칼W0QrQaoBEF_T0qr)mq|q^e:-~ycn4H,Ch 5ýh+j:;[g&v.޺.;/&#ƒL õTVSYiS1UU)QucLϹhGBJTśVDץ1CJc2*$ht Ӎ5ISD03>K3.|%qkȇ~ +ȼ_Q/aI!nZniݔ~T5ngmӣ,=K{ᵗpms~.|⚶pqvvm –Hg g VZr-hL^|P媆,A$K e!aD\v&ϜfhN c/ F87s9^1H *D)1Sr>sݬfi Zc1FY Qa,_V̨i6 P) CsCXC\/Ο_Ȳ̓i$4'\ UrŒ+  R4`јcK{!TG_x]Dn^{y6n뇏D@/ }LOpqFX|)' }(22t%zXv5gGϲ畽$d3n/+KϿuoCm?K-a{m?SӴѽC1;;KsK ]{03=C65p:Y|]]8 . L!BQr?JS*p4EJ?pm&$[ID" m.7æf B5frqL-ű[Y8J"wQb5L(p6m Fʊq9#͜5Y#xЅwZ3fF=etYM~^.n x쿻:D%RR^xKcn}xPؿ,Ţvgm繯4 hcly!s`vv董\Bss%J$lݾCNgx#ɒH&8ru&&&hjnb y댍sE8rƠJ$KK; @GWk֭l$L̓ϢvwvwlR^?:i[Z͝mǠrF We "Da?D# f`;(du$EMs#tǐJ!@92* n%xH'_~^BYRtгҌ$0;3G#t8neљ"Eiim&Ǒ@6a+{8rLTmmq$zBWO'XL&_Sڂ(0xcgϧimm491j477y ~a c`Iu2lDUUXCdc)ۉ -ޣHEJjMxѢηkh[T5#t.!*4'&Х$H9̲&Zq1`Vc˼B>qGIKEZ͡K6̐4t4E`.C?yF@g-m.jf9#'?6Sa$j&IulJKi$BJSSdR)Kddg}(h4eM gb|f@sy͆w?|j6bmc1D#MVEQ DGf5|[bGQ<DQPP#`< @&^v6!e;$Yprhɩi6[hQj?:o j(CJ1^xt)ХDm-MHE @Ǒ(` s]XƉܞ= aJ@~ɪӀCn8,%9"0T8q3̰e!!/B? E`/gC_K {*2-Kiӈ*yn\9y|5-nA"_/V_ B0&H}cvš R)~_y|w}S}[uْm# BPBڐri-PmBK| rıc;lݲdj1լvWҮy!?y=,V+4 &'$3+[N*y ʸn/4CxedSFQi9ɿj4z#x jT@TgΚr.ĊWpTm/pqC./>Ovt8Z==&=R_VQFbh2*TAQ>ٹ=Иs "Flv݉/Ph{굊S "\. FJv숕(n?n剫^ ذQq]+% IDATU(PTRD^~u&jHPV- cģKXs"СCGX86%DK^\1rLUYB$ĀDπ_;g;(0^2 ^;+Y^S=aԼ8wDy!ǃ$y{hU8h,ywvQb!բ\%< 3g4ҍb zTf,jS EQ\QQ]!B%HC)8!xm>!XU0ѡz@˿Ebᣩ;AQI{SC)V*_vi3@wp‡nIqvCO /(xE?fU^0|{|Q6xW\%Q?Vë#x ʪAZ5apZE(be+ݦE-(:t, A  DA@DVPIZ{!T P\ ySJɛ<]wd?Z>V / G5W$oEi^@="#-[! ^H B+M4$=:tD `0PfvHr<1ꋄW#ZYK8񱔵C(UFUV7"2XT:oA 8qU!@lm{ غn˶@CCD N9l2)mNNӏC@kh9V׳Tl 8.\.9j(-h4bR˻TX-N'`E%pHөl21pD lh0vK#[6z6;Nl fQq"uTrr5ۍ`l2!N|[,\.ǣb6tx<ds,rbHg̲Bmj! -E;jm+=a|,e ;#KPQ:9JCW{_.ĊKyqC01m\[?3s.rғ,j֎[ tNp}wTa6xϻgr|Ӂ} !a9z-m-7h4W_g;n#EjZ PQ^JMe%&ѱq޾쬝5%ao>Ck{'IV+;m!-5IK[p\++pݬ-]djo#cvZa=գǸGIQ7O~Ԣ6ծl6rJ՞$n9_`Dl~K``]u%Λgfz{4%9[7$195ŅؼiiiHĴƙs΢Ʉ&z|>o%+3KTV`etle=3gi$7;7AXM-mav]9>ix[HMI!#= L^niiJn iJk).*`";+6txYj*+$ AK{ǒZ;߼㦝TjQk>xZy y@}.'XP|8ϫW{IrY9]C! ώf@0bod^'Y]"_{$r/N6/E͚:LF }#X-F>{xt;yts//_B fߺIc/-kL72͝H'C̜N'{ @uZ:tt:(/ctl\RSFUhX, H\niE$0L4yc9YLNN119M70@O_ ˍO`4IKMb06>Y?$WAMU%Uk).*`rj #w&&H}w2>1't]Q闚 HV}rLWƦf71kS䰾e:{ ط{?3OLb۽t>&7i}n!ЇBK{o=}}̐M[G'#PYQNwO/ff0pBNvCCFJ 1Lttuqb#܇mf".55kVسc;XLfvoJJO_?]W{>4T_?4~:#<߼ϟ;h.nݷ9wqv{\=S))󟼗7߾+o;?}uw^Ur!&>` 8GSƘްЃiAZ24?yƙ|rCs^e9C\r54m?!:j0.ezt.̱&4MEyA&#K瘱;=SohXC7?{ e'rftj{ֱS)݃XFBz|RtLFUWEA~*5s܅^s81xQDr),ȧl q8{?mf-7UW{HJN";++29YY]nQ [Akj0xI184LOoi瓕IFF:c㲃6 99)+QDk1] IZ)ˣ ?AxIq%yq&:jsѣc)k5 **] `H$L$r=k ãLx {dq>Z/bM0=5MKSߢjv}vDVr@qq#rHd128f#'=\ⅷZa6W/э{<(Tgap܌]ܰ>=4LF3%97rK+yy~ήn(.c>v#ߤo`0rTp:]RVRLrr֓Ja~-mNfFYYYlȜ}x$ 1.Ʀf6֮\Ou1080ka6`n΁ᤰ < 0>1AK{ Mg5HMI7x)*'HOʎ[h@jrsEΜ=Ooj`gefPY;]W{4ogᠻ[vD''XWUln]5cHRtN=x<3m!jQ8шdhxΜedl\hxu\cAcģ8 =-r3ͭ툢AM 0޼p u]n;o6&ggq.t)<;꫹en=$<6/v -deCQJ. p7Ojm9+<:5|EC{ߐQ|=Z U+'0?0UB6Nꊍ p|#$+nBqlںLV&'Eڍʠ fRRYq==ݽt`Md.m6p\  rq9]\ner|@ZDo5FGFiknvSS,^9y9QYSEvN.ae'+$IbW] 7o^: H~V [ iR0o\❻krٲ<ڲ\.zuT6UkLa;IO KpGE8.>gdt;o9$6/4^s 0:6Fٚ5  5z㡩[6te%ǭ35m㶛ol2sr+YhI54*=P33~SڶcbbrP64"e$x6mwF>jazƦZGGiikjo`0ƛo7?7Z|ˆJI䓛jH Qy^nhez΋0>9vcs5?!y$vl VUxV,C5ՈVy=at|,e^Bkm0=5opq8D J7SSZ-Xf<لd6-Z ۃb($}q<yy j+.- yx<͗RS㱑19׃㡭qSE1Εa=a#anF2w_ Me4}])W(9Tg+.F ]ܲr}|b9NDp1`~phl~ i9it 7tߴ4&#F4ОzzGxc9f"%}CW_7=Ȯx 쨯/kz¥z $?882+;9JCXU2c\-̢ HW. ,q3zTy"9hԓp$WRVQEAq8LO*BKټ1Sllm 17l13>6NOwkZdNzf:v@fV&n\,/p=axGDBwb9v/ Y ץ4<^|⫚_#'Y;Cw,/O֎<%?r#xXb)N\:ÁVzvfQffZvy3@e,;;stiuu4m( 3,?`85JKx"NcFq!%շ zơxߞ 9%yalСCG<i)Iʜ%O@No9y q^XF>ZΡd?Z>V s%7ä!\*JU)]@aqc*yQV iyyg340DZZ | Ņl򻙒Sٲs n[Q$5-pR_6c^~.Y si9-dMP[U{B@%G"!0-'.䉛.;ɑ`yU!j򪡩ZOH !3FQ&"H]&|7*3e`ЮP0LL&z'sQbwd*ʗj_JsA)"~ χ.h|,pqE$ tт1Lh#E<\m7+ _* **\~8 zAoދ`qN.mhXJd/]QD<jϸ0߫B8e"Ϋ阮v~>Ꟙ7LT0FeuETxoq#Zz"x3ˍ튛.;!ss1R$#1wMBȯ˺xJtխ`ѼGG+<Y(瞿$r<*AC@XrH WTqFrq\q_݁H$]07;cΙ:FDy$pBgK+ZY:ºqc)kU!EG a(||.R>jg;mbBuE mm=LMMp849AF¸`<)55QIb댝̔Կa ٪D"颅9 Cj8jG<9JOctttR;ˍU%|СajD+k0:>V_w_CC` ר!ϸRh18a6>gwE[7?֮l 8!VUū?jjjjx1W=knDUyA/x[M|m!=uI IDAT[޻TFE*^'GiJ66mq YRb̋X8#ڝh` ͉˓γ:zQȊ9G^CC^( s %iO'* NrXQf r/R`uCbIA5epbod$yԼ;8C:jn'q ÷K$i=a|,e^_@zD߾;7&IZ䒇j5ki.[-u)`(k?%[!"^_!m̫" KQ&NžTX>vugG_lyw>,ul8jVB-SkGRj}ߟW] C57, :рdh*yijlv&o IuȞ?>:Դ[A4E_OR!hඊO{ćXa-RⵞwI$ rKK˲(o@BWz}. JVMauB)XT>V + ( >/ zՐ4ԜNHzX=t$Asc nIC#LNL.r v^G0ZZ0Qt a.H$:H$:HăWLA~D)Zm hʛ|'aa+YWy[-'*7&O|/0abYt:ѶMhԄ}v5e%-evf-ئmQVQeh`bNylfgf)^S$SגMsc3#x<U(.)g^5l$7?iZ.duU+o+JmuD\(%uҡC%!K)Vu~uFRjo8ޜZs 'q\>¾˕.FG))-oj7r299f$CsCTTbZ0߰ WگPAFG&#+uU NdIfjDQd>G[sY9YIiy)E%LMFXCK$jy":^:tDUڈ!tfZ"!CWw)=*BɷEjRj`; k ]`yF}R^QƚRƐٙYSX-8N$$fX0͸<#ڕr$+))X0LXQ1[,ځ#`4IJ"x!)Y]q=ÃÜyڋ%?$q H,8'e9qH[I>mK\K ם$՟)+_g(e-Y|}ƃ'C(cRjɭcUFUK Z>rm Z}/pRfuDV@rJ2dfyHXVssF# `q:8Ny(GY[Bzy N&23)(8]9_fv&[vl woczZ.F|~Ɗ8'Vk ӹF V㺴UPg`;o ˭8*5d!$;ϼ&=aWMS6 F&.B1p^  ֏\ /-#=HL+޼/AS(-B9 L'} %5Ы},&V^!H6~m/=/sUΠ @|s}i7H9LdA jb  >#BI*(^S, FçgQW_0@jZ x0ڪr nAdH@jZ lDrrs`0Pۍh{%`=Hbfm@eM$!"yH-$L^'Y]"_{$r\}+}Q@{m>;[-8zqoo|>DA+~Q}=;}M|O]?u<p<;HOM_#$}|?b.4u~MEY!/>kO~EyM{@{+z` 98{#ܼo vn9d~'/K'0 )LG?8-GIFZ ?gt|5E}C{6GInvB͕lj@l?߾zw"ZЊCe`mHxb(+y?,78SQ!!ȅ[=x߄S-qAT !XC?T]`ĒdŚdl`Ed6cX BLFV Dٌ<_d2ѨX-&7[2& (˴X-Xq4?3~1!:q㧓0$:H$:Hd/gwn`:N^;~2k`2)V{^ 2bfv{YZ:z)7 lvFǧ9`dlo]$BG\M7lfpx'=וLiQ.w߾Addl EOO3:>Em~Q &f %y<1vX+ q+8 ͨ!)Uq z!ᔉDn|AP]pGwGSw!ǡ!+5.[`qAXKKK'KCkڱ _/|^>ʎVl!!ξ]-uśWxJ^URN}JO$Us(F87êr5V\z@l4HQ':tXTk hcpdQ'oeYՑMkINb ]B*zOLsgW8Ԕ$LFݛW~Ⱥk\Ȳ$$/p8]8.?G#'+IYI5^?ߜEd2߾vI.#-WS&lLfDQ&gx9HItIc?;7( s^[wʲڮ;X(V+XPyFRj(CUCEUE{,@~h ) /AnIAfa?A;ݧkEغ u;K~I!l-0v/m>;[I??ݷ?Nzhn[og_?~j*K0Dr0{KN@jT4,M&>|!s3`זDQV[ٿk#/wRZGQA)|V<{mkK Yq=yIJd4({JrU}As =-$sҰL `^C4^C?>4K ycc Hٌٚһk,ܩRFuDXVܬ,x`VyۥYsu"Z[mĄvqܸ\.pŽ;VTn(JtS/ECWzL*o~jz;aU~)Fǧx[J"py췕dg -NUHlΉi}Ś7C:gNGG G)nnۅh007xˉH(\G@)KUT;h/O8h-d?hwAJA5v%͒yTAmD5#lNmC#$TExG?pۮItvIK4D2./++IwK /wm"Cլi}YLp[R6PJ ABDC,W Elȣ'\)oRS#k3|{ߋ`G}'xi %կ~׾5>?r7y'|3tww/IXt[V5yp!.>vZqL KYW3j!.S4j(z/J^-^Ka~]UW\ ᠿ /Ú4owI~`pOUJ0iXJ j&&Զ~RAVQhBd5dQ$`rrn7o6Ǐ''';31Ξ=m믿jeǎKzo[rrrgff{9زe P!k\tO|tIt>tYV^i 44HjfsFj1P|tYۏʾ sj/oy/!E6P]򱔕ȼ:]?0XJyZR$S&6N&'NFF B(c3,ER Aq#El6wq?<Ν//'6W\_:---<<455(2N%. GQg}/'?__xԧ>j'_2 9Μ9׾5Y|AΜ9Crr2dggկ~7'?ri;fS6 "$INee%{/?O馛ؼyy,xk[M|"|"^y36;jm+=a+<$yयo@>ЗQBE"Ρo 0#*QbRYSAaq!U5ؑζ+10m~Em6Ν9Op*$p"Nkop)'Y; .rIFGsQN})9?s!]˽K/Ļ.N:ʼn'x衇AMOO<裤zٲe 'Ə~#HWW/ԫPWW;Ʌ 8v=###)en6l@ee%flݺ{*y\r)x<lٲ@UU߿|tww322O<ɓ'yf||:~&&&HJJ"==BJJJz{9222xx')//pꫯMee%555ȓO>oojUƷzL~)etq K)Vu~uFR֪y<~CGe*'ϗ.`~!o!>J/b>ܐ .N_!v:}$PX-#f{㆝wʵسa}(NIKKeӶMKN@IY {!%5۴ܨLIMnSmt ʢvOzex:٤ UUUQXXO?>nVz)ʔa … ʃq0L~NbA$*==J>OsU <-ܢ0k.>^}Uy{G{~fZEI3L8N*Ɩ$IYnoݻW)j|hra;2;;ˏ~#}QN:wn{<{Wdyƍ~vסC5_JXʊ.^jD+k-T`y/%I4|,e=~N/W^:9P[V8Ϩv:H8srτ>@Zz*ݝ]45\r)Kƙp *zdega61z93VTy.j鐇(dU %?$߱0LMMUíʃ>111#Z[[wKZZiii|%%%dffRSSþ}hnn'> ~i^~eyΜ9?yAPzbO>/~uGm>;[j ~?x?s 6 o(Y周ǒ8K8-&#K}j|lL0)_0*52]VcaffM?re "M (7rƆˬ)+der|.olN8͆:FGqr9jjkdM1WگPRAdtdڍt+]mBCJIBcOemI.pL3.;ld`t}fQ^CCKV uUŤXhblFyq.iVں1Dj;0F)VJTno+! 8Ï066h2Аyy*: J TKU )N=yR\G33LF$<&6"!N_O/QJJK6e##+l͚rhrevIV IDATPK噿eSǁ%$}„V:t Ɠ#16i޻a|jηDw$˗Nq`G 3ںر~R-~uZbw4|,eQGoTa'|L;LeNjx'ܖ&qDC(`5* q9]#_ƺ< ш nX/l߬ \_N*A~k=Ƀ  Net6KU{B@%G"ǒ>\{CUlXHu,"F +Osor2Rqݬ-!jݵ "_/bR-8nkPήM9(-̡wp~dj<+']j@l?߾zw"ZЊCe`mHxb(+y5s<x bÅqA g%KGQ/o/` Pu]H'l62JBolR̦BD$1]!/T_d-8"xXLLLDYHNNAW$t KKK<lTC'6sЁh`2wvzHKB,6d}ٵ+X-f FAxC^DUI=6>WK;k {L$?N"2UM`WeT?GSw!Fpa204DJJ*(._.V'ZTYib<}B/:dD/>0L>tIt>t=(=uśG@B5U. V^a>©mU9 hm4|g6q8Wur9ٳlϡ֡CŠR$9Sft\fB֭-``dmBU:inZپ\sng];8Nq~fjWXphʛ<]}H {ÞCεX/xI``rRn+p~V;n##Fݦ1\ yh ,N$aφw3477cʮCkA]#+( eFlŒ23l^he-RC( h86\)c)k5j_`4A?PR2 T]yEd4D-H9“P># a3,()ksv-\W(͏u\sm-cc~`9yY]ٟOZZ*]vя~LͲ?c4Ivvx }+_&'';|UUUJz[[<ٽ{_&~%ַ_ }) OM Zµ~~nqJ&%0"wڲI@<$QϑO]Ă 2{=<a#[ *(;9JCW{էL8 ^ [0`]И4~KxMW 9غu+7GCww7l޼iz{{ظq 000??eV&&&8|0Nr(..\Baa!۶m+`χCx<8q2zl߾Q…  "pJ{{;CCCm8{,=lذrhkk۷355ő#GZܹ" ONN>Yöm۔ZtBU.m!- .yW5_`+yhɊ1/Zt8),HW.\^ǦF_pJoޗG%!}9T{T8M"u_0`umyߵYkxs j0yT$:V%'> 줧O?MVV/6099c㸮l_,7`(UUMI-ؖ%E.yd)8Ȏ7زf5j%6  ;uv?`/$`-gδ{斳p"g!}kq5[x뭷`桇䭷ޢb6:)))\Z!B}]86N:? (s'C[·a?aʊ{aL % Bnܼ%d?N?ؑsoի䩧_ 3o\;ݟϖGy =]gg'<w߽<3w߽Ϣb6۷n),455aٰX2ٻw\nx ݟ= Xb֭oK&v;ŋ|[ofʕ!%Wr]xsˏ> 6nʳ|qC&EWrו$IvvڳIWL{aCWa%# ڼCBATBЏUt`p`Fvn6 048uJn~e_e<yLaOhf\Yd$3BFfƄWNGT,pb|99F#zL&*ٳ+y0L̴ @ww7:/K>Lss3z+mmme:pN;Dgqk$Vb~uPTկrwqA222Xtna4СC OeR> ET"(~_dIdI6?ӧ02bd3I'ZMѨΏhnc:4$su \hrl)F5124Usg%=#45a0)UD2.4: F=Tͫ[mRY~2lV5jE>n \#=]SR>˿:d_?M,]EZfBq9z{˦|v9N3uhx$q 3Yd> u"Ņdfsq6iiTΝMzFzdu1 }2-q݈.%r<Ӆ”bb劉;hhh[U5]}}=^ܫN&墿XWή]V%%%%ar(P$It7f#n 4tz]J%1wbQwZnvnWvEmMkg'Ȳwm444P}8_Wڂ%EDх1D[[mlڴ Á[|ߢ2ɟBʙ!-^pJ¯9Yeb:֨}2nKn-餥22447|2N'gԱt;)(!/?!Y*d_Esb;ٹdfY8ϳBbgGTΩ눕4h]WG'tRtib*k1801H~aͣARͩ,XQAǂ9s8USKbV,Y^s7"BUUrq֬\AF>xb_f'͗1臀0 xA6hh oBHD(5"XG(|4jG$=g̜n| w.]]ݽV]0GOr0g~b;s=i6@+ESSm6j5HnAc}ÁVCբRp\huZn7kiorGVt"U:O/)D~a>O!͜caa L!C{ XnR@Gg'{;"=B*~\NrK78r-Ehb)a&7Kr}OρTS0Z0θꑥ 'gaOI*Neobٜ$KVKW,rb"HNUkV248ĩaw;tbΞØbpV!] z#VF#vN% ˦oS6^O%HVjrpݨjzhmnp z FL nKV&奜>TTUDX~?¸j.:{PՑ#SvLq/1 v*v1JLKK_-$]CZh˦!fұN夫F78{! _r9C4^hfⅼ;|d|?a H7Հ7lv~#Or׭7{ f3_򝼵},Ok{;SSټa]w|۷Da~N^ΛKey)e?uyds+(p֛ȲdR.|>}#ͩq"!,{"$!?{F/oS1:dTfNYOx{ ;iP]rӈTgx.}l:Pޜa^M!Q4GO).05u\.r23LO[zp:]QWSGG[',7s<)&Rikmn# f`0jicͤSfo9RͩtuvS}͎%ӂ[tԌfrQPUWwO8y Ǎቭ&{`V4t˩a@\y^k:9QCZj**9V}nZ2ek35fgOx/ay{lBnp\9Y\F> =Ш5\uJ^0r6o+/ /;+W,/^f384Dk[s**S_?CUE9 ae|Geu[pg9xDdpdld'!N>wPP z!bI$IC8^oxΉeehV],Y V`Vi1y:/'+' Ih5H@QIP<5g02<$Ij FUFyfNcӵ= jZ-UEXl1nJJBRRPs<RXd.х jau #Y=5hh4NQmi٥ECU\t%!1bB r0hj45/5KLEzEAN-kojEoubw:#GK! !^DWcHhX>2 B@8_hWF@BUT*:}B 3G/ *z}`mHDRt:8V{8^v* ^nM=eh4L/TBC8VLon?/K$K$KRn7h8!pqаFa~U%=7b~U%g5tnj,v::i핤qU}Er5Wɡ'S]n&Ӓ:h/b"ay}]W%gΠ T*T[6|0%s;:t&SUpK$ s;@1n'(P@@ti<^yƸT*  QfBE:{(w\S\P$InZ Vi<l,8N.b2PGuM-YdZ2oh0?^ S5dfZd``2-h4jlv _F23f| SNÒN^Ngj&O ƅ sΒ èۉ{0hnm7rN@NnQ-hh/4Ld1B-ǻ Nq8W2ʤ@jeo?/~k6ڀ=In -(I76jdC:n줽;]wv}n.{C, ^8E_Qg{馳RzEI!!v4uOOdY .(X^>?s]/c`:u:}Wt$ᕌ2)P@&_.VX;|l]ac *:?"S3^>e] (<֛ %s瀐T,:%=\xOP5I*+VBMgʟ!(|bMĚQ2-@:)n&')^y2і.EQW~|q9JrXfQ(o Axx"pj8Lw4$/IRy 6v'K'Pu*@\`@Lbd}ERnHzA0Qo5WnO,wt-[$ynh(`"~N6>:儙tqJiwtfL)'wU$@A|fI(?uM7 >|'hu[1"YIex1";K[X)p"ć"#ӕDt݌G+kcm2Juln |'DnYcp4Ǻ4'KCVLӌn &xӶ'2 }z1 #';.tBYTfz=~(8M3 IDAT3AWf_}#ݍFA)F h)~2ވ@Wfpw&'A+`sOMv S^$BЏUtx|jNb;, :.@ʤ^žd4~&?cpdRN,',',Z)ixRѾT H;֕/C8x_>+$THCE=??cFVlv{Q$"_{ 3rՃĜ$AZB+# h<'ɇy'R$CyBu(rT͇Kyy-Sx!Wd%d%de:x)bQ~t%ȉuӭ+ϼ}6;#'8QRLr;1\vQi,]A-,XYIXR&CZ Q&xI+P0Cw"XĹ |C#GO?8¡F,GO'\}ܢ^T3H} H8]ɿ Aqߢ(UtcDa+C6n 8HɈVapÉtv (H]C#vNյeiHVGnH].ňpJgrE. Ik;qKHHNXSlJ p 7ts%KV;VN.QdjgpЈ ۍp!+}σd ?$DL;pMynC800[TftPO | aUFx$iش"8FRnHS"ҧuRL)gc0ho Ƒ PUh\81"9YUri/SZQ:nD-ѷ$/u<'9l]NX% N4JHltwu!"jr5ID1#u!kȤfQk;Q3TVT3HSK7N6sd#Mj9elr>!;3Glt (xv9L7qh*>9XfnySu-esEXW6V/Dmcٖ4)f݊Я/8xA}+η( ;YprxA&wnb:6VJ Pu#sC/͠8UC n;\.2X:ȻF{OqYfQفSu)˧0VNN ,? QZ-t(`ɜY #q*4߾Z%]AkgZguV^ˬ,Gsn_OmC9:ȚeKo졤 aB[vlK*WkBz%itM9|IAC8<*U2rիUj[TWdI$IC8^oPX\4Z)[|@*ܢFZBE Ytwb40 ,XQQ *ZE؍#';fBYTfzKn ())pv#s344NÜf-q":x'k4~8]Ek ͪEe ܽu5j )(f,C@NIٙinÌ$IN$I%1|a:^=]7^ANK{T#j/Nvzށ[(/櫗p;%Y3lYS$ŠC-QkPk4=Kߠ$hQawމ 7?sAjc;&) GJX!>?$p89'@K/Ēŋ;p8h}oII(봘z>=TKA-?>XKQA%3=A}IHt e[`8͞#uu 8XHQ[ar^0ZUdQFlb8Uw?4ZEs{EsYzh*GNtRh ^c4H) H;ݼc$BzBCFj"cAŔœU ;|m*~NJ[H|5~,E"].^|y<ʕ+Cp ud%de:xILQq߭WPQKYaFķJ(/ߴk.sK(+榍Kp8]ZTF9y5i)t 2ulZ=~ͤpttljfMQ(INˍu֭"/LI˫1stz2fR dY (~wLTGN'8+eRIp  aAR122;rMWGxc"LĆUs< !8Ȳ.^BFW$wP/mPa0̨Urfp%,7r&RlSxf''oVfO멨Ows6o̖-ע}VrGV?Gk$h$R4b=^SP@v:!"]Y֥!GaW! cX5 6!|O[G@Jlc+^ưH'D$X޳S&c9> ̢7:g{vSU5m~T3X uyr+I Aq1xIN;|M&jeWNOdYWPrzy3OWdB_eA6@p(΄+V>O/H 3 SR<';Ė-ѳNtL @pRt9!e\gRx0ERxa {nK~OOdYux*^B?aʊTWSR'ɔxq_BP8yd_#cT6EQIUU)))444 "&n-Z4 9 6nRfww7ZH V^ ESZZ3_uu5@EEED?Naa!999qԄᠪ*JvFDQeYGVLngI{JEUjs}544 F+e1tq#0kygZ8to*~œ7D7d J:p\8H^*i|!DjǪkt:iov#"A!#V;wknj+H&M1FȾ #2;߭e˖sN{1|A~aÆ3Y/ )nggw/x y>Ӹݲe ?O7ɷuȲex嗣%'|»W;yyꩧԃ'&s^l4T*5zZRajjkEj&',',>R #L}&YBJ6x~};>>e]  dhy$iNFF:(X߈h 3;+̙WJbhpddu`J5ML!a&kSC8V;wEՒ|mBҊȎ;xGrc+p8xY|9)++'kaƍ?%KO`0xgٷosؾ};7os< uV~L&O=V_|_f|[Bȑ#\lݺo~|IRRRp7ndddvoK/9|0_׸[Yl>_c %%L:jkjԴ4r atL|Pŷ$t7Sd| uӭ+aky~dY򼫌ʌAI }o4!'9WI 5tvN~23-\li z12<gѓNg*ؾ};_W Igynfn7?.?oo߿wyϻo>N8]?9v_?)W_͛oO?MMM ࡇbɒ%j  ͡Cx|}i4j dų#nTc2f֐A RrݜmDm3'6sbNW#x/70Bop}Su4t!ʞdh!+~`JwP\Du p,9Jڤ %TN&?{0{BpoEeZ93H*/`y8 !<}Mx>^V Ivn6=]= cN712XV?>/o4ϙ3,YBAAvI^jbh4}Q^|ERRR8r1. >33믿?{Ě5knn.֭fHCC[GRf, ###kuօ=Zd";;ܜ[FFZ{>埧(!p;]n^~s/[9w{pg\e?Uʉfw?9٦v>3.}ijy74wryDMOwCMY̴]C)tC(WGe6>e] <#|s51 PnEd q.*L|W^PpOfB]HA@p8`VY1i4ԡ2 V3NqH/JtMs9Fzx’M`Q@3l裏(--g|{c޽!_dǎ>}M6IYYw^?)z+ş777n>CY(G},]{RUUū(=r W^y% `6q:pWc^uZ-+WѣGw˛oF!33=tb ;^}U^z=ڊ<=p}O=uuu9?xs-[nz~ Гk׮_L/^~!oCC+Aܾ};w7oF0xqP/]̸R&8ً +UNvfj"=^׮`htZͪZR'qY\}<^x3ʊY<'a"|~MGf9,*dϑ:N_$3DQn_]᪥*ϧo^ys&&*Ksßp\v xZ:z9kHK1pe^˹ ,,dZNE:ȶήc XTUD%αco5nذʒ]J$3r{xj~"77ʂNz/}FC{"|"˺xpQO(M++@O|xxIPi4hZ|{{1h!h B8*ʢ3Qc]48{Pi"&SV">lr./xQik --\5v)(goVFA@lvZBJ+JG$xI'+ݞvnVGaAa9 NbÆ x㍔NII pBRRRx衇Xh~)yyy-}ܛ IDATyyy\.RSSyGXb6-[zjT*ϠٳgԄ"''uq]wq=ʦMh4޽ry1tM̝;gR]]׿un6oٳiooŋ|_rqn^Z`0P\\̝wIee%撓7ҥK9z(|K_ƓO>ܹs9<[lvn[naΜ9\. Xnwqϟ /_$s{9z{{9v?0۶mcppVGaXB-[Ve۶mtuuaXO/ҥKY`A*SA&L\lr0.c*]tb1T[v?:~}ʒx5Je|6[Dž&tOt.ٜ.+Wn1L!N<3ZlK9>XKeEf''XJEei.Ͽ){ Tm4o[,K)%D]S;[^W7pdv}XMDzujt:-. -vy%o|x<~~[ZE^v:$_ݶs߷a":zY?Ăم QUnaUh=VWVQUV/m%$KoGPN)yx[~nZ;;C?'dv `MGO凑xs<0UUUϢV1tؙx衇뮻p 8>Q(3NWGAa ݇;HŬtZ5.Hk{/m]J w/ߴ9Dr2@4''()ĠRVM=.RZMGhi\5:JxO5+M~HxNY>. snΝŁ8pUy`k?Fj=8!.tR^{Yt0HwB۷ڨ e&kE@@yol*3?JШ5jT*jPk<èz_Ax6)4.iT:c& @>!Tn^-SD?A,WӑGvvvXc0Z~NGvv6yyydff/:>dIv>d?jʅe ѠcQU7,wa5fӴtqEfziMmfP^͕+uS l\5UW-Ĝj\skWRΪEe ع+(􋚛efaU\dԱpNBM\v.^֯8UED;_ENf7n\D[W?ʅe,_Vy.] LKbU DVe)+b9w3lsÆEu?`#=⇐ ,R}0\Bk=CF@PtMQpqӁd#Q &ʐQ (<2MK_)UlX _24*u&.I! 2`b]_Qtyڃ^.6n܄2 Zq;F#F˚VԖ wx3SSq8W2ʤ@BV?z)|/)%Je] Izy3OW*[  pD|Z4,aaXu|A}>H%,oޕwfe!o8 /_NI:Q(3RW_C}a):[oe%OpL?ẏ8t ɍ& ͒P~*nF !\y;Lc8zK4LjTgx.}lc2}g^+qvJ"n#čX'])P`B#$Vک-E1x&( !~OOdY ^@ '`[u1FިReM~{?q/%BnܼT!L6$LɊ7,I=~(3NWGRt>7\3v] Fܷ_?>#pV Ȳ.>WBh2f01& e臰h4rsH5]<0 ɄBE-=c {bF&dH9U&'=ƊXoNzbNLW$\SqM$,',', 0j=KڙO,!O۸@+ t^DC(IRHEȲ1 0ywh9質! cAdIv>dIv>dfl] re]t*tahָ&&O*(?uM7/O%O)Pe$u_LTE{4BN7шbF-R49|0{v|Cfv;>;;wqpAz{zTFpdćdΕ2)P@qvK4tc;AISk7n!GNZ;"%G{W@ށ်z,IO,7ya9|IUN./R  h \0#nRx>ΝđT?EvN)UĊ->]N6y Q>!'Sa"&N2.2)4a顷ۻEP\Rr"GmjǍDmc!Ngç8/w}S.,8 ]yD7HEqa[chY?Y֥ !1>s)t,&Rʅ͊{3" S|4zE$=g,'/\T*F X),.Dop8~1 S{cJ ,!eBuzfu5Yq qJbhF{v]P}̌TnX-* ,_AψCϟbw~"r,ia_Q@gGOgzZ:/l K3Nrb8qTVֆ_28OH$@n j:,!PT4jƤ8jڈk4cqKGMZ7^~Poˤt*5Yp{HJ#)!_ )!d׃ (b9e3Jj2NOF% PE6tZ q:͙LFJ=!N[4#`yl1,>P^!H]-3Jz`tOϦ}UM4\>TY< [rd9pU {$Q<9/ L1 S, h4,sXxB22ӑaDCJ+]a(hl'FIeGv; ѨѨ5Z iii{*8l5Xȧv8cR6wU2}R=}VZ:zI3%7x9>dٸ̴DjijfL*jih<- 0gjmlGt:5fuH0P &'jH1I楣E-FE,n@߰X2aQZx ?Mh^TjEyQը5AJOloQ-\Z8S,2\^eV+99m39*!"Fc2mmmh4GƊ&]#J%`D8e4r3M,5dN̤O%Ք(f0Y'yd%t̚ˬ)9ߴ^:8;{9X1E2pv!-$ŃLNAհ"Q<-;˱~ R1)x30[l3 $N^05/-FGRXt$|$eE3U zw)6 mrSC*]|GhO6Z\hCՒn2y[1F1-$t8qz-9pm ,`Ռ!#=455Ƭٳlw؅ǒή];IOKCdg猱1NxŒL*UȼvGx _eXd acc a%|Px%F*k|,H:xʊ0ҽ=[gSaka7cYx<8,"Hwϥ8^e.@C(ԑ 27_*+r?֠& Ga+9MF#f#Geempkŧͷ~C||8|BW駣.`(1˻f y!ū:|1 Ay=(co8; W, >QXFG](owJOKg]Ih5yOw /3 :[ V9[0V8##)pp9BOS\w8)Fg{Z̬Lz݀e<_eV+ j'ƓhLaShhoBr:IL'uܬ\[K8=xp4S-pd!?bq_$aZ3BbRK6&L?Pٌ$a?Vr$Ѥx! %Ώ.8HHʊfޫ' (62@P>CVk0ZlTWy_ € jI__m~n@A}ftzUUS~<(LOwb4 7V#T;l6sWpYgo>?j*{9.3ZUUUc:DJJ /Xhկxw9c馛X`߿ .N8޽{yᇱ ''fnxgHOO筷[oBnv>Cx駙3gyyyTUU..]Jii)~;ռO<),,d„ |o̜9m݆l7k׮k /dǎN{9F#`ŊȲ /@CC唖rS\\f_dΝ|ɘL&VZEii)k׮nSWW\@zz:l޼UVaZill$33TUU+7|C]]EEEl޼N>lyg8q"=O<w}Hŋx׸K1Ea` ج6qz OPetwzqvQBѠVhu:-T>sH;> =P=mD:CX߿?[l.>rn;wdڵ_?o\r%[oŖ-[x7q8L_:;;innN8B~in݊fb0n 11***HLL䬳"33Y)..fTTTxb."xXd -hX|9III\{TVVCMM [n7&L7 k֬ zk׮eƍX,Yd l۶"N?t222x袋sW_/>O?[oફbڴi<#t͚5+p饗~z,X@yy9Vb֭T*Mv܉СC|Wr7'i&v 7/rJٿ?AUU uYIuu5ɜy0g~m6oLoo/k֬K/O?m۶rJz)-[_Ojj*+V`W\G---p'2i$xꩧXr%\r1 QIJJ">>9hnJJO8lh쎲;Wrjr<'/ V%#%Ljӎ/6fwp3ij *JOIdr#hj~Q<-T2JH4ۙR9F{WmݬߴJ>EGW/ErfA,V;9)l$?'mifըfv'ZNˮZvxYr )~e@,FG k8ӫkf'8OEy{UyJg'|$e`=婊!)3ry4|45bX)F,Ymش6j_,0-@RRՇh4hvoCAO΄Z[Z @gڌiZ:;7@Bb-MS_[OWg7ɞ+#ɘHwWG$ITDr&᥯ZFe8CdBф0 .I}n]]]|~eh4OILbb"L8իWV_WN;4RRRZp> h4f3?<* NDŽ x)--%33_~Q1]_:CCC<yyy\s5b.I6 @[[k`0"99^ŋyGٸq#?Chp8apDӑ /III!11˗cXx(++cٲek'JNCVىjj]JNNFEz=K'$Itvvp8HLLȶll6z=ż ܹ)Sp=V1Lqd0صkwy'xwenPSS}ĉ+9s&mmm<裤pBn wu{/ "l6wf0tttxbju$"_HKKo3@S05j\rpFV*LmC;YSrhif:͈@VZleBVlͤ4l6y3 ,Tc=zrL5[X%?:lSL+Ȥo[(m#o |,ay!pxpJeP9,y!Tfux~aZ 8q];T>5-ζؽcy P[]mHKOj?Ԁ$Ih4H6*$::nwiKUy%6mՋKHLL}|{(2dS2ijlvM)z\Nb]MvKfbR"$}v;FS2q8Z ;IwWw6vQG)ٰad*rss{СC[z=XfϞ,YB[[=?Oe+V0{l.vMCC˗/bpuQSS#<㬳΢|V}te1m4n&.X,?aΝF=\f͚Ŕ)S;aɒ%\|׿[n/u]Gkk+=OS/I||<7x#-Bq}1k,Yz5?O$o{.y6lM7DZZzZ֭[GSS-bƌ455n:jjj8c7oӦMCRqr9`N'3fG?K,aŊtttrJ?=zyG1駟z]G555\uU޽;ctC#-=cĿ0wC((^yixixirt׶4D@FjIȲJ1p8q8i+bcIMNddjf''3I9ɨDFoSGa^:YFle`L#͔̲3p8ٲIqkotH&گW^EC9HH:\xwh#W" 4PhokVn_:ewI0eښ7>A[ R`2*kZ[1o&MYq:]iAp8,vdIFQ;*oh5HN WK@T*$a=_mZgA!|幖eJZFvGGN@%j5~SLQD׾NZibLZFY9W5Fzd@v :.[Z\86x_02N']]]T*p:j$ Y=xfz{{=j5a4&'@ד80l6ݍF :::p8h4PT(wUoo/tWfՅD{E jFCbb"v?}zzz%33AWWv8$DQds9/u;hZF T$''#X,DQI0vvv(ۋZ&)) U*+Wd2QTT=W_ͪUjztuuyZ26l^;NRSSikks'=,_jzuuuy΅HDWWNssgm A, dff"vT*v:RFj5vVL&:G]7hջufdUv7d9xُHoe;3m}tsM5LlT/<O7&-%9b>>Xr:(cδliZN/rqJP^]\uOY|9ӧOh4zDRRSRRK||<ëj/׿غu+7t]v x V庯~n̙37=ݶR9QT]n'm7e^]EGPOuF80IhICR 楱L)`T" HN2iHN2ibρCbkIOI$-%3 <䦑`СV,5S 7>+73d.Eœ\-'iNRBKyWZgarLIh}8Y,=q䂢Ry7{%0$IyD&IBxŗ`tN'짫SN9͊D@BCE0pӣc!Tㅡ7|4*8ҏ/*|uR~ |eA;7R^e/#{ ٮ\v;<><C(+#GR25?Tn^q*+ B *+~Bׯ0CX*q(gsuLA"rGYY]3 nGpp:!toHt#EV㨴P<8/(gF w([b~A7'6(ރD˲ƛqA,W PzㄻOT=? W {%QJfp G60U 10" *XѶ jlRұ8u*wBa t l [S}R_Y_))M!r~ W, >QFNъY fU8l5XHWhV€' ײGO3e/q3EPH:xcP!!omvVxmE)ES.3;OwB%0c xbn(mA-! *6x čNWD^xqG.G.0$?G;?N##)+yvȔs`]FAW g(r}> "&#U5$ _~H2ߛTxh7: >]IHѤKѤKѤx7SlL#um+17 7ecYx @$J!!bT"ٙ@dfg"=,- F͌8XIkK+ Fn~.;젯LBbJC]#yX6Tj,c&,$%'qB (=@MU-gNNA$eKh;FWpцiёablQ1 #3V8⨴U`5j IDATIF1(lWVyAgst7 wL8: &!e4B|>>gR-g ,[M tk] gym +s0FO^YOJNxn1$!* ,& h4,@4!h>lA+]a(ѿFۣ Ie÷{H8q G8ɘ@Zы졡i9p']d%#h5jr2Si즥5]z&ejÖ`##5zr2RHJ4 YH{}s/+S_@q"X]}(<rk1Ρc! <k*|{ q:FN;p2VVC"T8iil&5-Tj5zN(On<1A>~DB/7VU[@AcE벍tu`H}EQT8Kt*G. y3p/w?r+?X#K +~ WweAzM|%>zm;:z$6ks޺mh {nEYx8+U.`ykXm }##)pc}$cg."C5g(ryG/:E b f,PC}|5~$N'jԴ4DQ @$Z[Zlh4?|4|42䓭W~As['Zڋ^Ro 9SsImh3cڤLiY-7v^Wfskyꕏih"}SG'7$[=c;k\Wz>yuʎ˲ƛ1Tey1>P 9cqGhIz4`6ttݹQClP01)a/"&5U11R;<|i7ip%9)(rϲ3ow10N:x_;K*NȲ(HgW/l-LYU=wE,V}f+cqGGR»7A!!d!?9v̑щXHbW4CpHlFV#Iσs X,|tuvV1-HrN G0\N pcÁ d$-ԉ9!'dLW1Ht 9Nj 78 z*->9)9E?(nyF[eAV89#5^B\v{2#ߋ~a|HH:xv{=9bq ˹t)Dj AM 17zyף # #?V{̦y5LǼyƳHDp4]#QiQ%2w$[ EmR~6$.<$ 2ШU̜VUjU$tBU*(޺?ozX*f1ַ aJTx =DUS}oa|D'}ByMv'㩴Znwx#XsJ g,CA^!+ޕG@DDQBEID@|.!*w9k CC ZGeem* 2uuR8JEyy9ᄋ9oPyE21ǞY7_&rŲxՏ4sXX[[ZGaF&Z̹.Z qzN> NIBNoYh"NjcAό)lE utjHcs8#9)Jᠷό PD$I20[lt@WQHGb|:I_Á^%.N@woNI"9)Yim&ޠ'N *溗S3BY]wLmњ:qQ>U,s٫NɔQlPQQnp8NNNI 6SO=Պp陙x F0wxVJ/* Cn!T;ү`ba8XtvZkUf;==Po!rcUN(>XKHhk,dYxXc3Χhz2<}cj&]F@=jJP*'x4tTSGNA2ǘj<yFEZJG%F֨lQD :Oi)ǢVŝ$&L!$z*l>@yǛG8,{9o##)+y//Cp\r}?0Č?^;Pp(gUihA&9tuIMJ&;#-tf\TjJN?hޱ[`|5>"MsGGG;'ٳ=+G.G.{j>A [pV8Dz]y*~!T:CqXp I|eNK|TlTUV1yA5ojYj4`ШUحkp_cZYhFm۶p8HNNN;ͥosuwwSWWMh0"SOON I 0V3fʞ={sy,W@Vv"h&*YquC QPT|F*HXX‘u(A׆LH>ˆ!l8Y͊U\qO+ill ?ooo,ˠg;CEO$Ng?Y#R]UUŹҥKYnwuV .؞{7""SO=Y5\_|1& k֬r-_zjeHj쇂(3w|z= ZU"jq8ܳgb#fΘ6z WbÁwCC99bq7~<攃WRV\g9~vդ{nhlhdrϮDg/Vbw8HOO-PyKkEX#PZ$Ir9n6T*ɴqw`2Xr%iiipgb ^G^^{/?<ݳztz*jvڍF/ ,SYQźױdy&iM>f+k᫟oj`: z #㔜4W_uBE y].K_w|wz\y啘L&nFDQdΝlܸg}^zoM6VLxzjjkk9)--3|."ߏFnn\ʺuؿ?}}}XrUW]EMM 555孷b…|g,[neҥttt_`4;INN ADrssbÆ lmNrrr0c=r!b8⨴Uoyǜu?[ !0|)&nh 4 Fc `9FʏeY{CR!)c@}md zC%6~n$99 .t={{;~>xs$'SN̬L22ؾm;wn qH)?_6)nw# v)QU]/Qs  Pkuج6,Vw|[G˖%G<'q& #!hBB _{oBm i $){B$쁳lێmy۲Y$Kl˶9<:|׳w^E9rȀxh4JJJhyꩧ@*SOCLL#~36okRh4?66ٳg)))X,L&#F:5kJ*R)YYYrrsm>q>6nHRR#wٳEh0 \wu[ӧORNGNN?Ow[i&2331.=zzV\ٳYbIII`X馛8z(---|"fϞ`5hY,AZMII t:=y睄c6m.w"""{7oiiiu8$ 111T*^~;2 4BVQӴwc).;$U;*5 F&;ν+/1/_ew\ǛWw4x[ό3-d~}nԫz}U4\>y/< vzeπP8wԛd+w;AAe9/o=2zR׮d]g'm[l!..BT*d49VO'=#pVQ7o8YY(ÕoQv^mTJIL,,dRaAq \KeR11N?7x'|D®] ac̙l޼ÇvZˣocI\9;v젾ロZHKK'Ob6DBOO{q1BKK,_~.AK/%**  {a4뮻8z(ƑgJJ }}}8qK뉉K/uě2e g?c崶 J3fVg߾}p!rssqSRR@׳h"e.=wjVɃ>UW]T*e̙l۶3guVjX~=̛74 o&===.umbb"ǎcΝ>yy<\g撹l戲FTH1`!\FjR.]ĥ I%ZՌ2<yGKK /fΜ9$''cX(,,$''bMFZZ7p%%%9s|~ddd|iӦRXl%%%#cԩhZYf 7x#ӧODff&seʔ)(J222P*L8/Df͚Ŕ)Sj,XlT*̙3OJtt4gΜK.[ojŋ6m466rb \L&ZnRٳg馛XrzrrrHIIo|.]Jbb"yyyr)ONNIII0i$*fΜI}}=EEEZVL&wy'rss #??sn:tq0ydXj˖-c޼y455qwpW2qDz= |,X (,,dܹt:Nʚ5kGpעT*d?@VcXfɒ%$''}r"BIce IDATTT# ƫTjw$K$hzU:(hTzza|ʪL`4uqԶMlTg[;{.-M.zmA@@3`9U[:R+y4j:m!&*(@iMNzz ΃eTֵPAlT/%ug۩oE !&KE/,.ŷZ/o*kL5N:EQaNpd^΋3]]ݴR2QDۧLFVVkQR:Ė& a*Katz4"GBAb/Ɩ]fΞ<.F#۷m7+yu7_ KO_ϑʤ׏sxY}յp׽wŇ01B ;MGUG\L9i9'\㥗^bÆ X,z=YpxB!9HnR2 PVı,_'{N00LX}t>w \nY?_IAV6)igyL+"1>>;JkG/)"6Z͑5|yem ^M~VӊHK[7jZiЉV7lrd}Rc61UT7իgϑ Z< ^%8\<؏]?'W=g tڏ B_.ȼuEVhZsj* d3EHLI>̹~G5&lWn6Dw^k}[$AmEѪrqCG ba?.v3؞m.kC cC8/XJK'u5ZC [E:$:c`4QB!!KlǏo(Zj%[Klټ0j%VČI>TƁUDFpVO`چY,VNW7ًhd0 TU#a \Rᓵ4Vq<ޓn;cy ]:j t ȼ7 hJH$XΫtZ"C~p##Sb'=V߇hKLg{Jt x=gq9/z_~l1赍$, tkK{lg0:t><`|c E+vQ&xv!]=},]T*a hu"ÐHrqjTpOB GrBΙd&6*LʢYQeǨDK{W/HR|rLDo4@.xV!Z=rH,+Rįk/>px|FrcQ<f3&X"]~*=[ѩrݳWB{qxG#rtľ8ou2.z2~/UB5(e~-+Z@!8G9lw1`/$\L:Rt?.J]yx,VvDCºfq=^o_Uq^[n㤆;F/փ}=#Dz_݇1ϊ:Jݒ!7r9jnIe#eem*B+Or}@ޛa c1Xwx ^o8LA_B~GR]2zLL_ [6 s~C 9rGpds !!?Um=Rw;;.!fAGHW`#a_CL{AWo?]`=BOVW O v|C1z1$#KBy%m̙C%vjuƋy"8F < @8~]EW#VՊlb`6mɌhF4hϗr|yE5D0|0|02Ľqyu( qǛ1nX.qۏd^λts=ֿW+I3|2|Nl'\?<At5BBX͇ yy-7>XL;L;L2yv?֕݅Gk24n0AّcYxڈBIlL"Z7Ȫc4RVb@ti` *L!BÂP^w#BX7k7(x\|3P2 7{#vg!¸.n3GNe\GN ƹ=(S!!2Gȼ!K y5!i^]3wy#)(pV&9w\!],!To^ 0@#{nxu<z6c--9pEV1v@ 5;6D):7`|c E+P^D9~}#兑.a^EsȈ=-x@vΗnd^d|~tB~8JG FGYB}>=#IIHJ LbԂ(BbrrVdR)rLFKTJ\b,OϿ?ڏT*%6RIs[7yI/o`ӱXTNAvYi=UKu}+&;DMc+&'SSMzF h? @L\, FH(&ty=h Cp8B~=,,cϟkZEF#fp -ZZ>\vvD+Jg|fL`Bf1QR F3ёDÉh4R*HI&1. QQʔt]u kԢ 44t7ɱȤb"HU#2xғc d31/5KgB #RN|UOSgcY1py \*x}|X5y溜W)9g˹%Ŏ(.rk`{e-&c6ky0^(S!`W0"BEtL4FH$'$t,AoKA&xv xv!2HKanIuM̛>.; \ʢL.LG"ձtd$[f f j}<ҒbXl&-,dB"HP+J* AP+HUM]S'eaEj:Y0#hO@l'PUajQS ҽY^js W:C'???u8wUd^g;X&47R0z`^ o>o7‰?xP75ѫ 9 ּr^򨛑8ZXV,f lLfLF3fb8{"X@B@bՕl6&Nbq pJDDDPW_DP(df\Pʼsܤd鹓2{Xh%;|ob#]~/./ Q,oot39/63qMV ͬ)Jn4JFO)ȼԴPVV\.G*#fL&qqdefbVNJN!t7*)P[N3/CB~Ǿ!b֑֕(b0ZED qKK mz‚V_0/xxxI.νUS^海~8"C@^!+\C~ x[n_1D#7xwxCIC !BYFm\n ?x8y&<fЄ!?'S|02&-/; PQQd䫯k̛7k׬ER}U5Xzb'YB~1ȼ!?\aS~#)ϓ!*B~Ǘ! (x`o W*|٧vuDG4;L^۷ďB~ǏeY̓g|1d\d88/P8w™!np^}#_ B: ,q[0B! (deesmE&'7B||  |]P3Ҽ.t~eqvy_~wCsGBn?5[^s-ц%qsu=7}8^u4^: J L!@/ \q2rssYRmPǽr@d2@HcMy3F0y{/???pMF2wijwt@r̹BܓXX1cEut 4PH%yGy=WU k7].6?z6Y?v'0~ ebAu3R>H1*a?԰jEԄdݧCKK״Åd' yjf٧LC(ׇ`4lg<_ljjBףtXV ӕ^ Й?p6Z-MMMe3ʹhtCN/HhinFT*h"V sIPס:pn EA;7R^5^D?>hޔ;L?]Xx*q(sKey >{-ihjhUӆ^`0rvZ5t45PWP5mMF]u^-ɉj*kl[U =t46PWSOGG{uT*EtuP_SOoO/(ۣEJC]f BKZZaNSbK#/ԯ`cc#EEEm6Ancڵ<\ 6$@ڼy3w}C>C%>CnVV~aN8C=ċ/Hww7lܸ1LWǎ2i$s=ŁG}_W_/!JEyy9jA"A&L.GH` ^p|ý5]ýZ=Oӧыϵvr%jζG]S\E_enۻ :XƮCe=V90pNOuC+ n'k 7vqV+'*}g;a 6yyV.p/"^筷boEo$ 6p!VZEII /ʢo7x۷Ώ~#ٸq#b>| 6p,YBUUVo~̟?Q>x /l^x?Κ5kصkor-XV}Y*+++Yv-O>$aaa??9zN>MFFuuu̙3zy.r{9]$ IDAT'** ;w~nV^z%222;w./"֭駟^cʔ)̙3={pQ曉/znV.\zm{̙3ַE__ׯk+V_v$ L2C_WF#TSO=Nn@&yQZZe]ƁKXz5~!w淿-?3g0sLn6_db…=zT}J%O?4\{$&&z}6Ϝ9ßgz=r 3f`ӦMrit:o{'NW_u?Çy~Ǿ}hoogÆ ,[ LƊ+@RpB~_raG~7nd_qĉYd w۶msG_=/>k׮u<ٳygزe /2ͣz3gΰaZ<HRP]]wL&sOeLGy>s V\ɞ={xXb6m_%%%tttpS[[ˆ j;N***xGyǘ={6JGy'@SS6l %%ŋ30yd6oO<ƍӟļy8v?xw1obRڻ㥷w?fq;O=vԣYqdƓ˾cl|{S>U_7ly͝<^[9rv@^}:#eն;S`4xoov*mO/o=)/?`m]<3;Vn?dGh'_^Nyu3n^z{ϼ1ޥ͟s{_=wsd^BVUx@P`2m/mGQGd XVRDMU յca GZ{{"+7 DBSC35U5U"bZH$eB.?o+'2: yh{(?UAoOF !*w^tѣ?|r&MΝ;!66gyVh#jXGu!//BBRSS2e j6>Ct:{+33+JWWG%!!+WV8po|ùKPT~z^|EJJJrFDDB@Vk.Yf z#G8.z!y6m)))C\b6٩`$;#3d b#ίǥhL] &xv!=}:TN_h>}Ξ>:{Y8cyI A_!d| :xw^@.:egn649zSSYhICJZcY}m=)i)DFG: X(Jt}zGϠTyNOw/]=N*@.A\ ?!?Caaq|b8z͜@rr2aLeeegrJ֮]_|AJJ 111$$$;z >DQD" Jinn_dʔ)GGG_|ge„ ƲvZGo=}Yyy9&MW^q1CRZ-&Mݻw#Hcxikd@FF111ZLt߿7|EH$dee1m4.\8`Q#Jm+1b3R)ׯ;fN:ڵkپ};Ŵ{nJ%yyyT*Z-0\-j-|S^^o툗FBBw[oe޼yܹ\A0X,E"x7_~e=Z&))puױgilldݎ>TNss3_~%rT^}Uw~'Ji1cO<'NᇫzZ[[]9_* $$$Ş={p4X~G~_+G6VSupΓa?yj 4dR)9Z]/F0VEa bb)?UAcYr2IJIBzzHJMB6T9bjiQUQBL_S=]=*TXZÈKsMFmmGTLTWNl\ \:RO2BZSG( \#AVfZm+ Դa_|… 馛(**ĉ$''RHIIaĉF~P\\̿oj5O<iiitwwc6y3g\q,^Q 0a?kᮻb۷:$ %%%,\0r9T*.]``ҤI,X}1o<̙ñchjjiӦ^ȽKCCR뮻޽{rwp 78M0cǎQQQދFᣏ>[n{zH$رnBN8ʕ+`+ wYYY F#juQXX رcG[nQ^\\ͨT*1|tM̟?B}]fϞի+d֭}tMp! g֬YW_Mii)r-466~:jkkO())ᗿ%ѶR$~QRRBCC֭c„ r50w\v؁( VXAFF:&''S__ODD^Aټ{,Ze˖qIm#<š5k8vEEE,Z1o֬Y|,]?ܱW\Crrr!"" /^Lss3ַh4rΧ~1 M{=},Y |]VϭkpL)H&sn$ h2 ?;HU8:&MmDUёJ#7=bɱVjH\ʉ$EXyTյڌߕ?xo&΍A[EvI .OqV' d^#J]߃R$::;R:Ėf@PPن%yG 끄!} 55ѣd"gue =LfRSHIKB8pa}-b`1[0Ͷdd4P1ku\SOq1j5W+WoBbL:Yf/l6'N!0\sܤd+z?N9t$JiysSih頡d2)1Q|}̔8ZRFrht28Sgij"?+ԤNT428ڳm$Ĩ9v5ZDDQ$#Ŷ}B!xBQp;Nu)-rSi쥫W۰gDUT$קRNu}+-=ǨT) W8~xZں SZd@i5:)E(nl jf3X"sKP*8r^RIN6#L+"BpA[a?XHC؉dPډ CJFrI8y{SBYY9r-'{ch61LŒl &@@Po@_V'x7e!)9n~'#\zc ZκV~Ǔ,rreR]"?1R?C jl[/$o:#E+o\&\30{'S xp#{^j{NK<w^=0м 0N )?e7dQs/T{m_CxY{b8F2ݏCt=7<]]vqҕ&3߸(+kDHW!ˆp>׌-ι6/€:ح""Fw'Ery/|Pċe7lHpK2`?L'>m`)XkF!E*G་o8q/v~u%3B~2wGx*k\ :ˍk&<fЄOkl[&oAI`I`ICwdG܋cd !d| :xw ?ŀV|O3uCpҷ`;?~oc5.卷L^[T$K$K$x/hxbq/v~ur'րe0h`}ヲ#Dz!t6EqF tιqᗒF]½oUHE0 FB!_FׅΏ,.{< bnhßC(q5ѽ@[> u)u=F- G2 F`p&N0.2B! "|+XS5L>қz{[qccTm2>y#WϬS233v.\3{)h0|t7?իjjIeY0BHd0!',Ù qfB̏&e LdIȄ@HXx%ے}i~U]]es{Vu{ ޟOR!(+()aљEOU8%D/h>KSӜ=su54^8}=}NzxDFFafj1:`a/?绰AA۠n&/vKwGiiideg!"3t2<4_ IDATA [ әtK-&ZDIZWVcB)ΧG+ Zf*mTThtp.^.DtF,VK7cjTZx<sEIP&rvyYgM<Ú,>9r$/gY!vG;RE~b|ܼrr1]CvN""ݽLMN1>>AO?NfiZN0>:4g[Ƿ2>64gS- x3mۋg`r1:4'dvfё1;{5I}{PmX8J arT' 8Ko518:ps'Ssg{V%E8;C˫--8>9 CFP:-1ˎo5XP}2t-t0^'3ˊƎ];8a*TYWhWb˶-:&"(xP~j`28ʛ~h4CVvb6D]CSS h4v)*)";';ZC$J^'N #=V3@{l=VU e$? [+l4`婗v{Ю9kTu"הs-zr0s tS^LAXk1 dXKJդ˒y@uE'Zz8Or9I~Qk`I3cqW13/W?3M_βRC`#RA<-!ddfPXO6VԮAׯ̩:ۻߴܼ\ mCoںO091En~9˗/+'^O^A==L&22,FK8aFG)(* cG]CҘkcQ' +Ǣn'7R; Pxui&#,  kx3)gs]= Opw^[xNL~,:='[{rC8.*ca^}4F;̺hq)|C(@o2a@%Gn9{D1Ro6(]tFlLMǬʯxd&+V!ۃrvq\? E%wZU V FEn7ft/̹Y؟p>"fs%z]4<}bL SEQ鶍ty(@.7:رnm\30JEqytFia빰36֯).nb>sX-fWtyD;sv' ʨ./)!JOX , GNʩ!-!pzܜ.iwh(H$LYjO ҊhD\p].N\*+ @aq0qO'.7,T'-7z ^[!-0X"N Bn^~ayF}>>‡EW9~x{"=Ko{W˱s+2tӰ~p[D]kq8$upC, qBb@.;ؙ SjN\(UlJcűM.)/Dm.?^KVΧG+ ZEp8)-+t=atzrrsh`DZ/Vs zkϓw(bPtS C(Pλ }>%!@(JJ2<◳THB߃!~,D׌Ֆs,ԼAu &AC|.T^5 ˝X/LY/QiqAbo{ QAu@?ZX!TVդK*F%J,-"JդK*x)dE>ձ'>ն&_βR !:P5%] H4&jΥF4|xFtJoҐ"by%*kˬ0q>>9B`O i)qBʏ o 1]bmtT.JQNV |s!6[_C$G)LY%bM9^De]c}PyS#aT1IOuEꙕ-Y,D~z'NJx Fq~nsA!ܟ|Dzz[v9zX>g8 pmx$v/@;1mUX\^ʴ{bo9y5*X?@„pɉZF8J*ȎEEA:Klxx&H&{I}PyS#~[8_Jf.>ޟO|{?!DCFU$t 8ٙYf`SPTd0hD0HfV&] r%v9܂9͌%@ <󌍎aɰ(QPT`fj\{1 x< tLMN1:2ɸdK\x8!#MUǃX~y-aDqtRD1EN<SӴ ``~ 6DT+cĠmukdXdcc=& Mffp:twv3okcblŹ6fcM024MǛfzr5I%6Eκ6N)Og>psĉ#UzzzسgǏOH/QyWywl8p Vl>:;;ٻw/MMMy].?<'O={+=gϞ= 1+y~uw~xAw::NCف|7 "|--DYzy8}YEDL/,!/6pixz{#!s , BO/;ԘNhz2C5$qEl#|rrsfCC""g[s99m!|A k*YSSbbM38_Ϡm,EXSS{s/'wA$Q=AvJ6N HX.?رcedd͕W^wo_v;333f:::HOO{n+G}뮻Qɡ9z{{x<Þ={kA#"r(**"??ft:LZZdddYصkuuu;vG}'gZfa4h4ֆN駟fƍ|K_þ}}s aX(**d2ݍnǙΦocEFFUUU ~K/n硇7ʢ1CE.Xf7YmouA>'i#\(B:`W箶l寯b[jP$Qy%D%!4, e* 8%*^JƓ”TLj N  tuIa t:FrOh 흛ÒaaEuox<y܋iNhr^Wi]tm^VB% -TϤ5%E2x5=#>{)DH+3QVVFFFs]wa2l\s5|_*FΟgyviiio۷o]w_~9Ǹq:sNo055?yn7Ⱥu똜wߥ /믿okSOq&&&~]?n_୷bӦMO<CCC<3111=u]Gff&'> 穨{%;;W_}1>fw F*oRRRB?'f3?xNzz:}}}\{oc{/NY^xw]l'?In_|)^u.R^|E~_199s~_r1f3Ve<<ØL&n&z=z(;^z)uuul޼sW3??OSS?fcݻǏ+wހm~~￟1Ͽۿ͝w~ow^HOO$͜ijjImlld޽vN:EGGUUU\tELF}sFvMQQ<W\q{׿5TUUQYY;ûKaa!===t뭷rW~z>`ZǮ]{ ԧ7iꪫ[x뭷ĉ455poػw/f)}e mv>ն+z8U/&|JR,+<(;bH CFSF4)fHfN8rMUu%ťň@Qi1M'q:4^ 8y$N@N^NEU8Q6ok$'7IvסKSXTRX\HYE) PVYΑwRRV9[1uuذiŅ<քdv֓خfX,/ˌ(<裸\.ltReAΑbttTQvNNձ~arrrgeepi^~ex oߎNvvd2K/qwOO+e~~>x<({<>o~SNi&Aff&={g}w޽JpmNJ/m0ؽ{7ַtݻ^aۙ"?/EFFz+z+mmm\wu~W>8n_馛hoogӦMA333i>rm=$;.#++ ǃ(˿ ;w$//˥}ntn7:_-tG>ٸq#;vࢋ.z??P]]MGG< p}Q^^/KvAzzzH0)ƠE4hX#q=TQں ;0Bm]ʊT}#wp鎍t20<[7$Mje% 'пP)/LY B4!p̠B@iZ&g!u[fDdŗ 䀼\v/d6}c4([RVBIYI1zťYkf6+(*@l/d@b\[a9{A`| _6_׸7\s 7tm۶1|wx|{nsNPGK/D{{;ܹb>o>ْrrr Y`[os!{_333۷JKKC|?K/۹yxٳg / ~EE^{-8^{-eeeamk.hnnHKKK/n:/;;n}qV+w–e4ٻw//N{c]]###ΆͧYv-6 rwpp ɪ Y~;/WȂ% IDATw'N#ձeZ[[@cc#555\x={ݻw~>EF- Y03v> _~8܃N&?pxG<7sxcOC5HTVoHq=tBxJprS$'S:"Jc# ЛL3Cç/^-A/ԃy&x$!@ds6ai)˚=Yk m(T vqܸ\.Ӆ"Ò;1m6466277GGGx<1 TVV_T'[333 1==Mzz:SSSfjjj蠤"< )//Nޅ),,dpp\fggF333/s͚5L&Z[[[nazn~_s饗_V+z׭ߓe p\]lIߩl61 TUU}jfggt``zzz{{ټyroo/vN^^EEEEeVr:::x<\.f3Q/n/*Oqq1N.v;SYYlfnnjkkzI nZl>j1$2HO3NǞ1F'9܃<;<<ߠ6ÿz3m=qǗndMYy~ y]?u5if1:yT߿w~pųi%?'ݍUoAONVM]\uV>yX4t'^d`xo+8].~kar2n m]6Oas}5?Α3޽t #O;DUyw|FJ m'mIRaa.C 2Γ.6TN yف>;RE;x< A ɔV~|hVF|tKUePQqP@EU$O, L+I]1 8!%%%9zlܸ(--/[S .-[;T|h4† ×|e=ӟg׮]~s[ Ė-[l]TT`SBv~\~ٳ'\NGEEA"==|\`Zqq퍔eBZN p@}KutzmײX N*mmZzx/'Ȱa;.Ba_qɎ8x5Eo^~my5* w{Ml[O195{oVQg:"%{?'_g.hp_ckC-|Gt(.^ '3_s)偟=ju>=߸3m\W?_x[=F'qy~l'~&8C`#Y!?B#4E^g)ҙytB)ߏJ#(ҕ|gV q\Wuµ4I<[lxVOlݻw{T:믿믿>e:$ZKK G)/l2ڻ'˶RTͺjd[1dZٱy=bs kXsUƥW>}sveE=ɱSO.jX_ſӧ?;q\UF4Qyc~p]ugiik)6 *fδpu\b]k tΖ!@+S1/6pixz4 GbC[7(!@Heds !'wV|yɞ ;%h'_g$vQ'"Rݣflrd2vp&!>|vJ7׽s䟨|ޟ/9J( .(S8 gQǼs8.=ĀMܼoO YV#5DUYY4hА皱rNo)y8}YEDL/,!/s(OO,5z[!TޒNsldæzl}6jײn-=ݽz1180(D9ںFD/6q=AvJ6N FP:.nJ=zhV*GJ=QdcG|m%,:YZdR;bѧv:N8*J6n. ݒ].?,.>F#VA(+/C 2hqk#&)t7V: !LP0u:iqSy5v^M,kBʓKΧH-6> XZJ&'iefj12z2Ԯ_ںLFlz7m`fzP]_&0V,|85KNJբWx5v^MW._&I6=?y{~^P^%YӻQ0kw_ON{ٯlnefv>TZ$!oD,+UGCvq{~y_et1s`,Jє`"tNH};{fZY_t4n@n7ajZ[v] _G鳜kms9|HΗc!:q@5A qAMs9q{}!;`w? J5 <^OYQCG&jI0IJ xDF(+A;̜|*G'yY֔38: L&r2x@5\P_G95o"HW43N'Źtt)ȵRkEEeYH` #UOwWS6^>KTxܸEBd!pLȀUJ29K Q2#}' @iyi4ׯa]A7ok= [MKcp,3d@b\[A=jtԨ 4Č(*וqkqvۧhXWơ&;l]cjlozzxp+Vh;/m ?J[ o;KUi>7|h;#}z@MuFF;x~!vnw[ju?B5#:+.oq}:[ɲ_xHvNfY!xJ{!HOuE} [<@*V4*RRZN4 3(;_.M /H% >m|fa u;%IW)bqqʗ2bo9y5*XחLhVOz@䯲dZ2,fv]4l W\ƺrZ:ȶSV윃֎&fY_]Bk:ر[aCM | &千-slHS'Sg+. ֍U~5+Ю.jf`x:[yEu zi :,iF.K)r &MV<蟛'tߗ7<2P䥗K'O8ޏ[ C+L&H.2 L-b;G!qʌƹ\TFJ 0|rKB$ʯ86OVVӵ& VcUJK+|'h&˚x/kr&ٝNL St``[j*lڻ(_ p\hAlz|+(/<.CZр-[137nHւ"ffkkORzNjE-ܹII0 듋 >%rjɢ=HIiaB`(ȈWQiXX8J ARQk߼<my5u0=3dD'LN ztڱf捃g 7+(R_[P}݌MlPIUi>? '5bw89vt3s:X\m-O>L@dfn͝L1 |C8! JyyJi9|*e.-S]b7UB싾"Ԛxٱ`yhk>Yrጛ\Dl~EK)A8y$?#ce].EeRCUXu W9ZH :+tp6F=m_d4015G~KCpp6+wmu zmx<"f#]}#n-r#n61kwsK  q ɉ CFc59ȕU?G'RvAljqcr͇S㼼TW^jEtQ;&]RLʝ^ κ"+JR"^^Y!-&HxX6q~9J5HzV]}GDB:[H(TN.2ҒkD(nv5 Yl\q@5A q!,]V:̺H{ޤ=0yUU!)|C &)o XeQ^4-!%Nh@qߐ\\žU'vnS8jۣF4hР!f(E)+$W)K8yHqCMOB9I"|2e?_)(r!Q,-\P! pK58OW2~< B"=]=sNhdMJ+J(;pm|"aQw~T+tF)J9q$61?@ff&9y9y6ok];r$Ǥ_4z!/aZ >ĢrjU2ү/l=VcQRqOLJSm+G(~L7<2oUz4)|$upC,BX4/`gF.,LQ8qTS2⇀c k),gzz㇎SV^fdxǼk\'CCnr124$3+k rs3=5M^Ayn7tE(,.h42h`0`2HKOch`t9.رl&'9t]@(RPXlbvv#l01>E?ZIXMך(4[EUiPyϋ" J [ICIVyAZ}:K*ByʄKG9m,+<66!}<_1C31>$}}jallGɉIϐW`jrU{mLNL2;3KOg#twzi> 2?`vz~εcxh9ZO.ZM YV#5DUYY4hА皱rNo)yRZq_ ¥C!T!i?ێd2'. [h4"z<̬- zEF5jȰf`Ͳ259YGǩk&v, =CTծȩx.\9{шlnc]عAx/]_t,43k^]mC4aMMB/kVn#=V3@{l=VU"[|IV0.$R;HZ2Yٸ8Cø].fgg(-/#zdƭv{1haF22,LO"z<0: IDATFoX#z}\IWg70װf] g;cxp oP->ZX!LNZv^MW.C%jSpqLYje@wP+ 9'rW2OzɰfPXR^罍~:Mt2<8~S=.N0Y9YX3Wdjr rch`+Q)sdrb qL+yZ21 QGNxظ:: DfV&tuPi#y^8q$N PKJh>q9ׯKfjjΥF4hР!.D(JTJYp(Gʫ 78Rl|2e/|ޟO(U:Ȁ8d" JʄpD6'E(2nP8g,xVsa"yW\Y +M @ yj4h%a5]kl=V䭡֋"*KJpnyyR/hËKG1-\D,+<B-!5*召w2'xl%p1`7V"m'5Vee-NhҠACB)rjK˹0ϒޞ!)!O 0 ѠjJѳhH>/r}Mju,K&4[EayN'VɁlAE:"x0!$Vg!,`خ(! C.*!bȸq4h8?5h $XT,VD[d*#YV 'kDl^+=1F.gUH ߧɹW]!/ow1η<Ыki']grr<4EH[MCCa_.SE&1XZq魋V^2e+#" $ټ|>?jI(~b__2[n`GNN6:1c2e# D'H7r2==yTWt:p\LNN0x<"Sp?]4֕~}M4Z=Wk+Áf||шh055EQQ1(SSgJ|lrjx{"3ӊNtp8E{|Nξ׸2q]m\d+A`nn9 ٌd)f^{ q*P\❸<7ccLFFow"@lnnldee8}ЬV+Nd'. x(--M*4hРAÊFFNn7:\ffαml4շȲSTTLvv6##b%u~ގje~P__̑#mp;ϽByAfl69 hj`hhB ock?k?YZkg=kxև|m c8݊z˅t2UlǞ? _ zNKCC&1.d3N ˅,˜>} 3F`9v8.'b4F|pRz3 W{1 |>ZHjj*ӧql̈Q''# *κSX++IZcj*h4")) }HHHDVr Z`oʲBdc>|8Y;wnXގWTTp֭ͫ[6m}GiF._ox|4K3NYoҼXP }/.~_>>Mx _CAr G ztIN ;NL&#NgS!IV#35$vB[*?IH0zQUߒr#v' =WMi(&d]h:j뚸yI y}>vb٘fV|pySSSERf̘ol,'ٿ3g2cLZZZykӧ`رcL>2::,^fɒ%jx<^e 73F;L tn7&x6@Fw{W(sM7 ~,Z(SuvJJ U_:$M.WhFIq:H,K<s.g?{#RMmM-ϛyʌoy.kZgp]xa\RLM(Gm^KrQnV%@$^/h4}Oo$I.gϢKJBv2t ?g_D=Йx<^zC~(mWnbLe;&S99~P<ŞC(LF<7$"P\\̲e0l2ƎKWW]]x<0===tvvp8a`Xp]twwctze4i==vv;E9REyy9cǎE9OA_!%Dq4ify1<_~4ˡBr k8ԣEN wP5PXE<&I!"jKdGV+m466[7xGo`yƿ2qIf]2PhǠJx^t-3 [=(M!JY1NFS$v;Z<228 _VgOz$z|.'L ԈG7uA?,v!jxzz| t:l۶*DԩSYr%6}w/qF&NHcc##GDpyϟʕ+Ynnp=lx.\ӧyE PPϕ_GqGW(3ڠqZJ% INoĐYpYoaqUfLU59 Mp&Q(<dYFԞ:O׆J磨h,w/pK8{NJf֬YڵcqT]w==vZ-'O&55v*++9y,sU>}:|_eǎ՝[[n!//9s\f񍻂:mll%R2XVHII%#CT]],+yZ,] ŋ7 :FQTTĚ5_IG$45)H"*AUDNOKM(JHCȸo.b23:2;v I4i455q F#SN!114#HKK+VĉCejjj'%%R9r-[R\ً[raP%bq~)95#hqwV$t*| eYR6Ũ+(d]|!f`C,p[mhe9{ >f75j < ?5ri©S|w}z, ڵkYp! tR~qqVZC&"i%<{wٶLee%7޸'̞}5S3W_+~ $1h?x\ gs-}loH%5~mAs[Y/]xSU~7c|~?]dgd̛̙W/ܫ KBByyy搖ƺuxqQ$)?ZZZ{jdYl6Fgg'yCceFҒ99,^I8z#^}u#===tuurtv{Gⵈ;6Ud ́Xd k׮e֬Y}s4^ AtRF-N^8b-4[]TLj|nV͛dߨdoFssS0 ȑ*> b9R3vYZjjjh4x^o0@>{meF?Ժ ԹK(8>ח++L'T=I7x錅imx&j[J6hR܍dl`Y6REF k\>|>Ʀ31o`'ZuY㏏AfpiQ5,^)/Q5$'/}A]]/7{~ )Djj*)+553gq853gp8Ρ "TT&$49nXT*5/`Ŋ8|, 6U5ה#pwRQQAee%/`̘A]S1Ce,R}>lYl |K_^+P^|>ۃhBl6pֿTf=KJJٸ.\{Kii :9,[F2{q}֭,_#3pm_\lEC\>x\9]:Csu7`l![GL K4$NbrRRzݠօuPBPgVF<^wb!|d$+V,:̔fLT*W&gD:-(A.+cpcVlVRSS{[ 8EQV)-dƌ,9RũSt?~0~c%ia}9(czvZ(q]ɿ^Cۇ֭[?(~J ))qRdY… ٳZV!==lz9®]Xj%SL`HرcHz8q>1(c=}g>[~7a/*,f~]5-#Feۺ;,{0kFU%Gy6AC6`ZRꓼd/!Z1k6|/4m6Fu2=6_F~wnλk"h>كGV#G>]Vv;u,ަ(v֛A(+Iwv"vuat}=o>0E(_K ]h~ ||M|>{w3f*1lsAYڟLM3}:Az^^<裨T*-ZDRRFoٜĆ >}Ɛ'jrT*VDo IDATii ;v젼oO`00w\IKK =,lR|M6mرcپ}?߀kUe%Wd?,P& 0:wݸ܊z#(%m09r$;wg۶_ p ٽ{7NZ…xg)..ի|_}0klXpg*Ѩ$&3tttz}F˚5k_)hT`a쌡L <ۨSjuY6szhG՜ zι>ᔳ*Xq:|^8mO&VdeH"OF^/8Fv6F܁@e::火PfS1 ?z8a?ȟ39OG/|t>gA:m6--$ttB$@+.W`SFFp?~>/h8MIRfF#GӅFN-!)e&AhМ$ż*x1ؼu3gb L&V^zS̞=/vZdYg?7psT*Hx'wkFyyyԢ(3p=t:ٸq#ՌU-t>QX,nJKK裏l̛7'N+Xz5%%%>|J233Yf ArQ^^SO=]w݅ lٲwyBN8t/7޸ŋHEEg1c˗@`9riJXj)))jŨ""$a0$ NNk{-be/?,qC43칼z:{^&o> 9ᆸᬫyҕb9NZm:J7Ĉ]VΜ8Ąe${0**d`_yיC 1N/Xݜ=[fZۿx- y]CRY( DQd(, .o% Ih>ʁ?REnm phZo+[^DC]~*$IB$z}cn?eߧ,K$&YL:-Tw84 «7f{ǟ(Ɨs`Y9VQ;_'M۶)wQvˣNH`׾o?Ѓ Ķ]x&O|p Z1L^/(2/zjdEi22]Ђ贅8iݸ _{md76ia_df0wL8a`u=6~Ft] E8Ԟ. M=Ϧϲ3j(ÃW^WNe Dkd $2Ȳ?&+[CcD"h=EQMb~X)ݟ U0wz ~-u}!22.n7{"o^`A}eIW}aj'C L&_|>DQDաix?;B$Bhs8?0DU_Nep0h1Mף6Cj޻瓟_9]w#\ X(++aaE#Q>;$ƽcqh~1qCu \^l0tZ]9>q_W]]|=JJ\|>>LhReAPQ*`$@_r8B}s.L.lÅ~?5B+Gc_ W5ج]\p 'b2%Q]uS̬lj5njAo0 z,I}h Hf!i'<  QOlJ^} {[j;4|4Ňy{h\y\aNU4>zY, aI*:3^ O{% iեgA _S!֯ɏl_mJzYV?CA`@wV^> W +n#8#8#8|Bf3DTtco#9ߦ2ȄˁǠ_xN 7CoDljN#Q\c#A 2F‘2}#3=6z_MW7rK^fzEү.ǖ@Ӈ.#Y;=U/?$p\..V{>ǍU`sLpaA/FdA Il@6yPCD󌤆s-uʼnJHO}Sc +Co9[:,}02<ɥ҇NCH>ϟϠΆ28C++Wo=ADmf>z?/IENDB`liferay_6.b1ca978c06cd86fd0c88798e4edf1f67.png000066400000000000000000004107521325274564300423300ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRX] pHYs+ IDATxy]u^W&ueIHŀYc00`c;Nb333d$8`J2jX"ЎRK[jV{_wQ:9unĞ ,OH$R"@5)%@KH^EJh2=I#%/3dK!4B~XS>n@à?|]CS_Ƨ_/¥-Dd1>?a.ۏϟTL)-ÁNO,83V,=,ÀIJ) tMY \ cv#G}}-}}%gk k,yJk0?p Fw0gedd-mmWN~H$,̉ϘJVx3R!śU}uOA7)D ӥJ&tӇEK76mƛ+Z҇?!۠mǁ +) hjFA\@SX!nR d?OTC] -/,z?JϜ!yg0:g6p0 J1f1c3P}raiayw9? cMs>RNkk+yyyFCNRtuuLVVeeeb!PsT*Ekk+PRRB4 ܼ<"A ׷ړ8I2Pi{{;mmΣ&RT@q~ۛI/KYع*^̪^ѥ)|!JL&6:;;5>+@" wk"NJq~SSRJ[ZH&P%Ӂ)ijn+zhjn&L"\Œa%*Q)M 9|48gPvt4 5 OLX79&~ظK/^(?j_Gӓ&0W.?KW gs:rS>+~s{ T̀PF,3ŭq=U!4LCi(>=4x D32'//OWuRJysE1{la,\Y.bҤ᳖:~X\0p؊! JDb;#dp}5ݤgః9-oR!X Ly  94 ½OiWa&knLN]~aE3xiC .C AuHAu}"7ČͰX51s[{;I=OdddI^nɒRINnhoo'77l!#!;g!T:;:innbʔ)8pFh\2.Jݭm)_kaXwCm譿ƂBR_һDLDd2AVV^+kGEwl޼Jͻ sn6^zw='L:X,O~cBp=ł !++~EH);w.SN +(K?ljkjJΝ;GOOGf{ˌqm}v/_NMM > uuuttt0gݳӦ!e#Grmݺ\x1d}{7hkFƄDrܴjdqqΟq>'Od &H&fdJFTf=W\y%"s ,]>~VlO"$33ӧO}s,_*#+GH$̴8w:Oߛ,Aڹn*@v}@&"ј5H>̩'5j5n?M0V̹xַя??cѢETUU3~xn&.b*++;())a|k_cٲe<ձ|rR2o<~q7o'`ikk,#FN-?lذoٳ8z(/2B@2q?pwsϒe+v())t)..kN8~ .~wߨgH$[oeJd9?cʔ)L2E_᭷?x5k˯H&|g?ʸ;?= /~'^}UoT \BčjU+[h}g?tam :"CI()"8J'GֹF?DhQ9`v#(0/j2&FG]2:̅$2P a+Š@A <cvb=^l]w›34#S!~-" 4B B+aUB]On# (,Tooosgض#j;<|͂gil\M"" f 33T*E,so):bhBIXI jIܝ.л\20ͺ4L$t%&Kv(_z^?aWZ8 1¥y X ih FR{I=dt9fPF)h(ĢnXB3}3*` w I,U&1A:Jp >ȕ-҉+D5;<O;Za Me kbh6M0EqNA2W_ìL&ȈyT2E2b%0Y455H2$lŋrS$--ʹ55r7q6Ba1вL591Nݲ Z69JCED3YYYO,#DJIo_YΘ Q֮]g%33]wSVZJnnWj6lI&q7;oS]]͌3b\xD"fƌ<3-ZD$!JIYYgF֮=0eg".rNFAA> %wXbNz4#GL3fѣ]C^^v 3Ew֬YDQ.J}Q;JYY9L:̙K}}=/<ѣG8Yf5&NDvԟgΎU*Du[P}0sd&,/|+ ى `$ eWrʸ'm%3,a;ynQŴ1kL;dM H^.c'P!"GrOs P,H_N'{b}X7KMaaA&eU\\^%Cab01 v?tceŔy]?UUviFZ-ic3CyEz=%Ф; FIII<g}#`\ED"!b̺x.BFr$x 27Ƞ;Ռ3'OǥyUZV٫^ {k =JrȬ"x?Wb)F" HR\\MҪ6]9z懕@_?Wȑ#ttsYXS4“vuttO\vetM6N K-,+D‘F/4 bHo)C?*|jJW[ ɠ銙bha!nS7 $mu wC&V+0a} &\͇Yi>H;d >^A$>F2(~=}GfO,F1_XrIw_.N_|Y3߯c$4~2{v) 9T!8'4 B_ Νe``肨XFTSS{& 8**+o.5ZŘ?SgϞ##"d*EFFDsr-RV*4H R}uh$q&޾^L2b)[Xx*+Uw)SSN1vX2228x? {:<4Ȱ?.siw Vt3냜MCZ;[U_&[3 sQϦA/?owQ?O~'f6aH{ͼQf.H|k+>C+`4j KS׆S\I?!mj|6&x?Pq04'ݢ(}IHB h4@x! O"$uHhmiݡ)"nŨL 8OIIj&sK8 I~a 8cwM!AZ iL駛FʡԠ'tn҃Nʁiz`cL~QSnU k۔A z>J(FAPH7n+oC|2$a06PIGOyp1ܝxH)4,~BA'CH˃w_V--m> :[wC\"»8KW=3{8M6P| qLO7qh@2HV~])^- h#ޅiKr ~"Ų[iz?OB7j|X T4|4M:嫞=nM#l]iF"g*bB[0>*~f4h]$ '7ߗAoJWW#i(LJ:>,*&AneW%`u|(sQbOWf3aqA_YŇ4r'2?Mi(-1> xȱjO5D2eI/ BDyj(M)YJKK !gΜ\c XXϫ;.`d2ɻ˞{y8vAr!qb7 S9h< Sr,P7Χ7vR @C?qrկ0xːO%9إoդeԛ `Z F鏦Loގ0h*3MMj [P(^~zdT뭡.]iRq$ ▨R_7V';x4-u>0t/=ތՎaG+'k.Nڭ\Z[.M&~tkۡ}Xϯ\Ȳa\r*i׺2lll ִ1taƇJ%;?u7iNlNjX,jv̘Ҿ|£C:<d<Z~RSZԩب.l@K?oee$d*I*D"ٴiDL˞={)Z.L2$JE2c455if$p)]<*\4 IDAT֜4NpBÍq:H{{͛7@8pyQ A; NMvzZ%cLv@;~V|I?o.}ʣGCZBU|f!l`vQ4M:̦l'W.lzC ʹ?3a݇g 9TdC?#_Š=ƔKj8A|7?DW#˷=<>wAMqJ7A|v0EKް8 mj<Ƒ]]]L8X!i#&xXjnDi)_^"/"GE/W[F`o}=xODn_qq-0bcTmBb1r`>ls=lٺ&8}-[2bkwyQPP@{{;~;ƍsC:SU +W۶̹sؿ?_~9ɓxi"(.oNk[_hkokikkk`׮]!+_@COOO/}}lٺ"^{59s&[m͛9r$_~;︓W^y<ƍG$ĉعǎu<#,[č7ҥK!++e &%mm455w^"Bk.nJkkͼt)\2wcHӧOs50rH&Oo~[nfHR| _`„ ? rs'|ޯGqzzyhinfM|[u6j{\3F[nu(_*z<] /L?|8ΡCehok x?>}rw;n l\߀=:EЮd0=M «0YB܍36Ь] SklI?-#bl{{v[py440)u2dWRɧd@m7_v/QtןFW?ϲ!Nn?c}Ͳ$ w|xz4կTRkPw_ o Cӟ8tmj AےϞ^ HTMaŀ@Ayy9 Φf˝ ]T Wٰa;wo"+;3g"7/O3f222TWW2bdJpɫW9y$盏=M7ިag̘wAnֵ7-կiӇCq?'|FVFss!rAf>^z)w~;W]y%_8r0_Q]wwطoG&Dغu+;v&__h4ʙ3ghim#ضYꫬXRCdeeCiYӦM /dΜ9[?(_{!6n@n^=ǏСC?&(|vN!m*!4O|:l˜7FG&n!6&5Wס+5f0W ' 7)9/@l5v4&O?~j^Bxw\fdàeT$ee{Ͻ={Kmm~D\mM4rrs)++c֭"\1!gb׺LaQEEEڵ]v1LC#G`LM 6mb*ZZ[F"D̠ЕHǏL$Xz5dǏسw/ـ3UX &P^QAKs3eel߾7~}l*>l#R$մwt/4uZ~~{a]Ԍ@?GaϞ=tML?|1c#K͕J:cƌ!33.={jjƍGJΜi@AFFyyy9s{inn&??MOO̜IQQ'L H0 ̙3yIv'6mW+U'C~)zf;G4Kr)ųg;ELqO<Ȗ-[5{67mbٻ2mruױdj2cͥZupﭯpIFW#G~"3g뮣 E$hVb*I=i;{P@ 6k,# 'H y77ə[F`¬(W9czx3'3ː]clTڒC尚0׉͔%VeS4 v~m|MhWBšTmɔ+T>2 8Э'7>,;{`),>b[Zwb=}*,Z M wqEP[Wj47+hŹs(--%JpѣGE:?xN22SQQiѣGSXXHوP^VFaa!3f2n8KJK%MvڵSNQ\\i8x cǎeWTP;SSSCqI ^pN$;'gF{ B'54-ə&N3f ))9(+-R:u*#GRRRB,#.3b&NөQFq饗r!PSSj5bxaf!dff2qD*9tL0l:uE9}X,%\Bo,Sr 6}:gƌ0qDoNYY:1vTTT8疤+C0iDjƌ}I&h9byG90hЧ/:c y8qֻ[HX,hii=]$;:;A\BQAMT@FEjV꛰ \a[;EZQQsяرc۷Oeر^kJ)_yD"wAAA ZaQ̝kзY nBIIuh0vEU!ƍ^s5nYX{嗹ʬW^ynMѣ\"nV*p1Y|DJ?n:qW*7Ʈ !>괃ra}Nf חa7)iill:?C3^zmdMix %hQ:$ooS5Ym4OoM_׉h(җTtÑ~uۏ'4eLYaرU|Ȁ_v4t?X* =й=ȷ ۰VuU3QΧԉFڷD$J4%77WXSY2  ޒ/C},̺Kŕ*δ*}[j5PY[m6IٚKMwi]M>-p&kbl09Nhc QJ1 Gt|% 6 gڸ m?{&7a /Bt^w_1TqaM5XhMV䜮L>EД^uhx, o&+^*ד}rhtg`l87}O˖ٸ-{Wޱ+?@ L [>z&OiTY&G"Ϡ /p)^nm3a6L)gsA1 媒k@(~\t9wtL[ZvlZ;z F(jꅌ|iN _m80&#-z W[L9 +iSWB8T20tQѕzҸ }lf7`* x* %JN3owرX0rh6+GFƒ9֟жw.D.59b #׵~p`3ikoi407BӝgGgwrjWЯxf.7'Jd,5LJΐBwf]6·<#_%ޛ0SG7zo"J@.+>>S 9P<|w̥'hi*]yrx~jZ>=[ZރCQJp(rnBM/ii&]Fk*aIMCJC{H `D;6'*C=,)[+j 5{1b\C׀ţ3`-9\NaaĬ7աpX՟L9>iz~kzY +9P0ǯ<7lP0yR[W-5qsa GZSd~my9Ƈ"u[SeT Q:4@YIid.~Y׹P4-:G}^92 j=8bB .:aDWx9e=.)猤! zR{bEM`JIƶuԉȫ}2-CW<6*zS);ե/<`S@ÿ:j sh 4,0үX;=4i|0V%ƄVB2yRXz8$ 6WURĐKVѮӃLw/%G/G fc C❩QZ@* ]7! m]؈Tƣ%-b˖_kwi&F#;/@UޜggC-zMU k-X܉+LG: ZPwC}[ʠAq8 `l'K炿2WG! jḣBx"AGG'mZ,cq3 S@2 H!ѽ5!df-3X(0 kť\(U=tfCqXEY5+ D?;좧PVPIDE=xQd==w:̱ơS]nnm&?/,TjWt^園)ZZZ!}!mJ(+i}>myN A *G?U&x|h$u 룳lMJH$r$?XK&RRR>io,fX{Vw/]Jq{ܑĢ]82Ŭ'/'Uśq۹iCywɮӻxi"2Q:4h$WyYyq>nk*+#Mw7Y#F0n$2b1Gx8a"Wj:YkyN ӗ`G3|sb咙!(b^Z7n\?mOۧI[OOdffH$8|g2 A_p ,:;;Uz*%3G9nZ>.ȟÈXѣw=yr$qFH_;{,7e+cK} $1Im>CBMWw9%!;@VBΞ=CQI)7dIz-5aa=-dɉb/P;~!RUU@^ξrsN~'N)IΜ`|IӧPLGݢEzmۿ-K_LU8m}OWr뵓`dƔfQ;ތ^ΡvrO .Ky!\\oηq0??2d5i6֯0 8m8cIt~ƚ|>Jg;՟H$```LDQ23c鶰d*I*è3Qb{囹-5gil8/WTQrz/;E& 3OPi|<zc|8bhg}4OJ:SS9cһz{PQqꐆ>}8H#l=WW|fFX~ +lHmŞR=w 匒?N IJJ9֡I^49ڽ\ʈbZ4U 9}H/dBR$g+P8z4Y$dRbȾY1uK zqB^N-1cYx1}~E$*J_/~v2+Q inyCa_M6K:f83gxX|~g/~ѣGy͛7[ygx㴶ϳsNݻ_ٲe RJ>oΞ= S8yİݻPÒjCC'N No߷)|GaKꟿR){=ϟOwww` >wwwg}˖-_>SO~q*b5<=zT?֭[yW9fa`֭$IZǑ_tzR~_Ylj*H)Yzo`Mcaꢾ~/]]]?S0&Sm~yN5 /tqikk# ҖBPcmFOO===>|ḟ̍Hz=I[@!D,xQ@kK+gϝ -\8~(]gXqw~t, vO#?ϊ~<y~O8ޖP[w%'϶i۹ o<*н(شG)+&ˠ?ܒh.}/d;pm6ظq#ټ曬_1cƐC]]ӦMcժUl߾ɓk9|0 .L.]ʪU+cҥګ̜9ױfZƎ˲eؼy3gX~=^z6.]ʺu먩!33zUV1~x8MrJjk'F~/[K/^{ >9y${a„ ٳ CII +W$;;2VXe˨&//z-[˜1cxؼyS|. ٹsgΜ>ܹFƏo%_UVw``Frss?>_R6m:qeH$8s _~9> 'Nb̙3+W2sL~)*?dӦMرSZo_|9ׯcZ]*?kW\AGGuuuTTTPTTćnd!H$qF,\rvɥ^Yh }׮=z*vɓyꩧʫtY 6rI&L8tR֬YM$Қ}]V^Mqq17C23ʲeXrcǎ'࣏0w\x)**40:uwy={pivĉg…dffJff&ObI)ٹs'ׯUV@cǎhQ|ٳgdowˋ/:>RPPHWWmmmԌfoY3p _j gʕ8pf B333RE9yyy H$BAa!_)3)%Λpn/y7Mmt8,fU$ΜȨ*={v禮D#ODz{M! Nt7NZ818y=sIBBͭ5rL+n`]z,|0~{<,z%~SVVJRo8ᓇȌ1b|=v#q-TCi G{s3-ǏqtzD2InYQ H$Wqw3~bQ"2ET@f,2;U0`mc AnVO,vt# g ik櫓 }==ۃdӧS^^7owus^R.};W^yx<ΓO>I2ȑÌ3ƍ رc;'No'~:jkkyuo*Jqwˋٹs'MMM\r%K޻9c1gͻٳgOWbM]CWW&MbԨ*:;;),,◿hT*Eaa!UUUs`~;ʼyxꫯ#n:ٷ뮻 J4a׮R)fϾ<3</L?k׮W8Rnfؼy3>Ww!;;Z^{5jkvZ*VXك7xc)wuϨԮ]Xhsg?Iɍ .B~_O[[+cǎeԩBooAܸq#6l`ȑ]Ûoi%˗s饗r1H7ĬY7rMur-,\cǎ}ye8p{9Zw ئFK%K^رc\p̝{ SE{{;;vlgժU̜9.nݦRo/;Mwwe===\n,͛WrMMM]G]]s^7 rssy_2H__ /ɓ'ϝ;ٳ 455꫎>3,Y\˚5kK[W\kξ}TW_:,]#F0fL K.eѢ:̙YZٻw/9ܹVx .ꬳPl!_X`eÇ8uuvVsCwwQy:rt_e˖'>͛G*e}IOWD ~ą]ȗ|O`ʑ#innNzQZS'innr BΜ>řӧ/($##6ɥ---Wp!w?n yWA,#dfr23UE,|LH[ښBu 5RzhlO<&p$KOo?q$Dh뮚OVyʹʫַ_~̘1)S3ɠ1v6ZO<ಉ2<_q?3Fܩ*YIZf4HIwc#%Gog'O5*ڱX}7-o?Ϳ^ƎebgK{젱JZJD$ˆJ Q@ff&g$Zzz=᜖ɵTWWקys]" HRnTU<3Xf-&''l2"ݭ8fZ"L9z{!X|epp9:;;P0;;n wS޽˓8,]sss>[w^`Xb%b 477qs=OH< ~ӟ?1^xLH&S8xp>(~; hnnF6Coo/-Z$uۋ|>/癩z{{b +o,G?Q HP$6o~o~L&rJ bllLs|/^ SPjxm݃b.cppDsN8$ 455Wݶyo~6&''Ԅ_$ۋߑNgNߏ^yxV)-Vkr 2 7߾U>:`owvtΝΐo&\]k5?n[شi4؏b(N>׷r,* 6n܈.z7GMۋicŊzЇ߉oh}r/_vrViJw,_>(1ƐNj*Edـ^u}0|^x%-xjj5?~lOOxE`y҈xvqݙD,Ùvwjxbv XZ޷m$#cNYG2^x Y.6n/CB6twO:u,+!Hk TwFmnO3ٍU}R8J_ ShpֲD)IĢ1䍽(â|Qp`|b cA q:pG'uI,C2B>[v}W|D_=֮];O?4֭[X,d2N8W]ur.Zu /3D2@RA,@4E*u߼ͯqb޽ؽ{7D"8x }Q<裸+j*|[7!OÉ'~BGG:;p_GSSxƱgق5kbOb||d {{/} Ɔ ٥v܁W,^L&q7"J+@WWKXx1"Էwy'V\)⬳7~r/ۼ׿ƾ}C8 Hr&h+ /w]jUqdYu/aXt)A2sA7yJ.CkA4ë^*eYRIiL&7=fW_p)7 gIMoz؏|>N|cӜ :^*N&Sd\p533k:8.rWܹ_Wހq+`8Wsqnf?~ o;kNd2Yi|cli&0AVE,C*B4E,s[g(xHRr=dYpΥmx~lٲx{ރ|;Tܹ׼W]u ?|H$Fg=ˡaB{fqNܡ6\}ҞRIn 2 n4&&&-}lٵXfiG?䤛)xG1z.ZȷVSSS'jyiR)޽}"8(hkkCRힿawu&bF\F[[kmPؖغ} jIG]9|Ay]lh( (2$ NJ]]]9 SN ‚}x'1<<. ,ޡb"<:;;՗&tL{066i `Vttt1,ك>!8vBdX6oބ!455'2ӹ^T_7055SN9E>66zCŋ7476:zgqf+Ԇښ߇!T*wq{McxxL{D淛mL .A _oAsDv؉'،!bppP`ߴim{C["36_Dd+HqQrt&-]hwTJE }'; LeOx{'w?IZ'Qb:E]ke.p\r7ׇm^ DQyi>lc.QI?fGr㤓Nw8"7 DCl',Ppx 7I $C& \-㹑0:7D$%GE a̠)Dcn5uv;r&X[1d":kږ~Wt/PC`h9Zpy18BwOkfjطwH/MŋH$T2 ZMnWj5LMMVsC2C1dr9`_t0v >&*F?odZ\{ LCzpǠRh"9m?N!8?X^QCazY݅mG}~~ۀBk^t;ck-<ҋ1e6v?D77_SPŦM~fFǞAm^ 1umtaRVfG%g 졔a' zmZ 9SSb\0&FbL_]Nr0_4}UHk,vr}W} Ե\LQiِܧrQ!C_vT~Z[N}]M&zk@^9mX^mnWciTܨMgmjm(] IDAT_}^oJ$o7yr_p}P$:!Rv+u,A<懥)n=*p6@z_dc7ٳYǞIS=3_E~l/@ESWP"4T-09OIR}H=o݁z Ph q zLt=~vZ[Q9cf1B}vlrui)=Ρ- klyxZ&WK:_Ƀz+6?cǏ/k}kҝr^&Ov ҵ%ǁ %a3 MQ1a005Pj=ZcBx f(Wbol~|1她4]y'!'7M %~@WB*##b)TCq䥡bјVKm2Mwf;/rL#,̜4=שh &1Ɣ8TۘCowmnyEms<4[*a[qր{'&Cc,'_T*J~ri+ (( HSG;Y)WkʂE4E2@A,C{+~*BXB"@ZE `vf\epZ Mya2b+>֔з!}yғqe{pNA:B&?Zh9Z qk+-R{Y!n{yyP?ͫ]]ݘDq OctA$IΣS(X8~l]=ƲKb wŲeq` {1xroٷ+V~,Yڇqcz +o,b>,ޤmŃɛf6pQ9V++ e.EW )s 9[R}g׼6 _,`υDˤRp_ަ<^NyꭏF4*`v^K!S#}ш غ5DH+xq \q7.^4ʁI{,] 'ߐe0mmH&EkbSSXdy<0sWq0 ED"iD" j H\.c~}t<] *y>-6ASoJ˸H3*g˾\A/ԑ~@DVo껐+d:[Q'crOԓQ Fäk΅(Ƨ0XOi}mAzuIXzeúIZ.©WM/6 `囚l4pOM$GdPt&T:X4l.XAEys!C# (lVoosnAt轠){^atMަ5ɺPؤAr+Hvyy8 VS#2ѭڄ:A^ ZGWwu#N#C^iq8พi,ilsa?Np ?;<33X r~c쏭d$ kyjsz!d I'5:;wD267ւV3^Zړq>fG23j͌TWZ? dej|v6ĵ /hv o[/(5‚h== NœV8ea4|(p0uo3u A<"MF~@*L P=l25*g#sׇIf [5քY(JbkP**[g>qĵ eマ+3Ml"m4jScI,[ Xlw4b ֕@?G mpha,R+>M ]멮r Di鬽>>czWj3r&&هo)ꔏSezX{iMDƊɓ:GMzty߰A}mߨ O4hbX8Z bl(F6Z gJ˵>P> ?oZ WoH`&Bt5~6PG gsmw ̵SJi& Ȣ@EP?HC(*(k`* Gerۊ 6ӁU (dAI6}0xhc@e. A,6W鷐baȧ[EAv^cهCjTF"!}ۂ-2Mm vhVv=ѱRtM`[ Lбto2 /wk$GC  C8O\1H@X:#}}26c,5 -,jtu^N?2H7?4+8\4hyJyu*pbZfLG>PCC LH[ W<ȀPj;Ͱ"$ t 'V݅Gx΂lj`)4I?i_^[HnGSY6~b;u}K56qb7RL[L'/dĝL#IT~^㚣<=N *3je{3_d=m<H ~Pld-j252Yږred(|WY)nmh`Z5}-zNBƜq"#MuHɵPRA…1ACǘ!|M(*hAs`%6fBRFCVvP?&s,aDCs]>䵲 4bJwQa@A> MHz"fKJHk؂ѠIr؂5A3WQ3s߿&[FfMbDLpBC_T&뤒f5ڂ<4H F{ב/#R"ES朣s"p]8TĉhY0usy(&CŮ7bƓ\2[R d2N`fGY2xY1$9S7yZzmh;;, <׆kC5iW9NTl`˨>$}}45< 0e;\>[[+IB?orÖZ_oTn@*6UL᳕gw¤߇()i.REf;-oNNlls"'CffH\bCΐA6$ Z ӀӽZX^s,U`^)!:HCX{nQ$::ڑL$Z F̄XՕm pC"^C!e^ˆ{a|l< Gz 333=VNS(KjJe.+.0+17;Ae^4;*f?m@6g=:o(Ou jk΋rrq ^Dw>scV?G&P4Ř9Q+yMV4U.NFVs9obB2j;UMobHm 0 nۭj ''9A#kӜ^'{6:)ԃDAU eǩmĠrr@:kPBI϶\7Ӵʆh}' ~ #J߼r gG3?^muc)`08Bׇf$ېG!ES@[{s5i*HQcT',Y8]?G复iꠦ3H "" N#kk)"$WL0BQ&8uD zfJQܣ24H9XL!\29jC`ar|Ʊs.'IsKeݽ; anyS;x{v#1975u.r*/l/<~FCYb]]1?tGY4ɓspfanAſ E(JܕظRch0A A r~BB>\&cu!ƊM\,2)ynS%ֶco17! ^tj<"fqq_j+ L>jdSV{K廉@يv=D z|攲%qGɝ@6gf ,[>!P&suNW4$N7:s- i~[*I [F;jYwfvƖ^83ފ=Xz-C*B4Ak[+"I]ә4,[~ ߒ &u!N 1t-F9޾f*smbq= H _ B[-J[Q 1BCf|,@Ai!dx<Q._X<&ed2rlT*EДρ1&:5eeQ[ P`(leXܐ0w%(vPM8׮1琽@xoc%lzsَ>nl{y2s׋@ @e:y2E`jxGZ>{ (/7d\VQZsI<͠Aɫ .jhSxB';=_ýT&;^h/icNg#O'? w\D"m.\7ߋtpC,|8j&'&q` sOy}TiRQos4'jKFqYC'$ LKpAUFEG8{O4PY<`Pt\_=YGڈ B𫚮Lx"yĴׁ 7n1Y(dlL5yxcMheML&h~hY7£gFȔ+EƎ~9RׇV/dE0mS>ƄYME0@Ƅۇ&OK:Pm,O5 0B'DW?%7ϣݡjG} ]Q%!hFh''WJ--@.qjDA,C45 d㘵+o¿}K-Ǡgq7n{x|2eXv|i6a@{gV^?5DQvX(GގRc֬p3X.W,ULaK(g68VƠ/ߕ&"k @Ռ)=dhGx!&d-M [vLB /Wc̹,̿6[AM5T^J%V'WE+2+5sp< ]/,7H6wgI7A)%%1e%S-}2u@ ccc,X,e\x1i2MXC!nG|J#EioG,F7hyK[K :Zw'ڈQ_}\ƀ_1BӐB F! IDATBJgψ-Lq8SY#w.pPg$ $\gsds.U#ö6o{+S$fчǏ/c M\b]Hmi6v걃%l=dh KAul޾LS]#7y ]!'=Aw^&cm䮖#6dg~6Qɣ^!U1 $@`<9XL/k"6Qö`Χ9[!óky7eC2e[qAZT<>ё> ،^ d Z9CKܓ [ȹG$,lcl/e-iܨd#`壥u!{=}H"QQOR EQM8׮WP'r t U?-NL3m9!0\QVF=f.%׮(\ޣWՋPh0au⊜KHY1ɆF26K`)O+ "8u6 l'!#b$ZSe ;[1ӚcI_3H"~]Z>pSÂA)Լ\HeS @~kqJl+p=ޜ\˓}H,7ךY( = oi;푮!If†aL8Ҙr /j{gΖ)sOԐ{d> i$ߟr) X=$.slݱmy㫮ݦr8j7a2Ņ]4c(bK2ƭ4JQ;g=fʭhK_WEtvJ}ÝH *`ܧ@ֽ;MǦ˫!NrM1p LdHfrM,3̛/4n4twM^LkؤJ42qzįcg2l#{dqp]ךU&eS [Dˤ ̹=&Hmh!ۙc5W纄<8#SN1K˚#S"$g#`%JS0 MAhqBߧp@hpZ&MLL^+q_&&ƽGP@ZE\Fa~ss((x;ކ9j5 ϻoqT0??'e|胗c]V?6V0o̵"aR;}^/AsB+?)(zRORGW<8ut]Hz-K=^(X.K= !S<4$?\NSъk&AG7s+ [~}(!dr}9egMm&x Fvv8 EmOFdxPjy'DžPz)Y]FUٖ#}}[y-dW6YL~S\kt'ߓ,< %dMX1p2b>?@F(BNk]5Aտ~VKP@1\2b 2Q̔;l3Xmr&o8zI\??h4w?oc``9>\x9gr++P,~!9~vO[Z6S,(˘/L2WBieh9ZhL*k}b@d|L%>ai"q 'ct͔$ ,٘*y* hhe?>JSŨ$J?7j:l֪( zΝXv-r&\t_/-73ŋū\p>q7/}=_@$°l`7}?ZZ[q 7op'H&5RbǮ=̿_%эWe&Kv p`z(Ƿ y,Rp4ҿ^ۙ a^JY@cc־NO`]}]O&=6c=ޔ狝D>lvoӞv\dHKҸT&Ss21!#ʔ5slŮ8{yT6г  PppU\ThCIXy*?__F&1==l.Xl/&={J'zZIJeu1ݳ ;ZѿkrWAd"9-9FToZosA6Lz/k71lF7Ϧ#HS0}rM6dӯ[zc$hdѨ\aޤb 8Pڞ/̾6E[^7e&<f?= A< 10' Pkh,J1'@\e?Xn=z,ԓObh#ˡ s ,_87/|oݍlT}o x _EPxEfro@~vaZ WtϗzA& jo]hg+3l9 l#Hmm kfFdAA:4:G)z3_0|ߠ&s#r֛6jH\eaVq{(&h[*qDC]Ф=;cxX\g&h(ڸwW_,4mcevc1ONA9CیOc`bt:YCwɶueosA֏~ź 6avh:s=aϦAv0BhJ2#L0;Mt j>jdn $?cL{Ѩb!&tk@/+_ aGL^4ʹRO8XMM9#B \# kզ#Q*}fѺ4b"أ+1L!?6Vk n A}ᖅ>h]dܵ|ϴw_nTzLfpm/hiV(d Yl;4=3@~Y%o׾yH߽=jq[f:4u^JC\Q'I&I 'QPh:! y C@/HG+S4ʕ*5Dc&q[( fۼjpK##?,ІiitզM^Fm|h#2J"&ϤrgxMgx zh(򅛒&^^FxzQ^U0`F7b.(Cq70KrIIkK'E@H˜z6٤To` PlKNhA;I ?Htp~rn:tZ'mnJelsQhaY/vߦcPz`#hxvۨaAkƴOМ8LgSG>1 #a޼]/ȶ.@nʽ@/| P{J\hsqQT mtu05Ł1Ӊ'J@xP>؞ha kL:Qu _z _gQyNAl FR2sO($Y`sj * 2J2Z6mŵ`AsR|l@G~‚BM&vu dFh[ _F4hs4a@W/4k͘ t/c'D:3cnqjT $TS`sy[&lPE!ggv2x},QcsvژF"z0$C#@|ׯQ[('I;lSo%_#xѱs|< ]=Ÿ53d6Kƒ[` #ߕ[o&ntbC1@$T{˺|3| 'b ̶u15  z3͚Pg9j툃`{sC&qojj sLNLbnn^s =&E8?qڌv s~G Eګ i\nRLY]~DFÄ\&+K!_Zꁁzt"kXom. N1yelr@_/;W.Tpo,pV6:@a&Q=/o>PA׮}1F9yd(n6syO]RAG=eV}}%K`xR$MY00 1X,,Z##Hhik(KSסZ\*gq7FP,ڌ]/Bsk -d&tZȲN}|D:τ}m#N{8x ef+m h 3H&[0:6aEGazoV 0i ?zi ~P+a_1tp ÁLxU ~h{0D# 8ժ921]G"Lq8" Nq8`D1^D/"Jz@[C y@3 62kĠ71ch"P\o]mN$W$X?7yH/OFg: 63l=okK2$WY+SFQ/n*>JaX846E]?|b ]`M=0*iFQTUDQL@XBK[ fg| M&ڏh4 p`h-y!L#FF6Df.@Gݼf-T]=/( A;anig8Ӄ}sѨO} [l)L&ؼy3> ?яpm[n}𖷼|7tqYl>3<]w>Ǚg>7nDOO.R^L_|1q7\. `ظq#5z5 o43hV /F8}_LüWh6*P}th/3@|3/11[\ lBi E|깗@*t0_hKr \@/up d&@٭E^3a:c:AG-@ᛙj)m>Z-ϔLN4yI'l)b1tww#jMqH~G9pB F #>cuw2 M%!/ue^sdT+drwZ aŨVD#cd2#݁g~[Z[w^T+UDcQr2X ss@ZC$"C<ظQMYtb؝1 qjǘ+v;);wow sr|]voࢋ.B6w]e޽_pI'![oEZEgg'~~'?~sEXħ>)lذo|d###(˨VַW^y%/V!N;?2R[ Tq'W=,Gl ɮj!6H UOzķVءa~ 3xwy}8,zGq/{X߆L [Geh#"v?U}& \pj'\c 8.Q}4>ȵՖZ,pi` Lu[r =NrIIk4}f8ҐGLpݓ2|tC#[2Cx>/JFl [AWvuP\-2h.zL,Z3+xX {ԡ䦠)$P=r(Ϥh4 '9ߏ?q{xއB&Ygk7oFkk+xW^y%mۆhiiA&A6c d2ć?a477#L#PҰ v#e<:n'aGOkJ'}XbIJ ZZ"f[gYx#2BXVܧ4Mpe*BD1YgV 0FQ\1ު1LMLD=&M a3a.nlVlx IHBL,)0RO4E:N~޷kt7Fz2N߅'( ULٿ Q]{6>au>^Byԫ 0](6֍[NhBQ9ؼgUg/ cMGcXiʁL!~)e ޡs.IW0t\p% wߓatZSeh~}bjOLF*dh1bfd [BޛGYvwx[r_+K҂ Ex`cl1ئiN3=ݖ|10XA,ЂRUYYU[oFߍX9gBލ"~ݛ9 wmhSaD$ D,z&fTf6:֛̬NWvDl7s[{n|-k +0NBJ0k@?C}:pD%4DzC{x3! |U vgš^90ҥEGq 8t KgqT"&r ws,.N_guTJg9#$%9A`K2vߍn - X~Ith58owIh}j IwIj%P׮qdٴF lДC\“a{_+ziZٯ29 #* Rv?%yhP4rV!puǴ)>{Lm+_ [U}7J+[(x C$XQ.:3:t3|J` t/ZkA͵\jh=G@؏JjGbA'AW02X}S῔`)h%܌cZ)F8b#NF $B@6%VW0=uT ]=] `ݶ3 #Ko)P*>loZ'|zq~Z ]1Z:X$. fzmC:VTqly1~4}Jٮ݀=jzg9x&ShǣlO݀<ͅ+H%eLjonP#`ʒA/^U;ДַKnNc^ȟ/M Q@dFAx X3_ @gRBd*@ٱ@5YL؞Mk@%Z2۵xuGA1.&̓9v;tξkA Q(&PkֵN֒t\O=T D:8(s[80BalN#vдAWH_eb'[hahDiIu=B\*cxt]8ituwauykkk; 0ua {G1\xݝ:} )!ڂ 8|++XY^̣Z!Րocan{zQ)WPT[[)ۄ< ;Eban@GgBs--M@ӫՒvt2;:Z7m 3QJE`JL$GZ&i=[U B9j2<_NilUß)FSU @-ezl "4;5c |x:sͯAh%`΃'@ArA?Kƥ`ߩ EChtu^2RʲL7&?m&Uh߁}|2<8 چgk[+ZJԔAVCTµ+3PT>=u#}#;;ftvwbeiZ gkcc+K(ʘ<8SW ttvtvӶ? 5ho׹NȔ,sQMc[;.ԩ@k|H<<,m&̈^ً$@ړ I;9\%o|tTwc+=n$>98ݤg$=2RєdA Q fkJl:rlS=R+z0]©QTiޠc§Q5fyC&"1HDrWa9GJ'X'@`9ZǾ3XL4()!T˚o.Kݤ'mJ6QHWTU`_4j5Q*P.|Z&^l<[1?;l.__񗊥hd khj %r, jLSƯoNMf`XcY`6r S[,^_]/,,z3'O.cr|EOAߓWbeqy?q羭xqeu]!X!=zHVV ҨV]&!Ҁ3$/ \gH=zBtQbHĀ,}B j "BpRk@[:8N>Bv$qGq[%m‡fbjZJ$PW) 0YB`@A4 Af @PfB:%6nd.mԮ6M8̉D|i]P?r{{p4!|2Fb`h.L|A-@ %2h i``;@]hjjҙl.Wayqm@6EXDKk+4:: @[{UkikE6-@KhRMˆH2DDƻ$ s=]%FFI}6$IW,0}u-%Ü)ɞ4Qٛ.{ԢMO#t@<\4Ģ ;pyGlmي "KxY̓ǁ/1,$ mv2iތ Yd)@4PRDD:Cm*wn#@iv-4ھuyBJai}2i ja8N˨ZK @)Y&CEYtG}QH:'CƸ2V>AyX#v!(Lbqt9xTvaKk@Gg;::YcoOC[WBy082`$p]\'봕' O+>S58ৌ:,% )I1>p /`99 ,ą `b:N֧ "pe=ETtyW5蛱cAꑸmOl /<ь{Őv0 aٮ4nZO]1Dѐ̝YX=$/uM1+VlUG-rѾE9=%5J|u6 -B#$+ F eDmTN>mwђp<`{Z*2pr@J~1 86_ jXy'fHRYk[mD!邵d\-\k5N!1;2=@>Kn'5V=,ȾP!L4^b+.U,m@Mګ4nl,p0?K+e<*麴5 X(è>n_zT8 fNr?I'-D' mdڐA>E/2P,S$l;NMhJJN &YPZƗ WSH)RwK/=324CqK_n.šB`xp%;6;5ab| 7tsB`n:q8#(rm/x-Fkt]_N !A .G<<8[nmmL· ;r% ̙ҵۃ[n=DIŋDwg'n-7@kK3rrZ➥M8vn:q= sAO&tjr>1\?Q.̬⮿=o4@XRZ P*WQ,WgA*T\TkϢPFox쉧pAa(F\ } D.ťD6ۄa7f3'O%zG QGJ>)%&ph$ ~;|--xm/G*. 9y ';c809!sY|8`r8;a ?;f0j馲m-;6<ɽ!o^4550GGpA )%:مg?;|'OARղ|)TAH)`2 !h-J8}<:;:0:<׾v䲑΃8rFѿ7pT LӍ>9#'uGKn/I~vիk+EYO]|[ks4>%T*UKAw;U/(q*a]ZCf]0 *?h|RČ>8S;{i􈘙hBJZt>,!Lփ5 2Y@ܲTr  3*ߦ@--D ܋HY="R=J4,ECN9}6MQk:~ ?3 LI:)Щ\.u)MƲc]Zyv?M hKg"ڼ&X:ã(S\H d2"As)4MK8 .]8^  cd$S G~ CCї퇔<'΢ZCh8yi 9۔~uOO÷L;XVqmv\}8w"[n=%gloիX'O߶11>W i86ΡX,bsk C8}:=}h`?wōǯG5 Js ȪWpp( X^YAX F1;7.twv M9y 7g/\&77pQ/ 8r@ 4" j{Hz GuKB`U\:;0<4)twv`ii#CaF'čׇӍWh.󧠃w *VpWVqCO_2|>Kӳ0~ eou\wx/~ ЉC=[|~Gn+x cz~ Elr m+!Z[ZP jH@?J4:;:ގro>mA 5Wgf1<8O/7{P,qzaOH&|u ~/¢ůP(T s 8vvM(峴1Tk5yTUtCJRS!aO_/775JD>\ZC-{ ]F[k R2Ξl6.ml٦&lmmcvn'anaϞqllnbbl ^E&z?`՜J)ҌyԂhfSg"{{>T׸K%lnn2!ӍX\\gԔA%Gb;xH%m|ϹY.ZBFۀ[sg."nフ~hj;1߃=?īз~?hGH-kTȆV"i[65_4r*^& IDAT= !H=c`ESeC51 igzrE!]:q+2W {(H5 NCꤔ:ER P`#I$*w'<9Y$O=4;Q*edކUlMqT+Utvw"NSD.X_GJ͍MascҼJfΠV 026g~m*& KH/:I6[] $ X633QZt* 3f+D-?t+Onn]g.cu6tZȢ%/'puqlͣ9m*` 9v4V1`5 gOV 0;2:єi 7#H Z[p=yd2hkiA.?s.r.l;s.V<!·honmt2H 坾z}}xٓ8zj}76191%ol@@܅V  <:in8sqyCS&kXXZ8Vֱ -ej5 g09E_/w U|‡7024B>Πxi bqiXY[Z j>pr-8u0>{=¢}pRam}D(β7/,! 83s󘙝mϞ/=^ዒ\Ձf~aIv9KJ& BI O9Eתo$'Z|)טؾ&g"!ۉ @r&!۔m7ÏÏďq RB`RBbr|_}hiΣ9vtw:$ z}Kߌ/sΒla+CNh;&DoE<};R9"{k942#}ȕ'[ 8tu8cVA2O4V9yd]I?,OWIVH۽fRƲT#p1qT٫3v:F\" {Q(>׮\6z{1suT )fl.~|x1N\'v>mW4UalBtPB%+c@E?\~T|0|A#ULIsڄ"CY).I LgX V{ZKn;zeAZdEH3{k(+{9@ !r`tXY^#I܌ՕUtt ++4ڏŅ%؃NtuwauyB.Þ=jhnisr9t=_Ý*N],\xwpΔrlN\\mZE } j*;f{KӦGݖ,At1v(+&}mF6xg2ddZẃ& # fsMP>r}tG4t@04vxPvLAѝ%gt8 Ry4Q \SEQo`pIyD8Y'|ݸcPQkHb'a?c% ƬOAPRP4|QX Kp)) 3r2|R?%DȵmmgOHS4|s^~Ggv-mRbxl\ZZolzz1eU nxHib8.N2Vo\zvS&Wb ~꯷26~k{ipL}EOH*-xk)eA_1yaW1}+4Ȓf;z|qd/V_&6s@V@)/yXĹ*DUl=D"6 _Pt}Z+)! D "B*%,ULL5˜_sdwgx1(ٽђ2 IfF(I][ /.RB;ܪDfmgbicVqP֕Zzā,ݤJo~ᅥѭhi4#e8T[Xr]Y^H)|i?h h{W^,lmw0k>ьf$YPEn`^7#X1a_ SVJ8fYߣ)28 O5CCsg<@46P7"Doj[npdלR1!*F,"q\@xcpC0:h *:! T~/mg膁(cCطc}Y=G$emNd@Fo'J̎'ӲyƑ':#TdGdjsmC*/͆;`L2) G:f *d냣݌E%7CuzRO^ʋEZkkkS\BP/(A~<IޟUC遒ɫ(ۏFP0㒳уElқO9+a"z)4ZtrhPyz$JSw`|6R6 2aiZF*[c(=_[@n-ZWQTB=B6-CT \J E i `0ۅ0[ȃP \ôf@7l. 4waxTt#NGc:iw8q[Q8_YطO(ъM' Rza#'Txcc 9>\hkoEKk ._cz Z݉Uct|sfю'6;Ea}}=(4pU4e08<"\ 2,-,B`hdtڲ2J"%'TgnDWEeߋO2}\%;:nj7Eu#t$"|OZ(kcNK $KkZUDsOwoTg )ˋxeЏk0Mțfi":&KhۈMUIM&S6zhI:oVϫ0lc27 M}翥It" uk'zͪN|΃uɦ _'AEBb 5CqhAlO.cAdSS_Cx9iGz*Οf̡s̵ttc, єmµ+3KKFWO7P07;Vlo`vfX_DCX2"yP'9aa} w`sNkzzԺ4if8@@DWo("߉{<04P\z4P\>[>99o<>hv/t}@w D>`!i'|ߵrC=K`4#'K>FLT6b~q̡cb3X yIQ (pgØ3t WF2;739ǑcccRY:(KDL0ˢ lZZ[ߵXYT\@*BT4Zfd,Xl w'Mg4ch "d^*n2cI %e\S'wXxE8.ެ Өꁤ$[5ҿ0tꚫ 8̍y864 @RE)/uP RRZ?MFPm4ӷV @RE0kM' 8/ÀOmlԚ$`Li YׂC^$qzpq_!ql?E`H赶+7;ŏEQtIQxE/1l>j-nxq1S+jb1tDAJae};ѷ_BXSd2p !bb>,-"~V*Ԃ@2 vϞbiq K KfK(Ԧ8.m׏֚zET!߉>9z BV3IYzcn2Իxԣ$֣ؓKy >9\wã.廱#L8Nk~*I`ay 'm_[;N;)rM{I\< ;EJeKVk(ߝށOKeTjUKlnRܥkOnl vbrR9BTj}$aB"nh B3_", ۜfi$(F9^]UP RcI_s至^dU?iR:RڿEHWt '+3冡#B1컅( ]ЦP?칧}{z m!}jgБу- LDO_|co' ۜo4} f's")vT"GD;+Ik< /3ţteG\cx13 %KӨI<\@>\<8RgS*g=yyqz|6IUlE$WL)0G>| ;x؟sY|w~=}|k(W8<9#GqU}cltWDOW^xSoc'~7ۇϞo k['Q,1}m7_$/E{[3{N /Cv|e#g\tIP|.Yh |GO}>uxݎO?Iӗ_㵯8A eR)?57z<ƣ_ӻt߻ko7_3z!Ŷ"Mk#XZ* -4 ℹb]V̋ϢOqcm*)ov.GDvz;.?D)0c^s8H4b JvY2)lɭ"ZΌ*LwljmGO G~Ǣ$d %wj {c37Chl%5Bk- `~A'$o!".-; `үQK E@oLu\ӏayh2q>^Q<3xo:֖"[{]gquv 3 +t2i/kxrE9>ҏb DS) ?RbXܼyNkKpm~ !tw=Um@y*%v:|+APᵎ׺^SGj`PSPkOR>uGA6%M3ѧ^Z^2rA5q"$A\ H.LU.|<\Oe'qMۅ_cOE- ]@)C=hm)շc95R)?2\.8L ACFlދg/)=G1>l_|hkx)zՎt*H|z%!Avfɲ LW3&ȴiRꈙ#"4!fdg`#6*_%lUHY맮.pQ#-s 17;+אZNZSc\/>tW&wZ$ގ IDAT==hdE*hP'`,5΍͍CZDЀR پy(e;rrr21w__07\ՓIඋ/vwN5Qߵ/k1 $$t_Ooڍ޼浞Izs~6>k+(4w #qʗ9eq5T$ ?'OV })~g^o%o'zFeg1QswSNa9'Zrh[ M?%4м촵u+f8WOZՑ8ǿVO1@\A'm웘@gg**2CVJ=ٱI'z:|s.7R@RQ/Na:꓌lx"IJ82a[%_-;v SSSx{ Jq>Cx~"WRWx000g}wyg"YZZ>90y3}]weX]]>1A|uykWւqƗpنM瓙M>K|xkݺqJ_=>M7"?(^J ;EBa?i=A䕺Ns B>–[Ye忩2fOG04=sC!k5PXD` t- gp5^VU[/ɏdfy6kEtb uuGW :x.uڕwv݅.R)- S;1d CM<;Qh.B8lODi64\/ ?UmBވ-81wP^I 077W\sx tMH??mo{|A|2KKKV?zƯʯ\.o~3=;|_Ekk+w{ ַq7Uz t <#'bii oy[pwҥKx"w{ř3g𶷽 gϞ1995cyy|{/z{{ot_x199+_JСC6w:m̑+i C9G.:i@=\t8H&>Sjt4ү0Aç#@94@Hr+45// 㾌P-p-m blM;6|VaanRJ,/Zb߁ lomcue ]]tvw(14:lT&:ep'Ξ-RD756n#4/Fu34ʛ-([n 17;#WW)4#iDI|6c8_C9ZE/Az=FEt6761731L]1y ::!ޏg.BWG3ϣo` FGp%ttu`}m)499u#C!DH ?HC&5Dvcc(ß7G?<044tvv A`vvjl혝Eoo/jVĵkאf100%lmmahh333A*•+W4d2RN }0" ͢Z"a~~}}}(FPHXXX P,ڊr !VFE>O2kW]ܹ݇|XO Jo vmS[-[6]xXk!{܂jj!;7k!®t- @)$I&#^$,3NJ.DiRbKk :q mBwO* !d݉utuu @hjjBwO72$=lV腧"I)ᄝsYx1$/ձTmXN{Q4(RE. !0::R,Yg]0޽{ccc19,RJ[rQB=#n>*Tg+OhL*VM_&5zr~: P=pK:6ArBMko'/T_ A kxmNY":p"VuFP0ʎV)p/X:ֈrHHS`-evr߫X!84 NtIpL)]K$ĀM4nqd_}@hI*1. `~CP::]T"ҝXN]ЂLΞ>ãÄZ?z>;4j8/ &V_!6uڟ իJ =\8ٻ,UPloocyyb,\}}}; IR/xP>qFy lKR& $8Οe nzhKmwيsͣSNd28yM ."m­7GTAZp|~ -xc}\&jAh l(B(e }ŏv|V1 JJTI㡰({Q^z0db +3Vص ՂyNˇ9}fzm kZüH HamѦ/]`fY6w,",H%&%!?t~^UI >AoOϮ{@JZ^Yg}cqI_@s]pp tqǏ˔Fιڒd<2gjIi=G#r[wV#wz?q_q xU$'ƒK 4V@s!: hևA.V&xDE<<XS>xZ`]plt\6=k,m.l]'ŜX{TNE@fU+%Xc=!\QSR4i}|ӁfeLsɨlvMN䃵`}|2C"?U"1QG?ݖmmFY!R/j5mS<Ax2QתӶFJVCJk+x_[Vv/˒m(Ite/emK&9.Q]?}26Կo>~pҜE_ szZ-nbkޮ6~Q}%[PJُ!Z[%s(L`g4) =_'K iE|WQ-ˌXB D`+J5B2.q.>qٹ1NcEh%dT LMӋ^6)ظtuO*IU>Ocmm j7qHtw! alloxÏkLAR S}{?ۿ[LMMw~wtO~;QVO}{5hT*( (JR"ˡT*A\.j((JVBr_/F=`z4(Ov2%&6]s6𪧧O6ޖdӨ"+'&vhԘ1q 1Iof~SJAhnE ܏}1Y|gΜFGG,//a``׾,//3 j*wp睿җbykO7\w[pÇ+%TWǯy'9{ KjS'*m YWwLGKII2 H,dqw;`o]c]{鬯kUlm!giʆ߷8-h_,"%4;<\U@:_48g r(z2 ̩bFu!Bxq:<(w"KcP 0QTIg 36[P>,DX@p34 5^T+йmP`X4Lc R2eŎ61gƜ"$9~N/|^|_!B`jj ÇE!… .੧BGG077w?slnnE_ߏOWxk:ۇVT*_?[ՅbѼ{em옔Hi>.~z_Pog#:ԣI}5Cj.碑=k>P*Wo>[Fi2-8JU9k:2~ׄ7iкNgz2aZG[8..Ia<{O|-Q1ax0`EQD> (>#8Srla'6 NrG 4)ܰ|kR/}}'?΃Ӡ&$Bߗ6ٚ%\77GS6JBs"ǝNO _=$N {NF>_@KK +++X__氽lmm;Y>4?YIŗH>& +>dz ;$D;Ozxlmoosx n1+,j4^TgH$N[ĵ0i6CLj䠲5=El#\Py:4XX]h%{ I\63=ά:,% :EH AAȀH!Q!]Rz(B|(@,H,p Xggv;ߏOkяe#Yoݻw67xc@b@G3 dè!4k6=(}QQʦ56_ E_rw[N095oZ,C&=H$ S#>J fftӘmj]R2˅ϱN_#]Vg<&Ba*5@ RD) k8vRv سt\b0q.RCX:&`hScYd},Lc6z/qCKK38v!ۘE2DSs"hlnVWsX\XDOo11ux;|+X]]EKk J [0?7*${q9lqr>FE$@ZY P C,ǹKp(`J/cbs CJIZ+*;IqFB⓴ =]dV‹/`ff&p iylzs>8 B&OI7 ZɃmgdæVa6չkD7x{g;4;'Dȇ?D hmi-ce87xVL΢>A2@ц[v#X݅W'ML  IDAT֊)d245X*O}W_yڻ=] XY]EG{9t>Hmva!t{`ʼr?6СC3z ' @FKd u*7VuASzPbs,(y|EǶ`Hϼ =Q5matdf02<@n)ittb|t}{Ny?=59t& ƼݩYcۃL&LC%L N.Wt:i+bMM144ald+VHXZ\B]Κxm oҠ)+AQui1p,ac.J*B G5Ŏv; VA@8X󀌎&6.eZ@xD&l.PY]avff2e# %(W\8 1yqMYO`cz{r~`rjMYCsS#{m8u<{n,./#L /P(`}_/'ֆޞn\՛p(W*呣UW\KXՉlyۑiC?8t8&;u1<'y˓]LXXSG# ::+&E>nY!)!G&zXtzH ,%0e4m z6 ^Nb 1X>BOJD vق{kТ3QpT :tu'~@k{p:_907;moC"@[GJ2Z[[m"ˡtfߙb_U4x?yJӅcذy@/c L[mAXDWwԱ ygxo]) :7 2X@LZQv(b/9a7lz]d^Nk&˃^ \}ҮVXﭥ hGkQ d>V126CP2Yykv_|x<]۷ZȤӈ㘘^R.cbjsshkmƁ~|ߟ@CC=6_ڂ Au(+(J(W[Q\r45fSg뮱( >2QŢ\^r Kbo}:Q()3/S^+cC QQC0+ڳݩ2P8E3cN>Js%PZ33c#v™8Q1xF; Hn۴'K`HyےuuNǵwFcST:T:\.{OOđNM8:2!scS.m4Lj(hcAe0բYj+oJ[1s/.Ŀ<24z5xiC?GFpdW HQc=bpFF17?A*U\/\y%}%0d2 <صc;:{78zΜdһE98|)(1R?l% PzוD-)e AGP_.4;xO@#`.itL|Vt1c%xKx/Rbc|qaLT HDьA"19 ::zitQۑL$04) 7jv=iKW0wK9m0/ףdT(K(K((M$j\L~ KkQ Rk7DA翲pNJ(j经ciyy-Afn?fCvnk=bbREϥ 5 ^ߡsd+_ot[Xip"^9x[lF2@TFTDR oۆrfD@i( #`,= g@Y \m T@`LGD %XnGBC Iޚ,DHKLX/4c]>qͣJ7h<NW#89*8*Rj&O-9q>m.QM/՛xs+JO%OtNV[enpZU,W >w~+6j۟ 2k\84$OS 0(0JF%+uE%4}̙#Hp >9 rNNϡo"JBٮl˹BK; V$XF!ChtŸn|J 5>I.' :ir(sHBӣ-vv_"}7jBC,aA%nDOeTms*9-%گa:Ύ mVRkn]Q&ZAm~RZaa}My>s~tEZעsf1ZEkmo|=]^cL5@myd#}-VZ;PjLgۂ(eD,CTIN듖Tfؒ%x>[|z=g@#:7Р뢿h'|[Wd딆6X̰Ni(Qz:11_8CL3JaINpqNih [wW*1' qMEn1I d\ת3h8VT0Q-:D4ZhZ Ȣ# PHyM٨La66>TnuDҗ'Le#\(_ [b6wޅWO^{t !BCa Ns.>ET?&~Xg(8J -=׊ŜĮv8cc聉2 -}+++tqD{Q,؀D" ("~6^ `-Z<BkזHLXGl Ҕ-,k*ZxP7 $t9sm*aq&͞QCw(ڮsjܱJ$X buϹ>=ʽ$+P-[[q-'R0^5ALy-%OZWse?|94}I:e`Ia"Ktua-fkX4MRN R;VXXi0&t' 1]Gmĥ9Xvzji 6ފ,PWWT:LOMcePT0<8Tzz{0|q1C@Vئ$ơրג0֒x"Z}*0?7[߂姂jkYWjV:kySU@ࢇk.+^B[#QW;ۊmcr[W0|@lo oqm{Ҥc^β˖v\2d6yd Ϧ]تMUT)ir(J6]炟}3tthu?G,ɬU%QəE.idRuhm;Ϗ ubz;;W>.c^\E\Ύ_֋}NaCo `~1\q4[غ Ղ5>xl vlKJB6ltmF$貫m)0lxm(W1>6O qh)f0P岕MwvN@[}M]\7i(u 1 %RYofq%B!/׽5h46Y(༔!kM~4\EXx(dmMS$ g8'T9xD0;E%(]=x6*Qr[3Й`-joDRUo|<9M9j(pF jY 0U.q C(G- .*u-xᕳغ m8y~ j3XՂ֦z?;'Ώa>du87<֦l\߁W .\=ظFOG3u`t=\]MTXY-`c_RuI-`u׎'e ͍;36jA"" tX ]D_w+\^xs +2Ѕ|LNXƷ6`^ާWO,IA Dy; 21p[$|i+@;rgLgLɴ. @@#^{po(]>4f2<hPFl5Tl +0*rMDɥڬ(xKm:0pwOI\ VFF_1P__+0aaZ:=vMXlYe6 H[K[sza^h,8|o߁ťE|صkz@[\acLe#Vazjo&; :LQ*W069{#s`W~a9u`"j+C"ڼy^@w{K㳚\+fKNsKEt4\`fn=(+`**[ۜ0-V0>j#G®? =3}LیF/;5\8syO S]딦f?ݮ0:⚖osp^ձ!!f@qS8,l,Zې0T2,)*q g(x~JV f"j;mS4 h;W@~aj$i3VTVm2J1.=Kgޕ"}Ӑpl]\u$>.ӵ- X~0m&VD5G-EWҢc53S:Q֔q\f"7ȖM9M]tΜ7ux=L:n~U"!k2d'LO}n)a~ͥK(ru#KoW\O4-&<_IO}; CgL DcNh2Ε,1Mp5?D:8vM>C `{bGqմ̈́'2O`N*o'ut+mZ!mqHӺ u{;W27"4t4iK':c:* :g$+5jL!Ƈ(&&5R@VKrlsfnNlk80i#EQ=c@I[AF~6![rqҥB^6ndd\CP> ^}U~y۷OӏsQ<3Ӯ ,,wuu)zpN.5vuaG~.EÔWce^8+~AէdTׅ|=='at9!er*TnRNiQ9ky\%z?̘O/8G$2d0q9Wp#;tACs^@WAIjt:%lliwK:fp L%0|q۶QvYXW2nG)(9O :?)?Z2/Љ*1|ñcǰ똝?l6۷… fVذagoOStMxN++ٳgqܹsr+pIr9xfxhS IDAT144s=T*-b/~ ر###صk:{ȑ#x_r7x#?T ---XZZƍ؈<~gϞݻ/6m³>~~<z8pwƳ>'|_p\y޽{ى ˡRnO<_~7nğ@2 8#Hx~'a_[7.o=-Xk6[bVO2o,fN-"fYFY\DBČ'݇|J_OMF{,yRXJF.ѥm㊯zHqyը(i":nF^fܷK(G}PvS.o zZo@_) '_. y 0bc4kG,CX@SdŅ%$ ΡP("H aiq |MGnummrNBдN* -Enr%YM/Y׬`Y҃pRERERA\AScZ[[5:69*wy'Μ9FEKSO=z t?0}Yczzuuuعs'~X,Rs=lܸ{c<k[Vy[:JO|ۿ۸+hoo~<Ӹt>_2:::F&}ClƹsԄo|xߏsٳwtwwOON4y\xxG{nر?8>Ϣ/8CZ"PVb54dсX{LKKk6oy kg{k m]sѳ٪(Yl.VKy$ii<JΏ ,.܀X,%|C3XZ'åY4e3 N`trYIsb+@Z!H@ H&]Bc}  +VsEdRu2yV+%уQ6kH;V $-ah m[JB6d4rB!( 1x0,]1ȋ/c-B1"ZRx:hooGZE*r7'ydj4ަLZ?t.T7m`9dҘAk{+r9,ͣw}/'GkĹS|3g'5SmU\%ȱhCh:Aimkvd3ڥ` Θs_~ymll ]]]^;55|>D"RV|'N/cnn?8 lٲvΝ;Ç{Aww72FGGֆH133d2iEtwwR`zz HӘD27ߌjn?0y,..cvvMMMꪫpI>}MMMxߍ~Ї>$u!ڵ .]$:;;+RC҂O~q9G,CVss{ ۭ_- kH$pvd-[7crl(tD4*hԐk`hk(?,:J^Zq! ˖hl\.|^{-/G*§?i|3#<;{0;;.$I={׹|A?[뮻ӃC]zضm|1u]xGw?ey`}/ߏd2;wɓ뮻p=?!*͡\.cЀ{mmm?F6źu~|G?Q\㳈cuY*UEg&q#CӗN%'~X SsKb(8yaG+`covnY\@GK iyb +hO!UąKSؽ}=Vr46hG\ ΒH F‰4w8K"KPF8?'N^8G}@_‰W?P0&Ә8g|#|a~$R)m!D|U"Q biDFd/3DKY8ZVVsc]:*w<9.LNL{]S6ױp\pɕ EL-~qmR%- L3WkJrRbB>l޴Y뻖ײ{;l$~&''o} 7pnb1|CCCwD"O~rL-`EZ]b+&0äaW~6byۥJ[W:06L}3,0ӯ} Fs6B:dҘ[Ʊ3#jeXXZ,y՘[\{ācq/Ĺ1\qʕ*:Z]Xm`\CR>N=8w :OMyC7n3 `횬uegi<\QK Ö!Ka  <'U>yMnNf QR/Z}eJ6[^F#[wz<4k_A[)0&:yciiݴP &4Q<,6?p:,)m]L8&(ka ΤIϣmos&cX(^28 æ{|E_^6.GJ:3J\ "ӎF"T2"/zqvXʅb7 :ktqiѓ,Q')oS`Œ$adN76ؔPs(qΌ/.9^|2mJ%MN'C4G. *@F;NM4\&*,}ÒOUNJeWOҖT6A=H;@j_Az0\>#S6NGA>vPNa ֙@&,ф%? @/,2( h6{`ۧӤa~6߰q2j}l.} Pl-nڊnBa;.8/$}$Oq%ؠE <^M7%БX* ()"jmIo*YhB*騼vdFn^o/*9P:K'XWbrw@>?cUDvP LG0f 7(B9 ʤ.B]-7^A΢+}=HRr*o BFg@LSpklL\YJ ]( [{S>[r0{M(0` na L6:6LLM.6sm]ܥYLy\`5.`69]u\zL HL@C<ԂN-c 2? 5ChRx- rһ*Q[LOF,Br׮|O9Z{J;,1VA-8E"SRk;2\Db&OP3Q=(p5t*0Zǃjtq@,s>f|mhN:S/ht82"b Ŝ%cϜ+ǢɐP ĐI:1'<5Ò}`ƸöϠJ󘚘AMRablK l~Mta> GƆWVVbw:'WBEhϣ uQlkKsO6J ۱-ѼUlya< ;l6q%}S\.P(xF%r촕f+æi+KarcZkؘ|J'/{<مU.N. |<8>poxl KًcsY^``ܧBJl5sTS,`Bt~!bzG%mNrڠӶRa#ٗJ%Գz lȥQ`DmX]!LJm`l ̰Չcۜڂ>ju#DZWJ..r"[-6 q5Ez tl;(.@汼-chjnFkkUavqf8le+,l|L踅a izp8l}j.>YlЍULxWǩ1,1eiG?;x͍826tַWѿػWغg.N Cq>8?/Ϡ7y;mDOp/!3/#_A!_]u#!$Vm(S@2*B9gBR&?< DqVƙ'0Ix/mNw~'wy3QLAK[BYd=𦂔^ ʙLH5MzY6t9urnlbjrժ*8-zuz  Kgځai3WSѾ+Ug-t]Iᵶ}% 􎒫[ duTrvX.{8hr$Am6]ݦmQ,,l Sخ|]kҘAXB._B[sVs%8|jTRjeֵJ u lԃ%v9+Q,UpXZ ]O- Nj)P*(UD_c>7<n5c/{i2IpvADx!R8ȧePc`Oa"o 3Zx0``I̮5 N)"4擟 Ȓf.$4 \eC+0ϹېH$ 6n'Zׅr$FT>H(lBu졶EGX.j5as'-ScNj`Ḩˬn +m~n-$,uW]-:ۥ 6.9Ֆ06mַR 0E%k K>MwZle;S\1Dxߢ14%y6kqH?ourjna| {ZXӃׁv{?lڈKsry6X>jL L [QC2@kSuWlD2SfejGh 7ܴl/b6VsrY(̱r~ diu􉝯`3P9)}c>H#e#s׀U lB,'2WT^r;kάԠ4 ` Ŕ9کcd]Rof.SYgRP+ Һ@"vPuL!hЮؙe뼡/7/EP̌:J˒!%iD0@fZ-Gl\^X4iK0ޮQ2[u@.GO:_Qʕ2׷hllҮoqsZk[x 9^&[1΁L t 2Z4^>| ^~**2:T*P]MH& IDAT㘜Yˇ#8GS6t]`@sR &LQ,Q(03RVL,bc_yLBOĈ}=G:l-i`hCn1 #5, -2wpUc Hr8 = L@c Q|K$v#A eKP@ԶdFѫن"M[R .KQ`  |r1/s$SpD}AA@fN<z@rlavL9ck6}]G20u7(.)0L[lS<5V+ Q*G?'Ɣ3 xת f\ zغM@L e Z6uli5dR`J&v   @A rAңE G$VBAvdb!vA($ AIf=έ?s uB GP 4A3×[BakFv4Pa 57D;a蚉6J^j 66~0`W߸wN8g-܊'Ob=(FfOjc.J"I`3Thp)4}IwfjRǘQG4|nsr*Bk?i};N6A͟}_P~`ZbW7U,2rlVl%hR[leѦMn6`ZƜ^ sO@yۏxԃCFL11Uєu<8LkďL~|G?a^~tWHeosx[neV0K.x;COSQz{r1@M +-<ПӉHO*K 0X\#:j o,x/^¹ALO`zj'3qyT\*ajrK 8}40?7዗P,1xn333RIw!TU  [Cǰg.x/8rLML\)X[chpKKJ&wݶ©@tA@z`kAY:G0-YsD2} G$%Q*PVQ*P.Q.Q*$B9<*+T*8K?я~˿\E\ЧJ ժ|x>2J%T*er9TUTUr9J%g><(JRg=yLOOW1?vb DQbG/vKZ+|Dkk'Pȝ0+J*ݞ*GRC}9ժ[z*rjU߹b\6kd[CJzI& & j /N.U;)^`~gFw<5Rټ]8gv ي^ a}x0EP\Kʌv2t5J_' (h+e0h ~&ٞt).aE@1 c G[[+q7p&LNLa%pQ*@^ &d3B{g;F/wlc뎭cظy"0?9::9GGWb]:0zׯRurX^ZAcSU X^4q}2SN^ڕ̀3RzJO<n;pD<׾5l߾ЀWJ 2}Q<b(hllĉ'94{DJϟǑ#Gn _Woo^G3<Ν;q]wazzO=>GŶmq5'@*oo{,8&''q}F\E=__`֭׿El۶ vpu _>O6b6㦦&e+T+O%q[lA۱-4lovlş2۸K0.ت^;;?| mhM;ejH(/WKK{߾pK`a)ׁ1t7ay5&g6x/#M%Q*QrLL/`~)罠tc 2Ru J(K%PTPTNC|Dُ}r*-!l"6u,`K%@ } d`=ɕl:=>[\U \~k,~,u?16%J5 jIʛNΰB NlGX]*m1prH&p2XM8}4::0>6XY^ " Iceid΁T:ՕUӘBK[ &ǧ[RX_:to~|wq0::x_W_/} s,v܉1ݻjoQT܌O}Sԧ>%Ǿw}7~#NX,ɓ/bزe 4<p,ЀݻwcϞ=~x衇pmHӸqq8??~mmm_b$r*9_Xض+kk Z\l4@k0@,nGX*\G}:t : JjStƧфمh!T .s˘Y W_38~vuu lƱqvuߺ}_ӝξAHH " qeAeħOf(̠xHkҝtzzSUSuiAiTU:ϝ$ːL$pgãhiƖ݇1kF-ˊmQj-X2oOTT˸K1W $C0fQPB{(gX*)BG -0ԹjE;W_Ap6sJ듩Qf ||}g\%+!)qX$ɜQGۨǯ 6&/t]ǻ~oǐ*H]TRS#` F$ITT HH$hhj@" ;-M8r(هT* p|.rw(Caa! hjn=QZV¢BLNNh,JY@ۤKC1f>y*maŖX`+ow܁1R)TWWcX~=v܉2]-•W^ksAUUcx׻ޅǘ1c41c 4660<9QZZd2d29s栨1XĹ瞋C!K.444 /%\}!NCLLL`ػw/?f|cáC{.f̘j,X1ܹ1"H __OZTVVsR)|ߏ. ^x!PQQCsAMM ߏ---r<:;;yf\}Gss3155˗dըGcc#ϟ㪫‚ w صkZ̙3DW\qn݊TTxh"ztӦM 2Wk0uZ[gyfkhY"kc#:JkWVp.* 9"WUQ+/9 睵U%RUg)oXL&pf߁1,Ո9-.Üzz̝}O$ֽm9P\Td2Ry52:,GOqgbpx g-p809%by2}тHFN?r 8ID& 4L⇬!6 P#oY$ PwG 7R7Nngly^k*x'BAq ?hAJ- zj #\CtQ[T*A@*0veqbʷ .xj;2 w-&fq(S̚7EŅN;t<o+!K޼NexOγ!d 赮Qܱ%e]po~RgaD{oo/nvcٲe]6Kذa5g<|,'?Wm_q]=C~J*eo_!A/1J@iDI#0/|e+4>3"C ӊ:l|#p_X0:>y,q]Nm;(-F`9VU3X Rǘ6$R} m<5Mg]6&.aAi'ٮVYD ֱ7wܘg dQ}z4t;TYқc*. 3ǥ. xGspf{Iaօ]ym]@7ʎ0pF?t#æKT+ʚ 6 8wfגF`MOC/n :n ,j3%N0Q:a$ROq9xڒ&˾hHP˘1NRJ]dKtWxN>`_ԋF:n11#2f$Noim\h<AC$c9KXrͫ(P86G] L7ɺxQDE .ֿ 0z.}\6P#>OJu6p:.;\}@DX+A̜#[85٧ JWh  %{PDe8t^A5 ^#h?UGqeAmL>fa!2(v`! 2)n^)b )R@A8tz`,@[t ̌~Ԫ?I@`P:z; +L[|F+s6M]T=jAJO׍DluD2a}&iW:d%86f]X +nq'c.6?ڮ㎹mnMgR)q}zx}9O v6yɴ:Q';|hFv3!IP^ 9uUQK41_GtAxu ?r0fZH? 9Fnh$z]`rg0'vy ̒4''P8yBa@~|p?G;u~@!O.aw8Qhi)XCX{[PNqMWaT&kFu6xe0w߾ie)(czJ!ڧrCwbԙϘ_GUAXm.n+4P@n9723K"T: Ӓ6 wD?qʛ   d 1=t}HCKkF#f kasD2|(O$ʇC0٨v`kR4Q!h]E'z Nhu;xG^ U[d3M;Eq2 Kר.6p67+Z\./LN~<@b*ȓ`/╊BOcyWIGȻ0}!y* ] /|K Գ3ќp>I? 4u^ r6VNv IDATbq-O'2_6KM9>%Nٌ4Ħc2`FуޙX `.S-y͞74JJQU]alt u CG06:3W-ǮvsZQ^^F€IosB1.=xB נ ro?a:Dv'E4z=npQS0_ǵfmO.űK(a5^qas5JC6{7 d1En-36cN1p &낛A7|_%S nd} )`gUd{(2UiG횿,ɩ\MDTڹK9rSE~`^-gI@ι7x"a 5JeI8467;@@^zTVo;ci.J{0GAA EKmuZ4!" >ZqNC<0IЗҺ{/ O3lV}oQu 3+Nq֏k|Cmt5q}ǿP\C~!L9Vmg'j`$ׇW\WR6jj^*K^Sa2e:YhjHmS2t&#k3G\lA޷ tLU+|h'W< 6UT;{'ӆV](4KimZ.~$gbll=}@ܿ'9ْXp BۂF{[  qQ[]Iצ=80daeB|[8mfb4Mr9"na˝,|eu 7lqL^=y %rOK;Tyr O^zzBN}D?-/)ij^1Gz%_*WR zQd&% r8\K*Ab flk_ /c-O@M\.0.phҙ~0yӒf1HtL&Q^^w|`E?&s:me{17i_#c*zZCҞ)Q'BtPdsS ClqWZOJ=7_s?u}DlmF_Fl4_Ɨx>Da 6M`ww?7 S.q5\ʶAe.Z&$Q^bLfj7ˆ`2sd%<5G 4X?/YJuEEz̚ӊ!a¹`,f,=j K 6='pΑv_qyd9,9.7dzm;xNN38{89"d {/xw88[V L_AwfΨCߏ"| ڃOe [o$йa=̫Q|Dt чSAp5Z,U]`p]p)^Q”|7R|a0xPIKƀ!UX"&hQLjxS0NITW,YMy߬t>z3dp"##L{t_NO6n kWf#`YWX[ 暲JTglה. l@AL\NeV 8ffIci0?*.@ 嫰1+#W.=\/7bP;4𳘘?o~?;48_q[nRt|5߾ Hk| ^s[ٯ?x['_6~ /inwC#ɼj֑U~J^? | td<6 j,?}K`T %'s(ʈF.`0@"}jԿ+mSMX MRB)3X-E &-ڠ1dI@>΁1ɉ)  foWKPXufԢ(mh3 wф%64@+Q.RM}u|߾ T )E@[oOQǥc1l5s+w8^R\a R)Rh/@*͍f̻]zXcEitvnd9ݗĵW "dߠ/Ldz0xLV"O!u'ϙHG*E!Y l| z=1:&hJfQ蘙EaaH;])lwWtaK.;L&._kp`b|{-9 'N?MhiiAwww9Fo 7I(xq(J8@? :+PRR|;QTƌj\:xu.>f4jPQZ> oQ]Y5(*L -M(H&񩏼w1B"%sa09uEc] ߅+Tવ-{0^j-:AI:j^Xg%Ӂ87F22:r1d#Jh L2+|`5A`~+diBTN>84tq) , 熁.H!cQ 4!Lb|j&FXf\&2w,QYYʲJN-605qN={%3Jﰓ$>ESSO~8|˗ /d 'QN-3[߰q6K.%3a`=޶ywNgot^K*bLKg6S_St.$ϨB!9T W͵zCM `VG95Z[DO$Oz aY\e,2s,ȓ^l۶lA]m jjjddp,ZL{& 2@ hqV,UY\NpRZۃoexh f͒fdh;v )Nl䱅ȴ@bMNxv0ɒ]"w`,6|)W &zЋ ^ xsEF |7Rpb\T?K%-B rN 9A~&PP\m? OZG@OLյz(;?)SZ]wޅɩ@v'^~oSqAg/rN租~k׮__O+Rq)snܹ7n ȰO_n%+Z>tttXu`Z2e⡇r&T?l=u~a۷/;'> ޽;Ǵcpp555XpE Hqcxh(Ul1G\ÀߔO^qeQMB|d7҅_ x,6Iu* 'Ls}l!㒗z<΢cH%{2-y^!&Eȃ9XB Vہ6,\ {wmoz!lzb.{gos߈'7>o |B0/}ݽztl%92L$+si&bdd< :;eeekqF444`jj ###x{ߋٳgnɓ'QWW~DG}~/ك+6lٳl2$ ޽~}fqM7ooP__M6ar-Yfgŋ} Dw~p [|C=r}ã>GĉUrg>VxG؈3<?֮] x \?O?dt(..ƣ>իWcttկ}MMMسgjjj~K.ų>m۶aҥֵ8v##(--K^YGv:4-ț0m&(ڣuч] cǙzlP)h $wxxK.@t 4^2y>H'Pe^OɊb>1Ɠk*#с>*,(mOɂ7q{ևk*!ӧ[\ݝSzm f+Bh?{FM*c3[T]hO*OPGNpc \^K\Ooz 8C)uGwUga`og`kx7^xEydHfQ%hG'~xϽ~cnQfDؽ{7:,<3Xj{Q__]vW^3OcXf }Y8pB*ի~zZL&Ҍ~EEE:0vo=0pfHM6..hU(4StpɰJ> g`@}M9(FS]NcF}%$DoF/KҌ1HkP͂;4m*!5AI1` VM|ZjAYc^rHAP>2hcG!LgaO7ԩSҵbH&Xp!nي?yTWWarr{o.&FrHҘ;{?mrbow_|1nFviH$+W<)Z /1o< Z$I8쳱yf`Μ9ij>bpΑNQZZ|>+W'N?t ())Ass3c(--EMM ֆ!68aZz5~_bժUHt]bo᪫B"@II VZ۷غu+R}v\xhhh9عs'b <ʕ+ 5k矏9s`ƍgd2 yW_EII fΜ^{ DQQ 'IdeQz$@/[ `uT)# Pqm#v+ά96_@ h]z3!o|s zG@'֜CƱk'N?Ue`xd 4n$Xsga.1gfBYIkʱ@'*1`~"L!|%~+@0'CV3tOaq,s\:bR"3XVlu Bk u-N:>n Π(2ʧT C8ٯ_&Οk0zj_Z}6_{}5FsK3ZkދouV+yj;ցw\KcfNbf}@fr&W7Xw'O"N@"͛i455ĉ(ZZZPYY ttt'099J x188\.3gX`"hnnF2D__FGGQSSnա000!477sE&Agg'QSS.LNN]]]hjj`j֬YH$A__ǏGcc#B}}=4:::PPPRHDMM N<2444`hhluuuN@lY%ytŏʘkF홮|tdEO'Q\R?w@)(`i6̟݀];L&pf=xc/6ᵽGqY,+Ƌpfces s[sx'=&i >y 5| ,%,պ6!~P&}}xѿ"xX&0i1B/S&l_(0R|DGxIy >>TVV X1u&BeU5 TD$F~)~f[bCwXCHR(q  EE9ك$I  cbr555CIi ,P]Nʢ?5r: 5 )Bii)-ϕhMQ@\2l>6qVQ{fWx&=μzmq퐯iH$Tg?:N_ЌَdaiC;i^ IDAT}1I\rxmXulIDW-S8vu()J#L ]=C8pۼ,~װ1+}Ik9p|r%'q[JqREEc'vvme=q8הeW2쪷a0\F|G΁"ͻ H7V> ݽCX4%ŅǂY H$(.Lcɼ&9D JP[=.Ǫ3QQZR=xZ땯 COK >>= a>\Wĵ^ބ̭\1 TH1L(&21*ˆH9y %`DX"Εt4Q|]~j09:LLL` ajj -Y"zEh&)  :L6'fOy7ARRDߩak d(-I#|BkK^&Z| D`ЄM:[bJf̈-.m:%|Q2\,jms*l<\|L2[6]tq8̋բދ#cxlkd dQckJ DgHL1bsbVY>~ =ݢXQ oLA >'Q/, @J iїy"I j3Ck&"Hht٬x[srb4_%O,ڏ@Δ' SOf ^wlǛwcthim Y:s.횂zm'&aLz K6MWfX}>q@H0d7J(ߨFť5eUi [Μ-5`A$ 4 [H: &hpBڔL9$Eus@_`r:dvnܮ&#G2l%E9h;$' t(K'S)ι8~,m655sisw)TcцZw@+zZv!eŽ:W6#;Af*)LO"ڇe?A t\-jvAeM6;L>.9fuBe:%æ7θ%#|mDɈK9]8Ylu65:Ɵ޾c 3y  ^be=DEiQs JFĂpx$b. z sFxRG0ObfxEEcVj(?0F#+CET10ǺhIɃ=Ou0vd.`@6Ag&2\>%;JOzM.h!Jx2mc1t#a1HZ^vO6$@ ~qׇv" ~GJ~^gOoh?&5sba|z7t^ !s>p>PXF!,0!Wdώ*K`2bjO(GOBJcL3|iɇ.xj8'h7zOb`{qcrrccyLLL"ztjO.ɦh=N?jA_lZkr)N]s60pHphk%}ߕ]46q?&7Lc( em~ a4.ӱ..*Lװ9OOZdKzF01HiXn x4#sɩ,W/ ,F(K-!||%["7@YNdf M(uᆍn Ip5"YE>gJ2PoQa>`TZ艙+ɷi.[ҝ4g8YDat1@; (3H9UyߗhWph*&lG8DI'N耑¡ r  d/҅i0P]S]=Dm}jj$\.ҲR|ct=-Iؼ|aD&j%n;PLCd|l].'3[2+9%<3-xX[lŇʴm4赭=Wa:D _2%y7ptmDEg'qMν&矵P{˙;u88,j@Qa ĂY xP`r*q3 DmU)Xa^krya̛Y a^k=.lAA*T* fɗL$/cχ HO!tz@AQ^-Y5ΠUH2cfp.",}M9}e;Cqm#k6.쿦_L$ Z$W*Df&D &q S@?wBGqqg4b(g6#JPS[D25֡0] QYU2TTUXhC ܮR%\ڲ_C?ٲbU,WG9jPS?>Q<?*&:햢um!L8'VtT?sc $(%l[g.CaQ1*80>1~8e] +LWUrf7bV@}5(H%K{qX *Tb`x/~ܼ U55ض2fW"J_{:1{F-jJޣZ/er19QBH/S>P!߽w;""_&w[ah%>X+Q?aKT:YBև| C*Ak2^k7'2דz8\9WXGЖ4,B@uMBCCeeWB( Ȁ8N}cgpZF2 lP)jRI!16f1lqylEd'KhBLZ+s2 y-v|1uݷ*(ుx ;e0y ȘF5}\%lQ@9dt(AޡmJg+bMe 9:}mPSSg# .E 7TR5&W,V&'FޔLk g 4?0^DŽl.򵧃Fʛ5ƺbR! hq!,fO ׶8%AR%nbASQPgKpSYڅR:zjZ XГ]Y7 x@&}vn?؍qkakzc" _A(s.$Tea(Rl 6e5ـNM\]JSɘוXW\غl1ջǩ.89 8*tuuᮻw/mo_MLoK.{l1|q' ^qM uzfr0t05t4K)A&T*. 1F~Y\ #'nN9oܧe>g|\(l3@Gp2 hP\A'4Q- hABclz MMKCi_eE~ۏj((ȨCiBXӖDSPKݤ3}e*q&(~qN'asICܾ/t иȥwf{mzq}FdI0^ښZ47 Ls\6\.6>ߦSt{T Fo~qD=OA&P%[|"2H2  tGiɾs%r.ƅ1TaīA,]1j<`>֚)t|+"_ RH*Sq8 .n 7%@iń;eN8  =՟s1tFSsE&A `p`G;ۏIw='{191#8|0<ڱw:ޅ~xƱcN !3A{[;FOblt G;p(#G1gз+?Xv6lH*aS)s< i&id2㥗^¿뿢7t6l؀&LNNbxxgd\(,,$zzzL&qA[_?9r:jjjPWWbtuu ?я~z:tmmm`COO,n|'wp#_:fΜ.ذa`Æ ;w.n6|GQQ:|S[V w[{( 1|:t7|3^y9ֿ/d{?㷿-֯_͛7x'hhh?i~ IDAT~;y|m݆b|37 ̟?DwuկlAuuuغu+2ƫX6065'j}dp`@)4(m{f7AARI{lOkqX؂! cz0n;žDZhn<,cDYq!*Jm,;|ڻpn8-б,[4l6y桹k֬s|K_O/"l9&&&ݍ^{ O>$>ᓟ$6n܈|;kpk_|Ar9r9dYpQQQo===?>?|,_\P1kkk/cccO,ZXl?|~pνưsN0K.wމ{ ֭C>ǎ;0w\p (((>l߾|s!رcغu+<.R-oy 6oތ79)zayy/@uE uccG ߽:)?ua9ض(^rg,lT&aɼds8psfas;;߽:^qU%8?@]uvcvIm5`Cw8.-]BѳŚּ .ưg^PXTCt^?H ̀stA &"HRhokGoOOt#ɀ1T*¢" LvCyEH `8ݣ/A }IMV hܼ]5NZt'YmD1SNx+--E}}=W_EAA֮]Yfaݺuꪫ$8 LMMaʕضmFGGQZZ\.>̘1FQQ*++n:q饗 ;wDee%H$f(w6n܈Q/^uk߫zWK@Ob0XG^g3` Ìbg@pY<@HH-%$!imݪZȌ/"j!/sкy#_Df[m__~B*>u^css}}}199ǏGsE tvvW~W/wy'nf1oǟoǕW^(D6EGGj?[tj<666ptuu ox~a!P(ޮxxzzZwxGGGxއz+ގ|>=xwWWt:n袋t݁ ?a,--avvbhjjBSS0??=.o| _@ww7wᡇ]*­ފ-LMM!B~H)׽u܄G> NSSv`h0v`xdon9uL$3FeV$S" OOƞgFJk+cy)!Uoh,C n FȢ}{&3hMkaDp8ru!粸jDF}y>1oiz lcp [M=҃0ò|: 0Ɲ:ٺcde&w^ ;_ _N_җ ES=޶%z<e;'>֝Moz~iw?'> \~vdt6u.*g۾u!rx"[Im(J9s6S3(䳘_ZK%d2)H /]M8$ b}G]J,lL:՟ǩ3sȤS߉ޮV,nT IZy`zxG'fί\63Պ''Xv<1Ɔ546K bym8r0/̹][:X,㍇w Lk^L 5' szI/k[[X\Y#v61E Qqɝj.zkyyنFdyߏB khH\J !Ndi,-ҜvB9œ.:80&,`)*ܡ@[V|L-+d1NYZ/%(.{ndԹ1}];De˼; 7.IלKv|7 ?n%#~ Tmg\ܮ-:(ad_>W߿܈KŽɐ0~CXy,Q`-'~b~7t77ٟ1#N 2sbA+WI_VETmdh(FYЊQslנkDH)^7'֓ NoEIsJY7'DvHvP`]^Jqsӗ|e3u <-.sc8>n]#p`=ā N$ W2Ձy/ ' d|%="n D٬ ^L:Ap!WR.IÎ *PY0c"+A`t0WQra I'2Rnk D* MC;5NӨWXKɉ a<3b+eB̥RQH ⼐eCv&`OXjȈ \Exi N/=i=I4 UH$S`@ƕTe.Y#-ON0K΍oc̽JPq%>^ ?v0*.Qz^۾Q|aG%qc@Ceٱ_k&+?&iX@L͝c%d ždԉe='m&OcR?2h࠯aAn9Xa } ,K0 { j0"ͶnM`+e9|: iB#c IE+{??uMkWe(Kl!pF$jkd\fE2C8F-N*v$)݉zn|mEtu>]ߣ.$.9q )`SIe'IcF\tBmk ,v UxTpŠ@iE{@ѓ!SǝuF7HB9_ ;ۤ%u#'idx?hC_'oZ-l46DpEOg|)xD߽х^ӠSi/,J>JY"4ڔ8N%p&ftk]֏k5Wme|^-z2^F?At!<T2؅h~8rfڥye!|iڧCt 6B/M.yLl;i+g˶Id6s7Qs"d}!ی&%T!ׇ{ζqvdžDzz? :Խn`xһԑL \x~47xAdi~AXh|7@x/s"@"DQy)w.͡];AڌydexK-C&1 qoWQ2T)FtiP&J w: 7w&̽\i#CǯT} ډ^cjb [[%w_ z`r&1 Zbue+kF\AsW^=IE@(y'R; uPizCD,}t'uWqSƞӶ8?Ftm}v1jE[3 _Z"vw1ԙ9B[s {]y!Y(6g"tŊQJ@o#xNVj#_PLUT|"çl)(z|(P:CZvع\ENC#{ؼ(JWȁZvCoGp WDAV,x]@tЬ@܅AT."% X[]{qz4**߃/ 'P֊\>!t*J[%!}qQuHYGwOMElc 8;yC;QּNO֧ #ܻ=:QpHVT EI6I8~f\ s;, tM0q$?'qxc2#IMpvcx$K@|P h(䰰L&FL-cqekXXZǥ,V6KqCGk{v}}]*Wp vuc<6*8 A \/ iYH$kkT50O Lv(9j< [%%vh+[6@RUԿ%9^QHC$8ͽxǬk`i(#kY`bF ?` >;V=!@RE:FRE.C*%fygs);^*Qi!<[<;;Pl*"bd0&NOyg4`spM70w4%Z8q%)N?> /INRNY/ pFߎ q஥(J8?7*l}}N;\gYdG%80r|㟔H|Z ??3l&4V,.X~΁NSؐ:ۚ0=֦ſA6| c!iW+Ɂ<)ނzs.Iufѧd:tc(-GQ>9\ =%GܣDl ڌdIi 0'bck[h. DKSv v\2}ıZN0qnp~a|[X,y U ^ kьsYU49{?~ xRIuI) 40N_^$x f]҉2Hf'FR /[A#3Uytx:WnqFsS" 7HtS3+ MΊd4@Yj6i'BgO':;uwu@@7}xZ6WǀBwvw(2o}P+ga}b;5#em_9-'oW[n$J~QI%Y P"tI# qKq8;05m;${z X_[Ңіėqʨ} %צx4.` @67gQ15@VԙY D.qr|L XX^~к.f潟ji(䰲RB>'~Tx# K$M ɐ3H* %B}IWٯjgAT_e\ɴy)–RnѶRmaBBk'`x%3 ,t4J@+:WHAOAS(z; bIzFOnWл-@X ɢ2N Y* ' *0z̨ 0-qk ؊KTq q(J'WKR]xؠ@G doO}P*ՅzB[vDGg̓\[\d<|IN CX*i<\hO5 6s\A6xʏy>~o}K?5&$}%Ш ?7_ P-WQ?VxTw*W%CXOP@_$Zr#>彍Eh8 GTa.a0|8Qm Z%$q¬ӻ/G~;&aPց|rȶcS+v2*\q$o IDATp')vrwQ]%I(zE$$JlZ]Rrz]^.j+HRNk)m.&WIq}]@ {T[R 6Vjdž ZxZIj߆2DV'I3|^J; X<x[kE+ܠ'V[\0|,=!?qVcڏUåx.3w\[GI Id P`y-1!`KS>WG2;JWW[$}%[Np SB0iDx%NR4&-ڏ#\$OQߣxQz;>S0 Exp.q[xxkBk $z@IXWqG9_i@IwP$ sڏ{E00bZ>0JiMtʟitW$A:"x|DPB-X(jМE!1H F޼:uqAUrdzEAHU­L Jk0 \()l꠷5. С x ̾+ 4${2Td&erE*>q.䈠5..:.yq.yr/.9s\Q8>9 }Q9䁉+Pltr㢉=.qreGԘs5ƶu? (;IPV!;J1~,'UOc'`uR`CљIүDT\% yw3)7ML<s9( o ~54 |ɫ&`[c.!]S>+cGBpe82@wGX @H2J: QҠaMLJIZ!RU TTKwHBP@;p"zA i; OL0zJhGdž) JCkiׯp$ƐEdcA6 <жkEx:uaENM*֙ |k9!öQrIև]kBPy&/Ai,gҊ'4Bl(ݔ}jh\,#V(0!~ǔKi~3rU$@oyǝ}شJ%6Kck^ Ghn~C(X$eЅD@Q ղ'W\@R/R y6#͠g'ϡtJb"%|>t&͍Myd2:u TDRE r9dsYedTUkuj5 yR)o N90yqC[ݫ79GK%g?5 9 +7]k]A |])s \'"J7j)Im^w긝dN-}r:x).7:*i(.i ~YpV1-g:ff荤Bz rHGZGOﭸJ@^ksR`T>4P~X)ZW~@/CIv^FnNj@Cq.4o"6v|1F~ ّ(-"V•6RD)<@?Bo@hh\ 0H?3Nb<Ϟ2ΌObsc'^u=s3f0?7CZ6pn^|%ajb ˋ8ILML\3x&AAzS_pZVe;*ks=#<]<a;333!?qs xGC=|3;>RJs=c||ظmWess_/?!}‚^wu NmmWؘSod2GZe YyuI"|E֙Q66ܔzV 6jRqԅ <( j7㙊m6eA=;F@C\f* \mB'a[{˳Aye{1V|$I=$>[N[n"|InCR>9e;7!"w, <(J[X^^ɓ'ַfoW|sK/SOᓟ$91?~]wG?l6a8q ???FtuuķmСCxp-n6=z\J} G?{ Q|Cӟwގ׽u7vazz7|3N<' <( hkk=z{/^#gwߍn g111Cᦛn{^Mo7 E|׾Zw]8y$N8}644O<v|>/CPc ,N\l0OM(D8?$[xO[Ѝb:J;~y)6لS>OFzt1P'"8Hm{Vf&w(T[D6J`)-L;eX ו/LŮ @˴pߩ_agS%FybdQPGje: LγJ\練49T*ku9k5ywx ckdM8ur Z ]=](϶̜bX[]id2T*<T*X\XB\F&'H-2X"S7\^ld|Lcg:a~"Q2JvR8vOz+{ɓ'Ӄgy'N>}x'bE\}8|0zs=Ї>j'xMMMhjjq5_">O"`ff_ב尺~s=WG馛ގQE`bbo}[8s PTpW#+-܂ wmmmJ7qUWG>}s>r9on6vڅkwՅ{_|1{1عs'w^ީ 2 2 p5ѣ8r!gEgg'x/ LLL >O;}݇ ۷zG೟,;68pO?4p);vLZP(ȑ#x{!FzzzlVݞ_.ӒhnS]/ ٲ|+1Orl&lAU+VL4A3 bՐԋzuڪk4(O_%'4~G#6$GS Z8\C)&-8@e%jc2({Jډ VO0N9(t@q.$bM}\DGg2 ֶVR),/ \kpx+K+)fe9 X^ZFWw4 ammm-hhl@:F[G:ؠ :Q6 +ۃ$>)[+u#՞%:8|0n|Ǒ#G088bG?Ν;/B<F߆r9 x?2<< xߏcǎass͘SO=sXDP&ߏp%`}}@>GP xGQTx'qa{ク+CCC%W]u~ac׮]&{1ɟ3ϠT* RJ`pp}}}X^zCcc#Z[[Q(ب뮻pxR?1Qp%&vڅ{ 8phnn%\Y`Ν^xgr333رcrDy3Gnb_s FpşaQ@Շɥ6-ė{sO:r,>ſ`~o\ 1P W_1FߩiY^` 6J/бTRK߄ &c"Ry@(ԏ@G: v![q _}!>dhgPRB2f kt4pC3vq}u\ZZF|^X\G.У3@AE%R[@xՅL:e/71O`Ί݂j2;|*KJ-M 8FS[\[}O4ꨖ++((mc`v %#"VVVׇ1̙3رc/3 n&\y啐R"JVY\^^8 )%ffftOR!dYcssݨd?`ԧ>V;FGGЀNb޽Z^[[SO---8{,FFF )%FGGQױw^9sطoN8똛C^Ǘ%\qsssGVݻ166={̙3(ؿ?{9tvvP( Q455hmm1+6ϣMMMΟ?\.NLOOT*wz܂9\ve(E.H)1>>v bvvz(Qs߲qSFy"[)݉<~7ߍF|Ł={ފ'x rGwG 􎿅o˫j?>9wwÏq/cO| %ߌǟ~ ?{iӳ x[7𣇞/ 7ㅗ/:ۚ}>3+UG /o~Uli` 8.dfygixۮN~}s ++LVf!א^aܲWI,!*J_z@Kkm,"[(U0YJopRGjf1էLKK_>ܕq PApJ7\QEJ`E7mP6 \XvN{n_b[H9w"Z[[#$ bQ JJi,//l$6Dahh8Q4Xu^Dm\ 4dM_oQNIKLwQ}xsGU%`0NYn-ă| ׽2|[7]~~t)ص׼x1 n[>l?^o+816#އ/G? \u"(?;{4AZCTo ?q3~]\}v@pBbyb =7ϗ88a[gsi&$lb~7t㾱,i=DmcFY#I^,(>@L{@֜Qz` @lVJ ПRӁQR&)ԅ8",znG9U-=RJC]-W$'?(;B6 ЉN8nm(,5Ǹ:ZOzkpe:I2Gk' rhj=9zjjŞ={` '' q8IEe?&N8YQ.Tf_Èk̓ٲ789v^4b]{xM>MtwETl66Klk`O?>ޮ6T55ot***׷Q‘Kv#Icum{т5˫8svքOW**{k_Tj> / Aas 9,L`ȐfaګbaO&~\i<%D8ӹgUˢqh[Bݬ`|bII] BAg#yOwYv&{-"sd~g+FL &J@ K/kD2}32wKg_suߣ9+nG \*I k#Ng$SR>tFnx%Oc>qItrOG\Bq\' '~NEOvyjnJ([CxWqH:7ہk:{[hl7]??uqÃO[[eG'^`_oeu&[ԽkZ?`O8Hǐ3"-& p)`d FaD~B}pj\pq:_@v\_+e3X?hr4e;t'c9D=sIl{_q~i*Ie܎\y?O_g-B H!UF&t*rl&J|%ԥD`s Bt{c!TdދsYodPՐN @6f +Ule3粐Rbc9L:JbJ ʕ* yd2iS H0oX<AGxy%Њ,zZ7*q=U n;Bi$#!2NиRJl$~+Xc)2r[;~.HK8Wp1q vrŞ^؅IiD.$jDLD$Yh0}mdq:P>Qiypqv%]\7rZ}n׼H Dֆ}(&Ď(^>IgmD7^.ܼW⃐@! %L& @zE y@s ;)ƂNiHd9]6Hy"Ss)%r46UGQ7)PX>E!ZUL^Tg'3fKr.+9z #G.@@/*7T}ZA:, !Rp)F R.J'u^epmqɂX[]COo7_+KFO^cnfRJqH)?CVv`;jpQ;eai9_(ϸvn^D] T=_6; F.>.zeq`#)v}E=OAI ю d\ȘkS:H%*a -L/hO!@A0S!=ozԎTˊ@ZuҨ,M!bJ IDAT{B5}dB@h- \*ɓg}_5Bi1[x95S9ENEOKj~ 6z. /=Oㅟjl6я9s-(/ee0~4-I߶ 7Eh'FHH&;!:Enap.\hSkGvr#G 7w]`}wct̉6B)IO ,;`Ώ4 x 0"/T2F P %\3 Y>ƗA%C(:KFhλdT I(FbTATq]>r4$8ue8vZE_y|$H id0zzцL&@ AJ)17{ s(40{kkX_ S1V `d610KIt7`JrIvIN~Ey] &J8'm\ ؕ>U ྻWܺp3l (/;w4@G1bzc +5T5ju{`\_VkꆾjJ DRp%8;A-hаDJ;"}@'<%%}67JIk@x4T 'i\|NdHM;֜;t]CG }-MLB1n [ B iRG@ڣӓ-ZU|ގ jTk٤dX]ەNJىqe466'14<d2`}}tKK(hh,(aF]dStPz,+Z td=ũ4GTxj PK%=k\$J$J:{8ZN>7m..J\L d:9_:O[Ofk<9rIV/MJOk[h-42:8˰|>*WАbrzO?bc]M8gi㏟ʋ0f-M *^(佇ⳙ409W /IbTf ,~~m1f]0=n=b:gAR4zQp3 ќ\CR)Eі2)qҤt \5?d`vBv]MS!#{FbS;vs :_نh +# c'L;-_ >@]8@'nz.8p] '\( qh[ 9Q 3Φqmvi4&NGwq>q-ևKm/ן+0n%IxKccxqt5ѳێTJX ,,^.ՇfMuWD6F*-ބÇV-M 8>'&f azn[i 0׎]sLP$OxHb08QO 3si )h(g5}=ݱZAc1fSjfQ2R>R71K v^ҢnCrBA0ccwp,VPy\· č-TlPۺD:kzWkqOF!å'5\04otR%Vֶͤބ]ޭB!p~,,ʕ:ڊYȎ.< Nm-x1;Ǟ=+dth^]8~b `@':~;:UBscwvZ#b6|?ۄ0a=ta\!mpU.HGsRv4oncs8Y| n0Ql hBc e )Q'ϸLZ" )%-K`I2BhS90u@ \IݨzID%`yFSiLPf0A%S<=cQJf~dqzr!7,?b;ŕ\vr9K2\|q8Z.lʋ n.]ʶEruTVܼr>q`Ɩaq|JO)g79q;nzP)4"9YyccjkF{klK+hA{k@*sщ ,ZDKSl1Qj [[eFOmh` eGyU(z}x\JMz1WWKĩxXCJ27XmjhLlX?وxy2O̓/d +!G =|i*4$ ) Ӎ`WsH SkFXdBۼd2N|0$GNQa -@NڅBmpe8y$ Uj\r]WK-/ xtdFKܵ6(Qv$+=IJTIOܼ)vF=Q.a>^j2Wֱs fWRIV!=GV|<$g/MRBI`k_]dRxy|\\/:I?W:L&CAJ%m (Eo939$t1ԼP K[kU/>F_SSs6آi2Ay%~8C4T%|f"dѸ+wU3=0/Wif Nrqc[>:$vJOx]%.!S@$o'*z .Qrnh"瓨5n4.]U}\ewzck|8ٶmy:ikغl[k5QU6N"-t`CNC뽮* ȃyz!w>Ыn==M&EaB L$+};<}`=dp #0~O?^1֏0d%)>]9hB9ڭJڦ1p Cddv=aO~@s.FYt ~[{NoW:W.aٿldegn'1v\6 Q9_fEOe8e9h>2UF%'`VoM6$)|J ݔC>dh >`׺,JaXpBxސX2Og%).L Vp\ >޾yj>愅Zf Tu,*0҆yLNL"͢ =}=!;Bt٬SصwxH{. ysblXZ\Bgwy~<:{x@vzWp\8/aS(LKNxk[4(J.N.qD?p`*Kubb=qvvsP#WjA5 "H>81y RXQ}qX2獶oӣMAҍEEʀ5I_nUTN.h؃o4ϤI|گZbpxbOP(`* .=r>u B;ŅE4#J/4`ҩW08s!R]]:3޷lE.6771~Kяp22 &&042euar|j;wqJDgm URw[ 2f ZAr+a;}w;2趓<4yr;mޮN]՗\HcW.p3Noӥ r<$m(u* XTensMq~T@#e ͖g%)сnT ӷVٸ0A ax6Jv%)!g} $3h) EQa}(΢ _AZ%'4-T Y_M(/8=:q@C=]HKEj544!_cvzZ g؀U?Q)sx3ghllкlnn^457ae7;3u/ʘCҌr͍M,.,ac}+˫L(6Cvz;F@PIiYm+JWcmvnX6&( vml-#J︠J0]KQ\:Gc ʵvlخL;6# F'Ow< ٧n_: 8@E/ 6\% $A7g9]]vx> 7Ri l* 9lKjj ݄474B(iٶ0oQZU/I_Z\B)=5diz]5 p  7#LfNZ:4L5CG J3.vԙ)ju008zsShimƮ>5$091 +Hg2Hg(mP.W> =û$'v]KoMk=Qv;U?]h}\p쌲!Jo9v/)p:Qv$U v+s;" 3I^SIuqIxa}2 ,Y,u 5-4cC .jyE Q'RL lm_vEŵu%Qugxv[OkOeqD J!GnFLД*gvܮ]$N5h-'mzt(6Р뺺P)߫,o|3f00؏b#׃(ԙ9< ,zz=Rйo78<6}qpx3ӳrDXD6:r24f挷ݼ'1V?1&v>O8.n'9[8q~p%F:O9ܠ>ݰ`]۽r*'ۦ$ʧ}m;lv80v:lZ QM-?. ;%mhDPdp{ x-Nd2,.a~[#$>o2ߓҼRJjuTTe61w6Y3ɟkcrGۮ W(>?/;\9QqɈ풱];lx5}7߶c_Id (àO|#epwؑp>0@F2&]Ɯ[z<߹/!c}e\ΘjR /=55 ^Н2S'z:Hnɳil^*Sn v204"T!9[De Z΅k23-հKT}O[v\2]i'#״. %T'ɘeuqvy+Fdh2֧B&Jd 51GN^ H~ʒi<c>X*ߥ4_ea#dH 2J[DnΫ|Yj%kYZZB*%P*xHy#A +]lۓ |L ^+ʿT-!YC*2ڼVnlbtZERW-T;v@:mb )%jR:xf!R)^ZyV+- ⬬ !PU!%t,V'@T [e,l dkZ^J>ɠjop&iN_Ρ";hH3ZNR_6T!ȧNUhim5pee###Vk[[[$ WkZy[+Ba}}Bرcj Ξ;SvaiqO>} X$NL[ZZ Y@[G+K{ ZZZ~*EH)NQ*ۋOc\ETFTBsygǍ;;U/t hii ]YTk5cyyr PqH٤lҏ:Aݝq/z۾çRj@C _y T gϞEccJ&4(Qd3"{MELNNt:q475 UR^cccwN8!8n!4lrrqqA+`|\] &:;`ᒑĞ8 ˦9?Z^ػ쎒e?'3 nB<.QcxGvdpog]Ɉ#j|/6+^RU8J[( 5LΕp4^85{B˿j ~:Μ^)o^Gv޺G040woɤ~O>$ )їd3Oo{ 7nж}ɓ'c_߸Z ǵ׾M܌w133.G\d2j0VTB>U*X^]Gi/<3hmkD㻰v^Ƶ׾<뮻NGɵQE"*oxGb}}z}CXYY__y|ww*3ߋȣ"K)F USg^:|wϱXYF>1|TD6oK_?ݍ1݌khW& N> W#74ֆ!cg7~JZ Ѥy+Fxގ"ob`]wNr9beeE?ށCauuwjT*oOc||RJ"RI>iVQ vUُِЙ´0}IvDAr~Oaf Ok+~SFxa~n; t-!W.L^?F&?_+v^sEl=sx/n]rs)X'ޭ B|j)%^|/}>mh߇j|as8R#xGoc]EryIX$eJ"R.%4!r-UM;A`;n=Gh@[1 pҤ&q8E\Kd%(%Jd")rwI.rϙ{nzyc#զQ]]%dYB_O0;;z9|eee022J]]-TUUQYYISSS]eȁ_<׮]eӦMIDATGY9t 6md2ѱ;v+j*wUUU@<8U$wʴDQb\Z*=V!%sÇߠ3g333Cmm-S@]]`M$FD; GY$Ift&۷ogl*fE/ױXZ1*cdd|1$Iu,36JHCC@ N'M V+PeZy饗ٻQ:::zXDQZZZDqD"q[eC ]KX3.ob.O$?f%L~)j&N1d!GNQTٸT\|!J[Ng۶9Y2 \`lY<ҥK;v]ryfgܾ} CCC;w,BuٌfbC=09K_oߦgFKK3.]bŊx7`]6nzo `+e?꦳b7o;·>:[q`Ʒ1kWRǔ2*Kxo XԶrY~*jjЯ^NŴ8IE^qVFI!tG2Bblذ^ϒ%KP,Y jSrdF#+.I]]]ʓH(t6=gdž.us΢LEe3eh"$g$B _yznqw[(4EQDQ`ʕ|_f|܊\$DQҠVkh423d2-ȑ#lݺ*V+,c67ݽ2悬_-[ $q)顦-[G11aMOZ :Lgnn`0'`&d"&IY}?6lӧm5۶mc͚5 ݻwlٲ4\/^djji^rݻwԄ`pp~s>cΝ;p\G?bl[>@oo/^7o[طo/ׯ_͛7|2ndzzJZYv,$Ifdd7o)_Hʕ Tf?iy;A,R],-(qY fcV6r?~I{H p-K"w\Kh-d?:9oN#|󞷘 =%߻mRtY$Ibf&anpWSc1yQ)]fdr 8**MUU&T*{FT* hǹs稬T%m!,ӰlKfG nZ6ڄ įZl{$@ i]ȅ \٪ЭQ莌 34tFF#Сh4ߏfV\{nhlldhh7nLU 6ꫯn:"Ǐl6s!y!j|AKX-u@9HP(/2A[T'ڸq#Gr#(2==͉''䗿%t{T{VWWW<"W;7o`͚5_p8oҥKٵk_Wr `PIY|9NBގnɓرN, $Ũ(rX>nZ꤭={zg*p߯]Ӭk6*AMLrUYY<<]7=­/%)N+H|[`{^s#17F?msNOtݺ3"ٞЅIUG?+`(Џ32%w:6j4 \&[$̌xZVFŋj|O0K$gTE|^ V188ќKe'wZ$t[JP9ZA%׭3|fc7 VwEp8J"h2B>4|'LD`}u,4WcE,KJS{ bDcQB9{x˟u_tvinni|>zٌᠼ\ z=NZ`PrvdYO8bb"ԄFvG0k@MM HbAej,Y1 8NjjjlaZZZÌzj~?vL&6 YillD PDQZ# /(lSd{sx6+2d%뤝(T|xa#W%uYPj.q=1U*As_y0Q"%r+*zu*tj0p\Jpu}pViD2qVk@ 9 BM$NIŝx2IKVOftOP2y&IJ_lȏ7[+U\l )I'=7Ymo8Bu0VHz?Q M>]-W~}&߉tKڙUXnHo'F'CBe g}3Q"$6hP| ?ZL]nfeor QT)DNRY۔fs(f !D=Y ^V4tQ|2}kvBV$txEQ6ȻQyDè41ox}_'1> v]e4\LIb1l6HD DQa~vFHLn ON% ի\IN!+FTX㴵388bavvP(HCY+mQQZ0H\K@ffBjԙ8 l9R~y!e-VB2 Ŵ_mw~s񘛷L^gad| ɻPPV^[,^ x],,FB-/?\afa Kq=>N^/?H ߸X^>#䐪쇍pf> BjMV1zU,b\r$p̐jX, 7fDh:eK$"0J:ŢѴ%(A JPd|bt>\eZM? -u~|9r'K/k1ulr9p+9}b\`}ry>=FVl8%Y$%i>'bUS5FL>Gy,uIp\|>Ɲ6M Vz~]82)nyvNՓe )&) ~@%(A J)Ow:huZ{o"W  E\MIENDB`liferay_6.png_documentation_1.9_applications_liferay.html000066400000000000000000000117271325274564300463170ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:liferay_6.png [LemonLDAP::NG] />

documentation:liferay_6.png

liferay_6.png

liferay_6.png

Date:
2016/07/19 12:15
Filename:
liferay_6.png
Format:
PNG
Size:
95KB
Width:
1239
Height:
574


Back to documentation:1.9:applications:liferay

liferay_7.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000002577721325274564300420530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR>{_IDATx]|=+I^(A[qN[PZ$$B\nYo_8fߙym8~SB錉IMMe" @OiDWͩiJ$}"a]jRلbUQXLA5qOH@UK!֜8n,ade&ڂhSr`8: BT\B50(:f4^m.N64RMi$d#.j4=TMP[=YN-UCNIIjR|ŗ(yd&5 )9`2Q\7 cpFcB}PŃEMPi s' bU^aQbد55/bR9BLݰ|c2rR>ht:둺 EMMI5Y.}+)@NF@ڻ`Af(f1 *i6#ID98)/q a2 T|]P- 5R\\.b>)ԥY@x 5qNXewf~ j)/IS1S@m[f-Ej @{>)(, H^$TCXF#Q{iPTFK^RbLIF\R:D$EC24RP#a \M0 I#y I|Z8%%PWyte &%% J@B|{Ƙ$%DNC9'M)z œHHUtL 20VdH$S-E!N<Յ Va|[y P,BZL g'QWF(tJsF\>31y0XH >ui2̡5Ԭ)ϟG~+W@77)ೄB23+Ԍ%#pt+Qr&&p/GKK#O(C jKp7%09oU# `NX:KA7Zx1XK#& դ(|ֺ*!Exͅ VA%Aꃴ$RwXV@K:‚vMA0}XC*Y^`m⥰" 0 %^)2NXcb :HpQdB;-ͭwՑ0FMbFd.*w?͎ !<([ +j>`0e˗www,_Bo߾QJWؙ3g,Yk0fCU raaaC(܏o]_SMv)"!4qlJ4':yאPF=(L[^Q^Hi^hQkváᎿa*A$Qh@O^ʣY@">s GY+@JDF7찱Ր#G +B(3Gpuv',m0WIq.3#\g$0'Phs΀ t\1h5JaE$;*XFRj6 {QȗZ_,.r=JҴ & ufx +?`"Ba+!pDmmd.42D1*)#6%?)+iӦ^r%,,fͯ>|~:e#;%I>'9~Lfg"Fe01&̟ t#O?L+cbFhģ_@dNbJt59[Ygçpk8a#ΕEQKpG,,ќVRS ozL # T:xhyaUQ%%BpUX;<%hESR K"TX|%]UZ*+ZV3W緡ݼ8z/p=Yih.\ r9~T%H8pʳ. ?QH }Cn#6B.h&ظ$V3TYP17YbĜQ jI-u:ZeP"dr7K 0F4j[ |"UsC^X^]RJi PXY+0N9da`\sEf U1njJ5r`Zn5sCW`L"E jF2J,ҘPUO! O=7>:tZ܌k)'zx`{ǯ_G^8w~à9sw%)*;&&M6ԩPbu^eZh1yʁM~ t= q$e'/5rƴOk֬޿_]s(?~x@/Yݻw6!,B\a@F_ݻ-viӧ gϩSK߽{'Now<4`;ztQ/Wظ1lEX ap7۷/Zs΍7!Y %Wɓܸqc)o~~~ -NLԇܑuB|P`E#F.KPPGa vV?fi8kK7!qF0BK6e<)yq$7RݜA`A"@#9Qa֓,>5=Aš^IP~q^Eh3a1LBl %mNsga)a.ݢч^-{ъ*+=*# |bXBA ZF>&uQj(-) A@YApꥻ;nBy"^ZhA\\[+ LK,tqqvus.YfH#SQܹg͙xтB 7!W\*QbO29$9UgqmuӍ /_Gz˕k'*"Iԟyˁ61Tի7o]|̯`hl5mڴ3gy#OM: Ep P]^z](%C6R^=~yywޠ>,4))IC*W2\JجСC_1bE @.^=z,t۶;w>}[ՠ}ׯ۰a=<ڵkн̏vٹGϞ[nٷo/BYOǍm,$Įr%+% tSn%W;iӦ>ciii+V'm;Fɸz 20aqWiW*rqai{Z2aq4F\^RL}-0'`L#iA>K}(V2BjbU$87C3 kty"C {2b1l`|8g>b%1 gBqYl$|ShdxC^0biث3?Jű #KērÍVV`D2vi$PA-HCR67 BA >;#/u}VWXdT^VڷoߺqÇ=*[7o{-y>|VRt7H> /W?"FsWWΝl<ۯ-G={e`.ɴp:먷\$(}7 x[υ$!  RҴ(J|wE]%ws}s$% J@%+F JpcHh+d4dOZ44Ra #_e{+Z¢%PW||$Si ou G A$M\ "QfW;i.;ȸyk@.E!aF,{@YNȖyq`AË/p-w¥7dQ 4ore0ֆ<w*o>\(C!d]F03g.+$58%υJj%uQ rd6ijkJ"a"9{$P?x7&߳Sv'G+Ah&qM3捆AM2q0fu//Ovԟ8v_FJ,4%]OǦ{uBQ_pr|DE>%ɩ38FX DOZZ ¬mct*U-[lذABB+W/_9t,"##߽{Jy zK׮ڷ:o1 u,1ccbV\Y&j*FճGϱne]t йsgo߾#wG~zuĈFa5DJeͫP )mڠ Pj DuB֔"Q,#5A_\%^`Fxn.ئ%_AIj XޘbIT^DudnH!ҥ8CTA :IcVpfh0HYɫT INP- .$Bfŏ)nSy+T")$=)!,lPq^ ^BBPc[}"FAoz]{X?xI_-WrΜawoU|ELTwoثi铉 Gi{P/5(P2_GjRTo]F5F{Dy|mq,F%^^^ʵJLH)ӫWN;VZvmSFMR.].Zl ?oܶ}ۚ5k%OϟrǨw}~[4hK#Gݧo%W*aOKM _^jаa|^|I >Fs(vMw-Zlv۲%xȐ-7m۱ ⸱0 <ԛWv!/Y$00-[wµ #mYx .o+} R V}:Z1 v^ zU 94BxU(Pg>B30H]5F?D'[ndĤX>*ne2bfDb(Q3amr)L!a+dJ OTZ8P!X&&G̈ g9OQ7(DV_0͇~VїbF* F4OVʈIq/IA%WPhѦß7~\"B3VoЫ Le2IɛAjm;v>{HrRң\e oݤ'ǜ :.gȍb ]|TE.xsǹ)xHrJ<4&:`0lb1.]:8ܵn.*V=7`࠵k4m֔ؔ8F%; mڴ ]۠ +T3jl!:Pe1,X`|@j];wYxG3M 4d4;vt-C^JKKEs[DRzn+V7wn^=A̲e=(Hr 9A=R& br¨1y.m%c?c%)(%P;i2297#iDyI1k ZDVL(\ zL8:.CU# 1n#lˇ$+ (nq1))XFRXA.MP L ̊Ƣ!P'-Hs>h vKp ,D ȧ\HEk1Zo^"b `@XEG!& egov2-C[vdk;O<(O|yZCŔycT&*S˲xҞ (ræ-D2*AtucJ)61=nتMId:Ҥ1qG~–(g:99eye`@Ppvڃ?q|w cP惱 ^1}a@0j .Hm SRPX򫯾:t\eʔ_תU: 2\.\ ҼrC ' u d S(+$7hZ%q c%wBJ;( ijS|.T N@b6<~3>>I zUTℑ*0 -XFEg";aNK s]NBwFuYvaSaM]^5B؟цG- ;RƒGTSRPOP ǒLSRU//oyelBJ5Ʌ܌&@K䜞/)gCjFEtL̜s5M>.i<JQP kPFnKؑ96k*e0h<@]$Yt$(92?7{ײϤfJ.;Y )zI¬TI.)NDZ .^atVF FF(#%&]q-'jl4 :hl^:*<,-_*VFӲk}/F!<([)*òP%C"CZEsc)R`S7Lwmr`͓?`J7:V)Rh==O<+f!RU~0#HD߄ }?22 xs8EO}RH"-4 H$H>48TbV\d =b= Ǡ .%j_ iV) 5Laͬ AGvÆ`C`dt``o,h% #s#0e%Q1?3$&͘|Q7K#nR9|J'OAيL9P^N*Uԉgƽeܴ_N' F6s9%%뫯jTV-55}F5Suh٢Z8 :}212 Q; y^27{4#{OzrZ)9c>0VQx,u$tiA- ܢ6' k&1X*u-Ѣ(IA)n|S jjWllDX+?@lQ~ n b2_퉀3CxP"SR=L/^&''i͍~UY_JRbVuvvvwstF!---9)8K@9AмCK 0 dSY ^sQMdH (TKfF )i eSR{Gj1B5 ⓡn5^I&*G3$ >A]L=(>1a~*b+}8Ya7n Pڿb/ ]x+,$,S?yֆf4t!`IЯr.^Aa1D@]; ~e^0q/ rNNapCF*Xj#EKBxJs0/JBs*qJ F /` ]X#Edd MZ\_Y]Rf6Z^kk:iMrR! ȔCeد@$ 2A=lњK `k Hf(^ƅQ.*m&4"ckl ( J C+Bs;>Y I)x %TU QV+AI5'_OSַݢ90T <eq5si&0l~VG.pډSB R% V%+*RlJV &a0B#8Lq4 ODm 1Hǣ]Z$*fAjkwAQ.mCh!R3vz2"3gcbe+!:6c01y9MU;`6pjjG@!<([L*óP雮 VL iZ8Af]@?0=$<#I3ЇWѣ0') %Q ([We6{\U֐0ܞED8PcNXTC BxdN [~h,SIa;0’;.-f@͂>#JGཁ">&]lC("RMNR҂i$C 1qk,cG2hiŊXnXhްl+ºaUV(D +1hl4z &(l4qqDo,-L:(0Ea%8d@ Kw;c߅>A ͎o&n9MPfm:#4^c< =5*be*BQu+Th2#6lI,TU-XIqkPIXM$b3- sP٪V1 ă1y^HV/l4Y& ţ+"Yuv-gTQtXRy KTR%U婲t[ Yӷ+M樈]eV9m<'r/^/S %  % :J@@@@@@@@@@@@uXӧOmV// ǏMUn,4$$dʔ)d?|Yh*Unܸ2W@hhRL ;lٲ8ѣGA]޿oS\#p# `q FSLw77C>H߻g21UTVH yc·4,JS)˼6[˜UKWPJ68b=l3aƊ2'O҂,~zuD3}\\ryO=/X Сjժ,5S<?AG@@@@@@@(ˋ/)RrsɆ`^(eE] gymVkܾ]\962&&X3?D>Y8ТE ~ܲ#*Ν;sfϙ3gnnݺ=u%K)SF}%g0>(J.}|_ xUѢEfD.+0 a+N;a4ӵkWV`}7]H3(QMe4W^}С:uN2͍5rۢ_a ͟Yn]1T۵ -ZtYj4vX'''V` مcǎ5oޜa@8@ a8~ҥnܸn0TZu9@|jjyΝ? ҥka+[!Va˟>}ЪU+q(XhKMbktaǎ ܹs1cF&4l.S5jqOOO_ve!guOH8v(Ϛ9f"Ĵi'NTvo߮߰pq(X^o(7i큅4nFufΚ>1?sϞ 0c̵kRA|v_L ֪]+lETIr0r͔5ㅀ )))i۷niZWג%JH` oͱ8q5kr £GƏnݺmܰ+WdS :SNЇ5rd(Uy)@؝cƴiFenO^npر\Hj6q/Jd^DK.5h@MBLg6ܼys3gkb-ϼypsTgP| {2:=<=ALds05z'%J/HLA)3;T\ `GP,Ɍ~~^,P]7mڔӲAԋx?0?zJ}9r _͑!u߮^:uuFՂ*&N9bD5ݓ֭ƃ"#"pDoi_~,d貿\eYuNKp{-/)))sұjZZTs૯x9 1OY%{;S@\]دW._)_rm"3*r͔e3A6` `]8˗yQPr2x.w-Z4u4aEڵm7wܱcΓ?y2, 6}*SZރӧE?0%KO‹, r>'Olܤ 3::ys6eT'ggȭ\ ƃN2e`|ִȤI?~b-Z^(unݺ-];{GY>kfeK5nXY%{;dG=ǎ3YB[n _xr'DfTR3)f0۷n˖-ViG&ݻ*URHڿ_˗w{We={P?҃ .f>}2s7wnfܺuvU,Uݺu횔)^ha˖旝4h`ccnjFc7kxR5,.SO:uٲq?{ ln1H3`w_"bԫW8/_(X9`M`Ylܰ! g=z?>kj*gQ|wHNNnРAU3H6z+_nI/zY&O I+Wk  FfʚB@@@@@@@ma21)))%,aʕ+.TݿmUB. t-P_a@,(prr1s&cvݧ7ce$$$7O- ~f]E=\e?DgC(& &h4..zNSwsB[/'"DM7O6ۤ֩[wA3;+kCtI|NvB@@@@@@@y*YRY@@9G<J{ > (Cp d % :J@@@@@@@@@@@@u , @X(AցPaYB X0hLOOW#Z-MZ+ 8qqqh qI>'$| RH///'c0sKYrn##t:;j/SydAX(Ð?.xϊ.Ƅ2e˂@RR@^%J}VUK+F$) d|xC!uoo<OitZsݫt+X&gW$f4d29ǩ3Ave2Q%JpsuX?(E Tq;wˤJ^W0_9>>>O|JIVEAtWW\sQ\];z"r#K, e2S޽qoD5D^ '~۰!y:zhZ>|E\^7$F}a ][Vl1!)NΙ3&L߿׳WY^]w6Uܹ_KS+Th4ӧvޥ0D5OUឱR컷vj) 9VBM&fϏy풫:nfrtI) 9~uu< iw 7FvjD5LHb:O5@Accqիgϙ]z.[>yd@ϿTt=~䞽{vmҸq]==_زPr=-},q7F7$oS||R,\XTռTó9DFD8''O@x 2&SJJjZz:cbڠ H҂(/,Ԙ/kGSvhF@ DD߸{{zxΝR*yrKG2Bŧ~I6 )|r ѵΙkww|hp: 󱽱迥_!mv:.>!惲 XujՋ~W`o>nmngWgCݷsk֬^ܲ_P&Gߵwwb%[rFi5}is1q$MRF:g޼ͩat:''gZ6kBY֣G>~*`˓'Or]]]UgOggx PY@A7i=W٫+V0簰R38²qb%E+U=|{.ҫOtww\br@jKIIO<315%5=`LM71&*k^h@cҌ.AF>3NLw2f_B=tH[T$LURe=t"7%$w}Łw5QVC=Y/W6,`d6~?1]qOHNaDŽ!h$*i; ]pö<-|ŪvN&%O5jɉkjj꯿+ P犉Jsz9͓!GJ% bŮmb?^]w_o=xOKr%dY!u.5jϫ™Z\^v={-]z 4,3ϟ7ujתX]K4C uΛ%">d~Ŭ9I.3s\c8D[W-H:z|W'M0MJOcccsA|'x[p>1Me=?|ܛaKD;tI ~vt;t|o\]ܜW@A߼uy^P&ƴ=sK֧\zqk\(# rnB4Kwmǎn3f=pְW,2|cǺO~`˖#F['WK,y1N .ZL6"HLΝ=7֫7klvSEa\:urqcǎ0p`U@XgL[VeEۅlh:*;Rv4@ͳC{ay)6== CRRR%K>>>/PXt:^2[t+T{ׯ?{ש"U*N62"sϞ>[ͯҎg;wn5it+ MI8[.ש~Û7o4k]2t+jGJ㕒FIѲo?W-]y짧o? ~KVB%P%K;z \T,ϟ7o۶ Mz d2qUAYK-wz~5u@rr-?z~WaRzKJJj3f4h/P`喲0ҥKذܸ[ʕN 2Sh8,тr]8;w^گϏZL+Wz{)F1IUr2fڂoիWWع6D BWPY^⪍1B,}7U)$'''[OKYwCmy "qg/qr6ٕ*3?T7*X/PSlǏݽ{ifr;zSfͳg`q7HKK7-[ڜžjuFK21oZY;|]MÎQ>u?4햚 srFZ d˘Tc>{e2b_sGw^f~ptLBٽɲO|tN -=džN״S(\,g/uf&L{…Qo۠aqcǽk3h'/xmC)'J[l8Z?nq᧟VC4BA6;CԖ\WHvА[^^^'N|oe1ѷ=xAm:uʥK3P\s+hN;vzK*`obO^CdB$%%_N+U4pv>[qXkliz˖-#FW\RRe@xΝ#_6$L@,1SPgVE`1ڕ''cU~m]M rtuj29s|˗/ɣ:Ǖ/_>Ռ`2^tFtxO?nڴ1~@ C7r.OII9j4._e|xK/U̙PP󚙉~Ve*E &8F{ry6l0{IQs'\h難.m2 ?ǜ,=zЗ-[6ftQ֭P& GN/^>|vŢz ~uzۢ8Y+U_ ?wEt&*>NzeJ/?aѢ`!CքM̝7ə;o^V-gթ[]"E?|( E ֫޳{ sef...SLXʊȕ7o.]߹5m=FHY^=vc?S33l *CcLʕ[~ԭ[HDq]vۿ_ rtuj2 yIſgϞD"O*x9؇m*`0ܾݫwv6mlM,۔{N>M߿+?ue4j0hpB?!Ε'&&Fɜwf029{t*-0ɇsxNz^.9G09֟1ʀ>UѹSim,5juh@At`G ?>iLPCU& o>óq?͓병=o.eM2@˶kWP; jL4֚`H1PF !G77]zr5".Sf`0T@\@TTF3gN5!- ʆC\f:)` YR6mtEzyz{vF:$Cǎ ,ap^ {<6f>~ڵÆ;z%?.VZubŊSvyo&ձ⒐k)XPJ=Һu+W.Ϟ3GRLMʦ>S3ɩE9gH"22rqw`% 7>JWWMr,466_x= :DGǜǥ(]BRZh~n08kcUqʘg &|Ћ1ȒOE WA' _Ofԍ [|}6!NOW'^MBDtFwRBeDNQWe%&j=|:`:YxVhä:9i47W{h+7m?2dǥi-ľo0_ go|H8L(~;v<J믿j׮ ·n*YT1_<Xb^{Xrr2۷o$VÊ4\ACro PN/eC[A[/<3&"gΜ0ɳgz ,_lR8y2m0L˭[NG‡$ErY+RXq_05 JHLP<{ZcEӡkz0C3So_4x+V0qc;^jk>Aׅž!_=% v ."ژjH3LΖaS3ʍL,c ү^ΝAoP?_ڛ1۵oWzu#WݺIIIT}x|<3=sncvc>wb\ꭩpŐ9e=f@m#K, ϟKeʖ=yD+ĉe˖SSNlifȧNK*U9rif>>>ї.^l)ʗ3?Ͽc+*["##(VTɐFe/om\vti (' t/n|o׶7ߝ?mjpQ>wʩ m 5ςbV3kL{Ngp5娥;~w3i]S뼱$ih~aCA(qGUZZZGZ[ڭuV[qAElƐEOݻ\{.+3 _b2}lmd ~9}|5a䝟>aӡӋci}HQwWuh:j̳cn>o4&Byܺ#?I{X9q L %e9 }=-j>u^yRrY V$.uefߺ9nԼHt :vˣZ$7kղ%۷̯ر;wMk={Η_hՐs;kl:k׶ ii_Йm9xI޽&C{ޣ?#=L*26ӗ3YȩƟ6<">.yEÆ/FyYMZ֖ıN4륭ɓ&/_W'Mo~&GMkz|C;2X 2S1iիWߟ^}BWvb 7tVcJj6/,44o 'N/rJJk]=Ç̩SN` }-_N޴-mv;vZD"K5s&bL{$-$ͦ;P{XJ]J^.cv1QyRQ=y%g)&lnCc};UcNXJJKq 2W,_F"͟-44jf ҤQ݀*1E^$Vq sk8K<5c͹߭=JSf?;+y;K!X|<,G}[&)({zdlf# FH˖ o~=m\ ׯv c}n$ڍlP6>a̢c\]]ͺ&9X333IgJNr!E劕 YE}KNJ%B)oO^0?ыTwFy˔_>yxk#ZqzqԩZ$&NDj;~R3YjjЪE\`w=TԋZs"_-1iL:kԨy 5QGN[7[dee~Fw|y}^Zz{>d:{Դv7#?<&6x/[ Er]JwC nCeĒ]x֓8aDSbhۗ'~lMRo޸ABmڐGʘcڿ /굫BB:88ovO6:f{r>F19zSWzvֹwq_h9wm_L QCRHR!dP2 c9re*j_MEnݺϜY:C*GJx%X`m;Y I_E}O߫R o,;S<rWQ9K-%kMݰ{GGի~$AHZ:t@'PH :y~ZGBЩoUHPZz5vmvΣNMꗰ*5d K .jK?N=5엜,e )ھ9^NRm7aHp1vAB۶ΞcY}֔!7\-{c?^8w"y@I{{~}{u&efeef>lDǐ0Td2^OFr\edjf.owi1U᧒BqaiC[g8jzY)Z^%<ܦiU/]x3&|Ktzf1B oޜ<ʻPnBC4b?@ՄL]ImXCg]$"!&EVpuXt hɰ6zd:k[O&ѣL&kynznuC^K)r90 A"L&W݊7US)ތ-;D.gggX,fllDlM)1Nl1@UbFʠAUGaIFODyj>=FEKĜ<ݑTKB*L^.ɷcJ> P*9ONN-4&9۱{'Z XH$&7'/7::߿\ΗLs*(Y }WpdRIfFwPl7 IvvG[Syrȶh| *o(@9|771ÃlkT%B>s\g@XP`&V|-ϯsvTXBH0ieEBK%V9#*8Do!4DPv@A eG; mТM*[Ε6L lZ!\L"юB18[F" Eg(,--m„ q1@`\U899ů[|>&Q(@drM?K5j|ǿX$}duPǎt̘jժmySffǺvfh+: }{20  o޼I77;;+Z޵wroԽ_(@ F1BdܦMr<& cmHY7$bPtJΪ_\!3DE1n_(P(A(X B/Uܔe0wrΑP(BZZZLLLZjX,xA...z7ԉBZX&ݵt+RiYL˗32ڷ@N\&+2,DEE^vѩE˖r\ks(TAe|򜖑}%!$GTSlW-;_`Preg l Tݾ];GbOzfyz!Ν?gogw[-k{?l(Mz~ʊK }M?"&bHemj8hU"p(rD·VekV|L0uV(l@SIXXleͣ\`s9,f2v|Vֳ[iZ.#3ۿǹ K^.ggߌlbԖNW7^FsXKVeb4DE{;C94W;BjMJDBNܩLN E)EbrQ\'H%2B!+^\.!1Hq8?ӳ:ť[N/[^į#: KdZ,g{>۶wĤn =*JPLF!aDnS}Me3U0H-3K34܉kメ-?[C'JT ܾbi_nKL!_h8tٛfIw!k`2]Vj./+ug|\.WdA|[{Uȏ?__g7m|w>$sѥ#`faCϘ9Yl߶My 2jS#rǓixH4,QZdpX \QÚD dyҔl [2L:RCNs/?i%az9CԖwm#O&=}L'j5:q|]\]x_6mUP ( )Ղ57|+|GW}HSvVWU@*%۴Bj|OCCB DGSgΚ赤殝::; RSSO>|T[ P1̷je;!ƈܲe*'%RNKC䡵THIȶW}›4$ ,umZ̛5s2͚4&+V\NC{vVAժ}3g"ѣ[WHޤW"_/]b ә)))t~mܲ|I|keM8rPk988ڻϗ^!]:g@Jj@#C9jv"B$!\j:Y=EmLS&]z5S>Ϟϟհ!Iy&qgOeHZw/_һ~~;-zך0̜NBy~ 5ΛCrOzu$1v'[:qꌃGNqc<<njdKj-@`D*ñ4;j?OS>-jl|lX<6ӆ=OJTu/ erll_%V(/?rGt%??L,]u:O\Y}$VYOi~>^|I陙Y;-'3ik ;裹귿(Rvhluhז.siaA!Q(Yl۪B^>@c8 3Ǫ\( >.d1r9eg ^4 @&SYjև_x@A-\vY(% TuvN7׮5Glܨ־ݻO礽pm^{fO\.'aRRSWېa[Lu~t΃[iIk-@dSF; ܲ-^2ߙm6W$ʒ ";kOV^D+߼V˺~ ӎĚjWv󖑒vv{Ms蚳sr}9bTUFo24i3˾[ث{W5yu6z+1,̑[T0AY X="SYW2)岕!(y,JL8R׮Qz6n`o<ߡ#I <(55 <{N{#"DoӚRM{)Jc[ML?pLC룦\+kn[^[Ntb#223 mh?^r֥5IAatBL8XZ֎BZYqW*(oGްYbih{!7'GZV!ʟӿxP9U[~=ؐPXfpi2NifT Tr%: >eISڵiӚUd^n*ĠzB1gpbکJ2\K9m${4IzrԥH+>77_ءSjnGjNN];u]z#O ~p+gNˬߴ}5u'Ex3 ?uzħlX^`Ǟ?$6ZAgdkuǔWq󖮝:uha z |T FF/gϖ"<9[2Sdo},X兠ذO\>ys~SIGJ$;o7~H]/_Ѭ˹IɇH1/ՙ_dF Db1*rkwjjo,#o{: _$lߵ؉S ƏtInMin]< \t{Px'3T7 잖IY| ` E9ʇB$i.QV/^ 8f/ilNTryeRSҬIT[n45;x$.ZEzw:}8 0SNݵ<$h$+3} 3N$ӧvayrY)eبSfmmmCBB=uŨP{ٳ{7jIN^^I U  jQܩrOhFmgI)Y&E\}cHVݝrfϢ}=+.?Oz4w}(]jEfAؗ(SVO999ڵ{|ٲӧN>zZs/MSNdkƌ^| _e֒sm_ߧYmfdҶ\6d3E R^\ڒ헞8mԹQ@Jfޢ.}l;kXZ~oݟҡ u*IV({ldy$ϝMt晓/\DJd+ni괓:Yv\e>kU/IB%}4)5nܘNG:}oUWO0a]7o\dod,^ebbpplCK/&[_ѭ[$'N(I*\Qveߖ}[,y<7a^#I.~y{$ݾ{(tŎO8㧓d H"!( 5cHx2kxKrv_z3 P@=[3bΈ7VOf1?[uD,-÷^J~+G(|\j@ Һ.֖Uw/x⳸g>"YEGEŔ;tˬ8BK$rERım۵_rn;ӵj׹q7sΜMדzZ.*229)IaIIw"Y)E,ܰv~Mm;HA)o^mJׅ؜3ϘaggGIJwܩS[[jojj* kۿ,fTmW4 m*F.牳Lw .nB1$˙?dL}EO r򕛐Q+[ _meF_=u,jF,eL*WH;Q ETo=Q*)*&eCaAlڡoXTӬ(P? wc'$b #~ U0_g}{5 9"kةsC% y2#GJ۷ ?8Ỉrqt6WX~QNݳ0v{okmI~,bIeiUs%Ir?KfDy,ѹ^5^%!6 洔z}]OZL>~0]݇&W(d2~[8c;yzQϮ=LuJzY[p, C DePwӤqerV[ѼA(}FzĬL&u[c~\gҌ2,ήU6scEf͘}O/q&~5 pzCN+VٸnX?g4 oU1E mڵWw܅D'{|`+Xl}3~M.S~jFҩIE6:z5oO,Ht̙;t(F;*:vtԶR_3Ǥ9\&Ir|PW3)Sﲣ-_64-t7eP\ͧ$lP|@$sV!GŞ~!W(1:6ݎI~W$l$:MUg6}VO{;_u4'9٫ l]sw"V\ݰtp,}`rlmmIШ>`ꐀGyb Z޳yס'rkЬaɃNc;27)B-ߟ.4&&VZLt=}CONBP? `Z[_=drݤȳSo7O6nOJ8{C^x.0eP^LÌkn۱?BH3 зOns6a0vGS̓kyߏjpd;l ,5 $cz7\Ɣuǻ7 R^8tD*oRs$smVL.<_#toDN,uFQi|ﰀchk]H/Ujڭ.[իيD"HHHά7}Q7nP\? TRx6Fg{栶C5!Y7 [ѭW^rHs0Ԇ:䡕٭i y_Yhkř=VIҶ%jqj@!(ftmCia՚nTR\*1MėR]A\}Q36k6eT#[թSj:Z5jtcΝ}KuG]9;99eddē'OHa˵:ܘlű*b/sV$Kz!gLO&'ed,tvv>wKD//?} zgC~\i"ox2,]L,KL&@@yBci*A؎<X#0(8^y(sNƷzūM[6 |_#MڻK@K+(T&Xdef=?aK%҇wU³/g/2 )}A).&8A3G.R"HMI$hllmrsr/ԥNHTڹgcKR6[yU. hLқ##.F&4kٌ]4l ]:M!a!cͼ}IVaҊ4 e谮\\IdBI|"JLH^3(efĄDZY[9:'L&wn ttrv:sD݌"$ nc<%iH$ګKRbҕ 'BP(wpʇVWhT/TasҤȐDФZ$kIĖ/%QhjJZ\.{nt1ݜėIC _/xjbY$j-2JPޮ@XohOg \99]]ԅe2Yo^I6aX)tus"1QnfVqRyuB͹.-l! ܳMvN0 '9 -P vyj(r/ztÖJ>/rARc?qSo)Ty  FAZ8RSR==ɳE@GG;ڪ}+[ܜKg/uEwCaI/ߺv<nXH@?ė_!Cjt*#.\MMNuuw o݌̄ WGP >qKٽP<5Ҹ.qqsMMNSFi${dzx%&$U2C2㧎Nx$~/Yd2MK>u9v8$ %1$='S&={K,U?$!h&cmu Dw?.aQ(R鸸9G߾[^mL/Kg/53Mllܺ_ϸA3==2?kߥ$+i7DJxHGlgX|D݌VKOK޻ EHX!#9g pj-RG=x;lIw`!5kT YȾ$ m\L&ϟ߸rs]tRQ֢^~^#?Axߣwu5B8EBj㦍WLUąkyiA~l\ҙ+BLB^jҼI +zZRBT*8 7oT* Px'o^p2yLWwlu7;CHbȈA$ qDБ{Ƀ PiaMBϝ_9-ۅU?;}@vjNy7-dSLbwN_/ywRReRnhn};q_Q$o5{ժ[^k`;i-9m,C~yJwnɳwk֪IA=J\Z d˻ ƺ{uܴzWuՑ< -jRE*Z9A_-Ngef(M#>a/݆Cf&|8E$8axX~n?Bw*w_n1.݄0JypzV 5B(uT,Rp T .xk}_5n@%/xΝz;D`iREBRfv5oJG{;o6Lf(8NGy7# f'xlocݤ^-7yliV5D`iYI 9~񺟷ZUFVrUR>`.ȭ^ZcgfeO:g]v?,rXIt@)A_hEbD?77w@1OЋ~ԧ_b},+2(ʀىW" -kN7]CBFZaҸqO\lYqզ{Ϟty^K>S+kשCYR y޻{ת˟?{8Yv+F%P)*0"0t!͢|H B/ϝ=3n(:񣱣Fm{ԛh:y֌?|TwdF/>|ӑl߽ip*)ޫDbD w{ #vőZЋA٫^n*! 6lΟckVԌB[ma'8{ =]fuy͆qԩџ| Q)@ {#PWNh-*;X^*I{C+D_hYқO \.Wwm[y)hN2D5rlBѲuŚJƍQy@@;8PpڿG0lK@Zx<|o߸qOC'$=&~>idg.O);+[`hVT=QhR:4322̭*7ׅr٪`PE$^G e0̑[CN[~݂~\rjřj׹q7sΜMדzUiV3dkԼ}g굝vp8%M/ʀR]o^mJ w80DŘq=|[sHL/џU;o#J5IEᑜިAΝ'F}䩓& ka/[̪/ݽT І(Ya˗.9sT^nn|YN$b>:}F-Ջ~9999$g>"hϛlm:vB [I`A_( Ccr1"pus[l{"1O -v֝<4y{P(P\Z , @Q ׅ R`\ [YIPE6W3G2&4;[{iJyLM‡ݱңn@En»# x8;gdkUoaP6$Q.<*MDo{ImEvDQ"ۚrSYs=`D .1uUSvV6ILH& _ĄDfge9 l8ԫb<ܮ]ިp$>qy>/B|ESbH{mF6*ѣ$+ ;֯_"7i߾EUI>X@Ub+Ỻ$ A#Wn`TA)c<)U .IBVwEH)\wI*]zى6&q"qrbrIE?SNM.u$ܽm]UsX{L #[(YD"r#iɾ!l6՛p\C4#@w^ܿ*(TPh|qLGUu-;[…O?ٳ*>|8] ::yOnݺzN\vmԨQW^5;w=Noٲ% $=t7n7o1bĽ{ ֢rرt߿?hР#Gtv"hsP)pYζ>"}+SBNkŢ+: ͓*SVRt( `L}QVy8; \P_Iܸrypz3+3>iӘ]Q7ZwhUgpg.<\ssroD$9*wO$o?gO{xyiU$&Qw*3gdddӳk׮#cǎlۣH#1ʕ+IhGBMu둶mֿoQhΝI`I_FEE_f͚mݺB_Nor֭F=yȪ;OKRiHLjFmmQ]lQ/YVߝ3HpTm nQvʫFIf(P63O9(WP^J^t&%P1*JYyɥHoF ߠKWSR웵j.ְiOZY[nU٩qx<F@zUh$z隫kM)Hz`7#@Ry$ ݻwn*ˋj666{+g(((3o߾M?u֭ͭ[0h DFRsy&.sI?~%5Ё+ A-[FP:mB>]N?S4z:cgeX41K\ǺɊ=ŜN*K:ʕnxTy}$a+& )k*Gg"ІU4sȩfkݡVT]zu $|R[Nm,;zEE`XG4=1S$bbL11FcKQ^@@P)"A޹z^s"ghLw  UI_M-meee9;+5Վ] |̐iwKϾKKDDDg:Q&{yy9K=6~faai]PMՉZ$ͧXc}έa޲Tb Vj[})%ɦ[+Im*u({)q׬Yzj{뭷H=I#FHHHXhCk͍W{{9sPZjٲeK,kfΜw^3/Hz#}R,X@H¬ "eKKzPl^z * U.1PŚg.`kߊI,ћXڬw)I>ScHLՃ=FDA),%YS{~E^P[uyP1T 1c]3w\*~ׯ_|yzzoC;˧z)K.D|ɕ+WH8$@/8o޼?{‰'$EDvZ]\m6877wС~a\\\cnl. xF;1U\zo#]^ F5|}&HOՄ뮶\3WȻڣ32\ e\ }>yL_l|.W?j\Hq e'U4z?~ЫjJpnOL03I lARV'0xSz'DV}Oت P ɺڧJ*͇+Nb2yʇaSAH1+Ǎ "i] 5+o8$`3Y2n QSPNH: cfqqO&~A)EMqY!c\]⢞{jjsZ3a.q~?S5 b3屵V7IZ}4*O0^fPPM6X Y0e3 =#b3g풽U0'OP0*@bk`VM9޿^fݞZroa1f#;I5zΡ%,TQ[ymY2nLTPk2废P 0H=B{Llym7%NI@b5(Q^g)#_DG޶duR/U[rfVȊ2vֈfe ,`)7D62\LD2w2In2D֕(j 154kI&0ݝ ,tf+-)=WA3͐<3D# -,)B1OΠ8IT~T(IkU^"U6I|>Z̐$G 5I""Ce)B"(9fVI4a\Ҭ UʴT$!UaBTWL5ɆZE'wL;5HĘ UZ\ I/G>E a}X_hƆgZ[ZGG0n{T~#c#9vV=*OT+H%˥ .\Bj;3ަ.9!/KީAaA%E%fM>B]m RoPNkfni*tNQQXc_*ɝbUfսgkW9H+áXֆI*0I,~$(1USO6O0c3jU3Ԭ&$;B6$`\oQNsD5j YHeI֥fhZ)'?)$ nDYlN1t]g:$Oh8A>HVpFE~6VgϤR)Tښs 3=g ˅tͺp1tDeiq))GDSkDYʩ$Xv +y#7BVH$.~)\\TAPKeC).trqv@2[՟}94PXpy<+@GHڐVCR}(#nT0ڦL%3rkN|ef,ICjԍ{KRkm*Uo>U5R=(kOTʫ@mJL5J@]OjQ>gaQaL&ۃ$IFe({לW p9gN%M6#Qs7Ost gs-!0$f;:;^J@ U$%1nm@LvVB=Pf*|ϥ8 b_\6E:$LkjjV0,0LSτRgVW!pԻ}]*RsrUU.U䌛g*ߕ`XemϟjCdK:V~dJZt`jf7e 8\aswHL(pH%\nL'9ZD][wSe*wgLTbͿsRt(rA}m]MuZT[{ۆj' t({HaGha rJs&zuT&'I+W;ʲ*99X|ڄ :[DP|Qml2 "> FA2G%Uԝ{1D[ZZH]iI) IJ$w.-‘؞Ibm:<û{H|>_{iDw/_XJ┝mgo'skXk!tx c&ƣ@gW7;^:ލ[LyT}f(TQ9SL&̄/WRX2yz+LOU24lȳ^A~ 9y:BIB3r0 혝 !@`.>H_ ;wt%vjq/mWbmwBj\.yW Ë럃.k4ohhldXhLdmߡ#--ho7exWg'BQzةUuG{ʺK$_z7)TmŲ%3/d]> q˶0ollUMmĤ+Jcwd4b}n sN\PPH=}udOR=3 Z|*_QtJ Kxʄq~r&nsOCcӸ1qw@μ+)?&9g֌~Q#.DbGñ>"48;7O,x<mmln0~C|&EqvӾoqo 3sy%Up {qѨ oǨǿg\Z{Ҿqթ/]TP5duom.4 q~z;zӞچQ#6|lM\PPΘNO.Drvr$)œ:;ݨ"5$ҜNJ 435V!?nJMpkim=} _#L]'?@MM OVUא3$sR:nj"޸!*q1nDF:~+x{yG~Qqv⌌$lgVρںioq?oJ}0<̅Uaϯ͕+>k{?\JG?/RBSrnࠊ&Kg!@c\PX,1tN0-n⹌k]SP=0`;ޞT ٦m;Ha)'.:/?R(CRgGH|9͘LfU55E{[2*K$s~̩.*V|@n_wrVTviKycFqx]vqrt(UۯYS ?:qvgLoq?oJ03suM] q=I$ZpB t[}X{+SUʥƇdU,S}`Z/Yg_| G j񣷥s+-"W;փm.Tz.*b=TXsFg/Fo_TtMg3r er/, Cl_RV9m@>gW# ºX )5_rwM}T!9T*c6W 9r?AND})R7e8:RYW_ߊ\`zNέ\_L^S'M8vYm:t䔉㩀y5t s /Q!_TzV0ho﻾ЙЇu=zClEԮ+^f-OD3wQ-I\;!+9c)TpO jW In9S\#qd_ 9T[ojhS{\h'>u~ʗ ?~|Ud?,=ϓ8z1*U}6靑'Ip8NNAB(;Zl~o4T2I"G9c&vr1Q 96ԗ%ᓺSC5[--ꍩxI3f2]/khlze`gw줦F ÞRT\@j:;]/:^IuyT ިQYHCiu^+P=݄'d2ՙ1mߎSX \]v{&o,+\gSSsӋF8PH"%uE\פ)WzI>?W~0ݸ_[Lqŕoi!dO-o$߽P^R2 c#Rh Пu7R(tDг#źy\-9s*i1i}NEĪ舰gCB} oT76L?oE}O'&$58望I'O{PcSsldP}[bQYbH.=ܿhAW׺we=2-LILI%_= $M jwM#刐`ĝӠ--Yi`taCY#澹PO'|L¨lkLN62L W:ͫg+ ̅vB_4^K \8E:;]}6=hbz*mRU2g\NU5je8#wpٹe׈t_#c#9vVIeuej|^Px$FʀtjR,'NV889չ\N%}zxy_UT չ m:' *)*Q(c'537klhL<8mִAE?cO})ڿԹ6'NY2BH<ߓ2!oP{ٗ!^?nv>#sGO&0(U2X]1#c\Mkee ="8)5mVމ2Cufʂ{<InN%̘gcmUUSCEԢ>|Ìi K-Yi`tf.t~/mxnꣃRZD&y(`av+>k0޹$18症u6p5G0ċ%'үJ l0!qgM]GǛfo_o[xG-J;+_O?oqa@AnƜelor! #KϞI%1͹$#-YYήc'IOˠɺp1tDeiq))GDjDEP;]ֹ m715t)RYiCʯ \{9׾ Lm]RQ?*nz!s=t*v'5$ߴY$~Ơ!R.N +*ػgG#\k=Wu>2=+菝Sq=8No﻾ЙɶħPMdU GG~y/a"FDn$3:m1|wMNdx+!?oyW?)cù\Ǚ|Ϳ&|y Jʩrd2c#I ]N :rWw3& H$.u6HaZRU&yo)l6; ؿr_\ 'yHvni;&QunB<\J~~:3s_[Nq'/VkجHҨ.-9*x`a3Nsp)ڏU)/qmk& :) [jm-G Я 5tW^d)~hQ*ʿ\P_[WS]KWR' L7[DВiֈm lB644UWU;8rS]48;y]gFЮ59F,߫7H<6kf|򪘰a?0cX66/]g SN ]]|C{I/-)O%eMh'l;{>x"ܾ̅ޑ\iOz6M^-*,;RaTI7F}ׅ-"b-3gҕ.EEC CW[YY^-Z_4iD~F= A_Mh3ܿrT'ã BW566(\>}R=r#RRD"q@ *ÂOOr2vlF?Y yL&_Mh3?ahG:wv0 j=/xgcNiSa\2SN/?<>o $ض=/g`$vRՠs#ٿkklpGIB}9E PyO&^)JNH=#H53r 6u!(+Ncby|JZNVH$ ݹe׈226aImmF*+ϟPW[ƒ}ϠijYZRt*dXYYE;8W`E ryQ~Q@H1\Ic'-RHQUR.nj>͘sL?;^X.$1ryd3$p9deP)015OnxtP%ӒҦ'_;oN$7RC_JEQaQaL&ۃ$I+EW̘LQ5A.̩I&24:}/ɟ$Ry:ttr^[;:yKߙ Pׅra0-P[.K :+jD"jP5ɺL2@FZ_.U:g̘h%>'N`ckӳ_p]蝈b9:ْHTJ&y*~1]zRT..^^=F|OLV\Xp,q0@ :P88ytj[pHmMU۪ 70 Smփ$~V/dJֺ_)s_W /7V̙:.?ϣk7))uNbrƄ?}^ 3__dG"fO{=6R5۟m)~ޮo,ߛ4F]ݗ HzBqF.t gx(zm墖+M-MH~c_-{>zvLT n Eo}o_3GRj)5>]Gރ<;Rٗ_hޟ1z Gk!%+0'y;U'm̱<>owڟԓȀ/yW>1C w&Kf;C%k~jQm}#)u7:t7(N5PE&WF;U7fan Hr[?-03IX,ps KMGחBrzpdyG.T*p=/a.ɮ04xHlʇfDŽ #Yjur@ymUyW!3D_Drj8{m+O<^g6%!nL؏fOxնy}oC2|~O-aARﭾ:ex.DsnzmG}mI$O ^oujE'ey JʩBAO7YL sS{|_= 5)E=ՕOOuXk?v|򘰗oA@!C&566d&Í, eaaffw<;zأ.=rՍDЖK%RSS'ggayu N:TUTe^Ȫnfnf-,,: CBB[FDr-,-[8 6 5|L:Vמ9Ғ|uȰ![[EbD! ^-kǎE{d-.)+ 5)t "ɓ-̣FF^Ȧ2huej|^Pxb8tJaX.PMu\.hin!nnʖ"qd[x윬TRT2mԾ7Լd3&pE\P)4:T +áig[=J2sqQIFZF ͺp1tDeiq))GDS"(~ִS#53==LNsr8?Ww;S699:}} .meiBIfF9;o"0P\?{U 1-)McQg-\BZx<<2f\.4ܹ+G?(߱nׯXYZ.Ƞ0QDaI_[[ҒuI%W#98HgRO ]]|C{HcE.;{/&T*%RFÝTׅ2N:x:;+V JʧN335>cV1 }fٔ/9C\=.0&.-ȫ+"0~~ && iH ."&d^ͥjI&iyયo\pw\ s.f54~3IXD3.E-J#Æςk$V5~.vO`uϛy<9ZEgf:~KʫMnjf}p[l[\.JeRbGX:D̺U__ EJIHA+F$(c`!ɧS.eѕ;3Ps6v=uTЫ̼[a牷TOW/ȹFkgۏ/U\V{BЁͅݽɋ* ͧ |\8Ue.;vLks;Av[Q"EY[{ vS͟<}b#6%2sB&dWQg^ )Gd'rEܗ;Ƃ F.mlҩ1ԉ IEt( ԝ׽@1/i|؀a>ζV/W PUAATrnnKUEr =lʺGbđ Jߩv>x*s~|ԩ)o6km~sv )zݘ:Vhwf-SSӋYffIqƬ,//w@'ԓZl} ~ؾ768X\:Y纆,},sN\D¹+# @oj46miiiaiq@/odg_qrt$kub?yDoT'U6&~l0yQC4 )5g/oذYNJJZbE^^ç~l2#[8x`llիVY~=I$j>t2eɓ.]O9r𩧞"ɓ\.wԨQisܹ~4ظqcb,--y;vns=hI41!SL }뮻wرpBzY,g5˕ϝ+@H-MH )իQ~Ν|>6I9s۷l>z &?~8pDOw݁ EP 믿vZ'NPQSc\k:mڴM6of>ܝ R(<ة^CS~Ю޼yFϾΝ^ MIoC 0H~@_B `;;w/1rAA^ԙS;*u; ,zDLuO` `p\c5./;ۓH%܋M&*+d+E ){DhljdwW=B;{ۊ.n.ޖNb8tJaX.K*KKJN%3 ++˰pG{}Օ^ ){z)ϜJ"iTԹ!ml6'BU' a}Xo; *)*6k*Y+t2iFs`@eu5˅Rd@2Dh5>!VťN*IZ szZRU=JsqQIFZҳ]NA@熴cƹ 7\)$QQ6JORu>c$=?S&~շU55Rch9!EדHd2Hi#\`ؖvj"p;B${G /[YY^-Z_4iDRIiFB tVEĄ[gϤtp)/2lȥ& oT^ZV\XBҩjՂ~oS^@ӉnW(XaQW߰e$#5R4ʜ+dz鼹$>d蘨>_~|zՒҏWV-n]]7߾G>"y~ݷ_8_ߊ$N=M6o.R(@C HƳް!))7+,,cP#RҒRHyX#RRD"q@?gpXp32s 5mHu](Ͷ%ۢ*F&NNI<;&FՅD'N!88,9~}zDH?UVW_/:; 2$;7gdrR0'#BozZ8̏ny)/ɺKOnM3By ;,J>)3ރ:P\P:Ou\8vvڏYN^T/ϛ8uB(//Թ!j#kM23U厝4* 10~0MTΘ2@:lYS&Kت-Zvdw姟psqzO<*SPjt~BijR6jT2~TW?$HEhP`楜|4fԊeK‚w}CYngkK|T,BȈ1QW]{r._nfMgW._![Njvʭ^1-=P*&z R(3:C z?[}SssVf RswwW[kmY:kϯY]UMM:p8y˄7 2O%%UuGcێ%!/|R-k5|WI.쪲~c'KF-?lJ_ j`ǐBXn.o, ˅ @P;-5#ZpcPB Ù3_~1#; )^x6oMHI5X`C 8oaa~qc5PjQaOuS]@l=Vֿ )-ōzYyU"'8lŚcg{h2 ))` -Zq2԰X,Tvbc|s{0H! 0 dEDbeL&p3a4ω[ؔ]T\$+x\38`C `vnEX,k[k~FT*KmlDJrZaypS,_mĻ^t(Ŋl8{fυ C, q6^ )tҦL&+R?{G}"I 76776bV9΢V7NmyUr rtzSfaՉ#܆g֌vA )t"ǔ~>Y4"qrBr>vL,ϣ*ϜJ HOKrliIiҩdrieehohhhpqu#RYX^fmmM޵@ږVL.n_(1Gُy\Ae\RP)H\.//  Ш86v؂+5*Tfg9:4&=-nI"hxtP%ӒҦߧ;]DRb}}}HHhsK*br^%nPYdUKe2L3=ý%FT^TV s^{RC_JEi,-^d2==3*K˧Μf .PNBs s'W'7O<tB.om%z;-ZCړȺ ])EP0 XJ%q5Okj)W0M+ {{fY6%*[_̢^6{Av'}しVL׮{n>#ÆfW ޯ?)tΧ2aZ9?K"1%CTekk+U)0ą11gN%%K`N}۠PښV"|Y] Lf-oh˕ }qiR/Hl"8h*Gnѧq[{N.;p*5öSI"(Ёb9:9R)R|_"x﷟Xb[_TOW/ȹF{t!V9ɯ.v3(m'ٚGd'r˗Ø[ Kf7:5mL,7^\N=0w4)D*jw]SPN'pİ\\I76ԗ:]3':k:P hZS'("O;;WS+j9XuOp;3˦0o烧2GJ=9-xZ UC+Y=`כ$Ed's He>9vԐݡvpNd̞x0'.Ϙ5),=d?g?ye/i)tPm!@BmyjMG`"ph 9Tɫ w 6BOjY`[{cʥ5/},sN\D¹+w薁CVwO>L,7ݧHd0!~K*Y8'm9d1lxLSɣbY#;Ԯyhu!O;I9v쯣T_JHHAv[Q"EY[=r:I݃m-~Zsn?6! m~^U3et 5oomixJ?/^Zdx$ՋkL^]C';woבOިE5J}TY{w$PF.mlҩ1ԉ =g )zSuEofǏ;]r֭/P(2e{ps:~o K~g|~?99Gz{{7xdƍ##|6*(Jncr9Wo\ UBЃB`@4I5?pKKKUUESG&}嗫V277_f III"(q'xn,I͌3}suiC$ǚ^̲535H:$khl30!@/OTTTzz:I䓥KR /_uր;v 2߹sBXp!xfffT˸dz~ZvvvR}RR> 3bHKBoNПϛ7oݤ7J8en/^f[ZZZXZw+vt@EhwbLDZZZ[ۤ $_e).ccd!dFW QhmgjnBү_&G: %$!'D 5Qhmb`ajnthz }[G1|xa~~K3 /|m&zzDZJ&I4-QU=xVc :f5toԺF{(ӴaTWd ]TQ).YY$u=Ksx5B23ut$?~$t43QVqqIt-/H*!4ś誯~lCW& Xy@'jT D ) ޼~Ӻ!h! 4)+~Q$=50?/_UM& uP O Onݞ OZP;<&~M9>0ӻt3voy9<ש}g8NRJuG.G|rz /~Fb$g~MtnMJxZRԾtҭ1w'3NqծTN҈J.>\&նQjDݨ$%QUMN=B%{?}n=>RSDhp_m0{ߝ؄/-c -&޳!J!uuOI^}fnV3pw֬Q~/ܑҫ7tZ@E+,V_R| ҷ[&V ~ :!BJ@(׻~冫XTYϪ^>mlHծ1@D°<;5$9$޾RU@ǁ~Fpءfd,Cr9$Ѳyջ BGa,iġI׮ّC""}VV}EZc_'҂kSq<[ ʗ^|`(7gXH:FùPn,vOV5FHCVBS]ϧ/@$23yvծ$ki/]CDPẀ;]B (P>B (9|NX,m]mW=9#YBk:q߈صCM@QBk+eekEO&ۃcū7(lhdPCy~Rc%/Jڪg:dtdyv<#iזHl&Eb.=:WPGovɋ7 T {(Q"S֤>Dcv+oS@Z_ eٺ..t$'W'eimq?FƆohhj[WӈN<9w܇*))lr˖-YYYǏ?r)n:SSS\e߿u2@j >ѣGC y߿O떫wשoټ _xR ƒy y<$ _CZ0g ٿ'iYuݽݯ^bӭ|jСQQQiiiڵ4hеkƍ|e&i^^ީSӦMnE[1bĦMHHigg7i$y3&22rϞ=֭[ԕ;W˭WMctjlAGOʋ4 5][5Tdrz=^g8~p]5|XwXO7vvq%]ڭ_u$P%76m444ͭm۶֭0`?σ$퇯t=Nl惗!iଁ :8Uy³&f2=_$jϯWESS3++pܹsW\9rÇϞ=jժGYZZZ(2еk8p_~t(j˩[&ڙ9}=}|yczv'_ʼJ]:"}4i/k4$gvh'oǀ2A>Σa3 JJM,lvq=?\*:©EWnܼz]V1#n$iOj |]Zly̙WUUL Zu˗'Mdgg;`ggg//6mڄ/?Nf|2cǎ*mM.|`eff8t{6&zj3X5X!͌tǿ]_1X_+g$-m-Hiir* ZKR6[*,Jwީ@۴iӈ#۵k$8}cvҥ?Kr9suiǎK_~eܹk׮[nw}'~wTG2)@OG}kmɩK:jhcLrfYU-}nt~]~Zwhhmr&4Ogؐ&KwZB ܹsҙ{޽["S/yҌ-($ckkIlRPWTYC Q΀o:f7|R־j .ܮoa4fP;3_-.>b&Hr7dFHWWShar cxV^<9o΄;( V缱s:r$tNMEb4ibO|;+e-y'E]5 鄑֢;( }:r! 2 0J(P>BW7@ggDP%t5\%OQ,J(%pϓ X5$EX:_.+:+m֧ãO[._}CKK bѿ #Z>._˟+>e8x[]b bBJkjj(3θ-vKvZY>ݱ@"(ݏ[,c?-tEg>痶a ɢQb; ۣb{[.Kѣ:Wϗ\iXX5nS)! *qI1c1KXLY edQ #`1JľKlTq.e|@ ~Grl)Vt*o~JJJlvM_r}sԥp,&7jUV:}^BZ˼>T6enYϩE3%̍ūWn6hםw걑Qܹnicء<'gHҧ է4c+9'KW 4#+0a@y enݴզeqqɃѷ[Uz Ozf;+5+rATNA-DLfzf>f-?tB nH~bhlS >DIdHW$>[k:7.•]Rnߺspvk989ѩoAA+7$XGgy4^}:n}“_3^'U"pD%vIǮ^tImݹ$l-^$ʺM=g/_&M_*hW A"LrBZ܎5ѷ;mnaު]'Hڵ+D׵҉;Qo^}歒Rd3Rֵpp$ĿEj;#wWK[U+nIqvuMt-MVK0ḓyGEDWjJ Շc]3gOQhRr ccII l\ѷ4Iq=BTŒiΤ sKh(4??_ːrZVd.r~ב;6{'A `0999퐰km[!ɬOBDgA)"!a͆t-O8v4Ϸkh/{&&&8~ۑM4sz[[[K3E߿Xr>V%*ɪx<$ oz A5&h()Kp I,zZ8(E%JJJ$X0FdIIIfzG+S&&y$s>LO%ę,_LTVQ&TTUIx\9-hV\ˣ3EHPD9z $4|;w&F.KO[4y3] 8qG 8y8^a Ϝ6?GH1SSӣ'Nh9G"4tFZuڀ%%O7kS owsERJ.:zzrӳ7M=;LuT]gE9i H4"ы%w$%KO]>0ӻI5VZ؁U"aX& }H质j2p8:O:z:jjs8dnXUzVVM59{?}Iǟ4C#ׯ^4'RHܥG2[?qy$~x$UdA}/Ё,% },Z~$2v 2ѭ >![ʀyg}?|Dȕk$+ϝˋtYY}CR-!D\:$v_ ?m#I׷[&V ~ :APY vktñTK3}:gPdžsF9IJ'T0ߨ/_}Z_HWeHE7l+`ր/Xw# ,@Hଁ"QhmSϪޣmlH`#:8Uy³<ő=ёYc;: `121kԞ.YCS#v cצ榙YZϟ=4]sBTTUL eIr,~4i|EA8|!G +'9M&e-4(#!M=S:򵆦fFFLMM!HYA#7j!+))sWO@|HBЊ}S~x|3=*)qR27d%$sޘݿzW9g'Ű^;ӃyDI.iϻսǯnz4% 6N$ǵ5C;y;D P2͚7>{,y425r-SfׯܸyD-8|^&fwЎdދG 7Oכa7 ՛oeMqi//߈jՊdvlz'|Cei3f )r9KbEܚ"$l$el;þڌ`ef`iVD>b+KQ%E,4|<jYWd0 2$rhA$Utl-) q!7zV/@i#glO:eѐ˸\?i!A "EEE.^8~(sw2w7N7FMM}̙W._&=;ŗ֖lg͚z$jiߗe+gN2qv_-q҄ml_~#aYpHxnen5EӺtɩK:jh#8PN2/f5=ݭSTn_zH"mV^.UOB PBsꭤWI$smRX,%.0)Y5^ n`}V\8vv} 1?פ%6BVٸ M H@^zˁKϳجC:P.Bqr!7ȕe)xe"/s9n9[@&vr$[9?:K릢|']5ibObsF@WcU шt/7@"vV˦2s! $qby1d*3Ul .OD 5 P@OAȱTvC`XY#(|@%B UOuYEb.9W2ɼWl㖳UA UBWʮɻW4'S0)?UoK"Bj^) yܩH*PO帡lefWΉЪk(*_Z/P|5=B! (`!̾CTH*Qhm" 8I8j#DuAZJ[w232yΎֶV$ ZuihEG^zbQZNΆF5=v n] wpr057IMGwj-*IBPg7g6/#GtѹF _#DuA?IlI_Jp=3 $06364W Qh]q\VFfzZ(3//咄(Za,6o]=.|a!WMZZ5iLeraa!#12 >%]ڭ_ RB1cDfO7CQ&Q470)BB7Oכa7 2^ acJţEa7Iǣf _1DďֳG贝)OE7! n^*HO_ϵKMj 5KA7q|ӤI?}ś6/ڤ* @v}BnKIHɋ>ymMm >Mk5stevSQ 2*3{iܠ^D̓o˾3$Yck Qhu!tSѳɟĬu֩?ol 46yѲNk0Hwd-脖jnʻl=-9'8J(a9v nٸ"hʚ*5=ti>ymG9Θ߈Kt^PPmޒC+ ړ><ŇѵkFN4>-\ˤTwG7 O.4HN/$.]xcin8ud/ |DՍ/MPXT\PPHIU·B@hC^gElA|O 8KN-a:ٝڹD^(aLK5lBPjE/_ed権(tV3'/OG[(T9D Q(T5ji9:(^%6.Բ0QV7DP߻4/3κanT4DխuW*o-<9 nbztfy \_sS®._y^&(oP+T,ĉzB[;$~~wYSgoTH/ j)DBZ'y^\\|otV, xzƜ]VqߡLo{M:Ȓn-iܜsnG!6o_;+8Bx>@u@Z蓹%B$~$.a~ɘcvպ>ȣcSG&COލ;u R$@* B|*(beb-JwԑNs- 8oܟ/ݿw?ֈGCiifft ϓY'潻|Wς@*..&MQ,A)@'$&;68+(eff/b455|f̞NM'NN$QhTTx;WWWe r!Zu.*,U`υjikSp% 7?NE'N?<}PK\zv C;x:՗;tFPB3l6y)Hݬ,A%ގDɿv^xQWZ^?t`Sn_V4Y|2?nр<2F BZ^tfc+~]1{ INSf%yb&ȘH-6G|BN7AU&&giߜ)ٷHJ"F%`5^l1Ag? *#FzZ7egelz9jcRB P몦g=yF$VTVq@-6tFPM .T)jaa2߆TWUSVQ(tT-eW*xf@E 7jlđ:V7#EFF%b%<3(qVV֩)sV? u5=r榦YZXԫW:,Vjj*)/1Qhu2eJM^~(**S\THEQ@A Q(TDY?{7lبˆBRRR&L?{Bk#?z-\ //OWWL.\(` 9U@El \TTܰaaþ۹sGA~֭[ƎfeBZwzz Eikk9:̂Wn&I646rË27tF%zJWKcd#9W.lb',W>P3gGmkckmcesPFFܹZBL:96I|q=K${Z4z9ӅUTwn'TǮ.ѿ*8JMW@͍$~[XXdeeڵ_ΈB#c7o445͌-L[6wufX$'*2Z[)qdbQNw'D<:z۴C:6NNްb˷6 `1tFPN.!7f˥ˤe[ӄ71oÇ刟~:vm@W⒒ovHˤ@Bb*i\U;np{#=-Hݡ[^uj5H\ma;w#[jZuڐC(6c,;w7wOf+++k ZlN-ITIOKݽݯ^b|98??tQ-4WV-i%%QqfF:Ԭ_~s;PcC9$#2l+`րe_m0{ߝ؄/-Cnn8FIK3}9(qvA~$[&Wo?]2׉;; 1+c_:'${/xh0o{ςXE+ ##Ţ٬i~I*" ettu޿{AOfgILg avG2O $ Dv( t4GoMto*6...!& r9~m&.Ɖx~=Ñs iXOGAVIټ%ͦ[&zwV)-KV_-xHbvh'oǀA*wҙe˯(qpr}Ns7guuw6kޔuMڛ[ytifFgϟ=oשD$HeޓdY#t'S ҐVfoJҢ6%$  /Q]|<jY*5$tBkcSK.橨4kԠ.-¯GIҞ>t&Qo^6)YK[ˣtʫɩK:jhcXəИ>]CcCn./]_;<&޾@3rxC2pgUeӷT'D%7ܽDO{̾CȯP^n~υcn׷03c}[m{伭:w`,yþ$=qH5Tx\}R{n?2~H2ٯgYr/-|DPqs贱O)ch"P(7dΞ='##… Ǜowv9cfffB"Ho/\ѣG/]hܸO?-RB²-)0 55pYY  y9~ڧun{Y_A W\\b:vzIYQ(|"(P\%%%l6GdZS#Þ?T855*áJjzP.l.Ŧ(.Lg,I͸T. Of:8 G^󋋋 m4rb1έnCU .֬۾h)(ykVVz ur"#,nCFF+Ҷ{ujf;dSSc7WWJ̀icv-cnoo&)Sl{ +.#C Pgdl,q~nZz*a(NƹPURRl`hGo|<x#? \» oSaSSm U>e|yA}CFv (ܺO|γgϦ^/pԥrꣂ۴_F)*,,,(( 7k&ٺ)Ǘ/:|^$(5-@_O˥)/(PWScs8ҽ@ Pd&߾>(Ƞm[%ɥRۍŢOY5aFxۗQ9 ()7l8s+nGFX>K-*S[۸iX`ܥxB[WOII'mTQn<@)*(`o=ׯե6MJvtTTT^u .- Gh3xQ|/ C bOЏ)))d웒rnvر&NQŸEhհ0Oϖ. /I92n|Հl\Eoc^^:|S/**)*cJ՚6-?''|Z^h~;+9)]LǪU+W:99}PN'Ԝg͘ieeU oO>').*rZOnPΪ-D+Wl߱#&&|OiS|>zCX}N0Aօ^r"}ubty˖-kܨYΪxѪ?WݹsGCCcJ]]J蕚6y z`$ANvy'*JUU{izz5_>_]/JWA}}݆UTX^&߫qLu4t ]]]lttqizdRccc3bɀiT3=ziӖt)d޽7nz튀7n QcOJU7b9ⓢ+/sS%P'Qf5bEʵs+𗰇8_?Mrlim]mⒻ1w➽}11"z]۷$/QqVKSSI[|^SlvANNvJ ?;[+a8))l-$RE7~~O<(f]튍AA~RL2@j֬iU/)}5#=}9s .g^FOjr@vĉ:52dI% ڵsȐn+ξ={gΜcO'IzGӫW/ChlǧUˍ5_K"I(&gU^Y**:$% .u߈'(YgD'L|Ų6mГ[n5s6iy֜͝UCC#&&fǎ}|J+>ϱ'dDe*sݸs{9j]:/ha]R={Mes%&x0wނmgn^,Jo%bͺ<_|~mɒ%))3ѣj%uu(Uz#{ ?,VS=wȑC~Kw:u$8 $&M_viΜ<dRa)%1e..t t<'7|t-XPMMMzT+Q"{d@GW^.;8x^|QZ:#Gڹs=YPPlΞ%N:Ϝ9,Dw1Ee]9/;$V 'NVؾ=99ֶO?-nذ?W[YY曮OK'yG ėK22ظ)H|2[HQ _z P<9>?~7G۷$?%f߈Z6u RRzݡQ#f~jj ][,&f$59 .=JI.-ZD^o{555/_Fv)fϙ#=*ҹs3|pRVxcS]==3ѓ֭K~|Q.\@>|HI,c2QM)kR268Y""·mNVZt鮿^VV._rW.݇<["c~}Ƣ咹qT Axi&tھ}ӧc?TEoY||zF(f|!ZdY7oݬߠyb{ϼy󴴴[t)ĶA}#cC޿onn֭w p8<Ʒ:7n4k֌x/<O4Wtl=|;"kO7_9&DerS/T*yȲDt2~Me@_y{ݐH.B >.:8:nxݲm[g͞auۯYfIV,R]fࠁ Hd+> |27P$ 8gKNDx;/ׁqqv6aD7w7&Ǐ߹c D)2I>MN6Ǔ='?$AD"I3Gsr۴xA];%MGC%Qh^J OO//)344HIy葫nB |fiiE;t,.).O8JP/y$)eo(Ç >HXL(RSM.X@g41}4]]ab1&M(QyRNTG!?&M4`sKjT9dQGRȅ u~QUU%gѳΜ7(h3P3f3z?_ z/c)ya\Ng&ov˖rɓ}A]tw$]В\2Tt7oN (dGVSU)s*ykdRp=~DA&O2)_';]E2gqlՒC*x1'}R$/*eΝdtVVV;;;ɏ7?!߶mx?k_$Z,X0wn!͛{ WOOO>|Dpׯ__bS|?^CO)W% ?PMiiQ]R "@+kd1(*SP@BOfEJ:ݱ"oe:I$ǗhLƔ)Py۷('5OKJNvؑ;sMqq=gg!k]~j*'//75[8K+kQJ8nl6nTYdtTԟkV gF0>յK׵kfg:׮^?0$زyMdO@JJņdL贙YjJ*##qT o׏^'xbspp8w\Noߚ:u&fT$/k%3/-ȩ˸z78YTUȧuqq]|ENNݘ_?/7{K/!s554egHXcZ d{-[P#9;y#8XڣU:T-V>WBC{t\@8)ke嫨L#}u'հ0==}kQf5_#1z&idw|E˖- KuA'U*'y06܈=apv^ևC)۠𿈤 lG˹9e)\1?ԇ> n@M[8slb8GQׅP»رc۷m-'UϷ20pElCkd+> |쥕myy+lق﵈Q&92gÇ "ɏ=2ں%VȑGHH' /QѣHIFJY Y=GA*uXaR5(Nd ^EYg碠^0M>\ 4dqY[IUgΟ~z*,L>PFx&0ä5%}Rb_vcG0JJJTV5q"խ Pt?@Em&B#-ȑp\{=*Ŷoqܢ9,{]ݛFF^rrrZnM54'IEdRVI %OKWWg;L|h!_'S,ea"W"a``}rZo \ԴYf*eNQzzz"BpRwYϒSq2n8q2 0qyɊؿo&Mȳ޾״5l06C1/4u o}e@?&<\{J^EʕEEJd|Vur vJ(f6:]J2v{rnj<DEZ×ɵ+,,Ve&8=/oT)A ejA0C57'z3f> G狎&ѣhŵ#Z}@V2?)6} o]OQ:EyE]Iruukɫ_vMpUXȐqa%$'#L433ǗR%F(8fayZiT2) }YUzZ[GTQU!K~DbhݚR`kH;9# G۲u%kLBPN .ǵa>JK0)+3dYK>Eju"a&I(8T$?A~+ȩQ })~NZ'/D$c޿WQR[ikONs8E\[34$ܝm;2]b\n˗kׯAs䗜;wȑ#{}JpfL٭[˖-XPMU5hiSw*1JppE㗁]:w{xFK/#C9>}sH,/A;1hxe "&!1ќ"&ŻRw|U\e"睱Lr%("YJ5EErycc={nb͉&2Ν,[;D>KLLzcC)گڶ-)1Qp, /;… j S _ߨw]w[}ԩ0I"4;;^[۾}Iktg}f׮]h^-ؕ,j!!cǎh߾j6>.˸YsPKUj~WoĒ:R]toBQmیL%uUCpv߿?ݵϸAakRְ'([ֈYfÄk7g?*1`n]v =¾ӫc< a;Q feԛPJmKwCeꪩAWLF+gL|z"jm{rQ1W{*u0k fzFa!f. ˑ @&;{:غEF*ҨSFˏtGάi\>D$7o6{fGtɧ,#47L9-cJ88'@pa7OЯ$YrD@mGGGiy7nڮ.A/x[Æb$%(*2z7ȃ^hi!<= #>8uMZ6(X{*G9ҽRurT},>L .(^JNì}m|Pڪ(#ʩ(SsZ"IJI|h0vAbޅNiCXykM˚QFed,>qSQ7߲eL;vl_h:uNN{jA_T*Wc3ŌQB&誜g"13;ػwE^ޭ[Y*.:|JyIZbN 5j?>!!/?_t<W9SWp\f#pqVU·oYi˖"M Kpw#chzu>rFrjZzs ;6g\ܕ#!N[BD蘳ϡ͍v5=3+YƅϞrżTJ~}1{ߝet>}}q12+jk)sXLbH%Cd`r,bۖ?zDv7lppt4ޙ:0-x;'+Y?7 V^/x-Q z(RTm xO}˨=eʔ[GpW|D h?|eP~#J-Pۉ'WjSdfeyzɃ.]s;xc}?c1)S]]씹4׮5h4 MKO/ۼhMݓXǟgNtDuuMrr2O~BbbUU͆7HĤʒ ̼PL]8Çv_v]βooovd~㇜;;;R&C^GKInmmuvq$~s}Fef6MD @;wB}e$ AJ*t"7/gdrdmMS5"4hh~Ѿ)Sgz.ѾZBloGNdg{;jڵN9PnjczX ̡NDfy^\(1qӦq[حձ?LJLB62YBqyCPY\$ EZ[/Fxr\6g"9$8ЍUnp^|^qg/D3M_h&/11ߥϣj`q.S3bj/{PnFIBN``UV655:MIIƍ7tʜ:H ņ2M 7޽y+zz 'ݏJ2!>>//'U~~yEEʜ_Ʉ/ݾcc% jA(?-njQaRoKd_&BrRRdDDK>.R>=2o!mcnZ''=T\aHI+;"TUQ[h_S /?bvSi$$0qCADZ|W]]]} Tq`ꯈ\zYñA#<ނ@A=Hju甼mh珇NĄNd89[rE 5"w\n/RSrTTrȲ4p5PS\L2i7/WYN56\lt/tcGr#,Peme$+ m"z2ŋyqX!vJJƋ #VD J'hcq'%$(vCG?垟/UU;0 9FA}I7W }?#ի2ZMba5Eeⲇ?h/f^(Iu8%/Ҡ2#.,tww f,&yaTk @59tzN0 dl`ichwhH5%Q&^ #蕈`!^rEAEU" 7;zzXE>q?1m0 †8v޹j,rO]xȨHGByZ}% te"kLd9f]Pt7}:WB[~ <7]Cx>oyO)O3K ]C$݆R#h`b=v/k}mg߰h^GiHvmj`g'JC $V꒑ ,fuK۷YRo,8xx>L;77q>c̺8UP~+QB7I"I*'+;KL 5i{r!?euk"3mc%EOU Nh#E}#rho6< J56n\Niq:O}K%*ԗ01瀫Ը,0BC? B&0`͡͠"Ofe4pPT (e2GT"Gv٥wFiǭ0`Of8nLlw2E;naEZˬI@7eΎv>afPyzxΩSNmTխj8it1]tuuiOMC/@4d0)[iH4,y<$^XRQad0[鄒M+qɕ._8@W~xUr[=7 2H4,til[=I+3m-xyƌ9h> .5Ms~KcN:cbrgs5 +r:J:)Uۂ.Hx;Ծ<_xzOP>-zVM7f*g ^_d"-dH7Ѩs:({JiU{*yCYxn\v2/{)tx~-[]ou_]ҦJ'G*QҧM!#ZE456xɩSoov]imme޽455KEyDQ{L|fnIr&F.*qgr1eԕ)EؓVS <ڂ ~å d=N&}P}k_,ޤH}7 ȶ ޸g¨綶68@(xص_GGo%M^qۉSi<}]TTsҤ,D/JnN[c҃tEvûe<|r.},^?'F' ̲Nm:]D]:x#iGTșRe:IΏ& <4p*^qq~wN?⤡IY?tyv(s!@&eiڠ>JbS}A5|4dqAjXJ446RZRB~<_́DB455BYYqQ)f;:(@$EN~Bש2BvX**ˉ$9 AikkÇQq1,l`݊WyNϐ_>5-|PIE]+zYcFJ (D8ȫq! dD˫y+%\OAWz:YIvn4"Kj*a3P2YQg.۹5x-` Qqx4e`!r"rRFE/uH!t YŽIvmG~'Bmd`ԧr5*դT-[xZoC;eU©?G&G? M8'.]LZ&44Նl 3SS JJJD$P!7L$D"SWWI]g@Y$]]]477IIi)p"Z[hoo'//+*!u]xשa֬Ye^u.2 ,a&Di "l}߻DNKG}tkE<{uFa Xsa ljI&a(< .{xۃPSSVb=TU SO+VgΛg/L]]? | 9(..Fh{QD&LȤID"lڴUV 9e=X-- >)S>CqQLitVӧXb7ofذa,[kVZE}A iFN,_y;vز_{ݗٳ˗L&4hӦMcڵܹ+.,Yرc,m" -4tf63jC DZCA%8SR8dbu1M֚vBաɕZ5A\nu($%nlJycV*o\r08JއP[y;Q.ߥ&e~,om$pc&.NŠ`av,{DqH<2bbXK昖L& BB 2t0"رCGWWa%/b'bQIÿI$zhK#PtS갵zzL~ zJ.R| 6o5W_̀eaƍGQQC`Ȑ! :  0rd5g=?DQ~:zi?:)--cĈ,X0㎛,?lݺk}j*=JQQ!#QRR¸qhnn{LMM 7sٳ/aѢgXj_ҵ|Ilܸp8DNN&w}7d2_nL<(40999qyGRSSCeeAig=|Myg%7n]]]<䓼r.2yNկ |G>ba|aX=;QJGos>u_2˖-gkȩFSSe޽<\43eg((0^7nH|qg?ˣ.dӦ\{u]{+wsoRA | d;]gL#f @#zj`ɲ79vwm3@%/H.Wi<2I@M: kyq0p"K#-dakc\ >xw(%S$|XbԣhiH/>:.LthJ}C]Zd}*)SBzߴAm>U9DC {@tᐗpFq& YW)l27V˗eI'Lf[ॗ^+⤓N& iϵH&|oSXXhj>k2Bز]tE475o4Q@($ɡG!T\GT'ؘ?"R%QW˄I]w:##WF 8XWǪwVINzEZHW\NRCCk׮套^ 痿5ǟp<?{.TVV22d(HiӦ0aD;nDp֙grwg;voORUU[oæojjV&L1c\kgg'Wɓ'{&yQQˣӧn:/^u]Gee%X-Xg0b~MDc19r$Ʒ%_|7,Y S\\LNNvSʕ+)qyyy|s`|"gqM.|\q;[n_~9'Oʔ)SX֮yvgzDnjeVFZ/#69`̙CNN|!K,_ UUUb1,x /2"fONss3 >-K/:ΝM7}o~ʍ7ԩغ0С.tW au;SrOS5θv^m]y~(IG̳xn~\.&KF͝i|l:ƼOuW.*Qb܂㋷E)c*?ʳwJTv&<] g(`Y-_ʄfUe_ّn9Y!/+wpI2$!> < Y`!MIcpYuT~"8W ,A`t]hr=9񭮌!){oz_]yufKY.G=;-@O*_ &ṀaѓOcV{w5C 8Օd_UpESQY}+ԋi~%^7‹hnh3f>C$TBi8[SMFDߣIkÝ[q޺P1ٰE40_MTTVreWPRZfc:ok ʿ뿱k.֮Yo~>#?xt]͛?ajC5ͳ$Or\pHB7Åa]T37MWXj%ΛǷm."{Rnhh'NGz|+bUr΁ؼy3~:EEva99|&nvF#hii!P^^άfp8駝ƀZhkke5jO?)I&Ν;9sy躎4ɤp8l A{"O>C=ėU.s#+*c< q$twwǐ}V_3|Tp\hNMNqqod2i_ G-DQbwEnBCf̘gN:w)'!eeeoWʝ[ujkMD())|oM0k,YfgP4ٺu+W~ (@Rmų/fes2[+991y0`?/`ذa3տ5kL4~zs .@79g9k0Q _QQԩSS=:FL6UVr '9s&]]]<r-<̘99!a*nP Zi.uB^Vz>O:?:?yC)حdPs.qě]$oO!˞%#J v`~;\reR;;?y}RԳknRrX<ōjT\a_;g WPʲZ2}|8d˭U6`,Xpz 2(=̽2|sz9hj5O Q>گ bh4!C9xG?i a|6OC}.Bnn.6 MӘ|g8nDrrrI$Xˎ|Ñ݉p[7i|‹45aߞ]H4J:92*ܫEɟ#WwFQ7KsI;008:3 iyyy֢i999B <,4xooČ3b?䥗^G sެY457s}qW2qDGƍ7YgMEE9fĉ@yy\a0 IDATr ETU ?1?<6mH$ Ɖ'-An៘={kF?c8 O`a|[>}qQ8siTTVƋ/ȽKEe:!SBkkO<^d2ɤN`ȑM:˖N2Aq 3nxw37 .4miW Xd ee?cС$ >lspm?7/16qƊqI'1b!9sxy䑇Kwa}C?񏩨~NJÎm0vlES)D PV[ RSe;,ۂr>L@ICs u[ZJJǍu]f*+((,t ,}H$(++OZ`B0hqQ_UҪ\MRhBA::;H~~!b!u/ @u;} b:9Dž_E+8a׈P B0ܹ{mSTTȈ#)..&i}œ4iUU%_?d…4440zh~)[а 3.muɏ Kt`DC/U"=^/hJ c"|Vr<;p3Ǹ7|' +~Y4SdNˈ_WN&RMF EN:<*"0(yTJ:.Ԧ@ҷNև;ң0c_gORjvd[.-Ag]ft۳!wݏphFFFB:oiKHH&u:ѓ .k4ˢ낮N" (.)Fkin ̵J3nP>/tϒF\8F /O'IgBBjd/Ҥ0-Cz[K,NSn9%ժHH3+o~SdI.'[V܏O X2UڬC):v9pPݤgY2:%P-@ȤṌCM.GTN+oH}Nխ[WYK7/_ͯ!;f#B N/\ cԛ'/~(\6Kץ O\t;}!Pa92{i:8Yؑ.;Q\\l^*ë7iBfxjLn~%>HP/JW0KtD.7|+%&NXqK=܂׽}˓7>>*=ꜨY*%S&EJ#_D2Wl.ͺtg֟&'WV ~WC:C/g%Xjtҕ!-)^А 4;BzsiMP/o⥇Κc8;s))U"M+>i"tiYһ_7Rhsښ.lIvgÍpQliHS뗜g nNH3q}KJS=q{B[R%{ ĸPLU?q _!2Kj9()z_%R "Ԅ:x`rLo(}ӈ%2%`,win(=?cr ׍).j e!S\tqwuJFw=ЕZtyQtуܟm'= ݧMWN/7Ç4~]eU~]e>ں*=}I>GܧBV9R[fiu˂˰s64x$ʒK49="'U^>OUT|ry%Y]y kN/^Iռ̪!m8dt.HL*PAE|Ve ݤJ;K.G+2u/>TJ#T骿]U8+?{5$K[?C6Ck̅A&too9 qRndK 46 kx۶,]}3yfj />2Q!A6φ.p:A H?)JOu lꑄ: O%Thc_zLqԴGD~|j|_⛚OJ"w#@sa8swט^jZfޚSiB dxی39k7ݻw+%TdvۏC8:`ӦOd}|fIGS555O3ak׍˃W.u ʚ)>C]3]Y%li]TWN-nݶQ]vtҗdka{7/骏)uutu$H-2 >:/r]]$ #o񆿮e˖|e(XxW nR}YtVWyŗh3j0Amm<4?|򉃰#kGG^nw?gJx;w.xg\/.--%eMNb ϟ϶mlqN&}Yz O;9p2*p2m]2THG{,_VZJn^pRH8&/7ױ͕xd5ɓ'3zh֯_/̜9s?d$I.R:Ljkk;)/k_*hGyn5kOq&k;+4<5O?Ew sm8p ---s=|`ҥ^3fp 7p]wqwSSS]wEKK |TUUrq'cNJK׾Juu+e/^7wy'W.---׳~z?ϫʲeˈD"\yL0۷sNMM Ç_ 99<|ttvrg}v.\Ⱥu먨`͚5<8xym6rss7IuHu xկ~Ƀ%rEȆ 3Vz&O?ki̜9 .7|13x+555 <͟|}˜9rwqszل)--M__泰={8p444֮YKط|͌9k)S{nnv/^[oEqq16l^`LfYK:xvI^^saذf &O8q"_җa3{khmk夓O W^xXj%%|̛;d2?|@4k3SNտ/"1c]Z!se# QVZO?79spwjj>vͪU3V~+P]]M$b| =lٺ.2N>$.[ZZؾc'_/cǎEu?~~((,`A+V`Ŵr)\yfy0Ya=dA{ɂX6~ӏOKF|kk+dѣFj("7'#FLKK y*DFCԬ/k ,X@qq13fw^zE0bpŘ=F?ڵk:u-%K+?~<>(k֬nCG'N"r /[&|7ߠ[M֮}M6ƍg֬YLpO<\iʲesF‹;v,O=.#Yq֭r̙[o_|:/_-|_c߾}455r;|͔G4!//*ϟO6s܄ L8FxXn!M;ngͬ[Xt)#Go}uO,&=v/ yyyٳ?^jJnFƎî]8syM@cǎ||GOm>Yge:jƍK41~x4McСݻkʕڵH$BSSW]uCeȐ!hfg0 h;i$9(b޽z2erss 5nM7Dnn.'|>@PPP 4aÆeT4t]gŊL>`ݺu3G'g#FD(--!hj4MNb7of…}vկR[[˨QgL^m6>c^~u8죭~ k*/z:VN^^;nש|YPSS_Lnn'|2?1kg[Hѭ|566r5H:\p8ԩSy|+_駟4 =cVUUI8+(+9*\momtwwS^^o[jjj)**b„ DQƎKaj_fjjj>}:DrJKK)*.6e4l2V^M2<-bҥ=lN8`])H8BWW]vbѨ]Fe<̘q6Σ E^˗3a >իW3k,[555L<z*oXz{ hhh09wӗS<ܯ<|ue5K)=*/ڋ#FE,sN{jv?M36,kx?5 FO1bFF82 ixm۶1d'avp،ݞC3)#d ;eR;0ok.6mHkk+K.x^>Æ FFÀ/)-‚SOnYoΆ H2wBfؾoNss3C O欳"L7zyJY]]MEJ2bp:lڸ~;p8L{{9NmINrUP($:k@ee%7nd#Ñ8;wd|GO:igb 38j(&M:B3CcL4kײxb[phxܾ};999SCww7۶m34YgŴbϞ=vlwy'[l۶3tPY/9N IDATr]wߍFijjIe]ƥ^;w&Ϗ|:~sKuGUl2N9rrrXtSO9b1KYY]?M4T@4L¥^K/IJ˸_W${qFjkkyI$0m4?yN6뮻#Fo^ƣBeV(}s J{?UR?UoWa*7HGz, ڵ {}STXh6NH;~PK]B3g䡇"2cَZvÅ]SO=M{{b.b %Lugy{˿ ӧO o~KAA +WCAA!n|s /;v(."pEQaeW_}NN9-Z /`{aذa (+뿈466p≓YQ_>%v\tEJ4!9sy챿AYi)]v%1H;}?|Ù9+ hCҏ%Y3 @mm- PF!*3ZECa N:R3^Rܴi999Myyy$IZ[[uxBC' QW[Ǽqw`}$HiD#Q[Q5 *'͛7oPZ:;v,d?@aa-cxAX.Z[[4|Ѩ[DQaA[[b1~p]wӝ즥]:3'=ؽx={8Mhnnfذa҂t]w#;;;ikk#}{Q_mZݩs؂[nwtuugvz8F4D"ՑL&innfܹg?#//N |rrrBs2Ugg'۶m3>''*hi1nbKιi PeD"q{IDp8D,oTTT477L&mhmmn1v،Mϟ)f@2~]e |Ҍt] ڵD{;cFF׍v"MӆŸj^k9pLFee%^%@# ۗ͘\L`9ΞnVAArx@A dP, g<ϳAQahh4B}$MSTUpڵ1YLN,/G7zS >2CH$L$Sg0!gTXHOUD}`ʔ)̜9S92x 5 rrbfl&me46+$#iV˒O~^Ύ|iuuuL0X,Ann1h4bBW>s=狛5k'ck.(Sd jBgӦ8c!mPH7d v)Cuu5p̷7eɂX& c=Lz,hCXEO5.Kf|8o-))04t3j$Y4;I!' %qU51M+\@n|o@!K.b)Hvyz lټXNѣG;%d~&L zKL '>|e>.cٲe Q]]MaaaV%b_52Aߕzzu9*sUpuu'~8  @qq!L&w-cy>8=B/KSMq_C*ꬨ~k&t~yN# tf S-WxU= TLgtу!0>[/}IKp_F8BS,} wr-iCK"IzqBH$'\E-GV;O=u+P_*^ӂTiNL¶x~HUT*çfWt^k/:zOKOAc-LkazX ӃZ퍰%~㥸"K ccC(ӫg\Og;BTIfk:}•ZHRu\y VuN]x.ilAORU8pZݨKT7mѲIZ|:]^Y.z#l#ݧMWG >$K_*p2*֕iBmhE˲uC>Uh0J,7`MՌӌBLrxg穊jO.$o $ZJw-6vЇß*]p*] xgA:cߗdx!v>m”F A#L7.Vi=^3VOg C&*?$ȦeN7HGL/yl5+jC7zoT/1qm)W;b9z[/$6,Id+4ڴ^H)2 x Vt_OW,–_'xSYg&eU![LGR#>'RpdaTvS|_UJێi NaMvrFvTO&ͱ'AYm[O;DeA64%3~bB -oh%j:蓿Md~ZiN|I||r]R峘R>D; BG]H~~~~8T¡|-{DH&u"[?'$ 4M3?ƭyI]D{OXҒ"&kׇe6\T|Prd8qWJFȪꪒ.Zq8[ކ& F,5WBByGUm}{$BzH&ER,]VE*z^R!!Lf&93dR}އ瓜9g:kZ{7LLBme uNIE Q "I(rAƌ˸˸˸˸zΞ=K]9 .̬Hb_QeJ F#NF(L `YMVoR),Kř/Y.2..ӧO R?) # aDEk;Qh'IJWF%!Th*VhdXlKj j|-~E/~њutрB `[p;!V˾L~3wDNN;t:]T~^O||(6$QB (5J`pVDBbvm-Ro1VUsnf3J<C"M%֦79󦢲>:{}۷/ݺv`_wA!WP("j'oqODU7 ! Y(Ipg > CR__AΗN-R绔̫e^]<~ %fEB@%UI"%1,G]F(;Ŧ^j P* v<ܹNOӏG F ΤFQipUVnzZi j%Z.U*b !]btHjjZ^AP*xݪw0Z!"zA)rnsѩ8mUmVz ̑ jIAhE'Ne>h4kٽ0)eRYit_'<Ŭq}%IX1~ r*z<ƒ.k((kDNHI(ʕ N0VZ\",Ee2˱Z,?$K趁VΑÏ#MN\\)) 6|JA0"i)]Ej%$QB#IrBhuXrr I5Gaa!rq^}M6l؏%ԗyf rѱ - "o -}0~j?7onSEqq1~__MfchZ|I몬N:]r 7.w헆2 P*Q E@ j6R].r lgc٪8JU:[R QׅnVS+^PP@v~Fc/M%C5sDr R`\lΜ=J&)1<|9aÅ!EC6~WLPR%'?ڂ/+חֲElقA|~ Aey ^NjR@RSTRʍ+?ewM;w(`عsN>YOvQXTԽ<7/s8t ;vP996nКoIiG@ ڶE zccHBivN;~ѳaU|d `[nBJj*ZNǸۧ/+Vd]uN @jN͜ӻc ݲLvJu(dpBai 7]I[t){1>^8RQQCxG8$I_~?,l,\';PWUUG^:J)R^Q y&q>套㏳~P|%Fee%-]}{ٶm晧{8rHZ `ڵ<<3l޼ߏ$I9|?{E"~I-\twazvuI֬YﯨW^ᙧb޽~UVV(**T^4VXNAAyn,x󣸸A|㍟\Ol=<|'U "NJMM 6 V łf )'AOqw\Nn'.3g Vԯpͫ{?¢z蹞tѢV6A$ǎ8IpO Z˿ȑJ^HP[~Fzk?&RkWUU"+9^!L^tNuGqق M15yA$ ;Cl;REn8ۇLD&>r4`04KSQYͺǘyP[B!l!;UpYYdPiCUרJG_m}0ǙY^c}$ r-L6 Qyصk'oF IS{sfV'Qr>yS ~ -V6R}@OQɞz j*/'qb9'kj׋RQ].Noѣ򱴬?O=111͆9ogo7'лgrE؊+5<`p*D(kEd&:l$\Q*ũ'";ɓDrr22c4ѽAQdʕ5m XVސV|*#ׯmvvh2l6: d2`\.'I)>wۍ^#==EEv REzF* GII D$jjjP)$PQQΪU+1rBX<$=ȣ?~_z7zqq.>[~cK/1ftʲe[o}H۶ :ϗ}FLL :xK $aZ@ɡ@ nM$ #Ν񐐐@BB(RZZJFF@T*d2<555~zd2$Q]]MMM FTA*4 iiiT*Ν;Bj"bDFzz:* DQX+**Eӑ~VEee%`QIIIA#"X, iiit:JKKd!d2223h4HII Hc-\{͵T*q\$''#/\.eeex^f3IIIHQQr˅l&!!AXFHNN_TWWc0|k. R^jEӒ'4hZ5aVkHOOoqVnM k4 Y JJJյ6裏P^^F׮]&Orz1 @H(//GE` CE(UJnmڴ!66# sNNFF$QUYIłF!33\Gp:$$$\ L&犋IMKpPQQJ"555R$Ix<|$]}}!))) 1>[d"77իV0jh,(ώ|Tj32޵Tϙ3gPTl6bbbHIIi1@ IDATK\\FmpT*vSZZ7M6fj Dy(JlV+(JҢؖ`Yx[,*H{qE wAnz^JKJMR:u*ۗFi[Á㉔a4#cGLL :QԠTF ðe-Z<رcqq Ҡh(|G}~q1ϟ`(N4={FRqNjBvp8LȖ˅R$666RߏFAXxQ1Ą QrYnzӳ@eU%q0[ j\5JYq^E*w˨HЅ*_m)l6A?v )$H *dJ9MQi 4u  aձ)VK ӪUA,V ׋ffuL06 (+kJ) ЫW/FW_}d^Ƕ۸yDRRR>`RbXk-2c`qXX~"&*JZ;V!\pZPT:ص+IH))xᨵ"됔 -kM?1Glny*V-S`蓏$l206هTA@.ЩC[2)$P ʥ "2^h?]3X,$39uWz(gٲe̞=cǎ[oҳW/vzÔSٷo8x &OrIqGNbjeӇ'`2HOO;d͚5>r˅nۦѷo(.Z`0HuU5_=W]}5ׯc͚5 wIll,/͛B@qС),<|{_č6//GVf)))D)d9wL6i֛\=f `yj%pYnz={3dffrzAuu5ZJ%+V`󖭼"Ξ9KjZ*Ǐbeo0MHHyǝbb˟E/Fߞ8vʭ݆L&c۶<Ӝ={E"d5j|!jkkY`$Rgc̘1?믘Q)UTTT0vXTjuo|͈O1c "#11|>CdfeQkp=zt3mݲw}={>}:uj3%E yX-7ÉǹC#y\"[=z=?uuu&|poo߾x^nقAE^cƌԩS< <E6m:z>G$'Ok!sHLLl/O>#Zk\1 -Z ȰmL2~>^STeYl6^|ygϰXjL:%RW0}!09s& Q45ĉ̙3(('77QիagLgȑI@;3f?tL&}fZz5[nEoУhy$=nG 33ϗ-ۍ?gy&Ν;DzexGz<|9p\ 4&Nl_Ӻ~zsR]UMd>>~;6WQgfөs'}K,3b 4UVSna 7PZRJ\\,zҲRnyC ƌ3عs'VcGxbV\I~~>$m63fnp<^2AFIɹ 8馛xy&.\H=ۏ)S4{73gÇvն-3fQD֩S~JKe[޽{/U*UjQx<-G?h۷>LѣG9z3gB IYzslY%8pFBGk~B[o駟b…x^>sjVV8 r%$J||<Ǿ~>Gff={]v"qݛmRRr];wxڇ8S^QT}": ZF(7H0(sQkq)!$ j+i)8<>$p8=]8.KP+B/72g e5*6ZY$$$pSRԹ|7 >áCXd aҥL6nݺQUU@\9t(:G_Я_gر#ǎ?1c?p{ǎ9rEEE &Mf%Lz+=z`Æ Y&J!^{cZϙ|˔Ȍ}λ8u$v !@Æ1`^׏qSXxztMFnݩBx^>L=' O QՔCm~~-)++广]]w=3nG]Ȳ-8Nv;eee*>CMNEE߮YÜs16m:ټ{ڵn)W2M7M[n]˗ȣȑ#1idv; 1GпěoFVr o3ػgodL?`0RYY {&N7dÆ}Pޣw?o: zmXjHJjĉIKKbO1s,RRSX[>sj?)2PAP՘L&jkk1LQ+# AvnMHII'}իWz1vn81օHPk77xAߪ*+//zE_xGubË|ᇀĵ]Ouu !CΆ ٫'O@zz:]t{жm[πHNI'8ydT=g^QXX" ac\ȃෞ[礛L1>M+CrS^IIBtsV)Y; tmQӛñb5^/Ոa70( B%w4\.#M<pBJ3sVIf#j&piWMIhyb3>˿*[L.M[ L^]x[6qNL:#*Y}8z(1&&M BLh*DTJLpxm%mr{V] DBRv([@L Z UUFlбkdPx4{: [}k=ֽC0M1jPztݬ4^(Tj FJO 9RDُ٠Wnvt+h4x=O<Tشi#C`U;.ꥩCRѥKJJ(+-sΨTǭlnht\NZZNW(^vڡRLa!q睼{8NfΚl~ԩ[n}^{^?Ç3~x FvV+&c-䫯SO?CϞ=nwL:uxVVQԔ!I!̙ 6#GҵkPc!7=*njsEll,& Q 8xݺuG&QYUI^=Q6;JEv68t RIzz:žN||U 6 JENR[CFFF+d8(,<ԩS 4uJ"==Wx'OGT "ƍӷ~ɷ ^'%%J=tR]Uİu#>!!3DpGrI/'NBvk.;W̶m[ywD>}B$+++js.T*rR]9r0t:<v;YYmQ*tO\\,wz?ɷګriJ%`~J2M62bFG~HyyNI$I(!g'$&\kcrJ>$QcǎD^?0klV La!f [A6ns!ѣG"ݾ5 nVEoSZZge͚,X.:].lJY2.]Bcl<6ob;ﺋ/ ''g}|7mDR?bȨIz$XVfFrrr*AGAA>>ÇP(С# \?z#L&$ظ8OX 䂼efjkkq8~UWsq!$f) lViihZ#n_}/n}&,X.ᇈ.VFle8%%$4urW11VcԏqFMz))"HRx3_X|9m?~<>/2Hdn! &5jnLxM1nHBQB_~ƲyfK=/Y`0q|f5>NjhBR/<9NڢP(HJjsl6Im iܦBXSSÆ CIBjM%Z~nzfF&lbH^;6pAp ,'2VF|}UkGA")3/'f a?Y`N@]h͹Hr| AӈJ!+,އHU p+Qdd"05m@1WU4ZkV8q"r AP-Q@mT`Hi\&'a *DbR ~3$o>O(T  AI~ qJ*EYac((J&t:#]o N\.gil1c`9W\L׮]o˦MHMMeݺt:vĺk9jG|L>^={jj֯[wQϐ;ۯ/Ro{;v0h K~r9ݻwrdjXӻk֬ncʕsNgxo$Gv۶3h`Ξ=K9D||<;w젪[| mʀصk':t@&)..FPw^rssط%%%6d袳Et-9ضu:t$??Ν;5&5%EZP(.]rsغe 99|FrСC Y4 ;wcwÉ<-t֝#GRUUEMMeXS`ժ<̳dm˫҂ӀB'<HOKg= 8ݻwc֭; f„ nN8VKM֭[n]df*])--`0\pĤ$Kǎشq qfy}>]ti`ݧHR8/HBb"Q]]` Y-&N8c0h`?2zUoߞ'NpΜ)$ 7յX﹒s:t Oұ_ٳSNk׎ɷ܂СC;o5ztd<7n<>}#)) Q ԎW_=)Sйs=Jrr NETJrssIOOgөsg ~MT*v%݇o׬pEvE^ؿdr)Ak2r(6oLܖb&MĉyCʼBwa<d曡zS;; BaR;֬Wc;zRoEaizAn77mdʔ)W{zC8x fјƌd߾}Fٶm#GcTVV`3s!tС~9s"Vv2 +h0"o>Hb$/@!W^?4hW⭷b! [Z`%硇n9$KX{/Ą2u*6w1xsd29ՕeϷ!dv^~k3Q䍧0\`ܵZj%̙3ǏS\\ȑؿ^A}ϷůKA… 4irD(..&..sW^}Z}RZk-ݻ7J૬dM\swqg֭[ %~:ѣh*('==#GxG/Wg [[+`?_믿y|>fμO?]?DYh_k['_^|qioa̙͗-~]?Xm2h4XV$IJRj!Jnݻ5a1tMPtlkj& R(y4ASg,^ξ#"@LcEg pǙBu<;5Újbc8~V+zLv?DJKKyy}8Q~,"6l;17B#Æ\R綫ذ[v+)D)%)p`HL]l:wl7)uԫS *73w؝,R&]px~I@c.ϸ74}[zЪTX:wZ˹훐A,L&P,tރc5k C.lN* $+(6&S^^e Q%D1I$~/ CRDo3aU% !<qѩSȆKAܹwDV\I]]Fq"JJJI~t:oun5X~i߷wnwTO֭[O#*YrԵfu3D] =h1)0lMƍYSzt"5<_(/k;vl.X'gݹ{~q#??WϏ֒t:ywv} nT*Ub6zHelMVF K5H(.HTe[sMXv.tL x RΜJP[FkJҏQ8nGdu~m*?%Kk4Fѽ'Zۺ4fD$Qlzˢ/H`El{Ik 0%""z%q$ĚB k|oĝ<f=M5%洐.I YʷP۩JbO`5"?}]GfM^h-:M$QgrAn\G}X*J1&̀FP; PS]믿R*=$@tKBEy9Ç g56Y3Kbb:;2 \\(pD@0>JN(S+PCV#2#rzbe,|>RYGkQh[ԵͿu:rFG)NRv|A/$mU"׆ڗBvcA!_\.W>`UUUQBh8xu.+'|aZb!hXdJEhV# Mqٲph̫wS|X֨{bcczQ16FLL̏ zV+UUcM0.]Z])J%6K%l!lψ\@Pp/na(_B'~xt–d#h*HyҳnF\.^𓈚 dPhYLlBc$ʅꞜgwfvg#,,$(Y@䚳(Wޫ~f{/AT@ s3;9Ot8=5NU]u:eTz.T2'řB8gL#Vbחɜ)t)Բ;hzLPQZh Gd/DRƿFobڵu%ҘLE꒲r{h߁: (--a!3 r䑋l6w_xQ] BclHD1ie/ O?h_P( C8H.dHjiiiӦkmBCc钺:cshEVfdfʒLTPdAY t;[> {>P!Afxޡ̮chJ()Jڙ̚3>NfCB:M Ј/5n-in!öقgOW$\$eu'elnY[+,lTx}a+Vcp6PFe+e+?o [iF]]GFE%A4JJJbC\2-leaWɭyqvO˻˨ 08'Q:ғBAٺ'Zc- qE~RKdka :2;zdS6d|y'b93[[(l.-$w|ȧ-Fql=e{8&yqNփX*cȅ0[yYS9s[I(r?{ aBQ XeEm|b͌'m!bm]R㚛'l#rlpK2p6]&Dþ˭SrwE2|Fu/If\l5ױWGq>K&cqʴ?2+v<'Wtb+; Td/ t9_Vg|.dH'y6xV806ª)uǙʔ (7w~yG?J7u>Y\gZ e"7SӍx?Q'^~ BP( C!:f½52~…Ba:;ɒ(q$e x^r9V BP(˝70~eZ0I3uN[FD3˧%V BP(.ylƾ4)Ŏ(KIzD~DJP( Bq!]HGy޽9#G9X-hVKWKT`T=#qo$ʰ\륚2GJP( Bq!Kt(_( Y~F$B<8^d*ulGi^j|BP( B+ȷyfz)8Oիy93gbaZnw2ILlɹY(BP( B8 gf*jjjxꩧXz%Ӧ5p5cvsO44ш.i[Ť1(1exմ̵%PwBP( %0o|ϟOm]-Hɻ}& UF Jݐa;c:qIf MBYFf<>&_N[J BP( CBII s6ѨGIǶ41 3 , ғַ}\+* eDj(-*rD,a*t AM f$i0ʤ.`uuǙ1}+VwwM$kfْ\s뭬ukIϥ BP( E1Nx10Yuy7o_Vʫq/{^0y27}z.ߥpo^&7W]~ivw B)”Fw?7c㈅ׇcO}[qY#6mmLwO9Ee(W@eEʑ gz) w.{%/gMu5_-FZN>xt]g7p=gv*QYۇgt2ttvq9g> x|/ ]׹/n?[q'WBP(0'j!tY([{?TՑt2Ic ( j,Y8 SUSFyFui]JF>%`]AeP!Gq9g~\4>㺫2XY~Sz{.刅 ͝w188HUU%I^xXF'y◄B!OƆ|+*w+wR_W˶˭P( BP~+\lT&^O]Zk]ztE-Javv 1\>}-TVTr̢#Yj5' )%o1q@z+0h 4qC畫VsqrDZn-9boAWW厴=K)71'}z/~Ӝ~MH'ʌؼy +WfڴfLilLBP( B1BsX5Dct5XMfZ-P-9lY, io*( }#Gt[;@juz8tdʊMhٺv'gv*gv%uOGgO?\u|s|9\y9ZZ?Y=CmmLijK."?0=x?ٵ{cDr ݄pQ8=gT2KW|;bnΝǞ|P(D(◷a,[V;⍷I',Ϯ)n8=f]Wmev~xѴ7^w-F::;P(QYYN?ۙ4cZD}}==wE̞9[H#17K/☣nFO=sM̮BP( B1>{RGvuvS0>gQM8Ο_ʃGUeunow7?IeS< >r1<׿mog,]~J_HfѦc]fe+ BPxSkNVZ.0H=%u348駿+֍Iu-&^2Ɛq.YIPcjM1+,cfSUDN:r&աhcf*J"W3XWGkhӱ. BP(2j!4Bc0Иp ݋:lfZSy]]]D"I)YP "e( j~ mmk=… @3cGs|髷`5ؚ: e+BP(A:.c+X=ZPuƳ,\[`ωyQ(\IyY:S `ppN&Hb -" vEl _3dIAmzlP( 2hv y1*G:8sQSɰ,ۚO Qn:2Zjcێ|_.|4LDk٣lP( bBbx#.a!h0gq ^>?^xi:X '0?&c>d:u2l/yV BP(&VNqaDf˖,N栃}0( BP(rsaz8zm˜ +]np5u1K-غddAX+i1]sx7\mr t˜|"Qh) BP96j8gIE|R0ٙ=s&gַ2Hee>ʱعo___7[) BP(,RNsF=Go&wצn0yg Mb,QaB2i*sz$;h,lL[*BT( Bq!- m.dt@ϮM)jPT2L:2H$c!dXI&f|BP(V,._u20A[|: dr?$& _mG[) B0x,;9d=w$kfeݫ)˙=඙NKU"tzg&*}Fa4r?/˘9W_m6#IBP( 0do!LV/O5; K+YrZ܈agT >mADz1ǏHf<V~BP( E!gù!šX&}—74)]GBP( GzC콒b[~`:f?m ӣ cAWQlP( BPx -l>\P684]L }iL3F[VCP( B%\q݇ ׈,GsG/΄u ;+Awtִ2)w:D]*+9E1^H)>?ˏ([) ‹t(K^+!44)5!DBs&K.?q2m1ݬ ْlMZ儧ärlG?^?ˏ([) $Y=Џ]r;fSsV0ֲ֯ւR扱ͅ&这Q]ʳj2L+F3PN[Z BP(`_P<k*i/t<%*uqt ;'ϯtx.Ivٙ QVVCf#@MM˥o:n+1-K2;lgh+G^'嵰w]`gc~˕rU'yZ.crn҅`0HvX*2EBk8N4 mH֎97^i1fIa)Sa ءi̇2x'o▻ 3r ˳,xe?C[%gy:ɅJ,]$R˕2 sO@ܳZ;1k35.ؕFVN̞u ' csK)SY<(_d9hI(h\OEYiFˀ2C!*J,G|ٍ6:T66CyRG:smȓNS].W\V}жึLbY[nW:#)!tCiq;/6`:-L8{y.sRiP2###+V9 ACc#^| G.Z6co|[m¼k@eee4ӟPRRG>;12L,u+sDm|_ x]b/y~ V).~).~).~).~).)ޭB>Vc':ShiV a$\$粑gC>LBX[SC/h&DF?^zyfrRJxhٷ)Sbjs3۷mcjs3tuv‚N'M267 FOooU466RTTˋ/<σ3q^| `8--B!֬^_~ Ѷں:h2.Bg̠SO?@ lݲ`@ 4iA`RGB]Hk_kSTTDSSW_s p%%)'8:B /{K/3d- 8sزuk:NSnuY}$S] -WRR:mᄰxMnOC8$]`ChE&^WJywq ac-{|^l8nJ~ aIiYBڴLy$ۣanO; B<կpu~ӟs+_c]wpO87f.'r *f=tf}VYΧ;G.: <E!xmmmL>#^{{].] ^m?7>tM|ߤnn:hҲRoλz6s_vI!:t4R?nA}k;G?q:۹;xxKХds=ڹEGō7f|iok /W_E-[~믿/n+Vp/-qŕW3H,D.AO[_ne?UP,O_:yt$lz< #_^ZOYq2{m\vڂ ).\̂?ξa9YywڻCo1U78sl#x,dϿ{[ý_8v'QXanasMKghpCX?ǝ{]|:̝q<44Lo__ʴ4Mco`JFF"5 %Qըg~>p9anǙ:u*1=a`>¡|NeUCoo/*a.}DFF}v_{:c]:5yg8‹Xb9G9 du ƍؼi3شq#vH4B$bBuNcxիYt) 7nr^ye^xnJ8ʛN|Orwkv v v vJW?='ҽ{S7~Ug.d0Ad]CD99tp瓫E9! ߡ<"AvBj%W2Ϲex$=#s痡'z$ JVϒ@ 9Τ7xkZ֯[Ou3w I[ j:a~u/G$AU'OLQQ"4Mm~zzٰa=` 4M~$F4"jر}]YZJKrB!֭^C{G;? 'w՜{ֲszٶug0H@@yy9Z Єi!(**2t}(/þ=д0ejS_++c]|ͯw׽8s~c|3 <=,?7|7zY|9gdڵ|eeYM66ChlsLݺf$wlf+w.WaG[V?e,.~+[)[)[V_?L?n&WϤHCI>ٸhB0dTBwٱLkF:z& MN?z&EJ|3$waH! K,鷶yo'KMq'ƣnbΛۛZ?72G\Sr{2Z𸺉jԭc3Chh*]{oPiD,}F ;,X,TE8Yd6~J0Yv-^._x%r!$i=Px<-qK.{ヒ\Kww72+*p--^++{pWbk+]>>֯[GWgedatnIZQ NXztH}p8=$[*6m\{-9,Vf=餓6m ,'du.[FYy&D"|e޽tuuFF"Ηg"a^k"ɕBl_[͝pD/Oeqe$2LITg/ٷz[AM͍{)/-& D D:y,3"*/F"ٲSʹ^V}ZJi)]0R2&1Yɟ M^(%f=g LME : N@g,I9mfԳfG;1ofc5ϚX>nDh ʪ]f4T\\<'W1{J -.;/R*D$o#nb`xӎn^^[s () 8uL)- TWAEi/ɒyS*+xchC5^j"RY]F]Ee|v]7j9K +lwcaSRؕvzYE&(@c[ 8qmh_27ܐTO:O:.>KeZa9<9<+u+^ee%^~.R4kl8/<\y^u5q&q&RJ.b.b\u|˷X3f{?Q޶@JW&RGtDl95k3pŬX",YqVKlܰ={djjj/'k?锕ܣj1).~).~).~).~Ii˸30a>_|<{w0>yR$$[UHlkC.䓟$+W'Du=<ď#x,aժU|[b֭\x?0VXnZ6o#Hpc ;ܝ_rfZ $/^Hlll51l8_^gJe$vLV4dr ^Ԍ#;,_Y[Tɼ[,]:s>:.p '?bJO8xDd6~Pa(..bjdc~륝*\[yGU([eUV^(x;d[Z\l1 񱛿V@8>Ν;XzW_AD(h-d tsi"G&[ &4o?I#6p KhrJa ՜cMo\阸~v[ ==ooz*cJEyz})u˽f"{~>[S.u-NuNuNuNtI6.Ͼoj _6EP&2Cn#UrWlojaKS:Ӳ4񭙹]!8ӵ4o2!?2k+o*DE[D+yFJԍHgRW_ '6ܤ'{{xS隋ܫzfZ$к]lllup L܍T & j][Sbs֒*N+Nt.=t8pj[TeJ8*lLkB/[n\᱕I{}k5I2cLfy3#rnM[ۀx]b/y~ V).~).~).~).~).)ޭB>Vc;j!SyЈKb ot=͝! ƺ aG"9Bڷ380Hqq1%ƚ1-L**+rO1I2+J6G96yduM| $Enrx<6jG[Q@SRR( }ЄxMn\PcAfb0uDԅ5Q=6ܷgS#y&Yvn~V,8brs^'Qzg'zB.-0l_eQsc20P( Boجf ;Ɔ8wJ]d"9<6G#AF#ضz*غi+s% % P?nt lYiL8fݪu0w -{[عmSMa ӲM__SNaU3<[h̶۩CA&eGsNZ(+/p8@ Ų˖[8jQtutֵbp7BP(vdY.Ac2j4 c j=^-)%0Num5ZIuu5۩TǮ)*. Fٷg  8p hlj1f4SYU @vFIi e  [TTURU]@gGWF6IVUsލs~#]wBGˏ:) Ba'g^'SUSy 180ȡ;oee̜m;0VÌm$=!wYx Ba-%u _K<Bh Ml!BD:D٠i@ Et>[`l`kܻ{/̎;bVC{2f L( S8r (a yS޺R\uwvZud3<TfKhO._u S2_E']..~I~%e3B,O(#a0"*Ͱ̌4}ۃ|J4< G BcW%?`IG+gZE. zr8L,P{&:YYؕX#ݺ3ʚ$":'j|2.ɼKBP(I̛}J0g]%;f`m։,O~&xOwҹs<[伇<+7H&g96[*Im _p :4$z\(&/(% c?g~ru}{kO:c-wUcTn>;Z%x0R9k&d4[MIג%ws#LPʹi | F(--!H2GH+tw焠؈DKcm{%a|I.[&X.ۤ@.%]I,)+Un;ga-Ry½0`ߞ7~{Su0G~{^&#MJ(&vmG0dM eF%u #:ӧƚa93¦-0KjHׯGzw!9!@BɓE)q\xD~+0qlUcfy2rrm 7e"gFqlm:¡q4|gy+ޞ!O=/ِIlMs: F"  Gii)GرmUuVΟK E4iF(+/cݳ*ΟKYY)[رmuu0ct:;زa3յ5[p(@vcN**8laW2!̖%mdyBn^\C8~r0ߊm54 ٰ-mtwuSS[@ii)-Z%:~l!Zo>)-+ahpH$ʒ -#ZH_oD"QZVkn6̼1}tZ-2@EX?`ػknfMٵW|S%vwuty=mv:;AJ:Xb]w"4dM^'[I @nu--+[)d3 r[±cw9K@<.PyM'}Mc׎QQYpGFFA4͚NuM5АS$2"R)(g)tw u=;!SRRزq U,j'"eoAI8xygPT_к} q~E8N;^{?A17Ӕz7rKI[x,ȅQܺNeu̧'7a=F+<kz^h$s{j±lIݰ="8&%H䕰øv3 )yKO@f'3vF¤9<иbEEA4!ꥱ1v1{l[h䰅meu6k)-+55Nd$bRY]Ɍ3Θ=Mi&84%ΌXauei[K^f۫9󽧳pB0/E6HhP(JJaڋO[k;d >s PZZB(%.&R\\D8nXCcs#+ B|^dYfI7M͍,i`jkXzen t(OI 5K;O>Ο67|=O}g_ IDATMgzcNqzږҺt m[_w P Eh$oc)~k3:2/I!(c3oDy ke:.4o%`TIz dㇰy-^28ӺGsS3NGbFuv%*]nGoyY,|鐻otQQYhF"@oO/+ۑe}o dYycNE]CH8Sg֥ IBA TYAzIu,_p&Y 1##㻬hc6Ǯk?_>@0-ɮ/z]'CQ.sOGA)-]εx=^s@ [445к?u³apc..8]TTU 7+pn,/Y[`t>kO sYUе3u-գrk, gCXE'cЭ%2@rAz-o1#vyre&S_@uE\8~\ʲĚuX!Ο98O< BAnzF9xy=e߮mKpr445 2`@DyI,҈˥}0em _-&3ϭO{KS>zeM /_E Xb,#EPxn 8muX~5c??|FK}Kltțo|' /_^~ʛ_TZka0w~8<b7o{xH~I܉vHĕJȥEe4- =^ T1ݴуB޹oᗏҹv%,e\,361Fߕ~,s7ܼ-gi\#?x^~ (_ɽ46qm7Q[_Ĕƥqw:Igkcܴ`!yiy8~-|AIyJ,l|c0J\/f^O@AC(2j@!.cr#YQB4$-?KtI>CL1g S9pPtěIHn ,B͝znFfg R]S*inm?]'@h>/+ܹ͒7lZ9|YB!n}#7wܲg.[K[3'ilj@U|?@*7_>%twe&(^SㇰBZL0wC;!\ˑ$ GdㇰBZL|y7<W7XzY L,d[!/^~0e ['$L$e~y @kUʖ'G#.]\`ߪp$B83g2#vagŏ. "qϡT_dhrZ˖獷<̋񆷾UkVOL-ЂVXo>"85.y]˼5A˩,e;hWY9?;7u̻J)10!`r!jSFFƘ qg7`ښ*\kv7Vќx|0o{{܍Ȳ@ЏKyH%=^ɇny% :Q>.̥_zfW,J7!k[{e MG]h?MdYBUed)A} !Ld' #)djZt7ǑHrL BC䃱 B~;wf ,sC۴S>KdI ŧWE6z뵈xGW |mU hOJ;학7\Kzv-?v|1*g7L# |HJ)C0&|3b"QQ[st8PSZslURZx3dj|Lř>3"C9iEʋ//^/*]-0wC;_^Dasʛ˽tpx=d]CzH .a' t%d"(iJֿIrˎ^nm\;,w^UU?Ws+(_bZE0!FC2?L(nH4ebVιN]NuXhʕ [‚0˒7ܓݬW_̼ Pd%L0|?SF/5R[WO~#zW.ǎO<,K[N(_غm}}}L=S[WwCo|[ظq#Փ:xe˖;ǎn x}>+}_UQ{ʪ)AAHT%4pސ$H_/W{ׅ4O܋.{>oˌןɳĵ1nzQWvC{0WW_P{RjiRHiΓa{F!f҃"~-RTa2Lg>~/ D3gg\pJcS8{s>ǎq>{Qb .3gN?9}cbldJFGFG?~2N+(J8ajz:kʊ"B޴mw8x5}aiyS\0J1 ia aBăFC9b H$%lܴzdYC_D"~\k|p8㝿|]]|???x;wxx'?mux;w{Ox|qVo>⠼ʑ|o5i>?B,?FoT GW8pOloYBi!HDFG0e4iV!̚]Ph4%^0T$x^B۶oghh-[BQFFF|neMʹ-CUUѨ6 z~ww{ 67Ʀf&Xh'(,TTTauH %F,60'w8r 3+6i\bX L%ST0BO:~s3D$IȲ޿|Cjx_gɒVѾr%p05=gE{;r~?n٥+2. > ^Pkj*. p\3@e\n##?vk6n< I(BU +WudI+W\3ẗ́ó9up8LUTUWsp{SW_ 'O` 79v(if8}^7( g K)C~B~_iF'*u5UP:8pPUށUG{?CCALwqW\aEQUhllOM"EF0WJ$GrYLR*"+1}_u_F( }߼k~LsK B 7o6 ѷwtбre/u ܵ ʺqM7qyNZE" {m(+r)Ƌ}.ת2BQ4&rɁ80Jw-Iϳ >3H 3 1'Np[rCXpxO

1\QmyX͕7 Dtmv2KW9 \˲LǝQ(*3(^RAQW9pt`BA^h TL8sዙb@$!J]"ů Mu9B Acjz9ɒD0$'X^h\>'_*&^WL8׊ OPU۸IN3ZJ,Hvd˄W64j>%CXJ>}t0ϐVqdO}t/1 u?$2A QU;e4A.una~.q5Z׹UtU]zc&)4b sJym: MĉmD|TU胔e6Vdϕϲ`oCr`V!̚]8zϞ3gF:e^Kѕ|:j7i8d!xR@}jtB*cg ayK!4M)3?:0d!Ig0GmΎ},$[PӍBfR\IK[}stBMPb ĨXhr*w\s9r-$CG}Fu9Je9~s*w9vn̗N1rFSa0 SF hL #)L -&*9$1_,|^BjĆ.Bq gd !۴6.4#_PU3uc0E]d^7c츅)Pgڙm䚏:Q;_N;_N:p\|^biōWec~ڽEkBhE &)>uKIJ1dZh1ז|J mdt:y H4J/2puUQ}v8yld!SXޞ+?r!TU^&+MPY/y(P&N9)G8p o+͹͟43Mx۽ፘk^uzvk] NbYe g&sዙb#AAJw;BꅧDqTaI[~vʖrgNmXRUUo#Sԋ롱)FBhۍ쒙$<;Kee%Kr?>UUKZ[ddh5Ģ1TUq#˲boT"2.ATUPU]Ģ1dۭ@D"Hhs=^h3YҶD^ Ե"úB"\,XfhdCx_1*w\s Ȃ֠<]Fb\AH.jh$?j#,v5}A\BKo OwRx:c q!MRLk(]nOyoxA5bq;acQ&~]FwA[;9vU+0xgjr0)73KS2 1;;K$ (c033KυƩayrFǘbr~?m:2<4L. 55]`jbsgGoctfxh, 6BEeK0@ȲS{iItjGiim) 9@) rB^y9ٵk,gL]]]\zMo~;q)n|{gYMNNrav O~馛`}Ыҽr5+[e#. uWqəATx.j/~ORsˠDf f)K:*&b9n TL0zaՔ #aVBc, 31!%C,DUѧ>0հrmeKt%%I2!o!% ^CYE%|1hZGh d[,<4u)- 1ҕi=v˝-.C$a|l:fjb*`-b z5H,HT7]nK[$W7366N4۷xQ+XUQ sU3?/cͺլZ`kׯb ZtV%93Ic9{y"Ϸ->O9G}/Ds5ٱuAQ/}7E}Ǽ+?({OI&ga6K#7lo$0JhY3? jیKP1h]r?n4ʣ5^ЌeZ;n[HR y 5j#Lj#蟅 Cl;?fb9ԅhͫI|2>6A}C=Kږ{y#;tgg?=!<A,Ӈ@vPb m B³a?Gv{*Bnկ9}}dצd:$Kc<mg}7q=77j{1n 7޽{k׮[n' r1ZZZ{ؼy3[lᡇ⡇ⳟ,{8O=r7i&|A0CCC|SbժUtuu/{?~/}tww\^xUUwKK, IDATK 'b׮]XAN?-F φ#ںC9)\.;۹嶛ٸu# S 5yCX4]ZȗK12eX?~A>!I/"XiÌsNB]]]}E$<۷o/}KOqib}}}|gǎLLL/~+WAmm-w}7---1=={/,]|+9sq^/oy[#uV\.w}7]]] 1>mկ~G}o{wQfffؾ};ǏvMo.3_N;_N;_Nkںu6}?͛߼5l]vIFTf#*XwMY6&/x!QBӺD $~fE50UU`2&J`<vj߮|+o)[)~v4'tqˁs6!L¨nk1FG3E?9iC~v\oA X%m-ڢiY߇妡q<,}B|>ǏGQTjdYmY+}WԳ?{ˑ$Fm}Y}x9Οq(f)!Y|mO<ƍYjw駟fjjhؼ臘]vqrm׿uvIoo N:uSN_x^G?۷og۶mqzj=4O?4GxCׯGQFGGYt)wy'n￟[nt??8o~7G?Qؘ_沖vat- _N;_N,o}3 Eݜpr:r˶ٹwngYS +Ydygx{×s0s%mxI[j+K$m1Za4%j ݒ6X| ep/)H؟g[|1z5W~>Zh.Cw2d&(I]Gn,T9B<>/5}~6t!6$,]F6ԆHu\cmJq[o1Jv7]I?oimcU:Liv|557ܖĽZ<}}}tvjVTTp@Q#QUU}(ƍ BA(ɵ,MMM477SQQtMTTTPUUe!F/G~ A׾5mۖ6׽uWbڵS]]͊+5| _ ٵk> ?O9q<>}CӃ,LOOg>ʸ;xᇉD"۷ݻwS]]F08X0d|Hj/.K{5100EeEG:3 'ƓzFùGAm}dT۝Ժ1膉E8h䴢5M){ L;█Ot>B'-<_BcΪk1,-7`zpvr%ljI'AnYҧSvNd+9#ԤIV_xccZ5ݕt0 +~6l'?IZ[[}w]ݻݻwfhG?׾5x vAssJw?wӧO裏"2hllf^uee%]g?o{hnn& RYYg>>Ofٻw/o~inn'`Ϟ=ڵzSUM?ݻ;vO~ͺLb:]f_^WL8ל =ѵ u H]LTQQօi~ֶֆ<ٺ#UňDTU3QMÈj/,ꖴQA]R mRUhΘ ]/M !,w.oе%iddX 7p{}xAѽ.cnv0R"cX&yk'륡69%o*3yj-$.sDz6c*g'TEEUUSb_4F4# qUk68rA:d/Mxe+BVh ɼtqko]]ͺ%A=Ĭ¡S MFG^={?  XC]˒Lu ꌶGf#Hgg]!䇠 .Mk>.Ѧ&Ϋ:aZ-I;Ҍʫ3,Y\R^'ΐ6m)UV `Yq!qo348H,* s2C6"j|NcSs uJ_Ch)!WdZW0QJ'PfJ1Y,~D%z4|A,Ԅh aq]amrԀ]/2MY{ZWh9L5&Wrrrey M%zG#Tf .ɓ 핂WF_J7JgةS&bVKb KH^bp/QMl h#Jc*L)A1)KԈ 3JzL4-"Lk.4G:?B>Wy3oB6$N&5p;1E:7:ŕ=nrd9G֦ZTm0F&fg8{,.E}c=j/{^WU(Kv( $rEc.!@ ,hUBQ4. ˅P1%-z^q={R ںHFe%PHvFBr Ua.tOC CXu9ƕrU1jbUtel SRQ >Gߺ#mE'IB3޼VKF =6" ZګMq_NCJnWERZI_.I;hӱfG?!'qYZ&bl&Y_w;~7ŕ?SbeA|X #Gk ۑ4wÝ㝩Kܔt./4{-i9CAjjk&pqnnI~z.7ws lزݗ.mı-ajjK.zBPXMȲLFGƨby FF9q$Uе~-t324$յ8|zTUWc9gNezjں**+xHu7l3L TʪJ;۹}S5ŎŴՄk^ GWԕMc:&q(?Dc.V;tQEhZTh#tj%G)>׊#1v.pښBN$l%nND5#s"MLhyM@j[c,qme#^F|"~ )B*2/Id_\,kyHc I<$B5P$mDSU(hy4ֺ-)NF\2.~veT^= ړx[#T'oBnF eIBAm\ǹPo#ΆF"x>a+Ϟgr|BKSb\< ֮_2V~:vrcaE &yz}^vkiji %&uБ7X h^6GW8(nUM,S8l$tR wNvJ.$IҌqm;?@qN^um[@kC%|J7܉ `Ǻ6N\{/ }m+.YˬYZϲ*&¹ x.oct$i&d,k_n?iB a(t=cRZk:;Rl"?u<`#c\|-8ϥ+D"Q:ۗϫ,` ٶi=>\wmL[eٲ;7,e׭Yzs~~~yC4-ӿwOW2Mm @GSkӿunݰ$~]֎:@{ot~=BF'ٶfIZ}:y I$%W >&v5H(Tu4Aˀ7Y) UowzzK}]|w&sዙW9΢*,# 5m2=3l8l2jhYarIOƸ;>(B]CNܲK9+HHݰIeKyCTUWQPOm]-cH8Sy55\Ou lR.QFG׌h$ʾ_>Yb)ošҌ22R[_˅ z!RPg/d[5M_p排J&~5b|r+Ci^'K)? O؇gXbXn+殣ŮӅ8(U M,7(BST"A$j`s?$l*&ow3F5l~]?FuX\bx!3%zqMz* 1Զ)xKNd&˖W.mT?>aI-'Y%d)L"KR%qkۑ1A,_011*$QQ( zBvzHF$3n}^/niq395],nHpU;Pާ~˖EUe^>|4Ҡ;n-8vkWw9}۶?[VX,ƶ-?W|y<6tVɯjظas'Ϝ"J_?#_`oӧ匏OѣD"پl)+PYdtl5+<66Ƒ'ټq=Uʁ^l4g,j&3G/10:Me˭[Vp0zGx5gKg3>Oۊy%B>^wJ1Ǟ9 V˗+hoI[(]غŖϵ.\2qWC^eK`lXmW4?e 58x +eC<΅/f^[5=6iC@?ɠoI 0?~.eXuC/Jᇰ"U+yde ftspǞۘ}<47߸ʊJ^>th,B˯i}+->)lO> 켑8u,͍ 9{eK[9t8C<3,inbݚf0,|ے%H|;nw78H]m #c,]B>N>룲0b\fa|*+yj;m5q=.j! ' B5QY^bv6U+ٰv 3D"֬\CelZŕ~:®725=Mے{e=ϑQ|**BiKZ9wBW108ĉӧq\vt`phKxjfa۴%K8r}t(=Ą/o6.\Ttp#O`IK3^}EUе;yБ{>vuГGM7i>i_tX@%I[?yݳ 4֥lJ$Wf,+,덇_'' Bqs)z}֑Jõ;~gٹYB Nㇰ!,}s8^ nibYҶVN;Gg Fiin䱗e )\îv / 45vylݴ܅*!}"!|>55>v@ ^% б|KZɯ ++"1qft^fJv_ȉSxu.iadtg."IZJw9ڗ-edl+TʊLŧ:sQ_?;oY6̹nږ,aU45>{7v7g-Fzz 3eÒf~3bNWmI slbwl*N; y>|:~,_x1nܾ˖2::3g9~ ;m1twӶ!͍ęsܶad™VM39aYc7RS?:=jF?zv2@ۓlk*\-5:,p;})VS30'߳i lho"{g~T!=;n~<\evV6gј¾#X>G=|/'ff1Ntſx?Cv' o{D1vc8}w66Y].]ݼp49c#QG?z'od-{~eqΝ/f^W@UB^ݘ !,fY>Ibxp1GBBUUz{z9!N=l(3\B:!:/,\ώ߇бbw χ2I?080HՕ].|>ޙg6a6F*K y|Z}TUU*:V,gۖM,iifik+.磱ѱqN>[X!q̆gq/@Me,kme.V 06>^9 ۷nS >v 9I_f||bn+["A]sѱ1UR RYQCC(BOo/#kV%I"\=3Iƈb\r˽WQ q C\%02:8?{u~:w#6  &`rA$A$ QEJrMO+W+[+og^[򓬧dٲd"E3'g 0 rNnt]gz=ԩB=ֽgv>_\_~aiy% sixo<J|kfSpuj '.O7[5.~M41EVU?jKdth#o¯^?w{G;̃wS^7#b_W{z X`V$YB0GO./%yD7+])0:^|e7RVW3[jPc8A9FD:C=deJR"(B0$ e9 H! O0DQTU BH!)>-Jj$MO2|emu@X`X~ `z2<ldh$b@ @ B!Ddb+$Ըc#a"63pC^,3hԹsx}>= \OfXx]ʼnn193lZjtt:0Ž頣hijSIץvmfnRVֳ45Q9]47( >  IlH2M&N۩flb2gx{Q]UEC}͍ OLbZ۽ۙeiyϊW1͈&vwP_?`qi BWv[ $í3œyaU''w;#K|#  ɼn;Ϗ GYë(S_Y*(Š/f…y/VUwϟ?]YLT92MȎkitP*$%Iw{q7psꍳL,p8#7ﻟ}ڸqOMxB!ޮ6TEA8K-/ѵtM-$/qqG6>:)TF„:G'43Tۛs biq /@nl6+}Ȋ̎N*\|TJJ\tbtx3h]P]SlCQvtĜ>;=(,Bum5:5u5,-,֌sٍZkbF¬q~q712> {eaq+Cᆈ$Ks{ŹCHU57?p WG+=v;n/,p#2/ K45l^EA«r Upb>?'OUoJ́gn 汼LN0<2ƉgF>IC Fm$  YzYz.{<=~:ĕaZf{[g/\dfv< 0sx<+,.-s ˯HA%^;zB*++y)$IbwyeOZ|>"6<ډ7"2;s-vKK˼q7d FO]_q";L/?u:p+4ՔQώ 2<Ζ*‹4TR]Ao0w^ڠ o!, YU42ݮYWyHv#enC3q?ʟF,5pٽo7#㬬xٻ/.tHYEWĻeߍ:pYeyaaaqvv;x;wp8hjmAYh~qX?Ϣ8{zyW/"zv*KXVZdo{-=u|CGBnﻝynnbg Gh+?=~+zw GPɟv_ֺr&?#iN v wM۶ ee_ =u8 N2:ڹo+uF?}-pW~k2߹KW_\%˄$$s|YTU妞ql%zŧ+ɻ Im8s7|.qcQNybqS[6h~X,qaY,f96wDE^/#cLNъ(5́2ruŅETU>y-I&]{vۏUbeth:vv$+,///KwN.3?;匌D k"IuhGrǺ:kW y Y/F* T+Lip[k8eO2]"/W=|θv_4 ۓʁQd `6N BXq/@呸,]V:qجyUǦyAvmHy}}+KD ]lwӍmc#p+Z$}.sa! SD4综$mȑȢ(bEs 2MٿϲU о׋wKc%&hmk!J+ww^PegX\J&&޷{] MU0抍CKR-'a:YgiU$Gz結ٴ[B!Tx #Wb6Fe=OI?Gc#`pxwE)]ۛ(q9Ĭዤpu_HYz>kCYe4)P%* )DyAm@>@,UR׏Y "&HeU%G_=JKI/ݝ,/-p[Ÿvz imkѓ9OUM W-B0WlD-L*a5 9Wz3UYϦR >Hmf e.] )XF*^@X!1C^=At̺\eTxw)*ˈCg*;:bUM*.;W +LeU%.ff,GEZZhNIHJ0j{aǩkXHZߨU(\Ȳ"CWEQ$(ȒkH2\;d UU k_`/>VlVcPȯ!3~*/|]񛬋0 2!0LLX93UZ94 5QMF6:٪gyOUM5u5ki)X-l?2 mfmb+L0˜ȐBAd) ]e)K(@$]A!}\tPdP\H#Ue|vZe4-dԥ|O5W:JFCm%"1yp&y&%8[D&Tx[rd'/|:cg&O^ϰdX}l5nu ?YM- ڹ ۂɗ/΅BTl@X $K/}A] EGH',$]]AN+s+"8p+!~1͊_*#aBYգɓKS8WHyw8١qPG (>?`HU7n?kI.tIIzV-`]{ѭLs&;UF!}C]O?q kVML|ͬ@Ü$!+*ت 0":IL $Y7D$ UU[Mvݶ`Ǚ33]CPUi+:"qyyy,,Ԧ"Zͧ/*&FQ6N]L|9ȫJ #1qS[6h~O|vnu9kjV(quEΒB]t())d>Svٽ{w> NNL:ooFAI{=mԹ]< M5et5WQDA] \:2hgtfC3ܼ7.\ESM8>H}U [k8;$+ܲKó O/q6V8we;axnZyUeroo {<6ϕie$7tSҙ!ڷUб QrCg=/OΞVL,p%vs78G[oVp]<w^2!sYujr0[\%dt fS&<ɻYYTVƎ'g'9%<|4 -Dls EVPY%I¿D((r8x y(6+6XzQ' U(^Oד.zF^[Քr,!Yb*_LT4Pb\"!ErǺŸl'犢06=缥B|!eW~8},! ,0pe]w! EE?M^'2v= l"WWǮq@—(a,|2Q5l'bemaB IDATN=e+Nx^Oד.zK1؛)mU~u%e]|m 7,tof]!ڙ0LD3$ ӠuU5WW^MВY\Xdg-dkz@leشXk"yG 0``]8qkd]]ϔW‚ 073(mW']tY,  B,Iccl8^]=ΉӣN 03i|dWV!t)$\x-NވC_HY[TA;BQ"N+WS"d qh ))ZN6L((MM/lQiBtPY_BiY {c5 e96q4 ť--*RHBQ@l /(U$I"p)$1>6CYQ7i~K/n4/q5_^Ic=fzU!q_!a*{\wR5?!!- jy7O<|y!?>VyZSM#^!BڼI&S/G*IʈCPgVh3y pWcwؙBPU *ihIa~v^yU"Dgvk=ʊ)$BSK#/EZhlnqu*uꘛgdhAyE9]wt~0;$=7XoJ%\O/ [eVk$Co!, /q7HgB T#)ކ$yF„:r}ʟF,8`J1)+/ɳ4639>MM] \  0?@0dg,V VvxBρ}LML3?;OM] T+cr|FV wt ~7S`ںZ9/viM%8zuX[' [0` /P˔wt=S^6N]L|8̧dT%u'M$M\%)$U32}‘OHKeM.7's`{v3?7p`Y Bv;%)'2`Sdj͆ja6ZyK̥ flv|tyyIDvfiqoiM%$mɣ7zI>1 gV㺳ZT#QCml yw jc%S5DC8ES!wȩZCHRA]Oi*fٻbb`۰;L"*`w8, 6-6?PM-44ncltUQs#Cx^@55ׄDhrzn"@W._,$CM070^oF/YdSu|[' 鵑t;']I 5 C|G/<3}$)Ư % &_Ss |~ƩY_/?>5ǩ/o.BxFEDQ;>gB5yşTDLZȦL.rSt\sp"Lf!M-\p /@cK#I\|mXm֘j3&\.'lIp()-ILfVgc9~ܿ#I-8g̗[?xW_?䡻oާ&xCvO?sipR,Vߏ? _/><޾O<e|y9y~#} uJFny7xGFb+e"!Erwj{FEalz:L^̫?>}|`0,K, B!@IT|27($ Gj.$6 r6*P?N~ 'ԵViGK:0`@Hh-0ȡ®&{|t : ñ^{(kyjO `Z0"f]ۛ^;q:7{/^F'f}ⱻȇ-7faC{s=;;(/u[OKꔐPHĹfCqxo[Vu~9]mtn$=>]^Jg[myȇ?}j7^]M|wO>$k'$277'.= ) TbC,aGn P!:â3ik9}Y7,CX1y kcA|aQ+DU%Smqp=k0l=K[%Fnw`9Fg_/@ٌ$ˬxO#F(nf[Q=$96DV~++!DA@QUf2N0$iaE=ک Ov*JP/yqi?6%_y JY[i[Y!T5ҕ-0/ -̢Iq?׸yY!\+GfUl>I/Hq+q*fe:U^%|mj8^C\Ӛ$Kt6ɏ [x {H` `eZ,yko7Kvӽ`Wh VGyJ*1UcUEG?!_ʗyG9x&TEt} a3Cnu\O/ [eVk$au^\y7>ru'*9QVyAӣ+Eu:e 75;n2of]Ab07;GyySQ@ @0d2% `!3q?ʟFl6+PQ)(EV81vv(%06RUUMNEE|eel۶-ٍ,*AaR&I%_?h2iCAyEf9| ͆չSSq}v&srB۬ E2:~VMr7pWUq"~[47 F\w4(C[ ugG>TUetrx2>C}ujC=ݨU`r7WHګ mzS!UTHVυ.;:pWY^ Ia@TJJK)+-f~h^i \*F)4*򅐮bAkm !2;w{v+Ҩ۱m!;6@ @`e%`, s 4675}iz!ԣ \+qS`nnW^z)n-< 4Q!a&[zE>tP^$e=^?ux{hm5toS8VfiOeN2=LW[=.^l6W3#\ɞFV|5uɤ"ʼn.آm $|-6AܔȽu .m|B3\r%v;.+2`!;J̉3HLII {snq\k.#M-M\tejR>*.^쩳!\%NGB_ %б{ӈC+6k>fλS#;vro~=BGX䒛s:1 IzE>t){3iɊcN<^?K+8ƺJ.]l2pnA_} Q]Y'iO;/oBW{3JeF5ͬX| `ltłI4^VEEdB nww3`ijm)ҵ+i{UZw=17r0GA Em}m紁H7Q[Asd#PZRS'8tV !}}Xلƕ6`ΑᥴogLOS]Y(t4rظ.L&?rXE[.gf -۪teKU˖7Y#a|!em>õHfQU>~Dq,&6 r6jnls-u۴F[ݦkcED1AAX^^p*L QZN!Xfg$EV5`ҶL@Ϋ'/sxGuq LfWf,w&{967tpoŌ ӼV|J lOfZ'P@Hzjpȇ/GZ%[`!L3ԓNV6Ϩ?!uzT5RW56j @^.9S3&_^'s 7ps399U|#|O}w]W3KnRo/`Q1"NN'(Y\\/˜E8Z?B°Ulj~BBZddy}'\ѻPWImU)_֋rb\263KV T9 d)̢b+JL88HwF„:G'43T_W1 )кo+#aI9y$KKK|k_)8x W^ԩS8Nn6?(rt,sΜ9CYYr eeex?SO= /׿uկg?Y>Oa^gᓟdLӧ?ΝwɱcxyG?ʍ7ȺX,4440;;,g6(---Zl0`pXwCmID>]}Ty4y7W!ug0c>M0e7.[!'PE۬ Ell6/9n7|tuu/311Aoo/|>=\LkK/$IN<9z(_7 I444P__feQzjY]C[ugGc ;zQ+8|h!̛O#Ɲk"ELF 2uZGUTݳyN)/] b 8 Xy(R_lhqq;^'?I|pH90,SYYs&].6f||v;1sS̗l IDATEʣ#[AsuͳZnud#FzE>t;']0#K/.Fm>|!eOZq%0n%{ 8gSwxoqsEVVV?|Çs%@,}rrOӘf|Io'x~A>y?]zH۝s@b;ƙ/8t;']LiŎbʈCX8~3*6k0?7NMN`Z:]k:q&VO6u:j3FJMeexrvϓ 5a/|mt-TEAddIF/$ J.H1\zɀRAUU<+^K_/R.ގѵgyëU"?9;w5C( c4қ;/NOwłhEb`$YFPΈCX3E^/ȱga)v#t!dڪќHp~vQ)wWd@(VoX+"xQ' Hǿ f̆g<[mF>#(МXq׬\S:oi i;eO'SkiI9.vWbX؇$I447( P "˄!Al1c2P`0jB!Ud2ŖBR8^ `I"+pC~; ]r*qGd#2(b6[)jAãZ s X,+ˑB2& Ŝ! ~.`HkQAy8~Vĵ~~aq*<2¿~综1$IJ eIQ[**5YqĿUmE/Gu">0άIgr2;3b FƐe6J._$vwή\aetWGBf>wKێ * LeU%-m-X,l6B!fgWxV(+/m{RH}~Ea.N?CUM;:2p `F|^ӾʪJւ0;$=7XoJ%\O/ [ekV2>97'Μ[ K$_Y^u!8u! ۧ>P' Yztu&gT3)G<_YU,KKDr w^D>1VǮ=]WTTkOU6l6{wq|^09>IM]5]wbYnO(616ɛcXX_jrM=LfM8t*7+9];Y^^Fݰ@($!KilLgh煪sSnɣ dkQ0leo;wdº˕p-@ryéغjhS-㲧hHe|B2|)j!L!kGsKJUn[=0U.'{vsYFرصwgNimoE%8KK,-.pWb۰;.]jEetfA.]( 0[,fADdL' fl1#$AbPZV @I{,޷{M[:hx=~ͱĵa={גdE0p`֠M۸۱Z,,-/3xeʊrvSTWUp8Cq!^}ݻ^j0;?W6x>v/fgYi)>%lV+gc=}DAGJK(+-!jRUYsmQđ L3h!, F HTW v%-X; xVPUX^Z}>Ν>OSsK1ԱSw3?HIƖFfg±;/v=N:ZnkGcns[i>^z(Nw<ʊKͯ^=|?gO7 KjeϮ.VV9~Ju)fpsD0h;^LH.ȦL.rON((l6oě'PY9 N?Mm}MQ9y']S@U)F K2 LONsy$IB@^L&]Q~_+G;+*QLҲ;OP.v~eec5qƧx7X_<\?#aجpzWlRM׋^y=w^O_柾etmęsꪪg8s"$RdD_,{vgw7{wuѸzX_o^UUؿy>Fue%/xXܸo_g{ĸW< Օ- vu墋Io̟uּAܩM'/WXMXU7MҾ##ٵ[Q!桮&ǠƖƸamum ](45zڬ"hWcoKJ`a2V$tX-ޙؗTH7Q[AsQ' \pX,GvtS[S GL {v( %.VK+J( + KdYa7!P—p9ʮ,,.$D&1' re·dn~ť%DIE4FBCZm]1F+J%.U,V^e "IEebQ@A;4A=NPbfu:!a*m6B,/. hފŒjBB].9 mM7z!ףG 0p}ٵ3BF=LN9>037ǎvFPUGwE8 oei:N@ O|wڒR߹'xȝTG:wgg9q,{7zmT295n O9% r;))qOeO9ǯ1j/6R¹ǥMAtFo%Qu!'^򅔵("8ܬ:59Vl9Zxݩ2zGyL38zYTGG[@D]/E4U8(J8$#IR%\N4栟pϯ0l=%[)4_g9u9Eۍ(L̄qdآ2ãcRr195MeE@ `dlҒ2p:HHX\\]Y^=U&NLMSZ]T&-.2ִEA!f3tǻ?aCӹRd''yk[O;U$G'򅔥W~qzKX,pk"I2PJ$97XoJ%\O/ [ekV(P_S$_ęs$i5CǒEerz:v<>9TtR\lqiťرGDe«}N·uXxXv/-{XZJ)Y.27=̺6!)08:qLg;S5~OH3OME۬Gc+mN-o k\Ka+ x;y1kf|RV!x=[CňC'ވCMQH!8d iBN|-0ApfOH(]'{$wlG1q-}b`={עA1JKJx_⏮#n83zw:F¢`!̀t Lu^<VPZ[F\`!<~?mH)S1͸R( IzE>tY//vw~l6+?ŋ,/{2=2 YޏB0OD.qa gZȦL.r3-oy{ 8gSwxoqsf͇㼼)0?\[v#!TT_ejYf% vԖn.uJt;']6Z.p칄‰2Sz;֫.:Dl4Kƿ7bqL+氽 n 9~t QJd?eb$ת *WfC%PdL&Y2suZAmU'ol`뿍d'3|.yw^D4&fZ]#au!4'IWL:z q&#\ ,CXVQ^Y(u"q}W/oB@ v%Inp8:X.9SW  y:E..Nk\h> t:qWUp:p:=v9l 2^~?Ēgر]m6*n3nO3̸<1 ƺ^:7LcE9Vˁ\/r y[5iDz;(I#l~e^㑕":+ qWսh!$^Q+r{^OrX,:LONShr [oZ@ hioe]:\\L, 85YK$ztH 1n׏=$E^/ߠ޸80pM#A Aaa~oǘ* j(~ utY2뜏3sny5&l1~iޔ Ͻv>q>J #uUeDCpYeU!\f~ qß.\8,/rɓx1-hbO]Vmղ^+]ؼ}3331d2FAAlf >X\RLCS=], ;}1%=AuJv< >}\)WK% ID>n?;x,. T2A1ȿ'34:K!tFqu\{#oԱlX(,HT/?? ss.y8UMFnL24:a 9dNϩ;hY^?Q]'q{qb|ʁ( J7&f 19bjSI9zIÓȲL@ rcbi%Xb0ȍYFgE$If5437 SGq|Alj%(IO;a06,csb׼S#%-^XpqdA97x5j"Q_=>n^k|$2sm-˝WC7PPSrzBVTQXTHT$gO#(vtdKWt:Zi7qE>j2,NDž oټyG)XT`lohl5xUt[dy&K:K}2%f0)).Dᅣ(0^?z /Z`zؔ lnUUn[[PA ^ldVKN226^ҵs`5e߹>.zFRMSm,X[FmوN/P`1wG+%E<'x={ ?F*ˋgfVe(QUVć7Je ؿR~QZ+i.ぃq\/7&0t :vt4RfOr}뒌_43HpdlJ< x}=>ƦT&@!2IXLڿ .7 I_[SnG(uQY F'x5n/c4֖CrqV.ގ Ab>I\藆6'/Q\TzR "rlE,f##y{2=k4VTWuIOO.."ys'C|>;1'=Yl 8.@چ: xn!0sB9^:ɠN^yV+F~jjjCRW}׆j+<0Ԝ^t4S`6hGy܄X14:?Mfܬou<)nh_VR,#^_Rocjifpt7[-٭tb IDATN#JV 6t %bvb6SdVEvRSal22<>+ǯ`69u`PB J"g/ A$IF%Ƴ/Bױ ^z2 :{>,30NAI& hnzr 5+7r4J/!̌Ϧ`)q)y|lЛLEq'ukA3R D7b\:r1LTFcSjF$f\m8xX ̔pytT!NeK09=}YجLϺ$Qf25n9 B:^_yI 0="(Tٰ '.9_1>/wNwgl6Qߩ$ILLĘA ٔ8]W1k!##A?ee456!TUWQI'3$V̼l<05tgϞʕ+Aŋٷo_Ǐgnn={ܼdJ^{5\.~܉'z*::nF#(_ywRUU+ `9x 6MD:wIU4Vz iJkV:&!(=M+=Z}뺅Y !x>,ky 9=*BId.RZJ3c%*S 'Q"gVNp\1p]$ED6ɌB/n pxgp1Y{gx'9ydo[>=K{ߣ/!O~~3>}/~勵??b~~|#S7g?'?ɴt} P__G0zVPUSO@kG#y2=O$Z3brP%=/ysOh0,d$rȨfIzZX*Øc)#23nvn;BZ|ٷY2TmȣI+=V|`߾}a 9w> [nS> .](y/~`c7ngv^y7z{{˿Knܸŋ?!~?cDQ&333w}<JkVuc:iҹ`hU/ܪ^ ZqLOe:kI90:SvI{F՜r >Mݞ)>u*P\CYx)?9ʻi@iʐϩ{$llM|>?C=O?'?Iw]^/ccc<۷|+\x~!_"q!x ~aJJJ@kk+'7v?`׮]_EEE-y~tww355ŷ-^|E;yT׽8<|LMM0220??ߟm444ws완ϝGe),,Ly:k|.|.|.鲬'Gw.qqVb7%>z&|6e ZB0 >ЁgRvs!LKqFCsUlBQQ^7r \.&&&hkkC$BK755a6k`0܌N;vuVN'eXn: - "8q^fggzr)V+{/<;wLv%,))Q`0(d%v|Kj'Sl J˺նU>a,ky 9Jg0P!\V$[Y4Z@6tiH1O~ٳgyYnx;x駙G9F P__d޽;nH9:]{z}k۷;3'xe )>Ķssss @OO?$gxرc=O}{F$JJJ8tPv3 X,iɔ)GkJ<2u+K>a|6e4 _tz=zS ұTQ!)Byq dn`9tRՠc-(rn Odc#c٬H C}~E (Aff(†@owes8ل ,$IZ J&j2*d2inn殻׳~, 6mj_~Vv܉gƍ1TUUq=PZZJss3MMMx<(((h4aߏdb˖-qر͆^8;w{~z innfqB6X,477366:;vڊ=yOٻ{hooo͛ٵk۶mKێF mMyqꔓRkz".RDs&f^w6J'CqWW!diM6MgPe1}8)"ٔozV$HRv;$֢|D΅8^+=؎(<Ӊdd]8NӾ ^b8m"(uQZ^QLeqs k>Oq)뗫KB.!\.7;ney[bt&eA^O$ZWV8 ȔϦ\3CC)0<ڠV"T_AkBtEgIW.MFZT.Qde,ٌe=OI=|18朔pcx9.+ݸ]t%MC7p!'r 81::%Ln%fRǚr@?]=ű\>/ ΋ST~bش#׸tm1(-.Of14ej΅/,u121P?@e+!q@vny{Kl)!\,GJ42=euH/,#ʹrLlʺi!?4ΚC!+QYCj f/9'ZQl/˘-L$ UuZ ͗,1yŞ:%nDHc|bk=um:֜)M"S"g.F=ǿV-ܽ{=顳krs3O+trۦ&3>ٽxsNwlkMMz*DZ~ z]S.KJ Rüu5޺^b2Rn/#Cۃ^r bPՓWR'>k#|𝻹JL8ZMM?-Kmw&n-"vCH|6e 8 E(,bKJ`f:iE&<7-,|tRrq^j딈U...GLl 5<}t^bpt?ds[F{Qe%Vt:XSF@ R\Tj:j.W(/)BK3,z6eck-ElЀZL؊,US]igfMu,p ;:yc#[1=nj<4E&(tb4)[ߝ[Q&cKr׼1(Qh1Ŭ;9~p !"WAqFNHN|܆,c4x۽oYmG赮@:lDm7W}3D+#5\SyǒCI'llarvt:<^?M5eF0!(^V @Gk-#Su}Cm8^zGNuqh.9_tu iwt4202E)'{wqTnzY\Mkc%ӳ.s 7Nüw!o^Ž,sljKjt->0s>n>05't9tR5[qvD>a6!+0]㕋:Gy,t d^ЂlYvQ_]oG+uemO9iidúj,f ե hV TxFuE1V *Q\YLF ,&F&樫*aۆLFv z=uU: J2okNوN'{k vZ+qR\TZ(0 h4PrP^RD[S mt269=X S2eqK)+|´OyF8Xx1Ei,D|C˱qWzRZU:q WXÛwZ"mC9Cr|q 1qQG}?j-̢85vR*G'K o "VcӕJ!eLn%fRǚUG#c4O'<[}-Y~5 CA -^ee~3WAu98qWuʟ\d9=A+fcZrf< [,n%(Aao!t )𙔝܀7qE>aR\%\%\e5)a~cLj*0{J<B3(1/,P)]b*G2خdVyf9W.Gy$$ki2u+S?(}.3Zh 3DQO2vLCK 1+9b'u[a$뢗#ɂ 0=90:vaclE1(R^Q#Z8D8PJ.Gy6R|e*+d*/^Le-OP֌ <*>Zȴ0QZY MY7AkoAnH.aP͎R'8OԜAjir|dF% M-Mt]F&> ,351t173,/~?K"u;K1 ZI (qqB:i8Er߹nFGGedYftt۝RT[IVMm:֜dş:9+Ϋ)/d*CYKe~^H:QQ@0U*hK)f#Ht-кȘ8Te$$1/B"ƕb ')Bk!;ee>Ypr%Οd>?[gpSY);Ȧp=?0[-L ׯ9Ȕ_ɲVTMmQehaUDia bɓL~*ʟFj\ dY )y͍qR`Ov I5[y$)p@!ɲv<Q>k%0113<ӧOOhooW_gfc׾FKK ;vEÇ_G>֭[/K^y:::?#&&&O]]]ix _]w#<·mӟ4+a&;yG<~q?|Nolj 7?z.&/b+%E敳l]_2:9z*Ͳs?/>~Gw'~AoyBFfthBY^g7Rlʺi$ # GҍCq HkUb"<#23nvԷI54Q[_ Kz`2hM-(BoO/-;,͡OC%KL(8E^'N.G/=>Vs-_җ׾Ʊcַ?qqy.]?qկӟ~s=qrwyy8w'ؽ{7gFKK 1߿?*_2NC__|͓O>IIIɊ&huFVcjΑNc1ab ㅣZ;h)BdF玌싧 "l3Ͻ맭<]Cqw}(U8}[[w_brICu{򟯝h\[΍9*Kؿ'?u5سxѨ& (1 K @ru;@qGt\G ;:2M#$!2۷裏Q:::A8p@Ӷ{n~Az{{`ݺu8pN?/2n[H% IDATy:;;ikk~rx fffrv؁h\Rݗ!m]W%]r%]V+aEId PYf`[;ȵ J8z9}];7;.8頮ɺg0˖1:i)c. ݷ]CfN^裻1k8wuYz&ض}pyRŒxI2>lʺx5҉CrXwPVS+R9'joeQ'>qJtz9?vI,0價A!LwbpIhiiaffObz)ʨ333c0RQQAEEoۣeӟ;wF󵶶2<<̱cǢ6l@mm-<HNCtz F->tu>tu>tY >dRe3tX>2IQ@0(1)DF,Qϣ;szh+Ҏ/X;B8#XFlV 5e\62"2 *!2 5 :<5v6޷F}u zGݚ-G;Y>~a)>M}wuO>$?O)((h4r!cd655q!A`Æ l۹<#<6_쥳k/sFfT}>xNf8-场Q1+W'hO05B Jx}~Q>e56T>{GŅe62s Mu)~%˒ 7~9!\f>ɵ3S70YCp2ptR5LTFQWJ!\9(!IA1HP "b/ X ܾV5<#4#TUcR~吠 J<-tm\+7qSYj9ˋT8~[p{:AolfpC3TmC='/\NkchsWRldrɵ ֯f1('Ǟ?훛cptrj(wlkn+Lll:l+iG;JK˅  )AI)+stw]h4dD1H ৴&b;cdvIP`:D\b#bƥS)'A-еC@:bfv>npRHviLj]H):qeLh\:$&ney[bCS8Y$-C  kxޝln ; lE,ȱf k)|d^^BrW::.Zh9b0@UuuQTq1#Jj-̢tW.UVR9&;kKDVyGF{ƹVsꑱeDU9 +j'ϔϦOC8=J BZ||E,W SKQS 9+G A.yZd4:GIҹ[fI炭|%dQVZH'atLƝPM~ji7B~V.aE%X' PJ[ ̀E ,(TA,Dž") A3W7|Ds(S9'˯Ѽ?wQ'GuV/(t ǽͧ\E-cU[N˞ ,4HP~ppq{-Α.h`zk*[gCb{.=83U{*Pw~bR WcAvܝ${ ˲,ܻ_D|:y:J#uH9ɻ|wz MYΫZQPo.P)(R9'ʼ9,B@S3);ŃA}o.b [wX,l &u{b~1[X[x8)sr ]MP~†7.ЫHv#< wHX ZrGz=GoAhAO-PYKT꯬uRꗹCP*~Y uOtmy㕶 NcLW+/$}[_m[)xz'e3W%!!e$[Y46 EoV+9O:6v-C 4X #; E;:-{GHL‰\ LA}2xk& ,u-]XKoQP@c*D5#dtZʺ%0s>n~ q qTNZD*N2Έm>ݓA@ `zr L}S#^A,3bJJ>a`ZlքNP5 XmNҁ@=\âXH&'&QNu-Y$(fi\Avcpd#G?೟tQT;~I毾h‚|bfE:NDrTT Ł 2 F!Q~2,'AR]3EVcMJ݀,qtGOzm*dߘbJ(?21KDx"3y~d rJ?{GskLLͺ"]G_t$fE\3Z //#rdʯdY&I6z[GNc,GE.șg(//A`dpzVz6oDvֵ5S[_,SX8N&'&l6ik3YQUNSs#CìkifLOĨ1LcFZlElߵ{ncr|ZlܼĆصw`p!t: mw`43}6l ?)UqWpE6mµkאy?~r?/Ϲt"N|/@8Oh!=80_/W_o~@ K{ί}7z_$ՎE_Fΐ;/ZVKTj[aMvl#!܄G.W @-kIy9|.F+'pm`"1͹)_ִ[Y>f2zHIgSM&)S%K-SPˇ}t`0ނ.g^KA&)-QZ^J>&fgq:hЎ :p9Z A'p)t:bx]z}sTAEϼ[ @oO/nhddY331(tz z@@:F{q]Vї *%HXQ?Ud7_?[}nyΥo3NϱGh_<-~f-_~sn7y(y߱w>z=7_N{_1!Eh>H!vXkAb/.m:֜)MYqCCvq:'/^nwoecKMRۤ!̜B>UtFcH08MU1Tb@HOVj`b|az=b׋h4v%Id2t:EVf0M zz{07cg$k.޵-~r"[N Ӆ>@+RRZB]cpZGoYih F_ш,KydD79*R=Ĥ=A6tJu,Ç#KLGJRn/LUuw}q\r0O=$o~ z ~F)**׏q*ϝҲRزmf9P W9n#7tS|QK1/Ry?$n})VisoF>tu>tu>tYV>(YGOwst7={lXWjZ`8ٹMu9Յ Zoxd (I|oG*6ٶG؇/ %̓d1䬋 ٵ:6QQjcWw[+{/oA )4x74ɵ1:Zj8YǼ-J^,ZPm.Q\!V!Binu=>n^ kra>a.N;Ԁ44u G}S=:&kW{d1aXX,u:JJ(//c3/FurV0E"*+tQ-;,{:Ѿ/QY]As3qeۘoQV^Fii z̩1 qNdZ?t b2QPX;zNGwU$)ȅ< "NQuuuuzikoٳw~u--u8w6So::6O}WF J*+h0*D +6 rji!1V%]r%]VlpR}`_4kJA 4Ae176rg7؊B yL@^OSmu }塡 {QbJ;6RdSyt7ߵElfOar+Yjw#'${D>anC1Ylܼ 7\S]{vŌTlپ99l۹-Z߽wk.mۊmmjt:o;ih9SSWul߸e#\P R^Q lbM}"Pb%k N]w Aбg^~#\z;ﺛD.uv@`4ѱ ;ذog-~m6>07;mx>-[dߝw޾m۶ @d~ Q6a./, qwMV)ٔC9MY7 8<>Z}Jo2aHx*SȐRI)l2QYZƦFqz\i+uw*eTY1|GNEU9:HC䠄$I A1CpHkwݞQ9}}}"5FL¢ɸ#B̡D4ڠޓPtl޺څ mٍ9G8N͛/ "333TVV*k:BKU5&s0A!xHg4TW? *Kmx"H0(30N]Gi2 I ,&ˋq8=dmmt34:ͺ Z*y5j+L̸ػsWR,6Y zn܌㧳kՂMM϶ Z˩x}v{|4T2xc>^& J9\6lɯɇG;JK˅  JgP$'&b6E&l8]W1uQ )-+)aplЛL9ukA3RG3yL3.J9 h9"DF3:l,D`m2峪qv!H8CCJQ awMsꈛr#Π̂ʍ+"0N)6HV­^l"Wma*ۈ::'᭲! ?x~5 ܿ [xGVxY^e(k)|F*9bEDͼG‡6VWN%Ibdb"F|6e*tuz]ܵr#aWUW?EPP8!٩T_͑EYXfTJ(h陽RCZ0mvMygjձ#8! Aۍ^)pb>9B / ",l8y Ң/hE5S|*\)mFq)JkVڽpg4O'8wz/OT3KKZVyA@->4?byxAgh /8S~%Zm<߹}_,۲};8CCBPZoiO[xKiYZY(!%%ر%v[lKdJw9w}ѕȚ?ܙߙ<M<*[*Qm"%[J#J򲁏atu`Q>"$$@I{w+"3 `RVluБ>L䚱C9?ּ#䃚&ΧSքSC 0r$8qćKDk"d*k%UB'"MGsWhQ6EEp\u jd) S$.: 狝#I 'H`Q#]г-JBo|P5HZ|]w* IDAT?P |hgVG 0١*qL:[ŪBbϸϝ:\h?n$ I>F2Ez2|:eM>A(ԋFP5*ΐЈe>ц_beVяWFJĘGJL/j[1עbE"L&.݆yHD&RqΔ # fg"FˏFʺȴ.@W tS^O4>ϐ.a$}P'A َO,P_H]xu=!-y?~4y'xPB%"nUUUȲ4"WZB[Dq >]⽠UhVN^Kd|&3[o"U~!-Ɏϼ2)!j"ʢIl'Fk#ݦ!;;;Z\P:СCGJQJ5ZYW;?κqGϧSքSCh &E^(UC+T9c 8#>ٓ.7'?8t`0`ɂoMĎC $EZ6 fZI:spN>Py5F++ET+Hi3#Ev@9GspFçSDvӍ0tZq~fpFNyr 9?OfT(dbjT{e+y<dY`0`eo9d0tћn鮖{q%s޸dO^J'ǤE/ 𙎭7LʻZ<#ΖROv M  $)YU]줃O@B8aD>.QzAY2 o!b$C;݂@ϣyV+J9|0Ө,V!L a/1& n1)m!baF!+| z4qD{Gˏg^! sp"\d)}|TCt`4(..6d6lC=,\&dF2-uBqG G+#Ax)uB\N}Ċ믥KH?iC>4"x?ҡSh'+: !"j@䵤y-u^K)js*" %,nտ\Z!V!BmH5PDjh@C}H*{KyȲz! {h4RX6ÙgB0wN6 ޛzd1^8//:E㵪W*t:%]kIL/Sd#|cM$>Ӷϼ2)!j-Wݶ{(*.bk, sW;Ġ:tGiU1*|s:nFCD1p\Œ G)ttr YLR/xn# 9;j\.7?ìz*ˋ:\K!LNYO!AXDÿZRk }Y#P*ue*JKxmBĶKFj cCuҡCR|_>_tƻO7{MGw?|u:EMe)N#4Meu )-*ѳWKc̩\߾ʡQ\_HG7pI[bfU=w&LǤUj#dGp{/F,><tK;|mz[x9(`֌zK=]}Tb4)/-dnsvS-8.εubq <:ٝL{je搟)Nc?`=Fa^.O?:yq'eߡ,~a r99B.ڹi*"CnyΚTHkJ1Z~<47qzB˕l#w!N)fr<?W 6)l%myԼ1)+k)B:YVxIy#],;FO%ot`k/p/ް8n(9 wX[{7_<Y>}O|%fŀe-dfc>GmeY@N3ƿ~3\to>TW~}؝.lv/ P.3&g&pHMq)k)! 9lB< Dl-r,(/"?A*S @`%bu, ь)LnnInF=qJVTY),#;L~nvAeB09YV`4ÙuTSY^]@UD *(fS\ONOfĤYV !Y0ш`+.gNSGMAF)+@}5$zH0)ՓI,t-"$ C6a1JxU/e 7Ai H!h|SCTf:t$윘Xϗ|fz.a$ݴxZE뼖t:%]Ɣ {h0p͢lycmop3>of5&]<+P1 6L^G/l ! euL`3[b౳\™:1=2fΞw[vr쀜<6OWϽΉi>r@~F2IL[%ʧSV|Xl?.Hዔ6Ӽ !:ht: k ɋ]סc<`2b5N2S4^zkIZE뼖t+w&#uym!W]tY|ꣷc;Xbk_QgMcNUNjY|<&rrEsyca=w\Ǜ{rx w3! />ǟ^y+{nI$Qx=axxLvxiT#HzEwWƬ,c:A&V!":1;+6\uL&8]l6gq0 fa60͔='p.-CTUdDMޮTe]t VP/N݊ٮ.>:.ڊqJ9"&U>^Sf FCص<EiYSe$5Bd"jR IV!r"TqFZu VLc.aJs1! hҢN:tБ4FtSV:tI'oQ (0"y>8 NY#ڵ O7"@CHdc3(޴~0c#.KqKħ*FS@Q(W4#1Xv*Jʩ*/ {Fp N^NNPzubo03˗NJVe)tlgVz5x?RCh CT诬DP,D칋;dѼޘNINK4> 9?QzAC!_+cZΛ˴0 awp -Y]%, ^|E̝ÜsCLFc80 B}a"?0:ZJV*+~znQG f+u9"J3/IR@?aV)@K*?#=3Lr98J*/pJP1rKR̠4 :q "Q!O¹`999^~K8ݻoWw3̎FĀ^NQDƆ.OZmCQ!ZGk)%XjZ2?I|"/D5a8CEÄ!XDdG½?/uPYY޷GJ.\w'/SXXvJzp~ o3=wozcc!PQ}. nF=qJV*Gq[okV0%4: z8B!km= CQXTHVVmѿNsgÔ)|fS'Oq-qՍHĞ]{޻3o.\044[Ma"~xpcyn}yF9g/e]=p(FJ/nFEF!fN'=au:%]kIqCO&d3pd;>·BC18NÎ+‘CG8reee<#:y=#.Z@o7x׸M|oFEe99ٵ//CQعM<].7zXp\(B{e~ik˗,dpbw2^8//:E㵪W*t:%]kILOd uy!%AN^pt@wQqǻc3L(dC_Zi1Y26-Nd0D\(F~AiM&! $QZZJ]}\xӿ|/n毿Wlٴi Ԃ5: I2 2\{#6#8n69}8p.-C)!JFQb69pӧNo~w@28v&~a{M[pn<O -CZiiwx, *P_×=ヲ/*hS':tH1>~a;+79,Cvm{m6oeFC%o:KW e(``^S}Chh0p\,hqJ%nع$nY=4NS1?uwh)C%sX0sJd}uf"!NY#8#>2puR$QR+T+QΓ 1g0|Dv̿S5կN<'~ !%.^VȞ]{xah_";/AyE9HQGdU/wQP?]e{O<ci7mN\K't[%Ig+B~ !h`4ch0O0FV/d ݷ.%30g70'/rE>8ξ#dMgk4RSQLeY!n̴ n~Ʊ3?ڊ\O,H #%e^X# Rbd!O/ ŧSDvӍz<G&gݯlg4tNS xt7)sYzļtL@WUE^c5LGW&SYG VcR*ZWL[]lqa,ΚGV9шuA0`;yZ;↥3q<1\U4ԖqE0 |xΝ}b E[pusY8+/VP&d8ck!.$g%R0}mI}.̷,l/so#wWl$9 AsBzE"־""'zdE/&ee-EҡCǨ̛TrU){8d%&Ha4|9"$exW`iAOb|02AFEX6Bcge! TYV团_IH#cCUtU9M[Dƀņ_İYSpe&#F[V!ldnS-FABG/FFVr9YۃhhhZE}u)BsAV-G23lw|t\.,\nY&SBeԾp|$Wu7)ՓI,f9 !a41 zB)dOPMiCva+U ̘MƫX|Lm~iג.Z絤˘yxA[TƺU *Yi4JY2gHZC:NYZ ),,ᰏ!q%ɠѨ!oM&fsql6Sy&Spiq^^ukUTx-u^Khג.5uh.d%'E>FqrRD:eűKAډ8-Q$qOC>\K't[%Ig+BbRV򍖗FqS8TӪ. ]߄+RuEg"H_+'QfUk iՄTBU☔ m |м8iǝ~8禙DH\*C>9Rϼ2ͫ p U!#+ȩS,b*jH;'.ZA" ;A[x~ AmRQJ6)y cRVRn+:t ɼBo^k\ gLxoL[1\ƐA^}htʚ(<?8w8zh# 9Ba"%Wkć + mm'Fa;m=RS{ IDAT#`^/6)_R)ayT}B-UL!fnġ*qL:[Ū|Pl8i';q[I#N:\h?!sƇ"RHVWOO#mj_hq3zK^Khג.Z絤˘}S! /eR% gjM9xdE]n߾ӭBp2 c_P\?|ɐ.sOM((7uXwh+Ӽf,^{﹗믿>1:;;9syp8Z6p]|PT\eJUM̘5cngOcZtL'++",1gkxmu$-ӢN:tБ| rX0,>+r| 5[WvbgSk˹sB&̬~0)5%luːEy jʋ8ô@?K|Ocvoa2()gJ[(lG\b9Vdj]ϟܲbϽ.Cv+KX{|֕'e㙗@C>&__#|r G˩! ėP CLH|!ZRk }Y#I&2"v4n+i@VP?>p#͍TTUSR!b%?Ok5h\uҡCétnǎ'T :-x dM|`4H\G^n6w^̟|&=;aS˶Q% (U|L61[<3m]4Mcw,g{g8~h+|xb F XpzQLWcRnFK >8jsң)k" uMF!餮 9# J.sdnSAr 9?DSsL>*)(+/&/3iNlη!Jjjq9IpF*J }4 efga2xr"?/G&7LEiv۷87S[U5 :η5@ME1Bx/Z45ŜCQA.FA8 t{p<,9tͤ0,s6-ikܹj8A[ Iü"}1fNYHLgQ0ZBгGçSD <.wXzBu/0Z|\]*(,az[8u4E%EPPŶK_hgyHB0cf#.~:$J!SMR;_dnJCFM\C8Rf0:ZJVj!NE^uV`6yG I8^+9NUʞ9w-y%U&tmfcA~]n79Y4k +l[kpΕrp6L8nz#׌Ӽ!L#;~JZ+|?^} $0feޱB"yStʢ4{!P9FoYy§aTEUQm"k( GFx<ۃ!?/˖gZM:tБ$6꠱,1nB Z/3.pĹfMh4pL;sgrr]=VP_Sʉs,YG9MJN^fʲB$I9w9xdsPYVȰ}Tx$iu[B$l-6*J 0K= 1B.t2kZ prsXCnNV V>cW{,Ue`(Vdvu9uG(BǕ+aŏdTt2/mI p]Ȋ$?dFd/^[{O ~9r,0X5hszIAVJOWV" %MGkѾ:.nF=qJV*G}ElvMEeᤎHZI7SQR-R+fى!PGB8LQ> 4:?`o~5\ŋ1 X,{9Z/^<#Fd25*ƐP)o9 /vAi D+#g c[)2EZt|P-17p-RHc=_nGC鹄tӂ^qOhAZE뼖tS^00|%xu/NY GŨKŇ KUs"4C8Ba;>#yٺu+wl?Q]]/K6mDmm-/>P"$CBPEA"$#7TKnAm@ }88biq^^uI zג.Z絤y- >eltlgVzWbt8oضm1nv@CmDX2dhmtl\ZI:RBR2ʺqEC8z>&o0Xf Sҗ9I- C&/DvB+T}i|43y=YlvMx_逺dZV{ȵxiQ':tHK:C5y-z!TBM1>&? 2˗/G \M7uCq#܈aCRٍ[Ou]-=aqOC>\K't[%Ig+^ 9!3[o"񙶕p|:eM^t/USgzy&q>!5^Xyc3/8!& o`.@`00͑DH]Pҡ# >-I&x?O-A:vJ䰕S1 }=Ibr*=Ђ:D8ͭdE!hZEQd+\"ۅ"+ AeuuHQeB Fc ֡cr`0`0| 9&aËb@m_ePx+{z/}&Z&چ$IFo3KY'\'δ A运~;71= @(}7CՕ2:2![Q, 555( ;'uIюwH-2ю%ӊȃtʊG,['Iy-`L!;'I2q]\2zz5ȕ>Mp80\xRla1w-ۏyL^OIIqJW~Et`2ho{M),,d2,ų1e-hj:jk_)@$z{z(** l Q{ܢoaxZ E~r1tSRR$Ivs&++ V| jVc4(2de1(:DUU n#`0u!p8x?vۤxvuuƬY())ɴ:Ij288Ȕ)S>ɓ̞=;-N8ܹsG-KPQQybjR__iUtС#.F%\ҍmBpuԽ. 6pi+n_Â󩬮#vB)]HYY>99&;t zۯ5KZ[hsH7h"B$ vQ!,/? կ {&Hc=ƏcGSi裏Gv].O|STX-[{:~MMs1]]l߱^c`yg%"ڸq߅d '}8~8>v8yW^~ >O]v3w\*+*gMuu5<\ O7aZXxwd2q[E`pkJOO˖-gl۾? wi466RXPQuzzƶml6r 38<;w ;'nj_l}eXq /_+ʉr~+*%s9ض}.5´ihiiaΝsmQQQ$A__?2 7\/(wޡ~64DWNo߆,+Y_gϰ7).*n I+WkaZYj/Vrrs/~oi wB@{%o߁$5.N:Ů]oRVVNII ;|Cɶy&k[oy|-v܁d[oVwzʪ*~{۷3lio\|o_… x7Xle1ϳcrrr~1vMmM-k׮  Nv;НL6-FeneErudeCeYW^B-nD L:===f̙;nJ;=F#BnHlxn +2HzOy.}w; ao|v{@p:{977Q__+[/s Z5OǗ,]0=byN8a…={ܹxWt"e{:&% ncڴiA/ɓ'kXjUg,TUUßD~ӟg>CyYY96rrrqc.;p\vb ,Mu8.d2x&{<OҽlS"jعŋQ'&sm>'t:UB 8v\.'p|_͡Cw͜9sU24,!`>7zY` w K}~xq2ś*=86 kxR\N6 1ա8 0ge( Ρ!$.Ι9]]BVQn%;'p_9"Pd97qa QpW ZV ˉΝ;70bۇepYQFVZx<260} =f!:6}}} !f144L_#jj"`hh_~^\.W@쑿'Np=pd21!N9ͽCee"cXz <X,(А|(njٿ?ׯUطo/Cspp~NpZ~_k/6\ܷY9~8k׮e]wa6c`p7{m~z//xɲX,Ȋ2r-p8o%0LYafC<YVؾ}{5Yv-;vmݔqw!l߾ΎVXK/m3a;ګ\r+0f``_w|uE׳krՠŋ[bϞ=bw{CEaȲYfiF._d`6Q]UEaAlffpO?Eaa!pgϝE ,`_iӦaZy뭷yZ,.n͎f ٸqWt˦M2e YYl޼ O>$\>s!୷p7torawq8l޼l׳i&zfƍ466b0HlٲM?O>$55\ukim_dŊ#׿~3v $ b> }޵a>L__/Bm,'o6'Od޽cCx嗱Z,,]_ȅ ~nFg˅_j9} c/?0Yr((, h0rP0e'B vJCCObQs![Έx<<[6STTAV^M}]N{7V+}#5͆A}|ƶBv-[,Yywxn&Ab\app0^Q{'{<. #rrr0 ]N 3s#G|bXʧ!&'RvvP_YVطoַo|={xJpҥKb[ozmTm`{{O=TjϨǍ"|̏W_?1vmt'O&n:k{r9),( _\NB(>sW?J0ZZ|~:/wvq-o=L&/|˗Jyy9_ҟ̳ގl;Oa|ߦ H|С>ßax IDATYSOqkhmm屟G /wc LA/(y&--xwre\.^o '~sl6d< <_JVv6^s-֭ ͟yYη'+;+V݉O>Io_,pBضݻw$1mT6s)/EZZS^QHܼdYs,Μ>`ࠅuw݅*fy +2[_~ǍoC|p"_W|[.^$++\ni. 604lE5@ۅ  P[W" -f۶m=nL!ۿ󭭬[w]W?N9wvt8))-oaw[oMQq1nP N}y3,]?}d2knX-|1 ki ==Wau3xGys.+p\ **+n ?}h4yWyOsu?^ Hn^~ 'Nd9,ZxGxW0݁dfݺP) S6nk?>x{NFqQ1yܸj.m6;ǎcђ%,]?{e-̘ ĺu^p8?< `X"Xt)UUdggIUu m<۶/|zo/#?q'O0u*?ZC,^VP-\$a C4t%ΜgFv~ܥuv! } ɻx"F 004$ukhZ7b߾[/o%;;+H) S5kv!7/Qf͞ɜ4FSB( }sܹYf> 'Nƍb,\ PV+W} #7rL̓ӽ#*Zuuyoūi#Go:o㫪=ߔ ҫ EOAqyqe7{:" HUtB!Frݿ?ι7ބ2oX|¹]nk*9T5 ܧ`~+k0r&&us̓{ƣw<$ pt 쵵P"sKNΧ&؄N.I(g ]~jb|0)SDU$yRBQQ+V`#++ݻ%#3HƏGΝ9{,wnu8Bnn55lL=j۽{w:F)Knn. d=]ٳga Hw}IIm̒>u'ObM ,\iӦKPXX@tL Q̜9h4yєp~G1%+; ^@PQQs hnj<# ,\ș3S8̙d⩧g%00IFF:' %K(*: ɫ#h42aQq } 2|Z}Q8ȃ3gjo8x 3gtkjFfѤcMllC>Yc ȠҲR"##):]c٫'+W|ItL Z<v򕕗456_444(+-moРAsϡq:dfe2q$ŋ)))̚=V3?ϰaC)**"$$|&H>yag̘N"=-ߖn2PIF-JE yasyN93'O3gWPpXPTP\\Ln<9zjJtͰZAסie:N.SN;VHii)ӦMc=P]UŅ Xj5-- 0 JJKIMI׻;6A Ih4j E6jh4墶bbb)(8ي Lš5ݣ /{XzVlAvKR6kՂF񴵻577Yaa4663g `0bV^]wMVVU`ƌxa 2e &5TBPw1AssaH* Sp0 mJw+ V>kJn6BƗ!{j+p⬃aݜ+ Lex)WafZ͉Ut:\͓g.جh 2deڵ#+`su֮YÊ/$kW&OL$G Xj%Fb޳5kVs-p*ٻgIIIgdxh?-lܴ wM ?/ߋo1AZ[[ihhfɒϸIOK #<1wn6ʛ*킊6r9Q|5Nv;6 ՊD2t%1iq Bll 1bӣG6 gР9|&9BBbȚ5!>!*6oDzzLB||||]_~`YxN&6md|?Van]\VCǶ[IKKgo^v}ǡpQFWk!6&%KpS۵uqq1˖-#%%B7n_|9jA׮]eDPs$$￧A}Vl&u+/kaB2~+YBOow`0x=xa?P\ %hhW_4s[Hݾ^xMTsBz:& / PP| <~mry.Y"k 0|_G7xϑ.|L9> 9t(!kijH$o 9=Yvsccd2wtM["T~t8yw=Wfh42x 6_OyY9wGbb Npp9i`elܸ}5J ^'1!ѓevV6ZO ={DՓͦ=xwĞ=zXzc!+hW[&MT߶=}|W,^{&O&9)VKzt$Is9|\Kb5q456;8ucǎ>a:,fyӫxcbcbϞ=ٳ޽鰚-hYhڎiW?vO? .PUUEzZ:$B5* ºz$x c0'K{MC eu'ՆVpQkPK*/8.yRI*j5zǏ{fI[[8Q  :F"''ow[JjKLfGr:ѨԨUj˅ĩKHhjFQi~6={!zD oo2p C{v Wgݮ)o,j\N.K0*ZFc6lMMy\w eeetRN'ݺu$whKp@(g@$ !O ìhp:\. m$Zq1h ^`I?H:ue˗ѧWoź<;%$$$",QkdgfɻJX,9|^zgg_^^/dFBGv6Gaȑm#%BvL`%ZyKrcIOO_~1c DFzG 8`o.%vL l m:l3i$^FPUUMfF&z.>|qG VfFK,a]wsr\ d=s`,fl)@a0 mvJ\v~+ (B`Hm}3ew"ngg .9Ɋ1WPFqb 1; #띶!Z]U8=RVwlnW(> krWЫg/RSSiiiaΝx>͞홷+**>At:=Y=شa#Z ZQٴiV[nCrw jkk1>߰ݻw]wO[~LO`A{2uT"#"}@ A_n߶DPQU͉ Xt[(+HmɈx1i4ӻ}rf3{/avp,54YYY0KoSĹ`tX>ƏxqT{Ú5)S`b[]^- f3ƳZ͛{e`a!) ~[hlZT$ Ck]Ǝ34.k3m6.h̉6ͼHEKm-t:Z:wV:裢hhjBLT5oKZ ^Ͼ<\.=z`0im !ཿxBy V^aCy嗙9s&" H>}y <<{v+c=F`` z_qv 9x f'Orɇ!%wb5w$v還@Vaf]u8Ut 𯃻ZO蜜3yҽ{w]$%7[+/̣:O>?LVZ6mc+3Xj%pp0N3儆Jyy9$&&PXX@xX:ii-8[A;| :;VHkk+aa$$sy$))]ߗOG䡑w|FCǝv}yz%I'Or9ːTm#n<ùZ"N(?~ٔڂhA7u"##(rX,HOONYY)!J;{c[TJp88jRw'NJcc#UU" N#!>̭8p$y̔78씖 >3bpq1o_> >c411A>UKNcY9p`AVV;vln!_:wC4_hT):uDss{ABPSS_ee8y wХ0EVjrd1Ll>$(8h'32?ioCc1;ʵ86r²Qk؅_Y<q\ܮ*I""2SHZ тX,f"III\-+^.ۗbN#<<ߓ%@J  AƇbZY~96u!Z[[٬)'0(+|w:%&:Fq_6}@)];rՊf9vW,awv\{Mu5aAB?!ow^:y k2jš2A7LLyTOq>͛غu+]S<3gL&T*I^f6X,2!&o6b i_]S'QIaaTFϘZhn@{(֯c׷ի [H/9FWTTTPSSNO^<tpʛs.3 }+;d05o) SszY8Ģ8 rgi$kܦ '²eYFHc+@` ,( ~VW_#)9IDAT$+ |"G:!"B6 mmv|fzYnn xtZ.!h[}=KJX3w.%BqTWp ;= 9rJ=BkR[hw!Jݑv-rJVXARRs4,bΜ9hZ223Yyi<bзo_0rhgy$3gtc5L&(ay'5{66C˧~ Y#N' !ҥ *Z'ݻ7+W=jc8a],\kע7c^\^zeht҅;bDEENNx饗 LTj5geGQMC h",31q< ,,IRQ{ _$88K""ѣ?o',< S0h4z ifh|ÏػwOfK4h0FD<^ի7/!&hy`L H`@O뙛;OŠ+j5W{ !02{]=qՈ)kv]ouTnaxR3}a…edRذa#Sv?BSmV NdT3.{[:jȨ(RS1ᮻa2#vutI77ßuV:uƍೲꫵ>ngذay睞4,\a##3+Ç3zh$ ÁfCѭ[wVLdϞkحļyUƔ$ BBLa4j >cDza",*ȚSa<ԩ-LϞSPRU <,0Ι# VGC'dЙ3)oda[òvr{BL cN<رc9r]\S[˖-[oVVh"jkjc B̧|‘#GxJŎ;Yv-ɓ'ӷo_N:ɂ p8L8ALJ j(3Bylhh@TTVNkK tOK(*Kp-a5IPk4475=jZj uy{9 Wp0ǃWUw |`z+;a1[4'}I7Yַ6.'>_,~]J=Bp.vzf͜Fugqq V-9\ez4'KgnV:B>Sp:زe _[/ݺ3cƃzDmGp}a9Ixt:8KT_7X_zj3gL&1rp:1  ؏j#+;K.$%%SSSCPP]>/ATL۩w秝j߾}_x6w&^r%尥+)AK;P|I:__l^## ow=%]B\\M\"\Ge͕aW3~@?&"VW~)Z~tM7"ӍPъVxx$ QMHxE@#4:ܳ׭V 0F9VY} o=hRn Zs 6ng&p@r&3w(jKC.ԐЏsPu{tt4Əï>_ CcJ(kJ_~bZQI'Xmwz5B9 K/>Ƹ8$Aݻcon1'UL1cPvxP%IF؏f##3V˷v@BB"gΜt)u6Q!d/|{,w)V/mfl 덣 /SJ_+3X.\ 5ay-zu9] -._;_NםV?ehu_*\/ZI82p3k)3%眚JנWwCOOnJ'npQGq($I0^dj -=`OPpqj>Lr׮ v;N rױɨΓ6ۭzQ_N +oa@x)˛O ~iWAx \c:t_?M.rхJ޹u^)޹Ahr9ZÏя~, \!J0_\랳@$Ѷ_,AIGq?v^{7xqʢ@xD*2Iߝ !⻄@npPWWG|B" (߅aS݄pnM 7&܄pnM$a2 @US߬`'s!e=pl 7{^b/P+o$2/w;_)[\߾եb%i#G?uI?-.oΗG TV7iuVڴw,7^,b`XkjEzqdG%ٲz$HϊّEAwqomy1}Y:˙:O;6:]۾|ڗ^9h揿]̿4G׎|t rOMV>n&n&~zZI nϛ&BPWWdpPi .GKItaЯq}guc2xgKңe Ac|r Q0Hxˈ4OeCRCܔOF = f@ ~ eObwָF~T^JiǪ&4NE 8X&ii̜9ZZ߯iPs8gϞ%3} Jhkk7Ly344Dss3YYY̜9ZZ288hѠ抎j0!2CdWl] 14ƍgbK# o?q7za.[L|x";b֬Y_GMޅ3K%V||UoJST5b*ͫ^Tc UL(3$HT{Y`j͛+RGLV77;Aղp,LM5!jcB[wnZxsaΏ(//K<&*/cT`|nV=>sgŀq iQў!" :OYb6P_wEM /!M*A"$2u AB&侎?qWW|G[n2lU2<<0g8 |;P\\̬YBG;o~g?9na(_|1߿#B!|Mjjj5.b^|ExkjwͿ{Bz˿<?͛% $D" /<Ϯ]@o[S&ͷBWWs/hw%V$D"x4X. !4]H$D"-%@WfzYM]8+ $]-z+UYFKBxP.l` ՟y͇@b>yDcKT%AMB0LH&3"JM!MsF$4қ6L( .4S7Z00/CF1xiW#I4E&1A*Ln|f+M'X%OoQ]!~ʹ:&Vs,$YA.(݂njSL"qN SQQɽW^ IKxDIkC̙)|i :%#` ogq6n$=={}SR\LvvW~Jl3fpPYYD"wyB!*++d<ӬXP(D"-ZZ]uU\uUs(b֬O_.RfϞC^^. %7Xf b G̙3ԩS̝;B*> ˹+Jg<̜9sQQQGb[eV j p_p02Ƹ]#.bnŃL-(ډɆCujH!kj y%bHo)߫*|jJWI+8$OU|f@:*;5x(7dVHt:oKj&wWaB)`zRȢ_HՂF Ohp`qH#S|/(H9Ĥb/R.LY3 ?cv2{v)GA9Z!8  ' ^ g6344tATV^N$-nxq=Zwedg`ڷ}V1g",0"B&QVVN8>+;.}QKXx55}P(Jy<r %I$rf@?RBFz:i4-Ƚ|A)Ug):uAuNJޑ ɓIKKMEXƎNSIo()4rIK/.ťjG1fkhK*)F|VHōʾ3ꜱ^laHpwn ,а 8IJ24xUa5R6ݨ~my78>=^Dpwٷ1~*7IcLJMhFp MoŐzi?jI4L7 ڷ'|5zaF=:&.!ocGNPɓiTF#ܳY#|yxh(Ra-ܳʼnx`u)]酜(B E`OwcM3VI 3Ŷt0u0NAV}EYULV4hNꕜwrCZTŨ_%X1% T?=BM~,[OƐۊ#\-În5/+E%?mC0uo/X:0a+68<;6=y`ag|qD="^-xv@IbLj^W5.0(x2$n! J^ oQA鳉;=M)jܟxĈ|0m1|哫}pA-7 +XI8Nf٪'k/[S[~pPٻ ٓͨ "*,L];v%[% єP)..69s--zTՃl jۜŇβ^z|q#*Gq+)qhPm~} ':-U@԰LzKc޽[Liڣɓ۳w#, <Ϫ`SzS"Z–k@p[ Cc[rh9x<{zUP .jF٢T %jTxWūU)ING3No'2K K6cc(ŀxydE)P՚J+v+]ڥ ׄoۂu; j,[Uͧқi&M-+C`CV~?6'^Tz6hŬ;^fPbQcMe >={kOx7/<083+B}g!;uh?Fiii?hPR?ԯeH$DD"D}vb  S1Dx"ascƍ,_zg^<ɓ'ijjbǎ44D"]ZLX)Ip{{Ho_477 HSHbqCI,sk $.~ Dy;0cy!9r6n=SD,A<cǏ!$f^R8Xy0%KL ygHh…n<<-h'ڀҗG#︡?+Ä݆\AYt8_>_O<ȑqN? mI ^5Ȁ:44Doo/ӧO#-q~H 5h*Q32}QmRIkk+D}عs'tMZ\,Y>  qi*Ə̙3ͯÍ7Ȋ+H$|eڴi ?8 dž=_䣏>3Exikkf;OrJnW|3aIʰHk_* _3gS__Ν;ٵk>gvl"///}Kd%#G2wJ<Jd^\=!(--YFiiBTȂ,{klٲ{r7Ѓ xDoذ49y$<7Ϋv=|x8u_*_;|nv'~|GҗĶ䦛nd4G [n!''xSPWWWZÈyؼy3BѢ>ϙ3~Jk$aǎ.fƌ|?a`˖-$ 233裏غu+K.%//z+4GaɈ6:::xoG)GZZPXX񡃂Ժw~ yyyDQK&Ŝwy\wurrrHOPQQ>5 Μ9HfUd޽z~ww?0-"uVB.R3: J՟iHck%XêA$I8, VN6 OW?K5y5hNi؆Cַ-|IH4W|@҆H>\00eǟoOӏ C&>~h}H;mm\&? Y:֓Dh2x>h>DÄm#aPsix꺢M]3njJI AM|as ]%~=t sO>~*!AQҌ!2*pwyy9jIs5c JJKΦ;wϊsńDt!SPP@AAc߾}\z饀St4551~x&N.:j#.K'OX~=x=sܲsr8x 3׮]˴i(++vMmm-SN3gH$(**A|p뭷rQN> 8?}WTM⣏jTVV>iyy8p1A;Ɓ;oW_}5wӱRs奂ΤIHOO_)SP^^NSSBɡLFnn.Gh4ʂ (((`ڴi bL?g/'`=<9sXf ]v:xsa)ZΞ=K8 ȡC`"G)IGeǎ,Zٿ?[\s5\y啤GKwe`Çx*رc>|P(~5WHK!l/HɣC*/ fpM~$ȉ*}ҴKrֺQ1x0+ ﭢՁmfLyBDC;{ &kaJ\->D2V2Xdu?o asގۯᝯ ,>0չdOɟWe%}vep2IW3NxXzR%u~&liމhphVqO*H҈90gWx+n!goV.<͡gR\\L" UUUNP6Ǿ744L+0]]]1gFTUUOqQRRB~~>ϧ~LBQQwx]xDb߾}466RXXȜ9s8z('OfԩQ=G3qD8si'Nd޽JJu`0o]]H))++)%]vuuu1}t&MDqq1999dfdJWW7x#$ fϞꦡYddd ]vQ\\LYYgf1c &Oĉ9p=\tEL6={PVZJii)IKKE 6mDYYqu5aMFee%NbRRRBYYgyL0rN/g޼yTTTj~MƁXp!dffi&22(_Nz$=)x QXvu v7#=TPwu-I_ P3JE 0vPTI RH4Fiuz550‹#NP̹:ț|d Faͱtpx"Dh1/@飼@-& +}t$-nKV Mm`&pd >u{2d$SCZ%Ldj[*Ås]X:&!BY]*z.2M%KU0soyI=Za#$Vs1<#r4蓗Yz{@zE1"##L D"v%ӭo NB˳B1بHͪ_}`e@yV&oVPP%Wv :?WWW3yd5}҇[+B,o'//yIݵZAAK,xOY wAQQuh0vEU֭[^uUnYP{嗹RW^y[oUYv-r-zUB8yu$+YU'ۨӎ=J}}%`kbl$>2.S єǨ QJIS)cX >:A_Խ>Jc."*XhR%+79*psM>AȤř(I i3IBNVq cO.J~y~lbr`v+1q|hXgҔ2.aM5M0x_x5abe.íM|` y:S~ \+Dfo`*( 4MJK@=$㥡7̌G: GPF,Ir2ߛ6Y `0f?Nx=}xNsx혶5:vkptM2:# xH3ax8|ŷaHF}铍]m>00` /ɮ1X€!+ *ՓepӇ3CIPC1ޗ@!o/%-i>Iѕ7F9֟кw.T"BJ<ˆGu-0HjhSþef) dQMC\33v% JFar3Q eٮa7f(UvR߾:3t<4dޟ80 z->^n^°7FN.ٿU(-.v,|L;2@]f}2pƩd٩YhlIzvE)WYqjzAK]SKjR " Qm'5 (8DAkʡr$SfrUoۃ'45H&P]T0:aV6Jv#{OW:I}Kz:SGЎσKp,K@יXPr7~Pnb$ Og҅i, %݁l&O:ZPMBٌ;n%V1q%_c,M854]qSo]ճ+=޸Ű5FBg4j n`іdR51sa FZ֕Sdٜvs։`}YZxa<]ʼnmaxr7}D@ebzZ^$т1 LŪ^<$Jw€l\WwHYۘ]yڰ,щ_Xn1յws'iۺee]7i1 q,.a^Av|ia\67WU0n`#µbU٤ms4ZlƥD*tu1uO,.JQU۹Ǵp +]uG/h Yy!.jQ7lDf#7IiGm(i> ܮBҔgKt G t6l_5)mXz0?Mm W(t7O%L.btwO'I5q/AxX,FHDQ'O A^n.댰*3X$³{\`QQ BǙcmw3W%Yb=@QW̲E=xr»tsC»FnN.xeɏT:?%Avvp@X.mJz{))-I}>iq A ?P(uyP8in``^@%%b@>i`\QM{(9Hvw/]JSΑQnZ2Hà 'mQLptᤪx37|;Wq(]G>^ڳ C-GZݗCNFY9ܷ>k:$Nn" 1nSf -p ?s?iӹWr(.t>1U eC!1}tI ),%7+BnN6i 解)SI}>ioF8w&8;ú1!'''&X YKIǹ)6z<<=?*? Nf {Í]U y18罚3D]} U$iy3f/gϠ 7ۻ}o NB͖47M?gZt9P{p8LEEC9 4ݕUtxtf;R:}:HI|p0y G1w.45?~._-7ʡÇ|s%3)v23 9_ZnL]8ITO/$7$˧?!o;?m^ϑ&XDu 8WGKu}$zF=\ xPG'hic%G֩翞jO"TSX!HOOGA8&#=CAbȄt?O$H$VŧD"l9<﹋64OaN/A KI#/Osh>|< q@382p\]qL<LzzƁ!M<$BYˡ,^[3nf]MM .M߼5B(1jqhwON1JQN<:@_)0XoByjSdgo^~k i?o-xq~ƥI`xpW^!\R2S\L$l]Ί_jwO8 Kq0{n&O+k(eŹ?}z& )zbBe9EwoyBg!--Ͽ[[[G̙3۬Yz~c~y7F-൴O;o3<χD288꤫Cjxk`l*pZ"Z?+fO䱻rlD>yc'388@g{g&4HpX=۶nyD M|8l5?T{_.SK燡#lϣl:C[+9Nw@n'by ?ǾۍX8%D"i '(.)#h<2ڋfF.Zq1BEqqb8D㧚ebEN{(?va̙:t\wudeezBڟ3pm-|h6Q IDATS (*3?W9?9YOD^Eyy䕔y4^}T͜i+ݨcO3ӧO'3+,͛GSs3ns˟3]ᄠyqL8L*K '$1ba#v&NB5k8{ݻw_ Xf5֮zj֯_GFFN{w()-e-i/UUU9sKRTTDVVK.eϞ=̞=7oz,֬Yþ}(,,5WqM /hkkG1m4$|o'Ne͚5\|%^>3f75_C7.痿~˖. /7ߤj"w6>F,}E c׮]TTTuV233yؼy3&M"+++V0g֭[ݻ9z-[JII)Zu֒[V16o )--uTTV~fΜɓO>_}tÆlٲSs9UVazB0V"xwc~x<Ƨ>5{k0yqv|K / d>'loSOK/Ro+D"ɦOՓaj4?빆p[BS0h?y7#qKJ8} ='`4IAA,cuw]L2HZL s}s{کZ$;# R]U0邦.:zhi:@4t{01w\J8r0\r wy'K\}}bZ&w~2'gҤIi:Ô)SXt)W_s 즡}nP(ÇkU]͛oxVD_^޽{immeɅK/c L<ŋs%p"~̬旿55۩Hoo/3f`„ zzz(/gaTTT΁V9q8\r _ڃH+ggehx۶y&Z[Z=T˵\o-P8ľ}{]Hyi}z{{پ}w} K.\wIee%{=>ϳk.٬\җv7\ϲe8{,S9>s+V,g׮dggs7 /ꫯRYQIaA!k֬d CCtuvqIikk_fpp7{^qxM\;odefQ]]Nu,6nW\5k hnnqc՛;xyb1o__{c\rŤGҵ?T:JKK㗿9tvv0id>5{6tt)wq֭lݲTUU[oYr8yGN |… K=g+V7̲eK9q{vk#33~G}y˸ŝbꫯp {.K\H]QNn$:EW{vfݺu,Xޞ^vڥR[o̻C__ehkruב%\x/L8+dɅWBvv6O>| w/000`sT ϞtګTTTOꫯٸa^t+V,.ߠP-U]]]zrC__QٻfP{G?{/q7(/2E;-aLB|ߥy| w'œ9sٳgsX=gΜls3]]]L:]vR_WG8ݻ{R^> N56Ǚ>cwu71wXpDK6l#+= DB }^GD=˷W=1Gojp6JKWazUHDf8`h8p,AZ85W, zB<\~<,?)3 J%ފ.mf80k|57-.U r^%+jnq &VPt9sȦ"_.d?.;yVʞ~OsE)+E(ĸr (Gzz:ͽ(;ih[@Vf3gVSQYGh$|_W#;;vwcSVZiiiPYYIkK ?{wm9i={iz{`ĉD}\s͵GӧMG{{;ƍzD1#J!e_Z>m:***P,pl9G}hTWWǏǤI*(&NDMM r,CGúkmqݍ͛67ҥKQ[[ X~=ƌm-$&f`z˜;/~0mt"UB__/1<bժU܊1vXMX`ޞ6ƌ 8#PVVb׏ƨQ#Cu(--CIiekE枞n׏ƤITDX,GD"@ooL${cE"_~#6'NBKshKJKQW7 #kkc/L%bx1rd-z1bTUUaĉH$xW0{l$ |~a9RhooGII ~?çǕWT ;v G;gE"%^>_^VL& Jc?w\$ <0}|'S+~f ^evÈ#g9㎿aѢE(,,-ׇ=ᔖb„ 3z [i<8S܄w~-B}}=vnر0in?}׿#?X4zst?aTVUi*5N&L1PXXX,fݯy|ʺdR[[+~7p)'#@~%s7S_V!!9zj .dcm7k'?NN),LӦMv(--AYY4zzQYYj ըvp3lٲԉD-&>Natd CC-%E;.2(fXd>6mڤ9815 =6lEjd V/yS1 G͘GdNm/Xzx.Däf`~#PS]Iv'q[}6K!xE<PGo#SS3ψ}g )b$F6 k'G׺rȡ8# $Pzx</@Y*PTA$YXZSOw݅t:D<>W_uz{{qđGb֬Y[H$PZZH4+V8~^C{G;f͚-+LJuy16b1'Ge҄MڑRգ7=':U^]/p㐃sL   UBM̟~LAeeϟl6=h4_7 Deee3z F%^uwhbDu~.GMTZQTi0ݦ j*dziFW_B41ʃ;;򶶴.丽~dl6_[;ՅKΘ5Uy +[3$P샢TQ9ܽ\uޣֺTVWd%ECqӬgP6V[qNn00/fLp׋&L|2J'']h,Ub)Zs S)diPU]nEWg;\҈x%|ٷa͚o=`*W_u~rE-ƍ'ACϙNa9&lh[t)&Nx<ŋW™gKb1,SL#m Tmmmގ^ 'A裏0i$?hmmEGG;N:k@#| z{{hd;p ;())1 f:Mi]s=x\~jq{=ZދQYYFoo/^}aeu.=D};T*y#֭[(GiiN6^#9I4mځvkg``6~`QFy ^#ɠ(((ڵks9cȑVc-͡y|ȚtPYY[>a,.iҕ(,)-5 \O,zLCwTnZttt7xuFt7Tה%Fai͔hVN%Q_7_>X4# "H#z%:p2Fr=9"aҸZIy9 D\6`sv Ǎ{d"(,8Y3xW27ߌt3{q$a_vNu2R9" Uq hhC&@s`G":IW%]__?hxxp&,l#^8\FL~fj·nFNw";ե'8;P4J8-qqhcV!wUb8!BS/D,apxrxBCl%tEc#ҠĹs7C^C˥,8% zZv9v*㚶R0Iw~4qH0/ J^ph(&)}<1G3w^GOO֯_OR9 gy{Jqa/%Rzd 㘡r"1mXGCL/wxƲ^nT\*cCDHDIvY|D̐)  ྦV2͑(ֻ  IQ IDAT(]FwܹXRO8$\΀tM4e(Og% ]# AU>9›<5U =L4L>/ be .OȔU-:>xL dza1ʡӣ^աBFiWI(<_3~6Ʈ1m3f2>qIH->i ʀBu&t@> ;`A~0284�9x(UB֞ˊ:,ᩅ] "Mn^ 892k¸Yn mֱԇQx ;Fn.c lxf~M':dB'?c )>+ t 4J"%x1&"<|YVϸ gQl001>gSiwUa5h9z2>t(tr+}I ksGmgwjr ~MysP[?ƘVdzcV3+Ժ:D#O l=ifJe>6&WCD0v>&S[h|l+=,a.,EE wДm?C*]M>o2suh88x {xЉbbB3ŰLx6jL&ŵ=`io /Cai}>&ߊ6`%) 39 9X]CTpEJ]A;Ia q|O[i RWK..ǤI0a)&C-2 z`io /S;"g9~ ނJ#ahJΡqC >V04v\CŽ t%*qرc0nxbsߣx3tDu;nʤ. ;X?~L}Օm.?px #Oury0uBgCDŽTVam ݏP1lgGG>Rۺ{E::A8Ly`i-]AbD.C.ä0b*&udNR)}xwtB+uZ<6ţ?mh|*x=6Tq=(9Q;t6 o@_?ycg#޶]g>u1r 1,o<8o:>StqSrѕ>`&G4rC6MMd(9Liu*A}<.sI(^,w2xT$U7 yG_S:rݜBბ^Y%\}R=pw(/# T7z%:n|y=䗪+G\_յ#K~ }Ϩk ra٪dd2gT1%f9 fm>rȱJp#O T›),<͑IǸV2=(~u*,"} &<:}C+x)MW&|S/wN3rŃL#'b璻w`\KL1L =a20ЏVGQft^f}7 z-O{5oϿ*nztzFǃ?::m 367f?_;'rA2FǏ\XG[A3ưbr$I=f+I1eT8᰿s}݌#{L&{Oaiv>(ow(LnA#,-m8DQc؈D*b;w1aE7r1H&+gC?\*፼z9"⩎_=Xtgp3^S@.Csk+A,h)^wfsN"N=D e-ЄF!C "Iy My ,Ю=Kgh㻔B*݄i6qэ^.Bl8=HWXJt.ռwFsfFWzdа 9~-yt5s(`V[&C|.Kxff%vqJWprWuHfQRRH4X/wNcC`9n+Og'Jx`^ #^?"m: H&;o6FT~xTUbRaTY}i\ƣ= rß؄\.~8anLQwpqg L:Gy.N[yy9r1Xjz{p߾@{GVz^rc#%;LBNǗ~r2囦OׯHhf+!kVt5ge8cJЌhFe^+'5r6@zAˈeYr0E?*Fۚ+&@ht%h!םm+"~ Ϩ-tVd4~Dɗ( ۄD֏DWro7@'ecwrj.dp˻h2dYMёHFq'14t v' sNG}o}0pźano :| Pk3JB|脡/}J|tN>3] E0A*(# oaCX?amѡk$^98b'V"a9.ǬK+ tŴϷޯ}@6QVE,jgs[JT>c:nVT㏿ w? [;Q?j>@,p ^s>.Xb%>\3gL]ޏR|Ёډ'JKpUƺ?]N=]H.1clrtuu3N; ]u%V'Mw{k׮/1,|z^~cow/8= W <.?ىz'JJ#G$A}}vwǟ@*BII ]_oq:R=kNJ%[AktD]`/8m_ic~|8lux'{YZP`uчj+:³ûQh)zA<YVgGǀ>\C>w}b(.<`!\!Y" L߇L6 p/uvu!k?c#!e_7KuoP7r+=(ore~88XK/odYأ$k$DϪݝ*zDu>EV3 0}S9䫳|2- CǶ}]q| &AuA"oFAce[|uzhG.5ԽgwMq`e ? xkM'V6bŊ9޿GuE)<IoS6|QH$x7o ?N:xTWU .Ģ?@"]ޏl6 lJ<`nT-V\M`g #J?߰-ɤś-S[[;~466yk_=MM-__V^0ށ4/wMc w^s=`' 6AqAx:2],erBh{|qÆgA1X}llN?s[ϗP8>ַ/6gZ r N6~H8pxwWF<:>3jsO9֪NtfѴe|~!4/Biq _;zos\y!Ktg6EA, # })N>4s(t/m-:ArAY*]Js(ʶ ~ts3*ͰcƯw>s(te/9j*J1UdHar9L6s` M..1t (Rzt[Faan.{ 0W^F@kq]p˭-[?:EBѤh?j)j1vA$*_?JLЍ~·t2誃"a/ _K0}jR>ҏ5cmG_= ?~dnЁ7{Tޠ4M8>a|E1#="<'|[wՑ@9i7Q #i1M\ӋoBY܇4n˝Fl6]jyh}MC?|_W} =^ri>pm|.on͔r ™o|wd4V~461T6W tXvwsF~8uJ!yGc04dXue+eϪ@79  kgagG.C.w uض %~zf1Lc|7 4lppgsɝ[b,Zz]%x UZz.'.w/_/7; 8Х 4t}r6:8odž:^,z>u9!aAxbp?~4:l_wj}3@5kgP_ʏg2ӗfЭ>4vK4yBWr@GM6Аl3(d}ު'pGB I ZJ  9%3sh'eFPaBj_/rF!p1{M&Q>sF*hԯß7Nک^8 \LS_gņqëGW@˪-BCKDgs iԳWVydStPu@4"}<;cۭcF87YbcՏͳ?"B846cE>! nffxQtE$>6gyLC!68|2&>%F9vad>dKK>y$L_9˄{GC|`lv' '?9Z%宪 0mi!TlRVՌa`̅ќC>}:TAp`\H0 =&9,mZ zO57W& *HWry|wNmEķTO* 8 tdV4dxSĠ-|h`|vvh|u奡;* +q):Ad*)?]ŷЕ&BWZCk~6rBJ,4|0 UƜM2(۞M5S#}+g̶΃F?^MHc|.*@.:ێb&9826} >m*NcL8L~Cևկ@67=0 ƞ~pz:B3H IDATX@]*HyJc,>6ƇZg!_L+ufQiߒ꧁@9ãSo]ډdsD4B2\p.e HG9@;X~- G w-{m:~>AdSyI4h"S'f{: J46"Ǡծsóc'1C :+ţ ]u4D&]mW `h%a8am81:CUtt5|;ȇg9C>]+ۢ9'p7z^=FSuܓ't4tq%ř}g>hXroaDaE\r];@arL/>'urP9bSŪ%_Wqz?a/J4U8 :U8^uM#urhg#~&LYLߕ1HE"֧fSZlKEK$ Z\z~EO cwve@Qb hb6Et}p֪R:}5q#٘0f鰺wtbDg~{sVDްGf}?wTiN܏<>(=PLP4pΎ,iNa~2O*+&{(9Α*.Kfshk08; Xw231Qܐ)ϧR7ypu&<( G"G $~pzMݚM\mkh(-MT}sE~S`nwHEdJar*~B0ȝS yٳ`W)֤CVi -EWg'K©FA8&Sy6'')4vP(ذZ+gTJ+ N:>( "*8D)4$z"tW'KF}+bH(J'& ß[8vB/**r"zǐnoq~$b84w_dY:}:61qn8⨣qçx0cqG@MpM7o0bH<3hnj gu;nFիWaƌE&ɕԢ<+610sr Ljr7nDMV,_ w>yUW<Fpĉ'no.|?8L2(++ǜ|bL0Ç,F*/yۋl6[ou<8/{7݌3gk '}d[I&L&^sNR)tuv!'PQQ XdYx!޻Ntuu"b``x~|O}˗-7ߊή˰1} pOb'E~Iwh8|| ~|xGq[>r9c磫#T2 s,_Ul+! N( b磫 -Y&R)DQץgG<}y+q:駟n¬ٳ+D? S) $qj'3YxiH44tIQ: !ؔQJj*q^m:1K7vmoooro*EWwtWF7&xjR3請n8>z=XJ RM +@.+nz]}֍u[:̙< [0eL% 1QZĤ Y⢛^@sDC*G[W?~w3@ub`fLDQaեUYnV!\{oTK_Ρ: ۏ&{u,!wD:ĖʜY]}TTN7 xg#9T<GKs yA44LD!Qu` 8S0gQ^^*p\ᅦJxFaq]x<ˮ,|m3 &sx"_C\nS`򔩸kىSN; Dq\+QZV1ohy?s\t1b.ˎT8!N[;TF?Yq{޼yxǤ>EEEсV0P\\ F(((pW6 p޹kի*aDeLDǩDk`5hW:ZD|JKQtz4hfAQ.b BG"W j%ƙч HѻqxT{1[I]R>~$Yo}NT:<Q'"0Q8hxcdE1= mBkg/RhlN9x*}}9N?lh/o,ǘ=i$6vcUUy0 (HDQWU4k ^`=8kg<ѽ>Q9;3 Ӽ[hHx|kojl[;;_ErsK%äaST~_jGԠ[-7Ms0њːήT%ˡykkkP^Rl2`9w"L-O\#L$p}/Nj/~+W裎i?Q[[1k̙_ݭ9E"5Yjfu&Ph)+)@O$q~4݃Ax!|Wn>FܢC4Ca&F]ahx֍Y G~5(9݌51g~Rڐ8nD'ڜ#UPH$WzyOp>aʂxphW8T# @SK :;1u4dYܥɧ3ŒG|MX[1Qqp݈1W3\$hj20 e߃r{v&Ƽyގ3gwN=T@CC~ttt` hnnFmm-f͞gya^-mtW^Q4$\U𤝸M4ɤ4;2"wo1q59ju&Z2 Kg#Pu^?WOA@xhtKL!A3暱#>QBe~4m= ]Z'k+S.J}\OYpr5m=TZ!R.)5vuD:dv̷5\1y9ƍ p~skb:svv{l1$A=b=pwԩS$;b;}OICCCGZ8 P@) ؞#ʓgEɏ|GuŔЄ!LwWj ʣqo:5&8S9ʸKd D'4ˮL:>:fؾZO?=jwrշ6Q$hչE m AcaE'F;1ᄻ;Vv4C̨|si3x?sX3оCn fd2H_^*K&߭pFqqSa LN!ǰe]š-ggCZz9s9qB"]rSCXQz:'%ԣLMr%J*Bm + x~+  4>bpsۗ@$R>dɠ?l6X w;p ~Ts=D6m_ ! %tb G=j$lWzR&EN@)=yZ(A -f̓H  դ&`OoNy&hCD~J$N"@Ej㠠2*^Q<><44EhO?A':Kx0ܤ~]$W6!]ѳd+.rpn%u ōŰM88yfsHdg--_wd647`)Ř2}2 ŃGmMhܼawB.6kZ֬&5)oEX}R-v]:殾ul^nz)p.eW*bv|BFFr9.,mf0?9 Ϗa!Nc+م-M͘01"L```m-mؼq3&6hijiCkk5k0i<T!jjGӵfD5KKeijD&C.]B=#waw蟫N`(h\S~(>Ptlj'nw/qsd%,]Stӛ ?F_5>p:>ҍZ0L?xGacWLz +7O ;0Ū _3 s]j>`-%wr+r3CYq+"G%wk9gۗTQ/]gw"lڰ U5ՈcFuMSOFYE9֮Yh4$ ZP\ZH$>qdY[R IDATzS$29/4%;dߙRau-蜫eⷧk 2 kpa!䓜 5WL6]p |*{oGÄxu2%pRxMP+`0ܙLJNӅJ?H~w֊7xݞ'],^_8pH$"w/6ok(j~rQ\Rb QPPD"dAU#X*qs#FnLZ[PQUQe?<굨= HU5U(,G({y2rx<6 0vN3}]SKc(_,?~sӤ7)c|&oM*_b`5 cDP'*mOAul_C_/Q q|iE7>S-&~nJb_BGDs@^#Bw+fcGo_?x\JLLv*iWAǟ NM&\:x.\~+CO>v)Haal~Hק9`"lY 9 T8IDĬ&1 mbR6M\fxl~c(*,tNE>0I5zyc,rP8$$QJs!KzIm+=X-롘.4LTtǣMU3ϰ0~|7%[:O:hP|~t$L~IHd ]|h`(muB 3VbҧN?\]MtMrk;0&*\>1dC Hg2εμW.NC'Gu6';VlƦaAW SR<1 ԉOus=]s'{$XU~@ m^Hyh0ji8 !#$YܛIzdxNoh.$␌؊1>PL&WNӅln|p.e%(UMt$Ir .)Ɓ=||]tڼ,_B")Y\S3r:#5ǽFDD?wTo2 oJnƌtһz( 24f'K[ge Rw6'PyS[:$iZQY;aZЅ`D)eDGV!=oR'VW#T!֢*[6.VxZzlg2t[La9(m-ɸ}| g]w':Xz8Kꮂv=%t"bNRHSa!xHpvW;aI"M;d Ҹ1"!!Ɏ0| Xq*懫Ay-T{%iDD,FRDA|*Jf툈5X6礏tfjp)bIl/" O{X>iHK˫h01$i&c3znl#O !? DT[UNSŝbg13J6}HP.0E^ z΃ HG4c:SeыZ l9URUF9 L!݆bJ6`/EcimJ=][YãC}߻R V+[vsϣjԉSX/m`uy vfk%lnlBJX vd[N0lں>pdžlLqdS" [ƈ%'@J7nvLb]" # h.Vs&r6g)PzJkK V g;I /sJ =Iу1X+.7#KՑe㉈|JFRWaA.Ha76ST@/ VB9m#e y>'>($[7D m=KgNc@>)m*x>`tl-cHTxW?Ez7L Fs,L9+&&sBWi?~L?:VeIݺIEJedYT*XYZ3 j!h4PZ+ayiu\ !I"RRJgS\@UU^?׾u羂 'j {w|[oS(KBD>|.؊S+E㩓D`lpNES2zQ-BɁIwO͊@0@pVWXϢ0q%Kn+%kCxp=z2EW|<:jo-MA`ůg'_R6)U MՓd~l'V%5JoBʫ qEZ>Ŋ(a4 <IO;g.)g4A܊f*% R r #!rnq.rLpoB .r ˌDS+ GZ }c)V1ݮS9 -JXϠhbph@^[[s97O_KNߧ5aԩANDok{uC' novS^N;vM hk+kd,.Ë_B  ̩sWNaiaz+q<_>=gqE??F|__ã+oy9=._,Ϗa|WhB՗yTӠCČy}/rCX53blR@Px矖I+a,RF7uӃՍۉq k$7uRG̘wdَb_Po翆W_?3?{sVc0wa8yqAt:E܌Ũm+k&d204<9x};~֟_̔ K:V ϡnaqa g?Wkس'?Fm䱃}WCf|v&)@Z XCꪝc~X 1'柤:@nlIÃS›$Ou$~P=HzRO)U=h&8Spl% LG}ڷ#`0:W>[P!uu;}7?To˷=Ş"&wN|n:|?'m|?~LMO"I>U,\Zij d2{hĤԎ ~Vh.Fo2xoI8DŽCNSAq Oc#pcdY6=TLҗ*I,{gܢCqٶjl;Zl$"ðxܲ؃] mC򮄙k&s2>xOWwT$yX-5D}ݬV^[˺1op@F5YR2ZrAXUO-\ ŅyLt.g#~yqQ-X/\E6f+RdO24ЏڢS'un&{p7⎯} 2^Ɵa(|[ dƨCS x$$?^xYmX +|S"G\c\Փ^v%:a?cOMq~ér[y;?l]}Ld56LU/iKju";hŴ+Mml{Ɓj5l4Qo6+V *ZHPF/zA8c)6o m{rT=-\S"Z趜[2424۷ZBnr\0"ܙgvuo9䖪"Ll7(ˈ _ñ!]/F$X sMZuQqƌ>MPTh(Yn}Gيg|<쌥vm6M6.oeK;+8&aӢ6ѴnMd6V݅ތcN}RcNε1}9C;GXFf6]'p=xESߵY~Eӱ;OFpW*_lAX$SZ߀yAҾy| D׈RڿE(jrGB>ҫPNBQtc Lw=_T. zo4a>Ηc' C[)oW&a=EW_O7J x;X@J_ t'&fׅ02zp?JXVvlLDfpB/s*M,t0۶tU6r2ŇKe 7Z 3Є hRtQHjX!7ߨ){h4)y>(E}{P=om2[K ֘kq|,IQ.'W|3vjS.ҧesc]*Xq?{ibLbGOC0Gdf Jf@-vn\[h~LxS~ΏRy%d~:3&#TwRkV[:QfmH;mx ljcQ]K%%~̘[J8 Hkjo_<,"{m, xKj>ݗ(+gZJFG=/mF+xvC[1M?cp9#-k+=#(R}Nmŕ QIY"a!bmn@D2˘gR)eEŘ9FG9j@"t^}NP>Z\Xk.`ff7>?(!V>@~쏱g,,.lajZ-,-."J!͢X,ߎhO Vձo0:I@&k  "9&'D>l AOaJ&HKWO(!*iMd4*х3,hPv4SjGq!fCސNAuiHjBٜc)m0 dz.4_pS`2I&hWIjeoph{ϸ IDATEShU:$!53p.n`TgW9@h#m6yD] H8`F"tM禨qm["-d/cI3`Dd `}3={?q?j`ey?FGGpk~= ~߆;z;!Vٻo[1g:w~s/> t2VK 뻦wz!p5BQ~Ty1- aNjqȊ7RP6~OxqH[[`ڨj77]fpϾCC[Mç?g8xe?O}S~lnn`hxƯ;095?tq#ڱl-0|^ ff?I)1=+.K3,҉%O=L8$y̹8yzh&yt$7!nleNқ]N|č8?lt#iuE'خHt|v9G|y;ŷW$3FV$9Pb8GNxu`hVURq'c?}݋r]fرccc~{FFF1<NE/y L0ɊcwԚA! kc/II3}\IJ'{tJ~zיcjm($qI}L&mdOΏt[=^[O'om%Ny7GIOnƽpCE֞`Bou0$ @H/p%,)P>'uWI>Тqax_gx%ft?ܭS?AJg|3x/>_ҥ9>u |ֳߏkRJ\Í({syO=SA8x=OnX7t|םw I!n}tCD\xz~z :^x$y"m7~έ$щѩ/@)6%7- ZHO'aD}K*f+{K [a+dZM0 ;kvUvh~=iW!N}خthp9dR}}m;q=:DΗqa$+ixq}}R0ΐG7A}kQߤ8$ P>>NZ4,!_-${KoࣳxZJ']u29) {}T?v;@:(Odg =ݬ;%$vq ["ݬ;׭q@D8it3f܏K>=8͸󸾾uU[m̌ ##RRDdBqmb!1$70T<uF,O^pm:1[Ha.,&bw:uVVbX)6h qۄ IBS;Pi`[ QR_~U-6 v6Jp ŲpST(Anq5`|i?0d.\NM(HJqyq;&\~q?g'h;~p|41I%e2Jۊl%Ѱ !`#o(Y`Ƥid(жR ek8?B%"Rˡb$'%:YAm wh95Atl7 mz;,Go'Ia ;Q;0-| $B`-X"'C|nQ[y]}TI׉t#֠ $Y4E/y.sL-ѹ~RJZ3Fmt)C_ODm7;%cʇ1(_"!nG- H'$sl>>>tGK\${>[qzqɓ;bfWI0v f~_jBX([ 4~),Y'txJz ?%BPI)/O5UR:<6e/*G-Bx8 +Y0V7 Ղ }LlՏ>3=755/R+YR8.`fYDl." D>9- 9Its9vWdql-N2)zI7)g\>zqݐ}s)b* d$5h3-Gds塤1ZRD_ִ19΢^o`fv>0Hx+Ӹt:GqY aϾY nlmpd1;_fqR}_Pnۨj鱀j$HRj(N7MdYud2iL\o4)уHd@ڐmW1N4(O~wǍo# 0$i1mweO!Inxu3N6~-ɦQ),BF6E fS\J CyR Pk %051+@)0'PFM IIR`%ђ1NJ` TZ@CYZ2Lit؏ .ZmqXA)es5xze_m\P~֎L.1YG>}⇥Ǐ,^V<5@SE.'c=TWqP;Xtpl ŘV|B6 :T6sGIՐ==Eahxt4q\8{RJbiaQ_T1:6>LVe3gc|b Ş"*rVWS01Fl(vlYig!mHO>r:$B$@_#B-JJO|?assr9(ױ j*j.]BTg>t<1O_$IyQyhvA lϹKqt}zp>^9Wy]/';?/5G_} 8T3#\Pg i%s_nXJH ZD 4ahvTٰ΢@O܊L `"B_H Dq7[cr)tVIDzhhYIM"=$S`=Fv({\g$G Շ,."KX$8N`Y_Rc"w.(Hbi^-Ŭ*wri[~=VF1$׉&tZ \z4܇czJGUB6~<:͠^#Ncue v lQoo/6`X^ZvLO!ˢ} rq1`tP,f;Y =-qd:x&$ /} r >|&كUE6#xΞ=%h4g?o~x{ރ~||G 7܀Ç_2^Wcll 8vwø{{۱sNr-7<9xK_ RJ o@ć>! /} ###`wΓ/-/kI$tҡMjۍ>w\>ǢƃV# Oa`/&ƆSĎ~ /@OL*j/ *ŦZ; CmV+Z9R f&З `oOI4%PL 57Iŝ*r[g~5i ?5/mţjV)Ok L^x+)a㑸6"O[t}…5)y⮔~;F (Τ& K:ƃXZJA[++A&d3iLD^G&E&9kS8}4N;B1]L6l>( lU06>Vt:l>)% VKJhI@щ6pe ߌ# *o( yل=&q%nB=q2^[U2ܝ^:|COO`ddwB9|Ν;QO} py睸;𶷽 333Woy[v~~ > ~~ Bǎ?Ou{w%ekك\j}|}x=W.㡮'թm*> Pv'VIъ%n&r염{z#WU7LŇP̥ =[&ti0b*#Hms)h@ŴJCF@`!1l6m}S1Ӛ&Gu' utv4ޢ6dr)9)1A,sgU^30*pz 1S=r*慢jhT j a&zn 5$ N[nxpooIJ)fpW2櫯; @`]ص{`phIګ7+:?B=j yl|L+r~rVBVСfɩ /]}u9bN^t wB[щۍ|z|gˑ0DT1U;~^q5,zrhBC(f@ ` ᭽@նD6W"H#9`@ X42"|-%JM` T ^ J]Θ*%wv%RDN*GHj%؀3i>X^ VaIIk+`D&ru*r.EY@QJXIA{N@b B>"A5n1;6[$g%{9pxTS@ *6ZXcD&\Oh_:u ~;=w]8z(~7~0==I/7 ?B__wVÎ;099iR)\ _B|188뮻088׿[oN_z} 7j>(wb``GA.s3xّ5XƋoqy}>zD${\nlE&''g8Y|<<)v#_O.@7_|_y%nj'^|Lђ(UZت֐iћ;=!HUp X#E_KuB'II,B@| ПH o?|U,2euA Q l>:jm3E.5}8ᅮQNa1L?im/ AxWVdqTt/R d9I!h _F7vSd2(mi@ZA>:֞ݗJ( "C5MAbGqn_3#==~y?kp"gXojRUXm]=+h&-J,* `,EY0_ 0(5HɂDȇm4o| >MdO{" -MQ '+o潜]sh5[h6jV+8t M E?Lp8醂=Ƅq+ ].|8S KDkr3BZ$ !.smoyX)!,}@Q2[SY~b;F^Zr_;wf{/a??\gBOSR/xO)|XǀbƋUk(R9ҋL3B=&xX;xA`4@#ـȦf)bmQQxBnT#yIKBC$ UmoN:F6']L7X i.:Eo$Ɇhk\OCLF%}B R)R?2Fs#,EZ|- #v)*,lAǹ+>S`G)~U3dǩSB `ջ9i҉8&Nux[ew&D%eQy";>FN+jC)%7uG۲gjh:LH1K)ۧD#Nnht+c7:t;Nq퟈-}>]];&%7C J=]6ng-ƝEibͅ4֛@5oI T|PmkW `V`i@%џh2h!u#<ͺ8ZsGa xH?녰uf+uZc=%Cp#i-Qr+ IDAT;p R%&aJG?IaEWZJՉ[D)͜ #sn ~?+F]m1G>'IKꫧg':uk/z8 u+ NMa.5pU7vFȊ TnCƗ\A'@ʹNq4νC8 ۍq%n}m|'Χ+wyv_7rď[M;Wk[-\7±sKd?<>6B&b:^"\׀v x0D]P2Fb^ U#ޭp!h Jx+4قF;R}<Ҡ%LߨJ^ir윷6IOT>|myU/9<PQ(ғRwcŗ+H0+Q٨1$A (=VVpd2iaT>tLepqVGiS(mj*zaE ,̭:*oEKeW.%LO]oE$)ʩCNRNONSFN.oI;_I9o>hNN'Sض>Eyp[ģ؎a">}dR z &zr)T~5(:9S`z VJ)!PmItw -—Fut/ћ ch.5)W76__$;X"$nzJ.u #%u/Ylp2=0;=qVy s4dulɖs_N$DP";YzTkqlVd&`)KCSR!GfH"޳ĵc'uΑV 3{vaxd=zj ˋ(˘AS0g+8p<~8w4zz+\l60{÷x70s vV[VZ+!™ d37{{߃Ņ%4M욝ҙt>{N+u>V\ pN ?wt4dq%8 n$ێNd Nt9mq0NneI㫏>?/Ƕtݷ#I$[u+S'n?!@JYp{`Kf (MhR"|U^ ?TzZ4-÷b3zƪ*N3GD-$DP "y:4LTӫބJU` ;XDL!hŀCj9#iH~=+-ر88W'"T`q,a}-g$ PJN6$$/W7 60I4-T ŞpRC8u0R"y@D061!r,R&. .!hqAFP%C>yʊ2A;@drƢ+h84%H<і>;lBQo B[U@RV ogACmLMOl;NGqc}Cd:Bp9 keT zt:9}7XXIin].JWc݁/h6cqŁOO6ygav42я{c7ۿr?@݃ί=5"s;ĜwqcUmD*@*d3.@6!ZVJ)HUzID-{֤{dC kE*e B-z3TG$~56V3X:q&rRd -i2/17(^su^Tm[Ri`(jT'pljI|;om₆׋sgc"߃|>ɩI]=gΝ>={ abrŞ" Lzr&&1qfўhC19r-0VW7Џ1dYu{HC*hC===HE\g^GqcݫX;wd`mUOO6yJRCVG6K*A;`Es mڕ$ӈe^2i&l6t:w};G뱦Z;r#ގT8",ا}Jq:O*q$7I_ꔗ8MޫS΢7 }nG$n%Xn9;I ߱=i#L'jd`?B>\uaF4xh?o_hS00<qv (ku>&! s+Nadl$j6ʷsmeivy{#n+1r'aN綋K%^r=ic?Ag%ZblI xYttX`kvϗ/<_AHyӁ趘|Xn g] m"TWOɂHxY_Mv֑fݒ$ڲ+tۥwxL`H蒇*5 3X~B"T#c9D}Zޣy3q,֢SⒷ%^ Psևm\KH=~k33soOdPgfN;Y"%PD{IH~bXؘ *#Dړ-i;X<ؾvOVlL#wZ7XF"Xuz)=@SBhI ;@ʮ<,dVhn7rJ`3B9(SCX^]`j&*:GN+DDGH Ș+x[ՏJve+ ;P *(r HC'@qФTrfami^D:Ijr:B $F\Q NHu&cp< !49-XnRqQ1Z}I@oj>>}^UWޚ^,R y)Cv @^MBv8CqYzjYFxbxt*IvI)* 8t`?طgol`ltT Nĕ`sZY]}{QZ0?}֫uN8y  8 <0:S)Qd2옜=<^33CJ`rb=xעZNVs䣟 V{A,oǻ Q@>{Oÿ2|D6«{GM }Krv`ϼoq keR#E\ZDO!g}7:-rT_ߢ'oOGy ~O7nf?}7 zJ;&Igp"zz-P5PT162V}=hh4Z'Ӊw{ b ]U%´.A%#y1#کa̓ 3 vjUNV/BtY{swrLa%d:A,^e$lqt jh A̎ey>\ cV!2{ϖ69`ssL==f* j  ,fX1W[je&%_$vXB1.  @O۱rxS<`+NT:ؽkV ==8\y1c +159F+ !XK_RU6S(`),.`vf*UkͅEJ҂2i*IƾYvF>`}s w?xW?z>GZ }-?WQ2F ^2ݻ000 vLN `\KVVEL&ٙ]މNֳUZ{ fgvazVW0>:llqi~gϝGVF#|G:F_o/Wۃa6h~cj3P,P7"[ڷϩߏ޸/~>|-G>A{bµ&sퟴM ƃ;0ZƉ +k 8~~}=9+̧46Nm;̯y?ID*ӟ_6O)Q420{ x؉JxXE0zVGEC4PʣGaj$Ο!,bhdjgGp}}t:͍2ffwj*,/hZVY\q!ͫZbue ATz=FFQְ^CKDp=nGk3h1ŀ'7x*½֩UOUxl6{8667Q.+N9ZQTU'02z<*PG&\ ʝfӃz6;V >FGp챓{o$Yr <ﻲ*軪OnuKB+AB,Hl؀ Hv iwF\jInά3+3+23"ȸ}x?{`V?.w{=dX\^Fёau k58j6X^YAOw Po4qMS5fҘAJ vbxŷ^2~ )%d ~ߕoZAu}iO:"$'X6cXDRtS8隂G!Զ8.'GhXn G.FuȰNX;=UXiآ+yPd'%Y<**c?X/TQׂ 9  IDATݹPWwOckkS3S8) yl6QEDG.lo*rҙ ffgYnlSTtF/o>̝ (F&`#qqa TbiW|'.DO\ܭTpRXY[#dzUb@ݥJ(d5KXO]d\6JEApuPE\?>@is-@Jlnm1ކ֖Nb6#j[BE/(j1mGB櫦UyÕQ5gX?Թ=2ό':=BR?T;>1^N"S`B38:T#?Zmu޿S}*5R3Ԫ5,]YBo_/GGj6:?s ZÕ+0<:fl6  N;;OϖYN11dga /_uN $8J;']|dJ2<)H )'6y)!@"!0R@J^:ïJj+B6K-y>Q8I(#dà?e&c[ y%zV*dSv F^%"Pk<0XwȀt" Pb/o-.'7I.?G? -_̏hڏ]ApI|?h-ttd7߅L&etH#<~ВX &5"8 Q9_Й-odWͭF%~.XvMCF+[c+G9 ͋4jc- 0B\G@si"x1N1]u 6#D&AyIS)rylmn ===(( Ylom#ɠY@o_/11=ْu`l"xYh.#P;#lۄ= %w07<O\0Hvo4hTl5hȓ&c[ma~*j٨AD?xQϹ6pu5I%'N>\8]h߸P>@U9P44G9B>_P ZgG |и/4g7D,l"i=ݧ]r~0 ,CgP`iLsoq`2=M<5D#&Ugb"tD5rFc,.>R]E([\@R"@ vG.\>#E@OoeJ*Bo_>HR R(@ ġ#Zd.8>{9vMe.0Bhi'1%ݭMTkxRVP*}~+?{n ^"1kU|'Q.@yR{-im5&мㄝFDHBGAh>v֜M /:ag~+vH`D`JuƎ(< u% l]챠|EH[&.jN@Z20'P^J?ik?\4Ż壧P|D.Pe9lI]s:hq c;m!Ӗr9]gMF뒨?<69mOݠk>S(ml|~W*elHSϥֈƃvL6kHtTJbA y@[MX,@hVͦ<NRI*(/1օKЈ΅m}F7©4hӵߍVa,B:҇ig{uPlFr6`xOhGفG}[_Rico>-4OsH;$dr 'Nd"9ir"E=4tqCGw1p(Cv3a&"⼴Ip.پ袲FN|B+\vAV8DySv|W2ÖO}pobAtds@;=y,B[ۙӎKSNeXqszDkvi ʟҾ&: / .q\OJҾ$?. ?sJS/1?e҉-Wt_L@myFogP}H;"B 1HNTeH~@L}ܤ67jJF3>4@ua c(=aS a1ևW'8kjrKA9Np_xn)D[8+&tm d!`-hf w"A kI Je,vZlP/_P,`yiCÃX@µkE4M̟9._[8.-cj$P026F𻂛[Cq5R)(t:P$EW7 >1HpuxDy~:FR4H}lB%\l\IsE]<ׁ́/qr/./O>p{y"6}'HHNe@/6p=>?: L+MT 4q]xq>xvtzȵ) +=U բ7i^, :r0:P*.\D/֚IM[Eg4)\'բ`4 ֆc|*ʙ]֦]P:: <, Ԅ"zBJRmqI'3N/Ⓖ+qtOIw*\Xh >.8o >>\onqyq; Zwse׊^n{n bZ-lRbiez/%np XsOS,BEoU}C0%)]+ɴ+z+TO%sЈVm"NTj(oW &󌴺׏o=Ht'C?{MˌQ<((>u wTjd2,\\@c#¥ ё!J!_K?;+KEB$4蛍& HsG,QBqL8Y'u _~K$(c{ar#3eUjb%kɂ;/ $%t>\2tn+JRL86LoW]d3n|2}~JuN*ʥ7ե16WVwӣ8z XL&~(VQۅ]|g|oPā1,#בEDwW*G0i 1q=QPP?N[“=x'z:q cjJ^v{$I3_u@F5C]/BDFH:- KƷ>GOTqy5C~|x0,l)^H_]/{ &ub?63;u:, =n5z+A{cm^<b `a2rBE䤡drx`OJ< $%_Z=<\~ET\`$kq>I{ա%E\_8ó]){6>\[P峸ގѷh6|K!K?81w ߿.tuaa~$ i_ۅ^<;Kq|ӿ_/ほn‡6|'uOw~x9}a<>OB%Koa_(R%b99B]gA|% =OV\pАN|)0{և9 mT|AS4ȴ^dx%coTJRHwcR(A@G: %QLvdȂՍl{N!#:%d pCtlN!5y)Z:ݪ>{IR,.DG\uGRKvpP`KMD빝o|':>]|4$踎>=}~q}-ׁuE͚FkEoވn7|݃o<"ͳ38~">0ߍs|/#|(lS~ǽx}aހ[>/431[w^Mg?s„<#%d351xMTlzvsc;[2M6'Բ"2#Xط' wh PC|[iߚ~ >W՜t`A/9)&±æF:6N'"9$'$|)ҵs;KP2Y`&$'Gԟ+$Jm%bXS  tUO}B}vF`L?E_VS,2L,\ю/8yӁˈEug1ds;|Gӎq2\#sہ"YO['GiF 'q &z:m^Dڼ~;~#s*՚ebt2~?~oX$Je9wE_x~=\]zR>oYۄmC[$6 _CRX9Ԥtz"22&z~'ItA>,SH/ږ0r!{/)Ob+7+ެQK'W gxdvSo)r)$$J\S:n/?EH޽9p1uqr]KG>xIs>'/qz*w\^.Zטt">ֻQSϟ͘'>nL}|/6< |mfx7R#3GOFNpxSXZYǛ;T*@g~밵]ƃo]y}0^xmw=<~&9v|x闑ͤͤ7DIu.Q)oxo+JSgCaRmT6d aYD}ʍ^M#ཅ꣮=AYݑtc5B3X6=+X, |f(i5܅6cf5*u%WbtCCH(mV \j\3 ۮ\ߛ::j5LMNA'$mNt%]]^.rג-ԪU/ 1sҵѶB$y^eWJ; }H+tv> ?3>CySod> j (MGa1V ;'&svUMKf$ vBﴃph40<2l&i67Q. N|.]vp@< "|0ysNi_n߼H+ӥwvi['550ڣ +|7QN{I4X~3<{ p?^O~C6 )GJn]\FW6=mtCqصxN/wJk> o Σ;;sɯ};n}p_sP껦N]~. ǥ'ү]_Q;>]Ɵɯ]W|޸tIeH@w=tWZ:79ad=gVoD ͦܢcg/J_5ƴ),=)F8.g·̠tmړ+y!Q}|k^Kg.yM}ïPMB,auMGh9glQDʌKʖJMt:pJ%Z-=}*GQjpeT+y3id;/T,gNΡ[fq.k^71}; r񡋙jg >s _?DD aJ?sF36W@'.n,nOg%#o|YqӞ$`u}ƣMH똫h}2\D)i|Q Tnm9auI4kTMNKx = /+#F|/!`IzZ)c+<Ͱv[n%H8JbƸJDn昵RB?i Xogll@]+"!:NѫRב R}HӘf@pkh2j:VאNp-7^oTFSCqfkй% Zl[i-d-^hsq2h%"_vmېRϾ't<>/7ǧ'M}/:zˏwи.Iӵ{6Ӻ$0ݮdƭ.9ڥNFOxO0ڜ7K׵6J; q #BEDhԇO:|KU!aTQTjG #x5R`p 3%:!Xҧ⨍AmFʹqV`IWp5ypmokË*f\SW, Ώֵ7xb)y=xԿE?xrzdqzsX-~y8#vJ4I2\vp._U;$u؋ڕdu* :I@ o={c^;>zf~'.bcsVK{nWr+ElT۝GoW}]$TuObc}PV^C@?Tn2*%0S?A>ށt,PecӱuR_k($|bOtXSJ DҚ1>,^mdbISU| *l?F&i<\xZN_綋O?/|/.IEqeK+x4c/s.NJA\l;ga>5Ȃ@ZǭG sH䉿E.ϝFqn,B9M F/ FFQ::FjA+0S9P1U =AR6Ab-Gws<.Cdըi?`$ @oxdãaH)k}Zذ֎.l"X/k"WU-5ݙ)H#[}.d/LcxSZlҚYi§sj J"& BP@&!z|bx zFt*w<O28,"} UH0<n\QIsKOO.=GFqr\szśf q<]etw決,:^ Cx=G]-Vj(ӝG )5Ghbt )3U{ÒX8o3@Ҩq1T)@>OJZcIU2LJRi"ܒJkQ8汶3k̝J Q#$_ :FZASdHQ 2|QIdrG a˓]Cy$M(@dI'¡L_7U!'^Dom0Ij$%\~2;X_aƀ+mfLVm__*q5hT*auugϞߏk׮R}/yEM5LLLhݑh68}4vwwj008ëZ&^;~[)+=c@NL]huDy~Kƨ8.mL?] joڱŵyqɉksѴcQuGgquk%y n=2N^B#!vj7gS`R:: uZ:zm:jGL|3ձ/nǑv)ҦB57&BbHЕA!3C찀Cb!5o,=T?+3YT+ muQjX(Z{ -( J>@&s$[.8EVA09(Eg>:Fl_m<:I3{SE8*(15>\ΞZ o&|IcnnO<>T*᳟>:w>~G~_qa4͈l. bmm Ǐ@K?f1910+%U";Cxk/ l 1:{7NreSXp 墉?OmqSJ8|.xOv1yKGg!-d3'I Y7R_$z5֜,A<yJA4@nOCaN41f!574Dy~c>XZ@C0z}d>vj:Cr1( MHM:g3 ֻs f/z:zMt"dV m]|ed}md ƶ*BD8lG"@7*-?T:V| 'cǎ/|vp9Kr z{{+ XKKKQ gg?YW)%ҙ r<&099S\U,}wĿs}>;$å QZ.qwe#+= ._^􌛏"45n_*VT$>ob y()F:&ukNLe&s d-Q Ch F n< g1 HUȋ:/9]C4Gќ#2Ny&SaKnEǞӮe OO9 ҏ*f-WDDĝ@B2h2KB\ i$@:QFOx4d[1_26X#HS_pp]RIJ000Rԧ裏ѣPCC|;xq%Z^B~ x#gGq>c/WGq+UIv$O8J-E*KO_{`~D00*;@}:bEC眴2U7TaƬk֟KurgVo|NQ&S]r>VFzE]|k|hLŭ"J"h6[s(JNmYjM7Cl6Q\/B.r6x\EggowDO xWQ_Y \~#uR[bZRW"o/.hHv@^}@;\|]mO*%3Uv]{ok{_+'NP69>1R4|JiT(=銦ɓh쌥Q'ݥwTJvm[踖xv5N GrAč hU~\$`᫣.˧[X^[v;6NTcx3{+NvNƣ_ܹvCEoO̟|NI>U+V$T:>Tku NCX(Y; ts%usrdǶcOcIPgW4nϜ ( a=5"Cj=R, E:C}2lgȩV_W3ڌ g9ABQKFP&>DGҋ,Vf@ ˋCOo'qsPoj/_Aooc_c  )̝;^`\O_-ù q,wvQ,}hI(m5CfI] )8X/tC@Y !iSh c#hL9xldhxi@0Q;Z2usOl4q ?^g9i*cw)Ur4M#[g22p+2OlY6: %4 4u?v-]B2֮c~LLCJBTv+A.GK h$qe9KA\m4M4 OL^?p s8.=4DU[ϸı=\$8ᲣI?qj8=]9 ~|᳑;T*lܧy߸jd:::pه~џ?HBD#;ކQ<𺻱w-X(gpyq t};pw2Z&pױxh6xo`bl87=p^|Uc~߶I*>zRɑp!6*&9;B Ә$4H 񠓱&gEL$8>w`r] mG9@bzg\F qRkWND@s y <‡{AlՆFcHSH73jB`ygW'O#bfvV B}hHR:)vvu"Na߁},@#B !NczfBt:p-7l"J Z ӓ..fGnPhVthzK{; / $u.÷F>qn69wN\@.iupxry>{sʳͭ-o;a{glt#zd\.)%׋X/n g>,,.cb| j Z {{߼m|`:=%xQv) '.?:6؊g$X3 :=8@/t=_lweݷS]6|nK$f N?VR,))/v^x[JV3t'' ~Yq2|3I7ʷf5ݒc-#z~>< yBFFO~N ??j @{٬q Ftc6sjF$(Cp $Vހ:Y @Cir|`nAyǻދvr  ZczMDy\;gNDx1ڟS{sǞ=Sip6%qP IDATj1FJ [2'^h>щQ_Ki -pjq;Y rٖ~SJQ}mƖG=5{(.qv丒K:x_ަdO'=oM<>xGungs;\UN.;|7\v}J˖vƜv#Ob|d41v)&JQ}y-) :P`ŠoYCf]πMyi_xi|d2o꟰of\^XsXE}eqQIK uUu:ʕ/ԡT/!̿ɝ:|BڰYO)_RJ@km4>Dyd)GQd ub"lEgv R.'2%*qNCf<%-:ç 5h`t"%r)^Ap"zQq “sK>;|zD[Nͭd x%`]x7~IŵdwmWߧ_;v۸J,{./o0=97oeY٨O zc$?/*S.Fkjc|MaZ#A#m;?sf&vhyI>zcO3c'ԉ7Rةe]@;JcG{%W t̅iDXg`.W2rj8w"քs-Z*#.`ǜAkB|`0 S50-_ ?CiKBH`r h).M|Ծ9Ϙ.:2v}eT?ةJ,(ԑ@O!ytdR1.:nؖdiG_ >V#eo;~;#x/]Bʱ.3Qp k/&nV 0؃HU)6p`0ɟ2JxxbVک #P!SB0#X?кF@W-q wMOd-\pLӓUE65ڕ`(`O;4 :VL4ݎMdN;ȶ+.y_~td%1w}2xH[6R)[J]r ȤvkMt(nUqס~Sѵuq.}%"3Hӵ}zp|q들gah;L< ÂԿ]^c$." KࣴP'ƆG4wx=iv ;i1P-|v8Jx^bulاN>|'Iq`*#i`Hgr=@ ,#Fr8G8[_f(UU|b)b:!:X>Ncp?,bfk+pYuT5̟9ssl61wjΡQo_F&C[`l}pIGps%7i&/8v]+gL%%_/4qrK*zCvd|ꖮ k\]ƫ6c{UwU )[1}U眿=Pљ']r|~rr=t:s&X%>^+UkxF/Osɱ$& H,t/AU)OË$}xbP[@j҅ra"N?|޼auO\|(nPְBgfjEJO"jES Zt*4XK. Ta@ _\w$m%bŕ\%8znt:Ff㩧D:Ac\߬jŵ_Zs'\jqJݙoC]}^}r|'$v׮n>7FqqpC+!BzZ&I <V7$3^8~/:Rsbs{7A^IPBʻu|٭2tFЧKeHiNjN_uVjT@ !Jt^EׇAiZ'Ё !3AN ;!v;i&@9Y:O#XF:l-.ȋ72`_j,5. X @Aq2QGQ9PMڏ;{\nϤ3h/f2dsUtvv"aiI.Dn#04 f϶"pqIfړO4Uz݌}&lGI5.%q.}U+WZlYVT_~B@ղɧЗO#-[Q"@&t#?#?8+lG᳃lăx氏*.tvee|u (`܇7L;/abZlwzuqqq }xٓƑc΋g11҇ێL0=1~<9L Vo`2N_F>߼z)1ۅTJ\ՒK'.1qw,09R5g+ sH$;xt1N}:9mT/OmlIɫrȤy3jHc)N} Pd;ʀ؟pץW` H ."+|mN|}ܢ<I:'H)qy>yZ _?BVO|dnT5ux] 03և<{/RTtl/Pڬ);|(^&ѴӇw:7mv$߆{T2EfҘE7Ϡ[C>w_9+W7 tq > P7 /ϣX*Z͇&!!jI_N/\\!Iakz#{셫x02؍+W7PEG6 |hjyvd+E\ZZÝ7`u} 20+%"IO ȉݦ;*^.'VS=&:Zܠ2TQu$Wq~H.#@\1]=:Ru H$c *x^9M5)R2PH2FFfA ;˧MC.n R)LMO"ۑō7`TB5 58ՎE݃QvPOj22l0ev"߶`] B- 9_"K ŽNr??CXtO|}p{gek d)43o kK}wjv ?Ύv%[.ގܾ'5ON]'>z!$*:^9}Vp ;2fR |XXZǾA4-lTݙCW!W,6P/^9}wݺ]4MvPw(nIS@q.Ɔzq"xSqmpr~ @ow/i\@_O'J z}=8Ft:N:2*pr~wݲ"Ƚf2+=<: ޢs3 {+Eg:G!22B.rv9ERD8/ߠpL2,>D`/C@ {ޞQ}Z.nU\#:& <[DyP]p] DOL $<}-i^_Wݳ?V/W}?]0m8|sd/zqdcmj/ܧ?4+c+I}8}~#}Bo0w*Cp[ObWnq4[-z NTkuwk88=|G\@o'{:@@.jZm[š:2pFzeqyiCD*%PBVJzf=ح԰[cr]vMBp=5IbC 0qu??dUb9TXδnaJsb ;ueD,ebRЇ c8 jn4|r$-jUT[GHJ0rd<|' X.?3TDl"7O|lB̙3x|O} r >Oxyc= |@\?oy[w ˗/K_>O7xg?YtuuYF[[[r[1s:;CZ`cc#bkOhsg)>:5qq':>;(M-&5Z8tU ]U()i=zXg\ ppƇ4ݍ{W!9.]7ߙiȔ~i'=><УzLڿ356=e]wN ZvtU>Jo0AYcZ-*/C+~k],)fKix8'%p@8POԾB0 ۸RTD<M$s% #tS V4=ij'փO]5QDd& U "v4q"!*0bkV_0yJ,`2ᮝTO_qo?SZ 8h[__ǹsC;G,Μ9g~k_2q{? _җׇw>#G ɠ^o;>byy+++BjԩSɟߎoۨT*lj'kk>|3r/B'Ob``tpdX4 l7~X֕N|I+$w;vU#GdK\ׅߘKN=wJ&Q CHDB`4 6s*4UaT!;D Zp Ik`bIʯΑXN-7h[F} `)USO#5oKIYv5 ɩ&iURQ2'**e`/S%pGM.4HqɐzEڥݬK(#W;%&O//O~_W#GZGE={<;@ww7~d2o>Gűc077'|Z ?0Ο?׿ ]]](ַdY<|+>^::1=5F]_̾}rM#>>s%h~\wNǿ&%5kK%\^&eǛqRa &~ a z.H8CeO8n.ޛFIv\gb_dfeee]k;\DP#8;F1=CXcyY҈FFc 9cʢ|lR6Es J@cF{uuUEčn{!cr^tB!?``fv&sfϑ0vJml)aeOV_ل>7֍J̈TZg6*hmoYuw>;DƱU8sPH75^1Uْ -W=xSR2EiswM#AS_ % 8@Pq;xRE6;@C:}<=LPgg]O<pg?Z۷ҥKx"_* :zVV,>,>Oȑ#X[[ç?i]7Mʯ N8|fGqw jPPp2SڞWhx/7ee$$ |QH⇕ :k7KD7靀T^OV*2H>]ރ>-sab1M@o [cىYr044'104%+(2&#xvQko@O IDAT"ʥ2 㾻V3'.cf~#X]kюWޱJy| is2m)$="CdnoJ2e#mՃvQrY&G OTwArd(CĀXmC 8Ahc QcvT*8Y;λꥫR ̙.]MXBU{0 یsM)2zp͉ItʖJl,-uN\< we>(Q҃ ٥HVFM/6 ~}oy/"GnxݪMGDr<}q1߅^03m=u<)\ ga' 9yf Oq4 q=O?2iL-.z3yYؼF^7"bc"imI<: ږC%&D7Oi{7`hhGezHq![p:Lϛ<[2>ؿS;}MoߋzGZmm8 C!RAמ$W_xlb|ϼ?mF l$;ONIPS H=<$crq(/2' Ϳ}J5c~qwa ˫kX\^CgG;fq]8v.<7RIatlis>tԪN#t!hJF3[僴>$ 3X){\< EhZZ ɾ%AŒvNh ]Yy^Hf7ڜX]i(-NSyMz})u@^ I['ag }v6ɟ #xO-f:b%/i.fbT |$JC%5br ׅS=q!=B%c*JbQ-#`ov箌 ⠇0NE pӾWhΏBYi_ȁPήde|, C;9}9\}, z1}" &bAR_Lעm,F^l"ҘExc~z#&OY1nVI}ΫUB\P8zhw `βȼI!Ҙ|} 4['ma-=&șMKz6wxscT>2>OЄc@&I@:{6G^l4c6ۥkw2:;$HU(;y2"tQQBi@%-sA&DφQB!H6CbX4B B:5  f6385"_mkQy6 Bt?==xzr;I :/Ȣ1"#6:.0q0ϒ<{{@Y 8H^X܊Gr(Һ!0"HB$&o@/y ! 5VvEc7d؞غcn I7nM~ JehiHan)wwD-"Z?C)ۈ4@1XM El$Ԗ"/-XHdqMp K#zoC@XB@Fgga, N4rx|<@;`qI3&#i3,` ȍ$kТBi&'i4y#'98j$BFc9R }}h3olMy,';:m9 (hOX3df V@1Rs '^5vNS *>5`va+[Gl"<`: )̊Ę.cUNs'LTYpbVoVJNk[.+ءӿdʌm kW}o)hzꉡNҢܩfP/GB3Η_'l0;3e477qh1=9eWpeh135fVF'Ik_>YW͍ksnm>[ό{cAu',Sl~:?˗/ĉl~i\v ?|~111чR'ԞUy|xhS )4$[ }.4F*3/l%]O ><>!@*ٿX4rne$u/a>(@'-:fjE\c<kG PLfIL:S5dphm@q ["rcRy5FʣC14G \~_Wpyk_y,,,<Ǐ7Mᡇ?Fه(7;кげN/'[!>R)?R#^Hȓ'A IМ@!y柳ċ䳱yL,`bzsc((0G8(_,,'. !}f旡s~%2@90j|1ie2GVGr^eO"ٽ`x[B+?@L)gk(F<ďұt]HJ썊@V]}d#GHrT_4 x\JWdPm}r*Fciismmm>l p /MWӈ<|lao,\L1BɸEdR+H//7?0=??*>/}=y$RŇ>!'> RnX`K)%ۆJ_d\~ Ж'cEHnFX)2&brlQQYd>i9_bޤUwgSdm5FQVnZ=!x:ahDDRVdI  B5{c/9? 'u@gW=(K֋kWA|#-B)յ)f6gy"#$r:JOcHIҢ.O7aƒ=Ut/Z*#{6P$?h40;;^E{{;~a\~~;>cǎmfi9r|;/_{^|ɓ'qi`zz;v@^Jއ>!us=hkks^P&|lh-l-ѕxPY$9::4kFIY<U![xpb]K1^24:5ԡV \994:}n;7Yގ;­[:O>@IlpFzjp8~ .`aqR md㡛s?5?(/_$N^!0;Յ:x*C8Ȃa*R.0Vy<|`Ckzy0JFXt@p%rڌ9L }s;r9Է+eZk&H;Mx{uL2Ugmꏧ8Y޴H2ioWBxӛބc~~ _|O<~o~|kkk8z(~;v޽xކ[n@|As=ؿ?~|#>9{߿'OߏdJfiil(("eh} QH ;?׽앧$כӟӐtIUm]EAtQ.k7^AɰUʨת"܁R8wkS5a@ -qu|=]5J%TV:mlllRƉ3cطۈ὜3`'jl6u}az~7cSa1:3V! ?'ABUW_H8Bs\T}1T5=7?jGmZ9oF *fBdPlnے R.c~iSs#!#~n+L]}uAJlFcF ڹ 3EMJ,2EUhV?,<_U@l܌?-E`O͢ދJd4=v$>AkVKTrﶶ266hk+CkfVK5ګ76Ffj͍&*2ZZlJl]*$;r) M@Rm9fm5׵'Ek%!&ZC{&rz8>?Y5]d>i9<=.bva!7l=j0r wea'_xj 5;v'7}h( 4)oUUA6n hSФ"YU(1e袔dwHQYG@<3 L(%D|A%IFR|ai6~Ex(?!0e ɖc+b7k+i _I<^\ޢrIboVT^@!mjE䏥 3 'w\]y*? 2=1M|*g&/D**0_|Ȝp$̺d1X\@xj\F)= j$_*v) ij^.eHShH=EM'A*f҇BEFsZqr&<uw !tl+h]ivC:S'k!ѻX^^d\~ܥ\.cΝ(˙h~d'( S rB&qwY('gX<)w:T-7 xpa{%tPh2Qy|3ixdOkLpRq? }1rq6w0en4Ny.R|P8ARPo vŭsnr2xޗϯe:,>x:Y򚆛Pf8f!w1;%IBCP"ERZL9 pi'." A`b2vҌqZ Xyŵ8̴K(+ܩS=nT,#nzG:󭝗0{g3_bS/GxдK h\VI"vr9F7;C V!u<eL߭Sɾ|Q 2}ұ=%TlLJ$\7:Iy# l!Ӕ /9p'Mmd| o2sd.dZnޚ>`쥄PR'lEX)  oM5]ԶTn阒؅nf[%մgX2*hfhրGcqN.uBcss_Sg⹋tRV/)l6q\t:G&oL)Qv=8Fd> <1gwO^Fv4rDF&ice,}\k*E!k,(|vLf3BLE1;Nov7w6ye 1Ns1'~ ;_mL~J wb󔷾b%Cu}+Ϲer`yeAb'SI] 56&-|yk\%ך/2'VWop(q p6i6)݃}LA=hЏ|ۦ!2 7#|"f-'!Jt-JEϷD`ެ!B ܜ`nvR3 IDAT Ilnns4Ο/3h7pc LONԋ0ycӓӸxqyLOXn\@ZaL'<.3/ ^zq9=uV s3sX_o3FKpU;ssF) :3Y[[SC6(qM1^BB*&Ξ=t'^ăM+ٖ~(<أXL F^)[%$ w X;d9~dx{M7;Oڏ灩P}.brY F/⟥7!V9@w(L-.aye6MoXhXZ^fe,-`fF3yqi^sXu+ Jq_O7w}7dx8S*SGHba3ȞL$yTWJa}mC#Cj|2u>]]r*1q},*zjTc~~]&'&h++jp q6rƭdWRy/x($x,{KCfyGs) qY o0;,pYկۇ˗.m?3_?C(JV `~nJ/._A|5!hа,_\Ȗx(q@L s@_T6 e~t%:xEfL[ ol4q}rf ?w=5tkhZlm=[Bg *WN~jg|f s +lǕ3X\^ýw 3W} Kk8yn +k 46612ЋəE\ATBm/wĹ1p*n;Wqx߈K׽y![1IH}.O[$WC)ϊ5=5el%bX4_Yt/PT7#9l)Qbd3< 1};ݴPZCyQJk5K1øve :Jtvciq Agw{^EDgW'k(1 Jn);}])ShPi\*H^uܵNG߶^j0>vr }fʍ'υQܚMҧ_8 &S?eJ(Xxv܉Wxqm_F0>~*V VWWՉ>0&n {⡇]vر;q=D\FgW=x+Ĺ]lٟ_WJ?<eb@ױwPɳU7}=uFoWv ]X]@ۉمeG@Tv3װ}EOwFzۇz}«G_wR UtkL51탽{cye<M H`mzDdW0p. Uu#>ߙU^a|!ӈ SF,gϯ!iLڤ=g"`p/oXRyMDE_{ !Ykp5`XI֍@Os[[Y#aa^Ko`hG]6D9t!@Z#{Xwt|Gƭwf-݉m}D/nTd=~v敳!?A|ӓwj@ym=Ȏ- ޳ZمyB+=;wg;wchh:^c߾}x}M{LL^8.ܡĹkkob0tKHKeHb}C\ƐLwCkFttLxAx1z!ޡvn|!lڋҺ3IF/}i wcx}uW+66oj*'st0vcwڍ9 ކrYaxƃ{n (Z$m~=?Wܶ'TcsF.-&(-_J-0 .9RP3[661/믜 Z/O"H-ueCc϶l1GA(%e 9>vMͣOJ H t9pF:cBu>rlC^ ڀ3oDfRYq1Yu)oc6HZOX^])emJ R{(<)WIM *84?KkcC@% r5NK=WBr Q~,+ZXX@gg'JR.XYQ^?( rzkޭd_ - ݴ4 AY&&N{;kL6RDࣔ렭P|O(pL"ݰ 3"u^&m!^*hpJ3 U^H@uk({$ < NtBI M){)u4Me^fhu:&< mq $l@ =L}3Onk"B#[vCxs +?n@36O @0=  IͻHȬ} He~ڂ*1`Pk"Ԏr~Npcڬ3Ao/CA,(`D#ZOe6o^eU Ơ9YT&!1'!U18qN|cK} y0_Š'-Po"Ʌ}v|+ i7%0Y4*–|HVvvvn_}h\>>5Uy} OShP/rH eK,&G:w.fR} $uޜCs$.dЁᾘҏmօf `PJ>MhP✛l*{69Z!,dF,d/i$h?Dh1$ Z4<6ظCRݔϊ( .p)/ mBKB_4Vu(4NSe`QSNce׎"ׅZOJWQB?W1#.Ҧ5.]hk`hd+ATxY`5\zg#7c1ޡ z -XErFEib[a][Iև)<HENIB0X-1Y#dء!4ڥmPւ0M0PǗA)kƜSm_!7v230XP{%;E]14KA1(a^F(ҦtB0rDO i9w<ڒd_XZJ$}::j1+O^.S[!-_&Sd稭z}bsbF Er*7;N#/O11Z?wBh=(?D#H{]`1^!zVR_$z/ `Kb I+C#\.I'S|l$'4Ԍʄ&i SY3&̈v</ 1kZtF M;}i &pF%rٍ(=!] 2Ѐ;UyP.A_o XJ m:xXLlEAkA\.mdk9Ė}ya+q$=E#IijczzQ{FTŽ];}LܘDچr(042$"8 Ѵ:YV.[E}_gctkmj9cC1/~R>QZy(!oIJk)~X?оص~$f0s 5eϾxCW1<؇6W o }N`I@zO+~ϕ)`iybȀ/֤ }o SMu"Ac4EG{ cɂ-i?l8hNC,`Z5研ni47q/Zt+PwbN=:C%b})ЊŐ|A^yAX 1@#|\<roEU ϛ* XY:pdA }dtKo|ϟ]};?~P _/2q+`.<)o|?o<>>p}h8<*j6V>0Tb;0u^?J}^+97& 7ͷ)2[d9e֘"mV¾ q=# | xm`QĻ)wx ` DEj"3<"0誵fsk4_.?֢%TQׂPXhq7B,ֿ y2S1y9s}} IDAT$" LEy]^g_LHy eϤ9LBփ/z =]u~^/ݎ9Rc'(w_7x@tc( +y A*Ų76zp 5[}I@5ZgEAڻۺ܇OWQփq~z c(JoR {wcd;|[uԪUGߋ|qB\{5Mͦk ~8ڃ:>?}1܏w^C}X `>hTljl%|H:dI.4ϡދrY2D$1=?d2c=XV&34cE7?APd:m ƏILc]6紌|eN57$ozk3Ȑ*Lɘg}|afaOgةV]n 01[Ld:vH/j-g:&p؝3`+ނ00ѠHSs6y f@$Ik dL^.?ڲ{\*f.]D_WOd,ؚfN:U Гhxt0#/3٘Ex2 ***ԇU!yo+a>D > 1s{̅ # H۵/)1L3 MP{+6_$rfMOxl9 6P^C曀7x-&|OdxqEX؞$X 8 6w[>.Fى{V13󂪷nh''} jpMoƿPio7o~CtۃF+`M mh @$ y~]$T$KcnH ١Hց@W,U(c"搝Kz-1yپUHzݥ V>u&VѬ}t7ޑs*#M6#d-pѐ:M.=ݴ7V]њۛ1T>s5.0 @g5}Vrv? 0 f5 L+7S=LvkQ*tC ֙bE1%u؉۴ $wb?,wSi|_»| }ݘ?o|O˿v~ /gǶPtKcss3=!`ǰ7>$O,A)(hzhBZKI5EFؘ2+֯H&-*wUG[y`A^LV2"CǺl<2k,"-mٲt!ò01Pobl2YYaRtlaAdlhN8v)`6͐oLZgd4%Ohjwio19,.2I8E^ɘdQ UnJ#6/+*c~MӾnp zٜf붭ħSqϽgN_}˿⾟{6o|=~-oǟֿ ?x V Kl67qe[6hnnA(bTX$tix,X$;|HJ'/"^<ci؆j[t3,3xhG:> `+S8gpd> Di[XVT7Z%VZl4χV>) 38lh8B@Y CCE;%B3)4űL\ r])%*WOAڤ$< V0EVt ,^t;py(084V >* ww677-ݗ3XKB ?}}ut|Ӱ@|*ओ_2DE3 pmIEɓ;tGfAQU T/tb#OGN3#V@an/\ƮmY\FR.^"vae%s^\2Ǐk: W8rp'>v5p~8sNGFpU/z:p@ ` mr:@ diVb@,y)>_>e"^J^@ S9cB4FYU4~}|ȋZ*FqN4@xz yt:$9=CWގc7,Wq?矱6z왿#FO/EYZȋole|;FH[(0fOl<0:zHfm%эف#(ݐ~a*d<]NL֞<ŲV r \*0~hl4ю&pݸ8qv ;FI*v{BT2@,x.ږn/kjgjxq-]vT̏T$TNfSƘPŬF)n4*%H"56>TUNvمur--JE~Vc\-?K`#HT87nۃf m0u #2.R0NQ6&{@xwEb?@(REG4IɋH֚W^~IZmQB-o\S-^DOOzLz^ٻ3 te>V>( ́): A`82QKiij=;t董}b(4Bbj+ƖCtC`E:͖HuH}%?H$s &!їƇxyB<ēօl.يDr9ΜFh>%$T9mMB+ne4MʬnK/*{?2G22^Psɘ+9Ft'WzpJUrbG&m7b'7 `bAK飋5fkPLjQ4Pxf쐏SRPhDkdj10tBht , fCnLjAub$VNkݴN1B\Ij#xD< D#Q: j1Oe(ٓMiN(E_EO)"=WJf)tŭ,x(LGz۔9bxUkLnfE?YIj@\J ìY2_Ii%[ (uu2ڢthVҼ|f ehkfL\\IXngEx2d^nKi$5sŒRMkծX'Lr>UwIҷIBGJ'3U!I+nZ&S|`8Ȍ"*N faLND<Ϛe+_x??͛qCTZ%)%)h m_ NW $"OAK蕕 y$159Md9bh߬D2$I,T_DY_ Ѐ<çIH6YI'+`46}M_R`4ֿھbft~4|7N]E@jF72L۬UV]ߵpQwnZ++)UyF_YS3>f@O9`3csr*+Z繀d.dG9Vxlni"(%7X\ "K(XsH$*!v],x"ɺJ !09ifC4Ֆ1<>Gx9ͼ) ݌O/q9H$%]ݢ(D"u(9t4Or?1"'s.k\LuALoP3FƘ\D#R`5 ItkqYM0s0k3+ p!QDRݻT*_԰SkuXcpЁ ` CI7 := MDu PX\QR.p8L^?@YEHH)T2I4C :] ngM|jiZcLSm6Cm~6Q9FZvdzրM[gnTI2|i*fe7}\mRҚ!UICsh ǍfX,'σ(8v;55v$ O˃D|6'fiU zݲA@ CZw ejzc"zqV?ǣ.|6co5|@vlI{lmBQ1 fV f 0CdFdiWfJVcI}ev0U 7kC{cե4֔!5e8v.氉퍕Dbq&g6Trߊ/5PZT ܔzqm4֔RO/<fMK.`9 $NIمl;K1 һOSQ%];V=TZM$ -{T>k IDAT.jT2_Wh:MUdt4%*'3dmAV&TފS IRVVflxA IpNSn("I򋣍rjV_~*dlh"V,Lg멌dڮRvkĮ N/c\L,୮kj>wvɢHӐd&r?}cUq;ՋxdqAdYܥIK=V@Y[̨ל Y@MGa3L1ZKPy܈5 ,ۍ95I~ٱcϟgvvW_}UGw) ,D5и817m6,ijej_>wdg+ 22LbT~:&3n}eZ}W@N쉪'(M0-~fPڴѴʥH=,èVOIqSn"]2zd%$ڡYƽ% FЪV_ ٗռQi)/Ei zkL s>+N3p89y}{Xb7mYгrfVINq+d~GJN+kI_WvA :?iY铭TަvCn$Xy涚Ȥ]TgO+ ~1_ s%}WR}Zћ2(۬J L/+beo3߭@[.aߪ+XNKV ȮiMCI@9N]:[t4ICoj~LBc v>f+= ĤNHB4<3dI3Zigtc}ʦsZXdiY98t3ݻ<)oCQ4mh:$=[$X_T:s;,`g8kSCnfied|;>BV &Zf9p m!ߘ͒G.fI|%;҂| `aUm\֭4 q2W#\f5cd،Qn%;ytj0^AK! ,e O2$VG05L~2ԝ=cL?Ns, Ci&sG7 *%IXg1N!i - 42L1IIRxhUJ2l6ˎ$ϙD}+>.R QIUǝr$R<|(XdY[UKGsi y0$d3J=ZK(M+Êy@a' Iߴjb-rH L 6vsQ,GUq8 /-nIJi H_Hr4b8&rp17ݒB&?5=TFO]?d4fS9nWN_+[Svdz)B 477L$cʂQ)**~jHR;Kx9fGqy\g+(255&t:),*BJx" ky.w{vr248Dii @V^hIJ7P]^M' o D1 //SR\:?RXX4J4e]lOdV| L259IaF$D)(295KAA O[ޞ\![mIm:L-_ I*@t /C4%?Fnv p9PEEEC!ph3"LH%gfHfnSNόijWH_m3s%8s4VE+Nw>^Z߿OCC?_`͚5B4ەt%k^/m|3OgGc>}Ç~_|5$B7=7oDhjnw4=>ރ۷ B߿?C@&)x y===q⭷_%LNNugyMwߥ{á=}4Κ?kW^a555o+Jcc׮]bǏ)[6o @#Ix<ﱸK_G;x饗HR9ss>K'w*hmiɌk{/_ptÇS[[իݻܺu Qo?%`llӧOO<@?lܸ]v266gyV]чsN<F1>6DWb;v Ͽ@43,//_d*5k(---| O?4u:}ٙ{9x͛6ٳgp:< ~0 ry֬Y޽{sW\fΝl޼A.\覡7__sαo~NΧ˼書n> 8q89rPhgϐL܋/q{T իWgϞc}}|׭gϞڵ޽M6.tS)d*Et9B2 `jGS |;v/%H7/M1=M0e&;?veco8ő#GG$`M|_;Lu6ŴB˗ <3?b߾}l߾[o㕗_! ooFaa>I%.D" $)"%l e=݄T3 ITUUcY2>H$D"x^dŸ|2D`jjdžLev,,,PRRb B1p$-L&Y/&>şﻼk߷O%S alapnhnꂥ%xeHfg![Wc?s˿n3;oR1ַhb1DQ$HJI)FFqR8IJEp&O%qR$%k@l"""(rH$ͲYfll33c9s,###>srʊJ=͞TW0=3ͱw!2;;ӧ AGFtׯ_?gKw^t{6r 626>'xO'"^ _Pilj;>Ge9{p8FyM]ɓ'=@CC$ ֮Y{Ǐ?0@ss3 3;;KScn(..ABUջA>/]ȑ#޽GYY]]|_ԩSlڴ'ܵ*yN?@$xtvu255Euu uܹsAlB2baa==8֮]Kqq uđfˬY.Y׮NJJK| ?N[,>;v`yy;[ntrmFFٺu+]]]AEΝ;.t;oe~sn*++ ͛s= p\tvu144̺uq݌s݇7OJJQ7%QRZʝ^?al|/CٲuKzZ{.hgN+r9֭[[o}k;6;wJ?ԣO%|f2?Ja͆@JiBwR1.%.KԷQTRB^~x W].FYX/,bKX-3E eR%;||"mm\~"D@|!$ITUUqimm!h֭"EP |yfرs`w8F8x 33;Kuu5 sͣLLNѡʐ_ݎ8b7,[lv&%ٹW7sFFF(zhǃo~ ffq88v044 + X. =n|>&&'ephD2A^^8NN'03;ܹCinnNϖ-[BYOǎ- :zs ._̵k8wnĉ<|82rn:s4=z9u7opVUJߋWQQNYYO+ qs5<$lk&_Ki>dAQe*bhhA Jd:xI>W\aa~P0H4UPH_x`0+WIb}q@4K A JQSSٴi#wH4SͿg|_2::$řGLcJdhhH$ڭ}z 8{Lz$u+.꣸㲲RB R*beJKJÇS]] GFݻwx^֮[Fb1fggEQ ZnߜMQa!;wLMMm6G_۷mƍDP0 *`qqrQپ};'D)I;w줾)B sssq*+*x82 ՔC&'(--={P_WG }{t;yy,-.Y^&p9B! ~N'1=5EIٺu+b_Jᆳ_w^\dzz$y&a'Dbl6v΁ 9Ymm !"@iRUUU  1:*QQ!P{RZRJ8=8/IIXf ?@$NERh#DH$$b1fffX^^fc)"_x֎bK^cy&1\FsnX[]^_)8y sl\7n6CCD#\˅Mٹc \t).0/PS]Moo/6nƵk1hK oW^EJdf9sL;Xc8Y> ?:I4eiie"SE"RuL2c3ҷ2cn[e`M^^?IDATիTRe֭|ay~m瞣EfMl‹>tX _dӦMaYv-3;3,֭cii߯EAq#_{vb1Mó!fz9U섊 ?:v;Z7Ý;e A}E8w^}~w3aVxuy-8p@N?̿K(,kYϑ#ݮeZ}^ٿIJgR:9Lo/ɔ{Z(&YfzүwD"6T* Dl޼ݎfLLL0==Mqq [lŋĢQmNMM 륤||>`dx_xAMUM TUUr:unݢFB-,no/^FE̵,Ǻ멨dttxmp8{PTTf)֦& JJKijjڵklj㔔ڵkwUUU[7n011ή]w>mm7nFuu5vr|>åK{.<|pM6q-088ȆMWbjrÇɓ'I$8p@~j/]&''5}9E***(..A_y顽É'x82B{Go^NwcQ!ҟ'zW3 | yf?/~L}}k,|%LO{lvQ$ `1HH$owx3*}"B7hkoK.3:>Ͽ<7@?;w$ؙW###x TTVP^QNGGzOSlv;D: .3>>;`||p-[c_Ύ;/F}vݿ墦@0DCS#555%p}8 oL$l6R)@$X, =,gvz-[6e};T˰qc A,&_z/>|,TW@CjhHޱzYŘ NٗyypTV/b20S.#V#d}>u-,K/.v`ofpJ ?/f#6?)ˡajΝ6&7H$lHAsrb5kw%%8NT ۍ051.lv;$vI&~ÆU/Ֆ'|_\(tvvo>Z[[~% ax<ܸq#}٣ްRY x l۶Sޣ;< ^$`O'oGYYS}+H ($yJWZ?y?8 RukfE(4 M3>襣c D Hv|w\{_3~ #PYߠӝ(h2}Lcǩ"$dR=GoJ`06>Mq\WT0<4Dss3yHㄗx%ss||<U =x@ DQjkjغu+%8R^VF$abbJ7@+q1HD~^cccQYY)7 fffBbEFO#"hN2tJ㭷rŐػgO>~ ^)[9ҙ?hcW\W@<փ  l5jوLMq O@&([v'w ׬Q}H$_XP{hhlr;E2AHyl%4)K'⦏$A0Pm3AP2 @8'(2|~)LI̠jxΦuc?df|jnTA{IK _JڒSZp,cbB~r]ۤs[7X(t 捩Ukh {|0_,D ~ z)`*Co?T%iݥeke:ϴuӼB%O; cn4r2APS:FWЌyVELoK^Ro4c}_z.0?'+/3Kbx\$>J{Lv K@hFq{Boku3cnJ/2̻E̯q+sF^F_{8 |R?w2[[>~?@@+e4͓L&q\q'` {U$!T;,ܺ)޶6ET_Kr8k֬޽{466D4d"Aa~ͦȵB|N\4X(NRKe} ʙ-lq+*3t6,LDV%+805ƫŕJ22sLrءԎ.9}~YqNYa|a~$sH͎DB4Ju6׆ &'%%e8ZbA=\^Z?JYݘgb۾>}%!sDE#pU 7C9jQwF fęR}2[)LrFD>rx documentation:liferay_7.png [LemonLDAP::NG] />

documentation:liferay_7.png

liferay_7.png

liferay_7.png

Date:
2016/07/19 12:14
Filename:
liferay_7.png
Format:
PNG
Size:
88KB
Width:
1239
Height:
574


Back to documentation:1.9:applications:liferay

manager-choice.png000066400000000000000000000363401325274564300365330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR&?C IDATxWG.n9.kn)BzE4{8$lX <0|Af@߯s?33xNüبݻ*++Sii)=geeeReeʃT^^566QРrUTT蕻w2R +eee}dreeezT DDDDDDDDDK2v qDDDDDDDDD!뉈b#"""""""" A qDDDDDDDDD!>|HDDDDDDDDDCQb#"""""""" A"""""""""∈BP qs̑aڼysqqZǏO?4$ߔ'ݻ2 CVz;?"""""""5=Rii M0Auuu:˵h"㕕c35gٳ{zK=>~k׮u8e{;LDDDDDDD2e quuu=R||.\wjذaڰaC >\_|uɚ7ou 0***4~xٳG;vФI%K:Fmm<8 6LSNW_}8ǏÇɓ'+22RK.ʲq}m۶M"""WZWVVjڵ=z5a}Ǫ 1jjjsNM˗/פIoƺnժUF| 4HGn4l0}gŋuA1uTcjڻwu{mmϟ/֥d<@`__GC\W+@jkk7|S|*++UYY;whذaZn?7ou>ТEǏWzzlܸQ񪭭իWen޼woF lڴIqqq>={T[[ÇB2d6nc:\Z=6mٲECչstLgZh?s q/^a:ug+C0ݻw*,,zjݸqz뭠o߈;t PuuuЯ=`>8""""""""!ݿ_'N~MJNNTffu_Udd͛g]h…qiϞ=~ްac 6LϡCb ݿ裏c߸qCa(77׺Z233ui_~źTÇ׆ z۷o+,,L{ϟ?_ofߓ_˓z|}}Oϕh#C˗e> ,Ptt~w())I#GÇ_+!!ASNVX_]gΜp QFF  裏>СCsN> B{ҥ'j?|i…/_]QQQ׃z?PБ#GOM=vX^uܟu'>8""""""""zdKMMUxx***:>aoUSS#˥Yfi:u8;v q~&N5ݿ_{՛oaÆ7޽{߆ n wՆ $%%ʕ+֤I4|p͘1C'OԩSc'coh>}?3nz|y} qDDDDDDDDYCb#"""""""" A qDDDDDDDDD!ݻGDDDDDDDDDCQb#"""""""" AQ/GDDDDDDDD∈B5DDDDDDDDDK1 k#""""""""Rn"""""""""ꥬ!4M"""""""""∈BCQb#"""""""" A qDDDDDDDDD!!(1 8""566XCvr8***R}}}P' qDD4`kllT^^ܬV"""""YUUUScccs8""R rDDDDDD=^CC*++U\\!lCuuur2MS---DDDDDD=irݪ xGDD6.ۭV|>@O|jmmnxGDDpz;. qQQQ9sϗazAte? p---3\∈? zm3gbbbT[[k]GDD18kCܩS4ydm޼ٺ!֭[5kh\Ru0t[UVVs*""B3gTeeu%''kȑzWGDDc 6ĝ;wN.]RXXn߾-l?ĽZd_3g^SVVu0|}2 CGuu3F۷oix<;w/^,á/jԩZ|yQ!@(gwޑ*֭[?кl.\`]:uva]NKKիe~'EDDÇ.KC iDDDO z}_5tPl]޹s5;vLaaa9r {*1^LTffƏ7nXC\]][o)++K׮]SJJJ!ȺI&]nO򈈨!NJJJ;Cl555 ,ɓ'yڵke٬a͛7eٔTiҥxb W__'jѢEwi3m˗/gjׯkڵx<}~GDDq)))Zjn߾-өEiǎA}0C܎;TRRڴiTTTw]v)>>^qqqjhh-55Uq 駟f]{-$Cid5]tIa(''G.KwVxxf͚w46y<%$$hٺq6m<""ugfӝ;w4m41wSzzRSSZ[[C)66VϟTegg;ŋu+!hxyfEEEi̘1JOOԸq_46ęj-[LъѦM'xDDԿwZJ=+))fSMMMP'Ilz$pf޽{Zf'I+VzW8""ϧ,͟?oLwzl6Zl$ѣ>}ߪKMM޽{zzԤӧOfڵk]yC=ԤLM6Mׯ_;|>뱼^TI*//fӥK?a meggux qDD{ھ}$8`#" q999~fS}}}PX`A_+JKKSYY$iϞ=ZlYp!͘1C--- q@?GDD/} 8""zc qDDC۷~Nkhhb`#">~#@(0KCP`#">8GDD/}Cn&nNgs8""&પ̾GDD&\.UWWU=Er\jjj xGDDvNSvr:*--?:!t---zQ_2ԣGL!($1 8""""""""GDDDDDDDD∈BCQb#"""""""" A qDDDDDDDDD!∈w{TzC q@0!C q@044UZZ*)NDDDDDc9N98e\.޽fXͪiQVii*++ MDDDDDiiv)ǣRUTT(}!0`9Y?P[ZZYVWWt))):qℜNgs8ev*O@?!.##Cv=nip!.%%K.0 -XKze***8*++ax qPq: n[a?'=nD֮]aÆizav:qℤ qO>^mmm8CwC\KKF,믿 !E8n_`0P1~7]rEaJsՒ%KjjjdO뺯JsΕ$%$$0 -[XCܥK*//ÇZfFW_}U6lPSS 5|EFF*11g=^WUUEo&Mqo߾ٳgk኎իv:ݻwd1B&MRff^oPϹ#aT||F4UUUwQDD_~%%%)22RsѱcǬ!.пC\g>tE֭[ӧKkȐ!jllxz#k2enܸ!˥}$ϧyiɒ%駟tUl6\:a5k~G\.Z_ǫPkk^{5%''@4h\.$i.ө^jʔ)zrtY(+++ܑ2 CG֥Kp84vXܹ:~gCEEEir\/٭!.п58]ѯVEGG[Nqq ɓ'%qߚ׺=''G Vdd 4tPɷ:t x:sF%4ѣjiiQvvݻgݖ<+WǏȑ#s~arufi~;99Y>'պ}۶m@_cjk{[۷%=qY!غĉw )&&jԨQן:u*!n޽Zp3_i˗kСA q}U^^.0à Н;w˳f{ݻ!.oܸѺo .tk }!@W!n2 CaaaVahذajjjp9x`!kxr;v^{5ԴïvOx_gcclݸqCiiiA ql7o%40TVVf]5krssOqӦM톸/v:=oC7C\R׮]SN0 9sqnq.K᪩nw8Zn|>s q|ƌw{n_ҹs4vXW\2!ˊn^k9?+C\/۽5uǎ톸g>9+w5_~GiҤIZj^""";w(77Wcǎ㕙6g q>O3fܹsg}h֭[%u<==j=<Ǝ5kӧ5l0k=v gkΜ9^W'O֒%Kt-;wNGna 5zzk8rƌc q }`vdڵlVڰaܼyS6M)))nKMM;N\\.]/>kXRSSo:eee)22Riٳ8qj*:uo/]vǿ|rEGG+&&F[n>`!Q_*22R&Mґ#G$=nݪ#Fhرڼy5n8>ZF'*33S^7 q_z!IIITRR_n q:7|r }-C܎;i&%$$ᄏvR||>XGz6##:NAA>Sl6=OqYYYzWڕ#I(]TV%&&СC&55U펳xb8p_uqOqOp q/8IϗfӃ$=;6MӚ5kw8IZbEYzkj_zfޞe˖I=ӧɺjj++׫&>}Z6M׮]{sF88CӴituy^mT^^.ͦK.YӨ7'#tfΝ;tlbk(--mzCCܭ['fiڴiUlll6x$=o 8?5uƍJKKURRvڥ ˗/78o1M=9۷OEEE~Jz{=m߾]C覞:\I'OLLTZZ$I{Ѳe:CiƌjiiaCSCC+&8]@71 8pvx!t<>C`*))QUUU_? /4wYZedd(}!0`577r;#\JJrssx!0R9Nv"""""g֕j`B!8 `B!8 }eIDAT8"""""""""^)--iDDDDDDDDDK1 8""""""""GDDDDDDDD∈BCQb#"""""""" A qDD4kllTqqv;Qp8TTTO∈hب@g%:eddn?_~ϟ7˘nWKKK_,h;`'181芶s ~CыCh;xZ'OСC5uT]tɺoMM5rHJMMՃdd̀{:0t[UVVs*""B3gTeeu -^XQQQ4ivܩ&~IԬYj?ú_|9s4qv""tE۹ 3iРAڻw֬Y(577hܹZx.^Sj2sNtzaJJJR~~{ѣG~ƌ۷[7yd-ZH7oԩSc WTTV\)ݮ*""BQv""tE۹ 3ĝ={VC QmmLTmmN8zOÇu\2d!/nx=G0tԩScrZZV^-4+"":iȑ#e8 &v[oٲEQ>Y%"1芶sfՔ)S^^p;0X9Ra cwv2 C夤$>|غsNk3g}}II PMMh֙3gՍ!.kU"Ch;C\BBvs),,ͱ:uJׯѣ5uTUWW+''G&MRuuun5=|ua***.w6߿wk\KOOo7ĝ;wNQ qp dh +%ٳg]yfM:U8`بn+<ٳgumڴIaaa:qLt Çr_hȐ!˓QBBfϞ7n̙3z״i&:z]>N>ѣG[P[[>͛:tF&EDDh*,,Ç5f̘g~XCgz,X'O>kfY%&&jÆ *++kwߛ7of)%%m~ljҥKu~ k;Cir8?F(:suQffƎ!Ch:ru{uu-[hhӦMjlliv<:ue3MSZh4aܹSMMM?~mEFF*11QW^U_Ci:u&L#FhŊ:qDC\NDD=_(;vD%%%i&%$$ᄏvR||w[jj222O?f۷u\۹DȖW^y]YYY∈^B9߿:ϧtZ׵*11QRllΟ?5nwŋ:t\x{z{rc#""$)??_6M<$9l6ݻwOk֬Qzz;$iŊ^gK<Ӷ1!zfޞe˖I=ӧɺjj++׫&>}Z6M׮]{sm-F88""!iڴi~^TI*//fӥK?a megg=йs~a DDD/J}==qNS6Mwܑx{weOXCiiit{%∈X_qnݲF'|"ͦiӦ)66Vl 4%∈X_jƍ&׫$ڵKV/_o q@j;`#""b=9۷OEEE~Jz{=m߾]C%∈XOq}BnnCܓ'&&*--Meee={hٲe~¡C4c 0}\!v.GDDtE۹CQcm qDDD]!@WK0u1!? %39΀( qDD4`+))QUUU_,0MS}5[FF∈h$˥jޢ p)))USSSs8""4s:DDDDDDϬ+oIU]]-ctC ZZZѣkG5™&CQHb#"""""""" A qDDDDDDDDD!!(1 8""""""""GDDDDDDDD∈B5QJii8 `B!8 ` hiTNSvr:*))QsssP' q4M\.ݽ{Wjmm%"""""걚U]]-%40RUVVAnLRGP0C`r8~=.RRRt 9΀( qnvU>~C\FFv{3,.4C\JJJq .aVfӉ'u+NSQQQ/n?wvZZZixA!nŊ*((PAAn޼4}C q q}u>O'Oܭ1+'I oٳ5|pEGGkr}M2ECfӵk6c eeeYTTTV^-˥/RYfP/_a=z.]$ácjΝcv=~W8ϧyiɒ%駟tUl6\2_htE>#|>(;;[ݳ&55Uׯ$]pACU}}$AN7nhjmmŋ5{lM0AW\i:tN\HkǏkȑ;ru'պn۶m+WXm6vm]NOOWrr$|N+C/H577[-((СCѣgtEY>lvh:t55447ߴ2yk 645l0ݺuKw̔RddZ[[g)110 =|ﭩm6nhfm.\psuy֬Yɱ.޽:f2ĝ8qBaaa5jCK!@W!צQ Wvvnܸ4kk{AgϞUZZƌ#ͦ-\PӼyty}W7oۧKJs$iZh"""T[[ >\-_|!I|"##hĉp/۽5tǎ=6zNm+//nONNps\ KIr8Zn|>3_ T q]&0t1hϞ= ל9sT[[ .(<<\999W_iȐ!*((6I*,,TXX&M$Iz葢fb^W'O֒%Kt-;wNGna Oq~p3dž@){^EDD(99YwQnnƎk1cΝ<2nĿ. <=5-X@'O|k׮fJLLԆ ~Vyl6RRRݖw8-]T/^| j|ںuFcj׸qt|>eeeiܸq:tLcǎY_ۤۨQúnɒ%7no{UWW׈#4qDeffJx_~EIIITRR_cC\={V'NTttVZSNu8In劎VLLn*4Z` cD?6mڤw׮]W\\H 8Oet~:W^y]YYY?" ~|>ӕj]ڪD:tH:פ*;;q/^<й~#1!'Ilzf޽{Zf'I+Vz=YoMmK_qlS333l2IѣG5}t555YOMM޽{zzԤӧOfڵk:t_z|6m_.k-Uet%OXC[|5xo9Nl6ݹsGwՖ-[?a >@azu7>l6M6MfS||<g8@7nܸQiiizJJJҮ]TQQaue-8'}ȯZI۷Hzӕb-{ァ۷KbCԓC\G+JKKSYY$iϞ=ZlYp!͘1C--- q@czjr`tC+&8]@79~^39΀g X%%%a;k\ 1f\.UWWUugKIIQnn<O344UZZ*)NDDDDD̺Ժ:ݿ_ApC q@0!C q@0!CGDDDDDDDDD+2Mz)8""""""""GDDDDDDDD∈BCQb#"""""""" A qDDDDDDDDD!!t*..n'"""""T__ C *577jnnVUU!lŪTCCn7QРJOГ|>Z[[ve0р!#?:KC\ll]eE0 dM0A5""ve\KKKqK,7|^{MIII1Qo |2 C:P>?y#"?8kCыCP`#">8GDD/} qB!^C18""zc qDD ∈襏!@(0KCP`#">8GDD/} qB!^C18""zs8r}3vt:0рDUUU}3WUU`58""555rq---RSSSs8""4s:DDDDDD=Tii<uY qDD4kiiѣG=zg qDDDDDDDDD!!(1 8""""""""TZZW9?yr3,M#0Y=U[Lvykɒ*Q}fIENDB`manager-exported-variables.png000066400000000000000000000255461325274564300411070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRaQ0 IDATx{u?=@iY 2ؑE"Z0(B aD #Җtoygmih_s] 4M;;]+(ݻglmmm `0/joo8ёT9L֜rs/9'LݻFښ~jii1fjjj2F|F`|TWWg|TSSctTUUe|ϬJ/9rzc3#311%x ӣI9=%pc]wr{11.r9]wwNq0%xPxxgZNu41%x0xr/e=_fq0M(r7CR L06%#TNQgbbJAIy^Ixw]Zeee*))!"""""""">VVVJUUU8u;577?W_}#G>^]]-˲lٲ~eYr\O|ѣm6!"""""""J4555{OC Ѯ]_EK,믿پ}FL_{MM̙7F6W^ǻ'=nںu%kwzM<&;""""""""3xاV^#GǏ˲,=zԾm߾}4h\._.˲vh"9R'N'|"gMCC>c:tLDL}Z~/G|+;wsNM4Iiiiz饗TXX<^Wyyy;vƎ|mٲۏw…z#n|P^^+WTFFRRRsiÆ իڞ<رc_#FU__o?Ox{yq\t1B .͛7Q2x.|r=󪩩UFF֭[F{0arrrg˲,}skРAw^-^Xeic233ꫯ*33Sx,X9sիjhh_|!˲j*ݻW?#˲{n56>fϞ4}ڵkOÇw;ʲ,]~ݾ̙3,KNRccrss>HzwdYV@///4͝;WYYYmTUU}[fUYYÇߏX,Y &Q'NeYm_SSqu;y^/xƍ? jΝoK/E^]O.\_1r/Ȳ,OЊ+"=OSWСCuȑ.+--x={A駟~ovc>rɲ,>}ZeYuVcvڥF>OZfMcz-|:w,ŋ#sA 0@ڸqǬ]V#G1|͙3G>O .{-ٳGڵkۻzڸqcs{||/^eY:~|>F/2KII4e߿_^%swOVFĉ#xwԩǗ˲,ݻW;vЀ: jGeYv|Cק~Gǻ}ɲ,hv)))]V/B׿iӦ'~vҠAT^^w.\sj̘15j͛ѣGww==Ox;sLܾ}||]4ϧӧOk0`ٳgȑ#A """""""2>S=Z֎;ݾ}"_TT$˲tIo?| -·hȐ!Zf_EDDDDDDDDl9> 2D?ljhx8͘1C٪Wee{9-YD Kjo};(%%E֭ӷ~kc.]^xAGUMM֭[4[N{￯T}3gTPP]viƌzzvܩ 0@7nܰoz o͛ /(==]&Mүh'<ލ7N/7|>Ey9p?*--M#FPvvΝ;""""""""xW__oDW^eY:{㯅(V1i3o,={㯅(V%xGDDDDDDDD1ZƻZ"""""""""SwDDDDDDDDDxGDDDDDDDDdh}jjj(N1i#""""""""@0b 2Na x0Twex0`(;P]w.&]jj,ҝ;wN:(55U/_>sY5kD~5Yc}{/˲4~xTxg>ޕhذa,Kǎn޼)˲GܾuVYk>♎wMMM0a,P(,KsΕeY:ߞ)xWZZlY{OP(qoւ 4bM0A֭%Io,_m?~ʕ,K$… 1c  婡!F&@dY#IRSS5butt̙3:u4j(-^X555:w={׿dY}'egƻ Ȳ,͚5KOCUJJ-Zdܹsc$ %˲oJ~Y~[JMM5oHMMMWFF,Rmm޽k_dy<YI?,˲4}tCyyyZl 9 y=x@#Gѡ 6Ȳ,8qB,X;wJ*++eY{9Iz:8Fݻwc2M'˲бcdY}]͟?_{O~ ĉo,Ko$ŋ4h|F5͚5~o6b,zf%ի8p /Jl"˲gowYaÆ)##C0{V҉'*[mcy$6iРA_e[nIv7E---|6l|Ȳ,M4)/,?`~9-]T9990`RSS ,ZV!uVYQF)''G#GeYȐy cƌ^zIz:8#]GGMf S(ꫯjذa=zV\)\?,Rjj@}&OCjذa5kw}%)/ ɓ'7n,oir_|QÆ oa]xYYi=xxK7n܈E@2{.<=nΝzeYN:L7^ww}̙35dY&*wDDDDDDDDD ]0$""""""""8xGDDDDDDDDdhwDDDDDDDDDxGDDDDDDDDdhwDDDDDDDDDxGDDDDDDDDdhwDDDDDDDDDxGDDDDDDDDdhq<n\.Qn<Z[[cvqQO!""uqz:zU\\@ -!"CDD:y<UTTa{{;Q[EE<O7""X?DDD(]QQ~::: @WB:::v|q]S:*_gWxf]E %Vq\. NB/q'VV,7Gu9~1HDDQǻvc@hooxVΟ`0j2.SRS!w'r;xQ]t̏` ;buT]pE/ `8!fO{=vLeǎB""2;;1Nsr{eseK8~QHDDfx0 wDDdrwc0o3.o3.?6[B""2;;1NSԿ2F喈7;1N ulQN7s $""cXw-Un{Oj/f+7o=*IztmZJӧOUUU;b9AkktjnS+_[ %Fƍw<Ђ TPP ǣG_֍7싧\]|Y7oԛo> N8Yrr+]kҖ:*_gWxf]E %Vƍw??E͛u1ĉ}?/^s ';5"ƻB^OmgΜ _wDDdrƍwvҚ5k/|T]]m$';N>luttط}ڻw/O8b#""3n ?ܹs:u._$9';$R>}ϟÇKۖ 99I?_19';@xGDD&x0 wDDdrwc0xGDD&x0 wDDdrwc0xGDD&x0 wDDdr=wwg;""2ƻhƻ"b@wnw7ӈCDD<*++ > BV,""xTݣ^4]kkUUU`0 0T0TUUu>n?h!""GO=ޅhƻ`0@ #-EDD)- '""x?DDD.^wwDDDDDDDDDq񎈈 񎈈 񎈈 Oŷ^w0`(;PwC1b . Dn[.Sn[Gmmm1;8qkqz:zU\\`0Í󇈈)񮤤D  joo'""T0T PEEEL~,ES!]QQ~::: HDPHr}~>@4b}<-]ٶU?~~]]ٶUM[1?r) rQ(R0sq'~S~]oF3o~sS }koo lD+V$npMQ Hnwc0[{\8&`8!VO⨇pucw')({}ʴ.=vO`8!VϩeKQʶ>ʶN-[ Hnwc0x0w'fffQK*++K.KmmmRCC еd8 qtaE|d]sso߮ .p4q@#I'/z;͘O@rKQ\<ْi{-ݽS'ǹ虑]VV._%Kh̙U[[wyGӦMǗ*//OӦMӬY~zݻwOeee'0O$Q?.v)w75]nnTTT,꫺x]lܹ~… Mׯ_ׯ POHL8x+۶ォ+۶s+܌n999?i͛UPP IPaa7nܨ>LOHL8x@,;y^sssuQkIzx^zU}>CM2'H@&w?0Lݽ{tR-YDz . %xɃ`Ο?lB!uq (;Hw%xw !IDAT%eeex? >wDn[.Sn[%%%1p!"@};=w~p@T]W_Qwxw@|=xxSw^w~3;P}(z DDDDDDDDD 񎈈 񎈈 񎈈 -] """Qkkk8""x?DDDI]EEEƻ@ by^~""N~y^+p 8b}ţwGANDDԩ`VQQ!Í󇈈)Qn?hykTeήU*޲Yuׯ?sܿ_/VWy,hz[G_PQxr p( E}X?:b=N箐彥Ku+fSW %{q۝> =f Z:ZʤnpNQKMMSZUqov 7~eJK$"x /^p7}9FtycUxPg 76W $"x0 :.^z Wub9w\O?_K""Owc0`=;FOc؏s}\y>Èo]e>ڷ޾}xb_i?o?t⒈ݣb8!VɥKQҖ>ҖN.]MQn[}?liDD`8;""{t{|cn?h!""GQwxьw*..VUU2P`PUUU*..ݻw|q󇈈(=xx xvr:vxbzCDD=󇈈(=xwjkiia#""""""""swDDDDDDDDDxGDDDDDDDDdhwDDDDDDDDD񮬬ODDDDDDDD̕nr%sDDDDDDDDDܝ;wz?׫Jeee*))!"""""""">VVVJݹs6(ϧzթ] khh "kNw9DOT2WSSCV]]UUU%L 5']ܝ;w<}%LFy<.+))QiitލwjjjG.z|>_qMMMDD1ǜ>qQ9dG4:=~&8Ӄg2 &˱*//f?F"%IENDB`manager-form-replay-vars.png000066400000000000000000000662071325274564300405140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR IDATxWuYG@%VB02 226ҭ\$4M- C&|B 0B?x#0h_YǛ3ݼySmmmrT[[KDDDDDDDDDCN jhh˗ׯ_?7n͛DDDDDDDDD4LݸqC?W\a#""""""""nܸ_]]ݨ ﹺ:_mmnܸADDDDDDDDD#Tmm-CHGDDDDDDDDNDDDDDDDDD#CQb#""""""""@vkkk#""""""""!(1E ;]~F(8""""""""4lCܚ5kdÇ7;%''k۶m~DDDDDDDDtf!f3F%%%Ac%''k֭~DDDDDDDDt6C܄ F∈o իWk„ ܬ7|S3fرcci޽ǤT6lPbbΞ=4h͚58qN{OSbbRSSk׮>;h5k;wگ1F.K/ք 4}tmڴI---8s}}/_'*66VSLoa&NpW^ոqiӦ!,cΜ9{oCCƍus)--Mvw_굗_~Y _oq{۩Sd{7|#cN:pܾ}&?޾12رcjiiQrr}ݠ??V\\^~e{ecE׮]SRR䕈~_ 7a„~:$cΟ?;͛IKK Cܞ={m q=1U\\-66֞+++ 9`=}wzꩧ$-X@Cܞ={0yj """""""/Cܾ}">1Fji3_ 8f{1ct}t; JctQq/_VBB-[=XРxm߾]VҬYFF&;577)1.\Pttm׮]SO=3g&O__;uTmڴI}6.+ߟ~I 裏d]vMΝ1F- 8_V\\~G577!Y1FZ3Fk׮ ~Zyyy4i6o<ݟEtknnK/X_^SSwmܬ+!!Aׯ'|u)>>^o7vX-YD}u9ĝ={VӇ~ߚ6m5k,k>O]]]joox8VEE<#!|x<X8VEE:;;G4xtvv2Cc68 l q!@C1e ڹszC(c}ԩ{tt233r;}t `t1O.PMMPyHt:!!cvcPn-_\t)͛7Oxvᝋԕ+WF;::t:<"x/ ]C\ρ~o'O8n(CٳgcIҪUo͛yz! e+((бcu qFmkkkӚ5k4uT9ĉdQzz?t9rDegg뭷RrrUVV{jڴi?~֬Y#q2IRFF1 q#m6]tITXX/b8O-, qH!jΜ9ռ=jo/dޗx qF'J |8?eoqOch2e1:*66V`?qV۷{q}-[p|ccNn޼iG3+Nާ$رc!;vl!.c4*C\KSKKKeт $/c֮]k{j׮]|>[kk<Տ!D+sڻw=Cܯ*ө>?Xa8)a 9992hƌZpSNI!ܹsC_C1Ǝ/a`cǎ!*/M}u-eee͕c8ac | q@!1Cc68 lrݣ}k>O]]]joox8VEE<#!|x<X8VEE:;;G4xtvv2Cc68 l q!@C1e ڹsz(2ƨۧNzsL\.uN>y*>>^mmmCxdQRR҈>No2hɒ-[(%%Eoߎy<(߈qӧO f<$2ƨaT#s wKKKTFFnwq q]]]JKKSQQQ@z?ԤhD<C⺻|rt@TWW+''gP_;r:jnncl#:/C=a{zlmmmgq>޽{e xep|>n;FJ!N󕚚+wYرCׯ_{ァ˗/~QڴfM:UC:qcǏ+==]G1Fz뭷ti޽6mƏ5kYYYnǨZgVBBTPP_x񢚛D͟?_2hݺu~'iJKK}KoC$)##C;vt:5sL|>{@?/sj֭4iO;v ]ٽ{Onq=nYY1ڳgOPm۶ҥKC>8fT8׫9sW-c*++%WRRR4acgPZZ1UTTN|&M$cJJJڪ+..N ,1F/<.\L;4=S nܸ$ǫ_XX3!ҥKV||pFcbŊ~~!.>>^Csεg}s{ w%..N5k=ٵk=s!CZtiH`8ϧ"|>uwwtʕ+OZrl2566گUXX\/>0ݼyS?5o<͝;W[lɓ'5g׮]SvvΟ?9VWWkoxb͝;W7nd VD?aʕ'NG}TGsNcpBIw֦cy^;l߾]qFciӦRȓ'O*//~qLy|2(77W^W>OK. o.c^uڵk8q1jjj z. 10aׅ;}2wޱqQQQO_|1{ w%..76m1FW49?iӦ\ v͛%)WXXӧOŋW_}Uҝa;''G6lЅ ~egg!饗^+K.ŋ~zcЖ-[cuu׿U.KzgCC`Ft9s233mo#UϱMJ;FdQffyŋ%I~1ڸqcs 6~թXJII x`cI:tPrʠØ1F}Ko3fȘ;m6uqKn?s{[(??t:1^W111JHH4ą2c {Ç^(skոqBzH;޽۾_a!ȑ#?\}i{!TvvvkzURR'|R7o zr::{ҥKr:jiia ڨ\߸2qDIz??5!ԩSѸqb jC\xw<0c>!nʔ)2\Bze 6?~~#=~} q;v q/cqΝ+sG2!nժUv, 6دq7w!Lӟ4|ۼyxw^9N}7}cuu}рi:{,C`Fe vY`ꫯ[J8fϞNy^-^8`uoǏjooz.ka`׮]2`[kkTUUQ{9… :uJ qgϖ1F˗/WSSn*co?W[y<M<ٞI{q---r8ӊ+%s)&&Fz>8cMz_ڳ`B_0ϯp84o<*Ca ]= s?? qǎ psi|` .h֬Yzrym޼َW4{l/Mɮ555r:z*C`Fe\^zjM2ECӤ߯$;Zn՘1cr=2ܹsA.O<m۶!Nz1Z|yerϥ/gΜQ^^p8CفA >uwwk2/ 8.X}9sf qڰa4m46 w79~W2h}l3ĽU8C͛75\xQғO>a ?^|E]p~8s߿7|Sk׮ѣG%ݽ4u:}*++h"j>8`{ڪӧOgϞ{7b޽2r5!nܹ#xf#kŚ8ar-\c`ԨA>oOSCCBXRcc<h5ǣFUUU Qmm\.***\.jkkC$8 "`"!8 `"!;+DC q@0DC q@0xT[[+˥ """""""#˥utton8QUU.MDDDDDDD}ޮzUUU n8Vmm._,-ǣN"""""""##ۭ˗/+}!UYYvuuup_|R{{\.׀3***xQEEŀ2***9ڧ!@xC1>8ac | q@! -cnƍ-á,u`}D%&&~o:u1EGG+33S.~ӧOuW[[P~y<c4[{{1OnPj*c4{l;444(++KƘ*!֦xct>޽{e xep=EB!N󕚚+c5ĭZJon޼9=رcC_2⯈kmmUTT~'EGG&]xQ͝;k.cnݺ㕖KdQzz?t͟?_رeg}'I2fs:9s|>Mk֬ԩSp8'NؿnݺU&Mӵc{?ӧb=Vzݚ>}Ə5k֨_kJHHPRR 믿J >ĕ={\!Fk+,,_|1Ǖ7C\ee1r:A5k1:~xHC܍7x(33SJ;h„ 2wޑ1FO?;4itƍ5!ҥKV||Z[[7Fcb {՜9sdӕgJIwx9͝;W7|I&3X:..NgiѮ]1񊋋ӂ %ʋ/|Ptt.]s?P?^|E>|8`yj޼y;wlbG˗ttꭷ޲WTT,jÆ }'[[[t:nZ|!nɒ%n:'o>cSO=~ѢE2ƨ4!n2_Wkk]'W!֦ׯ1vF} 10aׅ;}vD;qℌ1zGx$I;w1F .twҥK$I_|1ʲރX(skll$mڴI^ZtIi_3eʔMsw hÆ p߯l;|>KzWt%}Zx֯_/8\XXJyWuu]۹snѣn#>Ĺ\_^u+V\t2ƨ=g}V=zT1ڷo_`?aƌSmf.!T}6i&{[[[1JMMtw|{^(!!^:c3fc> EISqq󕒒8} q7n\H"o0Cܾ}`uww{=;ܚ=c祩]]]ڹs~7{/jp8KS#oćׯ+:::=\ &^PLL#.77ޟqEEE2Vyyy@[֓fVO?~}ʔ)26k/=vqc4sL{Hq'Nc`#GSN)&&Fƍӊ+Ν+sW\\7w!Lӟ4|ۼyt:"ٳg駟^c=o'Ik֬F666mٲe|k=|>Cؖ _P{{{C۷p84qDo[G~~1Zv}y)))v 6SFT``Iwh{iCd x/.weI]B^}Ucn}w! q3g#<s3UTTÇ+//Oow--YD=vܩgj˖-q{e q---M1JNNtҥzx<RRRa? ?ĵp(..N+V٬Yz⤻J`Ν;cp8C_C1Ǝ/a詯kɱ.\>?N89͛7ϾСCfϞ-c/_0c2mݺU%%%)??|~F=ϥ|.\Yf8kA/M-//דO>ׯg$mܸ!0j"2IwohJHHPjj֭["ŋkƌr8Zh}j=r8JNNk q_}n{̙3SrrrrrtС⺻5~xc_3g<^[[V^)Sp(77W{^޻a%%%iڴi*-- +))ɞ{Al es_1c(33Szeѹs>{Fn޼i?ŋ:t萞|ɀkx/… zb!ᅲÇUSS?Pfҋ/ׯ[YYYڸq_'s{TRR')0T>uvvzU]]ұCucFj޽2r5>AxbM<^`82 zUXX3g\2ڪ^{M?m8=8ϧm۶iΜ9z'~_.5 q y<F8BxTQQ1 q uvvi@d8 l q!@C1>8ac |>uwwĀ;::qFeggp(++Konݺ5sQUUUJKKSttFt]M1FIII#8lٲE)))}vDc(_D9NcdC=dwFF^:aD,YD~eQCCðݧ_WWTTT4!I*))y C!vvVZ%cfϞmGeee3"P=S2ƨjOeH!n޽2r }>`gfb |#VEEE)**JuuuO1FMMMx1;w=f׮]2hݺu_+--Mׯ544t?~\?1ڱc˗>?O%ImmmZfN*á\8q~]>r䈌1[odL{մi4~xYF^7sw)''GcƌĉUTT搞`OKҵk״tRM8Q&Ls=_5ssA?7/^{u:9s|>m{uVM4IӧO׎;;`M8ݻ5}t}ך={{ܲ2cgϞ87C\ee1r:A5k1:~xHC܍7x(33SJ;h„ 2wޑ1FO?;4itƍ{oA^Ws̑1FӧOW^^+++|lgPZZ1UTTNs.---;vbccxb>S!=G{vvٳgoѤIdQIIZ[[eъ+mܷ&>>^CsUlll{?7|qqqJLLڵ8-X>/3btt.] C!n߾}Qo-1F! q۷o1FZ[[u5M8Ѿ?6]~#n[5552(??i_'N1F><$iΝ2h…y^;m߾]qFciӦ{jkkSWWtRyޣ@_+'g8p>7=_A&I_~N{ݷ&**J.]$}2(++W܄}ũQi&czjIɓ'g2eJy~ӦM xa |#>Ĺ\_^+V\pa{xgeѣG}}VϱMJRǮ1F6ea?1F7n?87CqW\ф  /(&&FQQQk?"coSN)&&Fƍӊ+Ν+s?a |#>IҚ5k(׫;*-[LҝÑ[oe:p|n߾-á'*>>^>B4?-X@ q_~V\#GH\UUՀGgOP.M =Ľ2h֭|F(C̙3#O_D%''\3c?%%EC\KKb Yf;Gޯ zc/K\p=SNI!o1w>aɒ%WTTo |}:SӧO<{lo[|ׇ5 t=8áyk8t落}n*co> C'x ͞=[ JMMպuTTT8~i'NЌ3p8h" .'Iz'p8˗ERW_}%c|=Iw.S\zL"á\ۿKS?3effjرA#ҝK $-]TW^~%%%zqn[_5feff\?1:w\n޽~@} ::c… 2g!|{1F.+ !ŋkC!۷o׌3dQYYhե45Ԥh! mٲe8o̙3Gcƌڵk{TZZxt>+%%EoS C!Cc68 l q!@C1>8ar-\c`ԨA>oOSCCBXRcc<h5ǣFUUU Qmm\.***\.jkkC$8 "`"!8 `"!;+DC q@0DC q{ՙq|*Q'cr("(1h@zP7\t&3a6q$8q5$j5j\/’!2VKhm}3tK T=U9~4oާ" mY~_^B!B!IQ)lVmmZ[[  !B!2LZ[[U[[+۶G\wSp577E`PmB!B!ĶmA(~")_${8k㨿_@@~)^lۦ JȶmyzK0P(Ć"QE38bG fqĎ"@(]܋czWQjjo~>fdQnnnT@@=Jۧuwwض-c}TVVj޼yuVBA׏ IDAT@RuttȲ,cdѿի q"n2ƨ힯GcQčg… U^^}x)\S*㠈 vq/Q0T0t_{dQVV[d);;[Ƙ1)NF"V߿_{#'B老T=XB18b7guvvjʔ)2e.^8hK.iԩ2ʕ+:1*((pOeoAj*͙3G .֭[Kdŋu)-^X˖-1Fwvact>C[ty͛7O{j֬Y?/Du,#>^]^zIܹsvZ]|ygϟZgyF㸯;ǝ;wjZhv'Ҹ#x}'OTVVfΜ40~2/ (ݘq>OYq ctԩʭ7n(--M)))*++Ӓ%Kd%7oΝ+c+WJ}6ӂ 4c ݸq1Sj״idѣG%EWĝ>}Z ,1FUUUZ(R^^1ZhVZ>o1c~ac駟F<={O.^x!c9i䜜YYY Bw~CvcqFhSRRM>}PIiCMö6hƌ***rH8uTKE"ʟJJJdѾ}*v%c?S׮]ݳeGVWW[nAh+--8ύ1$I_1ϗ]'Ewi3gdQzzlۖ$ݻW⋒8풤wyG?Gx999VK/) x,>#m>3+??_q gw~Cou׾)S(I:r䈌1;zl~wZjv5w{zꩈc"؍y#O? :(36m42;SSSC5kǏe#~pOB!M>]3gΔ8q-;n-c{1I*dѣG6TGG̙#cM^vrT;ұZm۶cm BWݾ}dW_};PHӦMsHwxE{*--ռypE\~~zaOQ1/⺺4uA5w\7Ѵi4eʔA u&` [2hǎ_~e}>c^ʕ+7oF "gu_sG3gԌ3 4"ns;vE\{cOoc9i>@Î'^}X+Oٳgǥ;<{MzH7ntϪ+((Pjj'(ݘqo+ -^~eI]۶8[˭޶oԩS-mnݺT͟?_)))=gq---n?* 'k.k KEEEoi&;vL/q…2ƨvc9ipٹ|rB!95khźpB\?{jȗw:u{iÇew4w^ܹSҿF*y=x v ):::ܛc# 1|2۶ݛϛ7O>{pѡT͘1C7nTii[B{6lSCyG}Tׯ?,c/IQUdц tʕB!溗-Ξ=+ӧOT[N2eRRRtgh"t lx}{XHKMMs=>~pΝ2(--MX{=??=>F@RI˂~[YYY9s{1Uyyf̘̙3z駕,;˭-_\zGauttHwi22ట!\ĥkƍ;w|I޽۽T1ڱ~JKK1Fuuuþݭ_]?RSSw?{id͞=;~u,#>^}qYYy饗^իWyD@^E, ն?5k$X1F~?߉ЍGu/M8b".ttŮ]Oo6… U^^L"ʕ+:uq.Eܼy4sLUVVc|qYf7tWϾ}E{ァy֭[ }_8b""QE38bG fqĎ"@(E|>d G~(&q=&q֦h58^֪]m'{8km]uֈSĶm577B!B!d~577GUIq@BP @$Eq@P @$EnG!B!BpF0("H8 ("H8 ("H;v7|s?dт  bzs۶eQZZZT755a 2Gi,X;1z'sNcy|q/1Fmmmc4 sGO<1jiiq_dQMMMo8`TSݟ6^QL/cGt-hΜ9ɓ'3g*--Meee|$M-^XNŋ*ptR;7oyw?Rw%ZJs… uVݺu\͚5KWyy~嗈 ;;[7uuunz'|9sfc>ð@;wԂ h"޽[E{ b*^1-I1Fڔ3fHe##_Eܼy4w\c*FG:ufϞ|M6M=zTE܍7iɒ%2h͞=[ӧOq /D<Oւ dQUU:;; 'c-ZUViԩg$\ĥ(55U>}1:p$EߑiH͝;W=ocǎӪUk.I*A6O?Uww_>4i"~A_|Iwqv1F٩k׮i2ʕ+:s挌1QwwU^^^zi؇O 3tٶ-Iڻw1z##\M2E#G4 b*$iÆ 2ܹsZdf͚^Iŋ{充TͻlF^qO?}(5sL9s>7m4;SSS͙3GM6M?vڥab+\;62pgY֠2m4DhO|1qǎsjњ5k$IgϞմiCiƍ[CŋZpϺpG3gԌ3 gyy1ڱcjjj%|.na~:l___cK7>}fϞ=bw~G?E@\j̙<(_rعsiw#.6Em߾}SN믿V 7|M6رcYp1x Vx .!ݷo1***p7uTÇz*l4fC ċ$^ $I;w1Fiii*--u>s+"n}yG}Tׯ?,cqJMMՌ3qF](ӧeQjj֭[RM2E)))~zϟ%c6lؠ+W( )77׽l_tpوKMMs=>Ò~ eʕ+ׂV^Yfiɒ%ѯ~+ q##\ĥkƍ;w|I޽[Dܧ$544hJMM#< 6d͞=[WMMͰ/TZZ1t_]?xTqڶmSOi߾}f#?xXUaE\AAAK8Ʌ".I(&8 ("H8 ("H8 (p8B!B!B؆31F$Eq@P @$Eq@Tٶf~y^BHԤ޸OyN!B!DXQqmV  $'Um{B!B!ˣ.⚛Ң`0(۶GIrlV0TKKq9B!B>ˣ.|>8ΨFq+zs<"ʶm88lۖ&uyLE\___< V1Mr8@@PECL"q$E0P<q$B@Px(QEEE\DB7Dkk,444(777$EٳgUXXt\ׯ_wnRqeee:qDTvuu?TKKKğD"K:t萶o߮:mݺUӟ}&`"}Ld,^VX!qTYY]pA6mx$&`E\CC<xTPP Awk׮i˖-ʕ+UUU'NPII233xt9IҺudY,Ν;)˲=t6l IeY%I.\tR+:z(}LX,~edd(Eܝݸ)rrrzj~|>뭷ޒt{^RR͛7󪮮ֲe˴gIO>DzwP( B1-oܸ\m۶M?KDŽ"OG+VК5ktA?gnt㦈,Kk,K|tUPPgffn޼)I :ylۖ4XTQQѠ3>C.}GZt,Ryy~'IMoqՎ(++K:p^~A.˲tMA^Z8[Ľ{=}LX.*++_W_Uaa޸)222 Qvvjkk˗/ZZ;vPaaJKK)i"n|]}bV2&㏒#OYYYts7EeYjllt_kjjeYz|>.]~8tAjٲe:v오E\{{}EEE_~yo}}LX,>3K\eff x".==]Guuu|*))і-[$^k˖-jllTMM/_>*##CGQss:%KIvZUUUƍPvv***ڪ#G0b͛ ϟ?Ç>&dq.]RffN<۷R} s 7E\nnNƽdq?222"n(&A6n^Z~_>Oz뭷$^Ph:l2ٳG{AO>Qcc}]) i``@eee:~Bۋŋ|,K˗/ٳgCڵk;bwSCC;ZJ{ubE2>y<XBk֬ӧچ x"β,׻566ʲ,utt)''grǏWAAy$) ɓm[ݗZ%T_~ݻw ?kXcbIGiҥ,K駟ܟ37qSĥqeee^/s 7E\FF{tVmmWweY@ ~jرC*--Uggq===Zn֮]{^,1!%kjjҏ?(I=K.Ibnl㦈,KkMMM,KW^ҥKݛKҷ~|9:BHc۶ZZZK̙B!BHu]u/qF0z㨿_@@~cXQq^Wm8qd۶^< 6c*1>q"y@l]S8"D(H8` y(I"䡈&8"D(H8jmmeYq):"Ne͛,KuuuǽD;=.5)28q"ݾK~ZZZkXKfW__/˲+˲T__?$b>ϸq~)E\GGV\)I***GDc\q xm{zzn:]V{U}}*++ZWUU7ߌ8cmq0y"β,566555ɲ,]zU>OK.uo.I~8tAjٲe:v오XQaa3tnĉ*,,Tݻ'tlxoE0".==]Guuu|*))і-[$^Pk˖-jllTMM/_>*##CGQss:%KIvZUUUƍ1m{9YGIߔW^yE]]]wm߶m??ܬӧO+++Kuuu1 [|L&㦈ɓ'U\\Ԋ+tСs/ImmmڸqrrrrJwdoE0"oE E0P<q$B@"@bP<q$B@PECL"q$O܋8ϧ`0Qy@]G]555Mc|q)y<9e]u۫Z˶x(ٶv֭[ź. YY~_^~577ǥcB!B!%uyLEC$Eq@P ܬx=400/ٙN._K.38Pgg.]/ͅIENDB`manager-form-replay.png000066400000000000000000000246461325274564300375440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR8W IDATx[W?Ee# [gPAmFCt Dk$.j$J\i4j6j.(. ji]mIݖN}WبUVV{heetu]rEܾ}[W^USS޽~IW^յkJMM E8DDDDDDDD^IUUUz2ADDDDDDDDzBMMMKVTTPCDDDDDDDDm)!""""""""@wDDDDDDDDD%)!""""""""@ q*566"""""""""b/I!1RCDDDDDDDD V!ۈKRCDDDDDDDD ~+-X@ƘvݳgOпlouZ`WEEE>-//gZvmہ_ q  #Bܭ[z5pNg}C_~5jm1MRRl٢e˖)66VJNNVQQ,XDUQQL*))I?CkNSׯWJJ5bm޼zOYY1*))Qvv!ChŊr:tBׯ_׬YkZ|?X󨫫SbŊDDDDDDDD -ZUee3X͙3G}ѧ~^9992h֭4III8q4g]zUzW5|޽[ӦM1Fg}]v)55U}Umm6Udܹsߗ1F7nԭ[O qҦM4gc'X.)66VK.͛5{lc"_ct=;w1FA0ѿރ-[[n… ӧ 9NM=Ç'NxL}vVΝdk;eсt:^tI6mRdd-Zd>sL5JNS7oT\\u+""""""""K4~T__ݻeѹst>)\cd"uV뵮1F.߿c^W^(z;ɓ'+!!Aqqq2e= q[n { F7۷<#nƍ2ƨZN瓂_|1 qm{cw^9Kњ9s;fGQRTT֯_y)%%%""""""""V!G qMsyz͛}: 0r_4Bٲeӯ!---2(..W?Ǜ;wKVZ=x 5?~,cbcc=^ jw^p (0.Y>uꔒ9ύwV^B;乼\;6M *C_37d9{A|rrFk#jnnBؑPadQUUUPնB#%''+??߱3Atkkk#5w:r8Vo#F.x nrQb,L]t)ynz77_Bm?A_\k׮rOwm獍w;zh؅Pr({}϶md8#_x/˥f(| -1o<c4zhkUUU)55UƘ^rQb,O?'xI1b1:p@؁PͲ{0֝jъV\\rrrtƍvq+++K0al"c/^l}ޕ+W(99YK,Nkn-ބBp84|p\.߅ dѸqvZ 4HC ц Zv~V~' 2=zdMz1F[n޿744(,,LaaalW\Qxx1ʉ }ϳl.\w yvƌ+V(!!AÇ׏?hMӕچ#o BZ[[.c\򖙙iu,Œ. 8PkgkQLLeQDDp8ܹg? rŋ WTT|N`'B5ˇ֠AdQQQTUU(EFFjʔ)iL#Gv*'OVDDIqqqRNN5}^^^mEjc4gΜn?N7**J1117n2_{םm%22RJII~k{}kƌ]ZVv{_\\,cСC]@w_[t1z%= H555l9"G/;vÇmPUUUAO8NOζɓ<?{ZZѣGg?+/;] W~>}={HZ!6NEl۝meС4{1OA:^t322Կ.-+;=/))yfJҵk<~W$ޓ1F}v9"G/;vɲw!.X өȘ'żZ~tΖwV:?1ZlՆGźtֻxe W_vȳv*"">YvPův%y^ƽm[qulOp^1FΝ^sw:p_Cɘ'E 7΄r;'NO>߿̙c]kێ5kBܮ]<4G_t쾧;]ko~eѶw^ng빽qӥeeGoVxx}hjjj4p@}ӧmzZ[[10p>s}Ν;umE;p4trk,eЕлxt;[t7xKʎx%iVڪk9sLIOn6~oKK\.5jN>x@111JLLTTT}9"G/;vrW.M xܹsw^IOx;uT;+MC&NhӰateIϻl|}Ws{:IIIr[%i塳B}}A5;V<9wޱnw Iw~Y111fulmo c=򗶶} H(gy2h֬YڵkeQ\\vJKKtCZOTl;0p:Qdd̙cRRRSǽ`=ݘv[g޽Ƭϳxt;[ݘn^| Nuc[ccX9!!A#F]Ws=7Y_#rc,{58|՟*++Kaaaҭ[:]YljȐ!VݙOMMmwLP~ucw| '˗[~OJJŋHKM69h`^?QQQjnn#r2c,{'~9R-_YlPSS+..N3fP]]w_cY6 yvz-͙3Go 6xlg]im_yʥH q/ :}m[n1 ۶m1F%%%]~hdggk/#>9oVn{/ S˛P,CO f[lQTT4-,,TBBgD<{[wNYCs9:fzW\\;wѣGr\r#ݹsG%%%=<2@ , `gwjk.>Χ>Н:?: rgg{@yVNwrjk})< `3}~} , `zM&{iW^ZB]`p`3= <n? BǏ5k,I҉'4~xq1w-C5552pW//08pgr8:~sͧLcǎ} B(-ͤ?^dB-g<.եyWZZ 6H͛/Rwr2])5(;˷o_C!^vB!mn&/eO&Բ y,5TuTkjBn:]xQ~͟˸[PPrO/;t7ܗSP2Ң;7vp?;]_\.r4k,99}璤7o꣏>Rzz~mǒ*++7JN>ӧ+==]_~jkk5{lj̙r:>'gM_]]-I͵ڸvn[{p6m _rEsUZZG$Ә1cjϞ=wh7nVZH=t566JzgܹsϴL?qƩ@͒'sO2k_~e+55UZlݻ'IjhhН;wڵ˺eE\22 `]s[{NYCs9:fʺ=^/]~]+W$*//OvRkk?~iӦiѢEpOM0*sssUTT+WHzR9{h aIDATSeep84qD8qBΝӤI~zqsڴi*--Յ O>{NNۧn[{p6˾ qyyy:}.\w}TSSƎe˖ڱcҬ~˥ٳg룏>ŋuIeggkɒ%gg֪U|Liii׿kԩ-|B<4rI:u>cѣ͛%uk { <؇*^/O:z+--:[EۧqIzzk.y:XZ_z |=}Yo͵}YLw>}-=8he_{Zɑ$m߾]SLt:|Ҭm$\F$k̘1***ҤIڽ/UYYJKK.^(!I>!dfɶGiͺu5_|a-םBĥb}3 `^Bܼy<mfΜ1}uu޽q[[_saÆN qW^^;p@~[{p6˾ qm3uAWXXL;f߿_5a[37۶mÇmcYYz-ѣUZZJ>!dfIÇUZZY~FE!^ oAK]s>#޷mL!ƍր+vE ,*ĵTBW_}L?nٳGjhhxF3vu/ʔqIRjjN:E>!dfɶ{)77WӧO͛UZZUVuX۶m8 `^B܁sN׼/M3fuCg/-e$,wcǎg.o+%%E ϝ;+WZ/ۿF7nlԶOv-//P]]!yG&9I&Y%KԅK>svcM:պ!#G4qgBۭ{u7}t۟ǁ>xg;wZ7pvޭI&yׇ~[7z_nYӧOז-[$I}.\hͷl>}ZŚ6mu6P!yi&%ɓ'p8gSJJ>Cݾ}[?Vjj tuo4iR8}0@(Á;} .͞=׼XuuuZh5eY7sLvV۾}Ǝk=Bxg;8m|ƌ<9s:藞1駟*##C&Lкu6Ϛ6m3F$|iر:xNt-[zP!؃dR\.[N4i+]tI?-矚2eC[22 `z:Ky+}iҗf(xg<>w3-o}ܲsP yd>g<SKOshˬ/qrh$kO|J!.08e@샿UmV\t]mMUM!.08e@ q[+..ֽ{<^Zݻw&d g{@y|KSU]]-e rT]]w&d g{@y󡳇CgԤSNZ---^/ ---֩Stw&d xg{@ysKK/QhK7kJ 4.ZZZܬrKJJT^^,#Oh2}$ψ7\QUW?̙o5_CDDDDDDDD|YtR?Ikiʫ~),8DDDDDDDDvvۡSt/MWy~E!1RCDDDDDDDD  8DDDDDDDDH!1RCDDDDDDDD  8DDDDDDDDXQQW*++DDDDDDDDD;[YYWDDDDDDDDD;{ut:DDDDDDDDD;{V-IENDB`manager-notification.png000066400000000000000000000554631325274564300377760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR<S% IDATxWSwTGhjS֎`ZvRܩZk[E@Q@ H^[#F..8yc/UEEJJJT\\LDDDDDDDDD*))Qyyn߯ׯNDDDDDDDDDt_uuu~nܸTTT0=:/%%%]~1DDDDDDDDD{%%%Kqqꈈ3ى1f֘V[[KDDDDDDDDD1fiDDDDDDDDD64ODDDDDDDDD1fiDDDDDDDDD6ƴ[n#bL#""""""""cSF{￯^zW^3fvܩNkϞ=]zYcZMM =f_1rJ?1F .6m&Mcvic|rݼy3Nvʵ0=۬1͛DDDD{1bVRR7|S7n>7h ڵK?z婪y~iDDDDOPNN1_lgРA4hnn޼rK}qӦMianj92~ّ1:XFF1:p 4H|qZZ1 ;СCzWxڜʕ+oԩS5iҤ>/=:4"""'\^:1[nնm۬ e˖Y/2ƨ{޽1t钪w&LP?X8=/ُ1 6lƎ6UVV*..Ns̱>7h X~1FNRvvv***TVV={>SFFXN1y#"""zѲƴ*""""zv-cV\)vۢEdѯj}nZbkԳgOmڴ);v믿bo>7uTM8y#"""zbL#"""z|>kٲeJMM>7cƌӪpBS .ݻ߫GZdM8Q۷oך5k4d[FŋUUUӧkȐ!:~*++=/u,4"""Ն ;(..NzѣqFyްc6|>^Z#FPϞ=5tP^:c?8:|=[;rz-~1#bL#""""""""cͬ1#bL#""""""""cͬ1#bL#""""""""cͬ1cӈlfiDDDDDDDDD~iDDDDDDDDD6Kqq<c`c`c`c`c`c`c`cAtpEMNSEEEjll54c\.JKK&߯Regg+ u4c劋UZZ@ `0&"""""") *Tv624 eeeYP/N ,/i@s8  i  p8=1 rCMMM]}t&4cL1 Mi01 461&4V^^.cbbb"sqc4{\kAc`}riљ3gsNc`gz-1 lL>|1*//1m2hĉ*,,dLlbL%Bū d$577+( >k^6itҘVQQI&W^7n֯_fLv&N>}hZpn߾vÇk޽:t竹Y4vXcrrrT[[+ccwٽsi*,,l^pit˜ܬCaÆiĈ ꔐ=zhʔ)=z15kV9cccջwo5#IJKKo!cmۦ6cZZZeիU^^fL{-W߾}eaLCT`LƴG>@jiiղuLۼy1JNNVMM|>  c<Oؘv%I)))2h޼yc?miҟ晓#Im4:~qˌ1 NZGm۶Yڵ+lL;wnط__zzuaÆY8yd;Ƥ3ٽ D4:aL[x1ھ}u̞={ƴ/B\RaUUUVNz&c\  12hsZZZ3lLKMM1F˗/s9=zT$=ޘ8D4:aL 8pm#GmuLV||bcc5gM￶GaLcVjȐ!1FݻwW\\\ZoÏQ__Ϙ}i:u(cLJLLc1hcij߾}ɓ~Z4cLt1wCu3gt1~?c1:Kv֭o9rd֭[OLKHH>}hܸq:qx=t4@gijjj:̴>FMMͳӆ D׺ȃ~.cm,O2iԩZr?DI1F%%%mG}ƴ"IR(ҦMdmAΘHӺu4dǏ[*11ÏQ]]9c$͘1C޽[ѣbcc+11Q}'42h۶mQ]]ԣGM2EG1Ffz1:KVUUա1ڼy}͜9S-қocǎ[Z-\P~ӾcrJ]xQ'N͛%I27ߴ<7o,cUSS#ϧ#ӑĘ,cPO={T||^{zփ%&&v1|>_i3gδޙ&I%%%Zz&OZ?8͝;7o_HOO bLt1v(cNztĉ%))Iqqq=z׿UK>l+''GTPP+>>^ٳU]]5bLt1v?=1 ˇ1 YZǴ24bLt1ƍeiŘ,cZYYscveee/ZǴk׮)33ݯӀ(WTT$ۭP(՗xɵiׯ_*((PAAӀ((%ۭ`0՗x566pڵkMW^UAAT]]ƌi U\\,)ADDDDDDLpB_Ã9N]|ZZZ54c:EssjkkU__okHIBt`0&577YiMiMiMiMiMiMiMiMiMiMiMiMiMiMiMiM֘FDDDDDDDDD;( XNS(jr:***RccӀ( rTZZ*߯@ @DDDDDD5~*;;[`ݯӀ(W\\RA555EM`P@@1Q.++K~_ B]}9tP(f~9vgLP0dHDP(`0(i@s8jjjK5551hcilbL1 Mi01 461ƴzKƘbbb4zh9Nr4x`̙3cLiVbbFxc@ `<ӧO1F'Nԕ+Wl3rrr~C]xy&L c\.<;it&I#G1F$ڵk8qk…}$iرa&o߾vS^^.csi*,,}{jСӧϟf}>fΜo߾6m*++ˀ1 NB._C555SBBz)Sh2h֬Y4%&&իWcW߾}eQffƪw5j5ٳGtZ}]cƌQKKK _@`Q߾}_$yfc|> 0@y<I~g(}Z!k+-IJII1F͓$9sF7NPHP m=Eǘ@'aÆYsݺus>tp3(==]R1c} fɓa,k~GfAz.1 NiΝ1F#F$}2hʕJOOJR1Z=}N:6O2h͚5}v cLǴ[=%)55U-_:ܹs:zjkk%ڻÆǹσcډ'dҢP(Sjz1&UWW+>>^3g&O,cFImǴaqԤCcj̘1֟[ZZl=Eǘ@'iwQ>}dщ'$I?~կ_?͞=[}ڻÆǹσc$UTThʔ)ׯ4sLy^[ xFccilbL1 Mi01 461&4Ęc`L`SVV2R r:ǘD"nBD(햝1 rr\r ]}9t`0(-˥۷o{e3222dQbb#kii{'c'*&&FeeeIsU޽5j(k۳g$YC1FÆ ӈ#Dom۶1Oc`L;x1?~#;1z %I;w1F&M>VnIRJJ17o$ѣ2>PssZZZ4a„GiRo|u}˂1 g0]xe2(%%1F X5l0똓'OYfIsڶmu̮]xL{Z,xcZee1޽|> IDAT_m6lѣsGU 3Z:u*lZx1ھ}u̞={֑^i<KM#w>l ~999=i]2`Li^.i01 461&4Ęc`L`cilbL1 MYYYjhhK544t{c劊v RPHn[v624 566rv+ vЩn\.n߾i U\\,)ADDDDDD59N$464&4&4&4&4&4&4&kL#""""""""i iMiMiMiMiMiMi  b(ka;Ӏ(EMc]Q˧UN/QԔiylji@ B.ǧkUtQeA""""""fN_պ|A4V=7u_E7UXՠ?ªlԩk~RxHӀ( tftxue|scZZZܬUN/H#"""""A^577%ӌi@jiiQSSV9]*WMMMiںL+j """"""DĘFDDDDDDcӈcLcQxi"Lk """"""DĘFDDDDDDcӈcLcQxi"L5E}i"bL#"""""" 1 @DiDDDDDDD11(<4?>4i""""""1 @DcZ(cLOa(Q¬dayD1.ƭ6X6_ݒ5a>sgVOCǍ[ꖔߊawg+nzJ=R|K_"""""-4?z]/;O+nFΖTYfTV-ziݒz&9AuKJǫY0{DZ}~~vgӺOwk@DDDDDŘ 5zʩ_jRAz:W&3GmI{N]/آv:\_1cL#"""""jc昖 hwf%%kG`&ګKةK`yMOώ"roƴ%z}2}z2n}WܣƴE[Kq3R453ʮ{}#ߤ~cܟo[Eހ.Ԥԃr\O7o?^E=-ըjGiݒe nfGtrIޮةKaࣞve%%`n}QswfL[z,Sq3Rt0TgK|ꖔk틎dhm+Xo_i\\s@e5+O4zmb5Zfߊ_DDDDDDƘ =4y!g,Sc.BuN\sXlq|Ǵs{?D[sR5oFӲ+4z?e\Ӧsր6zwzJVW?YWhm7x][rige Nܫ\o@TM\_\%Z|$C=-mwL5L; -)Y}fԶ h_U%|J_n?eiÿߨw#eacں1me_U/sƴiK:4&yrwUcRZxY; 5뻏 ׂrK뚼fW7E/j.$у11-x*Ik(b. b MpT9ހ\ހޘ3XcZfя/zܬ:}_)ww؛&;o@ hgatTY[VVϖ}>"N]"ge6h­?{6_CYzyUrǴrUZ9|oryt FzTՠ/Tϩ[Rv\lou} 4}ӯ怭7TAw};2Oks%>Lk1-{wL떔=YE5(נC׭uC%&|eoir}U O;"""""c4OsViw4?$1헬;k\:Wg,ju5||Wevd\VkSu bٽ((7tש%,S|F2紥rUil?+z}zjRꁰt1-閔cʭGU)fZyFwǴ#yesK?szNOn;{11s72G*suD;.^QF-5oow]T<7FDDDDDcLӾw]~SW害nor< *=_ <ј^Zam԰6=gQi?W:$dߗ1۵%-7*}/M,ۭO@ˏ;9x:ʮӌ†O@ ~-x`L;e}]#ʭw-mP('PY+ڔr hڽ1gg[OhwVIX^ꖔDzK5}^dF/ܦ߯V)ހvgWe}Vng~=_}f7{~}8LLQo|?z}22G}zQ?lb=7-QvKqS4a~ʺEG;k⦧D|]%%hAqhֽ1Xau1Wu TΊ:N]KZkXw<_IJ%6A_N=o@OiLu]׏G2"s]ЂJ9|Ei"zZcZq^IJak7ʮӲ+sRoniݒ/\CAP܌%ZPuϗ# U_Kq3R}7zÞGVXÕV/W.\G+| *#yez{To/آݙEOmLkr `n]ٞ6Wkex~-/lLjܟef7jsXOkk[Gwo.$у1iiy]n+nUry\ݨ8ywXrV+PmNy _GVefl9_K^EDDDDDU1ELW䰆~QB-;B*5n:&VݒuGת5功\ dh#rT=UYzrVֽϋ+cLQؘVQ\(U%mƴnIZz,y^lu\O|ANf+s%%hm2+5zvtgh{zޚNbx7~և+ڧz/"""""1 @Diu.?tH떔雏:ǩ"|AT7vېo7hleV+wp=x[ڽvg_SiKU^+ge+:s]1.]g;Nثw[OZ>3:ʼW?YSE>{eiOxs?_ҲcYrV>kr<[8^EDDDDDĘ 4GEKkW:GEmþۨ/>3%պxot:\ns۹{+eV4?i[F7JՌ-sV|R?lO \cY?~p>y.ыcmLsT (XX_ +/rVYzYϣQWŘ Ǵ w=Qǘ "4""""""DĘFDDDDDDcǴ:""""""1 @DiuDDDDDDDQcӈcLcQxi"bL{6]g.}2uKJ֚ꖔ]"u_a11-(~G3Z|̩:,'/m9 uԆӼfDDDDDƘ qL[y:W-ݭ[ٶSZc\_ǯ<3 fRg+N䬬Wm~-3|Mt\Kc^1ꖔ֐o7*ߡ~F.ک isskSyJ/ӱ^uKJ։"u^ئ?r4뵊LWɢ*iȷkt1/[R䕷aTq&>^-3뎄 eڧZ?5P^lE^3"""""zcLы4/+v6cZdx4yN]Vޝ~WY{~%B[p8JOZkkV{*m=9U*TP2\&eD&!N 1#j@{^@buY=~;YCoLBkޫ VmmZ״.ЫR&ŝ/eEGamkַ]^Jc_}kzZAEu j[ ⡩+cUiy,)8 2cM}j'gUW7(^5N;k&I$ΘdՓƴu\rH3tb _51$FZ-JA{bJU|{"׎W٘h3tb]r,ڢ-쯈>C'Ɔ뢰6k] : ()#ޚ]>U%&"v]W^\zw}5$It]gL:ԶU}|1EW|͋br+>_Z༼boݹǺ~enMXH}|rv9^a]kZs( j[qoԭ5t~eڋ=? 񘲥4R&ōOL7hԌ]7o$rk|kTPLZyUM$I:cUO j[cԲ۞;?k_ۺ?n{fN_<ֶFqmK~ ]1n=+na~l>%k/kZҏ״ ONŅGچɛK΍] 7Ğa?bj,+.{k1dۮ$I>cUOkZb1gM;[c/gfC1~]A׷E(o6KCk߯I?f K_\t4~4lRl;rnLar..<]۪Uq˘}QTw'ʒvk4eKiLܸ5$ItgLic7x[c͞xúsr?OW6%x<u`u8n=+c/̏yyŜ]g#sI,wĶO/>u*k߼(~X\t4f|??;7F,ޒqNˊōOό9;ߋQh$]&t.k&I$I4 cZ~Mk-l?(kʍGώ~OL_c1M$Ǐi@Vƴʦ+K}H$IRO˘deL;^|=&m.Z$I$̘duL˫nM^k+㷯,-ۏ}vMY?>}׸7bʖo=:`TcIDAT^x]5\[≅[}e=$I$Θdƴqk Љ5~all?Y<0yE}([ ȫnGg#gFNoLwsދ>5==|.^Z_U5\[bWuKM$I#cUOr+_hL3tbr?߷%zLZƨxuYVɱX(~9vAyȫnӴsG^uK<4ȯiXy-[LZ&vV\wW+Tc6s$IʘdՓƴUk.9:1X%16~|"FC'Ƭ#/˱VX|kW:OZ;+{I$IRwgLӪZ~miECڭ̉e'IKgO/]ވɛKcWUKLxw_ ~vnmhGYClJfg[Yua>#Ƣ²k;w7CSߎzn$IӀzژ[/Ώ_]7Z|x>967d8:g}_ukKkK+1cҦSzr2qSIǞ$I$uw4 6} k[{gf DZ cԲpCn{^Z4^/8sދ۞O.ZbgES5~a<ȯiGN~+=Fu=VōOό9&sl&?^}w_ۗ;\/غ$I$eL:V j[cһqٱ惺/S?۴d{VĶ OMAfƈE[bwUSVsv/yoo=FL׶ŠQ3=bOME/-?M_vs)o,vUg>;M$IzZ4 8VD~Mk9gܷ- j B[-՟롨5ҷwUDI}[zLXS7,cSq}[յ%}s{uI$IRO˘d[Ǵܪs?w,v״>>7I$I3Yei͒$I$i@V]ǴU͒$I$i@VcZe$I$I>c1M$Ǐi@V4I$I23YuvT6K$I3Y$I$I̘deLrЉցo؋ˣS1~RD$IR͘deLrhp4'Pz35$Ik3Yur*{DS_[d{l:z򚎳Xcp`~.KG_A}l奮[b޲^{M$I$ɘdƴG3sZxUTv2~?iE(}jVĀSNQT~=~XW_Ob񳧦#6f|َ}p{'#9ƽS*kyk9~ϥ^I$Iʩ41U4v1~<ƴ>C'g qKѹbGk[=k]UR6ǏNGb۱sϟo7WCVENEslh{_]xX,}(n L_}c/9r*}𩱤hl9wc'-!WֲoZ^T4Ǜ%UH$IRg4 4-_}!Љ1-qNjLj[cOMkI[ĠQ3bWeSVD^UK {}S8=M\U-[n0r*caѱ?bj7FneKj?]w(=1%f|?rϏF9ͱ9OWVd7ٮANEs<2/fx]I$I1 Ȫ똶棟E'_rL_^m ~vn'6􊯝$n}fNl>r2rَÔ.8?;'ihۑZxc/..>C'ƴmwb|5_]u>|mek"I$IfLicڶxv՞}삸i̵Dͭj˶c\IK㦧gŖ}×lW^q8zH}|rxUs8Ƕ+{4jM˱ s7\ޗ쫈̎ ʖk~[/S뮉$I$]1 Ȫiߴ-ªqs⭃5_?v1n=+.(* ON]q8Q~iQKc1e{q۳s[x v }_̘`[/m(N|r3{5$Io1 Ȫ똶W%^X7+߫M߿ oFSg+Ǝr~8Zq:ßğf>5=O,;*xE燣j?N};}eYlhe1p̘Pj\~iQ<4mUٮݫb{ez5$Io1 Ȫ7i*}qz}9͑_EumQP_5EumS~~a][ֵE^ukƟ9-QTז=57%{˲F5]ڋ$I$}ӌi@VcZSjgUsT5w{QݽǞp k"I$I4 1I$I^1 Ȫ똶I$I^1 ʘ&I$IRf4 +c$I$IӀi$I$efL:m)o$I$gL2I$I1 ʘ&I$IRf4 1X$I$I>cU1m&I$Iz}4 +c$I$IӀi$I$efL2I$I1 Ȫ똶X$I$I>cU6g'’$I$;p17Y={6Μ9><+>:iI$Iԝd8s1 XGGG|WQ~󘻿!t26 5I$IRjXɘ!*>}Z$I^S4cpE]G5I$Iz[IFNǴHRѷo'x"RT<#gE*Jy+WL_*}wT*HRq 7ıcǢ1cL[vm?|HRQ[[# w몾yȑׯ_7GT*&L ǏOӾݺꟙ6nܸHR>RTvmcڐ!C/"C Vƴ9rd <8n1bD?~`Ƨ:_EEET*%%%տKUVEDĞ={?yRXlY466Fkkkr-1`x'ѣqy>sr:u*}1wMdK.͸i;wL7-tZqG*woVRx""siӦE*cǦ xHR1qhll.=Ui#GT*Ç}ƀңP1=~_ѣ7M 6,JKK6o޼HR1gΜK>~1m:\q3fLR5kVf駟^qjLd)=. ><".ӆ}s~+cڶm"J?gϞ>|xs=qرK~sʕJkD\V]]sνxwM6EKK5!v&>}zDDTUUaaEcڂ ҷ .;yqf駟^>N>w}wRxHw/ c=~틈cZ籟y8qD~8zk<3qo|\~y@odLi1 2@B4HȘD{{{EiiiK$Ik*--G_|4QUUq)I$IzMQUU+;ژ\YYYTUUũS=N>-I$IR=N:UUUd#3A/WRRq̙߫8sL477GiioL^8 iZW|1 z8}tw VO6WfLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 $dLc1 i@B40 FgϞ5WfL0~Э>(--i=z4ꢣ |.ήI62cr_|E8p ꢽ|ۣ5?_~oL=ʢ4%I$I5FYYY!-˜ !c$dLi1 2@B4HȘ  !c$dLi1 2@B4HȘ  !c$dLi1 2@B4HȘ 9qDPOAu^w=Wwu zmU555}=UEEuQyyyرc?~caMFI^w[wTwQ= i4C!Ø!]wf=~L3ۮGI_zǨZwZF4C! i_w`N*@*IENDB`manager-portal-menu-application.png000066400000000000000000000620721325274564300420460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR5ʹp IDATxSԇy{ff[ܯ[=FcsD&9jC5h(\v`EC-hxU6t{y'y^2"""""""""*+իWUQQr]~]AUWWz"""""""""jS]]_7n(͛ iDDDDDDDDD]TWW ]zYի *++S]]uQYYcQ 15=x1(ӈ߿ODDDDDDDDD]ĘFDDDDDDDD`iDDDDDDDDDfi#"""""""".bL#"""""""" >bcce/_ާ?fmݺU͛7eΝ۫?~ """""""#kL4h >\ojjjzo̘1ڲejkku-jzs|ЧADDDDDDDDG>}2dm&0t̙>;cZ_vL#"""""""5JJHH͛7/j>0 9%''kԨQ0a֮]ꀿZ~׵awj?~(>}:\~ƎC7W_}e]Fxx ðqF˨_~[ÇWXX8s_uٳG6lo#Fh֬Y~zDDDDDDDD0 ㏪ч~^{gkƍYfiϞ=Zh Њ+cZjjk߾}ȑ#t:GիW+++K .aRMMJJJXe,ZHC ʕ+o>͞=[a9ɓ'+!!Al٢CjѢE """""""1mڵ1bn޼߿_a(77֡l>#ے%K/ƍ}OMMcZQQ {Zzc(11Q۷oK֗yvt%%%2dj2Mg&L*Ν~QYcZuuu{wG\r^|E-Z/Zj6j3F7oN׵~zUWWkϞ=4hn߾s%%%~zc|٣aÆiɒ%ֿG:Ç0 ]|2335h ݺuK|ϲe+;=zmLpm̘1{=>^cƌ xL5h UUUѣGe.^gZcL#"""""""z1mŊz'OZedd0 ;vL~G2 C'N{vܩΝ;>zy[XXXXcڡC:|fݻe*++LkiDDDDDDDD_֘VUU޽7|SIIIN?5tPǿwɒ%6l\TUUYcZߵ~zUUUa+++5zheddȑ#2 Ceeez饗xb>#%$$tx.]7}/R=݉+cZ^^ О={:<=>>^Fҭ[lJIIݻO/UUUTUu=UUUi֬Y֧yz5b]xQEEE\:uu={4n8KÇ믿W__~~.ꫯ4aK8q~gϘFDDDDDDD޽T***a:uTiǘFDDDDDDDD`iDDDDDDDDDfiwy*_|iDDDDDDDDDkiDDDDDDDDDfio&"""""""".bL#"""""""" 04""""""""ƴ[nQ15r1vQ1cQ1cQ1cQ1uevl6""""""nWiiv%0rJݾcڵk4l0 6L{=3gfq0 :T {ァ0 SDD͛JSgcZ}})Shƍzuznndwx=:R[[r۷1 ЧcZMMBBB4l0ݸqgL;}wvڵk2 CiC ѭ[بc0 m۶Mn[Waw0 XBoVeegD{=2224{lIҏ?'ZUqq"""4sL9v%&&j٘ܬt*;;[t$)11Q˖-SqqΟ?eeeIz4͞=['NPKKKMJJҒ%KTRRg*66Vw~4MP%%%b SGc… tR*??_ZjG}T }:ݻwO2 C ,h2R_^3gԫ*0ia($$:p"nm߾]ahZp{-//߇iK<%2MSGci***~Ti靍igΜQLL1oڴIǎSSSTSSc~z[#۞nWDD8I:qΩLm_/XcbW:Ӯ\g9Nf),,zo5e{zt1 cÇAi֘v9 2D駟*331-<<:1Xn'0 edd(//ϧ~";9+WHzLӧ[*..Vpp2 WQQ;Ӳ_t˯Hʕ+ИvA͛7*++e~uz^^^Ǵ'O*$$DV111֘r4sLC^4Lin[>;s挾KM6v̙3=6n(0n}'S!ycںudp=|P yդITPPƴL-^cjhhPJJ̙,iƍih7ݺuKi_Mֶ٘u`x<:{6lؠ8͚5KGW 0OeLsJII6m$04zh͜9S/ ni7o5l0-X@IIID[?퓈_wƴfEGG+==]7nܰ˓i:s2RNNlLU\\۹sۧs)..\V e'OMzhy^.]Pk/*##C^W:tuQll2S4]OmL+++Ӑ!C1Vz饗BΘV^W3fК5kq&''Pv]III'~;1v+..N*++ӧBrrrt:k.jw$iΜ9T]]] HLL>ܹs:uj 1?ւ t%n*Il UvvtM8QN c4"k֝1m={3Z޽[*,,Tdd򔘘(YzC.OlL -ZH1c9"@uVEEE)..N7o˗5}t{:Hر޹sGK,QTT㕙i䲷4Iʕ+Xmݺպ/^vޭiӦ),,LIIIɱ~ c1OXxӈ(`L#cL14'iDPi0Q1FDfrwrp;1ht:UQQ߿W d#cL#544@0x<UVV@ ~fL#y<ݏ~8l6""""""PYY>|hUiD$ǣ􋖖41(ӈ1(ӈ1(ӈ1(ӈӈAeee54 @i@1bLĘ1 np8dوLCNS ͘ pn[yDDDDDDD4`jllTee v͘ peee*//W]]\.рN d#cL8ݮ\.ny<""""""RMM߿ӀfrI^I.K63ffH X^Wn1 6M~xǘc1 @i0ciĘ@iGaV/uرonѣG?|ڷEO4774Mݼy\޳t۴} OSiNSz0 7ߞzrrӺ{>cZzznǽ{m6>c\kҤI֚5ke?ɱ=~ܹsG/ɓ쀎)%%Ei4Mmٲ޽{WK.UTTf̘L577׮]g}͛7O]>oٲE˖-9>L;w9@kee.\H}:}XizGѧ~vZc8ǟzQqmvUhݝkzcŋe6lؠ۷0 ^Z{Ǝ+0tm9s߿&͟?_sUKKN<)0-+k j%%%x:Vݿ_555i Rii$);;[ahҤIq?2Ǵc $eee0 }|Ǵ?R֮]+0_t'cZFFfϞ-I5qDǕ+88XSii222ǴT]zN .ŋ%=b"""4sL9v%&&j|׫ jҥ*--U~~jժNcbb-[b?^ 貟z&өlҥK~Yg։'ݶJJJҒ%KTRRg*66 Z755U*))ч~+Vtx)22Rk֬ѥKtaEDDt8uu?_xQ@r}ƍi]WǣiӦiڵr::t萢Ӻ:.#Pg[RRTRR=]?z;Nzz(33S׮]_SΝk=3>ydΝ;j2 CC ѴiӴ}vNah͚5755v<3K1Z44iiiѐ!C/Z/7k׮2 Cio=999>eӚxJe~N/',,L$˥<4aYcM6)--Mң4MY4MUWW 1W\QDDϳbN¬ m555)++K555ֿ_^֭ 貟gΜQLLi&;v1I\n+""}!O8)S|oۗ/`C)>>zf$m۶1In'׿E111%=Z>vjڴi>-==1q򌪎'NXggg+%%E>/;{q{ =,ȑ#^6ژz5~x=ϟo=Kܹs>>޽{Ȱ,0[oh2 C6m# IDATھGY@ǴCj2;9~8cLԕ+W$=]0}Nr4sLUŎƴrgSSS}^fsN1-88q׫p 1'OTHHbccbbbѦ#GEEEڿV\01~ck{deeu֮I|$9x͛s^2MS^~:=//1oVewu?_~222$I-Ҿ}cǎv/=rHL긺;=\kLXqқ?18cW@o30p=OlZ?oa>ԩS_;vL}_4n8;vLahԩ2>#;ri^yQ?[ǴO/ e{aĘ_wǴu4M+$$D!!!2M%vx<:{6lؠ8͚5K=BCCbz5i$ 1999JHHPmmm99s(++KEEEڸqϘe?ɱ 233xoC$u>8pݘv-kM6onlgZgN\ݮ($$ƴή?Oe=:8wLk{|m4@.7cq܎=κs``1m2MWWWkĈ6l-ZYf0 ӧO0 1B)))5k ^xA555x<0afei#FPLLG~?Sݾ} ^X@pIixugLknnVttu <󉷭 u!kǣX?~cip.=zV#b.]PZ{Eeddt2s)..UV{gg$6U\\'ܹS{LR/%z}cZWN\ǣɓ'뫯ܹs1-''GӧOnׯtLw\#PHKKܓ17cqnǮgݹ0/(;v5uT1Bcƌѧ~jĉ>|Fh;w:͛={ƌѣGkܹs玤'{g만=ZǏ޽{}u*,,lw>ңu~z7:c-X@.]<}֭]^Ϝ9NڵKZ`ݻn[qqqJOOWYYN>p=&I3g233UWWn@JLLހܹs:uj tL۷o+44T999Snqx 1 @ӛ˗7v2'q9͚5KaaaJHH?bL}L70ciĘcv\> }it:UQQ߇@P c0566@0x<UVV@>ivUVV&!FDDDDDD4`r8*++ hH1bLĘ1 c 4 @i@Yc/Ln1(ӈ1(ӈ1(ӈ1T__˗/nf vJKKfL#+??_jllTSSрQW}}߿ӈx/_Vyyr\uuu*//˗͘F4!""""""0n\.p;1hgr$+׫&\.l6G3 ZQ0*7rW1 l6<O_y<4"ciD`i0Q1FDƘcc}49Ra7dOVhhΟ?/ۭk׮Yc@1 gdL7n{=>| ѣU[[˘Fԝ1Yi͛}+O;ݹsG OZccLTUUUJ@ ``y&4n}OUU &0ti4gfϞN"ܹï{[ut^Zr%cZ?z̍in[ PNNc34mݺU~ݗcڮ],4?gjL{ ј?׫p᧭?gyI(66*&&\.fΜip F=]v4M>}=~\RaaaƴrTWܹgLvn;zܭ_txӒd 544vkϞ=2 CcƌÇvw<ݾ}[ahԨQrӈztL yդITPP|hUiD$ǣ􋖖41(ӈ1(ӈ1(ӈ1(ӈӈAeee54 @i@1bLĘ1 np8d99N566r[Q=ߏivUPP[nǣ7o@n\fmyM566UYYߏit:UYY)RNܖeee*//W]]\.='թ\ldizev}nZZZ!"""""g<-˥91Xssl6ZZZlRO&˥&y><@y^555rf~4`kjjfSss_:T>ucHK+tf=u6M }~{l6y<qodӀl i.oO4x~1Ec_ǘ1 _(n_:4?i1^.u1wJ.8tm431 _(>|#N_ה|O}dy&s&FNdW¨Wdj-%gӜŚs}PWvmSs崦nS4uIn+c4~hll|wԬ*ESS+~Ę84s7PĶsBQ^O;!,C~i{ߝ׻[oƓz/77N)din9r_3eL[խceLØi-ެ}꫊uLUZ_'(tOz^cמ}#J_]7ҫzI*GcW~uR2N靌<7Vk+4bL+..Vddd04~hhhxݑfp]q*ҟج3w}V*4n_Fm/mvΡQ[/+V7nȴM[u\V굴S2 VAV{i2}U1 cZQ\'moq:n_kW\ s]HC tA/l]C֞FjҒ~JOi5>;OmQNA~!VuX0E}}ƶD}ſ~VrޕyFCgo-X`9{YEA} ۬ z']AhHn>]rElVJIQwRPs4NiB];R7~,ի; zi9 ZIAq{·)hf^AV)4*UA?Րƴ:EFFj͚5t>kLzZp.]R+99YV4Mի2MSSNՅ tEi_IIIZdJJJtYj_qq"""4sL9v%&&j^~aaJJJjŊ=ƦMktСCfL17ߔa*,,x{YhwV5t;PEJR kf[V)m{GjO>𳌄 JYAI) ѳYח(hB [,iR||3ömfiW\QDD3ќNz%=uYcM6)--Mds{8qBSLe+--iƴǏ[/={$)77WӦM~i9ijE'UjUnvI^VǗTP/Ti;Rq974UM=\)/jzqi-qV/~IYA7)(x&c7|c ]~wkL;yBBBkcYң1Tegg[_ܹӺj޼y>WYY)4U__3y^Ӯ_ngi;vвe|ȑ#i9iɘ~{~w%Of.>ҒZګɹ[$\|kΟ=ifVͭ[us?)بKAѰ(n%5QH4a n*fqY,M#4tGZn6Ax=UN,OJ>>;Y? N Dw>!y| iJ4[ M4m3$h8sô~0-''G 44ͽX\]v0?> C+**‚ ե|ŋQPP jkk:uJ9%Rrr2i1cQ׈ Z[[sN̚5 nnn BVVc+o8m IDATn:T*ŋB`ZclpY(Z[[ߖDcpst{ВRш%8Q[u8?dU`wF9J)Ś%X~5?|ҒOB=i^ }$/ mZEllij5ѡ| R3i%%%0|zô\j:ac1cl5"ô.Bs"44B@V+Yp!Xd VZ'''a=a >EKKK1w=jAJխH4"Pŧ. -kq8ג-9 iHg!H~M\Br^ Eyf4<{ X| 6mڄm۶ ǕegK]VÇl2ܸq4ףj9vڥ,m4ɄK"66eeeHJJ˱nݺ~'c1Fd!>SȲ pXfcՅ_~0͞0卢Sm/ MAYq) [b6dVۊZtdZQӂ$4rY3d4aו&ԷݐH }&HBr^ GZ}B= ift۷oDž[ĥgs .yKJ9fL3!-:iQHsv6HpH.!9ӢZW`iC[~~~ EBBB7;`1clXiޘ?tuu! B̛7k֬Qn`9Ge̘1Cyn@`k97aZaHo-Be9TM+1_!&>&,'ǟPUCi>ҜXH3AkH/!MZq% Kac1ciXi}Vر^^^pssCPP>|hFe˖ }?n5Lw9eyhhhiʷ_"Y1&o'~/v3pe)C7@(cE.z$\| i}IiVB ΋h]9Lc1c1fa E(,,DLHHu5gy-4LpHb#$͐fDCc$U!9@҇fFCiNHvCRGÏMn>_ /@o֕4c1côr888 'N1}t8::ӧl=cQS~su&9:󏳹:yT˅R߈q gR3P7`:_#꧋:pG.4ZN1c1ގ<{4HOOGPPK"33c0M0qW.ccʡL*Ƈ?p<)DZRxjx\azUǞ )% #;0t&.끇m 9u}Wô2 0c1jjj`όlDiёeVWWo;')|b㟊13>TYG+u 3Oi13ju^+u/xRY?`g)O1k_!>Z,qܽ(nE}{ҰB] L& x'c1c'uvvBӡf9Lclg=_t:*+W!/lZ 55/_}Ē,w\|0o]֊+^=ݺukۙ#4[ZZ @uu J5$?ܯVx򘒒!2.dBsK+"x7{Rڱ?͌ifIiGp&g2T>n3qQ-&A^hƆK-sǀ4#bӌsǀ~;sqQ-rhJBll,߯M4"""" """zdJXlN8\2Qg2舉'l6ybII :0xxx  ۷뺽-Ϟuvo!ۧ,mmm0Lvmaډ' ?|OCCP2L;w򘤤$!9/lZ!33ǒFxaZGG2X^b\]]R1=’%K0i$L26mBss3dՠ'11| ^Y=ihh-[0m4L:;v찺`aa!%K >>za5L!jkk}kիW1o<4}75`CSsMoľ$=-pYc_rU`CS45ړS{.]k*_tBNܙ3g Y>E;PUUkBR111P_ Ai˖-S~7Z]?ןCEEBCC1yd̚5G(јNifg~5z=bbbA0oB999յ!Çɓ'㫯RʑTV3.[˳g|!p,n Yf1Qv=(˗/ŋ}.sŊصk:::z1AAA@nn.>SlذAYܹslܾ}gV~___dee!##rtVAlƲe˰vZjePTشirrrpiB5Ap7 D{OJJJ0a9rj۶mJR>~[T&^hDq +Î[k/4-* o>us۶m,_ck___\t & 7o֬Y\$&&[~?~ <==q]dggcؿ]}=oooTc„ Cr`""""ɓ<_$&&"** ɐeŘ5kQP7oބݻwCGB_asd0 OOO`ݺuo.[˳g}+!.] ٌ 6`޼yx]m_}>G_y={Bi@(njj… Xp^|2v h4èQ(ߗ\{z W*((3L&euC{B5裦~if20a$''[ Oܹcu}F\~aLcs=#cF)p^0yOiAЩz>=v+îOB1Le,ݻWWWk|^t ӦMS>B>>>VHsNzuMfu;w… DDDDD4m6{l( .oRSS! a„ pqqA]]r*7osGSNEXXݭTڵko.[˳gl41g}5???M&]m[7 ;w.V^܀Çeƛ=􉏏0M7 0O>U0Mpqlܸ...V'''+YWWWY z\xxx(M!ZՠP 0mkLkllvy}}c9e7saow=s߇=RyƌQ:uJG2!Y={LzPP111Vm?ןСC &""""0l4 J$&&*4+W`puuY~5 ľ}0uT̞=gϞ}2vZL4 HOO!@AA}wޅwش\/g:d;WUUUX~r h{;!!A9>ј9s&\]]^- >k}[sm=hu}.3**J !&N>i'Nx0ɓi2())QaO?!44/! *((hȗ#!4\8A4zlTUU=BNNΠo@aZRROn5 u b?Ng5Ɂ]2W_l6ӽNA ~}<׬YMOOG\\XǏ}\[Wur1@KOb,: [p F5Ţc})S51elݺEEEׯC[n)ô7ˈ|innnVr L Y0zz*<==N󌉉#G}hô'N(}#i0l6C 99ٮ:;;ludVXp^4az(((%KYYY4i>cݻʅe/^K?T.ؾgrQ.sAzccӦMP͛7#\ rk999(((3g1[Zg9T#Z,U:lǷ 6/xRt=mg9[Z{-ϲO >o2퍈tttxΟ?ӧ[ z3$%%ӳ ׃`ӱeƍ8q"222FxeIDATh8a?&M;vXkp#i],GQeffuىL7=%o7|csf6wARaصk0c ={YYY ΝiӦ!::Z~A,w_jƍ2e <<~,S/UXpTGkZOTajtJsYU)66i^^^pwwGDD_n5LJ 2*++fT*xyy!&&3Ls{_]+-- ooo50F@i} `~~>RRRP]]m5Duu5RRRkzގrM%8'&oN׾B?\GXpT+ᵯ7'c\Siu)]MɲNz!)) 7n7<9Eܯ#ո~ɂۦpܐ p_p3TTVsFDDDD4pFD6 vfى/^˗x%^xa،j%%() Z|h"""""8L#"jFDDDDDD0l0iDdiDDDDDDD8L#"j5/NDDDDDDhZ8ӈƹh_""""""jQZZjqsj0#>DDDDDDDhVE~~>l>4"hDii)j5rrrƍ<5Heӈa8L#""""""""iDDDDDDDDDv0N4͈hh t_""""""""ѮR}}hWQQz:DDDDDDDDDT^FTTT *5P6URIENDB`manager-portal-menu-icon.png000066400000000000000000001264771325274564300405050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRJE3 IDATxwXUgɼ'=jf'}RlMbQ$c%cAX)6DzoҤ"H"1S H>q^{oϹEիO>$O>$O<O<?hZ^Kիʝ%p„ ?W"B!իxyy1~C jkkwB!Bt[Bg]&!B!dܸq<(=ϟ!B!$^^^fP!B!z+W裏6B!B!=B!B!衤 !B!D裏<#]!B!GB(B!=B!B!衤 !B!D#<]!B!B(B!=B!B!衤 !B!D%P!B!z()B!BCI!B!BJ B!BP?0ʃ>B!B|A9st:B!Bt9sH!B!BH B!BPRB!B'Of 8iӦi&z?*kj|=ݝ *[vRB!/nJcnݺc<)B!BMh!ljjbT*ˮ]HMM֦o߾T*N<<)B!BM3g虚)^^^T*G@@Ĥ4~] ϓB(B!д3f,Z?֭[3J/޶I&R8uTץ<)B!BM[hf axxx_=cΝ_666bhhy=z47n$77?ŋhkk3uTϰaXr%?{=ʕ+2}t Đ!C9s&VVV466z?F WTrYx1 b,YZMII *%KhT*۶mhw֟wUJbܹW@ TaÆRظq#\v kk;;;n-#55R__OSSL:Yr!B!Ocp?)kBx79u*-[~]M曻>K.m1SNER߿S'N^1U@T*GMO[!lX|9*GUUUBؖ!B!+'OFRannRGqqyT*6lhڸqPTDEEgfddR8p`l1-Oa iŨT*7n^Y-[!qƍ+w=fǎ?)myB!? Rؽ{wFK!\x]BxւS]]}cnݺ{Zr oˏ?{jȑT*V\͛:DRVBؖJO?kx !B!+۶mk}ZoQ\\̊+Xb–wׯ/^=z4*6"**sU˼yky7oڄ߅-#22JԩS?*yB! j/qrrBR1xk!WZYZZڦc[EEnnn0|bwe[ޅ<h;GS; M%B!?iֶ>rxۦ&.]JbժU_B0vXT*111w=&''JŀZp1ׯ_ŸƍR9rdΝJ"((g\rss2z?VUUݻw5myB!?B{_|{U5OO֯mt˖-WZ?S=CؖV[|[?0''3gcM~Bff&:::̜90zh"""v?WzYb| fݺu撘Jb߾}9B!aPtO-Wz)B!B&Yv-cƌ٫z˖-CR+B!Bt6)=QT̘1GOsuOn.\B!3H!a]ٳQTߟ x7yw e !B!:ڵkXZZ2sL>cԩSٴiIII]^)Ġ#9)$WLgW\r2 &++"dWHBw36a [MdBK_>s~H!"-ֵk:m<<<(,,˗Nii) S\\3s%33 uNٴ:Wg*#9O5xmJ:Ogg*=ւ%gGQ%S#UHHuuq|~ ՜suۛP~?"< 33U7[o Wy*;Due5AA_@2Ui)y>0 `N`s3âʨn./= X0+LcdD& a@&3 / B0L5V<1}w*tf!ԩSlڴH\\iuB,ٿ?Xhppɔ }Q'i0o./쐵H:OgUV4B=TCL RG4+{[[jԸ89\]hjj0ػ{7ǎ%!>@LML$W݈&r3d搚JrJ*))JrjɩiNjZ:iwLj4.%54R/,ёPڱI:OK!w>gSՕ ΁O c/_YVOXU.W`_L&0L pw1L?GP ~F d~(#ʘ#(،mZ.ҲiwpQ:)>舣#W\1PXXO-V\UWVpv@'6'{tMZn޺Iff&III02b.9xjU7\UVUqi"SMTtDG:85 ĩ㉿cĩPǫ'NGt8jRSS104"3+[r ±ys܋0sfdIͣ&e Xg^r0C/8r&-@1{3h1p(LQC{eaMkBE|@.]?˖-#++ BBB(//Ғ㪫)..ֶMsuTԡo}4Y5Qjcr3y5>r-x$̀%LYIH\#3H:OgUb £SiKIZ zC/\Ε 8.$bog ;3}{vj22.s11ݝ;@rʹ'WW^ӳDFPA!FpXaE434,"lgE|0CB  %*:WWW,ɑ\Z {7=xSߝ\07*;MMc^bE5Nj?Kh=j?\ ;GC`wY(MQ|a2MզJ!"-VmmmM~~>Ν---JJJHOOgٲe|w۷ bddĭ[077o=Cc V\%Gx5Rn4$)s'# iq%j zF!%K\uثjkkIZ Ft!o7N๡/\+ BBH/ĄDLL8~<ښJbcb120\uCUrr2fXY㋇xx㋏~~A~㋷^>x㇩nn\K!v}/>e:]m3ag84]P*\( >Ogu^dq<|`ÿ7N o\yr ʗ(NL;24(EGiZvKVKll,Ѭ_K.&//m۶allLeE%s !""B~v`{b A ֍}Kh=BH@t %gKX率90E _t^UVInZ(|tOZڐ mt K (^JrI3(˗Luc+/w}}I_?IDx~/&Bee%yyyK*44[;Ϛ䊣.8掛xxx酇^>޷㋗>xzy酻'ꎛgLb3\?Z HKNi `X>4 ʓ(QL?24(c ߁2d3[ymZ.Ҳi]zCƆWbiiٳgIOOgEEEZ\N8իW9u%%%>|6C6Q=dr 6kvД}J?¢PL]F >nDm%SXSS5 DsŅ"sPA<>7dG0=kJpPU\{r=oc9kY3+,[;stGgg] IDAT~'gstlk;tWWW}tv@P>4Au ZˊJĖUD) YY,Hc[23\g{i 824ԓ(P&4?MTţSt۴n_GϜheX]]׮]cdddOHH/G^^=Ga7Q Rggѐ73Prps\_?K_/=_d*[f@RH) IosOi a.\X !a $,吰+@-} 1B—Aķz\Gš\MJ|AA옺7yjSUjObcc dN63q3+kkj`vw߷ K+ΚYr +k,\HbR2vv\K!#硙Zz7 7ȯ2.հ1ql,fSP>ZYNgG2Ky&1z1o޶28ea(C|eFO6 ƣ_hZs!={6ʜ9ݻ^rEs$001Řyyy;v vG6Q :Ww]~Vr=}!TO"=vzp*Y6N\ud*#9QԞ[Q!fDg|DQ r|D ~A H|tOKl}?` >~gc49b_rչړ+___B̙欩&YZaee98޾:肓+N.Gg7\ :8bmk6xx|*L̙3\K!zSi-+pJ(+Hl 9^x6C37i,:s,^e5ʬ(_@ʨ](C ^2p%jZs!2eJ/777***pssѣf8ccc2.g`hhHPP>>>ݻW#kMsuD9>ƐQ~/vL3W"fpqw] B+ kqQuCUUF$C!#}?Oy߆;/T5>OˠϗpNw 2Nrd>{bj.n~͆ʶl4@'W~~~89`-.Gg[JN.85wOw$=yzၳ;NoJf3fff\K!z ɗ 9X_z-yW ɪrإbFEDYd2e)'PFJ(#c׶iݹΛ7ª*ӧDGG|;~p Ғ˗/n:5&ٴ:WG䪴((i5o{)/(JCB=Sa8Ga ~,IryړӣI%Hjs#h ?x߄o0^onùUpz_UAsHQ u^賍XEkQnGPy3} 7='WQQQͷ1˛իWs>xzy'^x뇏?>~w^>@HXVvhm~f\Z 'n<}cwsJ1+?\t k/BhA5xTtr5ݍe\xe!:(P>\<2v]*haEE{eٲeann/SYY!EEͶmHLL_TG%+7ŏ3)r< $M͟C1q>U$E_drcH%hZɭ/=w7:ίk*ؽ6/K`*,Qz瓢v ;} a:׶gpyo>za!vqg9ݧ\ %W=&4,:|f=6b|$[rwO/B##"*("&*&8 K'($3KKttt8xP+KKΚZ{AԴt%WB[=CKO%˹xC`-o_ƛߞƛ$6 n70A^Mw,}up9ʔS( PFCXeFy+MkBEZ6Jdddyf***~Ǐ'//SSS011DkMsuD."}aݘ'0Azeۗ ͅ0y M7R.[cv}0i=n(/8h.D|j 5?ͥq=ίkE~,_[JBJr}# dTЌ^7}F)x'Rs'SO1}zCrړBnNefm6lf `g޽ت]b`h-.n8xuti|}|ٵ{-Bwf͞9:uZruk)ϥ<]7}5.& k' B1CA-)( mP4ͯo2r'(AyYk!lZs!;wn/7Yz5999x{OOO ޞ(o߮lZKӹ q5>Cɰ MM}Twh r=z*ӸlY &+LzMaϒ1nJ*/+((­ >5Zͥe(w@E%x Xe{ Iث***QgY F>Л>})NCu88۷/'Hdn=o_;k2m)!c.tuٻk7"3#\]U|hjmdҥX[Ys!1WOΜ9#ϵARys &ucǎ !`VQ``ϛ A1X_b/L gP82X¡ߡ Zkw86U a/C###=/޶3gΐٳgfݺu/nN!?T: f s>>Njh]m<6kBC/z܌TU7La8ٿô@O ~ZP9"7O>OTcnBT75(u֙3Ggpؚ<* __24y"y޽9Ы}n= y]G'ؽw7K1c3e t/(2+IM$5= R.SXTFEE51jNh7 }Νɦ-qsBruk)<4?NIGN2jl Jr rj ^zGQ= t.&w(7 Fxe>ʨ(C ڀJwB;hiRH˦U^^"55(tuuSSS9wG6Υ\Bѝwਥ` .[G8 5~#!~>ùd6A{½RѭnH*+-0bNo~c; `K 4{s<"g^h:_z`t$oW`"VY#GCƁ}zGvSR\Z @{ 孯=ʘ#(mFy{}0 :XX`4 A(C7L>27Y~5)iuP|ezV sxx6eee}}}6l@~~/6//uqBCCquum.XF6Υ\]"qe?-WYN?t&xo} 3F?ptž :cU>dJMfB/'z]~(~-+w@N'99E9HMrVCE,/'6|pG\`©*^\ٸcл7{sGDPMЫbл7{|c{^ 5W$''sj͛Y}KJJ*.9Xu׳^k7n@k6n܌:r[;[c̊ss~/ B#9wXK!|j M(#ށ2L e格;僯PC e:(FD1V"*&[x2pʿgk:_5 I<4ze Ezm_xzzV111MDŽOff&ZZZh/Si4 5ɲQ4_p M3ww~V)a5Aɰ}q8OGyR0@ťDBO?ɋ`Bsz"h> ϑt?9())Lu3UII)A("GJlFjL>}8ѧ/DÅ Ԟ?O[oqv)4-U7\2HHH`߰ϾYqtt&3#qr:QCHHIdgfR^QNnN.>>߸!CGzz,[5WذqHc-C{NjM#<mbDzebж=ֱhĢmFNvxΝG س_PKkm>j!+voZvM]L@@666tѣG`ͿlZKM }en0漧pb0^G̿=D>HUT*b.zOca;~Oa <0g-&4WLQQ &\5h|~N?\Z s`ls 4xgI^h*W8v˖?Ogn4_-['=-}zJc-{u`W,3E20O RjP>5J 3NgU6Xp|0$>Z BC!9ڻ7{1U7\STTDff&`QXXDDX(eL>o☾!vN9k8qc8},6ma,XuvENv6A:b HLLb}wN},IGt$Ϗ2%f0d6se(BF0l0d&_fAc]bTNf {bBñ? Iͻœ!|;/30MkBEZ6vMLL zzz!44xvsu׀uGUڥK՞q~'6P8љ sqC;񋸕ʰYd8!k҇94lGV |R *'U^'7>B <;j`LNfy^~pe0x<zO_ۛh=^ʹ7WKѮ\YC=y@ڐX2wodQG{&70g᪻;DG{T>%]\umUK1+--fYq3N]Jjj*vڽUoX,zK-eX[֮[N]],,P^-wn\"ϵm`[12)L7eYob'x0؃yݙwڝZƍܘwڭ{鄄qއ.gdq)6ጱ g}9&zvM%+//o LϞ%//﮷ Ғb֬YCffF'*,,t3e8H W04mrs[L~\ [1ӨN7+4 #+xWa?V) &SXX(idw8L?×i2ջNΛE^\tџm/su׫:0'xvD}EAAEmȑd'W^)>'9|'ws~'~岨}g "O˯H’TB__oXͷR\\Beu5qBqj5qj5 $%'MYI1ϐ#3o!Cbb"ܒSK!|)߹=Ka{*#<.1cw> ;ogL8q^iħj9e!A!FDQZZNQI)>Y%||kBEZ6v TUUannNppprrr055ӓZlBPPF[F6\dx|=̺O̗*Z`?p3w_GߛP}J gOvv>ɽ Kj.{? ߄Q5ⷿω?ʅHGR$y!n|"u4GW@GFEaAUH{r5bg%p)"^TfO3`ܧ/SO\17窋 }=rhk ),,U4.XDDFQVVNqIE7?37"JJ*Hkob؈1L6s rsr(//Ql9R9]oFeŤ߼ɕRҶ)lZ-P{&;;mmmУJ Xq?y;ٖ=l$!! $$H6yB t0l0f[I,۲լn:F̨CeA=`by'33ϏN ˗/'''')2Vv; yOaH{fmO]G}^S ڬo`)^MToO6e}-5y+, NM&Atz,*Ź^MVWD:9[}{^b;,T{[Zw%^R*^GS0LX+ _XOSc p Pl͊)X*2']wp\x>1m&Nӌt*1c;xt6N@?r Z#vO8\ wCmm-X*Pl&ʩB?x߼U[d8WgX\桬CŹ8UrN](XlBV?xq́(8_y 1#KEթShiir7^Oqq1-8]n\n>pW6JKK1l6s,K%<&jR55 >C$4CБzWc( !cչΤG85L9p$466R]]PX,>jjjW)dyz8 r9A}R)|T__gVy<[dعx VLbsuvXXuBɟWs h4*bh4:!h4#\WVO Q\g5cG* $ӫ'O"kkqq?Nff&@ f B @o_8NP(d2lMx5yL\p }Cy${^wkv' .!u NꘄSD0͔F988tU hB!U2VhƁEw!vʤNW۹ q8sr ;x3]ߩ&u ~8uI.Y8G8/=@VUی,IWNBсdSdjcQt*& @ $  tw0%r9f󌞸RdU `$? %k}^ CCCFRH?2%vnoƟ Sd{8DO--IjrBx`]e8Շ Oơ ʁEwM辅WO2tFu8OWv)+tYj&ڎEv3,*Yj!JzF6ղ9[fY2kTw㺹s^q&$rBF Bnq=#I`%٘Ǒʂ"*=#ÉBSSFTY`01[,vN'^P8|U]]J$(..WG2¶'aݽ-ėI<%⳾Bo|(gןB"!UtqzDI$~6ڼ*)cNM.J};!0 Bx$b׷c׷w[pi\ǐFk+8U_[x55HS:G8_z4{ߪ2:`)5k4-4Qa9=slѳ弞-9z6ӱw耜wHY;[2,ȌvOB;!d_ B~,ʌz.0FЕplHeRdyb0P5bخDѩSppXmvNJa0"zyw=>1^zzz)lxd οY7zm:~ Z~r&va,Q҆чf_OH~ %jNRd |qM4ܥ<)c^M.粌>1r?6XEw_UL-qR^MTOO/j{-嵼{Y`Q%od#%٘*:;UҰ6[լ=E!$ܹsSLH$ Ʌ````0H0psĀaЄAMpٸƑ"򪱱7bGRADѩB7h1M=.fhxkёWx h=u/^{Xt <~ހ瑛P<-5U}'o~Me"V2˟}tp)|FK~vX zT * !齪~r?@"\U)FڿN 8T ũN wea$`=tz>vr-Z$)x_,j9d{伶eGUT+B┷[daGydiy#S˻Nvktk:% ᧤0X!`U0A/Ɓ)X*̋L2خDat*UfZ$hu:"D#zz{9v8qJ`3)΢0^"G~?Jֵ۩ȿ`ӡ˩+)l MϬ}=IlM©"YsU}}RJ]n.7O +BOo/p)L@vW)D:gX{-m5hG:wB>/?)8!N_fJsI $ aF)KY{ր ; lo f~W)]<{~q 3֩iQ'$ 144ps]!:C}\1:<1y,X*,Μ9b1N ۍdFףT̖=& Dx}~<^/^N^^޸'<\e gͷ)Щ5=Cmal&gϲO;&Cibyrp*EH\ x<^'.Nj [lw \B$˫n'`9g;P Į܈C ;^u^M-TΫ8˗(/|YZyrKҰB.BwlJX̟Jxh_J(;)O53өiQ#Ȅ~_UhQzi!qDisFٝ4q`H:x ˏ*m,Vހ`bѡPbt~m`w8nN'p}{|«cUF4Q RdUNV ¡!b?@PH`T*Ju$>o"J"Jϧ VGRSIقhh4ti&֮]˛˖IW$`XP5v ں: hooGP8qzI;}Dmja[aVr88~?/H,DT-sUs`qq10|CH$ãO/X6#A7EG?J3R*++W)Bv1}'<z ġΠ8e$勱+Pn϶D!nשC^bI8QNb'q[MVXVIJ)lW䐆^ /kf~%*~/[ |I%_\;3ҩiQ&$UUUg)>,v{s5ȽԚ{3RoCZVǑʂ"c󑙙IWWϏNo@ѩDRA{]A#F͛lB! >`0bSo0tw^Ma根JXIM~s9>vGh fm.K"ZVv*H9ŋb1Sh;huÿ2[0[Xmvlv 畏w{Ji,'HPZZ*JB,22v9}beiO[!(ƆWAmN.rGdRYw{HKz^\^22.'}/;b^Mq;W(م0+{ެP}Z#]JG6Mr$ϛ[J׳_,o˛}Yn\1Y|b$#0 Ş\52^* T1uSα1#KEfcǎ::W9*k]REҊlsX֏mƦfj -5dmt(:TPT(UjTj V[b6WS:79hkiEz8g*xwb:ư]āBuW~K_xK$޾ǩsU 0sFp立hX,F<g``X,VGѢÿ20].}}޾>Q(*1W*/ ޠ G'( G#J!9`;ሒ^ ݽ:`w p=2J8㝫^gJ\~= -nj㾕Myc;/SۦWk:jTaB݃4,׫Z~+yn9_{/>t/?Q̹fǙNMB &$ H))~ *l}j(vQR YGRjRGJGo&WSxu ^ ϭ#RMmx}2AwT !am;UjB6|3{ 5VS'!hϬmV\[Mq;W~,w\+O<[ɳ{׃da) A ;l[cG* +EZZyyyVkPt*G?(T= \~,M1|EJ`Ұ«)xi?%1&M4_lo۴Y,l^<>8\,J-ǫf7p9 &8ݗ0/n>?J-jnɌC<>aJ`^Mqꕤ }Q|2h&K5P;DQHe:5(JzRvʦM Zp90A8I Y8VU!9王k)x NZgKyN1\K* n^5six笕wYpBze5f%Nة䉝J_5|bDw-dƘNMBJII ./b1<v3OK9|\yF[+]lr`٘Ǔʂ"cRZZʆ X~=GjRkPA>N OUU555RЀ &YZCJbi&x<«)x窆ewx0z³ٯc?:rs?Zkkٹ{u-R"==틒WPJޙ ©`<^?08ˍ\~>q bw9C!J/o?88lqLUye1-eRtިhT~#VEJl;OMG;Nd5TK΢2T07jPK)?ątN+F* 0ɸ^ϕn[­/E5N3oj5ąK"B7䔍WT`1#OT:Zȏ^\gyaq1rCtwwc0,t& @-V8Rn%3RT$Y^ چPԘ-V"Rh;Pktt(P5huzL&36ۃSS5zwlf͚qOx5yשͫ`]$ H|W>Mo2^n2tXŵh-6F6L$p{+DkCp*Wn& wc=]~02tF/?6:r/epۏ,cOii*W/ݫZv6Eh>E}[534ԑ3Nc9B:h-Uth0"U]I~fE5l穑%Fy5TVS]]-$Je j%?_k{EN%6C.*?Ky',^Ӊtww>&se bMWR6 łd䭽uTjeޢ1/KEӧinn& rه\?ra0F"7h2h2~LfQыEE{|«cN5_g/I,O̾ߣ7~߿s_!gnnc=ɺ}I.^(&)ܾ:<pj3Vn>>GFF>43Ҿ/388Hrx+ $@CCNSU)X:E@,n!?|ubW$~??1+x2&YU^YpZ~v2^Iײ悍-,re,β1 opj(-2_S%|P:ߌ)NÁb`00 lټH$2Xh4-BՅl6c2Y]6[0" vy,X*2^ƍ~@x0M(Uj4-zقjC&WNAZZZR&@:zJEϿ7Jߟoh<<'t8y64Fpj3V^T+^l-@`(thg~dy_)r9ňb:uJU)X9C dT}TU|WP~KvTQZFYyj v5rzx{V4^kZ3tt^>lt#d9^C=^#jbiWhguՌ.FJEgg'r6:::H[@ @__ G`8dGu&X[hbmwHRdM\Rrz~<^/ VR^KC9`Ru2W~LFo4s<rRd8Usoo*?L]_cˆ9ݎ㡦M;9TUTIa;&j=H{8kM5uԴp<©)xR(=zӧTwEx<^/ޑWHG5#\Xbd銺:1Wc\Y:,R MWEi]$I#Ysu$z:(--^Mau]rzE>٦e|>?@@04DEWHdX,N8p ."v\"իK))|J?ktz,6BFwWGɐ-3,'_`jC7v(omMAew7*̾r7໿vӴTW Rdy5AQQ^,ph]l_~ppWh1|,a ";;[xB+ݎF&q:«)LrT*؞YmK+dw$AZM7-^ }feI N;Ve{U7pm![x-1y4]v+VZ(xy a8& aۇ4jl%x﬚EǕ]:6U 1^M&d8e;kVxkwΆ o`C7G1.(cdHzHFR rZ=1y29ǞnoeqZ.i |#gꐴp:3)Qn%Ŝ|N_|B! W% DXYwvC3wk38)X*̓ann.o6NEqA,YO~~:~ BVG,_7|t,Ydbҥ狓a J]3`k }?gH{ *(HJNM>uÇBѩ}Cz/?R q8].^ M2r\4fIy;xgK!+bO5khY~ CV9.eu*my[!rd5E-3کiQ].׸p8%)<Hx<8׋b#Gp&;f OsF~lٸǗʂ"j$yyy,Y*\.zB6nȾ}0̣elqFvAii)Eaa! .$???i^MvrPUMj/1  2pyy~k2tD]64G+ߥ!JQ&«)dddp\]\nnϏ;j$MNN*Ÿ^]&d9eiБWcUlN`cz%ײd3{9\'ŹVgZsPml;j6e\bkFYTө2b\NMBt:VKqq1^}GVFKbDBKK * ~677sKja⤌+KE{W2۶t{ F\n֭߳lsNx:$۩\*)tNA[`8~ *-*PXW.'!j56-NM.g8p x} =/@Wõj2#´(v}Z'KE&+gL&4c2Rw*6U\5͙@LLx5Le|>ӢliT,^ pJ0JlSSQS ,X*"$`"^ & >ӢZiT,^ pJ0JlSS/L&,˴dk}g +AN &`"^ pj3w\g8JJuET*J%hZJlS@x%Wd#̝;y&5 @TJh4b6EF#* TJ0$_=c^ pJ0JlSS~~k=1H$B!z=2 D2-"a8CCCpWd#L+D $gΜ9[DD"\JlS@x%Wd#ڤ|!%Fۛ#\JlS@x%Wd#LB(@ Ϗ(@ @0CP @ f( @  eΜ90@ @ Μ9snܹz@ @ df͚% @ @ DΝ+ @ @ DE!b d2dZD&FXWd#@0HBFioorI$"xE{{;XZJlS@ ")]zy/&*N^е>3 @ LUR"Jb Mb1d2xZJlS@ *)]H$ NH$~$"$@ JJ~$ :k}g +AN @ BH$^=c^ pJ TNB?#.& LS`\NUU.]z4 hT8%cJ=Vq5ի]WSU^6nҥKٵkxJ^{5|I}YT*cٲelǏOژS@ &3VTVrtw" <>?nۃtas8Xl6+FɌhBg0Qku4:j Ҷvj)^0O!I(SdµԓO{nb1f !,+;wrcX`O?4YOB( fZ¾ϝ&\.]w_^/| Woo__꯸Xjz=Vq5ի|Hme`,qVn[rb)[( %|-p")?Xm$pQ -i2~|,mGv~V?YF j2;7 ?>?u * BB*!tBh[)bW]MQlt%BM{)!";I \s#͗{>g}9a"*} 0 ,[ Hz鍝'OiDDDn=E!,..69!!GHKK`00l0K߾}0` ׏}һwo|}}gϞx{{鉇ңGw =zښ``YDEEQPP`^uucvj ; w60ln C0t~4C_%s/0d?>þȯb6? b a_2F.g}׉o,W]~Xڷo=)..ӧJUTQéb;vN IDATN^~Eť,_6ܽ;999uޟfJDDs[¢"z4B8n8vi ||| OOOqssXqrrk׮4oʠΚEdYMޫ.nUM!{<#$2ta<3|A#2|AC1|A0rAZq:M<~ u"Iη-Jb$|ߢzl,W]~_yӧSXTLNN.ETTTPUqސt<)oBQ*.rXRRJnneЯ_?N,XPiDDDn=f['=#@޽2e 7nd,\\]]_n݌{{{pppcǎ4jX?!22/"TiD_E$KȠKERR b8r$&C>ĔS8}t>sL }%88={0eʊ.]\Qܹ3tܙs_ ɓ1yȺj;W5L&fҪ&bl^9+2+s.7Vd,&+^]+kymu6o5967|\_ X5W7yWhҤ_M'5-\tU_LN&&>,HOOŋs8N>Ég]wI&M۵z^Lz̶FDDyz{{3n8t邥%666tܙS2x`,,,ԩ;vCXXX`aaSO=_-W?Bx k!]o˻"u|77ƺ|Z[xscoo y1)󘼩ygsS _Ļ[kKUo;w2qD7n`;dD= $$&In^%\*/R'77|'-=DEX BB2k֧ƍ_~SBsp[š Su̬, ZYYaee%;y,,,h߾=<4i҄;Ә;; aرpMޫ.nUM!_;_TBZ;[ywSV "3» x/o-⃭E b> ,|x;pE4Bͫ.UDDDFO $"2xS"/]XƥK(++2JJJ(,,"//,9ék>.[_W]}jDDDn=E!39d].W;vHNԩ|miiI֭y衇{IXكs뮻%;z4,sUSǭ.R>,czPӷqP(ebCi3ݙ+s2 (ezEfČe]]eSƬݗY+dwT^ By*//DӦ 8$'Nq6<1q$%IvN.yRXXDAa!yydg琖ABbc8}&a{-[Yv7殻"**LznBkrbc;C"Xok^-+++ZhA&Mhܸ1?~衇xν=]ww̙3 9Jff{E֍U۹)o*}d_ħ/ >Sg0+{e4dl_V0g__U|/T2|q= as>,KOB~)W]rss 駟anɡ!8yȳp!DSRIKK'==L3HMM')9ظxΝo!wvFo';>5S"""- dgg{Z[[9[[[xgxiڴ)O>$O>$M6'Gq<4jԈY8B M|uy*wy;\*_[<-C_k$Va0/B3&ǡU"< Bx k!f+WfF`Ͼ`B3: 1@||qq \˹S = z6n͸뮻8sݐUEDDmQsrrLNRR999ߨv[[[찷N:ѢE ZhA˖-iժZe˖lْg}-Z3SOѬY36mc=#ZhXͪٵ{)'m`koՒG$teE_;63{m!LLL$''f͚akk]v[nk~}׼;ih߾=m۶瞣y4k֌Off*¯vƳ*4 *%8B`W̘|3m0 vݬx~<ע ?;rx1sۼ?E4IޣC -> =iۊW}#ٰɟ-[wmNbGn&pNlg֮[O؞TL&A/8?g??? n6>j"+++ǓC˖-qppXXqqq"t '''kkkc9l۶--[dkRSSMޫ.nUM!\{U<2haÝ`gt|aRj?}>CGâe'u|/gw^5ʢ e\xyuħ2X5W7L57]mx8a;ctLtgTg[c!\ݼ\UP1!\!rl9%᧥|a˖}MQ7ȱO+^+,|LAuڣfJDDc0:99tlllptt[nwww<==LJ^zK޽W^///<<<(56vЁOffGP -йC_L_Wxsl&&3lZ]xm_Q3jL^168tĬ6(,‰HڶwbֻȓӦ;FJv.88槮>ҽ ҭ yHy?.^ɏex?-Y⥫8}"qB3InknT!13E!09gFK'+o0ydy{=}>C>#Mә>}:3f`ƌL1ӧ3m4>c>C}{=NSx7yxiD^ INN6yȺj;W5кNsNiŨW3ͧ9yd䒕CfV6YgdAjZ:)i$Bbr2 II'&H\|1qD" v$''W]dXBh t5ХK/Ϲ4߀7 ػ6=g :Q3%""r- aZZ9AjZW/Y))$$%WbGtL,/u>uQ;~ ڵřW]dX+c!4߇ <@f06€e[hm 4x sߠ\ݼ㹪y'))8Ξ=ˉ`N/#Лɣ9r$Pc #,g$-tfCs%M3%"""7[VTTͱcLj$&&"111DFFr1rrr(++km64WR4S"""r aUUeeeMxx8G-Ntt4yyyQYYЧlhiDDDfuKBJ(.."hiDDDftBBo锔PZZ mQEDDDDDtS!1G#FP!1G*""""""fJPDDDDDL)B3B("""""bTEDDDDD̔ Q!1GÇW!1G#GT!1G~~~*""""""HPDDDDDL)B3B("""""bTEDDDDD̔ACz""""""ra2dHCCDDDDDDn0B3B("""""b099YQEQEQE1B3B("""""bTEDDDDD̔ Ӌ)??? ~~~ TEDDDDD̔ R!1S*""""""fJPDDDDDL)B3B("""""bTEDDDDD̔ H8K9u쿰ߒ~%t Ϝ氛 ;Z?K`Jvu&7$γB(""""R!!8uތϫ^3>h<+}q#99DԈČ!Q#݉9tNB(""""RKUUUx| _FŠA:1j(|W>Dz+>>h5?B(""""RαeoVe sl)--9 l՜ȞDx*Ȟj~1BPDDDD6.][mgcKII5Q`\j~RM!컩Yv-f rB(""""@j a 2Su H)|2“=)!*""""" Z,cJ!sᨃ ^:HEQ)!lsa# [˥<.]{t wOϏ H1垦e'._BE=Xep]mQ^Y ߽}LZ? dd2gN0n8RY:g&HyIo.x8돏8!8;~,ѓߢ8)_/>Ѷ8q'h Iڵv챳0)D n "tHZz܃ǹ^8&ktX/oύ H)| Hm =jKa^ic7}VaRZ^>K{^q\Vqsw& &\ E@=739bg}uz8y#qTR}ӫ;bef NBYz:13b =k:s_Ӌ#Ts19~n.d 'N̒%KNl$I$Iҩqcǎ=?O)">li$I$Ir۷o ӟ4cǎ=7|ʲeoΜ9sʏq?O@$I$I0e$I$IF C$I$I4Cݻ%I$I$ S8I$I$i2I$I$I#P9ڵK$I$I0e$I$IFr$I$I4}-ZM$I$I0Uq10000005---$I$(f˖-܆8T׳qܹ39t$I$I:JiooƍSڲ}tuuVI$IA3}6lؐ}V^G$I$zZ}u߻ q@%Z-J>|СCjiii8Ғzn!O^7մTN+===8CTg*3@u82CTg*3@u82CT7lC޽{smeʔ)93}wˡCN!!n֭9REFUsQO̟;SLIQٱcǩ> Ν?p"!9EQdΜ9LMCCC{wB1eE,XիWcu҇ݻw(=3Ԓ%K2cƌOJ߿?w}w.444)ofy[(\veyG2qL2%z/_|;?~|U}l۶-3iҤ<9x`dǎ)"SL\tEK/-WE}ݡ=p3@u'}{SEy̜93EQ䢋.ʼy2zE 6$WE2iҤE1cdԨQill,K/U}WWW&Lqe>}z7ߜ_Cرcs9dڴi$k׮׿Ee˖/ٓ{73g~,[,vm1cFά]6;;;ؘ_re.\$ؘ,Y9C܋/(2{c7LQSד$O?t5\_CgO<$YhQŋ+'HQyٙ={OQٽ{!=IxEY4/z{{3s=ٴiS֭[+2O=TjΜ9Yxqlْ+V`C\ooo,X^{|,L'}{뭷ʁX`/.?Eo}[I5M>ᗂnڴ)I.EQdѢEow xY'[~}9M=ܓ3gfܹYlـgm۶-7tSf̘?oFn`_n]Ν3g桇ʚ5k q+VȌ3Oǣf*;!Dttt/[?~=/CP٩vޝiӦeժU{wٳggÆ #z8^8S1%ܜK.$ /?CP٩tf*3@u82CTg*۰aCjک>VjZZ[[!(mٲ%;v8ʎ;2}qƴ{* 'ٸqc<8 qz=mmmimmMKK$I$I:Jikk`D````````C$I$I3```````zښI$ItZ[[e˖twwwnCPٸqcvܙ:tH$I$gƍmJmmmپ}{R$I$I uuueʾfJ6lȾ}RR#I$IR^OV˾}:݆8ҒZCT>sСj z{CPjiiI^7^jZZZsCP!3:CP!3:CP!! /LQ=ztO=s%HQ79tTSE&Lp!!nҤIillԩSP3SEoСCټysرcu^dI碋.o=fovf̘:+~ncƱڿ\xihhHSSS|'?!]tQƏ;_NK;m޼9zk/7|g/]47o64"C\6oޜѣGgܸqLWWW&Lqe>}z(9+?7k֬|Ϙ1cNx۱cGƍcfܹillLQEQ˹瞛(>3 3fHQ,X EQGpqƥ!f*_|xw};::rgg̘1tӏU___fΜ(rEe޼y=ztȆ sر9s2mڴ>sCq><$߿36z|k_KQx>wcccq93s };::2~E38#sOdА+|_~]6EQ!7pC3jԨ7.ެNL<9\sM;G=!dqWK/T~闦^~K_OSSSjժ\}^裏xix;7dѢE8Nz{{˿O?9I-gNCCC&N ֭[]vY.]:`dKɓ'!/5v!V?a:L>=ׯ7E{oHC`g?<566/3z=:6mڇz(&Lw<3?9{^|L>=g}v4s]w .83}9!OYõ^{77o3{sWΖ-[r\uUinnNAۛK/4-+䪫0]YlYN ϰ q'Cggg}|{ q#NNwСvm>!.I>s=9sfΝe˖ xܶmrM7eƌo[o-vueܹ9sfz衬YfbŊ̘1#O>Irqmmm=ztƌ_93׿u1 qWGGGRÞg?;E'#qIo) ?~|⊬]vD0 q>OCݻ3mڴZ*y2{lذT \qiKק9\rI͛^x!X0!6Tf q@e8TaÆjS} 8jz;CPڲeKvq;vd(!(uwwgƍiooUDOOO۳q|СCjiii8Ғzn!O^7մTN+===8CTg*3@u82CTg*3@u82CTg*3@u8⺻ؘ{өTE,XիWc%b*3@u81ٳ'{ofΜYlYz{{۷nˌ3rwfڵ?~yXLccc8P~ʕYpanHcccdɒ!ՙ?~.\wu_2k|yNts=dӦMYn]͛7̙3wk/CPىq6le]ZV^2k֬$|ٜ9sJjǼ69r1/op=裏O?+W,Z5kV~_5^|Nt[|g]v?_~˗֭\qIDATc=*< !d4/\^ԔZ*W_}>^z-:!w͊+ʏ{zzrWW_= !dYõ^{77o3{M sWΖ-[rfIooo.,Z(۷o+jwgٲe;mڴ+ikkʕ+3}lٲ;'_.8>=ܓ3gfܹYlـgm۶-7tSf̘?oFn`_n]Ν3g桇ʚ5k q+VȌ3Oz}zZ[[|(|}gu4iR3u444(L0!ZmH_O8nXw}3nܸEzkH_O8nĆ$:ujիiӦEYfן{E/ɑm۶e޼y?~|&Mx ҹՍߟ͛7g7n\:;;kʄ 2nܸ,X ӧOOQ曫 0@u#f EQs믿$5=)">`:;;gϞ)"wՍț5L<|ԥK&9!;8WE֯__g!f͚E~I5555yg9r-)"=X֯_?{N!!|yj7]("sNzˡhCܯ~EGyk֬K/ N!!(^sMQ92uԌ=C\GGG2v~innNQ6mZ!!7ǏOQ$y73y444d3ގ6%̞=; 8qb.\*?3p qPݰ q!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3:CP!3o9}}}8CTg*3@u^ Tf|3 qP!yi*P!/G?{FPن RN1ӓAogJ[lɎ;N1ലcǎ e_3lܸ1^ ޱ^p]w]vڕ qz=mmmimmMKK$I$I_~nmm;};߹ q0 q0 q0 q0 q0[jz{{u|eΝ</ܹ3_jٶm[:;;=3Ntvvf۶m󕎎d׮]ٺuk$I$I$`[nͮ]rl۶-U]azˇIENDB`manager-rule.defe1afc3662d4c558531ada63b4ce37.png000066400000000000000000000467021325274564300430460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRs pHYs+ IDATxyp}>7/7C$%ٲ˒lP$v;EI&nvx7N_f6f4i43#jlɊl+UbuDR)/_  yf8vgwbA300`qqR)0 0 0 0n^/<x~%%%x<-˚؏6cok7NGr :#{tСCpKcY?>yD80 0 0 siepp~k+_rTWbj ǺyfynB'|?СSǮn,:tСSG9eѡ3Y'J+vUy}N4Mt:|{;DžtNur--a k.7Mְ5aMq0L!/֬YYfR(u]|ڶַ0gu]^qaaa& ar!1sEXd2۷_ƒ%K0m4BuWntEv4:|;sv;L;6nCN9̗k1F:t vtnѡ߲Կ(c@ $jkkWO:EL6 7o͛ӧOc͚58~8BV\e˖! :jXgj/kg 9n^lCN9wZ:tСm_&6t88MD<}{D"cǎ@]]z{{;3d?O}Ǐdž ֆ3g*?-׭bsdSntܽv4y7СS,ͧt:tСS^l[&:u E@5N$ܹsX 'N@OOOF;O?:::O} UUUBؼy3N>3gf,Ӵ~nofӡSN6:tɟc1oС3G;N6mF<?+FvZP(n'f[S]1m;fߢC؝3tСC:t em6Hxh0>>qR) eG"{ク+166~uuu:[kw18mc[t3c:tqйNWU+Q]]={555xЀ]v)++=܃gy ,@__ưhѢE)g[_l=9Ox<:t.MT:t)kIN6'MXZ 1cFFFP]]e˖Ԡ ´ipwz0 0 0 0'HsQ4Tt5v_ʇ#~rdߴ :wrY:uYְ5[c :t8###aaa'fȲȘ˷sСC:t.cY+Ǯҡ9zUZZؘaaatF߽BmFj.'/ٮ=rSrTF'{g2:0 0w9j3[ _LtKNg;=*G5-_9cߗy1kIN:NmiS!:tЙZ'INѣVIIkaaaOR+0 0 0 0yL*0 0 0 1H$gDު;5~;6u1Ms 0S;:t.GW4:t:Й gdd.Ɉ^J횬MlOBt&$йXl\:tСSx'~ʥC'WQMvEsݟL1yl?wx֝,bstN :tСs8ل 8A)eGkL<]߭cTeʇc?mnytrwtg,bwʶ嶪c:tב-Cg2_.g:,2nêѭjLtN).5twT*z_caa9vԇu9NumrFĪiYp:)ϣJ000~$I0 0 0R|>*++QUUW*n:\Iw8 :,q[Baa撎eY()).v%WtWjT\$:+U-CޱwPϻ_C ɡCa⎛ss 8aL6 sAiinEt:nb1:3NN1:4ӛͅ:tkT=lyqeY??`Ϟ=HRիW 6[_Ν;裏rLdUݻׇ[nE/mukOT:y:tСS4GN6T!LRя~cǎ_*ϟ{k׮ŝwމV:u hnnFii)FGGqqa #  ̙3BhiiAoo/QRRQ#appuuuA,CWW$jkkӎ9j 󡹹زe |M\gφSnᦃY]Gl{EN4Sp XB^뜜_|XdɤZ:d~rСCNt|[&:n!saǎl2|#Y;>z{{L&}p|ٳӃ&bBx'ꫯ?9O'Obp뭷<֮]W_}ea֬YxǐL&|\s Μ91|_̙3w^ 7D{{{k򶩞,UE.RM3`dUx<-[`ppmmmX`ضm D܌Ǐ%%%X`jjjpAB!ܹs8|0O{̙3hiiASSS:{Tj\ڎcݮ :t)cYBיCɑkxU3=1>>iӦ!rJ?)Ro8^|Eڵ 066o=ߏ pQ@ww7-[y桭 ַՅ3g"G8Fyy9/_ٳg h0}txؽ{7ك+V6lC000_زe O׋D"\{XhfΜ!~B!|>]c%>ѺAąpt}Z>qjG0%%%(++C=kBMMҖ+ݓ!Ǯ^GYFgS1  jkkFߏ*x^XG("^/JJJ 0666ORۥ[똎:tСS|{i::qʨee~52ࡇ(/ӟ4~+7ߌ+V`۶mx'-¢E E!^x1O{w~'O4HwO^ڏX3U8M5Z:;5T xϠ%KT /Fuu5$Νzkxbx<tMXb~?VZe˖DZdTWW#H`?>PYYYf?Si*nFړuLFB(B(h/:G{`׮]طoZZZPVVM6ȑ#8tV\3gb߾}غu+;~\q7ooߎ{bϞ=hnnƢEH$yfcccXl~L>=Qq{tcyMEw6b;՛h։:tQ^:&gll xH$c0<]]] Uu@ԈӝjT oZ'Av/6eqf̘2=zh555hiiA,ѣGJ}v̙3 .ٳgݍ@ #ĉFE<G{{;qIdm3#n;: y90:t)c'ѡs144 {vZN.wTT+*>m=ݴnR('! ֢NY3{ S֯_ 3f̀swpkkk'ԇB!\q"tW靎'DuܜLatСC;S#FU3᯽x˵:ttLjllj (wFVH)py&[7NnNWWׄMҡS,0 0.WFtwwt(R ;(4'GƞvԬ[UL#|;ΜnӾg׈eK C@}u;:tѵ77e/΍vٶukvY{Dz, AU3UˢC'ߎ|W ,yv:t?4Uqb1GeZ/<-μʐW΍ax⭛6v>_L 8s JKKWaabeYFCsso{<PF.|:"WliO[LPGjMf:9e!ΝaaMMMѮbL`'+srѩЎq6~_J걼ltұL\zL:t ЙjG~ʨP=V-^n0ُut ۣzq1K5Cbqvr| :tɿ#N3WС3Uv)皩0&:0 [H:4t:tqvBDt86mYVƴ\7q1f}ts{*q":t)cר߲ˠ͘k79u\6[yZNj1=:G:2 X|,IN1;vnСCNaN|sTˤC'W+Ʋ2/cTrutGptZ<:;SkMN1:vtMBUC:t6aTtww?66n0 0 0 0OBhll4^1M5K깠qc< F:tqӡ3NOOį MrqڹN頡Cbqa3EYÚ\k&QѨSʦ3ƑG~2U=:ꈯڨ:ܺN8:渉:tcM?DN?40jh+5bGptt򠓻cG"mǝbsDu^C:娦}l t89~ wVis#|x<m;#^eӑ뜶Ml:9e!#J>I3 0 0׋`0כL?+b<ںȃ%y4T-S5 Ld|:wRF] 0 0 s1Dz,N9=J+nZ7U媶EwN6ϟ$읡!DQTWW~0 0 0L144J_&UU_"st?quȧcjAII |>_F[aazQRRD"?luWιwL_3-7G5XpiNeYFwE\lfCɆ:tѽ.8/СcO0HL:ܲ }_B ML8y'j{N C9%ߧCX1nEytСCp~ ֨r :*ǞfAd߷Q񱈘F<]V87-c;vy2`nM:t 鈝 >.֘B:t ѠCǍ';~]|_IqZ)7q`̷cz\8G|GN18N:tבڪvt꤯?rb#ۉ=W}5OhTk!UTσۓk;qOnWйP}~TvtСC.#N}+ /;~\VV˗cܹ2;%*C2vm"aܹ馛t}ST R)YW]u g'ef;B:4qӹXՆ:tcr :nz5믿^xo&~gO>h4\%?L&/b)n/JkCk/ʑzLǽ#>vjm6V[ 9s&qiڵ W^y%0o<(mۆX,V\y;#磼hllDOO0o|śeYطojkk~DQGG,yU5ضmxLй8ձv::t)jƤCǍc:vFDn:<Bbxq-?16l؀Zw7Xn,Yn|;?q\2<8w`򗿌ooqUW_WL&O_G(BII q4776nv`ƍ׾n?Z[[122χ~+bC=uattcccXh>c֭w玷~;O/֮]rGŭފêU0w\|;#P(q<裸[~zlܸ3fcpW_*O>a ›j.wG?~555={vzؿ?n6߿H$uVܹ [DFi&c8{,:FFFp 7`[o9s_G̙3| ۥ.7g:jk:L6:t)# :*)_oo6>cxg} DׯٳOxgk.k?o~>}sY\uUxCaǎb1|?s݋^{ ˗/?>+?^{ /^,ZO~7Q[[{.eeek8q?;7nDGGvލQLҘJp ̛7|>_z, ??Xd  /`||?O1::*81<(//ǵ^W^yvB4EGGR:X,KꫯƂ &m tes:q 'N-:t:;w19N?6`@ss3~|#Acc#݋6mBcc#,XnR)\qD"?vU+XQQ_WHRHR6uuu| vnڴ ,3L&xb[nI4chooOu櫮 .D__(z-tvv[nA(1o<|ƍqw#gTVVNخSNfB]]ΝsPSS9EsK-:fBUUzB!tuu1O=qA,]-wމN:u }}};quo8qDw}7Z,ZPAYYY8]uvut.=GСCNaF樂CdC( w1227" 7x#|A;v D`T Gō7ވw8}4hƲ~a>T*~;=egEEJJJp]wo޽{ c={x7oF{{;`xxǏGyy9z{{'=ЬFCC>O!hhh={> ~g_h4|я~sIɓ'1k,9rړXQ]>T L-B2L}Vߟޏҿ P?d2i|.urM@Υ初 ѕ#>VC:ut6tdb |>bhhpvZl߾Nƒ>x7t~g?8jkk0{lTTTࡇBuu53QWWx<~p### Y`/_z ׯG?~a{ /bj÷-$IXGy$>z /D"J_z{{3ϠgϞŌ3h䷿-ۋ믿ikҥKXf FGG#ZbwtT##> ʉbٳ|>q!̚5 @ii)0>>NbϞ= UUUػw/fΜ@ a?hUHQ\ΥB9sC{{;Ν˿D4UW]ݻw xQWW7ac y,Zhooǂ ӃG}^{-0jkkQYYTUU᳟,Okd'QSShkk׾5۷`W_}5~3gDOOJJJpW?0,Ya.ǃ/}Kرc֖K `ڴix0w\틮{}:kn1b _|n:СCN{sz/Cje555ew`݊, x'K,utR|p u[/>`ƌ9;S>n\U ܜ'O"#ds2D,K-txx`@E @8F*¾}QRR]v!aժUH$D2DEEExF4E(MAպlΥ5:t{LfppCCChiiɨ>? lllTnvn9/K c֬Y[QYYulv{;r;wbxQ[[yQ9n,Ztw}NMUD6l@__*++ى??L_vLйi>:t){r~):&ghhh€PtB : nC:C'fYbr܄Nvx0R>L(=1֢5'gևɵB:ta krbBRFtŎ^qS4=D>Й'Vn9UMiiiƿ[řsy:zְ5aMwCg2ש`ظYNSȖxv\7ӋŨr n W9cʑ׳y\:;vKBվk'>Vtf8:tΑV{Rqjg:)v'c@11-:ty:ɦCǍ=+eZ#ߺikJ.muyNˇm O@O婦ѡSPZ:t֑qtLBډڈF5w[+Smou,[trwTӼlj)#Mmc:tС_ǍM{:t:+jG#?bNN/>kto%N_<:9|{x+/G>&)fG>.zA<.T:tɿ#OW9С3߲,+aaaK3eH$000PuaaaaD"0D]]]zeE<7DI'97vL)'d-:tС3yGntd$T1LZ5Lczy)vaa&<c{i5rXX#nqEG]pLϗH'7 wXCN1;tСCxlɦZ:tub0؏Uibudtː}'GlynUۥzؑСSw9:t#OtL](vJT;n#GL&:Gv10eYHR90 0 \x<|(//0)~q]tn#UJ][yEMNQNSB8%o깤cל:u u 0 0 s$bxxMMM1B;NWLWTT(UqsTLupZ4@G*BUU|>aaa.$I555ʯ/SNU'^Sn'F總$ M@,C(B00 0 0˲ ŴcI}wP0~_i `rNPPjpcY~q.Lɧ#&BitСC'-Fgѡr" OŶr5}A[έZGk1=trs+N|wҡSh'sز,:tСS$ގS_\;^^ CFn9 y< j |8 cQN0:tСS8DeC1kc"Qɶ#oZ\cZ8]~ܴ-47D:6bT|>:t {:tСSxߍ{C'۾W,V5eꦉ8rͥƴlt:yrjozns*Sҡ3|VE!x_QC:sμr9r6^ s \VδnǦQt18Nطt֑kBדB:*!3b[:tСS84]QCtռ TF b?M5NXr5rGJ~oǞ'?rPn:wtT*ף˗/q$:hWODTKQ͓ݹ:tq:t!A!%K}r}FFFߘ7onرx1c \+X,~K/z{F8qxeW<ҡC9tN~!OCǭn{rBq-שѐe&G.? jSG|M';Gys1,\W\qn&Xt)f̘n~OF2Ċ+ގKHRTO:|>D"߿x?~?":X,{xgϞڊoS6և8EwC:wЙ k)NwpUE^7MtTGnmᘶ]5=t;1d~xH$^e! Jk< eYUO0ć>!,\۷o~{%KsN?DooozIR遨]F.s;NСCN~[<:t:*wLjӏuOjn^VINn`nU dqza kXi܁ӡcr144 BUriM-'GvYrTUݱ浕k<^/DZn:A$ w}Y;v쯗ʩŋW+nKmݲtСCN~^;t*'}дc[BtP&'OD8F8n^D"H'k8{CCCQvѹl;/tСCpqSK}_B(B4-\t0XV%Nt0qL+G3Q<ټ@vDk@sL΢C:ص} :t34jnx;u-'3yǮ7 B)FGnȏtСCp:S扙sImvNsuj}8trs6cݲ)G| :tƑ;@מpV1٦lm癞OOǽ:TѣCq0 :t)X|:ttuʯ;MptR\wu+&ߏW^yz+ZZZ2VN^ѵqrtu8n:1:";tСCx2gQ9u o6aY0{l|{C P.ٳwv477; =&B8S]trFQCyT{СCNƴ{pfYt";2*b:Y/2>Oa8x ۇ{²,;w?m*ɓ';w ,kSM5}S5b].>*r( \IDAT[q Cӛ* :t)k1ytdU$I$ |>??#:::o|_QSSns1?3L&q7GA(R.Oُu&][yprtۜOG:>ӡSƮW/uV:tСSXGrtqrDO(2*fg? ǃw}6mr!r^y={'pA?3f(7Ҵn5#3yLL˕$:t뜎y7:tС?4)tLWFMM VXT*7xvfB9lذ~/F4 er[ 6]+z*ǾozdmT'glt#޺9?mtСCF5M~Cɱr2 '?rX_^^H&X!D"hooO<χN455MX|_SުzAXHǎNZ|ZxѡSLV&t>C:qTmc˲27Icz@n>eee8*lٲQkgժU{|0***pu)}9njܤ7|sa-4:t8MC:m\ގ]WFs=A׋e˖ccc{QSSD"6>|(--?6466={7pCJBO?)mX_ &qtΩ:ܦ7:ؙqA:tj~]{:tthɎjjj2N Mf79 ḩft9'OD8F$Q:@СSlMC:t?TQ3Eɦ2"*?6-\n/i,r,Q\gez۫s:;N'a(C{ߗϥrlmСCNayt(1ʍDM{ۺ mL7>M.&:'z&3:tɿd~-ӡsTu+bǮGb[ԍfu8ύeMgͅkM5vT's:t.>ӡCwǖW{ЙJ'c@({akQՈ8q NONN⺃B;RU5tСCɏic:t:*΄@ygU8E<rqr]|:Բf5Nתi1AN8bF!2:tСSxGaCT5 qAqg757VVeʑtuv,w哺CXyZ:tޱeG\Cd_u;#ָ/$6`ݲTf ;< _/7CX @־/?C:qw@R6ytq6rëۺ\7+kriY*3\NA;4B89+֘νtСC'st196o'Q&׉+v4+kDKU#:G^oǞzeGQEwAKN>|]Xְ5oС#F!lNV,Hj혞/Fg):jCN!]85a kXS5nӡM͛7#忝-<:ʼd?\IЎ4әMj_ INyphW{MmСCNa][]'gŊBF'= TuF;X/WuzTm;9\,5N.wLޑ AN1;bͅ:t{CGv^{5o12BP mcJqUgێB:rihСCNZ_CGnswxPxM3"v \$>tmtʳ#2L)#Fg;%ӡC;4y^l#KбWko9 jll|w?SlLW''N۩$ќ G^Ζۨ|9r{<939zzzP[[ ]tXCXaT\N"@0D<G0D"0 tww{P5"wv:Պ,qxkT^@A^<+#ojNnTTT`hhht'+bn:t1ҡC:bRUMCb ǃ={ ###Z/O1^5╧9ըyy嫦v!䯦T === 0 0 sq%Jرc())ɓ'Ӄ`0Zdx||",ԉq]:Q͓czw~\j5]:t)#כ|]q; 5B uqr܆`T)fG>&r=СCN~\:tqҿCh7[U1՘V\$.#o<8vn_TצN8:Dž|LUˣC:q[);:L/ɱ:OR}ՏQUE4QhN:it#{`׈U :t)cߊmb:tunOOU]]`0HjnW7E̩hZGLEaaa9xΝX< CIENDB`manager-rule.png000077700000000000000000000000001325274564300456052manager-rule.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationmanager-rule.png_documentation_1.9_security.html000066400000000000000000000116631325274564300444630ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:manager-rule.png [LemonLDAP::NG] />

documentation:manager-rule.png

manager-rule.png

manager-rule.png

Date:
2016/07/19 12:15
Filename:
manager-rule.png
Format:
PNG
Size:
10KB
Width:
1250
Height:
247


Back to documentation:1.9:security

manager-rule.png_documentation_1.9_writingrulesand_headers.html000066400000000000000000000117761325274564300475350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:manager-rule.png [LemonLDAP::NG] />

documentation:manager-rule.png

manager-rule.png

manager-rule.png

Date:
2016/07/19 12:15
Filename:
manager-rule.png
Format:
PNG
Size:
10KB
Width:
1250
Height:
247


Back to documentation:1.9:writingrulesand_headers

manager-saml-attributes.png000066400000000000000000000340421325274564300404160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRʗ IDATx[[EQrXZZu2LRMR;u25(EMITwY\raafgpsxN555ɑf#"""""""""M999***RaaJuut:UWWGDDDDDDDDDr:S~~^ ҈^TQQ^ɱd½bt:l6iDDDDDDDDDfbFDDDDDDDDd20^4"""""""""1L#""""""""2oVSSCDDDDDDDDD/a7L&""""""""0dAmٲEatXBBwZ))):vXkذaA=n0 ^Zz PZZZ|]^|ône 8Pl~۟#\RK,q߯aÆ\:;͛7O_}t}0-\.DDDDDDDD/sAuwE0laZeeeڼy Ο?/0tYےկ_?Ç2 C4l0M8Q_|c***kҤI4hM'N}q駟~5d 6不r84uTEGGk:rq vZ=ZGƍwB_^FRTTƏ;veĉmƍ]v7L;w} :To߮r߹x1{.f.iiizw5d :T?~%"""""""p-ô!C(''|裏kD5jTYYM0AK.ÇqFO?w7_~ڶm=˗0 n3n8;7n6nܨ4-^XUQQZa?ѣG0 :tHr8;w9saZ|| >LGц do csyas{Zzyf͛7Υabb5bM>]k5JϷ(Tu?@pimsss5|pرC7oĉURRʿi?uVEGG+??_?V~{n,X)S6n87Nžy_XYY" oo{})**J999|֕͛7e\{'fs!"""""""z Gڼy v?naEEE_~r\o&0te2 C~9x飒97N|mVX%Kp2 Cwɓ'էOꫯҠATQQw[jذa6:|ﯭ[{.w{?n8}Wr8wo6wܑa:f͚5zwulִiӔn{A.9j8qߠ;Ltd=Dӧ̀+55Ua/MQrr PTT+**w[_osw~},X#GjZpF0oovUVGô>a˗5w\G}ܹsj75Ql_jĈ߿}oӒn.0tEf֭߿=zH}ݻ}///ׂ o6vX}W~Yb/^ *::Z~9tVZr]x7nkر3g0 =|6ͦO>i\ο޽+0xbnqF sΛ7Oڹs<ٳgk߿}jJJJҞ={kȐ!:u~v6;L3f/_$]Va ꫯ:~f̘ɓ' ܽ{WQQQ7o~iȐ! Q8aڋ)STQQoVݻw}}s|ô'Ojɒ%2d&L;w1ڱc&N*6670ԩSA)//Oڽ{|MEGGkĉڽ{3 ѣGkȑZ~N|X&L5sL:}F;6;LKOOעE4dM4I{;,͝;Wɓ;w Ӻu9q~mEGGkС?_n75Q â0 ]vs!""""""""j4"""""""""1L#""""""""2oVVVy"k,?""""""""օ0(\cFDDDDDDDDd20^4"""""""""1L#""""""""2oVRRBDDDDDDDDD/a|4"""""""""Wl6\. 0 1LLb0 0a`4$iI &1LLb0 0)aڀd}otݻ:Ord ?7xCa(--͢^}0LôǏ03BQQQN ]ًBô*M0Aah!>Mzf;᯽gwE.K.+g0 LaڀuVbcc}a޺[ ^iϟ/0e.77W/СC5a%$$ Saۯ_^a_~i9dx Ϛ8q-[wWj銎õ|rHrrrdfΜ1bx ]tI'N믿;fsss4iFСCUSS#0~oW^vMo ^v]K,4}viqqq1c$^}U\\\aڋV;<3f8sі-[M111Ӎ7|lhر=0+Y]YKôŋ~ hjj5`-_\2 C_~EahѢEѣGr:m>2dN{`8v6 PpBM2Ea>Նa(&&(GSLQ߾}eN>}"Mal߾]0'ô#Gjذa5zz4666jĉ2 C&Mқo[ ۵m۶MVVVmi}kvy_ѣG0  qG]]a=+k@uiz~mUUU*//רQdJKKUWW[.KO>aZti Œ/Bah͒~M/?1ǏPss1;wa/:@i=Ln XK@xǒZދ隚UVV :[f[Scc`۵:uL;L;Xk#}؍7dz-nIґ#Gd,Yw֏G=ᨳ#;;;=+k@uiM60 ͞=)ݺuKb /ᅲa:yd0i$Ο?_srr{n-]T#GACullާ>~X$0sNS$i.Kׯ_aڰaCy|Mڵk/|~z4z/<軍ٮ/iZ dqڵ9riҋV;s$i޽2 C|w5>}ZhԨQ0`-[4ç~*0o>I۷CDig_0aZg~g󎚚ܬEu:LcGo0mŊ3f飽{u /((ؼx[pa'8⮮@]׭a$}W[SS#á_7nҥKeN#_Bna߾}2 CÇҥK}/>@@ho&IWaZg~}Vaɓ'C^4L{=?Lo}}6ôξV;<4}tǔYgwɒ%_i ]YY]YôFb_bZZZC;ш#~z9c]rEah~3￯*66VnҘ1cdݳvW@ô`zo^nx,,4i ХKzsžĒaڜ9s4p@}'.^rxL,{'i&1LLb0 0a`4$iI &iDDDDDDDDDy ӈLf&""""""""0d ӈL0d ӈL0d ӈL0NJOOWZZt=yDA[_Kٱ"?1Qk0N***R}}(D׫H %XD]paaaPGDWWWBb,p2=LVaaN\.8өBeggw{~z6/Qd5lD1b" t544f)???(7TxLrب566r)-- /B7^ X˻%$$d-weݣkoԵ7*k=|qn<X@a-kw:L۴iSPR]ZZI#5-+3}i@xY_ s ڏwY9Lە2mj4o)ӧ1aq r~0刽wY9L;Ni/_f Ӏ08X@dcFr^ X˻߹czΝN0 c\ D6iD/GŀȪaZ!١#ubv;4zvi Ӏ08X@dcFr^ X˻]\7$ܻeU+;=.4 q1D./#iz @ba{1`-: y3×y3ꯗyqaq r~0刽wY5L+J (2yaq r~Nô9NUWW.4t:[ѳgrֹTSMS5N:sٳzϏ^؋kyבU4ۭs,5=H;lc2L@b-\iNSٺ|D%%%Ζ FOTРZ]L+<Ր-ZGQhȖLL+++'|Yfi…:{$ISLQnn>c͜9S}XV_~zpUWWJNNu/ɺ|:=^UU>SjРA9s>sSUU]Uk'Z|wimҠwi~UF<}p^}UmٲE Zn d]ŀ;(۵k^y6ڵ+d4ۭ]huKkVB:LkjjŋsN=}TgϞ[oG.u]=~X˖-ӧ~@b t4 Y=Ls:rΝ;'yn;wNׯ_x4i&Mڵk5n8ƪ-X@***/0tƍ$u<{~za(s]x}QY{Àaڍ7٣s./\{ߩS|`?"_{aZCC>}S(%%EsժU(-k KuuxϮӴxҲcϹZ}yN_^coj rG`}ÇvK{1`-:zށPӂUHiG͛}bߜݼyg0 ]zϗ(9={Lazw5h ͚5K:TVVkY/WUm;SYk@lk֦vLK:CղUYYݭ4o<n=zE={1`-:jw7Hc&O}bn8ʋq/= Ӏf0Z}ǣd5669ٳgJfYY PFFok׮Ν;z뭷lٲn}J;м˵Tfl:$~tHOUiޏzHb6PIYۗr5g%%%ٳgz гg"؋kyב tve͟?_8p@Gb0ʋqlIDAT/= Ӏf0RG 96KLLԄ ^9 'NiGzu(.)S.ia|_Wl|aZxm?_˵P}tI%eݺOn7/ͱ#i~ϟl~Owr1`8i@dzvр^ԩSY\վ_/ z _m:%ҬJ5 [}A%ݺOn7ô{1`-:b"mܸQ3gԢEtIL 0q/uV_c zsŀa8X@drvׯ1JOOoYi+//ז-[[oiРA>}>3UWWnaV~aVKWXSHoWr_ }ni9bZu0 @b\_ Y=LsݪSffΟ?ǣ?^prɖnkTvWSviBMS)4~] ^n˖'eySd^ X˻1\ Dpny<)##CgΜɓ'uI9sF*++iRn^N|O߲[A~_ЩW {1`-:bp1D./e񨱱QMMMjjjRcccD ўRAadGOlzĦ'XGG/Oŀr*.?H_~5D`Ab+ô`á0ӧ***Ыd D`aiD5^ tVjjo@fá0ٳgRqq1OqB񨸸XYYYzY:9_ { 3L#`Ab2H۴i;}8Ty'fSFF҈(Deddf!(Ks~"`aì7VWWnJ8hp2=L>477[=z>x~"[0'OTXXhDBeggX@ ҡ(aV*,,T]]CSaa233t:-?"zbFDDDDꔝt_nCPvv64" Y ӈL0d ӈL0d ӈL0d ӈLQb&1LLb0 0a`4$iI nl6222FD!*##CO>U}}};뗨gbEvaȏuLbaVVVTWWz" Quuu***RVVnw:뗨bEvZ%%%x<>y<vb0vTCCjkk~C0fP.Kn[BRaa2l/Q%Bv{ pvn=zH)))AYbzZ566N ZZZبZeddtx_~54 w@VYYiM6)!!!(7TLvyzHKKnҺ},/гX@d `bZ]mڴ)${hev}O7lҍ-t}zqj@x|ٳgs$/Y9iˁôAVfǵtV]]s1./Yйp555iTۣjp{4ǷBKKrt:t:rРO/bZV$L.,_fa2O.E8]$_1LkniQ}}n>ԇIhȖLEw4dK>LC7TA͝\.4m4h֬Y/v|]Р]zUIIIJLLTbbtU䨡Աާ+WhĈںuZZZa 0@<]Da/0m׮]zWڴk׮ ӞUT(%Ni)cU0u̐Ӫ4ev_^Ԥ)SnKk֬YZnΟ?8z5+/Y@0L"ôfkuR|y_i+ yk'Z-׳z5]%%%4i&MC֭[5n8M6M555]q\ze^n[6MɺzjC@… en0 _{ bZ=3ZAZ(iT_Y+u8HF UU64өٳgkz~W͜9qj2Lcm1L"ô:W>}*0;؋k 8q݋oxZZmcD6+i5p]qhޏ|J36 fl:ͧ4rwB1n>;p&LSNiǏСC ~rQrrr >I̳7c/0žܹ݋_G4/5 Ӏf0Q~]ҢTR6ô~ZqߗkѡJmyٳg᳸\. ۷}ô~ӧ=Ls::rHr$i{1`-i]xijjҌ3sNٳ?~uuu_`cj\Wb t4 Y9L+-w z _m:%ҬJ5 [}A6?|/ylիWeo{iG :@07c/0 7o… 5gm߾]׮]kb\sS\ff͚xݻwqjV_~zkiY{Q+֌oOдImi6&imC3-ѬŊY{QmҢ%KhܹNSSLѺu$)d4˥'N2'N@07c/0q1D./٬9j 㡦SoצEݐ ItH횾ϮveeQwIR^^Ǝ)Sȑ#y*//aZCCۙ|w~Ib֛ aZsKg+n\m?lЅ 4 q1D./٬BFv*vo|W5ezB[OWhMr/ִoB ^wQ2zVqkjjKo 3fhǎz쯿aZKK j1(55Umiך$1L؋k0-4Wfkʘv>4 q1D./٬I\~譯5uO)ooڝe/->;/|\x<~222zb/ôY?Aqƫ1aq r~f0QZ ^jʮ74nHֱv]Sd1.Ɓ"4ρZQqΦ[-KQe)}ΦTT\tKbz a= D``%2 d/ô. Kݲ;=aӧOUTTN@״HfggX@b-ka``AbnRSS}63[JJJP,vV__,vtvUTT,544tx_~5x<<@i JIIQfffPc ־S ͦ QȐf ʃ뗨gcEv^O>nʱn-{1QO}q03Ԁxdۃ S흝 Fc7uFFw>o -D=[(% rkm|6WAEc2LK+4W3p6+aLa^jJHu>Wqj]:<怎0 0a`4$iI &1LLb0 0a`4$iI &1LLb0 0a`4$iI &1LLb0 0a`4$ͦWrrrdaI999zXUUUVTTTWrssUUU3VTUU\镊 UTTn+''G6l6䨨HlfL*վ.IENDB`manager-saml-metadata.png000066400000000000000000000334461325274564300400170ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRk IDATxwQAQa\u,(ZqZt-#v\((V4jP!$!ln!]u}r9˶m۲y]6VJCC$I$IZ*YfM˦Mf֭ٶm$I$Iںuk|wk$I$I~ںuk/V:#I$I$[jU![n$I$IO 4I$I$L4I$I$d1m˖-$I$Iɘ&I$I$̘&I$I$:m޼Y$I$IR?$I$I$I$IUǴM6I$I$I'c$I$IT692EQ}w4hPH}}}y+Æ ۫$I$IґQuLkmmݯzƴcy3<(cZp ꪫ?fذa{ܒ$I$I:2:cZMMM:ꨬXb'Mc$I$I~UǴ7W#F/˜~3gNV^c9&3gLQYti6nܘ<1bD  . /BqF(|wYfMnz9crg??R}ܚ5kr-Nio=2lذ^w}}nI$I$1m{3~^=>|xy^c?(rw䥗^ʍ7ޘ(seƍY|yjkk3q|inns' /HQ%&LȐ!C6s͘1cr ':Ͼ<$I$IW?s/~r-YxquLkll 'ynᆜuY}UW:O?1\pA<=ݟ֭~^c@ٗ$I$Iґ]uLkiiٯz>ܜN;-O>dZZZؘ!Cd޼yYhQ'|>;7QGק%_}>{{챹{ҒG}4|{{fذaϳ/-I$I#>䮻ʄ ҒW^y%'xb֯_kL{WRE9{Վ9E/2--Zqʜr)>|x&MO>:h{9wٳg:Ͼ<$I$IʘhѢuQihhu]oz{Ϙwy'~n]6--իWgȐ!W}vWٳgδ_W1y%I$ItdWӚ?ԔSN9:linn… SE7dȐ!yꩧz˴iԔ\Msss|Eorq{JkOϰaJg_[$I$IGveLknnߞO<1|֮]cZsssf̘!Cdƌy饗r}eo[=ϴirggO3hРgO<>;'xbF>(MMM8qb 3gfܹ;vl8x}y˔$I$IiovԩS:555e93dȐs9={v]i3<3|<9묳rqe̘1̛7/rJ}477gՙ>}zN;r)oTǴRٗ$I$Iґ[uLkjj$I$IO4I$I$d4I$I$d1mÆ $I$Iɘ&I$I$̘&I$I$:_^$I$IR?$I$I$I$IUǴuI$I$I'c$I$IT&I$I$i!mmm$I$I:3@I4(ɘ%$cdLiP1 J2@Ii~z'x̟??EQnKT*EÇc_1eƴ;/EQ@`]ˏg`Ӿ'uLN[[[ڪ&ce1mڵꪫr 'dkL۲eK駟$=zt~g})7tSN= 6,7pC֭[W͹sgfС?~|>)"w^?sI';^7|ڜtI9묳2cƌرcϻE_~y/?=uttsIQ9ssgРAi|AN;Es禵uvvv&EQd̘19rdȨQՕ7.EQsImmmy.]Fc=6'xbFQzƫe֭>|xSV{u޾~: 3o޼E'#]]]+Ӓ?hѢEǧ;QkSE.T*$ /(rUW%=z}I~8EQ䮻<)"<@Z[[ԔSO=5EQd|޾G`q*={9rdZ[[#ܹsy{L>X#J%]]]޿yEKcڹ[=.;〸dɒ}>/?m1ϙ3g(<^~~ߧ(<}ߘvꩧ&z;^rKc=%Ky O~ize]tuueɥǴիWy TՕ\9r>?I&%1Z~ߥ(̚5z￟ye˖-|޾'OG1?yjjjzuwR(\PHdcڨQǬ_~moo~ѣǏ=cǎ>UW]UO>$I1mkiiiСCsoϔ)SREF}>o_|O~}W^$rWdС3fLf͚5>|xg}s]6SN'Ç禛nʆ o޼9wuW8 :4Ǐϒ%Ko2tМ|ɹҲݕ1 c1 cdLiP1 J2@I4(ɘ%ǴJקN$I$I:"ϊ+}}*J-[kf$I$Iȶoߞ>˖-KR1!k֭֬[&I$I$mݺ5k֬I{ǴKfƍikkKRI{{$I$ItDVT֖7~Ǵ#}Gttt-uuu}3VT iSNR1`^ cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J>}ziP1 J1O؃ӧoLiPyNvhiP>iK.M[[6QikkK}}};X">]lْ=ei۷oϲeؘJr.~|*Jlْueǎ}sקN$I$I:"OCCRc4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$c4vg(^ 4(#GL}}-[(~{cŊ)"cǎ=]lY: 4(- /(r'9?7: .СCSEr!{T:XcڴiREjkk|rcOX1>޶y <8EQ䣏>: rG5]y)"˖-Kttt-J%(oƴ$ RE/^$oR[[N:)guVf̘;v$I/_(2a„̙3'vZ9<3N{eԨQ2dHSfݺurM7SOͰar 7kUϘv%)??J:EQgvm(뻝sѽ>??ޙ7ם$)"/`ݝ: ښ[f=iСC3a„s1F]G <8{l&MEk6IYm̘15jT]i 裏NQy$zɓic=6[n|AgϞǴĘpEaÆeѢEI~:EQHkkkrꩧ(_:uQ믓$OQ=ztG>(y駓c\+"gqFdѢE)"ǏOwww]iEQ/H̛7Im۶V}̔)S|̳ghLN466_@ =;|$wVE,YRjjjG!CϏ;Z*gΔ)Sr)Cȃ>X=#J%]]]^Ӯzs=5s1kL믿>EQd…E7xcގi{{:7E??Ir-(}9rWGߧ(<}^@״1 .ݝ!CcMWWWd)"wyg&Og۶m{|vL٧1Q$~(2k֬1~͛-[TǴAU?[o]G?EQdΜ9I?=e]twwy睗+WxMo^s1z܎;2tМz9v[ZZZ߷y?>>`>;/bJmmmkrqeȑYdI~kfԩ93|tMٰaC]Ӯzƴ /0~{ yg/ǻ[vҁ c^wbL8T1m„ y*EQ?ܗ!dLKO?tM-Zt/CȘƍ;.sGC2%$cdLiPҀcZRICCCSWW'I$I$gŊپ}iJ%˖-Kcccmۖ۷K$I$IGd۶mKccc-[JcZCCC֬YT*K$I$IGdJ%mmmYfM8-]4[lIGGG6NGGGlْ>wLKR1O;J%uuu}?~0~TۍiP!Ӻ٩A> wޞ/2K,a/BtHǴ|Yz^_(ի|tvvK8cZGGG>꿻uه~3;lcZWWS=1 `1=|A#l<}n^8c￿7zvtt7y4pHǴJkLyuyT*^!{tuuUJJ;v :j]]]yi{ᐏinuLyD۾}ک2YO;jʻkL mL emmm*QgP3C>-^8T*= D۶m;"{裏7$555;}}'K3k֬ե&k֬1ڵk͚5IW_}5W^ye6nܘ$پ}{jjj|@3!-Zvvnݚ|0555E]I&eΜ9ٸqcn_ΛoyPνmA3-*5=C5jTF[P;ce۶mYzu?fӦM>f1mӦMիWKޣM6eɽ9M:5/>׏gC:ر:m߾:@[l͛_W__<7oZZZqϿ?׹֖۷WǴ;v;ȸqs墋.;SWCi?^=f1pYnn~F;c… Q}G#ڮЦM䗿e6mTʕW^~;ӦM%\箻ʊ+z{뭷GL>=Gj袋Wo[r-v_^zisfÆ 9rd^}m7n5\G}z۔)SzlϘ󷶶VǴۗzƴm۶Ӷo߾Wggg#Iߦ&nǴ?0^zi<Y`A1m珕{}c;i]G,\z3mڴ^ױvڬ\2cƌ5@X"_|qǾ{ƺ\|ٶm[-^*ʀyL~szO2%su}ݗ|oii]wݕ~x_Ý͛7Wߝޞ~1uZre+hA^]wˎi~m{oƴYxqcڢErEeĉ.ԤG-\sMi< tl?s1_7>555ٶmۀiJ%[n͛#Zܫ7:uj-[?}zfϞ]wSSSu{O}YjjjgL^gy1sOx~)wygzv}vzm۶R 8͚5+555 sE墋.JMMMF;v9kAlܹ{<@uL:z=iovjkk|7`ggg/3hoo_#G̟mׯW_Gyzۭ޺۹<> c@m}k=cZkkki;vȂ lW?~|z|w{?b;q3gVŋ/OGGGgy{ Ǵcoƴ*#FHkkk/2^ݝɓ'LgggF3gf͚5?~. VTr硇JCCC>5*}ـ=c@QvL΍7ޘ[o5_}U>'5类Ɇ/~A3' 7ܐsf֭}/~,Y$]vn_@c@ם$oFa?&tLkkk87oƍ{ i=#cߟn)֭ڵk{pĉS[[_=Fʼyc=c;3f̜9s^zsۭޚ[oZ*555y饗:555UǴ͛7W͝}L:w =s3fL㊓&Mʸq߯bIؘo=cƌɓ{q{ ǴcoƴOo~gĉy'?(3uww˿/W_~zyL؁^7x#cǎv6lȽޛqeҤI;wn#;oy !zٴiSZZZk@yҾ: k;j6m?zkc1mҥ{=Ĵ7̶mښlذ: A֬Y566z9ٶm[|Mc^hkkK}}};X"{Gzޕ3fL80ؘ=ei۷oϲeؘJR 2o޼lٲ%7nLSSSuHuD[zGAmijjƍư7ϘPBRIccc-[;vyLcZIR__:I$I$鈬> {Ji?$cdLiP1 J2@I4(ɘ%$cdLiP1 J2@I4(ɘ%$cdLiPҀcڙg(gJϟ(rme˖嬳ʠAhѢCwq}8SEVZ= 4(#GL}}}qu`~cڴiREjkk]\vL;묳RSS . CMQ>|xzgLIiW^yeȲe݅ގi;7oSE>=u@ƴ͛7ΙgCf{{eԨQ2dHSfݺu׮]*'pBƏ?iGQIcccy睗?w^/_>Z*EQd̘15kVN>wyYhQ^{}9餓rwٟ1-I.Eŋ~wU[[(/~ Օqƥ(s9͠ARE.]$illsfҤIIQk$9sRE=ܜsiL={vc)aÆ(ozƴ(2tМuY)"sL:T3o޼>g_Ǵ|4hPR{_i~i~aȅ^J$yRE$G}L9cک$OrGN^}gXg}z_~yǴ;ogL=zt1mŊI8E/z[WWW9E=~_͘sP>I&%IE9s$I>^c믿(re3]]]kLknnNmmmeK?zɓs]w壏>;3I&婧ƍz\r%?8/bFmL9Xcŋ3z466Vo{3~lܸ16lSO=+WV9sf|lܸ1/B455UǍi>kL{3mڴ^}Wɚ5k_<쳹{sWǴz(ǿ;4C9-_<555gԩSO?o#6-\Иs0?9f̘^_(odܸqiiiɂ rez}W^|>O8|wL{RWW׫+WV;'|{kȈ#r7oɚ5k_@PWW?O8q1 gǴlܸ1swdر+2gΜ477gƍiiic=c.ˬYt\~yWq|駙6mZƌo1|1 gƴR41͘@I4c%ӌidL3P11mҥikk;??! cZSSS.]bŊ466`gV\ϟ1mYlYST#yLkjjʕ+S__m۶;%IRICCCSWW'I$I$-]kǨ0E F.0i|PU?q;4͘E4(2@1 iPdL"cȘE4(2@1 iPdL"cȽoΈל3Z߾~Z+z'{޹3;Z$I$IZFD12"<&vIENDB`manager-saml-signature.png000066400000000000000000002051051325274564300402310ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDRk IDATxWWI4O.'$&F 4 D4"PDG AEA6ihꚾUU$ZҾԮ]{UߌF#^xǔRJ)RJ)Ǐֆ.tvvF{{;FGGa4)RJ)RJ)v[OOiRJ)RJ)*8::.m+C)RJ)RJn[[cRJ)RJ)RJ|1iRJ)RJ)z"iRJ)RJ)zLRJ)RJ)*dRJ)RJ)dRJ)RJ)*%^xA)RJ)RJ)U4J)RJ)RJ=4J)RJ)RJ=TJ SJ)RJ)RJd2RJ)RJ)R}ɴ;w ,, 1,Y/iiixM6;wNRJ)RJ) O)6444'o޼Ea8rˑߏ!B ''g뜍 uSJ)RJ)҅KK?G__o߆ 8{oiRJ)RJ)tJɴ9b DFFl׮]^8x;w'|O>{EAA+eN<bŊXl"##.} 裏h"_100AY Hvtt`ҥ())q-[~ xԄ;v V’%KrJ͹)RJ)RJKKbvHp9e˖!77'NU駟$QUU|,ZIIIwlقCYY!z!z=4 `0Stwwcpp]]]xʵg{RJ)RJ)WLx)ĉ|AGIO#*h"<ܡH|ڵ;Xjz>z=?Yoo/||| ' ''n݂ 8wg_~el޽V`@uu5A?~K"%%Ez/&&z^zl2deeI>}e˖!((HvO>Œ%KPTTPRlܸѥ (RJ)RJ_×L3 8y$Aʕ+"8p>>>xqe)^{ĉ'fX_}2c2 oz=N8<|xj*ܹs7nK.Ci٪*ƻヒvܻwXt)V\"dff&Ӟ?C᫯’%K_СC*RJ)RJ)/L{ۋ;cDDD[(RJ)RJ)uvޓi݃ |^? 33sRJ)RJ)RLVX \|QQQXh|X|9{=]ׯ_:QJ)RJ)RkLRJ)RJ)t!dRJ)RJ)*%(RJ)RJ) 2F)RJ)RJ2F)RJ)RJJɴ^J)RJ)RJLQJ)RJ)RR2RJ)RJ)R?~1J)RJ)RJR2B!B! iB!B!xiB!B!xiB!B!xiB!B!xiB!B!xiB!B!xiB!B!xiB!B!xiB!B!xj2߇ oVXGj]l6C,_U{ƴB[nB!1XO⨑O>AnBy]8_ |>}2"_BtuujydByM, iB!dZ[[@EdffBt:ˈ12F!2sf93d\AnBy]q2 nݺAヮ./ K9S}A@YYTFdd$A 7n5kd,_:ܞx)z=>Czz:&&&ns@҂KbP-o (..LHH gB!jÇ!O:A{n??W_}pes25<6mG}󟈍=gB!ueNWEFFJɴ??!K2ի6lX,{xw066.;Xx1t: 7o APRR!bxw `˖-n> {>3,^ Z? A*ŋcttt=A! PfL{뭷" o6A˗',]V֬Ym!2g]zUJ SNŋtyfƲe˰l2LNNQ Ν;8vHe_|c ۇ!<}A@__vk"44 R\<(%Ƥl@!:$&  'l?@E(%>|RonBy]_9N< Qė%.߼y3Aݻw?B?^ Bdd$>C坓i۶ms۷ol}(;nAPSSGBTTT̢ !Bb$VX!}jbѢEXd DQTMBdddHeLMMl6+^؍B!zf=L.v ((o6^x{.~m{HJJBYYj2 zd_Au:DQ ؾ};BCC;h4zԄB!>j-f ~'ɴ+WJEK,ŋaZUi ތ!%&&&bʕxwAM={ ۲/pFFF\iNLLH8/711e˖ᣏ>;#=B!uE-{)A QDgb1[j*y[ׯj(_~'Oԋ!B^WdlڴI\z??? `刌G}$}g͚5 -[ŋ#))I렫VR}-_\aZ=.\ *%Bob9,^~!V\zKi| l"k{lZ2mrr_|A2v#BȂOM~|뭷088(?66,]_5n߾ !X|9A@cc#X~=-[> p[Oljj }Cbϓ_ADJ!:+V`ٲeoiHJJ?O?AqqtۥZ2 zzz>˗/ǦMN!򺢚L#~ٜdc9B!ɼ8N5699*B!䵧̙3(..Fee%?~gOOϼ'B!MUqLB!`8~8?bǏ4B!ׄW1F!}Ӄ3gθ .\]&!BW1F!}`-`AXqq1L#ByMxqiB!*0F!0a2B!d`2B!daF&:;;@EB``nf <<CmmTu`4ooo}6"##H8,Cxyym055mU\\o޼dHNNF`` #V+**Jzm6R2 o>.\D趝!? &fˍh4ػw/^x!}T'8L)::;;VW"#B*4Z-߿q]NE$''#//cccX,xt:jY.;'ȑ#8z @*e$, paʸ}66lłB9rDu}BȟiuZZZ\5(,,TPlx4ʱ%iBLF7|9hs1h4 R0+ʐ%ɁNCaa!޽+ݞ ;6lHIIAEEK4p N>Ç둒ZKDDD___E;wD~~>t:(B! iu媫?W-^QϒivZ]WB!D2 7nƍ1999ηLNNbQeReիW5/"55Ui֭w jjjrrrPZZ t6oRiNNNtijZΖrJi^^^())AYYs<)Wƍ7D!̆7)W***\)kZILS+rc:]B,d9`qqq.*`(b͸~GWY,ի/))155hZt:ww.ASSa4U1dZxx8DQbAll,n޼92zKB!ɛL{ټnM69kZ.^^2M2#B*0"%%ϟW}hm||.u7ʂVEhh(rss >>vc&װ w>-(bϞ=dc0KNNF`` #Imm-\OBkk+233q̜$&&Jܹ7zO)k׮!!!)aw;<fQQPP^նj J>ײmk(iJbՒVBQQ`?~)77a%d&%^V,nxvKs1׺x׹k+WP_ۈDHH"##QSS3uylFAA8~9~qןmmmƣGǥғߙySS8Qۣrq'ur4ӘKM69$#vN @e}f_$v؁"$brrǎsiwe-[ȑ#0LX,u4 zzz:F}}}o+++^Ν;U˱i2֭[Xf͌itBׯcͰZ166p:{@KK *//w}łK.!--M,<<ׯSJ{ll ϟ?w[1,\}Sw\v wb0åGn[L&t:a4YpQR;ؗ4d]iii8֭['} $''ɓ?h&uMK%S'<9^;g̵.uJ*ih4hoo IDAT(DXXxdall m?ٶY? YHiɴrJ'+c\,8s BCCgJﻋ?ZO:%eDQDnn. 155ѱH-~DhhY}}}[fv&1NֆdffibHW$''#55---3n;w"558 Vk e1lρmV(<}T55UzP?j"??ضmvv-ǏGll,FGGa2fdddHI|HKKiە{6]lws/((@MMl;twwKY+b@J;w%q0C߻OBob2mDxx8DQbAll,n޼6mr V+_ʼy&o.-g{AEEmJn{n466brrzhllDJJ Ξ=Jn94F#ڦz`t:?b900w$zzzjۋpT?>bccQ]]NdZii)>WҥKͅN~ DIIT_O)m&&&ѣGb۴n:Iɵ>w[-j:ۮ>,=sAn[***`DzzUf{쑂ig\9VXX+WHTUU!==]q.hjjBff&d8vN%'';vK rݻÏěLm,4OxrRg;\l2`6e㏹œN\&"6oތׯ&Ӏ7[FBBLm'Ӝ˰}W-=$2FRRte$4fas=z^o8 lق&K:>dZ{:t]]]cgw4}NlYP48rv%5&]ͱqFonﲑ,],LZHIIP̧̹OBbhhqqq8q&&&$\Fqq1L&c;ȵRaaat:Է 8ˡjrk2M<ŋ!"f3rssN -- V]]]X~K2vߥKLBCC1>>.'ҳ߿UVd2)( wO ~8ܹ}Ji|zUUUزet6n܈O"""݊m쮏, t΍X,*~glݺ@Cnww7"##~999əS2mΝXfK9s%{.1:: }Ji|zN4KKK@ݻضmΟ?/r\-'OYEC`ƍej㥢zmfV֭[c`ZݕDQѣG0)۶m"\i2W7|mjuxRPxSisj+́(ҳ=?Zu>)3<< VnE/555?QTT$[)i2 >FMhh(rss)| ԟ4`yo<,/{]=6wLqUJm! >֝9oR2m?0SxǭU&\~t"&&j{›pN2:MkdL#~4!oLZr }6"##H8l~wEoݻwwJe#++ R:|."= V`پ׭[ 6EZOJtΕn4{Ajq7ǐ?qqX,vCm~gȑ#ߑ/fb?V TTT̺,BLޤdZqq1 ޻y&@G{kkkJ˵K:vۨ oooiGAADQT\/<с]vIՎj$UJZq\|elUbw4VJ,'>J5@Z:]Ԥ&JɴA#??%&"rssQXX'O ""Fh4U3LZ#WOvDff&pNJGn ;'a=i"TH[SS( [v|6m߾O4 ZZZ!z^唐י7)x700/;($ŋGttK"wt: cƽ{p闖L&W9W)P\+_[>RI׾$["99 'rv)Q- 4==2vňxL:"""dP?.ʱ4_r婵;!X04`իWƍ{ފ {Z^Ҫ IDATiii4`^Td;wD[[rrr믿J:lzMjũS,گv H!66}p-~p^QQRRp$$$ !!Ѹv, t:&''ONre>}Z:xsU]NNtyKyf.ndQL&V Ŷm۰k.iRZضmJJJjmmũS099!DFFYu6O///jWFEEƀ}555HMMEDDzzzT'N8t!\zUMTUUU(..d^uuuرcf3F#ޮZgA֭['I2M+++QTT=Ǐ]ȍadvOgee9L[nuIٳmjj\6< dsܯVڝ9T2 z=6mrV+044t ={\i(b͸~3l)[$ 466J5FRR= łdggcllLZN-Ptrss_="##QRR"<&''QXXFٳ8x 餃/& fA2ǣGzŹ\+TǾ?Z-t:~͛ؾ}:o&=***&܂TVVJWHNNL?211Yg5(**BeeM2-<<\|=66VQ-&WG`zBj핞%L?~\uvrvލF6KR#88:~~~O?+W %%=^nյWH:WY,I'4vvNUVVF~~~u^^vNK>휤JHHpIωBWW}]fG #bank"Yi4زe WLE{)HӜcJ1 d1@>j4͹JZ^ws2M.RI׆@nt(mRg4dljƓxÆ]8۱##$F:le6&''ى |}`qAnO9JW+O /L%DQD\\N8 X,#)) pyJq$AAAɴj ׯiǎCqq.@~~~>|~~~s^rr2BBB0>>rhZ)o!&&& cccjmhhڵkK[w9\#[(F#, nܸV+LCDDC[$''̙3m-+..tY[[+]oi]]]d bccC466"66x aZV+%]矑IL&lڴ ---c>fϞx)Z-UiruE[nťKL'Gkk+z=={QsJ*waaat%Zx< LrPVV&hq=x@΀3)Ε #]/BEfJBS1L3HII>}er.^Z1{nCE$&&… '\L'ͷl".]BJJ V+FGG.oμɴ*8%?"G]O.`ZWSGJE!$r"RR@jU^-RPJR Lsȹ:Z%ٳg>33)--ҥK[cV9ccc5Zvu6{jkY$VmFvl'fU6bln XP[r^VO;^,O܌X6[˪333}ql0-﷪.X4EQ{߼y×_~pPQQ!fpQN'8pPLvb(|x< &77g}O?Dcc#111@5=@ +H=zDttދ/O>%&&Fى!99Yw*NzL]Q[[訩V]]mX{{q^ᠨHQLMM%+Wz\.WUUp8޽[l=x ~iXٹs')))"6_=IoheQ/_L~~.rqn޼IZZ\.Z[[5qk8322Ī4ĴyN'333477vx>^l>weq\0?4-2.XĴwIh|W<|cǎkoC% ܾ}XK`yD4D"o"sa"RL{b$[Jú%MVV᡻bD"_dN} _$D"H>RLH$D"ٚH1M"H$ 4D"H$$D"H>RLH$D"ٚH1M"H$ 4D"H$$D"H>RLH$D"ٚly1-99m۶ ʩSp:\.._ @KK _|W^I>쳰vüזv555\ZZ⫯p88r_6mQQQܹsGzww7QQQXڥKRRgΜ|= ?3~`'KKK9iY\pA]__Oaa޽1y.IIIe[/Ebb"$$$PSSju466zq: 6G}}}Nss;wPRRbxm;SUf^/iiiG63 H\\111~< deefRSSIHH 77Wwоٱc =|ykkk}diiI墭 EQt0ffmk511AtthW||<ͨjz-g;77}ovvV㒒SܹsV}iŵkHII>ѣGA"yH1-k#Z vqƍtvvFT?9ήIltv9U\Y̼{.`FjDR۴ӊHF|hl[,#;vج=u[?"j2{7roEOOx̖v9--x^"--eHHH˗~\.+>Kc&$( | G1maٿ? b0eIMMepp6t n߾޽{ RQQ!NMMe׮],//JKK cccǓ 6MYيX/m6q OFFׯ_no>Nv3:: @LL\p\6bݻwOub,--cׯ)))o|466ֶlt mF`- /_tki>k];1Mȯޕf֗f߸n|>.K+bZ8VFc4H4LL"*heiFw7gol-GVcczzz}>i&%{I~e%E:O8={t ,l}fKi`ZN'ӧO0==M~~~HKKKo޼޽{dff( ---naOFF~^~M^^YYY̠( _}n}qQw/^e544puCk7n~n7*F"p*ݻ*(((ÇQPP@uu5.]4ɓ:u*=x?<)ŋ/B"P-++Okb@bb"o޼… x<D\ssXk+ݻȈZ2444$>8(BEE'N@UUF$34t:{@ZϧDi^"//&S1 _%#GVVYYY$%%Q\\ !UUiiizZ5''G<9xH|Lbgo?v,6D*JCC---("VqP^^[1.4F$[`n666FEEٹs'cRRR$55^|X>UU9x MMMn1Ĵhh2-119VWWINNfbbk pQ`mɓ'SN媽;wNLڪ+EQfa[INN W*IIILOOs5:::~:(B\\>Oi\.fgg4󑞞͛7#[GGgϞ 8iiioGFFV7~Ο?/n)--ehhbX\\$//![~:066ի @ccn fqQbcc^Ұ6:u *ujkkuv_tY}Ç ߏe``@~vS1-G"XKR}vyyHi4~:D0dttdTUlkkk+ݼz šC5vrrRLpsNbbbhllDUU߿Oaa!@RRRԉif}[1X^^&77WhY4Omnll/fIL3 cb]\njkkQU[nq |'iVul3G$ffqQ(VE+L!nCLC#ifbV300@ff&sssLi]܎bIOjldddX2f9s6v}=W[?lt|Yi_~LJKKgdHs$7[BL5C `dds#==!>|ȁXYY󑛛;Ocffm۶? .y.-=x ccc\r%̬PEv(,,bB099)&*~gq !AΝ;+^7Ӯ\BBBN<3`muT__BJOOgll!Eb짝:׋ԭL+MDH=TLt ͛tttJAAdeegs`&<EEE0[bF811ATT9sF=xy`r-t}ǎ;x󃃃WYYiݴif>blJMMN1T__Ϗ?(~ Z|ܽ{W+z6@ @qq17oɓܸqC͛7 Wۊi[ikk3gƻQ8}ǹuV$6f4.VņHVy^bccEkkku6'+Ӭ{zDۭlf5b]\]4C#iꉉ bccE실Ͷ.nGRvX'c66&''IMMYN}bڇ >ĞvʴHt}}޽{7͖ӞbDTСC:-MvM0lk Ǐ)--ŋ|WH4XKnܸa*~k$^WHĴ.]BjGZYщȇdžאH|LbgoXm"JKKy^jjjt6.((xrCbGLP>aG}Uî`=ik1nt:q$%%̌-#ϑ|l 16TUɓ'?se*++ ~ ~rrrt݊iZh&Jnn.OfyyEQhoogl߾]&XĄM.]\lgc``@Z?^m4X[ xXZZb``?\AӦT\ĉq}@/x񂤤$!-g'ER7M joo333#~944h,R1l駟HJJ >crrrX^^fttT`D>6z\?ܜ8EK~Nk~QiV6Py>!|~,5͛7  k׮01F%%%$''ٳgq:4R1- RZZʥK>guu^o6Odddp5TUeeeq>xXXX`qql hY4˗/JC1if] bYlq_.yl۶:#n7gkEXel`wΨQI&V{n?NZZn+U=5 IDATm'XH^ 5Ϊf9GLP>aG}Uî=ӬCtVRR…  m}fKir8vfΝ=`fn7GGHL[EMMmb( ٜ8qTT _~%. AEEn* axx( aeeGt: HLL ۶mz{{M_m˗M2?~ezTWWSVV&4lЕV="::Zz1 V jbUV"Gns΅m$ybbbYMى!99Wɸ455p8ؽ{1;®aSo ޼y#}x<4440::j*i+U=餤$~6&iwif]vKKK-XQk<ŋŖO#'+ mUUU*N񐒒"e3;wt:=Qs1ĉdggrDV}i+`mkyFFs$$Fjfa#bXsatH+1-t7ǽ{lA;iEXel`vQggiII6+ov} fWf q;'BiQUU76EEE7 ޗ?bڇ >l* #2{#[uu5/_֕uvmhY|>}fKiao y;TU%Ç;vûGc+D43EyXm on첳)}! ɿ) 󷺎=LNN%W+l-J$[]L7Ǜׯ_S\\Lvv6YYYH'2K!{bD"H$c$D"H*RLH$D"H1M"H$dk"4D"H$$D"H&RLH$D"H1M"H$dk"4D"H$$D"H&RLH$D"H1M"H$dk%Ĵ%HLL$>>E[[~KRRgΜ`llh={+#))  u^Jaa!mmm477޻s%%%QPP`ZWVVhnn&55rssy7nܠ#==dӹy&r)N'.˗/ŋGEl[Occ#^III (fױVINNk>[ZZ-bgHQUÇs15 0>>NYY]Έ F4K"|boFTT===aﭟp8Ί|j ::ZxQUAjjj!***,_&**QF/hoo7#kfr0kرL,mj]meHʳrGvŹs^i߿ݻ#׮]ǎ(;v IRRW\:Xرc>#&&F[^Oo>$^)"w+a6n"wu"x2bڎ;߯_o{x<'55Aƈ'''G5r믿p:3>>NRRnҫuX\\6"Sn7by/^@UU^zEJJ  2 $$$Ku4H^ `۶m}OFFiСC("wO?ՉiV*麟@ (|79rU<OX=43?1zΞ=(ۥ%bcc mhշVqHJJbzzۍ㯿rlm*ɓ~Eݻ8nY vZz n߾޽{ uFX\\$550073gz͊iVem^m4xW.-)_… x<Ҹx"G?@CCׯ_gllt(#tw(BKKNHӧebb"o޼Ǐ>EQꫯp۷G200 aOB|>~_i@KKGkhh1||7otehBv7'OԩSܿFzQQSSSmO`۽{xyyy455iyxx8F+++*;1Olv7͆[۷oS__/^_XX`ee!188hk矇ŋԘ^7 R[[ppi`퉨ԾrMcgC3k%Un~n7uEmmXpݦhkjjƖ77\;tpp: ;~`6.4K"|lbZ0d׮]駟+Ѽ4-Gp288)//rQUU~222kbff_~ vÖ6#++,(..P!nFNMz$9=ʳjY]EiV2gϞ*x^RRRF$>6!xw3З.]27TVVR\\9rDV}aK$aˊi6...244Dqq1KKK,..А$''3;;+&y\.a1T4'MFm~:066ի¶bZh?2x1)))dff؋i4IJJ⧟~׮oCOKr֪Ξ=ѣG'==]l;}n+Acc#}}}ٳaazݱ1~;w( nȑ#A&&&O6 Ү]DGG -8z(9޿+}P^KCeUo'OibKߙfqiקkVsDĴ_~LJKKu[ |ފt<611ALL ;wCccȟcuud&&&SNmS;wNܘMLKKHJ~~>dff277gierrrDnlW%9sZKHrn3{Xg]/(vwuuz|,//K?׮]ׯ܌(4fbbbt>kfcÇXlcf|"#4*>}} ,,,dD>6!vbf>F9ٸ|qaaϟœ'O`4+,ϗH6–|tttJAAdee 9wn[$kpi ۶m~{t gXxPU>|(*~gq vyqʭ[hjjƍx^bccֳܼyrD'8p|> #1mZ__(Qۃ MMMx<(++n"Ӵ~rJؾPFFF8w@9HL3#i-**Cm$[4֪͛7~RSSӺOǏ֭[mkkӝǥ199Ijjua->ĈXH~~ӌcgC3kƕ+WHHH]ì.vPM ϟ B[/ز=AFl\i$sDĴzqdEoo/{ս1-m@bn޼n[7###TVVp&''ōfĄH bccqㆭ ;֟es|\\;v+lj#ɹaUU;.wf.EkkVww7mmmn>*++bvvtc3b""_SYYixc7fŴڹܼy3"~;1mnflh+Ӵi']$Hٲb̌|u+3~?+++(Bii)uuu,..C'AUUٻw/o6 0^Wʾ}r&B={&n5R:1ܰ BL+--ٳgx^VVVw>|6;::ď%`Jww7Ǐ?=橱~2cjjʰJA6!fbm9ٸG _.#|d#lI1QZZ*&՟~lqv@g@^xARR҆Ĵn~-} G :.]\,Ӊi3|>?t:ܺ:Μ9#;22?@UUo˹rVWWS:ٳgQUB^600[icrrrX^^ftt0je~VU\N>2|2~?{axxVL3#=~)Pk׷VA̪o?$''ϫW~cvvyxcg[EQɡ] 캝*O6bYmfn0yFsD$=|4WRR'[\v UUYYYA1- RZZʥKl4X[ RVV<rffF*dh>iGZwuuݻwsҘ8{2~X^ؘ,GsêKii)`7oސĄnXoWO#{1?ĴO߿^W}z!xofsllDL3*,ϗH6–Ӵ%;wr&V;Jgg'd^ }a۶m_ooo$k6"իWDGG>-謬pQN'8p@'ڍv㡡01m~~ɭ[wo޼IZZ\.Z[[Q`0Hss3n#> N',//G$-//STT륺2!o455p8ؽ{7ccca[/Y37o/q\8***@ @uu5;w$%%E3055EttnkZMMf~bd駟+[תo*۬o ֞ty^\.nk 33Snkg[}6AQQ4;99Izz:.cǎQ__/j:|0eee"A2 Ҫ]/_&??_rq[[Ybuuذ_ǖ]!21m3sٸSK"_cӪ|;w4`,MLLP\\Ν;q:֊ջVXDZ^QUU[ٯa$ sc гH~"7xEEE~STVVk.#^HVY,Gsê|ȱc6uzܾ}Xfeeݡ(D"X1i;~uS\7Eu~a9d+!ŴD]]W\!##cSJ䦿߸Dbׯ)..&;;xt?< H$FH1M¿)"ٺH?#shVCiD"H$)I$D"lM&H$DbD"H$DiD"H$)I$D"lM&H$DbD"H$DiD"H$)I$D"lMoEOOxMQ;$))+Wk?a|5QPPQQQ$&&ݽ{sDEE133~||<.6E`bbh~||<ͨjZHd۶mZ[[tٱc k?~)N'.˗/hhh رc>Occ#^III /^T}4fggqݺfffk---|^Jaa!ى%))3gDޕIMM%!!\Luu5]]]%''˅޽kZk׮g}ƣG뾲˥:G>KܻwtIOO͛ԊH`/^ɓLLL!^_YYN:ڍ#::gϞYX!2{F}Vy}ER滰f%-%8q={nBVVV#..ׯ_377ݻ-~ IDAT!11#G^߿? BL۱cxה/_t:yi")7UU`rr{.\ 77!X^^faa^|)l̰* -ʾ}8y$~EQ{.M Nߏ_t:gxx2󤦦288h^UU)))EEӧnFGGMjdw駟ڊiTTTSSSٵk 'Nݻ`0hxHuxx<Ƌ߿N7߈qbۛK;޵Hjj*fY11==mi>:(w}>ܩaԗ@m۶~?\~FwmDoclT:{,9l8O1m#9](BLx<ò7_7n_2о˥F8^xz!nDLkU~kb$8@kkD>m6ӌNVh嗑{׮7U}EZflg$; ٵk|駼z ?q^/~/۷7p<iii\x@ ;1mdddׯ_GVV_'t4nYzzzhnn&//dPU5r&N8N`-x%7okА~fu顾Çۖmv-'?x?<,x"_~%TVVR\\9rEUUX҂Qy=233QN<)^Vi˪CCCdgg>"իWԤӌzjj !|m޽TUU@AAnqLNNR[[+6|>n7XSc*x^RRRtlm5^칲BKK ZUE᫯vo>=@DEuu5yyynN<_XfDMM 455 5556knnٽ{Νf} Iɓ'9uꔩ#AMOO2{zză:2Rc}_v}0V _``߂ynb(TTTp qHb>mYL...Xu8,..cccP]]ͥKDifagH{Y}ll1 6[9jveKZDbǖ~233(-- mnn&&&g=CCC"yyy Ĵ(8###q)ѭill$ gVGUUg``Ll6f/GGGPIKKcppDx9QQQo.,,"Vn|a}o[_[=( l߾]ivՅ 淋n^zСCdeeVH|wo'Vc"##fׯ_p:\zU\ci֗|>v#GꏚX]]%99 nݺő#GLLL'4+Q"1i󮺺:`mÇ|+.V%%%O?bF2]~INg6E"6VǤIjjUiyQ.xiF,.fR__Gmsvvmo塡bZZZG%66V.6Ah[bkׄ>|4P\\͛7ԩSnN>Qa\NqqqرC}Q{ϟ?kƭ[t+4Ĵntb_ꣾ>*++bvvtʕ+$$$z(&PXXm۩^9y7uj\rEfmW*MYEEnef#_ҝM[[?/ݻ=χA|Fb٘$55f`&<EEEnj2SLL[ף(#A266Fmm.4;3% fwUVVVC0ݵbF2|$+r:]fFFF8w@9"><}n5ǹu`Wv]{%fggy&夥mh][ ޮ{YYYِ*|R3a%nԸr ݈GTFIIIRЧpqAsspHIۣG!4: ;tbUV;w(.. LLLpͳZ\?4aЭL8[XQJKKcqq1b[tPL *++q\n∏3kX d nt5==]pt2==;"jҢV_~aϞ=:{A3mWVVÇ 1֋i]]]KKK ?/W_3!n qㆡ~Meeʹ%%%fәu>8]&''zƷyyD]b\P&q!5ЬrioսÇاA[[vqݮ vm(sǏ jbZjj#V>ZF>mYL(,,ٳg(a 1MUU۷fvvڈ0CVٕiU_~7fot=?زڕ.mj[BL{!iiiURR… `0ț7o*9!OdggY\\nDLTVV&31QZZ*>~AJKKt===~?w槟~\X{gϞz?cllL$n7nܠU~?999<}f73*w礥ͮp-!hoo333#~eff&"͛TTT ?ٵk[iڪ%VUUUu~tn9& Y+v\\'N .. Sl߾]MêF<* xްCĴ*>}eEmWK.Q^^.mHLx<,,,Hvvx_RRBrr2KKK={gL[?&E%f6{1999,//3::Jjj*`t4KӬl?%ׯ_KOmG߂6Ǐt:QeH40: ] n6sfZh|2~?{F_F,))),//Kݪrioս^?UU)//[4;h1vmoCY:tgϢ*\z5,?ilжZi۷O< 6ʹC4/^$4F|=2kfc͈ioS9jveKZDbǖӪ|;w{nVWW/q\a@Qax<yAvguvva?}Νa?9[LUU+++PYYINNIII +X&dsnn`0Hss3nŋ bCgVoV,}xjUٵ@yF_/;w."CQk<φ~@c}HLL5_jwmm-NCCC455LQQ^jʘ?P1̯t+=zDtt8_mm-ǏguuX6Nv:u ǣV/ژw8TTTzv$_YYѣ8N9pĴ'N c/^>}w_M$mf1va D@d (`E?GAy!!k:hs8R@@kc4o!mo4MIgΜ8CbNE~'b|8|08Cuu5Yq1M|>*++qa$&&*$'Ӹ;w4Ҁv;5"990Lhii %#ʍfgg P\\m5+JJJQD" ::Lqnr1M>I=jH%Z;oӎk>>)_oiz,Sv=!}) … _@m6~JZ(jb2>nB 1|} Fp|obڧɗ7c㷑kx&o}S˕)ÿ߭6??T2[4aZo`bڿ?~D~~>222Nݐa/i[Әc`6A۬L-n4 &10000000M01+i {LLc``````` `b$laaׯ_W%aoi __11Arb;zJŻw/q_DLx<8← Cqlffx59X]]`PܳL·B~駰. Enn.p )) Bff&|HQQQXZZMChrP_sssb([ZZ ;ωGTT49(p8yɤ"A8ܹs駟StlDNN9 GQ=4FOj66m9V>'>5!==1_ԂWv z8nv:@Vxf3< v-mLi@?b'>aK.'N  ѣG0 XXXP&f|>N>IXVloocmm III*=$o[[[q`4-"JJJ0??k;S1mmm 7DQ r4L ,\x^"..?~u:t>| ]t ǎCnn.`5L믿nann[[[8x Ǐ(((fD:677[r1-{BD޷ >| "nֺ'DQDAA뱹 AKX,LOO&''v?-R]B-o>rAjj*uo d` 'aͅcaaA1-ij>KLMM!!!gϞUu):tiZiWъ_BՅ'OBMN288"@|ֵkiX,pxL&|>5-777{r|'>5{<X,,--vh4bqqqGOӴYM-ӱduCmm-.]>?@ nj4&ZbNƖLLc%xgӞKLMMԩSHMM6@<++ DLJC7Z񫪪Ŵ 9rD1F$C*++c]hqN'@aa!5};Aֵ58bvvVӲh#irhܚv\>#Gmm-}}^W/ҸR_B}}=9]UoeeeqvΞ=#4wiu!'E7łX,51M>&4?Rߎ/>ގsssHNNƃPTT@ ifj$JId2ayyyGbZoo/JKK!+%''|f,//+Vׇ&#!!+++ysssy Isjj (>]FHvn~?rssI HLL>X ^\\)ݼL+--%՞ g8C @MM [,i}}}:p8p]1&QQUUE]OO5^Z`zyywEqq1ɒkZC̙3Qvl6cvv8}4^/n7233> d__?NXZZ¾}rFFfhb\ŕ+W>ccc8wN(y'&Guo+>hڞv횢>$ܻwBby] B-$&';!W#)) 999Azz:î9~5|obgqIpBYYӘhiWohk~~III^h$Vyyy(,,`D6Zǹrb?{QѲY.E[<fre-C-GҶC4<+qkqVl6҇011:/E_%YΛhV6q>++K-=KW,n:tHQ.@:&47 zgbÇO~|>\.tww@p?x5x'uEǏp݈S8y< 䳄bZRRyfY'A`QSSXMgZuu5N'&Mᅾ \ 'N8L&q̑i%Fcc#PVVZ4PTTD,TLAGG 6''&DPB{{;Z[[5ZfR`Q p(6f~?q, F#Dy^[,--!۷o7UBǶ6<}l&<166ݎ7nŋ>-1MnVֽ$ڞAfffɫӴ@/MjyJmmmmPv?20|k4-a$AEÇU1M- j\rsso{{{QVV;pTa+d$xWŋ6|-H5J(**ؘQAV ӉSN)D/5{hM>ZlZoŞӤ^[[<< p] ۷8rYtvv*0gSSSq׋:oo/ܸ}svvamm (++ #a "0::Wijy۩\ @aa!9L766p1ű!:u ~yyj4==M[Պulnn"##'K߾}}arrSSSyo޼(x=˩X`$\.B~?<l6^|a$''+AAA4 Nf!Q۷oidѣxol6ckk ]]]8N1РihQokڿv;n޼y۴ڔ6++ p<qbox,B-ߴ1ٳgȀ&xh5|Ob0' IQ;%Ĵ%->;;Tpblooieeel0^@p)f|111 R.i٣Gu|2zڞ@^o8VuuuhvU_soziV+f3n߾zZE/h[$iN=#&''7oiiW㒑LJ`0 //|YoϜ9)9V$(g$6|-RSStl o]\\Dff&8O?XfiGmPǏ?mjbZ#hܚvf1Ny&v=Hj}e{{yyyy(,,$bU@ MZ"1.."nݺlW]]RLS݈ijOiY+QI~6^ I>ͦqܿ@0gdd~6;NF&|}{^hs _ߓ 0.\u69 #W].>w?a``bڷAXs OǏGFFU7^Wks `bym[jlitww~4&10000000M01+i {LLc``````` `b74&10000000MI1MڌBtt4x8Appшnr.^a0pY|qܸqMMM(((@BB$$$ɓ'hllĹsݾ}E1,- uuuZrx<׃ypg`6o>򷾾cl6#%%wU gyyF1gCa?SSSÇ{{{%DQDss38d­[PXXH~ommΝ;k^z(8Νf@V999vSBTTiߣGlá8C0Lhmm /mgyyEqҒ☼<4LNNi9޹s駟]]YYׯjkkK~ ѣ~Q . Enn.:::D.ƐKFOOO"qTUU}Rj]."^c-900[||<055E+v\NK/ޫDIt. $~u!TUUPo{aqS>vjN۔b<jN~駰(py$''+LGGxhDggf(.\PIII8t233rN} mL7OZ0/iUy1Mީ?| rssz8|(ԩS |2Ξ=k{BIdee)x<0L믿nann榦H!AE8qMMMx<=`‚=aZ#M5 E+++HLLb(k>N:CQ $''c{{8t>|8s ~uu5<_t ǎS`ss߿w[6jhTl/[[[8x Ǐ((( NhCEׯ_Gff&aXv;L&|ׇr]RR9m iXχӧOES]x<X,,--vh4bqq\#߿?L4g`ث`bZ8>?<^srHԸf(pI)h5﴾=-& Nz|@KDo>r 55NĴع6Awi)}b ֐ۍIXVloo777 ؏ P__M/_b`zz}|L5OZP4 *P]] ٌli(bqqx q+++yPq׋FA#==F`0#ӀLSZZA@cc߿ F<122z A DAA199'Oɓa7n󨮮&+jn7, QSSEdgg?>>28\v gϞdByy9DQjUNLLݻwi6V׮]/8QVVٌrEz߉jOMMԩSHMM%ǏBzz:Tbffp 666ϟ?ǵkDܹs8p._ 8r~jt:p8ʂlFkk+DQT TWW8 \r6/^b'PQQ]Z ==/_g l/޽{GNOO*++ł&Hi@p`qn9#//!55p:t;{aaǏ S"%+JJJp%~:V+q ]ܿzdeeA!ю30e|Ob8jjjjǙ3gP\\ ?/i6qGtDPĴ 9rL N/-=-&x;-. hb455$1MEաݪ;#:ݍFE---yKGq͛7Uˏ8MLs8 *++c<~Xߏ!דyyyXXX@UUbccЀ~$'$?@ii"9~j$G-N(C>÷=! `qqdÇ#&& p8./ml6LLLΝ;'O\qmb(8s `XZZx0== ٬zLEgܻwgϞE ?IrcnnEEEWSSƐ4$%%)DcvvQQQx!KZX,466ӧdy|bZKK z{{Ʉ"A6??OXYYz~-ҮB^^2ᄒ>\zAll,~ǰٳ/^hL?atb`}}UŴ@x<>| %A777GfVшgϞcZqE+V_2ivcN9@(gDB("{}}ҸxV~hbZ?v;^/fffqn߾fnnfT1vEbXSSC8fssn?EEZZVWWɹ1tژSoVNr1 i+B>÷=!wIIk>a{ nf΢;lP*`ZЌG*U.ÃT˃dgmm-rƆ1I>Zasvv6&''JrHRX,|>bvv]SSSz*|>VWW 2s gM2wXXXXLùs#˗FcjjSv$ LMM|>}DPa>}^nĴZJ@__?N΅OӉfrW\QӤg 111|oe#<vqjjjZ{xn(^Ekk+~"3M"qqq8x [<133 8prϸwގ3(++l0{~V. QQQhooGggbv$%%!''999HOO`D6 xvc'Y1LZxFH~KEh`41MZ */^ QӲĴdff޺:444D4>Ä1cH~$hPKL=0bZoo/ɲ߸8U1-n͛nG r "233qlooCԩSi'Q 'N β]]]E;r8޽}GA1Ob}$qmmmxN'*GٳgbZGGSŋx4͛7UnfjjJ~ʔV{ %nvՊulnn"##؆nL/_F[[DQӱDlooÇduvAgG^!ahh~Vǡ>vvvamm Ϟ=CFF<677ɑof`ee6 ^Rm[-iWLӊ"qGy:PDi>VqW$ %^D*xO4+Nz|@KlU41M?@PSSNj}N 6%sL g ԩSX__ayҸ>=q0666͆mLOO#)) @L> b8yP1ME﫴?܃qtѣG1;;dRvjc9Bǜzc?r 3M&1|bшl<=Ӥ}zqyL&$&&A eee8r ] J+++&Y8iiy< _~j%=CZznXPZZBǓ^L~|+*++qa$&&  arr2|yKK APi. 8|08Cuub&ƍd˗/C-1ݻ Ru\DTT޼ya@., VkDFdddҥKĭ[?|GUmNeeel0 GJJ L&.\ZREEE8Cvv6N>&lh} xE>|0L&Z[[OO.] L&Bi1MZEYq駟;w4ҏwj7Ϟ) O>Ett4/XmﯿXVf2Iyd 01iZS4@; 9B#X j\̙3ؿX K+v\n4ZW4+Nz|s j\&i=B D֦$|1V'4!"aZeGq |$1c '1ߌ//>vc<u""lDAAA qF#5ILٌ}u\.RSSo׋\ttth ilڳ}>):tUUU18\~]ܹzNayyEqnii)Z;m ; <ۨѣGPGTTvm z:x n7(:x 099IA^>vZVrw\a̝;w~ O>U\+DEEtc:a'^n|ܜ fBŠl.,,P̊Al91M_vtt(?LCRB@4q9E:oFnn.(v=f@v\v 㨪湮HHH@\\bbbHx.R4,/&KZyac"hM;ie1y^~:::|ؑ͢(ԩS |2Ξ=y^^677Dq 455@N>y. lχ}bn7F#U]]]ѣGU}۶:DL(.4 1=1Mmx<0L믿nannN. 8t8~8{rrYYYk-~8T;RVi`7P/wI v#AbV$V+j||vh$m5qh#_j}}}())!&%%ȑ#"k#߿.C/ -E4i͛4x `||(++lFyy9b? v lll`ppHOOhD~~>5Iddd(Ecc#9vuXV$''ƍ奪 hhh'Op0Rw ?/_VZBB666AEyx<<ʟ:Z__G^^8Cjj*fgg %%%tUCuu5f3QPPQC 455GmmN׫s|2=t(---VG6oJfhtwwօZ~q \i8rEQDKK xGbb"{CCC'aaa@pVڵk *'.A"qP8ʐp5={& Ez/@cz~KKitApLOOcXYY7iuf^\]]Mfn7, =zDuZ2UE1 дw b\i1IEuuuhllj6ǏA؈Ω###X,_gΜAqq"fAibbßhƑ٤Vr~v^EjށhvDQDcc#xipb^>h7V~?~L&z5nA68~KmyaaǏNNuŴJdeeb @4ApEX,8qrht:p8ٌVLi;??ccc YUĴ5L&,//i@alnnb|| XYY󘛛C?v;^/fffqn߾ J"66?c،6l6&&&i;N/HLL/^ʴNTWW]cvvQQQx!?~ *+R+z+ HKKvB&Nk4GGGQQQCSSEC I~?FKC/6 cseTWWUz0f Μ9X,`T뜚]FFF?RZ]]l&+VpE"VPr1M&KZy-k'j<(**B 4f3V_M;mǓv[$\iٌejѺPr ڵܐqh#_jˢ(7Zܹvp@vk򳞞<ۍmdffbddW^ij88pTMwX,g>\Q}>neŘB @CCV+PXXA zp$*hiwww3 G{{;cppP7/rڪoD"PSSw]va61;;G3pU|>"%%<ϣ333Ď(SOֹ2gK{~ȃXmm-A@kk+l6666xDqh>偔fC$y\.b``tۂq>:ùs#oEQ.+yhh---i@yyyhooWojj"eݻw?&oqqq8x YvgleR٨1=UV@x}i;^rE1?޽{H&Nk4AbCnn.fggn~~"h!o {UL$}}}&҈$6i<#66a۷<98ݍC)e(=s fff/ML'ZqdfZi[NpW[[iccCWL'q7^ $j7EE^ BViԮԹڵܐq׏ZDYY&&&fEOOu^mmmUCtYLie\LSkkkZܿfa`4Qqqܿ_SL[ZZR)'P%fI(((/p8UJ37@! CK?ıc3k@*3l^W7/rC|>\.kp d2ѡG… d14gJ6F ҲSN. IIIE fǎ$._6)}kvvammM\oo/縸8RE߾} ǃl6zJ5dNAOh8z(fggL`ky[$f[[[qՅZ~;::> /^?Ib8jع!:u ~yl0}I\.㏺bTOi333FWZ[ZeV_Z^>_HNNV\~]oD^vO8@fC} xI+%le,K رcc41Vl5O *Zgg?TY!IqNTXVlmmQ+V?;hƑ#BW5OyGDݽ{%%%x-9BhM+/.wJޤi%WЮWkPG~ /,, ..NJK!ݲj:677|e͛7UCWӉDVѣG@'=>qb⫊i@p6C.I>l-G%I'X,ZdlYY9B6ôԴ :|ۻ|N:Sm x qvXV[;xkmϩ:UQk8"V"!9?@H6kZ%&A}${Gou:OҜ9spB=B$߿__#^81ܹsUTTd}_o) JO>yE|-^,YO?5D+o/jժUFғ>^~uZa#k޻wO7o6oެ[j֭ʴpطo}JѡUV)''G7n 2 IDAT|L ~eZ[[On'6 ܟhoEz½^vӼ<-^:iƍC^z鯂6{;%zFZsWWNg$ﺬ,ZJn Y'eC0O)ߗғBVH6LkjjҜ9sTYYpkeڡCTTTR lٲE۷o[\UWWXRߓp9O>Qvvv{znSo#=2"# ܟHgǎJMMՔ)S)))we޶ Ά2NoOM ŋb}yJ2O?Ւ%K4o޼%Lx<裏4g޳ \z[NoΝ '_r-5yo~ȷP84' C{(2io1,]F]G b/92 TN:l QҚ ӧkzWooYp1ڽ{uي+dѡCzsԩ!Tw3nHrd9п\~ݸqC JLLRkk|BID%%%)33SÇ .݃TbbF p$ nۺMnnn_Ѐ@nHrd9п'N$IV Ҍ1p_ϧaÆiȑa?"_SS۷+77WJeц x@8qbnQ,Y"cJKK_Ç5ur=9Ξ=+c|MIeѶmt…yal=ztvE 6L/V\i~1ڱcGuG[Sw6i$2߯#GjĈ|ǏիW+;;[jkkk"!ˑ@z2rYH'|"cmΞ=#G ` ֡ǎ1|R vZcsNI?ϐAh|Zdx ݺu+ꚺ >ݻwCsvJJJرcŋG|nHrd9п~UfQgg#Fhʕ͵ `III5kucǎIDv)c1ccJ:;;5a\|k.< F 13˴Xyr֠i:w~b}mCعs8124\,@ zvY ?~_gϞ%#CL#ˑx jxBX tvv20A,<Oi74YĠL;s|> <<:::b>OgΜ!#CL#ˑx zvi+c&hރŋ5kU^^.áڈzr8~kkkp8b?;~ϟGIp8~g1did9@!=g1b˴2y^=LjÆ r8r8JMMՂ sN=zHCvk=zt@[( a^WeeeئMɓ'O`Zzmۦ6ݽ{W"&ZkjjwĒ{Ԥٟ,??_NYy<eY,7Pr LX{{%d~z-YDUUUz[M2E惩P%%%}M+uuu=@X y24Yn #@l zVZZ.]=H455izwdͦM4|8qB>}222͛!i˖-Z~222TXX&\.߿_JKKٳcv>Ԏ;4-YD}ȾwސK}Qe˖=Z[[w5TZZ5]tIC***|`]rp8b]vQX^k֬ӵ`+ Em ݾ}[VҴiӴl2ݻw/:WXa]\\l}=:sEjԩ!=iӦM5k233[a`ۚh߾}az)-\PSLQ^^~GH"m1XΝ;{<Ɔ}9sgy^[-ںy1T4Ymrd9g^ uQ|iYp\*XN~?>~gnݺm gݾ}:kkkƍڲefΜ)uZ"=|-)z;f͚{c?~v >ᄈ`8HOiiu]II Bq}ݺuKӦM 7oԔ)Sm=5eInΜ9#u,ڶzh@ct钦M&mݮT^hG[74`!eee1%E[7`͜9S6lٳgCNtjʕ6mCN"/LzrHÇ~ eee> `~_ܹs5e-\P'N_ihÇ5c Ou̙3`_κ-[7}@<eY,G#ˑ eڱcc555Q !+8 1O?1ƂCXSS?~cǎeY,˴K.oۭGM.K>T}}tZS[[؜tI}>|(˥6=z@nUTTrސ"师e͛7t:tp,Nf 7x~9NYϦh9.bޮJ9Ny<[wvuѣGjhhWu]WLp A=Rkk9B`y<9NUVV#f =Cc'E,;VEEaa(SQQid9aaLe`e`e`e`e`e`e`e`e`e`e`e`e`e`e`e`e`e`e`e`S271$$$hĉ/ZwݼyS͘1#K{%%%2hŊ^ @]M0ACU}7ao3eo!cNSݞ2mPxeL$~mܸQ-X m~nnw-2mLe$}2(55UNS:{x W_}U|rc_Z͕1FG$9sF9r:IԩSC5|$K/kݺu8i7nШQ;wD… eݻ}XB:tUUULܹSƍӄ {n~kH[jƌ5jƎ˗Ç2e1z$iҥ2ƨXw^cK{[[o_TFFS>k,ƌ#cL2L-^Xz/*11Qn[NS1b,X !c$IΝӸqdў={rڪd%&&*??_iii2ƨ0.Ӛ5~x1Bu1F0n81B=/P%&&*))I>|xHq766j>|5͟?}1ڻw$_1Fo$1:q|>fΜiқct%k_Wj„ 2hĉz7QxΙVVVf,}jnW) IDATn֣GB4ǣ$%%%S/_8I]vIy׬ut?s׮]2hr\jhhرceуz<@6m4eggf_n_1t(^xݸqC/?N:>fuuuiZt|>_ \|e_WJJl+%%EG_o^@ɘ.|>͟?2 ĥ>1F/o,LZZZz\;/O>1FەW^yiV걮\p_yTUUe]ogK,1F/dÇ>o2pX|> 6L#GK/aÆiܹڵk?~l%&&jĉVYT[`(7ol7Pw͛O |Wi .=9ӂG+ \vIci&͝;WÆ Sss$ŋ6l^|E\DT2-pm۶… !|N2-p8 R?.cV^l%&&-[6|p=Z~njjҖ-[ouvvں>)kѪ1F~aHyL;vlu J*I_5eKVutthњ4ic]vZcsNION[v]I>8$={VGQKKKuʴI&:\ٳѡ$;V9 i aǎfq?~\Vɓ'%=yƏ/c*++^/I;w1Fcƌٳdŋ0'9{ly^|>0Z4oL~6Ic8`](|k=ODIRzzUoeZRRf͚e}cl=s$(77W/ѣKҵk׬~G,c4j(8|1cyEoE-kxbrĉ4iui j8L2!!*{$vkѢE5jt1O-R(99Y]|YKgVRR^~eXBaٽL>(2XDڛ@ 6(99Y>=C)--MGVRR222B zgtǏ$ 477wkv_^kIs͛$M6MŔi .E-2hɒ% i2 4ʴ>صk&N(c"nK(`̙5j֬Yl(e`e`e`e`e`e`S2񨺺Z*//gaaLEEn޼Au 4ǣJ9Naa2mmmr:xLVmmn<:;;aa(xvU[[+N_"i.]RKKyiCWWWZZZTQQE,x(񨼼(K)RJ)RJ)ݻ>(z)<طo>??^xRJ)RJ)Rz?<ۇڿ?CRJ)RJ)o?z zG/C)RJ)RJG}ڻw/yJ)RJ)RJ[w^4J)RJ)RJ#cRJ)RJ)Ti_E)RJ)RJ)} (RJ)RJ)]iRJ)RJ).S=ORJ)RJ)iRJ)RJ).S4J)RJ)RJ?RJ)RJ)RJB4J)RJ)RJ{~ L /ĺupeK_x }v?q~n\q ۍݻwM+;{PJ)RJ)~cs=kbrrwP.q7sŦMpKfn>G}n{/>Ey\p/Ç/߮P(oǥ^: l;cwߍL&s='pM7qO>$>kcկСC1W\r%83xxٿqEᢋ.—e|Ƨ>%T*ᦛn9眃_WX\\׾5\z83199ZwqرcǒmW]u;{'qW /ą^k/;]RJ)RJ){Ӷn݊O?_b:|xi:t>sݻcfffH$l6o} k׮ŵ^{hǴø+PTn㮺*s9Wbknbq1gy&n6ex<s9xG[<:,|Ν;qF\|oi`_|1Z۷^{-N;4|_F\F>톢(yˍioww!|>[_װcLOOfLRJ)RJ)}YL{GvvZx<|͸Lz/aݺuя~ZNji'o+y~vLKRK>nڵk裏HR/_YRIO`ݺueϞ=c?묳.b<8|0~avi;c:h4ۖ{FZ?3G?ʘF)RJ)RvСwعs'oߎ .n333Cn7n7.28p`5.bvmKp KGlܸᗿ nsϛBO?_=:B5kg]r?}okKf˖/| Fۭݱc֬Ygyg;s1gwwm3czwyٿ J)RJ) '1mqqSyg1??3<li֭׿un|[ZrbڱZ^Llo?0\.СCOh4|#8p12(o馛poiz.֬Y]vv7 :~L+%\xw0QJ)RJ)$ծ]3jǎ8tpӏb'~ӟꑭ\+OS=\MNN1;ĩɹ_x|;駟(ӎ}zipxqcZ4}G1;8d 7F)RJ)RW{As9H$8x}< 6l׋EݻW_jXnZ.v_|/~ظqq/~ }kg'x po߻w/8 | _$Iҏyq[o}r hOse]C=SO=wqǒh4O}S: zYgp8쿻pݨT*㢋.y]PJ)RJ)O=i( pݸq7.nlذvկ~k֬'ޫя~|;wW_ ۭǧ_W8SH$sN|%\s97nO~,.."O$ hgu~,$ }ٸ۱sNl޼^z(vw5`ڵQ*yVU׋( eSSSQL;x ֭[o;v,>1QJ)RJ),-..{A8ƅ^O?Ǒfq/7z _|1|><Ŵxxs.R|\2V(ww830==FV|#߿_gy&^/}ٿ婧g?Y\pKo~~;<[g}_WO~gq6mڴdmqq> |>8 \~woŴE1-wUW /G>|sC^gLRJ)RJ)}zL;x }}gK?2(RJ)RJ;1o>O8p'p7ߏRJ)RJ)38p333KQq= aڵx衇G)RJ)RJߙzL;p#giG?z߿RJ)RJ)}2QJ)RJ)RL(RJ)RJ)]zL{g)RJ)RJ)o!cRJ)RJ)˔1RJ)RJ)eǴgyRJ)RJ)R2QJ)RJ)RLF)RJ)RJ)}{?w^⋔RJ)RJ)ҷPiB!B!1B!B!e˜F!B!B2aL#B!B!dmL;r݋={`ݔRJ)RJ)tϞ=xGK/u1ȑ#x~KxW)RJ)RJ)K/~> 9c޽{O;J)RJ)RJ;ȑ#ȗ_~{?e1=8|᯼ RJ)RJ)'Hve{uAUUٳǴݻw_ī_B!B!A䯍i7x#v}kmL;rC!B!B9kcu]ǴW^yo!B!BF!B!B2aL#B!B!d0B!B!L!B!BY&iB!B!,4B!B!B c!B!B!˄1B!B!erǴgX~=욄B!B!>r-ؼy36n܈=.!B!B!o1oLJ>7ybګ ǃylذ{{r]B!B!B&Ԏ i`8p>nkX~={1=qW駟֏7iLNN"?^-χo|xߓM!B!BNL2vP{cH@Ŵ[o|PTi&71/~ <Ø??|I|ӟF\~;zx<_\s nwsr9x&B!B!'&ouϴㅴLL{%o>_?яׯ_lmݘ /xq}ȑ#%h<&''믿N!B!BNLNٳׯ믿H$Ocھ}s>=d2={?!6l؀@ ~z:t]wB!B!rbrƴX~=>OcÆ ذa֯_͛7_csi+ߎP(l6{s{7kN!B!BNLNȘk[n?{}ai/P} ^s ׋@ oSH !B!Bȉ !B!By?`L#B!B!d0B!B!L!B!BY&c<_|o!B!Bnbڞ={{ͷi<zꩿo"B!B!o‘#Gk.=-oeL{饗駟rOB!B!rBׄ뮻$_>52ݻ{ݻ)RJ)RJ)=!|';>駟!mY1B!B!"`L#B!B!d0B!B!L!B!BY&iB!B!,4B!B!B c!B!B!˄1B!B!e˜F!B!B2aL#B!B!d0B!B!L!#h]}/*:7b<:6й59gEtm,¸YB"e\BTl*YBd 'm.kwK*cdK\i ÔL'o܁:=ut_TK0LI8y,̳ )Se 6bFB3t{dtMе:%2]aΙN..O1]s ;`N pN|%%t{v7.P&ƙL ":|EENS[B ],! f `֠[K K,K000{ ˰x0u|%XBUzPP@ V_ 6_ @(>y|; {pVX;a ` ډHΥ.7\9t'"E8#pwWp';a {h\2\q x;Gp PP @ <*YLiASgb駘(3%BeCbڎi~ sTѧK@ gX#p4YeO xH֐GgOBG艖)-8Wͣ'|'z"أ 5{h, -荗/7RDOhq m#a@B@,&bbb[oXq q }RU &+􉳡tb* %$"$ഈ6b0UFvx?00SmCI-eTE`KIT1p+u(%a$-&FKMITZhI1lq16݉xf!60B!'h]d*øYBS65Y)EYS&_E^iݳUtkq*:=Uf0z2+>U5gk guL2l>U`l/ H0ʰŲH n@Uf H0*+037) {+0 00$Ut=L,ʒ " 2 L󰇫0ʰD*p+pe a~Ml2* /b\Q[A.'RSm;u"E8p rLWo4, IDATz%qhΈeD dY,ce%LW1b@8v8YHJ`\PTiI ="eŴ@Je &+LVDj1-l#@FtYeɤxNc Vfd&%fK1ŷ9"RƓ EkO'TPQjc)<63By!OFƂ:6f%tod+6`K&OK& 3Ut̊X5S*:UEt6kAkVNW0S0TpJxqmm4)?61`4UXdIU%c6*c8SF|-B! iB Ċ%4#batMuLqG)t0(K.g*0Ίiqlf0)0jm]3Xgdtx2* :%X*3L^1Ef{蚩QUWqO᭢K.2*L~f O+`T)3 쫠/3m0%8UX|6ef UE\&Ԝ!v@U KsT+'uemL5X32*,a c,>/$a W` 2BeUp0G*prjpdl*z" \p pkk B錕a 3VF_LBO [HQ Q Xz D -%8h q q }) 2zS2\"3Ug"%LVLc ?#ۆ2FGedEDH\9q/ᬈfÙ  ŹyYqhVPFLfeLU1S0b<]Ū,ɴS1V0Vжrt*sURT1U7,~[҂ZB2V"2PB!|aL#BN N.SӲӺ%}抩 5ffls*f]2Vx*0v<Oڴhd OL}2L3biq?3Wi~{5WD7Wͧ篡'ދgx0yxyeX L f OaJ*EH V 4ST#RCg k-,Q` Uሩb_#R=,wJpeQf ˰Gk0$Ce=F"zę3U` @g UQ;5UB")b-*** WODW`Kጔ0a?^A"-$$^Seqz%2.+"+z,Oi-ee e g œT#ĴX94WHJXVʼlCsYNXNK.L@kOSڶs51QM1>Wø4[XѴ^k/-SCxd[cYY,GZ-ڎnB`ØF!@69+pgk0N0Nev20̈]3Kxv͈Pf{uye5X<^f>YE4ef WAO[C;3Uu|5X *,l'+&Ԍ>ܳ+f+XW\#q2s*ƵeDld0zD[Fm(4B!c!ra{VI螖aQ`Q==SBZ S8'u<5tO0(yE2t 4-|5=Y5* A}-ڂ |5=*l:^@V-u| qůj۵k~``@@`@ [SsH!Z5V``k{S K WgsGvϴ{L%ZCohH X]R%갆e8b*1={X9n WDf-9c"YU1QJRN{T=^+@_ K\9 WW USኈ1k G Whр挖֓/^3!a QE_67^AoRsZ+Q#U3UA_JF_*q*IIO6RП)b,+/S+S@VFRHF`FL  g3`(wr\MLil4+i3-̫G5̫XZLkkUsuq62yVͩX=b,_\+4vP1&"ht+jpXU0qʌ~k/3By!ÌdLW`Q5#[C GɫU1Ef0Va`&L ~Li!=yvlPkOK92,Z\ g aӖ{Zuuq^@o 5 6a a 6`5g0TCMuؽ*lA CsH^2f ɰhsDzL3eXcb=*&~ UΠ [T5V` ˰D$3k;:q*YXl3jShX xgX{SH4z+++(b[la{L3VGO\ yTΘGbcKv$*IPg xIIi= 25 -#c SCZ#-Ֆrf32jiCY#pV ks5jNՖvgeS1S0֞ӖfNhk,/OhaֆX9|Snmab;U1іW1W*W]\[bNRU겪&VjXimU=@,M]aUF񜂕4B!c!r^Q`0zj0蚕h[ѧlі|Wa0fю30k*-:ڄէMiK:m65؀嚁:LLɲP`F-YBMtb'Ԃ%Ԁ_%Ԁ5Ԅ%܄)Ԁ9X-Ԅ-k'i0sU8B5B ,Q gk{D-Sa ˰kIJ4a+0G uql\5""+^]pTb*:qd=X㪸ZLk`Oጩp*p&pjp&'%pT8u &Zk'ـ-–ÙjUVזij}OH =ɪPJ+ZPJAORD3 3"UfkO+ȩ)(J+N+)*Crir 2 SU1aX;vL P+S1Wms*TLDZ~4nk;1W\ ځ=ao`"_D^|\C lڹ[Rǚ- u:*N711bVeę{NŸv\ ruko01WDFL pXU0?XM֞r#B4B!ꭣ˧kQ=-o@^ X-XMX-C- CMtU>f7`7`0dk66mb ["ialhiv\&LUc5uB-؂b5i&ڱXSLTأuFDU#5ih [kc [L#,b"VYfa g[[k[.ZGD,7`pZp%D8O4LH5ћhh^Џqhoָ lzsw7B_{[L3^G_TKxL )qq/^P&ߞ8Ӗn$DPWЛџVЯ-J01Q18ї1?[CF2XmhX9atN_uΩYElK?>WxN|CmՖ:VomRΉvXV֏kzP[\m-S&_3Wmkh61>wN;߄;A-wtmVZT9Um/_%B4B!{ViFA.o  .:WljaM,4000[B_ PƠ8^LuC *XnjZB =YۓfZTs+pEvQj'؂%X9҄=܂=܂-Ԅ!z4?DЫ{U"*z"uXc* 1q4WX=ހ=¬E2{[[gz֨d,-ªM9-8MX " zm5Z%'ۓMM3^GozN5žl™lolžl'!b5.Ñj¡}wW[W%o7ф+V3Lc0ݻL[鈗ћdZ?Kb-Y,`(]pFpF՗yek2X9b +OΫʋ2WĶVnmXᬂ|#[ױ2?60!OŴ-7cD^{0f{KֽǴU&[ҷOn_i[[8mK^Uۛ"mjۮXĴ[*i:lia͖NݺU[D[3W_:}!1B90{pZSa 4`8&YMtzq_4o FP ƀi.-b= ]~`,{[E` 7k{ -,i4a` i1{ݰ[pDLQ]p[p焚pD`k;-=ڵf5a`6a(&b-ڀ1,kO4% h)3gRLYb5.%joixHfH4ЫM9-Mr $Tgv' ْ&RJЛ =ym̑hh?ބ#EI6GTһJ4Sїj77!"П̕//stl SPdU,L+*H+0;8WGߜ-M 1a8[PN؟h#ц*r Ʒ6}uSP\cۚĶFrk2j-BӶPfhmMĖVok½M5W,"x5f{ kšmzK m-v.=ROkp6m~UZD[ENԃ;Ė:n_MLdXJ<0a͖N7Z{A۱-B! iB [/jZcf` `a &djZЏ5`` /Z-񬽬2 .8"wAL_Ђ\tA\֞"sFw]-K6s[[|C cqZ4;;8- pF}آ3Z\3Gk&0%Ś0p&N9Rb̢-t$ k;rT8-8MؒM5Iq KWބx=ل+ ImZ,%u-Q3BOF8WTKMЛhGuX \ N-9Msw/` Wd =DŽ4gFUۛ8mXse݅W۷ֱz[0 gķlo鶧َc؉[6W*ɺxHªmK'!1B90t#_)(B-صc` ,a-z]zL3[vX= pD-{O鯱]'56戈],'v]p5hD4.3҂M[&o.#-XuaL-ڀ5т%M3)-р#ր5^%!^Xb*ZLI-L i\]Ɉ{ dhT T}Iq4g{4G)삣@x}i1Lc2w/)&wj7 }ٻJГ^`jCk' x]IЛj/]G_FLS gT$l }sGid}l01mpzt-WxͩzHkǴ|99U0""&VnmK:'4rKd*eGs/ލ7w\?ƕrzM nmMW[Np_ل:^©W4qofǾoG5[b­X rǞzwaLliӶpVmkb&VmKY'6r'B! c!ra{踮^ƾvnM" `0 )RbYq{8-0W,ˎ%N\dK%QX}03NSb?3/yk9g} {1ns$?{d=2&8qRY6m1gPϺ0}Ki]_Iӳ,ǼDzt=gYsS]Ks6+kQ-KZA,-DlM[ IDAT2QZ)ƅL̠zTntEkyH-;[g(j0Kc)3H%)G;F7kpCt;"e&%zSt3oxA!>:WZ|:St9ڮkQI;9ǥ8sccc^wn8ݎqzqXʝם9NCȵ14Δ\Ii\i\sySwuwP38S)ԮP$\+E#f(ڙFLqqBYڻ^k {֠ٽG4O<ӻWc,LD  $ӌ5}ʳ8p _$؂ƱDZdYkxb 盯n[/ }_I<Ϯgg?]WY8L% ឈTI i^I3_z67Yx~iQ())ZKhLDϵX|8sA`"~Qd-ܯ/1+)|yL}e'vydGt?kYɳe>1D(H `գ)8={0Gײ D3r!3SJu?&J83{lyCz;bTڜ󖧸ѵ e0oyljm8jy sμqz2gxqz9>+# *Sjl٦TY-ɴۇ@qTʕʕFʠvgPyTZOVTZ֓d"#}SˣfuQ{D'+;{h%ոhܙgO c&Z̒xZ荦Y޻L.$άqq14jVK E_ =HccʧxS"Si b~t1 &ײ <\o83],g3y.A^8Lu)[ϑ0/yy_Vw|y_ 3mW2ma4hFi F,XIst7o&:rMlҺSlwRP[ Y0H)2MAAAAA@i sb,]f=2lc"hF3I8-msg̋4 ]Z{YyٛK.s]{ycitCBJ˒kH\(zhh fPI{ynN3w0M#Kϐ苦rʢ̢{Ezg-Ϣʣs3,Rj۝e1Ԏ 4jw4ݞ *H|:h=9uK}Ěn)]ΠRl\Bi9z)P^wޡq4,=4jwonaRΛCJvugu嘻| 3ƛEɠr͊47ʙ=Fq'6;93λSGJ$3x`舵Z3\3830TBf1Ȧu:;\eU*r(<ʚu'xu_T ~NG,N3&E,Bc,eYD 9',3k"iyg7E%ivcma$-J;壝9ATAAAAAAE)(((((B1Y~u?R}$EyYu/=gPyBu=*kH-((ʉ5D`pGAi=CyQRM(qʣrd!=[zO# eѸPSiQz}ynHuJI8o/Ɨ֗דɣweP{y=)LA7iEe?a&`Wk2HRlKHdZ,K8M,K_Kc6̆r뎼Nvr[O B!˂b1Hc IDr#ɬ4(;pK854< E2Ic f b 4('rܕ"4 _N&y3Taݱl<~/1}ujP6Cvێl>z.]΁ oeix¦dgQ~3$iwOx{mN t/*Y"Y}̏Ip~ΒwK,X,pH;oN-NjlI%¸8]QPPPPPP-(2MAAAAABHnqZϳ6Dsi{sꎓ e%7vʎ6sMG.{S7`l1V7r ?7^S/k/L/Z|m4WfW~Lct\Q䮑wLKkHu;gQnvnN:Cii7!>OT3dENX:RdH%w% ܝ,r$qHeKGr蝉P ϡ0C% 3X%Qbg02Xn(3s0>0&KXK4{s-%7Gs9,¯q_g qɒj ck/GyM)tC]ujJ秩M|ovU m=Aeif:AYN3y,1%vS},fQj0Żz]X71T`V1K5HS"Hs,4k֟I `K0=Hɉj4HSX8`frBEriG EXb9l,E>],/$ cY_=?̂.ݚwbwo=>l4_.=3k<_ɳ])+xeN"-3䶞}gh;M96#z2gU¶lW`;{g5vƶWP;wɽgy–d7clQқfo;Me)^gKl;~/{4}Ƚ 6rߊw}+*;Z2rHIKGܽ]%JY:Zb$p 4[PgEZWU]fH]E,vH)WC멡uUU*O IDAT43h n!L޺HILb{k+f!PCUWUZ(U)VA abĚWa;aT0y}5,FIie42@ u_b Vk!TEU1Be V]@Z(t:Xԙ_B, 1Z>XiQf "UlA!4f })XbD1T>sX","J DE٦=QSgHs4=-=Qc1E3أ9Db%UE"EWV>k|zOY81 1~ʖWYٻ/=dࡥ4q_6|~/w|q/w^^nSYm$ݧh;Mk裶֙+7mWu2{]c٫Pz*\d7X{N3,-'H;JjQқQ~3lc|αy [N|Ne |2}*+ܽ]^Q䞕%]QK@-r"KG,sW"'(F3 E)(((((BeYe]"qVuUuѺ<5Ԟ**W)J,u^! ^3y"aepW1y똼ut^I*|u iϦƼ IfdfV똤}tz]ȱ )V3Msf_ [>7bx}Be@]1\` f ׅ< UGjaL` J,TGgO9\ 1E`UЅzs-P @c"0K)\0GCLAQrjT1Hȳ1TIB9tj7ȴ(df$byyXc9Nsk'_x>w_hwt-o@w\9'tuvwbG/3~jl~ W|e/Vwdښ GIm9Nf1[S1C} &dYH czqLH]Y>#JA\eKl:| _gzY{Py֓$daEylދvzJS|  yjhen!t:zWU$<54**oUfjw^o]77Wc ,,L_mVzIb1 :F1PCtD1(?!0! g@]izS`V;ut8h P6TA(c VEjB#u x`1j̡HC.F'dfTX#IWI2S>"5jإę9ZaDl f԰EjXcu R%ZCYcuQ6zT+X"%ڍIm>BnQێS5Ğ4Im9vQig=w0( {n< ={ݧ6$ϛ]b6Hq ^k~$LGŧS5쉪<|Wx*(2>^ၕex}+JD"x`"~Qd-]G몣wѸEiSC窢s ihzOu`c]'Pu4m&I2c1 No 5g喯1!f 40= L&y7hbm7:{u̡f|3 Lp CDts~cl`0&0g!(RbpUPbM4B3BU,1!ьHY5 HMp8kX5Le̒ C,Px [lK=RD{lBNK#̑ hYxS4G_9a Q rGXڂXQixfdKd&;¿Ex/mwz CKwxoP{?nY!>`}/-y_s䯆 ˞/yqHwV?cʎcweڍ#vĮӗs*ϿɁׯs9;{87%Ԧ8`)t L~uG`7;~ޠ_$$| |<'Ouz_->r?{ƒ_Uy xʃ_-sW|<>^'"n!̮FwG1oB2o]̼Bu~}OTx:ZϭxwoFAAAAAAᷠ4[5;! 3ou`^k@77htm 9%'MLƬ kK9ؔu)鴠qP KdsX̖$`k6}i$,Iys1m}B]oIB#ӬX spSq0%y%%Ӭ&Hc-c70Ih]J0GkBlE-,&X}!XB'q5q!LƘn_)FFbML:xK1QkHu4GXcuh=^!^+2( ,xfrk}ɢ{a,X0RJş?~~j ~f {|/!/_yͳ<_&JkcPvjfغYwNr2NS==O^bKl?u'ʣ#Ӷψ `Y&;tgpXDKAyi^kk61l>xU53*+?}HٯoN> *|Y'_Bj߂"n!Iok L`7yj} f@ $,I&$@cx]9:+1hs9$bz]Ȯ`C,:m$ˬ@[-ޑ$-Ԥ/l`Eژ#jb6'1 ,6p kD6cX8KdRN"-9cŚX *xS!*c ,XkL6s9k`K4D&[Xuh{\F'F5oHjXb-ָfX]cOtZG#uI, vi*EbUL"}*h{@@H_4QqmD{4@@\~I-ZYchE+k1Ze x~&ZAtD;.SYƻ?|C|[Ļ?^w1??8xI~e= 7li+vRk/k6!8'h=Ec)rkы[_z͖~nsG.2<}h=KsY{sƞ34}(}w\ds;p_v{woFAAAAAAᷠ4[3Dob0ZXMp cj5-9f0F&1DCMlRDmiN"45ږo [G0[bSҳfᆔB{M,&$$ؔN/6-sv寮%߲FBXؓS" D hSL3S.%ZXb Id#-ԑ*6!stBN􍴱''efOL`ױ4B''޳%Þh? 9%^ D{$g}}2es~$GJXys`Tx%EmH#eN Ycc~;7SZ?Y>m?w?~` '?|;ܾm( G_XO|~vg6\ywbc7!8]'TaQrRgG.ȣ$,.ˣscp ],>:{SYwky&oL5q7_ pڛN^cWU8|̶t ;OɎv N_QPPPPPPm(2MAAAAAlc0[肓} 6`9BDjbMb Mb 0HLp[< 1ZB IPY;)2I}i)1Xm!b,,eNaK&鋵wi)l64%2MJ٣SbPFgj(Oom[MlDc\$,Iqm7eAfKDClsndsBi?:%4{-Ėh&U l7t&dS5QFL$:2DѲ,FJIy.0E2WTX0RUVX8Rdg>A'9 ռO^X{O}og駌o#5ޕw]e{޻_/Vz)J~VSIv3ϕ5zŽr#ozq蔀?,dyvfy/m9j;4Q}dսg)>Me ;N0BMgy8|M.ue腷8r yɷ8t:/_[p[?woFAAAAAAᷠ4[)0)jc 0Z"4Ѕ[M,ILH[ȴpKh[x4Pd-,<=ҞbI,s[T C`[l:Nzmcd->-:=nbD@t[|Vu$5mcM%*ۢmI#"f$lIlx K}tDD[\e&cc92ƘĒd&I$ficit5.=9=1I_E_r{e1)ٓؓ-F$?:=žlaK gm l`I6OǛ'WY0‚,:2o=Ydh"Fx;10Zf:wYb/ĒgFx>noHwtOWC=ğ}w7#<e_Y|T;}5?\^UXd7~9W&zRP}L~G/u,Ӧ$ZGm9vm'葶UJ}sꮓwc)&v'l:JniLacw ]n=Fjq^>uGX ^`or-x\|M.3/sU~+0s"kpU._߆"n!64PSP KxZ1)q 60Eۘ“"m)6ФHFiչoHǞX'bQ!ea/6M|)~!Hǧ5>=oӗ\[!¬?>@lJzoR.K[4$$D{'625Ѧt4(l`I1%XSL a,ƾbJ$ 6)zeI6MH {RdĤ F[y60:IH&}#-FZX lɖw}t GOnJuRi++,XYe*WT& d4_Ͱ$2 ||Cܦ0^3Ϡ㿿ߏò9sXǸ}2'xmX5ek< |?<Ϳzo<[/kx}WHo>Jq }:t^.|ɉs#|"f.vSzNk{OQqTPvjOPs=o!'Hm!4m')8Mi ϐ<[N2u2[N^e8zM\ĵk|׮s[} \Ej|Cs[3 E)(((((BS-Li,)́6d:) Hu52)92)R^ȯN2[l t/&-;bSf ^i}Rξ:biDȱuXGџe=>ȆDZG $Ok3m x3:mAb"K%ؓSXSF'̖h hҷb"}f?1Ib:2uy6f`4+mai?*8IO?:=b`E J=9wCaaȶ:(rp6& 3dйY&$9G9[ђ (4UMXm3MGINS/y>)&iދymͻ)P%_zR>dS܋\Rf՛P,{+xm-Su<8]SλlνwŔLy_ɋ?re.t E5Ror~=/\{37埱,y0}}׷ygw'Xӛ30]av&9p5gBw(I=|l fKBG?p7$'c~ˉoűߒ}K俾όw 4ԗ\L{ʹfʋBl"S9{Ŵ4x>rxf?H|$o%r+ɛ]lb'"'Ӗm䥠K7\/qࡠK)čчKbAM[di-gn ^$7{k IDATW^F> },v!YBঠtN\s.r袠{M..&A7kr 6| m'Iذ`M^|<3"iylpM=5"Sܦ^]4orOn<;+$*'6>Er܏$ T#G܋\-H(P@iT"[P<ԫQ.ˤ;7rr?䬋_Msgs*n: <2 *qC5𤲙gZVRR+?7{1|ıkA >>8Cl =2Sl R8ʁ#8":N8xO8qA&7zq'9Ķ!6GvE&ٞi[l ;xM 6gK|75C|?͑8q0qp_r_p_p25Ö|L1e7EnnzE-0miKܺD.r2u%nr]!.r<̽L˼FOZӚAg9 3Kn $=44 I2IOpެmp s-q%1@sQF쥠ًD'?-$K\H<œFf/i!&kli07kp&_I3ROڝ`9SrD&/sD ixd ~EʉD$O$GٗM5JK30Gr P9D\JHINU'Wi^T{j##_z?ʽȕ{)THs%TwQHۘrxg_{Ǟq+GRU4E!?<ξ,κg䜟qsXyi~}eO O5^L==:c _/eo2-vY%Me*#2K~)ٕčL@$<'Qk0EN;+H^KFNeYԇɃѓuf/ Bf~+?-$jMN.LQX~:i&(zʝgSx<4 Z Ӛ)&Uqvv\?yj'9~Ӻ׸LO҅TBq#zS;U+ߟ5'y^j UK'y&@K=7|T(wy>ȕS=OQu1W+/.eY}/u4*_ŗ~1~afͭZBQ~zTL/O*ξ*K~DɜムOܻXvGxgGkS%x &?p,+vcOlBH%s`=c4k;#gA&Q⬠+n6I̘䥠=ҏ\?-94*@AO$H7ڇT!ɉ\EEҐ_*_vT@Q죨هL)TF}H5~*f=&/ƴ|Sy}B+g:1v3M"G!WfZ:6\ MA JhiVBdi}4n$* ^j/ryuSFqStQ"W$ODq#U%W1_B Q:ϐiEjPSE>\s^!?@At{)sr _y_[Q.K6PiAR[:quq5qu G{'x[86^ylo.湗gxx9skƫ8 nT\^4?e;"7eKݡ1v&8Ķ!C3dz =3wE-|MQ6F&l c]Ѭ<h.8,2#+,˴tf$W^ҦF?M~r~r~}4]nL ,+heeiɖA dC#i#O姰9a&2e2pެ;%ҕe*/*/y(H^$J/i&U(RH,/\W}hH>A5;jH5(?dZy:/S. tsZo%@#OF#Ii I\ u^i&z4\ɶvʵZ:/?rx+lv"WHNZ7rB}/E^ =ْke!{!nb6? sT1y%z-|9몳"ވ5kc[m|vcs{f̝nͽ\/."οl~qٗ 9?G9Aw2#Aa3 Q6DzIQ6y;4ƶ!Gek;:h"c[x́16zakAֻYξ7)H3aeKp"{+ȸA>ʴ5)>OeF vDĜ{K<3}_Л:qƮD&"""""!@b~5i'w0JAA5oMAp4Y/+  MK7HNKI~ ͧ%++}Ad Ru<ԱTkrMʳ3%SkH^ =B2M@$HF22@QOH5)h@%MA$\PDڇD 7@^ڃ\<ӭROM֛Vh v#xu~ t|]YI&z){j]ȵNJu^Ju^i4#$LqgL:CI(ovsS z<ȵnd2M?2moV{)X{–za@sjzO/[纼q-rm\{ӯ!S…ky[9?׿XjjrRV+fvwf6t.7\~#JqmŜ1wr/n^v?} a40k݂Zdkn!Q% Զp&6tFfkp.]6xɾ9sw ck.<9'|ܓӾlm[xm 9/q~IH`W(;cGO\DDDDD:L!i Gi$Z~cIsIs >$M~r|6 L* Si7C5(n!igYHhA"QjBj‚HbL fE\Bhj=y2M&D O+<'SP5!r]"Mҏ\PX%H6/TBm.H>D&H&L)ȷB }UB+L@'_-dZRZ֋DVN8ψ3O΋\˖%DdΛM TBB\Ɋ"B"CއDA"$Պ ~>>B@iHdEz/:-.亞l2M;\bC/-}̰Pcr1ٗr:T|ɂ"GmWB7pD rWrkOi7p%?+@&EOoYM^[빥\9Ӫ.\6&:{Ye$7'= >KY!l ?f0[#t?Ė0tSl 3:WT6k\Cl=:PVwg~?qO5IaC$oG>k$Α/M}qG-2Ɏؑ3#""""""2MDDDDDD^Hi i "i 9DFD SД(,*DD #QC4aRuU2\!OD S*HAdReBe"M&L"OG gXaX !U(Ԇ)҄) ")PHUP٠ T!j}EH 2IlLHTt ZBHuA+B2@E o "3}(5Q\TZ'~tN[ܔiT1eCBXXXHG"5xSdGGih9@^;=i1<xm7d<(pS\7e*\yť'[n=/_LEm%klD^Ktw>PW-ۼۊD>bo`xsOvXe7˷xm7ʻ{?·|t ')'5:0|=;?F0kɬFW*2mwkq*>%pu?/ep շ]rsyzG/.^t/OnY9ʹʷx:Ȯ+ʙEXl}Yꏜ폙n}&ﮤ}6VM0o^aLI:Slp3i]ai5N i_OӾd6ia|fl O54&(c 22mkuna}-Aa]8909}B gG9LW`OcukOGi""""""? 2,_&_F m!QSSijbHaP6yvzL "Ficg+DhHu 5i uQQuQQ(}!JIKC"]$+2i"].TF%0(r}Bu"]$J+G)C>LAWVyV8/ևb-3'sІ) e6k L/T)F1$/3}>D1JaKDt2GI"n)n PeKMOyK@g o#5uU-NZw=̳;kse<;̵ePgPcPeQa PQFatQbtRjBarRdTc%UT+RnƼr>f YT~|rodbο?q Ws˹zVsquS6Fc l5/KY)£?sjZa}jW^:1>:cM A8lLt "MhO??]#-Ӫ3j^ksRorSbRjrSjqSdGqK&'e7 2 2>f<Ѥ㖩7Ryo[9z1cҟpɥgs v yr/yyqesYEz.b\~E;ݛx{Ww2Nh~|0·|tPH}?Mmtek5I'ԙʶc~қbV-#l b[dOp`;edF#B[gzi,qaW؝81 ew(C'~g\#_?ẗ|L!WG4"jbZ.TA!UG"D() . w&U((DŽ9S%- 82}*GQ)VNȵRbd˜Ԙʶ̜"B+kQd J[N 8A-Yb1P$7Q"cYKB0J ! !LaAeu3]Ŧ u Ֆee&?F?uf?3-^fZ̰(o PnQfPe3ntxV/sls_vxyo;]*~*,~*>,nʭ*l^*Z}hPiQkvs_N4qCTr ? ^0v09<āy+sۼsԂwg)S((w{\^?.jFZU# t$`uo{Of+->'>8AֺFcsh-Iv``{d-CtFM cl=&!':\̎=-I|_9W_?w}fDDDDDDDQȈb]M.DCJtq2}Aki3C dYZd))11A1BCa%Fy +Lœrs2%ņZK<+&JQMIJ q-(3)5PTXY4@)N%J)A1NYK,-Y2}8Xvn5&0kBs"k4[Z1L%bKRk2P s9J5D%L9B-L=H#D-L5HOO=tY 0sY6 =cA;|v~l~fZ4i2eG;k?ƽͫ8@\&~uEQYR^m>jZTvoŕ FNٿ۞Q2nf[zrr[{u ~SJN<<ҳTUeܒ+7!Ukx㍴S܌yųbT6ch/1mAvXev?oe/ü+ĻB#_vxow=;hd&aGt]q w8 8G`o|aMQ6G+9 `W# mۣG9h"azGysm֪xI]>L,}fDDDDDDDQȤJq(P0$(F)'Q( )J (I qJK˫ K dM *I*-)*g |k25ts2 [rkJG*V?5~j^9RRr~ʅ:y71iuwRz%(nV5}<\ŵ7^bMZPȔkxG(,D^r=/iXWeǬå7MtF=3W*oR5i[ϲ.Vnv˛;#;gv?onFY=]!Ɋ̺icKmE*^ݺɣ,YE4+L=Vi,7nqV+6_~䝝S)=AyRl ;6AO}C'p5z g8G`[t(!|#l O%OzxRGY3k_%\}5~D&""""""#ӊ3deRCrSR+)t*&.AlUZ($ ,s?#*,)lCT؆( R:LmHm$AdURiOQ:}ʖʊ*P6MVfSnKRmM!˪I*lI*)l)jZ dYFUiJLUTZT[USS(ZPkMRҚ$-8孂Hȴ*I [P՚1-#FE[Ql 15tGQ1Qj *qjԷ9\(s"0# G f3J7oyMOtrtzxhi:l0- G {jGjG!ZԴa27W,z{keal{?xϽdGĝYaasXibηkY<|+x IZH. oݭ}>\]k;Z|/Z_ }X?M4779YyP{m'[ UBgȴb[cMG9:$&ggtCl ku'cw8F'c `؟<΁ d_s&O7pL-SpD&"""""e2 %&a,3Rn̘ܔܔ,Tu({\n"rsZl&lTنeeZFeeuJ!dMjpZ%+#2uj iiVޫu r,}zٚ֡3d{Ol55$!ۆv QiOQaOQnOQ:H]fAdVU8ZEU탔 R-LvB-#Ҫq1j1hrw3aV[(qj1jbԵřіg#ά(umQۢLGiR=AmG:BV{>tiz7ğ^27/A pO{90#J#@m{UͲ8,G^~Wz.IxP.1=eq^Q.ɖS!|'v]Ukqk~Ҳ9O~:~1!@&wV~4me)7hKGeDnoVeRjnqb+{?L{{wwF`.[tF$ ;2ʶݑCl fvgs` !{@{Ga[t v'&ϡO|k~|ɾD?gZ+[9KXnD&""""":~^m^Vlvb׶Ѻ}c~Vlq&ol>%m>a=A><eM]aakxLHE-:h#l #l eguA{c{ Qgo#_o;c#|'%vƾw/Xz?Pl.寸}ߟ@i""""""? C[) HKJ밐B HWFUZY}VAUهY1[Gt1J}tմ21Jm aV&j 5uҪ}Dҫ1|hcԵ0ccYygFմQ>zlմ wdό5mT:(kOQ!H G(nc$e)R!j:iYeZU շHRמ`nG:CfNG1fř>1fw0=s: Yfvgu1w$c^{=<^wXw}ܷ<¥!]Ύ ;b]`~[Qn_#L}g8#eAf, s=|AufŲV1!,u^, ֍)D/u gK/?ˌ;e^ovVu{yu[Nv6ҍ}tnc>^rKr'^VJbS`.l h}l )0g#!yYfoa g8/ ɓ2 c|7`9WOY\9E3Ki6όw 4UQ*-#TYGQXQXӭa*YV:B}j0vAUXJ0UmkUhSҬuZ(Qƨm1J] 1FU ʈ4ܫiDVF~edYcQjGmΩkq~fӟfrZncAj:dYe KjKQޖCdCYYV9LM0qU~jP1Hug34w23Ɍf-KRgf{:4<"ȝm^zKoyhY;;1#}#j#MӨ7@r#/y}ޜ}z4,R=b E-->SJɪo9&iXxnxgN4eO㤪䭬z4nv3ڈ+h']%V[ 6ZI,l!.&+BbI,iUZr;N5=Ho2HڬtL>Aamw;(QiEY)잤Fa lv)Q7:MU?q\_'Uꆜ4/4>CmbOqhgBvm_3"""""""(DDDDDD>Fl`޵p.p 66^񒝇/N!Σk4ת6XxAy;+ɣ66o+W x;l)ksv6':o+ɁWpocsCZv6'څ>N6]{':ؘ`cCI6%Nrds_ _ 5n 7;>koN`+It9;މv6&LH$'MƣIźDu3v Q3ejӲ-l QfDDDDDDD>Q|xdыv6:y k"mEAyonJp iNMt!wމ.]lNp UjvNZz% WW^R|9!3v%Na\G[G֔8ٴ6/Ʌԉ)$'~)6OD;^koN'l88I-d;цO__$;걇wj`#.+QWvc!qA2,d_#m|ɛgBZQפ'J{(wQ9jhi*T ةrR=2Eͨf??O?׌ȇ 4^S<N6yfkgcMN6'8ٜbCG<oZ%bCMI.6MqvP8=k]nJ\I$I'ɉOZtJN[fW7yJk2lmlڵOMAH֤[$~IkhR^k$9Mdt _ Dv֥<[o-Qbd%6Ix%OpzizIlL!.dH&ٰVɶIjKV$v|e&,gs IdĊl?QBdFWZ JkCep6 I %#ϳpD7Dl`(qeVd'O''@f%Hj%Pb%H:ItqB$JGMNZH!X>_!DɺYV/B>CkFDDDDDDCeLj S<`cGlLp9 x'M ȲGl$qydڦ$AmNt''if7%ך,[f-lLrĖ|Rp/G*H64k+Y|MI3Kxx'O#q ?ٍd7>kfM{dld>NN$d.N]'t/;ؓkFDDDDDDCeOzgMmNd,iI.|$n=W⺭ZO6 ~3Ʉjkf dn%7[l? nϽo0_>$^)|Kq!P>Cܽ&kq O$@6EMڜ@5 O&NN.%veN6˜lJKfcs r;v6l(tpp%tp sm+n[a[>bIȭ(Ҍn yRkY' d䍬1^, eb1dV O'P:NP`Ar+A +qGgcd@ aAㄩG T ŬA$2혻\QkYdh}+3p[שvScA4 nZ'iSaxY^θQY%WTsTugrL^³"*J8,帢T%WpBQIU 'ŜR򂪈ŜҔ#+L|F+]˥&I(l& Vdm.wb'nS+Ø[GmRmlrQi|@l.\tP54E,nfLQ54Ee˃N*\Ԏi->A^+cd40D8 v6O})(* %X5Bbp4M5HX(;T}ɱPjAY3iKE2Z/OmN2TZ(춯mQmAq$vzgn%4Ol iB3f=93`SB-L1nr-dp[nGGWWOGnZ?)A)_kJ'*I|.6ӌp8+& Ӻy45neVz7ءgjQvkFWN"L De'D% Z*\)턫ӎd«H&y8v/Gx'wadä4Z[lݾLcvW'i-P5䢠Jf8ҚIo!ec^ hk1T;yJ1m5Ǖ9(E$tR>xZZ3Ʌs4 yF^s 8\sɹq\K^{ڹ6UqRBN6/j5w2y;\0WSOba3em(*:QUvl4_2L^ymV :&oQ t(ScWHi͓IW5Bj ujH7(Mo5#""""""!2MDDDDDcf~ignjUȦ j3?*f ,#0y@ٴGv+T"9 )n%T@j6'H1Or`̿Mb,•])ݞ0<)BUӄ Q&T5KjA3)܄* R VO{Zr ).jMWW_3ʅr`4jZiBlI B5Fr`$j~j).h S;X٧~i5r'1;eqʼnAVbTiƈLr"w4vB6v.BUvN턫+DZNywK\>Ɇj!45cl0B1.;Fݨiڭ7ASݻR;-k Mi⅔jpTQy $󴴀%y<-|M4Q|O^r9j}A5A&l R )fni9 4P_~,H&<.ӂkL*fQ{zBT=%T=$ ַ}zp,B5Xf^x#T5Kz_gQO Qě4a,\)POrqrtq`zRxL=SvFV: :8mqIC/g39o'`Y!EFF7/vq0epz'QI"46BB6"Rux$ZV8HbEFz0[RDLRr+ Ưa(+׏b!يy]c!A&Se#s~d$_DZARiҒK;u!)A^1«>r3jNy^Us2*K9'DHry6)g9*4|NyAQiu z*Ԓ*O?ɏ7_|yf okx+2.s\G\^QWvcl"قqS Wɼ2LVVLd4[Ⱦ:uqacu& ̭V(o'.u|{>׌ȇ 42*l]^j3^-毜%@5lc Ԭ &e9*!Y LHza-Y'Lioͭ,T=Gv0<zv(YSQ "-"e0eDbOgdB'O%9!i&ǒs9.RjQWFUكvKI,J|U [vM]k%hPUXCl^g9,BZ [EsN Y ݿ㷛~7%ug$WNZ)o+I,jBZ܊ mu/nҚFUхEy']VUэR8-Ojɺ2FN8y:)띢 JW5#""""""!2MDDDDDcDz ՜G+ -Eʬ(a"Dh, ^۪µ *HJHeuDV5Q["J'H(2ц̉/m\"ʰx)=B,Z 9-%b˞XxL7Iq-n . .b n'Q/syp먪dRܽH{4Z?M ~YZ&ߣs9GhN=jG).l%Ee7ɗ;4IU [KW·/Wop/Eds4s.k" NTUQWwFQށ$EHZI.iCVڎN;QUtPOZ ZF06N~$=c*/}ԯAi""""""#Ջj ,Y&DHzagK-0׫"uKD,ޔ_zA(mIYGE闉/_ pSBm1,m߉Z^_V OcKY$Ƹeb\dK[R2,v>oa-E lI]"&mEOc~cZn޳^aHY"3lI#8#[c!Z?EdYgNe~(hhil5yh㥬1ˬ]'0ՏhC{BR8i1mF[lOs&bgy i'm70zkԌRַH:C ǮS5My,]SPcYalʁ9d IDAT;d46+1GCϻ䡰"N?$wuw ¾׶_4_k|D gk\V s)V%mHKڐXp&o& .KQwP#lUW`n%ySm7;?HIK1OQ|g]&_Y$THnhޠs}s4 k\)蘢MI6;MVZI2v.X/HJZ#) 6؂϶NnJ;/j\~'rvq&9Z1Mi+yNUS25_ǫU'/G_=g'g𲶂R^O ά悹6r)9 \nbv#rͽ¥f[H(JbU [H,lEZ&:ȋ9QZʷ~~͈|L"l,y6.y^٭)5eV1(lf$Ÿ*1<}fүy$M-[Mڿ%uy~ml55cĤuMm5մrkl7-mU.Ӵ߶&춥͸e.͸Ķu!:OLN%&mfx03>Gy0̓aZٟ6IL4ÞTӭH\HF_Eyye0r !JF9>n]nϲ#um鳞ޑ6i5.jǮzhA].*Xg;6L`jqA֕ L8c~2[G16 T:w"-"b ۸ƅv)5sgy%3-bjtZ#/s<紕򤤈rxIc{\b:Oes >Y<.1dR6Naן~Oco }zn)x~x[7>._~Ok$iJ|vEe?qJZqE/JxQ]˚R^I)猡7Um|fo񦩖8Ļ͜h rZo'.$PFBq%H:Q\B] ~Пݳ)'ޏ5#""""""!2MDDDDDc([l]ߖR9_l}z٭ْ/LL[SI^ݴiZeG 3#;ү }؞qmlK_[Ugܜwk{k/=cEhM\mklXaܵv]gguH_fiiKlO_fiIx.u%jZ^ؚ>۲=mi9ni#)3p$gt>^b~?}\(Ba?% V2gbwJ;-ͼ^,>Lnϧwsͩ^nT^Kke}#/j89UJ9,+ᠤ 9I_Ç`IdLb%TEq >1؏~#Weo~Ϗ]>s'/ /ٻ?!|c{}w|+w;>?_<5󪾈yAYqy.Ǖ9M1/jxY_C%VsXռi\ff7s;Yb ZH,*'fsHʩGS~_o;GCm=|;_w;?#nQ|1c\ek굛U֪1v*ж_cG tkH~L[ۑy=Rl{*;36' $.{u.2ws-OuOvgǮBG#v4O2ٕ®vWّĮf\cw-m-3Vٕ*cN ;i^dGւzO"i^dggّ5˞YvgN+}ݙ3pɁqOs'c'Wȗ{|_~s/;O<-DxIɳ)sFW.f7s) f5r>w2y's l༹f Zux>LH|Nr;yq<=_;?)oٻ?O 뎏5#""""""!2MDDDDDc_o9t,[TgZEVӲGʼqSLev'S^ڕu'{n?>'{sQ;ٓ}U}ͫ6'{s޻wn}~}On}KZeO {Vkq;34/+{]kRm]n<lw,3ؓ5yf ٗ5ǞgX,ٟ=cYSϞfy]9^`W ;85á,fZV/gu[8r&;f;i7LV?Bj(iVR1֏8Azf6e=HK;I*mCVN4nR{15S5A;Ec䵍wuCGR8ȓvNW9jV+qb+\ / p$k5;xleiY\*bwM5mu7'NG/[$?|_盾/|~ͯ}O~w6KD<ȝ]O_O>yw|Ntݟ y?ʗyWydǃ? =V :-ŬΙy;+^[5\47WCii;$q'yW/ţ|co43|?>׌ȇ 4;MZM.n^yغ8ۙz۞|[ߞ YBsmޜؓ}㶬 }7ؓys9y/}Ǟ؝}9ׄuv\cw5^go}7؟rukY]u^^a*^cΊ'n&[/{=YKYfo {sWؓ®%v,;g^[eO[dy=ؗ=9gs<>{rٓf/7w 0//gݹ2x9cCB8l'ID_#3C(ATXXTXEbiq-1N~ m S;m1'Hy#Y+zQV!icԦ2H(͢1Ny<97yl:G6|pk|?GgOo7~qwWTU}K]cb1SL$;QQ!w^b7c/foXA f?R<~Gds{̓ͅk{ddlk7]tˆc2֙O;+K77xu曘`$z9uhO\0^Nh05CgNV|G֒u4 I:G<68?j.2;ԗ.:踖|&i1I.}h7Ⲓ|r[y+,$cuqګB,XbbDߖ 2c[6G'XgZ}s.K̥<fs^`Q sE-41OB\73[_gU6[fI.9|/n`ٹ,'8s0KDY{J,S?$&>:BDuDe%p dʫHϾevWpa.(g:5QWʏ7تVr[5r.T"Z6={cȽXInm/E\\AJ,#B\ZM~̻E8|;hJRX3UP^-ŷXSE> rn/"z _7]tƻG;z}|*^\&M 69ÜdEkzb"pOϟ̈3bJ/\;[`K_{z"hvC{;X:[/;$~5>/8)إ:2+a|2UK>-kD٥āSe?UE~λJ_P!fïfƢoY§_?BI+i:ܟgP:@{vcF _4@ Afjffyce.zfmfֱ33`Tja[+2U,11Gbd|?O-12[bd@\9Rsk-aR#R=seI ȌȌ̓/i=R# 䭄LH, Xgq[ 5"jby&扛'60_j$Db $9KZ#i!Xg`8updz3~μ$܂:Лo`4= :tCo;c z8nM'l8ŏ1{e<32 }ꄍ]xacgcQkLzd7}w>I[t3%;~b6H.C}3>aNf6ogaR&Ym(+ 21c@ܲޮ8x[ݏ@ A(@ y>?M}wTϬ3M3[g /Qq+6,62Wbb\xK [+ieE[y5Olj+^j!VBĭ{yD< /50_fb p;&0qHsO2;Ut>˺Ådcd'jVģ't恭 >q肍@ _'i@ 60+ǜ99wdr_{D%~Y,"1eCMKB|Z[(3PڶHֶ,UX"7Xi"Ta`żl$J:"$u[4THqD,ױPI3K-,6"m"Dܖ&Rl HS+kj|Y!FI'k"DB3)yDmr;w=']TΡ 5z.cω*?b2DX++auخzeV/RtMʫ+ewMviK٩-e  =%~)N*J[=Y[l^g{ 뮱Cw_OVUUŽ㕬Q"ZyTt}o *=t7q: IDAT7.bNIvu6KX-e5| 3N#p>}]r0_z߆>;?đ.=6=1\:o1@ Ȁ{no_ ׈womy +(FMv,|ћ8wcO{&O/7:ǂ1 &IN [/j5 vY2y@ʃXs$ GA\ė JB2X{'HUM캃o0^tJ`Nݏ@ A(@  >f$8Hp=ss 52;GOȜ\=sFEFdsA6Ojd˄HM,i e?YVȌ,Y(o}Yn~h%D" ^$oe%/*.V`"r#&+ZY$7Habe+X0Pnd%̅bPp% U&”zW4&㍬S>&SGDJ6,z3skR4Xeb0Y KezȚX$k"DĂy1iNx~@ B&?Hp-Dz0Oo~k`Ĝb=_N-,^ncyk"JSq\-V`7s9=&s& 1Uk[+M,UT/S \KT&,VX6_/Q S4yA|1Գ\ylV=fUzbM+ $QC(}/H׳TL0E#f(ZXjafXlf&(~l"D|e ,ױ@|sU`W}6*ؚWζr6(Kخ;y='*ّWf]?dWR%w> 67w_ϒxIϒx)G., DW̽JVe2^$+,sϐ).&Crti iRb5RVKvq +_̾xw]Ǟ^#Ct5b~ʿƞ>w *T%wPTvj%KXŸ3XC!pewģ5~ܘ3+&{xx`bԏg͎,scn ~? gًq&hD':cz4F}4 3ȍcx't .6#F2#x c>3;u0Nࣥ3m?7GRy:bMv!y>%}D!'fO/٤2^D߂eO{1;;z 3Vعې6~@ B&?HH\= Ds&60_l`ж@7 Ll`hR H ,ERr8^aBQד'\L9&”MP7j \RUK棪Pe#K ,RXeD#97٨ i*='*YXζ[W* FZC6dXM>ʇ$=t؃HwX{YK6Q*EUDTSNȜ "ŷV%JM5GԢ-cOa {n-\DtGUpm͡;,;RjV/Bbvۮ/E%:SF΅ $nQ|1Xqu,M_L91'_wµc&!hNYcccL3^8zaaIР, %sc*݆m{g۟ӆûaeq ^8kϗ?Ϩ@ߓp䀃݆m??l?Sa ?Ϸqo2c(dN;~ӗ䭩9OGYq$]ߴL#|οa?"7,aOL|<';|23ܜr{\nj@  i@ 2ßʴKM,Y n6?Lf`"qeRW9D5\^i&UIEy]Jd<ֶ{#^`jyzHثeq<Ҷm%Rc"ZHm9:#1ZfĪĨ=%^b.-$(X_g)+-(.ס+mf'65n!LRufՍkZXj"\]O\-Q6X{BU,?#Hۤ\#9 Y2~Жj~9Y͏ت+| qG'"T(y-Ahort ?B[GYZϾsY/ʞ JxR3B%O=%T0s"d!VYSP\ș2` 79v6.J*'34GNAuuk,9CVqVNIq92α 1{|5C?{vzVW#\tf /)to͐7ipf97"|YSp@ׂ͚po~qǫ'`qqó-#>썵oN;xwg_}ț>Ȏqo13~C?vgzoYCQ68߸3-5&[3CҺepw=quu;llrm1#/e@  Eal"=eI ,*ӷe@Hhe *#fb-DC ´īHUԑn!Re"Bi Bi Re$ZJ22UOјEd&Ԙ Dj[Rێ#1Z1Z=QZ1y&kT'CXU 1j :T Yyop?ӏQ_oBUjhq3[򟑪'F'ZLHm#FtMDꉑ=&Jzhi ;'}@ :T,Q5TYOGvIq29X{IʾN+$>Ob⏖]N11'Dn$BUO9NrA+5T4(#Z?a[#%Y*BazB)xRE-KV<$Y^Ƃ*~9U}~=q'or|G.Tr|Vp|pmšsUl\ekrOhˏ:8dgIO?b̌ ЗO|Ļ_|gO OOz 3;+=btaWFN釽->xW;Փ'O`u  {i C&wž5oN ϰn98؅i3;K|yh˜/1ğ눕%F'W̑xfI\9‹_ b}~1[>⳥=y^G:惐1#Y6 .n\ = V̉K'{F6Ȓo>XD{tocF _4@ Am5OK[&׷%\a4O)D( D) D+ DDkCHD΢:NTxuێ7#YFb5&Կ'ZJ|1115=QZju@ә:S1&ϜW:si3j_jy&btFbL絒n&KYKIz4$+spBM[zo؅zd9v='UHM1zujH-4yJԱ ot Y[*I\Nqf!LYGw*Gz:wpQ5jˈ̭"J(S"uD6}N1I'܈V9uGOAYˏ8z?'< c"OS>%RX5+kgm#6B om_/'n2q-Njr/AT\ 9_3e%*ɶl]%^,AL1.t`W[{׵]ܯC|_;pp hHG$~x1ts{ va.e@`"IOoZ}'X{w >\k['wl]mݍS[pw_ 7F~?m=!hxx?ΛA{0iXdMؑѝã5&W'&~0?K>fpu+c{6&Ovnj@  i@ DHK, *m!Ln TRY3a"dDD) mVVIPSGYbz=On0w/wHhM+1h_L֚˯h8֙Тu&bFbtFPcl+btF[{A\ދ"-VJ|ohL$1('Y UʧJVߐ^FcߙZ:Oآ{ByMk"%D]q:ωR<#VTKtv9H-'=Ԝ$"Ms jb(DeD]Jl b$^TM1G$H? F[O9z5 iw-8v1 7pA+P>Gz)X~,z@QD*'e6TIՐe.?eMy7ml]g[<[3{Cgopu^"MO%l-,a kYzE]S7w#0#ޜ9(|x1`\/ו}: .n8z;GW{,ڳ8a6hlAǞδoVɛ|Շ$'G@g gopd: rC3vn}c8nV  G/;ljς>&p"?¾5osT'@1]5'N=>i cf2'S5 VZ(?ƿFЩ;^]>ěvoE;vxbUlqt__:`ecw?f@ LUDo+-+ S4&o"BLHe QUzUz-)zJo8y9`n qs7?GO쁋3^taO`'z7ԯδdHo&sx7ƿ;QoS ؛a8zkѹ?o|ً?omx_B)^t؆׃;͎pj7B&z2·F:>ɻ3}Tf|?q}6>vzǧ->;/޷;maaՎνZof|3~@ B&?'yS[-D+ZUR˴/c (BF5q\OAI#Ee& \oXq3 Ng$YcSf.EZW:v FhU-{ɔ^^ze%ٮq .RrҋdɊYp7%&:ؘbb#HH%2*V.&!2ˬ]e?(.gfEb\] rcgf#8灝5^ntt@\__Mޒν=6ɇNC ҅bgjoLCD\,C'".G:@^팓 {Fmpٞ7fxk[~I]a˚ÀI=X|&ç`~|x*MN}V̎N]9 =^ `;uvtnx 8\Υcm :L$Ua&m"RҖWZX9q =1/ U#G.ԡ*mȹgKZ9^DlBr8xmǟ@R%V6AvO@l|0{|?%!_B㟮S$j[H6']fR[ مF{F3ٗ/?#r{N5s~=eD۠zZCVJDդJ$z@|]bsn}UOIV?'NTCBN5Iu$hkI֑#^@Xmqzb5ω%ZqJ"SAur+ +E kd7٢@]~9}ǫ9zwYP6m5u^pO԰A[A$e5Gj%My26HnQy5V^ZrI1k%Y Ev>Uƅw3Xxc%$&0tWn%UtDIWYLAqapG;Zvz?=N689@A=ze$fD̈́w@p;<:0e81rpt\@!]k[ F1;kݏ@ A(@ 7 IDAT%o&RDy#Q?%\DHu Qbzb5b5`9&kDqRK d_hd:>#B3;ؠ~FZ<#mXl* L:cZ|y9VJRRh$H$e=HQԑ(e6='rzȫ{#c~*zf}֩R,]U\3ؘ,V(z2V>#]}"]IrVqҴOIR> BTM!G$jW=!VXbUIP?&N8Cb$bY9HϾAjn)i[lΫDUDŏ\Xɮ(FE j-~΂Jvc*Iɩ SVE4CH.aWdKBv P\b%<{Cf33Y$sei>CDw~asKȔ]foVqYtEnpŻ;nL:.  K8y:aigՎ;cbN 7k3>|`l,qmINW7p,6V|Җ%ѥ[g @ vaK,pdChWzNҷ.},j$r ?I8f{caْ7dOaCqy&y2}'vgش@z||pp'/'\챲;kYK{K[Sa`@2M HygHP{UEZGQn&JH$,s69{O6p|?c1TOٚv5R<=fR H.0Rh"AB<=I$I˔I%SJkP?"MyLV.C{d)jH%Y\C|N9 _7ʩ AvDy JrȐU(M }rod%JٞWe9kX/Ρ(?f嗢lfQ^sJ<`t-6԰Y{kT(ZuH^yuU^aצyc&iq$'ŲpA0i 3?d6~sfdsoW{>m b´L*~[53.)n8Zdg_G; l C}y~8o3^c,{ώ:mX8fܓet sG|>XZaa;kl,+q ۊa@2M WieZ(Q5i$NL$M#ʧSԲNQz֪Jc5$GѻJ\4T$i$]zⵍ$巐k")ŜfIk&u[H.Гo.R^lI$4RBZB=E-@2OT='SgZe#KM(xGl=fjRI:VEۤgȹ?my^ *j)(m_jawXɪIW&K>)sȐT&`.>y5/"&Yz2wXaYa+K̡+e_e& oESfe)4]fye]aCA鹗X[ Y$CRJy5Y*l-j-VIJYs_cgY.ryH.ܙ,J &>hbHNee.ϗ_}7~ɌBJJiQ_FRT4䜋,yI0`3 yt򦃿'. >v? };]qtɎN]  ; 0kܻZ4es_hO&6' co h/K|J8]4*O%_{Kvƻ=#:ԇ!3 WWl,9C5׼GxK>Ki߱=}l9& GokFNuwGㅫܱs#d|Ye;,-ho7?g{kvع L:L$J@(iF>SOU#F $4j$}R=!Ceʩ!Mc;9|$H5SKTcI4j$5HFOM ):=)$k]mSg)-Bד^'|EzֱO^=UKuRMl<`G~-Y-OqG+ˮ&%65QDZ~,zTN>de.=5(ˍ:Yg~) 1kbײBĥdoRQ:m CV(.C.+w%Sw{$e_gՊ[甒t:iGKX!-esA5;O'ysq{k,)f-X-dn{4yiJ2嬒W|!M|;pI8t#H#9tXؘpbÈXBBlaK% >Ss1delYvζ2KN+?`[n EW0;K];|Ȍ#4{^#'qy $)#w_;OO':{g 6NOMҹ=)?Ϧn|2g}^׿2_Xo/wcw/k'\m6ڃppE{hg['lu{l|,>;7[;ݏ@ A(@ 5h&J@D7#o VH\%(IT7n&UH4Uqُ=t#5,Wԑx. = ;C<$i=ɲ"? Cj"VRG$i-:uDjIP4j!ED4]3iRuMDK'5<=)&ZHk&-5^PdžGo9t}GeiLl;$' E@{キOxʵ*jz9˞x+oFxbn~#@f^0=Gsn.R5T>|%sԎ?/rg/p~3w-G͵߽7'yIg}K]go97|2O'K@9rfxJgTEWާjKffԏI}7tܸO5?ַ4NIƉi/򩇔xI9ʧR}hޏ| 3R2uo}|Gh\VIfF2'HM9Nn^ c %,<`rx IUDžS-uq"1-؇"4> uob XʵŨr~/Ξ1bMκ:8k?*r1:FZHՕٴ˂Uە9CZp5j>6s4чvl߽pL̵2@E.FD-AadfΪM&.Gb$' :kY UC 췱ƀ:xnBJնlSC\LpFZu +04<|j~vq=vY2zTIPѐ VUFLb%Vn4auPVSW@  @R0' fD;?™QE׿@2ob-_S9H r>'s2 K2>xN(CT/rɹ:֏RkF>S_; t^2pc9S9TM<2.S1'h[ޙcWޝc쳷|[~k?^{;/֛ӟ1%wSwu_P9ч=dkfqkf25U_P=0u>׿t/hOWL}A]7JG>fsJh[S=ڿj[nQ~93(zHc Fj[>|ITO}E}*o=r&X潏>#"ڭBLJMoDC-Cum5drs-ZhL[JhߔM.Q6T,|-9|7{.3"=({B)##=%w9%Oz 8N?Kz-q |4ɕ߼ewOi%%Wɟ/K:?~A4xFW|F9*fP2;G%O(~Hc GS6{G~O|N4Mߧnsj'~ '@4Oާi>)W>##g(O#ʦP9O_0y.vO橾O)yJԳwGQ<[만 F?GӍ/})(!44ԴDDLTN$ƒ3ֻCtt$QaFOXz;x.껅uU1߬n#Rv 'L RF&d)ZXuC)Gl u kv(%my-k5Pn35+)ѩ:jKp^AM_NV_Zka!A@ :*qلX_Ju _JZL}DCOTvd[ؾ'M1ktk"7RcjzR4t4P%eEHK(J>5 W@  @+əxe.3%҇["KJF]v|znw%/i]sJ^R22Oڹ'$Lهĝ}Hs _P:y)[rb%jh]dyMGo躽Le穞Zb% t~C7TM.Q<3nsn%:n?[o?Q?ڙTMS6>G3GP1y?xʉ#rP9gO=m)鸱@ۭ ޛogKM?a7i}BCl)E([/)pvݻT]Lͧާd+JFSxA}GcJ&S2هO|KCJR8G>h!3)yB3Jf 1O}J#jgR>=l)O9#bwϹ״{H'$"T J>.$ ',<'b8z,m7ᄷ!!N>+TYLAf[kruXcv-Yf+MDĀ[ֱvr3A$S@CO*{mvN-0Y61X¦+ fr#gϟah}:Bh;=HdJt̗ջ `gš((n,E!F{&2CEpq@-d.:zRjP1RdޕXl7܃dGymb!=\7"TeTuQQE%氋 Cq<22u1" (E(ID(IEM0EX_f@ B&OHy. K^9D"YN&o Wyy| YR9&橛yM{H}+s?c矐umk/)ұEJF^Q5L"53)}O-R?@c^?:OOsp33ϖqN =ғyBRm3;q¯t>| N|p/ĥV\{($g~FG/)xLWO>b9eSP5o(nN|KCJ&P>oOc7 cQ:ч K# &P2G|O[hƻ/}BM?teןS2󌢩^y;Zo|Coʣ,b"$/ƒ$0ȇĤ8Cppӝ _#9G^A*ťOr'4-$%u4n1kPaMc5$Jh~ 5زc rmvh課~%*VjaA{ڂ2k6u vگdk-~ݝX Y׀Mm/` ߄2vj$E/B/BM_\JȴhPSAT*+ S@w6jaʑ4wWjX[DCX]M}M%&!IPPT@Y"BEUHE챷&,!u#u@Oa@ ?!Kd,9L^&kt䌿!ktk d]] ʫ${ݚq闟/?'2?xF(Xxld TL,{uܫs]{N _R8@;pl\{)9Gi2NGa/uT6cQ^\^xZOӝ9ő^OQC\7Q6 S$'o s?zJC ǿwzJgT^As'Sxfw\}&׾%HK#ʆS>_ ~|7N\ ϯ'4‹[ +n7:\2Z(ŧ^s9ՂSNYLi`Jɭgt׏k~mxvW~NKO[yAKzq->fK9R܍_a'59K鹛gUM@A%]՝#KD]!=&S>577чTN>jճO(~D%O(|Bw)o)o({H(ٙY'.6w!^.x!&67w{\=>b{9!I6vuEm('8:;װ%YaLDG_ 47gF:UKE$PPE9uՑcj&UR6mN1,Q5Ux&J !(\6p>kuoC}")(kPVp8F RrԴDȴ%ȴU ˳'0IVdw 6QD$WېHW)*A.aY"!j*FL})j tUPYF4T1ޣnMmF ?!i@ ][ }hk d,zmEFH]"yCHzEUi^>4Oڵ]{I?*glWS<@+J&)xE+ʦ)^lz%&)zu%(^xjw!2SKO.R0@O-S8H"KTN?)&\ZpIo-&{ 2!5(? }.S=%)xnWc GP1X5D̚ $֝먢)AMC=MLLPU!S"Sgyۡk:=q=K5ڙᗹl5dS.Fr j`Y- 1$Ck MckU1ߠڳfZHuE(k) Q V>v#F6bT("VV:LPRd&b$zke bCE_P6rL2r#u@Oa@ ?!)C $-tuW.2yG^4H"Cv%"edFU+2_5H# d-=@"Yc_ w];J&^Q4D ,ojWO-?H >D&")Ϗ XSӍڰmfsEv$exL~a2QD D-!*: }CDb&zhaʔn6WL"4#q4!ʇ z81 zE6:w5Ct8^ዪ%LERC]Om#U4*hPCgRXސƣanuC`উ2=[%:Rѝ Jc"]EVPR@wMݎvZ*RM)J*J((ی@ B@ IDʕ%R,6k߭#oQË,>Lk2IZ"yx̡e2^9L_!{-9o}[^;7䍽&g5٣{CkG]"glĜk=9L+q;]Z;q-8mB%r8 oa| :S]rv'OPr)Hhfl;J7`SCR-WpspΪ!=G qhd"lsqJ*.ǸRJ).'^lNߊKz=^-4p8I 84Y]F#Ok>݂[f=.5xd߄[n3 xfđ&";k#ϬF<,»Zj-j#N9'.;@p 񠦥R q#`7nmcoa6"4ҕX<ٲe#յGiJ 4ppލ-, 0^TD s} Vhe"C\UXAwK6I\-mVc&;}ײ"|!h5 (`A wwkcŊUzhKދZ1kUK'p3qhZ4Qcw#,P5PBI@Mjas^dcZtW\/Gt&n/\ 2=)2$(mF ?!i@ H[!qiCщC#RN;ށw(#B٭ wNPtiFىĻ wRn>=H<"CɽgRyv Fѵ%wWhCڊczΡ{d5.D-H|:xu"Dͧ#gtW'R}QnE]!roFՊķ o"t"D=^Bv`9ijMvzuф_7>wՅWtفW>6ۊ~@;wa߉ё6B[1 i0Vcx;jk1 a]|;kY}QligW\36xky݉=I&ur0,nj>f^x3'qҋ8«4]$Wva[v '} 9RqZ rHcpp%׋@_ ?/vYo#+3'C:v Áƶ|Ӽ ލ4b.읶i>vjZ ߝ:g:z25TВbqa*4tjb` }LV#7f7#7TR B+Դ0ZxDoXr ^*Ӕ].kq؋>|= &1aV6fM7|Ѝ()TRK[al4#0- YD[,ua>%Vjm:Uj*#_Je5)`eMedrT4J()+ @ N@ QFI (݃OJ.(Bͯw;2.>(ߎZI'QAѯ%.zPBߋ8E.ԃC5<*P 9ȿEBҋ$qp!}(! F9n{QG)xS(2:fQ @я4Yh+j}(tcxq`;*PoD-i|*1'Ev! `4!u(4! jG.QP=JA(v؂4cݨG!*Gގ, QH TQBۅ8픓(FюqEd HP@^f\3'1;цi\' ]iEX=l?48 4<&ݣ՘$Ԣ]j8֬V7DS0?ʔV̎7bWǺ$p(]IlaWb-l,PB;}S HdWD{# J2镽4Neq]7}2Lxf%)'"to7g=&ϛ_}/{g/?/7~ߨlh_ 7Ν^mV> XF`ON=.U\oh;>a~䋷?3}1V'2эšxjQ7U9u-;nA!fvcd=6[Ɋ ta*95lvbVTܣO~CM '>*8b9v)B,W@UO"kMkaV+FJ;-#.Bs2 om%E}"  (ki-`:fH* TBU_»RV@UK)vB&OșߒDJZ5Mxr8"p9(&GdљPwbqeUT͉.h;CnE;ϓEzG/I$4 qDzm\ svړ ^EBQUDTvSMbIb+z&S$4%mm'i-'k#sNb8r%=m#'Ij;Ibio'AN#4Ix$hgi}$kAR{OÉ>z9KLw=%AکARNKu$EqɃ''I?yK$_h1}8~:o#Nw˱^"[%$}g'wgHD[? -$r􁳤&'zH;M|G%#?+W7Gb#>{{m9ݙvpw -)POrNV+9`Zو.1^ZFR W6VGM[TM\\ Uflٶ ?FyY[fo+k5vRZz#Zi;ujF"dH4ذ˘V*!Z (I'vAh逕 ǍƱ,wlֳҔu7q:ThH[*5L Q&؍hZ*bBUqYu!:8lgal,HڨFulDREMTDԗ!RB.\SVQFTpS 0M /l)6< Ȩ&UՍ_^G .i%^+I% giHLE2O|vjz)o{5WDh+Z 9y^vGt9r ]g 9ODc1#>|k:89HxW/p,I3x辋ĝJT{6FL='I=^t7xI>خLE;ycg85HL9 $3iwu/~7v~뾄Ga;D4Oh9"'e8網-2&TPAD $,v28QEaSF(jh/>ǣyVš T=2|W}F3M8d`݊kQ7' @h{|@l$ Nx߻E|\'Id~0MtEN]%(H<7Ѿ_$cH>B<]= \+K+ab>>KA 69(1^N>ouAշG$BW IDATD+d]h&%R:{R9{ѯKݟSz.E7nQr9C^'w: ׮?|\l~[׷H :oޣ懴߾G{t?]gU`W Q!VpА'aV܂{mvq>;Bff9 89;mߤIJZ,nX;bu[1r+֫`Z35u~' "1:ru5QQa%v%֎r2`5ve&Ye%t:.Y[2NcMC)hI*c2N[0(f:Ⲽ0^/EPZCzJ0Yst6X.ET QSBT]t26 a`'FE rt7+aˀYl]ԍQ7VAAE"2 $D+AAYD¿@ '0M 5ʨDeg yK֞ƷN +8eNp)J-!&JpLk5c-pr S[h"Do_U~+} ~{_&_<|5VCpQ9fFhy'ѵDY3HDu?Gz>R)"D7%g8~Asu&* &4YH{oKܢb.ŗg)ZDx}#-4%TILEcﮂ>ׇ /w ((E ݒq#N!B1 wvR;|=uf}_9tl "gɺfKQJ3/R{0vRb݈m' oH:4w|9'z,zH1"71@k.ź? V [3/ifT;Lwgrl&ɱ!fqtm32Q5U1S6UbbGhEmT5%H5lMcF ?b@ "9,bMnc\z>CcҘSPɊ8n@l=_Q|fEe> ~~}S;u+G[9w/g}(~dFf37 ^pfO΂,f'37(B<2; ɞؘ4g170>fFH3JZCN%6W6ZV$[}F x649, cID. C3 ,2A)LaQPY,ffp"YܨTf:ey,KaVVXĊM M>"'">2֤QR|JvW^{^ N9[Qs^ J|R|JWXȃ΍8c>η}]8գ{g#O5N-X[ ?y 9A7|*AW/t ׮t2a7q*} 1p&/u.}.p<).t .u,Q's'Γ|"GO!;p#44㶣yY+d]y~m+*Y^ZK}S3pؔ8o?&Gnݽ 8OD̦P6%氒y3c7wc93ڪbb}ZF|݀ʩ[0uS_t߰L7 NL苕^FƨBWңfCGK,lљj1Zh1ɑ$tfy5]4X1akgNOK,`XuBcTcjApMWSlXbemBTDJM5?.RMg_tNH)BgZNX}btâ;0bDD!Z&^2 c1DX&B,!%H$b:L:QfE")D1O@ %&Xd&xe0/މ Zʀuw aRD&>\Ivt8.;[pkڏV\[qۆcnyaGOx fO\ Q* +>:ĥ#pv~vo/6];˕Njf0?-m86[XV?| gtKWp ϓg<{p?sS'q9s +|s'q9sk y|+v ~ zWqkvyv[y+gso{7Αv2/|Ѓy!OJ!'#-<̷gڸu?߸M)eO]{˹~ Wϴq~n]=oNEX_b$hAVVـ}6 -1Aьd'+r޲ y%|Ͳ"$ )%WPZ¼Bfd3/,)κݸ6cAܛఽ)buv֖T༽F\w׊_k~z'Np,yRu_gVpÙ(Ho?""MdҘ$FPQ3jgCAv^:[NxjKB*c_M"54U%1֥P[ʖPQ–q'$v}- `Lݟus dD:C\ l\Ϛ˱_Dz"CL "=1D?]?ٌlǪwaj'ȍVj9 ˮV(2Z*s9rA\Ҁ\@c0j, zQѭꏭ%"SI27+V'*~ɓ3]Fm0vP0 UIܨDa*r 4%|jD+²CG1Zsbb4+ZQ٪1(aȚODT"6rDJ&MiEȭ ]2+]zcjIJ@*"SȑHD"$aҢ1Ѣ4W1S`QX!Fa2nj@  4@ Ef07& YROY#n[PHmbj&Z%x֕͘T0N$UPXL LfrP rYQCE599&\Y]|u(6,6bTZj˹̭sj;Ih>K@cQOc*YSXI@c+sSX_Vǎ]ll؍{U;vb_XF`C 1- o%^VC`nkwÈf2]&tw.D jI|j1qLIfzH{g;B| q`tg7k"YʔLKanT>#sXX̺b֖x ^ΪKYr 1tґ2B;c: UnygueQ5PFSGl* "H"{ >2(GJ) "? ţXf6nY0k go%93$Du VLg٢,Y4e˦0c,7˧LD2|n)j´)=3fc#}^fv"5PKd̵u2&*2LJD+Yj1JAD֠ߗaUu+5*, 2Q鳁XwSƂ:7R^FGϬD( d "QOftXNZl{u &Bc%jbCI>aY̢>g3mPz Ŧ)bRX*B,!WUh̤M(Ĉ"*EG@&ۮ=eC!)<zmjeV|#܌BVhRk}+;zçVG^o)gBb2#3cٹeK)Iy7S)0>jÅ}>-u2ғ1jWoaU*ZJX"B,#Hȕ2t&J>a:fb42jV$,;K0a:bGyՇMRL;jڧ#j39]uHb;[|R1Rc * FJyG*vfu2d)jz@ BL_$E"ZO} j=AxO♫ĜB $F٫JʹKğ=OSl'i|E+_B>Jԩi"# %)"x6'FN^Z, TRŔQ_rn.mR˕M6rGNz k=BHA\UDhKI8t}mD45(MxV374瞀}j9#7D@ƸD3/9I gW(K6*Qɬbet +Ct.2 Xφ,_Atҷo'2ʲ4$Z ϭ8W(6iEWYLD^VJAnAd&yRQԿ#~^Da2M&n.L3'#9~#iI9wi?G/4s^\屒'MBIeK^7i!dc/wYozzlav>+r")։CVNrR}I !ܑu$F(ӕTo8+8aӦM߈>]j:Ahlh-u2f:9Jf\+kN 2%&z,MUtF&VK>(u2l:[ѱkG4:5z[%=tâ=0q$+QeXY3uxlZkdr zQC~=пNYj *9rz,s? b,s/3Z:+5*̻0T+Q(%2$2)R*%u0AcAmE,!3h`hAmB4(cF ?b@ "{,ovU8ld |w]ϲ:dVRw.B0aQHp!bΐp,qm' lڇOu#^VZRMhu N$91YQ'ɡ*rؔpZj8׶vqdW%QSE-̋aFP:#b@y$UC#*~}dS>Ẍ́3oi¿MX\}z%NpL=7|Sq,⛻d pO&Jwu[(sk##i1u0ِ[H@U-a;n;ʶ7);X2w"EyDn~6cG e Mu!^ ńœH6z-qn\Či pw & $鳆3z&L菳Ʈ㶐UGHbiIH_OF 8Zk-9^Å3ܼ~Õܹptɲ%tz3N60{+^'35҂053E#K)$J%>A/Ss 4fJj4fR̻XQ%*LmXwա1`mgmos0m,] Y0jH,b"K06E ː7IdbdJ IDATS5RF RxS 넘&Hs$B;N󄵟$iN^ i\')B%"%eb'YN!`A|Gm"I qo;-{I waA'G%#`gr+ͣa;{ _[Bkm1s*+ _ĊBj_\L&z'33(yY]Df"/Ƥ$Vm.eZx̠x{D0;,3Io>BclH%m+|"ʈTز\Ҫ,/Ʌ,!&bN7'v7Mdn'e1ui#yGOcXt4~xy.,1g4Ntdˆ:ϜIo'}V-DJ#;r[Hqe2;i[DM]mmܹO߶'v 7?y/_˗~'~y./~yu?wo?яl*v^}ӗ߈ei9φvc?=?L/ôFBDӠ5Ӣ5QH :mՆN=ljm̺3'*1(0Zi?*4xD]H s6 FmPbamyw VAe>J>ׯ'^Θu3l4j: &4 z1bARFE*AM;bmgFϑv{jнFi!Phe™X"oLpU"#)e( [kbQL'C#UcF ?b@ "YeMD8E0+$ћϫxT0QNv 8g Ocl(b}6Vg>9,ϢJ';{1o4"ia^0飾(@8,ΦR_[NCY{ ْMQz8[ҨiM|>ʷ,!™q~t`[ )$ԝĭ`'sX{w֤2-Jٲ]D!vrZR|mmDnbJ91v#KY<g0/(%oaMLAE58$[Ɇ\2pNDB*]$-d77ղ=M'N;1rL·ufcbT'祌0wOczt*\ܗ/(M˟vy7^޿==y1{s޿}ʛ7x 9?'1_?L1//~3~~᷼y;o=y!g/xl<G`fר.tmR@Wbbb@S5ѡ*3+Z)!J0백Qi!~ M0`kbL TVbΈB)O(u Ԧr$j1VR2` +%b4%b^X+Fbŀ CQkHbR bkh5H2Tz%*3% QT-EofА1#1M ξL`ap6k㶰*,f{ c<6}TĜ<%054Ye$"IݎGq3kVb |f2_59G4r @iN 6P)(R(Έ 5֓0ӢΟ^sd0=5Ql( q[Al#8 \\˘Hp chFa~HsXᙬJ'F|2+"-W#~n|s[JfR0ɛb={1/l) a'ۚ)>JC;H Z8yʓ?{+gs$iؙKcAQKvN<~5 ,&CTZ3}qY3KQ֯L/?[tљ.}0Z`c HJ̺17a 3:V3[;N|2'1ԉC |6'ݯ],,[=Ӯ:q_> Z Ea*ò bZ T O Xv6Cf*F$!rj5RXFB$?lIEHD2+-">c@&HDnyijIѣl;uVAJq+),O&//7r6~{go]޼_==˓G?=޼ɯ<n]!;;7֭]HB 8MQ Fz=^W{CT{_}7oOz_Ëy?xm<Ǐoܸ~m$78{{|gf-[K#?n*&`E Aa5W1U6(hD4"{hњ+12bFgFi!tHObz%]Ȕ2$w%H"B+>D4ZZB#ՊQ#_@ @i@ ng0Fg/ֆDᕘ}hqe eWQ;O()Lb{ c*n\T&'3;$aNьrO'8Н@?7%c1qޔF@e?))_O*6'nLoRBײ9֙HGCVdWQVU@TnZk>A ve&A|rB"i~߲6*,#n5ė5xV"jx}?LVN*Ȯndӎ:l%m$o-' $t+5ո]K`Yѵ$i$>Z[;¶v eRz ;N5SqZZx5ͅninbkռz /gwyϟ~ˋ?3sXV<=<_{ .h!1p/}wc^z۷y)?ݻ'3^zx!Ϟ7oys*|{̦#-;JT];O ߼L\DՋrk~$ ׮BdSq-G8|c:ȦӤ=Icd:Ify2#*9m7m@E/')1DBhjW@Lz"BB.ć'&x >pgo.EKdMb93MHq3Q[wZ͸dױ:a+S}X]ITeVHӪp˭!i&C?G鋔_ʖ3zѥ)hocœT^<[W|9po]g 4߼72nߠUoW9q+N|}}o7Mn߽7?{_qM|{n_ǻ7OǷ|qjpv??||Ëwy'^OO<{r͟~ׯ{WeΞ=LYy.Q>ȩ-ÃoS޽{ΫWy^7obk#_?'dv+`{Hx7w\xAA=ox/_G?/Wx_ěyw=o78}j/^G;z}qWy7ox/^o<{gûx޼w=?˟nُ<}=^o=luU2D )ȧ#'a[@3Yk?g7!ylHayd, WΒ]Wyo&(]ۻ݅*-m]CpR .!!@pw,ėklEV%w{ͼfspϴXbe؈ Ԓc:ҧwtǫ׭1CLӡ#6cx ӚA}3{woB?j8.]Jȩ$?sXsp1mײv-^&o>ǀ5gC2|m:Sz]Rq)rY&S1f6lLfMƭgMIˉWo89sp E.WřܕrO.R<%O5r%*3>9jMZ %Ja(-a6bwrtQQiSYiLU WOe͚͚_F_W `18 tE92zvo-ګ}I|+kݞErAeS$8:`"VѡԬMD󯆟;&,:FS~-BB jY/!v0QF^;`BÃ|' CLXP#SF "i@ 46F̣u|?&vswU8a-,sWL-!+wR Y7lrMƭ櫡 rqLzIWۅcѪҚvgnLޏCaĎLރ 1ij7ǏbAl>9F{PK:lDӖsf1ge=LzI/0a£|Oc^?A;i>~ek|ʎOY{&k$M^k9/9%zəW|K gZ-Y.RjdV Z%OT2^xU*%C&K![&۽u IDAT[FEѠעעRT#PCAN봧(>c3kp9 zq;x[ǭҁmC[~*>DU߅D": *+<~+V2^#3n*+]x<~3.[][SL_^DU&M@̠9Ӵٗ| TW/&^#n@J࠲DaJ#^ zJVOR[ϗϫ.n-$Og(͓$^豽53u߃V_ѩC{>/46_4u {ۦꧦ4jڜGᄁV(j 'f0GY7`CnM"ÉsUoGLht5CW`BkUZhhT(AG ("`jSީ) @72<0+SxuL{G/2sEǠM4&oѰUD+h=}7-ݴbVOB);i1q?HEG bȈ,Z>kdf,91+a0 DϾ͈iA-ۂ1?3`:GY$>f:dY.Šswk>%k^zĎ{?Wx oR^='9!GsJƁǜWdqI&8[J9ns!IB}J)ϴ2R2 T֨xVH+ jRrZrTjrJ$jy PkURZ 2BB^x"JiR9VsW>O9>ǀ[|&*f**6.#U;JU^޾SU~ ޾ vo|TTHL܌mj-Q^,<,ETUp{yFSG.0؜ ؘyS;(.VP^҆RHe*#6|r+Fcwz<=#cG"W`TUl VK9-f?3fvLf-泏hҺ-Ԅ|Oѫ ZF £_7W_n-j޿%aQMj։$8u]_XTt$5jY3OYzԪMD '42 BkPhjL vZFBHX1#?4@ F/Iɛ?OʧRZCi|ߎXMc7|Z 758f47Var"S'v)FlJܹ54Ofׂ~fL֛qX`+VLb>Lڟūjlc$V>keN9to$lI[2`!/SXsμXz+_`ٙ!8þ䙜yeq:%\grO-,{ 15r^jxpO ^(<撦UU!) RP#R+QhPj/ԠWQP%_WDqqe"23^<6S!N :@ \xF*|V> *7 ;%eT0CJ+o: TکrQV(㫼PP(PU糢Pd]\=[GJKqt]F׮np8Kxq{eE4m9R ]o)/Ҍ͞]^HiGt`w[|ptQU妨HFLkܘ|G}L !n5‚fHBkZ3w" "pY/wQZ 8,`j (,Ш`Qp"jJPX "BT0!L @7U|}wI_&l4/q:NPd~7WiE|7wBݎ"f15FCQ;uݧl*ZL\Kiѵ5zȐ7F173c#G¨== `E8ƏccXsN_g pG/G*d,H>KѻLv yKbclt1y&#IH8wSI^s65g޼LE.T,H3OeټR! .C!K%#G%G ӨPWGBUS_ESX%7'(ZByDk!qZ{J|C3~ϝӬ[Wڶi7M6}рwFS4jٜ1fҰESlє&m/>'$:[|KͨهDEjRʇ 'NhupFV+:!DIH`B" Ad"CAPxR3:ԭA!ԪAXDMCC  &<\@ _'i@ 4Gkh?cnߢ[{q1i&n8FdF:¯ gn5^^vazz\'j?$4=fӑ3;o@{ڭC:'sn<I-3ؙk&r Jܢal4+g0}Lo썻Yr3idLHdQmǶK0skeɞì9|%Xs,638n!O`K{[7I~ӧIq/rcqR3_s#5"d IF+'PMFNJAXDLJ\ j%RF:?Ue UZJFT P)%(UR2ґJ)թ qK:[i.G)ng)ng fCu; z͈L$q&N_H`gY41rJc0njό 3Q&;z s,am&oc}lܺ+h+i3.<|x en# ʋe^tIOʁ;zFRFNJa8E ,^gf+,lŸ=z<>U6~#@9&`wjt|E8 ,9fQb3x?BCiCL]޴BDŽѬ[ MvE[Ǝc4/O>iOzȉh߳;:wen|׶3-{]KO~ؑ5 FtFPZD֋$jEJAxt!j5>LFd"kEU#G-"? cL+k3@ !Lom~:NOʹ s~bW'vu }b݌Nǘti'an9ƬIax&;[z]}oز{+nceع5SX?=X|f`X7v\Dܢô߆b,&M5,ٶIǘ}VlbdVݼqW|aV=żl9wα-o^clv zϜ#vݼEʃxKi\L4JCl^֨4\RsrIHTHU)dDЪkh򵨴JZ5J\RDQ֨ɥ)JG*QVbe-f(*sR٠bۦEtlהkWs)LV REs$3zNӐ_>7A 9z&f9Sf0hlIAzD?wVe8p4O<&/_J3yy" nG)>@k$3|xB2\Zo/7p{tX: <:ځJ<τө0 Tz 9E,EdR(#һO_B1h/ЮDϰc>x4Y_ :A=;Vm7k{4jH_з_Oti6/n2j|_-K֍iҦ[7E;D׫EwY:DAITpjIPhFFxTaDDBxx8u>Awa}#BoGa{@ 0M #Wa:Ae80vSSv&vE $xj'o:J_mNz/M$fl?Ũ,N˜{4dDF/Z 3u0s&$~+ 7e+'3eF kكts!aR6Z%Yf1/b,K:8sʼnY ץXb$FD;Ɔ'|4kNdslyuOr>Ǟ<\̫g\L{esO]qOdbf0'R1ry!Ւ&! * 4(Tr4ZTJTJ*)r RD&FPxC((b2kVIVZ՜Yِ͒ϑ;رs)))wc;W-Y%LX͌=&soNZĐ_1{r=FVV6y: R9ɒ?kdJ+K8v|/iw(҉9Tm8TUpu8]%{>zt@QS[Wǯ-d**n/̙锖}{W>kUhu# &v#NxdA!AV_ Fp £B]>3Ӡ4VF?BE4~ɷnj@ @@ `c0l.$e꽴v3%|g) .|ZM/v}~G|zbfLX1񻙚B̂ [˒v.&!~BΛȐ}HܿߗMdN(dS"V6%-ԕlٻM[ش/OQR3VVGӻxzK^/hvp ZunˠI5m[%&~Ѣd-%+1YKk.,*.,1(/˧k LZ^;۫F S]FEǒ)-Uttb,*X:>IL8% yX,Ÿ\zDxAfFlz6K\LӡfUc&LȤYS3l w%-v] Ӏt8cƳx:&̚B>}:a&Y ?~ˀ=2iui={@ 0M  N\Bk4{%jN>&~׉n|9d?]Gۙr?}maܶ)d{>sUg.ev?Ō t&Sf _1 7u0Y}G6.a{Rrt')'ؼ'}'I\`e~l%%b ?|I:z /p.eSl|Ix[w8_qs.Jzn޼rsn@Ý7<gPR)x.Z.,L+%)C"QJdHdj)\qBJZkxiS)PdR^QlPc*Wa3ahYq;q9 18ZlF]\QQaf`K18mx]%r(ʌ*Q3>;6*|*+pڋKK xxK=zr2|^~__u`,0e"<22>ß[?`"PaptWnTYL%W^DMШ2;gpp: p:8Zܮ|%6S& nߘt2nCҢKWqy˞Qfaؖ}~b׌];pw43gbԬ[OMܥm7ҳ_bŲ~*bF C,Y={СgW61ENDGhP"BPd jW3-,"Hkn8? |ֻ&?.u'4+ȗ-'Pڌ3@ !LodrIn=͐|KN\cɉ봘F޳h?3M \̏h7}1vVETV}ˏa$yȪxwdd^~gܺ)Ob۶Ӈhol޹Iص;2cbnv<.L\72u&nKè ,ޝĴ X;A &%xc?}/pC6<?9~7eGV:s2#2OEJxN"7xDB$eqRVILPC4\YYLܻL)B''crEx4d tٔV`FA٠e/T.fT *<<,2 eb**yز=YM[ry\%T |帝eqq;K4xݥ:R^)*)#+gp;K{SUi5P7Q0p84ch)_SfP0c2%8]:<ܞ>}r>J{.o)>=&&۷VY0جZb<<""V tҔX2aqc9p Xrn8'x8~_1׮naHpeertJ>=ݷ~ҫOO^9WLQS4(/yM]^|>65ƒ^DՍ"^M "4*5VzCѽ GQhu޷8zդ>lGk@ 4@ F~8ыVnDF-E+4b=g&vF&e8MGf؊DƮMfNa&mHdȲua@ IDATƭf㥻'ϰUv]wKt W,c8V̈9y4)عf>-;k$ VaƲՋc3{bv\:Atcr-&me lrIoNcš}g 6<ɆSG{[ΝȣK{ΙO[,g$2^=an6ҹglr dieHdļM'W%%CCB t%rd"D2"I6Rt\\4yrޤ=y#\$0I(+PajՏYMy,lFM٨0B)^W!F*|TMTUX VyTUX˩"m kK8|F|.=?B].|$Xqqʰ;q:uez2<8]XuVSN `f8:FlRB*^F)UD `҈ծ(-.`r5}:-&I]]^"iWa6ʰ[Uc1YDiRtq?G ӯeV 3v[ѯ_7:wnEMD1t՛1tkWr+WТ]'jϷ[&VAAD֋"0"~_'$ob>$aAkPa8=|լ? '8"D{@ 0M m{R8yN_f}am?ܵL_wmD[DSi<`4'Φȉڴsuv\gH}cv_alrmW ݾ¾XH<k'{Wf$>@ẽ\}yGKq)%7s3Hs+ov"^Gytkgᇷ8%9gs' wD\~7/'\<7 yRT"nvO$" )ВV&]MF+Q9J  rJ(\ q\irZDR#%WɫT rd *|1:1v}>r9Rslf V5=&_ב˪.11TJ8myʫW=^#o+mցǥ3QZAeBEƲ%s 15cmyTTTTcRZh`4it?FmӸc7Ѝ;w'Rw{75Q-">]_żC!|Xƿ|CpT jLPX >' @N@ qS0mU"s6f]LY):AV0p*̊#vbƭaƖD.g ,=p-}6+WY{47np2q{SX~.gׅ|\k{c7.ry<#{YM+y(YY7M>N>ͥOsS9>b$ߺ\yW\ztKrx$2G,HsFZ"-cqUx֮i0\3F/-#%e;nYK^AfC F%F IUSǧjd`;i#*STUPq8 ynoW>vgVK٢bR^de\ΝMbVbk0e 2lV,̦lV)ALZĮ];1jFa 76Ӧk/:AU׃/m[ҾC;vB}hҩ'mҼwcԹ߶L]iW=^ZD{V[ZiH_Ҩ|K]j?~ާuLH` '8:0>lXzA1#?4@ F ɇnezOG̬tN3W2xN BQi1b4~_A c8{3y }cbpɇY}(gS_<(kd٦UlIA5,۴k8cmu3{\欚;9vlat?s3Χrss]K/)zkJ)M4TLRb3y,,JɧB*IN%&#E"_A\]}<\٠ĢW`(bc3kpXZK{ ,?__S\}ӡ*,i*frb*>^w9>?:Rd%x\z>.G >O9Wng.SFɂ]h8]8]zb jSLf " 265Cݙ͡^tp8mE%:b|\B" 2 &9dQbٵFY(jc6b4H1$fզb6_}Y:fv%ng9Ď@ׁC2pSHL}e+bb:m"4gߛ[5Сd6l^A瘞4҅=Ҹ}wZeؾcb7|ש.1Ӯ|ݥ6߶ES0}V}>}M;5|?&P烚 %~{@ 0M NWnBz^Aɳi;&C'rԯkUq=׭el ͙ͨy(9 Xwd+"n.a$eMIa5,۾if8f/70a![oa]lH-ٲ7 Xe $x2nc~xo8^>rkνIZV:we9!RCYOUx$ETZ"y&Rt <~KE&JγIxL9R2dYd3Ĥɑf#UIȖd!U"-J';7 8VL&A$B,$+9JieE٘Jd؍Z%wtjRc-nL*z)gQuX,,bq`rbjy ^MW۷v>#>O9vk[Gq ]S׭,aqaqwpZtq{J t8]8Ř-y%^,hRci9~4 M]Y,GW,B,yN˦0kzwU/0myuZ^sq\2<=.AZY,D3YL2L6IɨdP^ɔݮjɣHÝ۷f?wE^oۚZOu7߷;PA(7G{ 0hNOB #,:Znj@ @@ p ÖAShu&/xZA߹i=b2̥3i7v<eL[OLl?}x$0kv:ìۖEmr&,O|VUXw+{/b}sSw.| Sf,ݶǓI<c/'mR3dI;L2/(;9oB5ϔ9Ԋy,֛Tx!Jӹ6w^=$UƃgVd&$K-"[#8lV+rHbdIgHR-C$"[DDHDI~T2ߤU:129f%ALդƨR4uT^mVV} IÚGYWSU׭oo*))5ui;R&JpX J{l+{9vv篥p>sJoqcҺ{_z'v`Iߘ^8VӱS[zB>;ܓg93b@D, (IA$@"(*:q޵+tUwW{׮݀>gfZoUuU~>Fe|~;M'i]w^c󮵬,K_W_l'GM8yէwذi&O=#1wxMƭ/wͺ?W~?9Qf0 wFf0 Oȃ`̢{z{61o[<*2c2c+!v<ɼU;Yħ^dڲU<?CM`'X{7[8Ȣ %߷ PYvNQW|^cMuQP)6qU1s%,rm/mui B"v.8h 9gR6sR{hT:0]Ze76م+c)X~/6ɉq fߍkG݈M(d/~Y@\He!5ie]2I?Y%DN ev)rtA2"H] Z)"Z,QE,at&(\LYQ4\**W.)t(cEKirZ3__Jow9~O3|})ɥΎ4|TlXjXjPl)hmD:/@wfL6#oJkgbxӽe3 VDJ-tvPU&L:dJ_TENP*FӟT jVPVDr0\=BSId4r=D(DL"n&LZ6/ŜT[m$.z&Mݣλ6m NwƛgL:Q1,Z<*,Zb)%#iȫ"niZM|w%E"AM9hm5+97>S_/wg_`0wgi`0]Oŏ̚-L^ ^_;xfZ_\ԥ3skL\, ֿ˙*V!ϮĚw)ٺene˯7X6^غ|mg_)><9WzUs5Wqt 33מWFr5qQqrSp\4OvMTy-3"QmomkbSj\A/f&JNJepRjBDK(! jlO^k!mg.R!Fg!)\j/iVt5H{b+6to5܎ .w$K Rv#ͷ￾ݟr1.Og)Ε__Js=ANk&fz+>C{г0}.Xj%p/6*fmt^jAb RBZI'B"ZXUVn vi߿sA\UQ/"InYMvP3nԔJ5D:qѻ?ǃ3\ZzAv`Fƃ'2mt&MO%w ~J̒PVb5_~eO=B(gyah^~? {Տ} `Ff0 OȢWVa,[7mwYZœkְ7Yn=sWMn^.\%xi^~emi6>{~_ʔ'9g pVpoㄳc*6w#_Z(ZIk E9luTH*N+JDAEJBnj9)S=gq%\fJ{b.R^Bȫ2(l<V:Kmt[i/t=ƕ\*ii/$~e;:Jq::|ݩp=͕ /˟]@g)˳J .RE\joXՂ$BWg3c b玍e Qz"/6BBMχH+:'_l&ݭ)K!u(hĊ +Xl~b!ʷߪx~|.H6+Ӄd5= T?9=ɤ2i%-U%t͏eerY5#Q22i;JF^hisϨ^nП[n͈Q@ aw2vF;3~h&= sš/zŽHBKL4d(:$nj͕r<U?ggϟӣ/G0Տ} `Ff0 OȆ=ٸl 1oի={yq;l[}sooMYn wOĤ xr{7OndWGxQ^*ϰ4k񹥎FkTŗZ۪9p 9iw|fN88KS%gu6pm洽ZMmE>;gluԈvjD;_^LhS2Q`5sQ :M8p:[.ٍm`8NIU]хcC\np+8%7!(|F\&^;DԁhjO{tBܙL+t(\*FTR\H%djӕ4|%%s#I{R.η_p3ΕKJ*KY._R(J;511\+;4: i9%$Y-Ct7*&љ&oCχ_Z,@wbBMCۣ]ݡZņI$e ǎo%\Q,4SP{GOrzLO:%^FB]AZ=+K]Q3^4E (d\G.C3>I-[s{D;!cw<SMfQ}|&O-e1{`$cYڋ>uYpq S)bfdy-T_wIf?c8wi0 wFf0 O—WrVVoΤO蕗Ysk/vyߧ<ڻ䙵k2mg<}^ڻwO>8T.' IDATS={'~>p |r4q,*S9uէy3=Ǟ8axSgTEE89PAIs-'T V,Ӗ%;gM 6D'fm`{w[hXn, ׎px$7nхSp9\|uvA{%7v`X"|N,mlN3:v;~%!tWjX<-AMxe()RG22ɘb6ѮGhϷφ)"P3s-O#ʄQӡ'eJo.6R>Bg)ųGxsrݲZ+6Fφa4UBS%bmJ><|!JFIg!mjzDʋf C Rg{ؾ5lLU$)LEw÷H&JVΝR+Y-Hgg|.D!&%\+GUD;8u|΋yɤ܄CƎόysXq5'Mq\s 9G0YL4i'3j}7#90h@{y)Od߾1*Xq:[UdSilGi M{xƚxgI&4VW_r~k`0 04`0~BZM{?b}c{s-oŪMX=6K6궷xڶk^Mxi^h/Ǭ޷gd/q+v;ΓGm mˇ_g߹#}|?Güwx/U>3gT6NZk1qm⬽J3]K|v.۩lTXip;8|$;z~'IRg`Xf׊StO}c nD{ENgYv"NB~;B&@l}6>' %&jT~LAm逸G(Q#d"ZF./H:0=ח Q򚟒]oR!]^lكx\5,~a.GԞ`chRG/uq(67#DWT]LV υPa5-N2iUgQQ2jV0K+"U"mh*5U v='d> pgf5%purWe"6i'*pc%fZ\B<(\B>BS<蚀ӽd57GL:Swپ]cwh}'2bP|ap߈!̘=NYӹ ؇aa֍? JAkMkkq|/Gi Wc;@{LDXHPI_=^-~k`0 04`0~B6-[ذkou;w߹ {vևan?omΞzgf7XZ@ qѬ۽k^cŖ l91[{cݮyo|}|z(;~K[䌣:Nk9ai[5Gjb 0UQ6QP :8oNq^[-s?QP4qTMvQeo w6aZqnɋGbq8_u_p[pymDN袹Y$ INdN@rI\ %A kBtqZ 阇t5GK0z:GųHC(9Th#WRKe:K1Z|v%p=]QQF)AڻZAszR{BK­7BZvJ6JJFI4֝rGR.FQOIhH"&"dDHZIe|i;L>l>LKQE2i"uWi\H&<]+Z57(k.L PK'hZɤ r@XX(W隈k"&tW4h6Y^}s%K^X7!~Aύ}Gg2~ `Ԙ9z8sM7_0 z7ɄٸeVG'NFxf-a vHE4 pM0-ɦ(q㟒IJΟ{g)LV qQt5BgLL-ע\nOBxs-d͟N$Mg!GfZ1A z5'v-H$}()ɥ[)(Qm^tU&jB3ɄƆ/ilEݡY*#Oz'=Y;d>\Kgnn}N^t~m*Zo%#&&MTG&-vFT?-Q;Y-@"%)#  aKrzB.f|u׼4&=$V8='hh|yrQ>2A{suo1r(Gq=w0k fı +/?gfΜ<⅏Q_{68qKaĢMdV^y%Fc_3`0~ ff.cIlîwyxu{uf?ǖ/e~>>+{v1vWS'6<*7Vpȡ\p9mmmik5-UPj⬹S0&&N9V}JgU fZIvp< E蠺ą 4M ׁc2i2x'>сlB8]&$I[4"EF\Ea@Xr#hy'N< w$$"JZ,W]ei- ia %5·PH5s̚90Gٽs=H^o&PEU|3l*SʶWFMPL!ۂxeϬ'_N)}>n9XMPbmr%YZL~F'fe~xEVA \-DeR%-rߐk|+\3\oSУ-z6vdAl--f XS,4S*F(Ĺ% VN~vhMuS\Sŗ p(i8gk᢭*unƋT8Ys3hmԸ8YJ*k-J6-.94j aڈ#Au4Xh\8=Vꛪqz"">Ɇg&"6Z&NE}.DS,%A9\H\$[#рd%"&NHd"jB$Miz$E߃dsOMϝ˜?q#ʧ,8FM*!09-@^)f'aN/W()@IIJtSE||3jGV)[C2k=MKN!Ռ)QB+)nXR }xe5Dχ(#dҾ|\x=SMχH+"R-6"wW5/K- JHѳ!! $__S,*/j|-#<ۋ蚟Lʍu?L.+EATqOΑy73``M7_ϽC2Aяsc#S&&?<^͸1p^*>ؾ}B ر0+|fr-J\I s~sO`0 ? ` yb=1'?ǢOA^|c /2X6g 6-_|/qN嬥OΞαjVpiSp\)k%_.rVlj ,^?ơTd炩Par^G`"98vOcZGD&&zhh3gpI.jL5ؽ6͵MHa/>8qMFs.D*ۀր$؉E>~pGHȢM4(,=$5n)xMcbax-S:?oB{AOpV>7-j:Õ+Ȃ/wW"!B)#vh$ ){)~Z3(}qF6'CN$ExPmQY%Do3N1[nM%}Zm+HdM*%OzIvU}}_fe%j랍}a5@VjA2JDMA[$i/YMf]AZ9 +tvaXnGhoJ{)J{)Z|MȪ>tC!$L5͋8h6hL1s׈ӗFpCk} ԗosnu3SMfڌ2nzz#M[>ٰa=g<=wgG={6ֽ+OX4!{1Wfַ>ocm<5w[׬-ٷ=~;c7XKl^&P9o3siNs0'sQϑӜ9|Qu꿢gVKZ̢&JhZ 7URmkpbZ09ME;D3ހׁ/`qZ3PgA=؜&+>ߍcE xE'.Hԏc`2UXD"vK`#e7Ͳ)NDߍ,: Ƀa"(:&’֐A EO5co:Ӌ#ټ~@ՅQyK| dnw$no[*7KDIBKh)ٴH&!@KH<$cn /@*!NHᒱjOKȆ:bQ;jZxR _~ʪC$"67- H4 hkuIP2bwe"./'H D*Yn9ڮʴS tUI誌H']$4U"2ޫk3F^5 ]H%d>I?^w=^O5-6D[IVgaY)L)L^k~yh!)i&R] hLN>'sϨQ=wFͽnQMy-C#c츑\5'ܓcFpM=C\{ 7^ӫ= >Mdf^2 fϠϠطߏ} `Ff0 OȤ3{ Ǝȑw2f}<0aÆ aܹ̙5 ,ѩÓxf㼲V<fY|}3vnc7ٷs+?-o lzc˖=Βsygٺm~ys=Uzl&.8oosVs$\4UQnZ9Z V&gv]ERH+:q vꛪ1hn 8 J,hFH\%@ =4^B A@rInDNt]O׈g^[_' X5GMP^6m|q3( 7ivNHrQ ZL܃򑎹IH$[iJG:%BMd)) %Un;URHU !%\Fl^xf;AJZZ 3ޤ^RI/@"%j2i-vl,ksd-pѲarz˗\lCHdR2)7#pc'WhooБQ3"YUB8~C/A!p"(Y-g|=D)R!]ԌOVU}ݡZVjtC&4a Gpv-xW_{}~M[om73??koAau=uБJG1# QṘ(~dE.+SrY;P+i>4E eEƐ1}D7^fr'3fNg \}CO~ ϰ<8u2SgNẛz?np࿮/G!8n3Ckznzεc_3`0~ 2cڃ<9ֽcNf2sC<'WprEˌ౱h' :"A/I@r"d-,]+X됼z#ou{5M)"hLBBP%ڋTjn2rX'7oIDAT>8YE rZ9ee\j#eeT>5Wk"YU@UDJߡcW_nnՓ{]I>}է}a!כ[z䁉:} ;F˯~> 1kzm>{[5=C˯~k`0 04`0~Byg>ĊO0c$>>a+xżd.˟~ e X|=0.~s11lXYOdX rfOf+Y{-_sO>›u/s 68XD͉\_K`{^M̬ixdPVZ`DlMT[j`X;ǜǧQX\GrH/c`wi4lD!$ zq$E& 9&>.kN#!ɋވ9^Fܖz\:썵uH&֐dTDKdbW%%I*!t\&D6Uްedɦ(q=#&]|dS~}i-)%%$w ٔ=-e*~N^ &DrjBe۽r'*aZF"uwh ]$rZdM*!P"Z(ˤ})JW33ܔ&~4ONvWi-#EIR~ճyjB3|5+fߓ ֨˗8M I%'w Ṙ҂~i/9/-&U~(^r60#whn/ʈCowG_ ؜<0a?^E޷p߰! ?zB#G뮣g~>;7뫮-k`0 04`0~BYgܙL0')>~//[ċKgXd扙x'YB/}52n]<:cK0<3&~VkK-xWŬCX:{4w^Ǻ|H/ɼ:fOCaaL~O-c1GI"X ot g.&oZDuC%^A|*uFsymm xE'r'vcw}x<FBfKY<nB؉=7a_׫߃4.d c#u,xJa'6t%GiQcz2@.")iz:H6U]&)Bh$ tY"ٴHk¥b-)e y%HA kB\&@A ujٔ5!v&eUQ2AԸ\RB+6/VJJBM$PMWeTo[<[6b-MM$cn2ItKAVL[@UEnQ2 %b!ɔbB>LG{ ٮ6TZ u-0ܑ l&p B(i/ ȫ"ٴ&f-jڃM{@3n17 ̝c'wxz =o}\{5p~n 0rFɭG?i2Cψ#'7 3p oWt&׿`0 ? ` yl8 sfNsL0Kx4<>u/?ˋK`,=_\+-[ٸyΛӋG'0kh^_e fb^_2LA72v<9gw1nM?5MbS??&fڸ[0fNȌv eΌL7' cz/\υT9u:<`B9*j/`5 ^lN a=6.lB!HG}y`i%,9xM" "J|B (8h a7ɰTԍs)BK(->2Q=.RA & vedJ-#SdSP WerJR!F6#Zh_L| z9JG.rl35D)\+aԘķWtkWhIyIQ"|+JJ&u/5>$LyououI ht‹I]d21'ɄUкfi޴Y 2RyqA(J‹z\.=oMUDb鄗Rf!,M',Yj ly6[=rxLʍ񢥽aZ^f$FeȮik{+;x=$=.#+`\QY;=tfMtbtpTP֪( j.;bsޏs>O^p#fWIbR<*5V5n Kfn.qX$5kbX|3c%$#ٳ^dż˘tӾ![H&B|4Vp4 S9x15s%Ters-'a_}1+8RGSer9PN}E&Yj3'[*87߷&l aJvXͶIٶ5ߟF]:ʲ")ɈHcyQ4T'PSG}Y<$RCUk؝l* *84s/.ݛ7~~re` evQ͂6x<͝Sz D{lEpuZI'<,a=tSmSK?~p8VCĻocZ -=u_*.>m<=j jP {.8+}~ň5L֏!#' 9K˘p!Gn},3fkGUU6#fu4 G0wbl&2:ٳgƸdkװv:~<3c?1gE0kRO6#Bo!aBrt닩(E4@C>[ZxDZjxT/5RSR^SOh᥽WLcUI4fm6Y)ʌ%3)֒NBAF^$6dYi{8ޔksyXMPm [)HNF2v-'& ij2v֛zW^n}:9w<ZqK/suΞ۴߿knϹrmW7t@/wxS'.`[bxx!C'6SN[h?suԌ:-(ckӌij4gt+A\^%Fl 8p;:Q]}̯&C4'4;Ms{$efnG.w9g^ET VoXO~Q>7fy2bV31lؼE#`>ےhlbYEĬ[ge`)s.g5O6#Bo!aB<]h($Rjy,9yxG-'SKCE.+s8XHs]-{[A~& SDzR4q_d%MZJeqnaWz,E;)M@M[5R{N+EJ]A IfWR ;Sc9z 3y|~r,wN5y7|)g/}[9^[=|v ζ~\q W^l^;=hB6wKܻwS7`-;ob5vb1ae;bD@c7:(cN3jCW( ۂkmPDwZ>N@)\FT!ܒjM((>tЦϦB ΄ncB!j'y^ A:5הAt伴~ToxO -P# ئ*ʼ>L7•iS\ߨ:sv+S x}p5T87{SA_33b?rCol<` oS8]Mmܽg/k:P `l*@j=i#?\{P]ݸ>֔lĂ%+Yg{+IL'9m'Eż09X/W?>xdDeμ%=[73g,,`H/ZHԺ -3G0+b1)O6#Bo!aBr9+4o'Ey:7O J.ESMɜj9ߝnǯ`Ou&8RF}q24TtO/Ziu8pټ9 oOvgͽm\vO}̃ݾB;tܿEWG{ 3ݎs'cC(Ä6jTp Uj3T6it9)cF4W( c&4bƧTUeQ&~͊Ob?̸Σk% 3G)6S#ʘE >͊ y ]1*hkBUеAtm0ܪOX7EPxzQ}x\hj/̓[B\>0T$QFLB3׼l6mU z]v20)t^B0xy hpa;֍CrFlbcܼy,ZҨÚuYȨ̞?4^;<,YeKۙ35g>b0/b!/̙ˊ.]Ƭyyv sfifB-$LB!C?ڱ4Q^y^N5W}.(#M[#|WZ 9XEqvY)k)O +e5dHsU%۩.MHK{)ȋ#'}#U4ɣ:v$RQTJ EMUo%?}-6k yIeXIKc:Op|5s%9J OVR[AqV _ST^'xop9Ə-_.ֳ^,^GFa箵y{8{,篜ܼ}qUݺ`oRpY1wLJ315 ZaT Pƌ`Ms Tx!~}^ 2O{׆'BU{>ۂ_ 4>A} (A}q6÷d8T-nD@"Z Ӭx<9|45tB4n =\qQ=Nt_C5hhv_+c=xݡk|Lo\4`BSMt3|hn! 6>݂7QKxzP<}፞S u*cߜ<}].4 I۞FD^\MDŬe˶1U,X̹sx~Lf̝MrNfΞŬsߙ̚M,Z-[xnlEG3/bs^3?dz3gm|՟mF!B4!;&q@ e4n(Xs%-WdR|p)ƒ8%q2vndY(DuI*ŹqJ8x(/I2j)*AC}1ySI8 +r8z MDq.v'Oz: KvPek0wqmɪ?M8T:< < 8 v1#0 Ui!0~Վ궠{':k4=C]TdUO: XC<'TR?N6AcEqBafŧY7*T6 m|v f% ?Zu ~^ۋB!A4!;_ gG8Udn"/[c`_euE۩)Fsm2k(ZKNr %;*Jt6(/L`wV.'?s5e&ʪL ))MuՖ+k)(e߁wSVCqQډr^=^‘Lmz_7^o(h%'%u;r8x4|z<R JUwR0~¹kÅܥsy8_:@qe._ǭ\u3ҵKno~{]gvS'cCxFL(&&N3Ѐ}ӌiDBU_ˈ DP06n}0S5h69MLU }9nOV5dE ȣ ;W뽂=܎|ۀ7Ottf :i&~˓O,ig2`mB k҈ޚʌy X1,^Ke3seъ$p2,YXmO$2j%ϞleŤɇ!_}Ӿ!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!b$LB!B!bv;OZB!B!onj=C0|#B!B![`t]p BM!B!B'<~`0pmZIENDB`manager-skin-selection.png000066400000000000000000001667131325274564300402400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR[ IDATxytSw/Ssfy3]N^ϼWU3JuRUًT  I vI!ٷ "ﲵZڬ}wp`rdZ~+]ɺ?߯؏cG?~#?C?`aaa(?%pҤIɁf%͆lL8rX _>HDDDDDD?~`ĉ'""""""#*~ɉ}"׮]@żٳO>$D"駟"3- aD[wHgyftg#0 O<-[VmmmX~=x D"9rYc"IIIcƦfggC$'DQQwpiTYc'BX^^C;cDDDDD$.KKK+@$aƍo0ɓ!p,c(ވr fΜM6oz<=Lg}Lu:~@$aÆ 0Lٳ'xwo47n `0r/eQCʕ+XvmNV;V_|aDXdI!"""""Y!LMMH$֭[G{ { 68884@կ `4G4 燷QXX7DXx1֬Y3l~iD"M*&L} {}>JJJXƱ 6w|Z .… ["]v⛅PӅ|=lP)joo4!5551c='y'0sLS"޳G}~௅P$:!_k?<>BHDDDD4͞=;60333e$-*(E[uk_h́MMFYYYظq#^|p{wYKewh= al`0ܹs+,DDDDD'fnwܹsC 1wD?C$nij5D"~߆KPx^|>1+pСn ֭[Zw >BUu…NsEG:`x~u:}WTD~.w9\+-- 3<.L^/!d!$""""JL1->iiic6mڄ6\.8ttt`۶m]6yI7nlX5x Ho5ͤI'D.:.| oF׮] `h`EEEzhApOx{,DDDDD'\.V\D*s̹zx#)pʕ{;TBڈNSUU>˓O>_|1|===LS\\_5G*w/P(޳%|駘4i~gԩSzj{Bhnnƺu0eۿx ^4Xb&Mz oRƍ1uTń 0|TUU=r?} ghVB""""3j(I%)B"""""$BHDDDDDX !Qb!$"""""JR,DDDDDDɈ(I%)B"""""$BHDDDDDX !Qb!$"""""JRxlԔxT?=SwR`fn?=S-}ޅ󡮮.h&SWWDZ*U u>}0 OT}a9?=S @м^/W&ΩX*:V2 3|~z#ÁV0qJ]]Ǫ8V1L#ԱJBET1L < .O,t%j2 $Jt"KJ@U0BN ad, $J^U, Da!=B}e%KHXb/Q*¿(J,VF#.lG<FZQa+Fs/$l,Ch'd H*"zDX٨ aVjBB8`3LQB8Xb/Q* AS0 g!(: wբ Y々(e|!|@UbU)&*.O,E0>Xb/Q*a ^aAB(Pq-p2p`01BH{2V ˣJ ^[ ܖ&tԜ,F<pa`P[2R *U[ xl-k`SvCzew0օa('B+Y>/D: PaVc([]%$#e|!|#_UbSX6Bqy_Br_@x TrӨ߇=BI(/6o/MP%,rQr} $5gPʖt֝Cmh(:@e911lG<F6*@ ~G(fVcB,D(c K,RXʠUށN @1΅UWu.=0hy"{ `)Nsuxz: gɇm<<\~NfXK0Z}.iVW[vΦv,@^ _m /VA;p6/c)\-`*IEͷ. .O %p,B@|x;>CSlZO+`*I<-a!$,D!Q*a a'ܖjL̆NB.CRO’ԫW,Gjh,*V+[5_@ŪEɒ~oиit_~ŋMAѼ$^D783Y=Z>?EGːz-C (Y3貧ſ0,`cU0dI*%%MONΗGrLbU]X#€b9%Y7^W"?ȎO.{*zoLt6rT SI*߅dJR˙zKa.K.gLa* k\x$K:7 y(_A͵7a(Kdk(LC6|ىDYB}e9s& }hdPF>1DP0-B0OU, Da!n!$Ax`!d'Q*ϓGxp3BYhxX&Ip xq $`0B(PBHr1}zq|&X%BZ,Ī&`0_ o|" &t1U!vyb!(:,1 ft:^ e1c/LZ,F p8`fJ@ƅnIcVwn1+Q.O,E'|ىDYXn7\.W܋#̄>,ތ3XZZZ>,p<`t8^XY^h%+_dy a0^Gmm-Ν;["++ uuuzxۇAFб}g<e a˓PWdcU !Dx u|!+*,kBk!E- BB).O,F!6[Tdff rmmmhllD 0)pdBH*Ļh$Rv+4ntU2F5m3*Ա*%%0 ;whѢa d , ,K|x` .xXY@H0@"@P ''_}222pM\z())FAcc㈷ ?,Mrwrm&oZ`wym.0X`]Z-ىN( tvvq·BBOBeTTBPܹsl 08|0N:fBᇅ0ycbǽ}Bhjά統Ɔ \YTa!Bx!޽555n!t:hooB*e4r]X# dE{Xo!t:Ǟ={P__sAyy9:4B(B$ԕ,!XB8cќY+B(c a]|Cf!d! ¦&466bӦMGMM 6o<<օ\ȓ zz-B!wmJ Pف;Z,» a򆅐0YTa!h4Ahh_YF#('ȄR / bāFǺڙ>ugq$_KpU8^؉Njq^NJQDe+dObZqgJ>;_"gӪMX{Kds ۯcّ"8_ OwJ'JpROćG R vhĺ3Xs sfc\ƢX} n`Z(mABŪF<F6p$ːJƚ5kRp.)B}2r-fBz?Yxkw)o O26ݒ7<^@)Pڂhֹ0yjGGJÒXy*;Ef#fa)xgumDz#EH!R?V`ՉR:Qէʰl^/c:uk& ׸0>F:VRߦ_;Τ+w5W=feiub^Vm]h7`[f8C6< Sbl?V'wbފ9XdV^fnc/a[e|p#};mb;~d.&X5 4\Cg'Ȅ077YYY u q.-n@٠Nȭ4ih3x --~'~hpIDɇ6'xUj \rפsAa IBˀ+|%毿Vڂh:!2Fˀ $_?GˀFNth:ѨuCB5HBrz{PAl(AJk+<~wqN3E-pӁϰb2|k-R#[4l)v؁F]vv?¦aފ9%ؖm&/KÁ _b=X|wPܱ wŔԷp!Wݘ1:o`muboa!?H ad@-CI 16-+.O,VNgTu!,**Buu5 x`0Á={@*͛p:QfTF0I+YB2ұ*+ATpF]Qwyp^[Dg0ۊ;P[NfCUWY܏0Q*$,;^G W|\bPFdFb8qAhZڵk(**BGGjjjPRRkf!~Xm4@L5Sl Z){xB0ZQWWwO!oB[= p e hDvv6 `hzǁ_"==H$BNNN[YBD-. n ;?AnGv19/qk(7C|ޖ װh7 Ľ am۶a￯rh4f  bl6o2WBB_B)Avv6b1j5b1.^M6a/~?~Bnn.\.WTB(7%b! &zs=n"{?nj"KdH=8'݄tRj7H->(ހ~?K݅A[;.jjd!EӦM IDATv!|%[UX#x7P(+ :mmmP*4 RWBM]X# d9B҂Z( ̙3طo>sl޼6lm۰w^>|.]D"AYYY{ ?B/.;$ԕ,!Xz3_zp |܌];q{nN5R V 'K2^ł_㝢?]A HGy g4Q*$/ΝÙ3gw_! |uWa!d!(#9PR bϟ_`֭Oi&ڵ ٳgQWW#BuUVoH[e%KH\.X\&pR6Tz 8zP8p7?ǵԠ@w: JI<=|rBB,IZvR񆅐0Fd aooox7Z>}ž={m6|'ظq#v܉}رcC}}=Z[[zxBtm]֡d4h\y) af!|&pD gTp}-tp{ mG cg;Xܹ3|۷Q__+0=O5#*LV3NpTBVvW6NmkB±h`C[:v`8X}1Rr-R4 pkvL_+9? ) &䱄wBu_iY(ljGhitT_Gg%tm҂s,i(N=, 5謼6}:C{lt^DgE(7:CҚ宵 |b!LBH pF!.2 hmmEff&;`׮]]F?3ڵ ɓ'jD\q(V -ƞ&ߟ=7p8y|>m:Jn<D+ J;VԾϚ. lM  ߋ`0YX&7WsL)L?~٣ aךڰTDךyOPm]35WσФMAvF[oTA/Am%/f_35vjr(I/@|Pc ៲QQ a adhjz acc#***P]]/ѣ8x vލ[b˖-ؽ{7>'O… AEE*++zxB%55*{!Ca Ba C0@q; თh'd H*!B˅{T^ĦY~سfX m,R+~7 /_q}S h6x(zm5~x{&t_m{+:6|}D,2V *,bV\X#B! (--EEErssqY޽Ñ#Gpi\t [#EaW鲲U'nyX!5x-o1XP+j|;kZ5.k -/a^ͿbZ?ᵼ๬ y} xp0DXAx=xn|#[i0Lhjjݷl6LsAXG`!.!Bߏ\"//W^qi;v 'O⫯µkPXXrEZqep,Bp8Xn&-&-fW̯agmŦ ~Y?_ #cr?'~Y a |3FzѥR@.퀦[=񵶶h4ٸvqel6### Aii)n޼7nrUUU}6q TVVc-V0剅0(Cp8jڵktΞ=ǏСC8x ;gŋ@qq1kB(1z2j/|R`0A {0ep,%R! 眿[EY?  " wokB+~sdzYAFס{ޣ@ 6څrɅ{ V]oZ^X㋅0 LI[ rو j`}WW DR~br())͛7܌~TWWPB+Y6-j a0`@AA*++W… 8uӱw^ڵ+瑕J5n4 ᗷx ru؋ㅝ#S܃ݸT݅di'n gP G9X D< u%KHF:V :N] CS!_V26ՍcXRU+f3.L>^?cy+|f{BaǸp}8[m¢u 4,dqx,2V&a!|7ԣQ-`>^fyԞ?Q W aDZe\ .{g3gbHIIAjj*̙Eaػw/.\rn#\DGӊvΕ+مj,9\*tNbj¾-8ӆZYFzX$`4V?QBB8=.,|/Z1101_ZS(Zfabx%SX.`@ ˊhD__ HڊPT^b ,rK tЏN kA?dd䖡ʿZfϡYq7V?QBB8nl6Z/}{F/o&sO 6^:zf---Eqq1088"7PV`4`4@.PӨg!e,IX]^HvB\^WQB(\BJ  .rtw# ww!i?q.Tp tgTM&4 lAlk?Ɣ )/xAJ`ZO079`0BJ^HRSQQh2"Α!CҬ?MJvgTa!^SnPDw&f0剅0xBÁd2bAvv6<E{{;zzz`Zxm!dX9V=\"B\\ס֔O8 Ćb~x^rԠ--- |tuu%q,69687k\p x3FV]W'4SM}HL^_s<,ݵ~[GU a adFG=LhZFoo/J%$ Q__*eeeX,FGG4MxG:Bm%$#\ JKKB455>b\.4 ***puܸqyyy(//_5;;yyy0 U+55UpU< Cr!L0 amw-&L:e2L6ʷamZ,KbލyX n.~ؐ_|=P pųx<jCVC.C*B" hnnF[[= @Ty<[l@mvB1BRP__bcܹxw1o<,^W֭[~9sYYYCQQT*RǻE! 7a|$,0 y%ftnlŒXWcWo-Xz{)֢\N<Ƌ a @ww7Z-+---hnnFkk+ B'd!c'{`;2tX}M iDgD-wrjBբ.\Çq ܼyP*lN3>,wjY%*:ШZE}Np[xlԔx}B۝Q0Zu-^,_qa?#ӽp %\;ǶTZq>,vۆ+q'Tt^{(cUZZKIr!*,ըkg*,Z[X# dY,_rىottP* _z]F;:: J[u:]Bo!̒^1|Q>nb=8Y+a\ً_ <2j (ƞB_&ß k္ Bۚѵi4{!I-kٻ a˓PWdc[v{T"Bx+uW.DZ+%qN:Yp2rnc^qo@UWN>pqܨ7 ~Feb!LB8qE|.$DK,50-`X`2 LNKaKK $ d2J%4 z=z}T5 ԏ gPЖی=PPk;1`CA N~> ȡ5Ϡ~.72`_<8v vګE Zk֞n0Q aBjj| 7ݣ> *ouDnHHOhav w'bU]X#J! ]~"tJBL&CGG a___Pѝ Q2T1{^=|kU[J4꼙ۉ~jv'f{3NLj;r:G4]E $剅02B3;шt:hZ/|̠`BvGw.J[J[ kAdf/wEfBnAa Bz~;ܹsسgҐ;v֭Cnn.VX?ǩSpAn\~}\W*ƍؿ?ӱ{n۷/zj 8q9JKKn:blذ/_F^^݋?oƚ5kP[[ 6`q-#[pThXY%588UF\.x^\C^70L0ϐl?]yB 3{>@s 4C6QcBsR7\a QTV}yi/dF':vt퐙\C[g!|@]%$#XXn&Mɓ'cʕHKKüyֆk… 7ovڅٳg#-- *֯_ۍv¼yPXXsncڴiغu+RSS 557n@JJ v/Xh-Zkʕ+8qߏ̚5 W\iӰw^lٲn\ u r/KH7 >߿ [Ûp@DXy!x<|._^.z6 ^}f<u-#KkJ&fK1%_u]+XS։2.kk՘Y$.F;vaM +Pj>*cE+J:0Dŝ[*ѼƉ ݿ$CnfݳUv#Ʋ*mA}hZ!י0۝hѣ׌NRmnG|OjdB___t@O*B8F:V9θArȑ#P(~:pܺu 8}4qatttn­[PRR7nv OFmm-T*ܹÁ3f@&7oDCC=7oB,v OL&T*_~%._,fCVɓxb\ a-=@utVFZrJ VգV:txI~Feb!Dž0p/<jPQxGFe9Neoќ f"j;xK6*ZHz|qi9~hJ7*DzB¸HBr؈~ttt{Ŏ; шF466{ũST*͆j477ѣ8~8J%!HÁd2ގR۷--- |^B nw\~?Xܝ}C{|~& M1Uup5p:0 ZV5|Q4< ad~JyII jkkֆTUU 000FӠ>ƍ6%2ZhQF d+!WC|T9Ε7st*6lWVg7nѢ@F lBD2kTۊV%$jlWF 7UȒq[:|Y52[hJdK{`w8z۔9 IDATv{W/h Wh%+Y{ԌF! ABdYBئL҅vZȮiAN]2Kkq.:@b T:4Y7BAn5(meO[oW*GQSJZn wJ/CV2"2mlƙ-(i+ȯ hUԢ97+0ժq+T < #h'd H*!BɄ3g"55iiiHKKԩSj*ڵ /SO=KW^믿Sb͚5Xl<\k>}:֭[T`̙6m֯_;wĉشi-[ӧ `ѢE>Rގ^ t:='Bd^LAd2ݳBhhZhZ s?ـZUK*z>F#T*:NSLB.2V 1#$[!4 šCP˅` oMs8᳊>o``z===̗lbXx^ _V74tA>[jh04Lp/م]˅@ -\4bU]X#J! Ho| e]kې[׎jyjZ5q:J1`szKhht:{hP޽6g{{;,=}`@NYVEV+^/~?v{tr<|FGX F!d!CTZ-Oy!%% ֮]?Xz5ϟl,YK,ڵkl2߿˗/Dž 0c ,ZVŋh"XK,Aff&n߾'N`Μ98t-Z˗cɒ% 'b͚5R.Vj@Njr%׆bAoo/Z-zzz |vP1s:3G~ ~\p4\Y-~ q}}}PTb`ppP[ YG  a?"R!FKK T* R:Њ_,[eL_ww7:;; FŐH$f{uw,˷ekorkګIZZR%[eZh4$٤HQ%Q%fB `1'u9L{:`=sW N>vHBlcJ``X6nmm VR \=3 <;,T*! 6`[*tOƞx! !?vYܹ<֮5` zY 1װCK@\Xrz1t^-H0p#(Wk'+ݏtNp"ۏT7dI@2bI;CAlXGAVBVGۅŠю|t Z 2<vuszow#$dp7YXX`'\.zt0z@ ݯT*Hx^XX`iZHRj&! l68vT B!水7Ѡfd@VF:89{>{e">> Q89s3I(RR:XFΗrK~fh!3vYQjՊy`yyBt:X^^BrEϳnnn"b@l$1;;EBP`yyl~~bX__GVVVVjR  077 \.1??wA9ff H gľDBuHw6W 嶹\`0@CP@&ayyKPFF@o Z V~j.a2N'    YP*ܐ^I: ᮇp;tR H$XYYJѵTEvvbT : JP(כ1HV/3=hHb]%yJ/`Lz  ZN NBm]bta,P Xr/$;=L*Zf… x"+a/_\.g.--A4 VWWa4a0 B[m'! WVVP( H"JvCVCTlN$!J!A @njBVCf! "ɰCB!fggqaaZPx<>0+>L{1e'57p8 ۍ@ `0ǞZX,X[[G<G>gVezu*Ͳ3v\.fZh2nLFBx~'6Y|vk0LBP#q\ #{+N+++z6;; @t:~Pe0j7`ZFrtp' T V B `2vDZǬh>ĚULv.j0 lѴz<d2vnzսim6!\I !3Wr#!T(X,BZf_|x<FD"6l~mJ%J%<ϦF*Ntb2aj0hVR IoBn2LBXסP(pED" `yyZ 0X]]EZ4fgg7zr\p:PTP*0Øa)J,--h4b~~rATB(̙3z# N<ӧOc}}HOƙ3g055s.]ӧOC$A!N◿%<Wc9 +l8|0`||>;\.cmm 2 .\pey,Y}BrȼbKf1.L*!u ΅Bi{_pp&j4ErfvىMg ŵDtpe,,,l@ N L&F#033Iβ?tMgBfRbAE <0:: '& d28Vm6U=H j!z8BjA @4eg|vbdps:dmVIWF5 +D1Te2t:v;t:T* BBMM- , ;GA(*>( ;ٵ1T*+q{Qx<@@"`llE011|>Dj;v}ULOOCPT*122s!N# ؇~'NΝ;@iFd Ǐk7g@ '`:v kvX,x[oW^ѣGj@ Lѻ6Pf/*$'A(Χ, BBpkn$KKKP*X[[fF 6&&&099.\P(ءjetjj ӷ.LbaaKKK؉D"vS'L|2 8v«o 5e$2<m6fff055pT*v8JZƊ~2JB8l!dr7f((e.fH$A&A,\yM#+r9;bljFJBF0KCӡVhZlVθ2~Hl-~v赏ήw3 { !sM2s \.G&AP`X[[c ޶\.#ceez 3 dB[p~qqJ&3?P(ļ`F uLƱhFOu F%QռPfB>:,pJ65,2FF(BբR  "ljA*tn' H J75Z0Zv@ ݎH$.ǜz0Di48Nzf@ Ůh4P*V1667tj!# Z荹WTzH$$$9$;Nr^+~X,0kiv4 Zނ rFJvz]0!Hb/Re]t0ZhHjnn6 FR7ͻ˅\.GBB}!,sFXEh4b1crr-!pH$LOOVaaab2S.رcV :If3Ν;LƎ xpY=9{Wo"!رc8y$;-ѣG166g"Nb@$?)ZV NZ]+P⭷o~|dZW.{\X5Jb'Nl68΍:`׍RZ<86YN 58be#NxjYg2a}}9 Cg~VRE  ءHdlx~aq#Ln]8fg,C2D*B:fp8  7FvC." ٢_e0}<n:Ct:ufBrxs΂ӫ._, >(L0y㱟oA|_?ݱu<(ޜPKr7oW`N< ?VqlVWqdBr.8/Ȍ7?8|j!m4jjh6t:9$܄p礰\P`>D* ,J%wBacBߙI ~? ^/8{ؾ꾉urQQT!9U(D'40t0렏((`\1jQ BF̘HS]s[mw6 L1v7m|8;L ]cd2X>~H'L췷 gv(`Łgtxu͚vXOȄOJqzɀF_)]Q;Ђ%<;Ë~t:8Y|'׎9oĊ1cj,Z+ܛϙr{B&On*KWf0\ݵ?/ b,H. dbJ冷cfY. W?N&i]KsZSJsZKKk%c,m%c蝕 \GV! 7 ;kkkpR˅X,t: O$[۔eXCxc΂'OR]B.aU9e nPdr_~'­CBmD-vvRo8iX7YG/%>2sb &u\RyoV16-t?lT*cRJ/>;!䬐Wb,a"`/afď*֬eX-a|;xsǼG1qQ)ILx&#8=..0N}llOBwj !1d2v‰X,^No CBmt:M_KdJL „@qB1R _u%ZUbBp )8~%NH !a&a)(J0A ~~jq^AA~xgy fSxL8~n9^Zgמk֟Mx>'!cB)[_pp\gȰ߯O$[iv+H)5D_,>epF~V;uii-Qd$WM+o6!7B8, $Na.D:C*C&lwz z|pCB5{ p\7g=HN!AwSH/$$[BI[_ppx"!:$1xSb* 00BX(V $ !|bE|gX,>e/*BB !A#HZ__ n3O|mXHI.RH bAB8& ΰ4Y|^U$$$ N놛 L ًMAaiXHIIw'‰&>DBubE|gX,>eP(p 25 %XR{_-#uB%Rx/wO 7A~'­CBHgX,>e/ a>\H6KcR'1' fj|>OB8>pad2\7g=d)ۭU$ZV>p!!$3,MBBH #$Ɂp}_'ua𮉵&>DBuH b KŧU!v A0j5 $ !A ai*BJ }y\B<$d%&>&On*zZ{!s|/n {<nBp!!$3,MBBHgXj !A#HBGz;d2^P(p8h4nL&f9O`XjO놛 L !!$3,MBBPIh4B.C*B$`0CTjXYY(R)j51fy ڨ']#8<<8hy|ϛ^ )W6<]LQH=~]g]!7`G,#czz.\{PV9(JHRR)j bzD" 0d28}4N:N`0y… cT*F# B!j5L&Ͱ2, $d&>&On) ._Ǐcll ϟX,Ξ= q={bFGGqaBu 2qn=S8pu]gSx< 1#^27=|[)?t1=$a)6<[koc)@:!066^{ |o6B!N> PSNadd?ϠP(o"A `yy8s ^y( :tB+++H$8~8<&''1::f,//رcX^^F,011)HR=z2 NV믿X,S KBh n3O$[ ϰ4Y| a6HRfwoHb+ K 7A~'­CBHgX,> ϰԪp< +c־&>DBuH b Kŧl6uhK6?'zMV>p!!$3,MBBO^xg'|r`^{ =}:toFFF'? FFFx9aU$ !|H b KŧG}|>Åwo ~^>^s=뮻pw'?Iyԧ>{snyvwu.}ݸ{s ;RH bAB8&O!!'6 dGl}ΰ*BG>LF놛 L_,>e놛 ̰* l n3O$[ ϰ4Y| !A aU$ !|H b KŧZEBH ϰ4Y| !A aU?l n3O$[i"H_ppxkŧlVqpZEBH ϰ4Y| !A aU$ !|H b KŧZ{!QK_ppx"!:$1xSH b K"!$} CBHgX,> ϰԪJBM !|&+ 7A~'6Y|vk 7AaUo\7g=H !A aiB~?{.HvQ(8O$[ ϰ4Y| !A aU$b@d2]>$1xSH b K"!$} 4Y`/n { ϰ* ]4$[gMAMB BBu"kŧP"k"!$⦐nLA>|mU=|U}ퟸ~ׅ߽`^4\0῍QV.NلNc$bw|tT*>ת|q]Z>~A=1&9=>~QZu9uZl6v;~nfQ"T[\x+v\qi5~眞nq9VcܓD:vLzv>}']nC#R"TBv]4 i7mQ㿍a~_`5B@:Lrx<0 jA0|jmjAp^UBt:h4T*(( A0brEZEժC vPx-UPV9_؛ jZP"݃jC vRx/ BP( BٙR( BP(> !BP( Bvaz BP( Bl8;AP( BP(] !BP( BӐR( BP(> !BP( B^:^/ Z-A;`AVSZE * BLx-Z & xFn h d2^s]DZH$l6~:ʾH٤ZEP(o q ~$p]xnjAp * Bhmz:3Hh4`0h4.*U [!lZjGCVKlU=T( e6MhZt: 8BբZr]xVq~jBl?$AVJu9uVPP(g; &P5lV[Pa;'>߿pZ񅛿FT( eBBHj 6Gs9|Կ7$ ux/v/n {pnRy+T*T[P =Π^݀jG߇[쫕"ʕJ5RxRP( !A#|.W`ZBVF^e% !~XBo߼~ʨVKHRP( !A#|+&\ɣZ+ި٬ժjlPѬ,ȣȢUϠUOd;g[dkE*Zfzj':KHBPv/V4ǿ+B2xw}z~n7lP{g I) ٗB[[{/} wA8_q]w~H,;!JzVNngc60%-9y)K'/E' A'+A'{$#ǰ)[[cv5]薔V}֦}ijV\)R( Oc>q.;$!lVR᢭vn]t7 t[@ 4@Ŋn^Nj t'78vZhO:#|I"';V#hEO~v#WnGGЎF'q9 E ( @4}nިR-#RP(˾tf_s#w? /~p2P!ܘPB@@gڑ Y|ϡ} 9^@uyT]ayTmϣj{Ul|_٠j{59_B}E/j߸=jPw ځW n &1lw@v (izvRK$ uo&ۜ?/{t?O@~P\}q}K_ eVj7o,Pu[F;v-3h:GhE}; T#/6r䕏zq䕏~>| (j@I] .\( CC4]?AStg9 <=*P MfZEP( a~kG7#o{o#99e? !x&km,U':Y!ڑwtu뿢n~uCT귐id@VMdHJy{7|o!'wzգ+ANoJyj귐GQ1=h{ni @.~D^bY jއFo P(>g_ ©dnGH9J_P1u}V0:"6h؞1TFtpusF?@n}r&5Uӷ<|ϋC=Υt?/, f!lU:#DkDofyUwP}ycɿQdeFNrǑW<ⱍ3f1g{g'zguEA7ṣ1̙mJfa sF.^? ! !Bk!\Jk PR_Gl:{S0v~'ޑ/ڽ<{0/<k4ͳw"#zuw^;8|?Ƨpw 98|? g?)VO!w>/K_ ;OAS0v' ˡr>| C?@h+P~RK ſ_Bp?8#C#po8.CB8nھ6vݚ*h^A4?@|5 XGʦzZG*ߕߛ7Y{Uۋ_B"k/ng~%ӳ(Eܕ> 9^Bs~@1tO!RP( FÞZ`%'P'TMMMt/)sߙ*|@!po9,3 ݋ ωw~.}Yh: [0~/|gO/`ş!15V#Ƨw#| ۟Ch+нx72a9Y$_ė9WHe}.}_bG)?u>ѯ@̟֟w<Ǒ/&wH7/,1tft2h'Σ9vڑЎ~l)4BD#t иShG 9V4+8ؾ;Nԅ$bȸdƀ\oy7hfZEP( a~p_" 7_{H]mGECĭ(̈-9mq a5Y[gta05t Y1:919)y:9:Y nm; 7ؾ[P[TE%P"P#Ps(n$ k!U  IϓivW, IDATN h: P P[[wgҽ5v7m@n@3@!mBP}-%@9z?K_!Z N}GB8|nھv=1lWzHۥ۠GyQ*@!d'T( e{!}џµ{ RBP8 >6lzt:nhdPHw9\ aBZE6E4g`0P(P( χT*Jr˳F(H׻U{* PWoÙmžiii™mεηη)\E w Wgg 'svu3xnrvf:W=xJ]y{j=*]%;! !B5$ qid2b` p8 J?q]vk 9;X(vD{x>fjZvhbf󶄰"U"Z"Xe?`|e[™kÚj@)A.A-HTaJTaN`YÚaY@@+C’c-ضsSSv}Yׯ&{\}] m m h1M+~{j=*6RP( Z A!bpݷ-OdmvZ" vr! b}}bJJ\pV* JfP|FM(S-!,BLXr"w+4D4$ 9rPPKPKP z33XS*B,@Cѻ{_p`s9WpLaɑ²#eggbw2H:6.|$N#!P(IwG~?粴 !z$K!lZT*X__g#nD"r9TU4MvX,FJٌ`0\.3!]`-Հ&\؝p-9Ks8 5ZV\iHS W1eL60gcɑ̟!^=8KHBHBHP(ȁpǁ~ׅ{!dT*s]'8Nx^q vzF8t:4 , +zBn@-CaiC [zm0oM`3r-1oM`s $M-SXD'Xq^Kl#:k=ɜ6z5Lıh_̗>VBPx}VBO"ȑ#8x >~;v .]< d]쒼/eB$K͏ lJ0c0`16`&ٲlȶz4{єHF͒-[9^y̜3:羋\Bj{VJBVGx8b8VDf\VE*_;Ӏ' Dd2I,cXP*tuuى\.l6B m8G?ԕ*D5iy7jSH[ ALPCS;UBdOh 7Y@8w\}Y^z%/^R)J\`(`XfH@8I:˫SFZ-VG8& zX,T*zzzDbry˘D"A&3̩Q~'ԕ+J2G'u&ex|2FǑO>#u% 1f52qđhNqR/* _^ $HЩٺu+|]]]ԝtvvƁXn-??O- pi bx< T* vӉj-DE"477 ^'fP΃*AHOO(rD *yګ1A21Ua|vP`mg A ?k֬A,J HG4թ-Sc%I2 r,YB^?`O?$f2b^H__ >ju`[[:tJhnnV"[zb0y0GrŸ[;÷G:2{v÷ ߞ-gȆ1\?(;PAhn $H;ߧL&.x6ͅאV(%>?CǶZD"8v'm86E~>3Aʠ e0B)̡$p [4=ύ x1'q{<,X/sN/ $IqꫯM"4N~c' ち$NuVxB'rzM+D"SD"/agg'V^͇~ʕ+Yv-[lF$ & ?nbfNB]8&_@M!$=Ij ? c"I<4d*7rxCK,i(#03ˁ0, Aʹ ˗/GGD8MH0ZdY~;lBRIGGٳ-[fVX{|իٴiw禮.z=^x<>kP`>2w^GN[K9AvGйL76O(J0"Mf#A(s< @(|V $Hha$ }]F#Pḧ tNx`d8p> f*3 ScTX… |L&r9mmm9r26mի @j*>SʨB,jqbg}<ީ+;pu0›CdjШ!li7豆P4fzÅ7"OȐHgIfr3 R8, /K/_' $Hd"O 5ctz:Ʉ(ZW6Y86l6;-@h~߆slu]acc#-B*m۶ g,[hO,owP)isEa0H$477SYYY4]tŊX?M6Z]4:TK.@ d{RtcB4oT{iQn&ZÅ'$OȐdIes3d&Gluec\z饧:P Af @pdVKCCw}ǖ"n`~:;c&rn?e@w^.rV^}y.\C=ݻ/+믿裏x뭷P(:txGyGyXBG{e˖o>^z%~`lڴ|M6qvZ{=.2^{5 ^*++yx衇سgwus\ve,^{<3wcʕ̛7~?,X6>]@8g-ZVOII | 1+W?䣏>bݺu|g޽{\ |w=¢LPU ›Glf ҬӤӪa!s8hm.6'VO0L($6!Βft@`*'!x@(Lo$HS''cE'AE[Qvlcyw.j5n{\0o*tw9xןg9KE"KYYwuz+>,O?4_~9w?!2+VV\O?B3K.妛n_?!seH 7{Ǐc8UW]Ś5k;w.< 7|3MMM( ~_s-0o<~/tRnJ,Xs=w~a}]nFϟϖ-[я~DEEsw}7۷o禛nꫯ'oV W ǨC8Zww7bZ***رc~)k׮eլZ5kְqFmƞ={Po| P,/N_$J(":! GyP OetN t$10L nwbZ;9ppTo^Jw(ڿsW Fhp1M'z7AйϡOimm ^{5ٺu+֭>_|B͛y'ٺu+6l@PO?͢E_gBѣGѣGٸq#=wfժU,^+Vd,X@WW +Vxb>#,YK/Ď;x7yYx17nd޽?ҥKyX|9UUU,Yݻw# 4fΙ3 Nuu5{eǎlڴuNر jkkE@oNxhrg<̝DJmeem"usXA~/_G0!L @8 @(H AWG4M#ap<xv֭[ٴi7ndƍlڴm۶QVVillV^+;@KNe!29jҢ Ь Ь:@d и||~hJ<%9#Iβ2')ilA :up@8ᑿvQ[[|qzٳww/5B5@(,L#8p6l>c˖-lڴJ$haJd2d2RDRIL\./xL&V*++IӨº&DRvaNBAci&p…E_|pݻٹs'%%%sN9x !0 .¢?Z $灰A?MIdYO*#2Ѹ<ؼ6~#HJ,#LIffIىeTBA 4E @x Fnj zBį%Q^Io@CauMUZAxP86 TUU!8t;v`޽۷2lB[[@!EOOD"hkkC$Iooo!AMWWbZ[[t:RСC"줳RiX~Q-饙BWբm?| }Zf_YNٔQM(Ko;?eTla"j n ,2Ie SFCh=~>7 F0&ItABa k $H٠YPhJN 4 /*8|ydQ 8>B_s<0zL'kkkٿ? P[[޽{پ};LJuu5G8&NN b=zH$B(b߾}i4 bd2dT*FAc2X,_XK©^OMֱ5@Jx'] BbZ[[ioo TJd2 yW`0g Jp8nD" DQ"&N먉bb(J4 v3 @8I @q9m_tiˇNb1bɗdlKE;<ZvԴt ljjN?yf͛[oJ"J`x D"` pUW??lڴ v/^̲e-[; o[ˎ- @$! pxOi+Coo/R%hDpW o2@x7?Hp@ꖢq:,DzgpHc$)Mc`Eآ l8hysS8)t(cqO~7/[qgr׿e]εOL pWdȩAwFGXWYYg?%pvN90&L}v> =\:,.b?V;Ε25  $HЩ082*9Z-韥()NWW'V͊m[Vjj()ݞH >i(<@裏2|yϟD"!s]wq=M6#__,[ 2?L&O)))=HRWc4[<0Ӄh,iPp$f2$ :hˑ壒`߁dYvc*>YC|'> Ӆ FqSX;w}q=>5k ; 4[%$piC  ( w;'1h9M755~2 &]v~T*X FCss3۷ont:)///=:|?vd @8k5@800@,3[˦=Bd~v_< 4[u=@8 \`t = k ǃB7@n\4.ki*]2j#9/#i)fmxe% Zed'|Y4N M˨E$vwwrJ֯_O("L҂D")d5 _XK$O3\.WXGGEsO___* VL&_/^{\8d=Zo-Jw츀{䋯Ls `NJg}>4. FLjmҠsuq(:WgkzYW$!V[Kwy9{֯OO?cߴ#=~ӅjC?p/ߥTxpd9s~<T Jss3?<ַ|L tt @8񢩢#ap}E0x,H(p:P)KOC {!n9@ROih)#ٵi9}@5Ve&[׾NGK=n|f0s|#vwwn:6l#Gh4!GY~i ZGFioo3[ZJBVj1 1@xPxjZ * N.܄VUUQ^^΁سgxT*JB"p8F$ B l߾Ázzz#ɠR8x f߾}8r]]]( fy ~QNznTxzυb1^{ߺ8ʵp"8q` 7k0Mx6²VqߍYgCKH$S&xQ<" $h7SEG+9&dP>ȩ~ځZbK"@SRR¶mۨp!v;D"j5[lx<`0t8\l/49L pS(1 a#X0`K X> {8=Na%qb8a\.?.Ӈ qB8!vϜ9p+GpcCIᱳ :Q.߲eb F Lggq]# Ţ $H,ܹs -KG # p<E r9K.=?-W^磏>b˖-,Ysow^yxXv-K.eٲe+,[7x˗vZ^~e8@cc#"h9i/T\.G]E(I3 l%L$DQnv?Op:8Nj5{n_t:] <\SBC8>ӤnME}/%yhi4pPpB(d1EҘ)$PK(cXCт/FqyɆ=Nl%QHg0=+-W^=J` Dңgk߯i7x㸯 0*޽{ $HY(cE#H}{'q<!*=^0L+* ^{5-Zğ'~a>***8p{//>,>(.w}^zG};3E?^zz k.D"]]]l64zk h4RP*T*L&NSBgwtдt:]Qm+++,\X䢋."9]tcN}I.u߃UEzIz,>z-~Jw ?UÉ#Y̑ Hk85Lb &X1(`k(1ۋtbE#؆ql$1_{äJxL'^x'|bb…Ba A:͝;9?{~4t5 Ąh4{ 08&`8 ;Ģápd#GֆH$O?;wyfѣGٵk6l͛7S__D"fձvZ6mDyy9;v젢-[PSSƍiooTF_#o؄IՍraٰZl6Ǩ_44NznCQRdn7ވl|7d2cN똓BCYgɑ@Sݩ(?xd9Ri86/?PfL(40GX#ilTq̾f_?b0zlUZ9V_5km y[1S'|mZhkFBA ;w.sΝ;ӏcfZ4%恰(28 !(/Jf6 ^Z" H-xxϑ}ڊM&\.ڂAVW!{<"aPSCa:&NSWW7wQ_L8:dPPz 3Hs;\.?AkcI0t<{&,p[(btG0^LCʢBoSSWYgT+K^Juk5~^s& DŽc1bt1 n#FDŽccxpBãNS 8khP Af  SQ`yC,"%<@mZSfMH$ x*=u ~4Q‘'|?*910 ȍ'eF/n7\șgT*%n6J7G(j ۑIt pC$W1` 秋 ̞zGݏFk5t0,-=wywn kޣ U*GeU*Uz,6[0:ؿ:`}M& _h .@BA p[AΠ h1(fOOɅLkA1 PUuHN @jj sńHo/ZmH4]t=(R:)JInwV?]716GG<Eq3Rh#];D33gKMHMNz65Tz3Q&z i$*W?G H'PYh. V?dl6K"r"!@)uaƢx<jjG]of֭\}X-T2214`6 ?/v =x5q'Q,c8p4r\ $hIIDcx^KcS46@Ssc-y7,lonXOf<O6(_, @xiZ0$)l.Kn@}ڿ~7bڎ}T@*L·/++)Յ}o-GcD b 'bh>$:3}:jLtē)|J7BnO$"k r9. nhgmb:C~юv:%=t$hq2 =l-%Rh_^ FƢrCt:a乳x|ŵoqud $2992A $h%$p2Á0L!a8NZGEBɄjUCPN1J@x"yLYHe!$Cse%2]!r z(r%h2M2tX*C8$0Yʔq<>J˃Aާ"zihTr&ZEtKHdr::P445ՅD&@BXL{G MM,zD"T9sp-'Gmh :w~uTecMeM:  $h"z ^`2I vQmt\ߏdBPP*Wz .CJL&W"AףF!҂# .QW_t7t rH$2旘㝿kݷuHt̶CO@($$HSY_~L+J8ޔT*5 O5ÁpG^jx_u`XPj$X4MzRFJ)SSVsIGNΨO+=&:>syqՊbARj&^YƬGM:->fB.i4 Ac^hQX_p]}6NG*B&rIRds92l>L&r5 @Rq8NJ%bnikoG Iu^/*R QG>靿/"(h=P(T&OQk4 2h;w > Nx'z ':O $Hٯs2g38FiFדL&'Tf$%鱢#XYFs̟? 5mݟ3ު|}M|P4G:JY^yXuĊ&>yo=ʚVɑ,//dWyg8{Zֳ&~_=`KTˎ W_?inƢT桴 ٛQ~ޗFNߦ(>zUH-Fl1WxmOs%sOmkkh4b2B$k/]/k/]e=Z}Fx5(E=&ڶm߿~O IDAT1WXzV+R5Q_I֍J?d3VTw\Qa=ʶcV @xK©魷yW I&d2F#f˅nG ӫjDabh4C]."N@sK uu>rTRXLII _ט3g_~9~D"L&CӡjdY^/--=' Opg… 1ρX,_^k=9P AMYgJ [yٵkth4z\Cp0m8 Nu`:`0pm}ab!!X,k Պse%n T'&pj\zc?-[B@"NS+}>~?dC^X\.`4t:5dAN'M-t;A3bAbC!!P /Y'̙3_dY-H dhllf2^nۋ677c6lF?Gٲe˄} $H AI0xmq7s7o"fQ@8rxP8 'C@ c͛7>%(epuR`Wbb HDX,8\T*E @PA~"Brݎ餦fZ۰9$I,h ^OsktHwuzIRw5PWWjR^^ηpaZZZ|@~ ftj:?ϊ\r%z g  $I17x#]wwq|MMMEsc&2TZ-K8!buz4wʨjsvr}Ս>\OɁ6bC>l-g|V~dUhKC{7|f I/"/~Ӊv 3J&W_}ߗ%5P Ar5vO'?ϸ۹[kOUW]ŭܹsyGyf׿uX:nFnYP©^OMֱ%tD"Q(P>0 FI$$)8OALOMg?"9u2D=n`Ou iykwbUAV8GXQz1t,7P׭^G%*rY wͿ~839pEoټ/@2"(,H&TDxo2韊^y啅RS}  $H\7pdp8^\{\s5\}?Q_}\s5\{r˔'Jq,͈|<@x{^.? %fdYiB$OQ:5 *:iղ~wUrgVbgk*Z(릺KQ>+Mru=Z$Z$*;hfWp;UwV:)% uj0IܸKFn;ݕ#G64  ${pvr 7t7x#7x#7pÌzq к¡)]L'pur04 3r'-7?5=Z@xYEu35T)ZmوXވި0ͽP锘F &5Tu줾IlO+y- 0X,ff桟|i لllc)fZ7Nh_4&mGc k ~?gNznXD"B*'Nɤd|C>xISRIbPׇFosК- Fdj->i%KAM>_{wu|2Cʹto\Dsoo͎oZfZnejjye^ye( 78"LP 9z 3?cHvWZnr TP|BGO9tH *Pv?}#kٯ-kcYyڝwTKUQ]Sfn23eQC)Y,2kU Aµ \=O˷hKf(שUTTU)h{Փz~=hKzwͳao|UsV^Z=̜zmi7g»c^ǕUEEEAtme[ջkQQQ6g׌/+5uis'ZC^zet***ehKzg3ھg^X|Vl@3=Mkwk5o+ +\kE š*YllթS&Y,-w]m9s^?[l8Nd6שT*WL'JuKx u@}(_CWC>|TD%'TZ^M)Qq ;q\G)H9|Dh#ڛDG uJ*NZ:z;bǭmBEdp ރ𳵻rJ9[s%5[EEEڔ\dҭjŶ>uVl@K]{7k7s䛹zg3۷UʧZ/m}c}c-J 7j݅fͳkfNyfhu|-4r)Yu}2On|EҖ꫔y***kbx˛ژy~y-fmSׇ_άMdݫpJ- gAw k?:jQ}}m˙F*//=_|晧[}SSKU]﫾P5Mߩ:s*}k"!8Ѕ1bkQH=azz<<<fAQ3b0>>:B0q.\llRRR>\iii[_sAxeAعsg= W0]{NYYYNJR{ t=3-~3Ax?S=z#(33~1q: <~M1moڴiz饗PBB]:u׿'dAHd8 B"h{ ?!c a@Ydd$ $ Bp;wVhhӇf +Lkgpu8~)馛w aEEEW]wS'dAHd8.9 :u.]9ʩcA>DA㲃c1qD.Z;S#Lرc67D9[Oj1qD.DA uM7tǥٳe_k׮k.:?W^{k.> $ Bpahԯk 4HAAnSddP7Q߾}5x`r-ꪫ>}( @ կ~K P>}ԧOyyyGÇ׀d0[oUwynspAuocPP?AbϠ?l 0h0~A~2hA["x8ܬwѨ>}hРAׯ<< ;SÆ S߾}5j(u]}z)/// 2DrFzXF 2DÇm&www鷿t7KA>>> R\\ DA,Ctgߏ4***)G R5n8`0(""B111z',oooM2ESLQll?CO? .]_{1ĨgϞ2=ܣpM4I VHHF L0=zu7`0hA HB oP d-ݠM6huN6hZAۛ Z@mPh kVhhƌ8M0AƍSddF?+00yN>^/n)!!Az|bbb PbbƌPiҤIִiӚ1cGհa7k>앭'd67D9[OV@%''ːq ջwouܹ:]{ѣtkG~lG?ثW/uY/յkWuMnnnԩnuU7p`0CnnnիwN:`0ƞn[fͭA]:t :Ν~׽A6= _Y?kF=zy{{M{wﮮ]M;wVnݚMǚ]tQukUnwի*W7pt^z5777uM_}t޽{K._yoW_oQ]v`oWB Bc?!8ȅb1qD.DAaaaM9 7QGoZ $ Bp! תbrn'^D.DA $r $ Bp?=jS~Mԑě1bz!b_kձclo:r>xKNN!99< B" ?!c0\(1#Gؔpul}>&e B" aP Bkz***)g&| ZDdx2!c0&e B" oZ $ Bp=ӕ7DX~~U__엃AHd8v644(;;[999NcL(;;[f/Z ,,,)g&|b@uAx+##C999|A+''GbqA $ Bp;Ϟ=+ŢJ)33SiiiDeff*//OUUUX,jlltA $ Bp;%QE&I555&vFuuu $ Bp=FlV}}L&S2͌A0BȚѣGmo:r>z Bh!c0 DA 6`?!8ڠi9rĦ#gA 6`?!8ڀAHd8 BhB"_ Bh=!1MZAmpi+??o\|/ 6hhhPvvrrrƘQvvf_gAmp+##C999|A+''Gbq,1 Ξ=+ŢJ)33SiiiDeff*//OUUUX,jllt,1eXd2TSSj"jjjjTWW6b4B٬zL&"je6` BpQ2DOq8Xrr ξcbbbbbbJNN!99` BpQ̓رcDDDDDDBBpQ BpQ BpQ BpQ BpQ BpQ BpQ BpQ BpQ BpQ BpQ BpQ BpI BpQ BpQ BrKs5qD L+YK۫Z?ֺ6~@? SΔ626YSF[8NӿǾyn5vXghem;ݕMQ oR#BؾM_ BΞ=c5vX%ORxMj]x%Oc~[ Z +'d3U__9ZWb#Z޷R@+5 »Vdju﫽1ucp) B]2at8C BIe\2[aVTC BI/ƺd =1'ic>2vA8I \2hb?mhm6 °V9O>j_s.0:~JA8zvig4j(c?n%/H UпZފE^.xF;8#T+! ~[ʖA;ت ׮_ԗ lxZH }Z$ŔI >IDAT(58@+TH筹shyQ/\NPv=6\z+&B{X il]`F1y 6y}]vUXQc5V[zzz7]tƾ7eeu67Gpa-٫K|?m6w9VZ?&^q6F:?_\<OV+3ފfע%Iw BIህ#V8͉Snc\W?|ecjþ +'*.֢]7?N#ogkĂ ^ri܁G||wlR}S.x4Phwd9+|kjkjTFaC5zzhҌ~Wz>큾Z6l Æ(%зx;}{xzh`mn\O_BZ9X_ j>vFT$)shpA )EOc7cw{#IzjSfAW܂A>'\nSQuƷ/iZ kR}S.z`5Z,:S}1:G3vҍ~Z?I3h6ZG֮ֆP!ʴTWym?FF?lߦ3uu2/VsVJ7ieH~NWVtu}DcGJ2ǍuxM B584NᯇkUzr|7R.1zA#'>YG7?Ne5enVg$=Ae}I RU$\ wK3җ QU|rOS_LE\ewi=?ݯڼ<mܠmQmQN[T𯗤gzTmTmr%)?_Z,I3sA8i= B5a짱6v6eo$546/1WMqn~>[ijtkUPQ9N)[aVeaA9rcܻW}ߏIg*%炟筝~J PѻokLjh6T<=tud2e~3?\}B**/hx5LK&~X,ࣴ IR6~dpA3/Ʀ98gNU4< ^ЩSz?ꂟ(I_E1bTVSu'u{YՓKT6b6m.wUwdfjpvcMW-*%A6%OUά*۸A,_j|$)3a~IU߯T?oxkNl~uQR{tBQq6B//!M0hM.z*s\`y`>y@QG󹨏'$IOV'*)of7_fY2=7l;Dm ;VsPU3?ђ 1oyH; ~:{?Wm Py>蝷:YzIRzꯪܿ_|)wtoιUZ9Xj ̸heEk{LD(5Wibm>Gm]v4>Ob1yǬ2WO+tYLj8ZSGa3F SȜE|}uKkJךՕÚzfUXUɋ&7_iO$y+nۖA2RÂUm=Ne$Ljܾ?ݯ3ۮڵS~=qe)-UUnRyZ듧jst)}R{*ч$I۟VOKo *IOyKRǎj>'0evd)+UZ5qu)e?R|TOeiZ͏RFYE6D)Cu|:S[#KY@{MD/>^cG5S??FթtޤSǎ)~poZ :]^ kphL3l>?mō B5am*(Mlrl;S;deAخi2{zP; i2 DpANKf r'C BIa!.-p1'iod o=P; _so*hn_WO~AZr!Nwscp) B>7\9F rs .9額oȮmGmcp) BΜ9{+ o^aT@}%9*\HjC;y@?m6y@?mֹUѲ-> ZٳX,<&*pVg_ LPY,566^9*MKՎGuKmh?jGTNlKam("ɤUWW_ԨΦ!j#> ڨifd2]l6j9ȏ0E1E1E1E1E1E1E1E5Bpɤ)S0++ }wgΜp_~:Nd}ξOvg+11I I2$%%iԩJJJҫ,L&gGL&ef~^zEI$~M:Uw_0LJJRrr29Z,11}ݗu55a?yv=JL$"""/IMC0>>%$LՔ)/9IENDB`portal-notification.png000066400000000000000000000277301325274564300376610ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR;C IDATx[Sڇ K&(切:#Q˱tDQ" VRaΙ~: I<^ڳʾ-{QS)RbrrDA5*R&'' Vk2FGG1::qLLLA:LOOC$a||A5R"njj  bUe-:TJБ Ɏ dGC@#! pHvA8$; AɎ dGC@#! MMMA*bzz bA#! pHvA8$; AɎ dGC@#! pHvA8$; AɎ dGC@#!X333HDiA:HvA8$; AɎ dGC@#! pHvA8$; e]ii)cK"66 ~'?@_iiiv^v^/// %T*w}vӽNKXeqUg4_ee%dG,޽wŋ/xew%ؘkckv|r-= _Kuu5>tce755>pl5j|}V/]{~lζz9mYqÇ@ ٳ---ױ1\t  3{nTVVB*~Umn[[߫W̛ ӈÇ:IҥKFǭkMՖBoP1%Űy2-kd2D"Lf5[TF[Xnхoi:n eE1l߾hio˖-:i%%%&JJJĄ^:3־X,֑q+++ý{tܒucNv|\v_̣)Y_g.,, NNN ڊfdffbڵ` FX(a;99())A}}=^Hovvkvg^󴴴<7otv먬ĩSxcطozYnٝ9s...`~1IYYo+|T _;ӐJx"OыX^VcǻwhOnhg0&uEEEz |}}_{S^k c ?L&$?BY~=޽W/̳ܲpwwGmmWJ^%3RQ{ݎ/F貢dd0#!cѾn5<2118y:W:$;вۿ?`ڿ6\Tծ/_]?JNN6wbb`C zy>T\[[kϾemt-ue%0CN{g Dee%PRRt8;;#",fuzykjjt>|7n@UUN> fb2 GyPVVNqlܸ-^ɲ[<^vMh /^>8fffp fW\pe!b/HNN6"u,!Ʈx-u/"в3!&&pvvFHHv܉K.attkvʼ}NBdd$\]]!:G~~>bccWWWDEE_Z [y,..FXX|||o-y TvISWW!{A[Oh~ f\.H$ђ*A˗A*ӧ((('ƾ\ill=!٭2>Cee"{C[e!::Bqqqc"oAɎ dGC@#! 淫'N{,ʀdGH*.ػ?!$/0K'dG|dG|X[vR>}ػ!$֖xX]vϞ=CFF[\c\ y5qFBx{{C^^VV,ڵkعs'!{Ν;H$&OLLHHH@`` ݻQUU!>}pss3\ C.Gqa޽x6zt4Bjj*!x faL֞¶XMv2 Ǐ7~~??? ƍ&e/c[ 1DGG۷?{ &oٲbxYZBiiEߙ4X^[vwޅP(4ZO~~bݺu ֭3Zd? s@|Qx{{_EUUq5lݺر`< P^^2֭ϗV]].\ؗO.;;{Zݻwuv~ b0 bք& OOO?hnnɓ'vZއn㛙48FGG1==s$!,, NNNŋc477#33S'>X(;ka( D" Ų߃//KطoD"I+cG__^ѣRVP8`LOOI~Cpqq^I)A??e+k)~~zηa bhh(ܸq|׏5I1Ç&(,,4Zlj'l6r9<<>c9ڑ%E+0iu"77p)e5YfrGG~ƍץ5NNN6w޽|EPO6l0߆ ñc 2H6;;h eg9 Me'g<|ϐî]RjKcc#۷:###q~/9ؗ駱{YI$&&#((M֣ɷYsaUMLL -i=##cQm.B@||WɮJfPQQ11,)4uܹlRn[[NF$f ٙ;EYsa5555ux}jqt3> e[ZZ~ڏ,9RSSؗ2jHNNc k׮X,I[*KJJLKK3*_~ɓ`]C͑R>~5ud833`0^GFF'CR~Wo*;ka?"P=P[[w(usZAAAx^===URr~իWE.sQ2<ށxڣG _[YYYRbd >|yH$Fv֜~XEv |!ׯq}]N ϟ...AEEq)~X(˗:1,lWWoGpe͛:?u;p޸UWWtP,TWW(++CFFutXJYKTܼyͅO۷o5a ]~](ٳg:G֔瀰V{1=i6LS\s Ҵ ŗk2"99YzLese+k)n2"}S(l#OŤHvZbBӂdG$;XXUvbW\]jr\6&ثWnvvWogdd-HPXX;w!!!ػw/jkk1==m4n{oݺRc򂳳3BCCqLMMATBP;v쀇s^^&&&쾀b`5;n D(jۍ-y[.Ȯ&돎5+..Blق)WClܸQA*?ATׯ_TaNNNYm|`mmm>x)n޼?uÆ <сRG\bED+Ν;|gԜi# t:;;͞=z}زTf3tB#t<132L'ɓ':GoJ:y^x?}J*;R[n!%%)))}ЩD"_\/]Ҵ?Ӫ]P``7G^h~~~͛鮽AB$ӵ%sU.7o._NݻwyZss󲶫-)/Ʌi tuuidsNlÍINNc_>BsFn``B𑑑z*޼ycQ:ir|Y5%)a&z4KOO7w}k4OJJ Ɏ Ud7;; xyyJaaaf(֭[rss]S۱cWɮZ222Fv3dGjD"Ayy9RRRt4xzz`]To_vMn۶m\.cJ#`Ui#Յ+W|RTV#͛7[]S۷oOJ_=$;V(FGG!immO;v`H~*;88h:rkJvO43sgHva";i2/,sq8q1[Viהd6u@.#88hHva";;---h#++`7o]oKIIZڲNRƵkKTy'}jjj/ 466G3?R{Ѻbbbtd`v y~ ===x Oϓ+WEEE\R18px>XEvJYYYfpssC]]ɺ~LfvaZ[[aH$1>Xnϑ͛7ۛjIsD499iQ>>8{^q?[l'֯_(:tO<1Ɏ =ZcxX|ӲS*c_^Gᛖ]mm-?r!bɮ2===-GaoNvگGҰA囓]{{;6mP-[" kdGa ֨T*D"( BRA:HvA8$; AɎ dGC@#! pHvA8$; AUd&$&&իWvJhlnn{,:pwwZ~Sܻwk׮(o{{;l:6ϟ?EysI$0'dw>Ď;wzttyyyxcֱ߿@AMMߺ9S}1밚n߾mpp4ޝ&,Ǟ{+Yv !`app* ȀTjraC(GԔ 1#11ضmt5{QZZZL>moFBBΝ;_lmm-Mϟ?G\\EqQT;v¤#`CPX>mmmBtt4,eM.+lɚ9D"?ܒܠEoo/qU!JIDATk׮/1 S.׮]áC~zO*J0K111:CCCpc922$$$`nnSSS---|͵ŋ&8ll\7n`۶m:kfYF 6J<~Y2ƳNNN򂛛]ft^לttt B/BJ\|KRr–XMv%%%z8TTVV"$$###zhOD8ŋhooGVVYiH;3Xb4$B.9d2ܿGdd$FFF̶eO's?ɫ٨7$w12BFvׯ_e-scnn<C]]憱1֤9٭[:}qqqA[[ڇIki=.LzaF>|xYdwL^~~>.]85ҸpM&JmY";sdn\3gΜ1*;s=z777H$^SSOOO}Z4mprrȈ̍3ۨ(dffZ&Ɏ1gϞm`Ǐ:[j&j_ݍntttprrUnٲ;wDgg'ӧONoo/رfH;}ff@__! .:NR ͛7(++?]ss3ׇmٙ's" nߨW*ظq#3466Νh|LNNꥵC("""x!BBBn-scnn@Al"YLNN())`zzv$ V>6L&Css3޼yKymmmc^T<[nEAA NG999v&++ IIIv[c``1<|`axxjGř3g#a[l";TrEW^^`"7nƍqm>_~o߾%Pm(Eˮ¨9XH$ E^^r5 ٙdG6]UUբeWUUePv>|c   Hc駟 #777D"$''!!!(((7NFGG_RR_U2 ܹsPըExx8֯_(b񁗗133ctlɮ;vP('~GHRÓ'O dffÇصk\\\m6|`ݚ|puuŶm044d9}6LGrr2N88vݻw$;j eW]]hUWW]ss3T*&''Coo/T*oߎRJ%BBB> ݘExx8ݍFP-]\\ K .]BOO?zݻw#99Ϟ=ǏÇ[s۸q#ߏN>/^@KK cãG={`ݚɓ'ƦMpAnjfggi&TWWsQYY ???ne2B!Hv6]MM͢eWSScPvEEE 1٦+V>mhhMTTT:ZmJKKyzss3155Z DwB&d<__΍))J\x###|[ff.c H:cf~M۷o#&&jզOc͍$֮]^j455&e .}#ldW[[h]CCBx5ji]p{1vMl۶Mgc˗<]" ""~`ڵ1fTܑB_ׯСCX~޾}97+W^mw37~jiY2χZFFFN:!]pa^%c3-Zvuuue[~j=8jӲ?o>i7nY߿gHvz{Nr̄?"##1::J`ttTRi0FSJCll,.^vdeen``p,vegnjӲd|@rZ֭[n477#44F֔5ϟܹsعs'1?o]ٙy~Ɛ|,xxzz`l eWWW|m-X,ɓ'i& ..gΜ;3%y G'BBBPZZD"ၠ @RFGA}}I٩j?pvvFxx8*++y:///"77Ws!dVņ x);KƯ axkb[(t={;a[l";LN|b}===>HA|l";J~*c ǿoA+n~DGIJa3AAɎ dGC@#! pHvA8$; AɎ dGCH$o V$; AɎ dGC@#! pHvA8$; AɎ dGC@#!XcxxO bY<&&& H077G#bUFT@RϐH$AĪbZ&Z),&''1<< HD@DIENDB`remote-interoperability.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000354111325274564300450300ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR">GsRGBbKGD pHYs  tIME:@3tEXtCommentCreated with GIMPW IDATxyw]XvQ$#Vn"֑bIRΝ55m[m2ըY3}ukKURR9>8gz_17l]{LF kvFӃ7>9RLoo?^qXku4k2p%tD}3%IjGG!%6:45kަaӦZk߿_:UoXdAZw"IZxF1YX5ZpaUZU+Ϭ\K;t$}g7l(I\>ǘ_woۦZбGUaC۸Qj[l#=HmS|SW%IZ٧$޽…qxP#ԏDGhh׮:y$iٺvbɩXb(›^ -=s֭?_Ү]ؿ//3fׇ-d4 ̐[~Ǐ|'OИoԱGUB;\I9Ųᣏ?"!IW6Gckd>/V[naM3I9;&>Xvݺe~?tWZ?[cx5Uvm]?l^Z6sUH+Wu?=n\`#G+09åˢN˥AS_+@~$S?iD^ϩWOxC+ #b [7{H2}&Jӟ®r(5kv>;R\ӓ&x"os%E6'_*ݸYÄ =Xsrthɑ;CmzLzz_' ԠI&st/󻯾jm:yl߮oU *.݂n_捨PމuLP{s7"IJY?|XnI%%%V&*Ws FG^yC [۴a`AExu4~@@%YЦD֡g`֋/fԦJi4p؝$j^ݷo߯oZ_mݪ?}w۞٠AE?4,scwpK֤!C$7k~zaog9kzk};Νy˯:@~~|uke4hX]ت?4PrL>}>P^q_L/^Q/܎Zkw1CB&$ !~.h2c^y>'W|PZkM{뭨n=tǿ4Gc.D~ja\æLQ Ure5lTC}qs&Mt}N?~y啪Rjfg(F͞G%۫j^.nN,AHwocqcG}͚5]X(#\mV |ŋu 8g|0p_^y% FGĦ_"Zkkujش) _FP?@} 8LɩSz㩧/ wYjݹs7fA "BC`6@ DA "@ AD" "@ AH%2QSB׎q7"s׫Gp}[#8Z;"= ^YLF|G^Fj$ԝ>M膭Aĭ c@œhJ4v "q "@ /ǸY@`5x1A1-05GTv?y=’1ne3af[n^~C;v<'ST$兿άŐ51'ׇDcs:_כQm[6(ΧUٶmcy58"@D W Ҭ1nxǸ6π )N(ΙʿiiӤm[ᇥ֭Y>q !{$?lC޽ҊPc.; ?HYKi 8'^=Q3&H7(KFIsJ #06S q)T s//{V ,xuN=ScNTEl t]uTcGe'NZhG&#Gx")YS:xP2XQ>w],tq7"@ vBM6CIKJR׭+Mה){c ` \Z)ҩѢo?+W=?7vko1")Щ׶Z- oD`bu(a X佽K%I]ʵj23'$:|Im.E պ5Ц_a|c$zyo4\F]{dv !" "p.-W)pS`TbmpR`(N q  1 &vDAb =`Ǘ Dۻ2;v8\q Xxojy3X$AX[>"@D k$ +N/΄ Dm# x9S ?4S3& !$r-.zu%OFc;froyA/hD%jzXZ3q>Z_AJ'L)̥}Vhڪ-cqn~ v%>Cw$k:Sc5$~S=QAjDO&@[+v+VbG^|8j6s{:SS5uP5DC4WsUQ5\%Iՙc|KbS LC!J߅>ck339>!-R@U?py]$Mb1Ʃ.| 'DCkgP -r(ZUVe]+5Yj`|"0ƩN>">H4p)Ǩ׆##gLg#c<>"qSdoz vv\6'tG;$qDR06^оީۿ&Rf%1B7cz&l1yT ~S6):fn^^cg:"8?ߜI@⎌ 8DI$g0zL g*i{d&d LO/UKCtѾ{`|zLՃ^֕hOe4yд[ݡCAIҙ D3owjqzL!>H]w)^=Sڹe$k$'xt%$tIҲ_W\_S=R\M=FylpHZ]dvI;Z~;t7NGᆱ{gT*U4kh;zT-;u}f%tƍGhQ׫ޫ~wY4q ջf/_.I>>2zY$RN;e(X?fYnc:"iNq&c@pXfG Vz̺۾nOZ&?V;v:"rS~?=޶Wn \||GSn^v>*u_Ѿ*=C[黷mծF RE_t.] w97M >O׫ݻKVVuVoׯ\Sh'{ \f]3fh&L<"ݗ҇><^:U/>h9p/^\]7xVkΚ5ھi0roVa>άjRi9L~_7eҥ.AϬ\A:?ԓjݥK//*<u[W9i5x<}TV:^OkjقZSP6=8f9*ۍ;')3!m;XT#+K>XmWz͚+6+''\\U$HUYy4W<8:mnnFKziI:ߌojݱETDTQ.]OSC%NUΪ\JܷON_^'//pyvN߯fvǍt_>[_ڥDRJPbz8IҤ%Xoj1舐ʓM)h~x=$Ik $~.IMw)*t_*둤(nؠ< oj1A1'7kըzn ZjP7i\K;tаɓ;szrf:vTN"I4n~7|fƌ)UF^$>]u޽sua|H`.vV5SwV* s]Զ^Xܲ);e̲*ۭcۉ;ک#be}`m3&pkٳG{ ߠ $T*/ONTæMխ_?p 7I [MZ!m|a%78YxS.e bBWa5 MA8݁W=_ ")kp[9 ccS "7 `@B=&X*p& x^ $cc8Y 6W"sa~eokktfp?#8Z qB=vz} cgb=,wa9i& @Od#q'_5F" ^;&9")B6$3 R+z?j b`Vx#0‡4cĪz) IO&ԿNnZ3O^zh+NNΪ6KDlڭ;:5*z@? M30]ghfܡ|pa_N qv >|o{sվ{խAo\Vs2?4(;= Z^aęB-vS= 6Y#I'yMX[ҲX?ԝvԍV Bɵ PuӚ5vupǎZrn $99lF!bb]вeaVn{6 QjP*UtEiҥ=2xvRuk@Wׯ.̟Nxe:o_S'Ojȑꔛ991rJN {w[iu`wڇM/ Eة bl-}?ׅ֯-[nukUqY6:7?|W)SO=.3i^:UCyDC~XL}4}8P/.F9`e q# jqb+pWfC̜)COCݻ8yZ-[@k }&y<+))s_6UB 4$9>EEǣNu*^=-={w޹SˋzuU^jHw*w<:u=X;Gt̚1*!$frG~ ntDyO]u޽s˖<4oLJץ:hɁk F1c{c}fZwo^C>ݶi#k !6HB W-8EY|;&yY;6MHqΪvA2"ا¦[#MpS ɄIŇ#$#D\χPbnXnJq}AwS7Y V/x[ v' d$vV4p#,3 d@0a-@z2;?懏d "@L2Aĥ,bvG Y,SDI^ l(ZG$҇ k:-{ Uv0M,O$VG8!i,; ۵]s@p Xίh;"Hp*ZfUIk |A HR "đ!]bN!|m8jb }oHߊoFgX[8jpDwف#SQo$@@0 # C"T]OA68;Hp8I4HtAC "@ƄsZ H ‡5!@@+@ B"*hnG#$H"uG W"!@gJ#V D\'\aA Ie i&.Na,0BQ Lkn {D$v #6Øu@b@Ru"4'!ua$ZH5#t?! 0";~xiZ qsH酪~+=C;iD_5ݏoHwIA  !f:KN^3ic?H=Dq+SEE/_6BBVE?!1kzK5n +8$o?߳]o&I:uOc1 F7ó#e58XQVmΩR3}?n9$/_gKjdG>·I8f5B#8];ZZ_yߟ<ٮݥLJK-"U.~w {L|A@ p0b#=ұ37{o|l)+G*> !sgWI]q{MZHtt0&=#i.@bCM457J/oO_<>PY^K_z%pgH/<"5wMe>aRv]' %mw;Oa|a֍u=z=ʲxqikR3vn=VZDڸZjD~S2T:_osNY^4DAYLO*мwTiЯtZ9:cq=7ڣVҷ;B<&3wfr4:\$匞^+m7[>r߿ t獫}?.[wl;6n:JO"Ei~cA=$_$k:"ӋD Q$68KϾwTR?ߝq.陇| )TTrwUzz=Q!_ݙ?,3ܮVKMםz\UTMhHSLt\?)NhRoUS }#-Xdg?p$&s1x5"ȗ_ޱ]ό|HsJwj հuZΠj]}#;ںSSUW9 h= ڧ'6mmVKu:,I:KN"_tP]gIpwD1\G);Gm#Ҡgۡb^!{yqR|"#Z[u"/"npuW^TkkR+u8wVj&koR?{p{ZdpUQx$y˼6tC ECF{n$ݨQo[SRu@;EgJ)I$kZEtR{ <~Lۄ;#k0?$o܎fh'm 3?tk5Q=?m?ׇM$)p*rU_4BhND=}zLÔO;D)S1^046.s!tC܌H ̷wgֻ[תjC}e7EwuE>DXKZC5TS$MRH;!5Nf|+ެ@$ȗhh`nR9jIxRkXH2~~ 'y_D+[9ǴMtR'@MG[ {zMi)5'ӥCh̓Yswn[kj#yD>\4-҅jO#3pXfaV 8ςY_w=ҰtCvjKc::Bqp3A +ޔjdIJJjH+}ADqڤJi7fi  $0"c  gxx*> }ۂ[C=%UW4\M=>dd5Q5G`{ >(8]O}[[_"˄ O3 UWmgzIkuKR&*2cR]N8-:vT:Pn̅*HS4Ti0߄VGP|0c6h'BURKưL2 G\"Gl T;2RmD?g H cddw>MCAw;$ȗjԒV} ,lq=>VEUR:M"@ u?Nlw^v|&V:׷t Bq YZʂ @RqKp IwC}Gb%p4e:>>tXHh$Ȉbfh [w@?,}BZD~js!-qZH.Y)ͼ_c_$t$'?=!(-9 71_ەv $tGwA4-Į#43HA 8$ vm$莀 8$<2UPE3[xc $) n A@<N=KuA@bY ;bsu<8DhtC@AL $|   )d"p}#A. 8!B*ݺ##D@2(A?&cxT-aL v| H{톄;\xtDBB'bk隸t?{KwGktG2A$ B,>q~ #IݐdIW@q-qM ?B! Y3qS5y~5h ;B!) "&HYFj%+V]W` q\C$Dk1D`D&5{"ŪiG 8ɞRH=6h'T4D$&5{Hbh5nHf"0W*w ͍MԢͥZ VJu7FGԴ6!'Jg.bD`wv_q"j@< 5K a6XA|Gܵ(rDYRz ݐQ3)tC8Yfd k9;Ab#,&2Z)|4*h{GsJ V zs e2,F[.zH a3rbL<ܚDR'lqghmJ%&NYĹZF6BTˮ%qw@kFwh#D)zu%OkvGvH膀afiKa)D $FZq. G5:no2A!Q&RM3R,C} DY6OU$D|2dÈP=!+NH bdIkݖI HfHeuF7 >HS<܇̳t#LS":X5AG$BKf[k2#ep͈UnjtH<CB7@1LX"6wlkAfEpJf ,F>DLZHt{k<{hHh  $FKpMB} wY#m3I/#\Q;#VIvG  #?^J bF F$t@Q AFHZʒ@I"D[0&L$ܞtC@AWRQִDx( $F&mⷹ1LlDo-K$Di !E8@b>"'"H%J"լhZE} 8gDMp&7"0^KKтȆ3 |9LTlk5:A ]J4DOAĢ hIq8D_eDz5`_zgPO<ۇ$Z&7dj_M6F$")#&neג6'TdKK}AV78 (u-Xp "\PRׂ 'F'Dl&Vs8"6Ʉ6" documentation:remote-interoperability.png [LemonLDAP::NG] />

documentation:remote-interoperability.png

remote-interoperability.png

remote-interoperability.png

Date:
2016/07/19 12:14
Filename:
remote-interoperability.png
Format:
PNG
Size:
15KB
Width:
546
Height:
439


Back to documentation:1.9:authremote

remote-principle.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000324431325274564300434320ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR2LsRGB pHYs  tIME*5tEXtCommentCreated with GIMPW IDATx}T՝?=32 hDDj7ԭ5A#d h.[X.haLH RbSnD`f`fmOO{>OQTϝ=99o1MUP @*Uءm֬Y ***2eΝ;>iyL,*'NꫳL[ooo߾SNmkke3,2uTIo_~eIguV^<o'kW^ye ;3v)q!F7l i9le_6?קO#G|嗃 rtɚ5ĩ'<'-E6lػロ@\g'-ۋH7n%PZZZn喾}{۶m~>}&NgYg:?~|ܹeees=qD97n<Ӫ׬YYȽ{3vŊw5~^z :tƍ?|yNK,3fLCCCcVk&(|ǵo!{ejʕ9;v֭_}ݒ.]Jz衇jkk%޽_4rHXC%=Ӌ/4s}͛7K,m&iٲef }K.^ؾ}$49sҥK%vmN֢Oqz>q}:KŋsV6m4lذ@FSPdxuuFk3(++tE-]- %555566vcǎY,aEEe /fQX477777Kur~P8u4iI&嶉s0tvv՝A?c Cs֏eeeo3cm^zIRԢsb(9-l˖-a/ :9[Z?EO:Nx\?㒪 ϩ¢~zΑ#G֬YsK*--<͜M/޽{ʔ)充3fhmm~rw &ܹ>4iRII=zkx9zٳ͛w QF/_\eoYi@5"+  PgL2UDJ "q'*U*[TTmT4kTi^8nkfH[C%pe5ipch,@9#ueFAҌM[ T -@N1&j$-p KCyW|X}(g+.χ-lv".%)0d KqЃ #-A7-lu(""/28+Q_! qTF|>ե #~VTU#[DQ6"\0ۡ*\ګ2aXɤ<۹&(6;TbɲԙXёrz)OzURӐ&%$&ڨuʦ0B{R)BDی&ޔU扴t=)۶/وI /KӪHcǒ&@8$u|_NATDyDzӍrf squl={.bu Cj\zgH:V&a=%AӜ9.e 2sۢ#*~Cf9]ͽ-\*4iƍcpywYK,]GԼ[.MF ;7yUp#ba86ػWcƨV+VV^. Eܢ}U^{UG$;ٳUQ޽5a>ZVvixW/ #_$ضM]&_}hD}0x,W;_2N f$l5[񂏭W}n#Kͳ<1̛xmیdF~/ļ0۷G}HfnF2#Gcgcːixut6sFG%ˮYTSZZt*XփuɁ 56W~: }N=U_|VgnۦV6SRz0j1"snU}JRyyp6zp9ѕtv'eǏw=0pWTuVh̤If˖nD}Mo~c~csO:^NH/٩ʘYEh! PcZZԿܿYee`~:|XuNz0V1ZkjXR: 8;Z9ț4fw'MիparOYR[Kԩשϧ#%TZ[BҎZ/`bUT%Ӣz"gܑf99y1\`F2fx P;4ee7&;1̜iJKMfcʕČGW2妼uW׏"QF-gILIw1W9D-́p_ @-5+{N][pG =& ew>79$`> ;<e R EQ*j[fҠA**ReLΝ]ݹS'_?n eqXSaJKuu:ppv\Δ;̙z)W+-]:]w]W{jX}vлk߾jovOQ*G5c9Hޥ_ s֬/߯o1.ԁ/t: =µk%iʔaя$iz9 :SZ➤Ǐk\UTLsĉ~#ʷźw7T]%8q4h<{0@ƍӆ ῲ+ 8vl кun]6( aC検F2O?m/6?۫l< >usȓ}ll$S[8>mZ~wreV;lb#F2{~\Hfdc# dN&ਂ:F45n÷:&eeF2]d.5mmNnl4ǎ[ ʿi6,pBAY$pWn^8sf{,1˖̜٭ۼZinuc#fSSZww6SrSXh͌;w3}}͵ך?ַd{;d=7 آWtCoi٩Bﯖ&鹿E%)g~jk l3\q5 %R}酜tO]~Sڥ"]q,Qm-;ʫȏř_)rZ y|cN&3òm!1 g # 2 QsHB@JEb@xpO|p-vL<.)o6W 0@A ̲߱^b= 9aEJAt [`= .xQAsݛYȓa0PB`-#!;ݨ93U05Dw:p^sHNbp `? #aE8YS^P@t*`4 g H `"g`Bb$!TA' p0`YDv *-`  79@a^ΞUПvzA=Ys[{: LVv :NAH,)U0z",U0!p0H/缦*H--q| LsXE9mQ0s'h +Vi-(fY|FLg$rKPX.XApD0,ᨛYjI"1ӻsV{Rf0Q{-"$"=BsCUHUXFS*+gFpYsC*rɎM(ޢsAjF$FԖ- H*H*ÀLv4"kے-n)rcwGHe;b _[j.IZ& I֓4"*+k'Ŀ<ʅo>Ĩ[ޖ$FGKykO-e[l;$HWCYh>Ky"QGgFv!,%yL7߬_֩!uvj(=.,S +adN cGD6GU⎜tCWSUG?s$n|k~^>vW/mڤQ2HٴE$j BEUub9iUdG蚏?|kԄ z}͟Sڪs+iSf[+E7$UN Gݤ?Бb H"TFKN>Y:r$- adtC"'P$M,UЫ2$X3[|z]Yf ( ϗCItECF";ŕ3Joz~ ;sϩPgKĉY,A.;97opl-QNq1%!zkFmؠr-\rwypn$pڨ[M\ ;x¡Miǽ_=a0x@۶餓4n{,[(Q) 8tyiRQG iёDT3$_wLi2 lA&+O8p-SId'" I8$702syx 8g-\ёDn3 @bxFEjp027\8Ncqˆe "p42$.쌺TEPt#p~z8O|THv7pKzQ 9_ Uݹ!{.gdmv^rcm1f#X܀NR~-+>ӏ>: gQֶvicB*>,a8˾*38EQYō_uACwF{Bw82) lɟsFnBr):{6 -L슁4F][݊0OD!tUʉEus5&VEB\J  $*B @wd^"a|-Jo.  #CnQ8\Oȸ}v ArU\K# w=^kd݈+/u^t-a@9BD"a ȏL";8f-sK H9R4, G]!aCs r+ l0ү 0!*1Qa8¾^;¸[YvxvF]!u 죏`[;E.<aLG]nkUc\!w02d`2'\'8C'" v)7?fs[ےˆ?:ږmLRn!E'$5advF]mk\g;S#Ha Q)EUupn$~Q7yQ/MX7d"Xm ;yхH"'D>s$& ɩ"aѓ*4Hpx񅑔*{ +dU$rb 'k$J6 $`8° $^}>I"*D61Ψi쌺 ?U\8EO]k# $7H* $e>ej`[|OUt]='kFI3Gz an`pMbn0P8E巴h U08#jV$@p=J|]FQ7]"QC°iHO ;oTAL[$5fhMz9*wԢ:x/]m^UwmqWln0HsFpI6p6g.FpqF1U@ @>) ^U??U@Y۳g={؟>8Рmmmf4hPQQQee)SvI[ЅvZISL >9ƘQuVgoӦMƍ2şzWW_}tҺ뮻jġcݺu~'֭/XQQ1z?D[R޽LD:@ܺ1s̩t`%, mQRR"/trc9HBׯ4ydc5\#iÆ u2se˖I6m=Y<ť^je#Fծ]Ə_RRҫWCnܸ1l`=ظqiV]]f͚X9Ν[QQQVV6w'N=kB7 $ 0@Ҹq6l[no߾{5s|w?C+K.y:::o.&>͛%+sKz/^,iaOYz}4jht+WecǎݺukWw}K>쳒z!c̼y$-[l۶mFa]" e˖͚5fcY[IDATx c?.i6O&<ۅ:;;O9唂8PXXXSSNux3s;VZZO>cǎUV5zK /'nݺJ.B}HU466;vLRAAAl|766izcdh 6m6lX``ɒ%j$qƘI]"J\Se55;v1+]H5.d-'Dm۶`444X5_UUZWgVN[/*VڢE$M4i˖-Q+Nmǚ+xQQQS?QDCM?YsaaaX`նuКBI.T&;e5ѣGWXXѣ6O&<ۅG /~!=ܳ|rIwqGh]:q8y3gδcc5Ϝ93"qTb駟.iaַo_kiME{9G)ףT"Б455IӧuBKKK9VVzf>8p`Sj,kt 7?qllַ% ٱcs=7X'O?tǎ{кupAm믿. ,ύuӖԯ_?I;vXdIĂ $YY$k^z%͘1#s&nmmMoC|UV=zW^d [ZmiiټysXG\/ZV-ZViVPyy]wì"rrٳg͛7ĉ+W?>VhhݻwO2zƌ};SO0#gΜYZZj]~>nhh5jTyy"5VCCC"?~Sd](`xz bȐ!FEA /P k%*#\pٳǎc>7qp>_}Շ~KO8X G-(%IWzniRǏ}wxk@n* a'׍E4@$RP@V%ƥT@&*# sY3$ NQDXuwEM~l IΨlQ2_"*AV  =EUML[ `(P0ؚ̏U @zB#kP~/z"2s H"_fpyfT 14"d$ T 44d$ 'ꀆ& T :t~T=3蝔B| O=*I a <* }`x›$a ޭTƗa2^(H?֤*<û)@b<2\W( !'+TLh57ĉO{kpZ̶6:tjj4w矫@ TPR,qy&N|MZۑӵ|y?hz['* Ax?oWCCI҂O$Wޮk%饗OR T@ɑ#*-բERi~SIڳ'CHWK${{5gxU UtW~:K{7|SutO~Q|=[睧iT\_2ދϜCx]W=,* "'zJfut{5>]̉~B*+Ss>Hz w^W{Hg$]&vOI__dwܩ1cg:UӦE9ք mjjlԜ9z9utt;y~{˩tx2f1aCY.%Zs{zK'ISg1J OҖ-:,?w;{بWU0=ŲĉzUIV]Fh&NTk~S͟s`K:rDŒ4o<q-jC0e '!,@AFe}xp䈎h߯Sk}T ěHE0@_wڷ^'* ftѲeJm䓶>'4y֭K%c4 CTV}4H T!WڱC:l}?ۖH BO pV]}o׶m:LIڿ_UUOT"bثoEEjj=h>56{%ix) ' }ء/ב#]--U}}̏(c"uBV^|qvO_[)uBV)ghç!H(2qv;!eX& TLO T$x"WHUY(DY@8'*k >zaUV@mmu):$h\<^{M>Tz飭[hc7z_K$>sIjigZ g״i:|X[P0,R8}Sx/ÇWu|Q|=[睧iT\_2ァJm٢ѣURU_OGшUK/3%-%a'_tjn ;/{nGz9~GV$?Y&HҴiԤիب9ssvrg$qScMӱczYX0!F^몫CZ@F֖-:,}I PcZ[կ$57B:rGVd'l}$]q$͟XR`'? ?Z:|8c^TXHCHF~X--jlԀ]C~}|uh:F6e 3D g5}z,=Mu뺎9K.Ν]GN:I6ihH,  iTW1cԷ~]p뿴sV\R7rrm܈'*Ă-YLo5ysU0VB OS"W@gS'T\ [ pUG=+|>\l'Q%2=(<5"ɭ08(g<#v)*.dXͦ0H6U8Q dphJ1g<P[*\匊 Pz's,*"^"4 OY e~OYRlfx**HU8:R$^(H p'z^ jχ'U#ף>PI-YE O_r*D % bJa PRwU 5dhUR((U@ӆ|e! [}j pmksnAEJJ} =x.fM˧>V@VCHz.+ 1 p$1yҫ;#- |.5$Bҫ;?EJu='@n $"ȘU\q zaDd>v$Vza_)ث|H&"p՝aɄ:3b>- #-VH)U8+.jԀcB7 fIxѪzfҐTasǛPU>zG匠-w;취P TԀ^#_$6?akdU8]Rt[q@B7D=30H)U8]ql$"3 #-P W&?ðĹ> Op-ATUDE0!POh0 C*c0B%J|aطHTJ[KQ 8r EUuP@B6>WI7X/&|UqpM)'0@*aaTA[$$Dg @݄æ3dAI'@^wF-zI[q@[(aR: $ Z/SD@a #LLP $*a#3@T X*d!IENDB`remote-principle.df4d8eaa9328b1185237cc9de7d0d25d.png000077700000000000000000000000001325274564300541272remote-principle.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationremote-principle.png000077700000000000000000000000001325274564300474032remote-principle.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationremote-principle.png_documentation_1.9_authremote.html000066400000000000000000000117341325274564300456670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:remote-principle.png [LemonLDAP::NG] />

documentation:remote-principle.png

remote-principle.png

remote-principle.png

Date:
2016/07/19 12:15
Filename:
remote-principle.png
Format:
PNG
Size:
13KB
Width:
526
Height:
460


Back to documentation:1.9:authremote

status_standard.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000001136571325274564300433660ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationPNG  IHDR~ECmzTXtRaw profile type exifxڅN 0 { 2A/S'J@t׳ ! IDATxy|T?CBm?* ,.}@-[*- {p!(,H1$pHB,LB2@_3w̽;y<99w;wA]4ԡyDDDD """"uhCH@DDDD<""""R:4ԡNC`NCiN8#"""Ҝ: qGDDD9uďHs4ĉi?""""ͩ'~DDDDS!N4NCiN8#"""Ҝ: qGDDD9uďHs4ĉi?""""ͩ'~DDDDS!N4NCiN8#g)P%39NC;'~UJMMz رc? EEEŋՅF|ضm-[c"$$Dq'LN'~N>NGy}҅ /k>v@IĘ/i?uq'qvuԝ\t=eeenv]zV1_'~N;:z2dmWNzzv1c&P!N_ۍבm5jOCdd$k?~ . ..zQQQHII9skܹsq Ę~1_'~ӧc/؈.x衇ЫW/9m4|0hnnѣGtRƊꉉO?O?f/zj YC^^\x.]·~|w>[JJ ^{5TWWf}?8۫W/ddd`0ę3gb̘1;h W_Woى|?lF~hmmh޽{#@ePgn:tnDcڴiq`@ff&{=|hjjbŋqI㩧BrrG'g|*&9em}IKKÛo#Gd2Y/jkkQXXKnvDi?uys=Ⱦ`ɓ?~qv뮃 Q]]?.?>F!==ݫuNeN>#Gd0}>mܸ:NQp{-g4FSOGgƌ8uGm$I8Sҝ˝/[g;>Q/?s\p۲}%cظq~u_#44ԫoVV{/z.=e_#::ڧe?KSSO=t 49y睊}&kwL=q˽j˗'I4ĉ|3̝;ף:::<{Qn2Ͳ׿vh?33ӫz) ͛ݲeOu/]/믿C2ҝw٭q[;v,?twۀQ>teI: q._ NϨ:tcǎEhh(ѣG^c*]a00h صk>}YlAAF9kSSk]}ᦛnBhh(ƌZrwذaNZ޶mFP9.u};v W1bDGG#88!!!4hqi:] *{HIIAHHF|r tW'm9o{$''CC#)) 'NĪUPUU岏J^mTNLr qllldЀ, <zBbb"~a;vi ?Y'~fXMMOi?%{j,,,0aӲ{Q%KHpA 88,dE}BDDY$z~y2q^JY*l}|G߃"88XTVӡ\s=C2ʏ?s۳~Jjii,(.͡'.8q"6*=8Y6++KLSSRRR$ի^xU#<ovp[N?䓒G%Y~͊E咒3glXXdQrgϖ,_TTo?CdY1+YΒ򗿔sΜ9UM#ΝS:ftْ勋v裏D۞S,dY %׉'t{=SoRQݻדO>)YO?UXJ\\.<Xz5iZms-QɲΎFvDV*Nb]gg.g7?(CgΜ,'ʤC*ꛫӭ'owZVx;=(ۓԗN}Vۨ/@G%:Xti@lGdyOY=n7.pb߾}>e~i2w'HW8\8\$I=7P7WtxNO|=gm`0w\U0arvvv:sWog6ILJ<QIξ/f3F!Y^GNN*Yi8SVOll,L&d]{=܃zaȐ!5k^xuۮ8}]mۆ#F $$Ç%577O>>YÝ^Ç[b8d;vCpp0z?ꛫk>[n,p7f}vVdd/SII z! 4aaaFX= J׿ѣG+SͲpuuuU__GyuX\s5]_V|gӝTM׻|áCuKzO>R … x1j(?~;o߮qP'OwNzm#Pѓ/;n# @O~WëO?.\@WWP__O?o̙ӋY[޽1|磪 .]˗҂۷o#<"z =Ԅ.={?~8ׯDZcp%tvvǻヒcm,nvGMM P__>n>H=z-+N<999ؿ?Μ9V\|8<>swƌ#kӧ;૯BSS._F>|5kWٚ2e ɓNdVk&&m8z2>!!!xe|0ͰX,hkkCmm- l2p nG}Z&~D=_DD?ljh8# |?ljh8# |?ljh8# |?"""i8#""?"""i8#""?"""i8#""?"""i8#""?"""i8#""?"""i8#""?"""iCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"u÷`z*6+ƣ~j46p5NԜqGDDD N8#""?5&JQoqGDDDÿ ԉ_PPM<:u ---hmmũS3f ((eΒ2#FO?;voAss3`2pQ+曽z`0ir&~rbO?qmMz=8 {WVV^/Y*g3fo=m7|Q~ss3~aDGG#22sEss̼yaСC-܂8z㦛n֭[E;CDDDdÿ _II(w(O)jC޽{9yo=mWWW'OJJr($*c4a+::YYYQ[[\|١to=m)~VppLGG6]wu%'yΒo=mgƯo߾e|uS4ďz{̡?.*cO$*`$$$@ApGDDDz/==]o61w\DFF"22> fEuL&Qߏ`.]$*7{l3f ˉi`TTT8sܷoenY򉈈l6}#ax@ot8;hVZʼn?A0sL磶hmmm۶a֬YkDYYu|m2o6Ξ=NաӦM/'~DDDULH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD "+V //O8z<,_\8HuoDcӦM0L0Lظq#t:AQٌ [ z`Ǥm PVV9s ,]`@]]%۳/?l0ҥKt>C :TV[JfY8?HNNF]]5.HUodɒ%(**Bbb"QTTEA_W(((^zP&`n69sӦMCxx8^{Mv݋/믿.?=bbb{}vĹa/QrssvAD#BjjG'|A/~ #44T!"?q5۵kfϞq%%%4iv[[[EǛPmGnpﯨJvZaʕ FӦMsX~(..| "U&DFFZeN*DEEi=D=U :tvvòe AP__z gϞEKK v܉}ʮh4`0۷#;;шFvv6nqfggVq77n \9N+Gtt4^xرCvkKRRUUU!))m;r ٳgq 7(@DDߎQ^\\A@HH$׃2DtU#.f7|0Tҷo_L&•_'* ::/^]3~{^+Vm;r|G{nھ~+g1?9|Tk<z&`#â ULL_II &O,'0m]AAA8qƎh,<ۉߔ)Sx<3(,,DBBPXXŋC5jփB#OѫW/ 8ͅ o7xCjAA캳uGbŊELL /_/r9s&*++'wz;ۼyXLDW 7^z ff/-9s૯BHHCo޼y8q:;;qYlذ+ljM6صk;QWW'V*u9%%%hiiAKK JJJ0b. --MѸx>o&~ @}}ӟljGK|b 4j=V~~>Atu<""""R:4ԡyDDDD "+V //O8E^^o "{o6f޽C \yeЦM`2`2qFklSCC 0h G4?Axu:}>dBii)&LAg}VTgΝ;}?wr۶m=Wc9n8gΜ \ݓ97}t8pp!!!AuuuHNN~鐓F466bݺuG2aEHP+#\HNNF]]5_D0߈p߾}f(`Ԅ={`СEt͍Nb.gX @llCRm=z=z49ⶼΒz=:;;w}'wAG[Nv{򺺺 TwojqIF~A6i>b~Cӡm]};v ٳgq 7(k ""YP  ^Wt攈~߈/fYg'N`ܹnNLLd^^d^$uꃫ唴p6Ii?ODgr,11Fqqq^O\sws'HڨQp9+ZΗg*A/<ؽ{)~ݶ3(,,DBBPXXŋ;]iii.… _pn?"otWi/&&SNEMMJ骽"wCdd$"##"b7tP;w_jj*jkk1qDkśoJgjw XhΞ=g-\gؿ?2z췿-x O3gRqoc*F#K/l6l6_^%\ZZ/v2HII?%%R]jr5556lӱӮ/Caa!֯_~!44?ޓJ?^/vxvff&Я_?p`ҤI`ʕ+w^yJx\deei^(`yxx8!:$zeѣ-,, mmm.6uTT'_L mcެYڱc׈껒 m{͛ϓсo+۞`@}}=A@]]'껒 m{ݻ7Ν;g?OCEE^~eIvSy?fљ昘L&E`wy(9kjjDyVBDDqq F땈MMMM,Nb]bPQQ&g_GEjj*F#G-P__' 겾N=g}wܵ'g?Xnd]Lct:tuu)Ν;sE}aCA"7^/ZzoՁYÇĉ;wۺa2a2_{b\+>gw%0,ш8'~XIIIX~/>3~x筗LsF}َ}gȎqu ?|T7UUU3gٳƢ!!!hmmU̙3_kldf۞^}U,[뉟փYqt-))ɓEUVVbرy_׽+ɳɝNLkȖqu gPXX$$$/v\EE\֝ ZpB~EDD'^O#L:555֋SSSQ[['"$$^{-|M}3jOmС8w?Oݻqw ,, }UׄYYY}#)) III(++sW;v 55!!!ӧ֬YJyxr'͛7[DD:㥗^llƋ/hMj4_RebJJ 奒>tK.a߾}馛dg6QZZKTo]pwW}p՞lϗj,=]3f@EEa6k.\wuZ(=m9Pږ䈏G]]oLaÆ 焮_9~R̜9| Ѐ|\s5n\}m%%DtU<""X|9nݪyRzn|iCulDDDD>yDDDD """"uhOXyyy(xs#l6c2d6md}ƍhM (((Ad_W^^8myO>zZ.g;֮Ү]ϊbΝZot4iֆzlٲ9 $N466֭>U>Ed!>>^KDt5kXȻd!11(**¢EKHHիQQQn[QQQhnnFssMJ,iZYY)Erc/^{-"8p [|&M \{ŋtDYY~gWyJx\deei~(`nxx8!:$zeѣ-,, mmm.6uTwJ`>G}#G"%%'~rkW}y}vzäJII &M$tFARrΟ?kע +W-X06m'O+ۈȖqu (fbccl2:ֆ K"''my%}ifmݵ'w9gA@hh(*ja<ݾMLL /_b4B鸘f/1110LnRr222ԈVZ[_#i`0h4jN(`Ԅ={`СEt}Nb.gX @llCRm=z=z49ⶼ>&o񨩩c&9kOrcn nw߭FI1Lӹs]niqX,ֺ>ct:tuu͓C}aCA"^-CDW=7,lq ̝;m݉0L0L֋Ľywƫ*U{.'/?|xg3~J߷mb/~#]qơJWT?}ڦٳgE>Hթdt99}ďEXXZ[[e/))ɓ%nו;vwy?gyxr')S?"F\ty"!! (,,ŋ.WQQ4ub…/\z?&~?򵞼v؁TO>Xfuwx\233~$%%!)) eeezɝN6ol= j4^z ff/"zD]KFJJ)))JZYяq͜9| Ѐ|#䈏G]]/wg:6l>s9sy30`NDW% "˗c֭!E;Ȟ:4 JZFDDD#@DDDD<""""RĊ+y"//7w=7bf3vލ!C@NhӦMG#lܸfЀ 4Hx8pC,]e322[oz]uuuuHNN~鐓F466bݺuI&mmmǖ-[oqwQ"99uuu|Q#}b͚58x A%KPTTD$&&-rX.!!WFEEӺmEEE͢+y"7_W(((>Ǐt]xA233QVV~_~(//>&MBDD V\{SBǻ"++KJDAp!FugaaahkksYwSܖwGN/P\\PW*Q>ZRRI&>ĸq0a׫ْ:sF>8<֮]2\R`h4bڴiOct:tuu)Ν;sE}aCA"7^/ZzoՁYÇĉ;wۺa2a2_{.~a֬YoJ%_ll,yٳGQ3JJjxƏWÇKMMuz߸qPUU%^9s>͞=m,ȍ3//_^D=hII &O,cǎ;Vy<ۉߔ)Sx#< bN@ZZ˺ssspB.\h/q&$$~Q^tBnnL߿IIIHJJBYY]Rر A>}fTVVsǓ;ym޼z""hՁ<88/f3f3^|EPKKK.U)))Z^*냒8̙ !!!Z\䉟'xաt:6l`}'̙3' 5\6Oow j@D˗/֭[5CZ7w= """"uh@@pG4ԡyDDDD "+V //O8E^^o "{o6544 rȳ;L&b„ u_i{p!eeehmmř3g`^GӧhooDž 9 ꐜlL!''hllĺū PVV@!oQǻ(:k~(` RBBV^ >k׮Eii/^l}P{L_~ׯ,nÙ3g0m4x$nذx\deei~(`RXX$\-7k,wb)ioǎ3gCvL4IYee &zeۮ]yt%66.\=4Z%gXv-ʰrJQނ P__шiӦ9,?ydlRll,-[fK^`0eoګs{N({ǎ]h4:fQQQd2O=Ξ=ܹ}u(~[qߔ 555UV!""8~FQIDbAgg'<ԝg[^GggCݾj6W} F$Dz}9?ntvvBӉ>X,>ct:tuubW^A||< /`ǎ28{,nE}SzmDDDX= qqq!!!}Μя2>|8N8s:ZNS?%Fs!==]r,Ĉ&~AAA8qℨn9<ۉߔ)Sx#R ɚ`ԩQ|WRSSQ[['"$$^{-|Mu}.ZgϞ~3BnnL߿IIIHJJBYY]Rcoނk̙3QYY8FOvyf,^XKDHP^^g{93JKKq]wZƛ~_FF۾}:K::6ldd'5fشiڊ]vn8py30`^BDW "˗c֭!E;Ȟ:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhCH@DDDDooTm &~G}:r+0j(̙3[cbG~JZz+"@p NxRRR^;=jB1Mz IDAT:.%IuS$ij*_<ӟ^䁒7}u{ںqQ@o2 BMM/jWyIgΜ`r%P}W؇'6(1cƈ---hii}vmi2H5fNz.5)e/yyyغu賗^zaO?TTgg;|d㭷‰'Ԅv>}۷o$=#//OFGGG~;v7|ftuud2ѣxWp73C{{;F#}L4iLJf.iӦ!//NBKK Z[[q)cƌ>{{ /8|0ՅTUUo0|pW"Q_ ?@";;| :;;?`˖-|O ?f@mm-6m89+#7rT}Qo0 F]]@8}t̘1Cht0##CTB Q 򣢢}v߈vGb ,Rb駟ψ#pip̾ormb/gnIߜT%Oқutqŋ8v6l؀Ay~lK7nܹs2d/_[[xl\|Y'N **JqM/=پ(@w}҅ ЫW/AwBss5HNNINN웛it&ݩm9ˊw/3fQ[8q:mJf8peee^=Ժ+++2RSSnώ- lvzjb u8ێ!7jd"aCt@z׬y(wqX>77WT'?䓢_]?w\Qӧq#&&z-p@}(y $$#F~!dee[nA\\z=qM79mg\t Gtt4p}}}Ag#::;whbj콍ٛu*ʟ2e z`&L|Wv8K} Gg?9?YwQ֕}jkkã>($%%@_%Jp6fl_D0ۀ޽{ŋرcƍ]x:nVQRQ~ii([nᇢ;!dQ;w.{޽{:y(߾} :Աxb|gYۗq/?C'xBToc/'fo]cc(ƍaY sΉ.7e?hiiqzѥKD^pA߯_?ϲ/1HőP&))ITh4z5ͺ۲eUVkQ/=㢶N88ڮېX,nnLӵ^+ UT'~l_D0ۀݻwNk֬q(fQ]v9yDeᰬk$/$ux뮻a2.7ArÌ:$%w:wc//1{g477cԨQҗbbbDmx=:ek7=پܵ}ݾdy_z}QoZN y9KwwFEE~2ڵKtxW6 >xʳ{yՖ}:3~%}Y>(=>>o]#G"++ oHN&~b?sS7|갟<<'qrd"$=cm޼ٚֆ~Ynndwp ́ؖ,X/]>#QYE}Ÿ_'^w3foם+rhfQɞ<_QLQ[R7=xWk "v[uEDÿ h9S;7j(Q;nV8͛'*WUUYf!>>:{ưa0k,_^z'ߗ.]={6z3{uYf="##y9a_/,kr3.};w."##|Q7({ocv;v ӟpbȐ!@PP֫Y6$ʿ=~7|aذa@pp0Z,_Le|WGoߎ}"22ѲwsjGi]o{6_:yCRI߾(`&~> $uϞwCjcBl>`+&7x۴j*1gΜqZԗ,kQG>}$ϔ٧>}j콍u'7bСexxGg+ӼYUGLL [|z;oKIj"}嗢 8^%Phkr.^(cAAA2e /^DWWf3;|,^X=R ~gϞEgg'PXXiӦɮO>Auu5QWW;wbҤI-o2|gYۗqt9s&Q[[VǶm0k,B={ocf 6 .Ļヒ & ]]]hkkӧQTT,w&DYYkqo~m۶رc0¥KpI!--M.k_jѽmڴ hĶm۬ofSu~ED hy#<"b9t1[@č7h8o~pDDDp4DDD:%'UVVJLDDDI(@/֭[CY/njj‰';`ƌNo ""yDDDD """"uhCH@DDDD<""""R:4ԡyDDDD """"uhzlڴ & & 7nlFFz-i=0Dm={tAcc#n:ǏǾ}4#"F,Y"$&&"11EE_{E獿=irUFd!ev@.Πu$ "HQ!a 1dd!$.#(@ $[QM߻]O^WS=U݇ӧcܹێo[BժSHo;~SNɓ'mEii)2<>x }Q Q GErryJJ ?pcԩ8p!D$7?NFپiii(++U>r V\R,]f]vv6jkka00qD5?HCC""",u:F#A|MOOٳgDZgzU*ޚ5ki&F{@dd$e-grrr,l-[ aaa©ST?DD> &fFd ox"F ֺzDMM bbb7LFAGG0߶6uAAA "_BB233qiCH"J̜9;v)ݡ&"#9vd9~ `ǎx뭷!D$ sFm);fs?"d(..^^Gqq1͛A}C|2FN!ꖺWRr.C!>>(--|U7WGD=`00Xn-oӧO7| w QTը\h4XzjI}QzDD^}dQ=""""R 2TOzDDDD  """"e?h4b޽0`AVڵk-pXfgׯ_Gaa!Vn)QVV#G8ҙȭUmףiii}.\hSv…O}>Owyj;ڵ˫xejj*JKKq \xnMߏXb;wvz˗/Gyy˺t:466g\›:ݵS.EEEaҤIFjj*n; 뮻P[[}ˤ^iUUU{ݺ#F7n1h |.}^WR$$$ !!eeeo,N'Oiۻ hmm 8zϹ897Au%%%0aRxS6xEjLAݻ! ꫯ}شixٳӧOvrn6ٴ4??bԨQ63u+W`ʕ(--ҥKmegg'NT}Xۏ544 ""²^h4:lŋe E!??cy)]<"5^>}P[[ Ag"??gϞEpp__xx8jjj(9uW^E>}d]n#-hD}}m׬Yc[ۙ:@NNoUUͺe˖!,, YYY8u"Hbhhh@QQA`2lh4L&v& rDEE9,'|KM!7VE{{Ǐ[v\:Ao`ժU[a@9mL#q_|d2Y>|{QSS|L> mkkY A$+".A}566Js:9Q7 4;f3/99T={2*ӧ;L6c.x[6NNɓ';Nwh)F-6l؀ŋ vO>q9]:gǨ;ѣ-nj#i>Gg?󍈨wo ,@qq1z=z=1o<ەcʔ)n޸q#̙cy>g{sq:ݵvREFF"==UUUWmx+DDD //dTWWcܸq wߍ-[xlٯswAJnn.:xǣ \)XR+\ˎPM3 ׯhhĺu,sm7eQgeΝ;!CX2ΝwotwIg4QRRcrۗO??~'|HJJ292Wu;Dv)Z%F7W^p=)XR3޷|EFDMx>""YTOz]ڹ}DDD#'@DDDDP=""""R 2a4w^ 0 ڵk-pXf֏ׯ<_ Gqȥmhll9O%%%HKKYR-Fݶ~}ݮ]\S<9111شi Z[[qL8Q Yh4G]]j*I^4i9V\v ۶mJu.DD=XaŊ ?>ߏXb;wvz˗/Gyy˺t:466gu8!""8vxQQQ4i-71bΟ?q!88 |.pWR8cHHH@pp0~aٳGIR$$$ !!eeen|<㈈@LL V\Niw!"rA?CCC ApQlKIIqm!!!hiiq[Yzz:JJJPRR &x,/ 6Hi={1wmi_|6mrY7?oC[[~^***l~|slgu+W`ʕ(--ҥKmegg#Dt;w#~ t0EEEaŖgu[+((@^^-Z|Aa…6JRW^E>}<ƳouS> 5% <}f̘XCBբ23?.\oou<)`0 ::?_x^Ύ΍9555Yrsu3Mرc6s]KMMٳg->}èִi<" AAAqx'O̹O$nu^YY9Kg`ϛ6׽ g϶.g} ڴw…O>9h4G]]j*I^Fhnnŋ:y{{"A;[qqqXb冼X߿suNc(//wY5NF466 Tg:~yEvU[n; 뮻P[[}rRR$$$ !!eeen#ŋ8q"BCCpY IDATw}Su]Fhh(Z[[!=jm))).-$$---n6KOOGII JJJl~?o_~%> 2Œ;~R?w (,,īI]XEEa֍=mgY~WuZ3W\ʕ+QZZKڬFmm- &N>'"q7 ЀzN]TT/^u[+((@^^-Z|m0Xbm=œ?Ag"??gϞEpp'&y[.h3zbٸt駟"..SuZ3999UUU6-[0deeY~6 ֏a&fFdlg2(//GTTCb8qHII_6X?:AUUN<xRמǏW$/)3LyFAGG &&{o={tN3s ,,򷭭f]tt4A@PP<8qa(il0c uƢZZsQ u{ņ lvRڢTǁ7oG,*AлwoOL>׌o:낈n#Mرc6㒓]ΏKMMٳg-6i0+G68SNOvR8G<3fxǎEFFZ:~)]ˎPMs(..^^Gqq1͛rrL2m7nĜ9s,̙{c.J?5_nn.:xǣ \g͛m-,,\+\ˎPM3 ׯhhĺuj]n7eQgeΝ;!CX2ΝwP :pdǏ>~{W^{r ڵkquܸq})u:}#JɢzDDDD =΍GTOzDDDD  """"e?h4b޽0`AVڵk-pXf֏ׯ< Gqś6xӺlNNn*VDݑFA~~>PWWUVI:'M#G׮]öm,m Q 8XX߿suNc(//wY5NF466 ?yEaa>~D\"!! (++tGDDbbbrJtN{ [b Ekk+AѣGm~,%%ϝmf())AII &L౼'R:u*8`*_UTT 55<--MϫY E[[3W\ʕ+QZZKڬFmm- &N>'"q7ЀzN]TT/^lpVaѢEXOyٳ6DJSCF\D}}z{1>W:eܹt~_xU+\ˎPM3 ׯhhĺu,Bqݔ)SlF9w by>d;wRS9}t|7 RRTEѠr_իW;sY.QQQtGD= y#"EH'%{)CH'@DDDDQXX~9Wg~ףiiiN봟m]V[z Iy5NFy=uN39}p\Xtͺl`0`ĉs""7(,^%%%m'Ze:իӧu5JF:<22]f 6mog43999UUU6-[0deeԩS[""0Lhoo#**ʲ91Vv}3lSJ&  h4݃>8mfr@XXo[[ͺh Yyuqb>x`|w1c:w #~rqďHoG*++1sLs_ɝgL\_|mA4SSSqYh4Yɓ'{5OjO>s$ѣ-nj#i>woeǏz(qY^^)SHEFF"==UUU/997npwc˖-&*:?sssq!#>>WJź:]J^vgoSLAYYh4cǎ6'$4/''c[ OFףWv܎:}QzDD^}dQ=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD PJ|A;!"" Fggɓ'cС7DDD=뚸oze`L8hjjBSS***0am}Mwy͸r ֯_0I=^u֡08pmIMMEee%ZZZpy<uFDD`Æ vW18.DDDtP6FZZ 222pdff`0 55 … 9s&BBB۷ߖۇ7|IIIjAzz:g)3|p dff"88Æ Ù3g###6܆e`gqX>k,ݻt:\tIR;~mmm q[Ν;1{leǏǁlq޴×Ql@Wk׮!66ay\\jkk=n+W 햯M&m^wqlذIII._|Y::n/%Džn+tVuXoܸ| ~#<<ܲuyo;~zY7h ݻ---om6<h42֝O͛7mYocMJ % V :ze kiiAhh@vv6***}v˺W"::ګ}!5Džn+;/++ 幫fDFF,[dW_}1c8,m޽q ;w駟j_Hq!""ۊ]u0ƍpUoFF Mg|el^l/,,͛ѧOt:<(,,ƩS0b"11/͛m=tz)qj۷/^uY :.\i,;vM']>RbǾކe`L<hnnFss3*++1i$2͛7m۷/vލ&a֭gƙ3gֆ~K,q+裏b׮]EGGfÇ hnnƗ_~p_BWyH!wۗs'& 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDj7~aʕ+hoo˗{n7OGƎ;pU1c*m*6 ][JN8}4N⡇®]ҦHNNF`` Gb֬YvDDD*A)gB߾}_pm/_^z,HJJVELL ӱo>F R:EEExgϚ5 {<ߺu+dp%9ݸq^>W^qX>vX?~|ΝǏǁ|r,xG}v`^~el۶iR-u?;[ֆ9Q7 R:׮]Cll8Z_t wqۺBCCrJTUUdxRxaaaq˗ѯ_?2ᨫHYqDrIII.;R-u?;[~qlذIII>ߏDDD]Hutt@:,@GGy{{;ֵqF|'pK=y^).'=n޼㖔J֭kG.˵{Aa޽hii~m۶'F~%""RrįL]]zͲmܸ$OJ<)yK]z~=f8qdmD#~R, ;;ؾ}_5DJܰ,|[nx+fDFF,[dߏsΡO>N[ %vv܉~o+..'Nt͛P^r;SgWkv""nAtƍp5hFF ƌc)g}զ^GPPRRRl,,,͛ѧOt:<(,,999oO":: ēԎСCqL6ͲƎkӱ̱8~8Oձz?FVVt:t:PSScvK+Ux!|8q?R#UVV^<裨Ē%KTDDD> /^D~j7[y&zz͛?a~9sHSMj G;@]]G ʦ---8<}Y 6ڵk{'NFqZF7|4czueF455/СC='MMMhjjBEE&LЩ6IկWBIIͲg/Ԅzlڴ s?/^D[[Μ9ٳg{ݾ8:Í70(((@@@"""i&Fcڵk~DDD2?HEErrr<>|8 233aÆ̙3x-eGFFMgr׮]9sz~iu:+r:~;vYлwo=jjj0`⤥`0 ##Gff& RSSnv]rqqqQSScy~=Ʀ999رc8Rc͛D"99Os=' EJJ  |I444CCC x}W_}1c8,s7oC^zs{&^z%Z}N/{Mj tFc=Q7 pQL8Ꮜ/ IDAT Qp1CŅ 0m4cǎՁh48}4^qp"l:u #F@`` xbl޼?FVVt:t:PSSJڏ_o[_՛`c!eNs!//=-Ϛ5KURctco>ddd 66Z}Q]]{c=Q7 <lقϣ (++s:R5|p/mI'r֬Ye {gƙ3gf ~7oԄbbkɨDss3QYYI&I֛x={X~NmϞ=No=l0۷MMMv6nN')X3wFmm-q%ڵ ɒaǏ 1NDDD*S=?"""R TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TO?""""2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CHG^"|O;HԝcOqHצ8s>aAMp~Ŀ˦  R~?6np|W& Ԫpz=5JއuWzCeǯk?%`+E\~\ǺpvyH$tff+C/ g%қu}trן0#ӫ<_|= rMpG8ڈ\O]G] P׶M|3\VHo-oy_?1z{@ \{WMU `WM[&ɑ/fptXu]rb?e&;,?9b%gį۶ A_ /ɶܹ?>p׭e˿9.xis뼮'GǠixL>_ %>^MaO+B[I9qHy I=7bWSMF#ٯs{|@;L|dXfb9}oe'Oޱ.)e}`wmwܛ牧אUU,gxzǘÍTO"$PY-#quwo7{:GUzr,۱u&+#5ҫM\ޱև<#s]_gt)e}zJ}۫{j8ڇl޻כ?O!9q@1mm^?9 9ug{+~b"p ._=Ck/ʝSy~c~ouc>}Y_3_zfx;@lNRw'uiU!zav +'_יt>|֋/R/ˍNd8O"ӇmLܵ7.Sl]uTVq+ʸHq֍)Wo}}iq{e;s%Beu{F1s+˷ʽ6Eqt.9 P*~szI1}(8J`_99{ԅeM$# 8qy8՛ˍ a|$2BJ{c"߾%}q,Wv.co˝ğzqdfY/`9s]ScмUpya{Nݒ 2TOzDDDD  """"e)CH'@DDDDP=""""R 2TOzDDDD  """"e)CH'@DDDDP=""""R|s,AIENDB`status_standard.f8c29f3c4f6aaa50e10f9f2bb19d3a2c.png000077700000000000000000000000001325274564300541232status_standard.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationstatus_standard.png000077700000000000000000000000001325274564300472552status_standard.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentationstatus_standard.png_documentation_1.9_status.html000066400000000000000000000117001325274564300447530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/documentation documentation:status_standard.png [LemonLDAP::NG] />

documentation:status_standard.png

status_standard.png

status_standard.png

Date:
2016/07/19 12:15
Filename:
status_standard.png
Format:
PNG
Size:
38KB
Width:
638
Height:
581


Back to documentation:1.9:status

error.html000066400000000000000000000165621325274564300323350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:error

Table of Contents

  • Lemonldap::NG::Common
  • Lemonldap::NG::Handler
  • Lemonldap::NG::Manager
  • Lemonldap::NG::Portal

Error messages

This page do not reference all error messages, but only the frequentest

Lemonldap::NG::Common

Warning: key is not defined, set it in the manager !

→ LemonLDAP::NG uses a key to crypt/decrypt some datas. You have to set its value in Manager. This message is displayed only when you upgrade from a version older than 1.0

Can't locate /usr/share/lemonldap-ng/configStorage.pl

→ When you upgrade from Debian Lenny with customized index.pl files, you must upgrade them. See Debian Lenny upgrade.

Lemonldap::NG::Handler

Unable to clear local cache

→ Local cache cannot be cleard, check the localStorage and localStorageOptions or file permissions

Status module can not be loaded without localStorage parameter

→ You tried to activate Status module without localStorage. Configure local cache first.

No configuration found

→ The configuration cannot be loaded. Check configStorage and configStorageOptionsor file permissions.

User rejected because VirtualHost XXXX has no configuration

→ The specified virtual host was not configured in Manager.

mkdir /tmp/MyNamespace/2: Permission denied ...

→ The cache has been created by another user than Apache's user. Restart Apache to purge it.

This can happen when you use lmConfigEditor or launch cron files with a different user than Apache process. That is why it is important to set APACHEUSER variable when you launch “make install”
Lemonldap::NG::Handler::SharedConf: No cookie found

→ User does not have Lemonldap::NG cookie, handler redirect it to the portal

The cookie $id isn't yet available: Object does not exist in the data store

→ User session has expired or handler does not have access to the same Apache::Session database than the portal

Firefox has detected that the server is redirecting the request for this address in a way that will never complete

→ Your browser loops between portal and handler, it is probably a cookie problem. Verify that:

  • the portal is in the declared domain
  • CDA is set if the handler is not in the same domain
  • portal is in a https virtualhost if securedCookie is set
  • you've restart all Apache server after having change cookie name or domain

Lemonldap::NG::Manager

XXXX was not found in tree

→ The specified node is not the uploaded tree.

Lemonldap::NG::Portal

User XXXX was not granted to open session

→ Check grantSessionRule parameter.

XML menu configuration is deprecated. Please use lmMigrateConfFiles2ini to migrate your menu configuration

→ You do not use the new configuration syntax for application list. XML file is no more accepted.

Apache is not configured to authenticate users !

→ You use the Apache authentication backend, but Apache is not or bad configured (no REMOTE_USER send to LemonLDAP::NG).

URL contains a non protected host

→ The host is not known by LemonLDAP::NG. Add it to trustedDomains (or set * in trustedDomains to accept all).

XSS attack detected

→ Some URL parameters contain forbidden characters.

exportedvars.html000066400000000000000000000231321325274564300337210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:exportedvars

Exported variables

Presentation

Exported variables are the variables available to write rules and headers. They are extracted from the users database by the users module.

To create a variable, you've just to map a user attributes in LL::NG using Variables » Exported variables. For each variable, The first field is the name which will be used in rules, macros or headers and the second field is the name of the user database field.

Examples for LDAP:

Variable name LDAP attribute
uid uid
number employeeNumber
name sn

You can define exported variables for each module in the module configuration itself. Variables defined in the main Exported variables will be used for each backend. Variables defined in the exported variables node of the module will be used only for that module.

Exported variables in the Manager

You can define environment variables in Exported variables, this allows one to populate user session with some environment values. Environment variables will not be queried in users database.

Extend variables using macros and groups

Macros and groups are calculated during authentication process by the portal:

  • macros are used to extend (or rewrite) exported variables. A macro is stored as attributes: it can contain boolean results or any string
  • groups are stored as space-separated strings in the special attribute “groups”: it contains the names of groups whose rules were returned true for the current user
  • You can also get groups in $hGroups which is a Hash Reference of this form:
$hGroups = {
          'group3' => {
                        'description' => [
                                           'Service 3',
                                           'Service 3 TEST'
                                         ],
                        'cn' => [
                                  'group3'
                                ],
                        'name' => 'group3'
                      },
          'admin' => {
                       'name' => 'admin'
                     }
        }

Example for macros:

# boolean macro
isAdmin -> $uid eq 'foo' or $uid eq 'bar'
# other macro 
displayName -> $givenName." ".$surName
 
# Use a boolean macro in a rule
^/admin -> $isAdmin
# Use a string macro in a HTTP header
Display-Name -> $displayName

Example for groups:

# group
admin -> $uid eq 'foo' or $uid eq 'bar'
 
# Use a group in a rule
^/admin -> $groups =~ /\badmin\b/
 
# Or with hGroups
^/admin -> defined $hGroups->{'admin'}
Groups are computed after macros, so a group rule may involve a macro value.
Macros and groups are computed in alphanumeric order, that is, in the order they are displayed in the manager. For example, macro “macro1” will be computed before macro “macro2”: so, expression of macro2 may involve value of macro1. As same for groups: a group rule may involve another, previously computed group.
extendedfunctions.html000066400000000000000000000350461325274564300347330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:extendedfunctions

Table of Contents

  • Presentation
  • Request information
  • Extended Functions List
    • date
    • checkLogonHours
    • checkDate
    • basic
    • unicode2iso
    • iso2unicode
    • groupMatch

Extended functions

Presentation

When writing rules and headers, you can use Perl expressions that will be evaluated in a jail, to prevent bad code execution.

This is also true for:

  • Menu modules activation rules
  • Form replay data
  • Macros
  • Issuer databases use rules
  • etc.

Inside this jail, you can access to:

  • Core Perl subroutines (split, pop, map, etc.)
  • Custom functions
  • The encode_base64 subroutine (be careful with it: you must add an empty string as second argument to avoid inserting “newline” codes)
  • Environment variables, in some cases (through %ENV)
  • Information about current request
  • Extended functions:
    • date
    • checkLogonHours
    • checkDate
    • basic
    • unicode2iso
    • iso2unicode
    • groupMatch
To know more about the jail, check Safe module documentation.

Request information

The following data about the current request are available through functions :

  • hostname
  • remote_ip: the client IP address
  • uri: URL path
  • uri_with_args: URL path with query string
  • unparsed_uri: URL path, before URL decoding
  • args: the query string
  • method: the request method (GET, POST etc.)
  • header_in(“Your-Request-Header”): any request header

Extended Functions List

date

Returns the date, in format YYYYMMDDHHMMSS, local time by default, GMT by calling

date(1)

checkLogonHours

This function will check the day and the hour of current request, and compare it to allowed days and hours. It returns 1 if this match, 0 else. All e By default, the allowed days and hours is an hexadecimal value, representing each hour of the week. A day has 24 hours, and a week 7 days, so the value contains 168 bits, converted into 42 hexadecimal characters. Sunday is the first day.

For example, for a full access, excepted week-end:

000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000
The LDAP schema extension can be used to store this value. You can also use the binary value from the logonHours attribute of Active Directory

Functions parameters:

  • logon_hours: string representing allowed logon hours (GMT)
  • syntax (optional): hexadecimal (default) or octetstring
  • time_correction (optional): hours to add or to subtract
  • default_access (optional): what result to return if logon_hours is empty

Simple usage example:

checkLogonHours($ssoLogonHours)

If you use the binary value (Active Directory), use this:

All e
checkLogonHours($ssoLogonHours, 'octetstring')

You can also configure jetlag (if all of your users use the same timezone):

checkLogonHours($ssoLogonHours, '', '+2')

If you manage different timezones, you have to take the jetlag into account in ssoLogonHours values, or use the $_timezone parameter. This parameter is set by the portal and use javascript to get the connected user timezone. It should works on every browser:

checkLogonHours($ssoLogonHours, '', $_timezone)

You can modify the default behavior for people without value in ssoLogonHours. Indeed, by default, users without logon hours values are rejected. You can allow these users instead of reject them:

checkLogonHours($ssoLogonHours, '', '', '1')

checkDate

This function will check the date of current request, and compare it to a start date and an end date. It returns 1 if this match, 0 else.

The LDAP schema extension can be used to store these values

The date format is the LDAP date syntax, for example for the 1st March 2009:

20090301000000Z

Functions parameters:

  • start: Start date (GMT)
  • end: End date (GMT)
  • default_access (optional): what result to return if start and end are empty

Simple usage example:

checkDate($ssoStartDate, $ssoEndDate)

basic

This function is not compliant with Safe jail, you will need to disable the jail to use it.

This function builds the Authorization HTTP header used in HTTP Basic authentication scheme. It will force conversion from UTF-8 to ISO-8859-1 of user and password data.

Functions parameters:

  • user
  • password

Simple usage example:

basic($uid,$_password)

unicode2iso

This function is not compliant with Safe jail, you will need to disable the jail to use it.

This function convert a string from UTF-8 to ISO-8859-1.

Functions parameters:

  • string

Simple usage example:

unicode2iso($name)

iso2unicode

This function is not compliant with Safe jail, you will need to disable the jail to use it.

This function convert a string from ISO-8859-1 to UTF-8.

Functions parameters:

  • string

Simple usage example:

iso2unicode($name)

groupMatch

this function allows one to parse the $hGroups variable to check if a value is present inside a group attribute.

Function parameter:

  • groups: $hGroups variable
  • attribute: Name of group attribute
  • value: Value to check

Simple usage example:

groupMatch($hGroups, 'description', 'Service 1')
fastcgi.html000066400000000000000000000074071325274564300326220ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:fastcgi

FastCGI support

The manager is now natively written for FastCGI. Portal will follow in 2.0

LL::NG Portal can be used under a FastCGI system very easily. You just have to load LL::NG FastCGI support and write a loop in the CGI. Example with the portal:

#!/usr/bin/perl
use Lemonldap::NG::Common::CGI qw(fastcgi);
use Lemonldap::NG::Portal::SharedConf;
# ...

LMAUTH: while ( my $portal = Lemonldap::NG::Portal::SharedConf->new({}) ) {
    # ...
}

Key steps :

  • Load “Lemonldap::NG::Common::CGI qw(fastcgi)“ before any other LL::NG library
  • insert a loop around the HTML printing, starting with the object creation (→new)
  • insert a label “LMAUTH” ahead of the loop

An example is given under the source tree : lemonldap-ng-portal/example/index.fcgi

fastcgiserver.html000066400000000000000000000122221325274564300340400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:fastcgiserver

Table of Contents

  • Start
    • Using packages
    • Using "make install"
  • Configuration

LemonLDAP::NG FastCGI server

Since 1.9, Lemonldap::NG provides a FastCGI server usable to protect applications with Nginx (See Manage virtual hosts page to configure virtual hosts).

This FastCGI server can be used for all LLNG components. It compiles enabled components on-the-fly.

Start

Using packages

You just have to install lemonldap-ng-fastcgi-server package, it will be started automatically.

Using "make install"

To enable the FastCGI server at startup, copy the script llng-fastcgi-server installed in INITDIR (default /usr/local/lemonldap-ng/etc/init.d/) in /etc/init.d and enable it (links to /etc/rcx.d).

Configuration

FastCGI server has few parameters. They can be set by environment variables (read by startup script) or by command line options. A default configuration file can be found in /usr/local/lemonlda-ng/etc/default/llng-fastcgi-server (or /etc/default/lemonldap-ng-fastcgi-server in Debian package).

The FastCGI server reads also LLTYPE parameter in FastCGI requests (see portal-nginx.conf or manager-nginx.conf) to choose which module is called:

  • cgi for the portal (or any CGI: it works like PHP-FPM for Perl !)
  • manager for the manager
  • status to see statistics (if enabled)

if LLTYPE is set to another value or not set, FastCGI server works as handler.

federationproxy.html000066400000000000000000000103301325274564300344110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:federationproxy

LL::NG as federation protocol proxy

LL::NG can use federation protocols (SAML, CAS, OpenID) independently to:

  • authenticate users
  • provide identities to other systems

So you can configure it to authenticate users using a federation protocol and simultaneously to provide identities using other(s) federation protocols.

For example, a LL::NG server can be:

  • A CAS server with SAML authentication
  • An OpenID server with CAS authentication
  • An SAML server with OpenID authentication
  • …

See the following chapters:

  • Authentication protocols
  • Identity provider
fileconfbackend.html000066400000000000000000000070031325274564300342670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:fileconfbackend

File configuration backend

This is the default configuration backend. Configuration is stored as JSON.

This configuration storage can be shared between different hosts using:
  • SOAP configuration backend proxy
  • any files sharing system (NFS, NAS, SAN,…)

Configuration

You just have to configure a directory writable by Apache user and set it in [configuration] section in your lemonldap-ng.ini file:

[configuration]
type  = File
dirName = /var/lib/lemonldap-ng/conf
filesessionbackend.html000066400000000000000000000110351325274564300350250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:filesessionbackend

File session backend

File session backend is the more simple session database. Sessions are stored as files in a single directory. Lock files are stored in another directory. It can not be used to share sessions between different servers except if you share directories (with NFS,…) or if you use SOAP proxy.

Setup

In the manager: set “Apache::Session::File” in “General parameters » Sessions » Session storage » Apache::Session module” and add the following parameters (case sensitive):

Required parameters
Name Comment Example
Directory The path to the main directory /var/lib/lemonldap-ng/sessions
LockDirectory The path to the lock directory /var/lib/lemonldap-ng/sessions/lock

Security

Restrict access to the directories only to the Apache server. Example:

chmod 750 /var/lib/lemonldap-ng/sessions /var/lib/lemonldap-ng/sessions/lock
chown www-data:www-data /var/lib/lemonldap-ng/sessions /var/lib/lemonldap-ng/sessions/lock
formreplay.html000066400000000000000000000200111325274564300333440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:formreplay

Form replay

Presentation

Form replay allows you to open a session on a protected application by filling a HTML POST login form and autosubmitting it, without asking anything to the user.

This kind of SSO mechanism is not clean, and can lead to problems, like local password blocking, local session not well closed, etc.

Please always try to find another solution to protect your application with LL::NG. At least, check if it is not a known application, or try to adapt its source code.

If you configure form replay with LL::NG, the Handler will detect forms to fill, add a javascript in the html page to fill form fields with dummy datas and submit it, then intercept the POST request and add POST data in the request body.

POST data can be static values or computed from user's session.

To post user's password, you must enable password storing. In this case you will be able to use $_password to fill any password POST field.

Configuration

You should grab information:

  • URI of the html page which contains the form
  • URI the html form is sent to
  • Does the html page load jQuery ? If not, grab a jQuery URL reachable by user (any version over jQuery 1.0 is suitable)
  • are there several html forms in the page ? If so, get a jQuery selector for the form you want to post
  • is user required to click on a button, for example in order to perform some script ? If so, get a jQuery selector for that button
  • names and values of the fields you want to control

If you don't know jQuery selector, just be aware that they are similar to css selectors: for example, button#foo points to the html button whose id is “foo”, and .bar points to all html elements of css class “bar”.

For example:

  • Form page URI: /login.php
  • Target URI: /process.php (if you let this parameter empty, target URI is supposed to be the same as form page URI)
  • jQuery URL: http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js (if you let this parameter empty, jQuery is supposed to be already loaded; you can also set default to point to jQuery URL of LL::NG portal)
  • jQuery form selector: #loginForm (if you let this parameter empty, browser will fill and submit any html form)
  • jQuery button selector: button.validate (if you let this parameter empty, the form will be submitted but no button will be clicked; if you set it to “none”, no button will be clicked and the form will be filled but not submitted)
  • Fields:
    • postuid: $uid
    • postmail: $mail
    • poststatic: 'static'

Go in Manager, “Virtual Hosts” » virtualhost » “Form replay” and click on “New form replay”.

Fill values here:

  • Form URL: /login.php
  • Target URL: /process.php
  • jQuery URL: http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
  • jQuery form selector: #loginForm
  • jQuery button selector: button.validate

Then click on New variable and add all data with their values, for example:

You can define more than one form replay URL per virtual host.
handlerauthbasic.html000066400000000000000000000147301325274564300345000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:handlerauthbasic

Table of Contents

  • Presentation
  • Configuration
    • Virtual host
      • Apache
      • Nginx
    • Handler parameters

AuthBasic Handler

Presentation

The AuthBasic Handler is a special Handler that will us AuthBasic to authenticate to a virtual host, and then play authorizations rules to allow access to the virtual host.

The Handler will send a WWW-Authenticate header to the client, to request user and password, and then check the credentials using SOAP getCookies web service. When session is granted, the Handler will then check the authorizations like the standard Handler.

This can be useful to allow an third party application to access a virtual host with users credentials by sending a Basic challenge to it.

Configuration

Virtual host

Apache

Configure the virtual host like other protected virtual host but use AuthBasic Handler instead of default Handler.

PerlModule Lemonldap::NG::Handler::Specific::AuthBasic
<VirtualHost *:80>
       ServerName basic.example.com
 
       # Load AuthBasic Handler
       PerlHeaderParserHandler Lemonldap::NG::Handler::Specific::AuthBasic
 
       ...
 
</VirtualHost>
If LemonLDAP::NG portal is protected by SSL with a self-signed certificate, you can add this line to accept it:
PerlSetEnv PERL_LWP_SSL_VERIFY_HOSTNAME 0

Nginx

Since 1.9.6, LLNG FastCGI server can handle AuthBasic handler. To call it, you just have to add fastcgi_param LLTYPE authbasic; in the FastCGI server call and remove error_page 401 directive:

location = /lmauth {
  internal;
  include /etc/nginx/fastcgi_params;
  fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;
  fastcgi_param LLTYPE authbasic;

  # Drop post datas
  fastcgi_pass_request_body  off;
  fastcgi_param CONTENT_LENGTH "";

  # Keep original hostname
  fastcgi_param HOST $http_host;

  # Keep original request (LLNG server will received /llauth)
  fastcgi_param X_ORIGINAL_URI  $request_uri;
}
location / {
  ...
  ##################################
  # CALLING AUTHENTICATION         #
  ##################################
  auth_request /lmauth;
  auth_request_set $lmremote_user $upstream_http_lm_remote_user;
  auth_request_set $lmlocation $upstream_http_location;
  # Remove this for AuthBasic handler
  #error_page 401 $lmlocation;
  ...
}

Handler parameters

No parameters needed. But you have to allow sessions web services, see SOAP sessions backend.

header_remote_user_conversion.html000066400000000000000000000125101325274564300372770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:header_remote_user_conversion

Convert HTTP header into environment variable

Using LL::NG in reverse proxy mode, you will not have the REMOTE_USER environment variable set. Indeed, this variable is set by the Handler on the physical server hosting the Handler, and not on other servers where the Handler is not installed.

Apache SetEnvIf module will let you transform the Auth-User HTTP header in REMOTE_USER environment variable:

SetEnvIfNoCase Auth-User "(.*)" REMOTE_USER=$1

This can be used to protect applications relying on REMOTE_USER environment variable in reverse proxy mode. In this case you will have two Apache configuration files:

  • Apache configuration file on LL::NG reverse proxy (hosting LL::NG Handler):
<VirtualHost *:80>
        ServerName application.example.com
 
        PerlHeaderParserHandler Lemonldap::NG::Handler
 
        ProxyPreserveHost on
        ProxyPass / http://APPLICATION_IP/
        ProxyPassReverse / http://APPLICATION_IP/
 
</VirtualHost>
  • Apache configuration file on application server (hosting the application):
<VirtualHost *:80>
        ServerName application.example.com
 
        SetEnvIfNoCase Auth-User "(.*)" REMOTE_USER=$1
 
        DocumentRoot /var/www/application
 
</VirtualHost>
Sometimes, PHP applications also check the PHP_AUTH_USER and PHP_AUHT_PW environment variables. You can set them the same way:
SetEnvIfNoCase Auth-User "(.*)" PHP_AUTH_USER=$1
SetEnvIfNoCase Auth-Password "(.*)" PHP_AUTH_PW=$1

Of course, you need to store password in session to fill PHP_AUTH_PW.

highavailability.html000066400000000000000000000072451325274564300345140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:highavailability

High availability

LemonLDAP::NG is highly scalable, so easy to insert behind a load-balancer:

  • Portal does not store any data outside the session database, so you can have many portal servers using the same HTTP host name
  • All handlers download the whole configuration, so many servers can serve the same virtual hosts

You can for example set up a fail-over cluster with Heartbeat and HAproxy, like this:

You just have to share configuration and sessions databases between those servers:

lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons/000077500000000000000000000000001325274564300314765ustar00rootroot00000000000000access.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000163731325274564300376630ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqIDATxy\Untu-t I[XVQAQgDdpygDQܑU [ ]Uun:!a={sY99%R\y.P`SB(DBY.@*RH1(ژ@I޴o G%dRL4yRsB3XSGɨtt%T {l:?t{z騗&Mh([ַӥύ4/8n~GKF[iPmP3uRyOH֫ss]Vq}p&$;Ҭړᕕ{8OC55?@m|{ϸ]r8#ά) @`hЎ@C6jC2^1 hX# LqtޛbabUOz[_nvVfo VqOgfyGdu|ZkaZda( i;TYӕZ/IPaOS-'˜Q,a$z`gv~oN++..f__i}.:kٲiܑ@(M |ޑЇ8*^0&UI" 0*>0M84bŎqv=֛5iǮ[&˸}Yk޻w޲,iKu,:{`v+v/0JTǠ=$X9X|`l<͊]l-_T'{׎k %`~[ޫ6m\*X]<ִWoOuET/ q ̄>9f'>f3!XV}#+!  8{Il?cphLnxj DcIN <6?Ff~`1|5 nRyzʼnU "$-KQ+|og<9Ԋ~֏J]QqQQ y'´Y:)`7bۿQ?|vNLyILxm= =VGbkI*>V5(Ul^Ir"*lG+ssnv%^/H $p|D6Y. _7U|5G@vS Ԭ+9>I$*BQ+&OD:bH8s]lG"Gd "xvӧh(W A}[_0qG7B$^guv14eqWdUc`uۅZ0\9NϮ20j3<~iH*㸅RE k.>wmuy S/`ݦ1>נ~ɂ6j/b$)3;yNXO#|^ MhJ7HMa>qI1 &2yجPs hJKϚyrK5IVM8 A<1owr|s9ea>0g{ٺkϗ"$7|ySȢ5H Fs(BP0q= 豘&b!(G#BC&LLd]$TdaS$AN"EVXD,>{b'?3r/=y,>/ށ 15@)+%4 *|$Ac;.AQC+ B] PH BG; \2=OƐ5t3t4BRe" )ji(x}\0ujh" *㈱$(P mǵ Lx*i(]W!~4- I'VݪAևdX 3fhQpx*I1~Ő("6yS4} ڦKɦ)Nw5zLdP.hfGXzT?(ohhE3hf݈Qt#[@t}Oe7++"H%QG>w <ҳ^@zTj%BCGpB А^MIÈ1)4M/mgR"JGy#p& Bf'Dq,+2:JCIU@MWGy:?°h8PVh!tsrU՘(B͝\: agBHU(PH"@y29Q9D:A~Ule)slzX$f2|p% y3Og; 캭=ͩf@'ʾ4,SM݀s\Jmm]v'HkhMJh1 &bzaoɜ.; GkBJ"0Gq7pwsCcW,H+0 U] 0L.d6+_OUDKރL~x>j;Pld˞/m=/9d8BJkW1gA'I6үرohg m]-o l}2)h\S3񙿨w g_޾-X5 yY3mžJ7ak6/ٞ$h!ί@sc  %+Ec2A\c${l 5 d"#4WqNL0C&e4C2nA _,J#ڐWRj{4esuB:sm(gnЁ (epkl{6N&{_ǎIYk_Aoܻ B}sϝ⬯ w{ fs㼲0Z-y(ՏDbݖ3Ͻ!:j\b ' ;CU  ™t(/V^1{>2낡OJ~Lhh7~N +{̘E⾗r#qݱu]󩫯z6dh: ~s4ׇYU/=Èdt+B躎i" %PLnAyWS_2 ya`JT-ǎ=ymw> GXmug0߱>tY1!$P=L=RB3 ˲0M@$+AxZ.(uW_d]Ǹ++0iӷӫn ?y <(i.Y sG3~f5k,YWzG򵻟aD"^'`Yeaa .P$y!΢l'qǗsg\9:M-SzJ7c}[y*tκoLYz|]g=SE\ήY>~ {z2E:N<9 *]HGvHgrHc^W?oHP˽m?ފԫhhnb39i_u^{CD.?g\s<{-N򳛹 io*Oy^_uMR%JX CG/.A| icons:access.png [LemonLDAP::NG] />

icons:access.png

access.png

access.png

Date:
2016/07/19 12:15
Filename:
access.png
Format:
PNG
Size:
7KB
Width:
64
Height:
64


Back to documentation:1.9:start

clean.png000066400000000000000000000112071325274564300332100ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqNIDATxy]U?so~oC:a]YUraq Tį $ :D$nQD [HHm:IwBs;,&H:^:o9ks?gSGG"p;p/iGGڼF)yh{Nn8Nh|u0{ Ɇo+Թg>"/o$:SyΏ226BJ1@G>-vW9KőF@|Fgnf:FF(Ō? hhME{3NS Gjʩ,|Ж0bĮX4ʏc Wv}k\{E.|wf yJJbD {ذ(s:fSv}KK&,xKZPLOwf3_XO{Q-f uNr P+\++,}/-*qhb@R2 ح^#.`Ae's֠3V:j~aNfzڇ(󄶄7^bo TXL)hH7쁥Jd{jpڒq "m"Z(L gN,}ȗr%zE?ܻ~b({rYU8MTJҵkX,EK;(xsةU5%^Ų陙>yB]q7VػQ]^|~ãw83vU8h+O{iq٩:Y#T|mn/aAlzU`*j!:Srw/c*?yAD@yE&ģ._w)ѐE奵=W?ҞN ҡh%ܘi?%ŬjEy^ Rlzw̻ JUIe~GȫAڧ+}oIN/Vµո2䜍^/oH NE[iN7Id"jΝ?"ՀE{HyWc`klY嬛_z뫎 e[&4%(qGGVs:@s^DZhgOv}Y mڵPx{'uiOyI/y}>4Dwr[k |th!^^&q3nrmD(-XpBܣNf#H 4=;YKzE"~gdg[}~[]D'~ٌG1Z%XA{bB]}ϿAZyWykmY[Pz+f]MF6#(9{+_ѴvmAI-#8(w9G|Ogus?g 3ܮ YrQ3 %g{wd` Pt` )-lM+g^K)IqRLc` o@Mlm w9n]aa<Ijtۜ~-lL6d=b3jp̿-ཱི5  ۗ"HZt㰧#ե7J"[FవNGy*RT{(q#,dW+wr`0}Ek`ضe،5C=/__6,u Y;})g<OGkX>1X:A*NjÓq{Ex XXߤ>+Pɑ|ߒho: N9ҚEօOQ@k=ټG4caߪt&k2LCGKxʔ#'wNj m<12SiM[b8@مt7#664,+;Y{΃Q;B,q ^(PZRb} e)x/oT}Gb+3N;*ǔUމDZ83{6i&r$T\VDMm~ce=eJ/8Cxld96oQPZJE;T^ڪvV[ಾ0N|Axg[O{4hTث R?قD@̪gFhz_Ӵ6kaCI¿;c\T)nWMV0⡇.tKG˿jOI15*{W!''=#H4yMZmY<~jj32{![*qeSل6V/+)mЙDd$'bxyk**b ĺJrGGio <|߯} UOTtV{b/}H$2+W})+:[Zo5< aC ՚ιUf2xm~V_ &^P;R0!'yMOR'Y> ~]燨mfpGc/M9vqoü9c`--xNI; {>UG5r_ YS-M! 3U9ܦŮ:ҟ{VNh0M=Ph& pI<3 l]wZGעBVD^7xx%>nMιOn||ũG$BIr%<ח蜺hOgD 8_^몶kI:gl26ȧ~qs٧Q$5)|N/wn1 +KfZk79vySP>o;\JO;}|Γm3[׈Ȑ 'O}cYkΟsNxv:H,udt:ᒅ}v}qU2|$ H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_FFIDATxy]gyrιKw[Iݲ%YՖȲ1!0g C2L&LH؆%3Ed2@SIHy 1oȋd%Knnse8Wn[6bX<NW{ϻ|G~ GG '>{i"F`wċ@Bo*E_=?vz2j{_k_&k7bPMp4|ke?T3Xc/C_o_}mvJ2G/><@e~Cܲ| Ciuخϭ||3j#D^ǻ|-h>ۻkewRe=2@gm")?5oY W0m&VN?E_{WlXַo;~x!*c{-#um_М̨`WKWz7߰4gwd)g׺m-xtwejfkoi+GBD(p7v˃WP9-??m0".у]BNWBHH 8wn?g |A!e)#LV߸/ox3.?5?77 /Jm*@CBG! @x#DEoR|@2{;G{i> @BY}wk;ɘ>p3mֿs_w ԅH}k.2\JlZ' <&hD AQtoȨwٸ{J ջnӫIğ$vwKS \2!Z\u;v jMb2hS$duRXWޝI|ba\C||#ݹmoB"ʿ#\vY7qDKHPanE&&9t?rh`.i 6LИ4quSvmye1اl1o'VmK%7_q~v_s9QQ)J]fxk!7޲#Z %1 G{:L}tv6Gڬ$s {ROAioD xkkSMg&5A bI!RLoAZf$ !d*@*7lMRU*$!N8ӂĢV1Fi|Z+T g*xޣi<𣃟韾?R |[M7w}!Ybz"wR00?19&Q;LY&Wlf;_i*I c \*Fq6h BHP '8O{|p_}[Eо9rHBza Dx ړMN?5J6qu&<΃Bf!+ҹv 'py }v7%E)J&x0xP16>C[=}PN/ӯpշ_:M2L-I; &Jr,3 ^ ` Wc=0džA8T!onX, fc?k׎\Fs|g_bWs#DVDSl,cUi/ QC&}}{FP/^sny_O~d Zp:C<=8|XP0͹Q+ڸOfTks5.p.!E %.?ZB9Z8n.Xqh-h8CnVGyKcs+Trخ?zύӋ‹غ˟ifRTdzƋ8n*5C[Q @H?; ߯ {=8Y%Z~m ^VH9ReYn9 L+޵♐Z\]gk54w970;]<FzxbSs~`PX@C!VJ8-P|c9+8"ꍔw][״߹`%ͫJqEj8Z+oEhPZH2HL~ϲ^"j+4mJXcNǹ%TT!xp.,L};1>bמ.{d|ڼ%K a 6MK03Zgq:Oyܰc/|?W HT J<89<3)(G9BM)ɐpM\\DI{t=Q7n~YfNU;K;ϢaucP2Z7Dڄ$&[R B!M3*]7P*(v=Э[x<rt&70]]>o|Xc ~?ض+im  4(u[yͤƠBIcic _J]2f׳]^;cw^D8/7-i#ETZ"" $H@H簩Z@ B !:.Fj.Ԋޮ̸řlqR:֔{˒Vvʋ"'RDg 99 f&PNᅦtM_$IjI )R2 JHZMZFϕ@#TֽVʁ(9Iq&[buU:p\x"qlX, 䛗W3KJH<-6(gskk0=11]8ۻnX\kX qnuT=i2AfkD$Ml.: |Ξ :H2 -Ri-a[a&Y <ػӌS:Ek"'BV=Zii!=zFryQYeV/7"Mպөq^r9w!{MTEnI- G{?:簮B Ӭ5- T U&R|O>WG#8 B(R|Gz[3,E^C+ʞvD#6W1dIƈŦ>3_OV/3lRGwC38ԎsUCb@ ä3fJ8Sӌ1&RP#DI`,PANKVi:4fb_b(Ɍ{YđSsREМVy{$ؑ n B ,O2'!+̑Xp% ң4LCYLGJT_Z)ITޮ咧ER@E1ʗxna(?bwڞ:: =ՇAoZ8BdO?xv+Z&vs$a8p+^+AI> 4cAYC3(t dNsw417ӠjJ:jM 2R"E!Th 8ϊ?7{q?2aoZVVr>ߴq-QZNw~&>=aݺ:|"dL%CT$SbDl2oso%bY \/@G u}_0 )SS%]XE\{XLy7`35BITrYfr=P,P(pBq$>lۏ~v.$,?/uƩÊ%1N!1 ‚p0 Y!z6A(x)N=c=6qBVV^Fk\, A Ȍ%ifXR"$"Boq/ L8Dڈ9z|gv~`aUw׋JbmT_HٜlFL":sBi\I:GY.0I.u #yŭoC1:1ȱIF|-KO$a 0PaB1a@\`Fڜ%3yqW=?v%]E7 7٨RH wil ghbS e+twZH#wJIf)=y:ߗ,OLjR{|?md; AA!:O | Y_2lw(/_fɩu:LӂEkGj.( f"i_讙 я?3 #R}㯽ï߾)XE4HӴ5!Z"Ǒ6 xŖ&܍#U(х- o=YHQ*jW_~3x/xhKMʪEIgQ%aQ/,q֓y3Ey8ؿL uM{{Hk}K§uq3ebvόpó7]_P`y).9%@MňsK[Jm+1QJT*Q*(9O$iF-NJ2{|Mz,o+O'ա!_.%˥%%!ϋJrX1a8"s_q3!;xZCIENDB`colors.0fea6a13c52b4d4725368f24b045ca84.png000077700000000000000000000000001325274564300463172colors.094118d9efb18ef5f74bb570694e8d3d.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconscolors.png000077700000000000000000000000001325274564300420342colors.094118d9efb18ef5f74bb570694e8d3d.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconscolors.png_documentation_1.9_start.html000066400000000000000000000115361325274564300411340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons icons:colors.png [LemonLDAP::NG] />

icons:colors.png

colors.png

colors.png

Date:
2016/07/19 12:15
Filename:
colors.png
Format:
PNG
Size:
9KB
Width:
64
Height:
64


Back to documentation:1.9:start

gpg.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000124071325274564300371710ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqIDATxk]uks}VA²0dQ$xeM\`\gƆ˜$خ<$J؞Id&3E qΝ$ɕ_`Ue4 1*Z6nH?A055=;;Tq >YV߲e?~ZLLL044""8h4J%Yz50>/z$IEU袋;gzzID5kְa «u&''e޽Z[u3\Ca$_i- "B|o=Yy̖e^jJZ嬳"MSxXz5_|qa͚5|;rرR ޵nݺ^uUAs*jSl;v,Map.UR\^6re3Y vZ?αck{=z7"mcu=yu׭V$Ie:tx_|]#TU%H]B^E+89 o]_{;/巽0j 266Fq~ Gm__C7t%rVEZ}OO/z{Ӿ_ũ\JDQDDQL4ݱ}Ї>6l9Gj5V^O?]wuE`]h6>p }T*Xk9|0wߏzryڳDc RRί|$MS<ϣ\.o>.b6oz} 7xWLT>֭ѣ?m"/h۞xk}Quuݻˆ0:Ū8966;-oy Z ooA\9r9NJ"*b+|'gTaC=^ciȢX|o\۷344szG~h>O1559{ނAPU1v `zf#V0 ~ƦM$I}:"Klܸ񗇆رcAPxM)1c,"BOOB_YKoLLNN233Cٚyŗݻw/J?;v`ڵ \rY:@Dc9Xf i/1::Z{O*ozh@Dzs Z~siӦM& C6nȃ>E!E$Iشib(8rJwV q&$IJŔJ%E_ܷofsP(0224ׯ_sNs(n-nxxU3<|3Htj5E.L*k33ӯA0m۶100Pj044LRarbWR*\@Q܍v܍FMXkeA#O6l^Eڶ,fAKQUsiJ8(>2Q{8;J̖7mZ!"KMiz" JR ("RQީC5 c)MRc{{l}ۖSjiT*ͻ^, Iq=@w"%X*[,x֭[y@pJ9hE@b@PR@o|mOb0*/NfffԑbR9ep.}}\ukƄ$=a]}1L9|jgуʥlI=MSR;gNOs >g~}V G'IfziO.[,Yl?^1ƤjoOE4;CYJbzͻ@G(Œecǎ> B >t"KW;BJZZ#"Xk}|n<,%!)T"sNՀNk[tpPy_en[/ IyBf|9?Y-Āx='N I i*8'}:C<8 D,c7'a`NAۖWp-P3 9ti4JPQU8G >mGaʄ˕40HE0fj~ƌ3::JX٘B`ޱ48 |ǷqP\fzJ6@ 0y'vb13 4;?J(W;o^j6ÃOy)xQ.}Ģ>UjOmU+6uX?OMd9A5бwqe6{ϲ?*a(Xk "x{ʷβlqЬ+[Wҩ[q|>ߋPrt)T.$OhA'[֯WԄsvIhag"P8ێbl#zLD`H6hE?bQ~G E@HҬ?^`k c mW Ƨ$e ,|C Ot*Z?r}1:PWnfY%!jG$:_gjs6H =+(F 7;O_(N8훦5\% _c $䱟_L)hڦvߜ=E)N3ZSv/ze"L& ,/_7A5ó9 f@#䦫't8!A"7|oW Qh؄W&ȯLVE—ϗ.#IIAayi4Dqb-۫*`U5lժU׶Z.1YT-q8Q F 5b"LY|OmN8Ê>E tzV4{*d-"TɎBq5b8of icons:gpg.png [LemonLDAP::NG] />

icons:gpg.png

gpg.png

gpg.png

Date:
2016/07/19 12:15
Filename:
gpg.png
Format:
PNG
Size:
5KB
Width:
64
Height:
64


Back to documentation:1.9:start

jabber_protocol.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000105011325274564300415530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqIDATx͛{$Wu֣gOƬQl0L!vHyBcGDIa'5#<v1Zv}LOwWWW{ǽUݳL+֭{{jVT^9`m%66QfWyp@4 WPʕ~ u8vIDlN# ez x7wjI.AȗD `MHHy"pg{c !?uK\[@׈7\i\Q?q%++F"@Ĺurw> |m 1j6g1d{^jв78q`.zAQJbBܡʇP="rߡ'/m7 "ްVJ=JZX}(Ux; y<~U({tvz[NZ/2\yuK`;뢮 +NOׁ# Ƨ #`\ b4sto _U_$x1`Q[yNLb$$LI6. &A)x3Ԁ#ù(0`23vp)غtÛq|v=ԶDp(5vgAZ+FV#񴧾e00< .ucmkoD[_Ây5 i^# "B2!3ڪY[c9.WnJk!ա9W{wِdxs%;Uj@*2@#t:A-9YپVV :^B" ewn0 -=@# ~0>GĆ{GAu("Z8^ ~!V\A&[Kߢs~BqC5Guj?^GG6">4PSzWbxPoD Qtz=>IF[aHb>Z3i7 &*3C9\A=>6p*?fE26|QvVGF?_,|&>ӸoA$Ƙbh پ.6(\Y+4>{, 쐨畧:": иӸӂHS86@ =Rι'xf|z3L:-/WGp6 0<°΂ZTh "B0[@#r1E?| 's$KJo>{V?)͗m`uh<_z9ϻ~6@ B=P4j QFq,8#%ݤ?W[V`DF2?vfDƗfYv@ ͋ȗ$ M :T.Dc>M{ {F&&wjLYӳ>}͝3l &bHor$F02H*킗+OHA,?8V0ގVkÚ ^~4mw#ӈ 9 PP9 2L_Q/IE{뫜qLdxX#.wlil8׷3qaWY--B:k"%>+-aG=p'O[D" X7hcM {IcǾpށdU} [h@L <0H=m@v)$f"opG~%w+|bL GpDUcuCfKx H )d$>5Lj͛ʜaN?w}qAdgyG~ oɛǼRRRhqL"n|ȗ& FA;8{71I/qƉGw60)IMI,##kӰTHȂYZR,QSߺ95z :A$KǺ@ow/"2mg1g&}bY˜aG"o_ʒ(ʉ>&fͭb*z!WOQLАA`vA/P0|+)iHA#*e k rƫ_N\6>.Y:8ePsXk)(gBӷ"8ud6;+u~3ʿtj :k#h4FOwz| 0&HNcP^Gӥ^̙3}3>}v{nKaii6 ghw:""Xc,LNצ:55֭[5iReZeYVgvv|3nc!$I(`@y^k`0磯c Qe7s%ZeIY X1q[qDF;sVEqLEiJ$IB$DQT{zEo߾O}3g͈Q!"4 h6LNNֆiJETu\uVU.z薅kV``0?o]:PU4evv_8pnmo|#6N~eYc1U}qX=cz9GdnnU?ᕷzqvٳg+v߹k$ A"jrƯu~8QSIENDB`jabber_protocol.b77c2088803ff10b91d4179e17d844e9.png000077700000000000000000000000001325274564300516042jabber_protocol.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsjabber_protocol.png000077700000000000000000000000001325274564300454252jabber_protocol.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsjabber_protocol.png_documentation_1.9_start.html000066400000000000000000000116461325274564300430030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons icons:jabber_protocol.png [LemonLDAP::NG] />

icons:jabber_protocol.png

jabber_protocol.png

jabber_protocol.png

Date:
2016/07/19 12:15
Filename:
jabber_protocol.png
Format:
PNG
Size:
4KB
Width:
64
Height:
64


Back to documentation:1.9:start

kmultiple.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000114531325274564300404220ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iq pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_FFIDATx[Mo=qEY-K/Z 0  oMOH]d""EREr8$8_v!5e ~€9s #"`>˗/G$&>|_—Wm1$ɳ|WZ|1Og̰L \]\\GD& HX[[/ܿ⋿\_^/bֱcKKK6777ݻwѣG[l=wo) gDFŢl68"`wwwŋ/7߼zL&?ظ{ Ku8qprrVn,v"8 O>=χmOeոY~( 0K`Yg38b JeYͶpai͛RYT\s@#e܍FR* !;~|e?Ww$ŠN߼yh1!@4v.f2̇6RB{{{9{k sj^[WsiRTHz7Un2 뺍|>97!sѲsxxNm TIaџc4NGn&DOn( s ζsĈL@qY Ο\PUUk4ݕ`N 4{hL,W 2:}%PDq(/!쬖f!z@#}p9B( v2*ͰT"3.bHySTƉވ(yt#q=Jd"VM$ EN JB|no3~q_ r|lɹ•T xt:Z>/+sF;P@Bc؈kŷ%t:cg6zZl)xo M tH8Ǐ_!?@@h[gAt]׋i{ {H c hZieA>F,0h ."VBucCN099&a׏d`n:z?V((q,'IUܩu*c4 $qVo޹s$,UBwCzQz8"i]Q P&<+^O:<NS_a+^7fH ! j17XZ7AQǺ5B(B 9Nz.ˍ~BJĚ2E\{{{9qL0,( H&6(-Bs"m;h[VMK 5M*l bQV jTLhVhXׯ/_?:yTQvl60NP0s\9\Nu`eY:636/8|rSTYFekk+.8( cpfSV*f::>JujǶmvxo9"qFAӎww6CUeA yh~Tme~pttV(Knve@cg:l&g.ZW5i\6[LSZ*t]˥Y(Pi pW~k5NitZu]7 d۶jnccLX /P[;?+w6,1v%(#Q IENDB`kmultiple.5268120c7f58a76f373f9038dd1769d6.png000077700000000000000000000000001325274564300472342kmultiple.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconskmultiple.png000077700000000000000000000000001325274564300431252kmultiple.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconskmultiple.png_documentation_1.9_start.html000066400000000000000000000115661325274564300416440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons icons:kmultiple.png [LemonLDAP::NG] />

icons:kmultiple.png

kmultiple.png

kmultiple.png

Date:
2016/07/19 12:15
Filename:
kmultiple.png
Format:
PNG
Size:
5KB
Width:
64
Height:
64


Back to documentation:1.9:start

kthememgr.png000066400000000000000000000071561325274564300341210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqgAMAܲ%IDATxM%Usoի^؞q2 1q(##`AHE e$$ba!@"8 a䀱33x<3Vu3UVs?w}xއe_~n#_q b(A0 CxV|]d2T?TTת ^Q<txڟnQf(_uzQ{Y/Ӊ71R_]0:0I &Ȁ+H1|3}چ_zb,"j&B ۏ_}tmqNEQR"5 2B/yeもAQznsKK,kjC'?WW.JJ27,~;DE4tE:c³q:r2BI >s]}Nx̀3vAcKLPfM5PU8FDP\g:Rp(|5՘ بw*lt<)MT%0Q#ųtԅg?w/\]2@>UN$9lAQ=qЋ Fޕpt6׉ z-L5o6Np哗<zӦ'Ob><"hlŒ}A!VQ"f5 ~ } gσ`-CetwGLLR@oHh@3ɕǟz_dSɤ>QaEQ|rQD 85҉ӱ 0i96!w9܂}tW_e fBb1ZC& "> W{.b0#2}T,%+!@P06#>Ii%&=3 q#JӞ'(D/`B~ʓ _多\(CNUpa+sT橯Lýׯ~1bQ6_Ͽb9j7!T{ml2nM[o{ ^ .m#;\C谇1$ʕa{ne^tF HHbL {$(t7Žp5~sQAά|gBT8)_'濊*iGω*J`Oʤ "U )"HF`DH70ģ>0FGC!"("wx' WVŗ S_j} 4͋ʐ92L1irWDFRTXLZJ #8KRQo|I⣿xW^Bl~s g!Cõ)aC=E~0Z #9Ĉ$Գ~ #`*_ͷr%dgݞ6IhŽN+ey@{*vessu LxMDZyKi[8K,./P&n =5~x,mUJuŰᆰsw9sC6q8tdڻh9}$Nb d[[$BA!U+v̟X>F:izG))]X%fpgcQ%D1n QT4;w5VFŕE:.IB='_xV'X]l0f:hae֨'{& K kFAr1Se{CY>{{a}x޳ []>N-ˍwיPz# n<#Jblt{/ K3~ǰ..^>G`qBDq|#BE$0S ɖ1mǡXcpΣ@9T#8r#a[=wGݛ8𻑘xak>ID(chi Y?mF8B!f39XkQ\ōV4IVqT Ժ j5nNE;Tj=}7md ґ%@Q7#X˚=hyjpHkXTQ,r @?߬WC!50xqe2' EHThuyp}VG%`*2D|:,)OuP&+TE]э x =x :$Q.mN(TTFJ"uvqdYV-t W&j0')%@7=^VGmz]xR# BdtMP 1"AU z Ƣn*q*i<2ТĊ0PNCF8O8G22GEyĊ0`@_?CH5n="z޽2*h*>\Y=UܸFP ෾JGII2R r M8|鏎-s?vz%oڷo֍B?!ͻⵯ}0 $bq}A^&0b oɀm`)_`qJes x!@HIENDB`lists.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000100361325274564300375460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqgAMAܲIDATxkpTՖ#Nw:!(IhpJB)uE)\-N k/tʲ >/@q@3b$ MmC}s&tծ眽{;~ ghOlmm}PUU}0l]d!@d 掎&r,$H"8$IR,͡PWEd`;}& Aм,HXExP(t`'&8=EQDUUT(b41vͶDQ%d;v>z  }I'E{^ɮ1qB24Ȳ|Mqq٣\DH$T*($Г\m*(C=hXz3feyM pd4d4inna5a"{8($I>K`0O?9 q' >k$0 wEL&M,ӟeYl?mk#>nLJlhhf4Fk(hNoƍ8΋wd^{;'2kdu9fڪ ZZZ 0$Zh2pw|95|yv7ahgϞ@ޤi@7DΛ'W X,ƦMz‚fBp?4n1F7oMN ?sL v_x_555pw@Dv^{Iar@J/===:̙3}饗n[vmxpoiiիWrvAȰ{UW'/r ͍3teK`puib_nݻ~2 -& -v݋O}@ {EQ Fb`4wzz+$^x[wP\ ׭[!w $Dxl6:Y XVNqɒ%L&Bׇ>zdO=Eam?Vj?ޏ>?IG !IZbd2D"ʔ)Sjt:000`˥L8fhpgt8vxdoo x 4c$<ϙ6Ő$ ͆jطoVI;i;z faۿo~b0.8$@jV9Shٌfh4L&FDQpTWWSQQ1Rzg544wޯIO2H/P(~SNZj$D ˖-t:Fw0-4(S%&qر޾DYY,@,Ce, v`0(9ϊV.2wnٳgO#bQagH;dvUU`x<$Iqp8q EEEmD =|CG_υ5=B, >^?N h4RPPss\kue4'$2jkj4Zp!1s)..rׇb4+/4~gAȜec1A0^*BUUN8TX,Ɣ)S(,,DQZZZsÍz\ײ1Jv{vPUV\(:ugE$m۶+ƴeقeߗ/L&+Vܨ F2/h"8y$~?Gy'O/gϞQ;ɉ"#4~$  v֠'HH$tqF6o̹s8:*:u*[nwIGxhp 䕖x L&, PXXȣ>J~~> gϲsN6l؀)pB6 Ps\3G l6& ECCw"Iow^{9z{{suuV?4<tuuuS \(fՊba< I~~vxꩧؿF|JrX }L&z5kְeM(hkk7xjkP-oA$b@ ʐT`t3&ANGQQvnV\ DLW_}G^("":.S:(߿͛2d342px`b͚5{zz0 |l6FD"L2eͤZvϟ)++!2v=wTZ H`6QT*uQ$#G/^ X`3α,.q50SWWҥKz|ל;w%Kv1L@Lf*BedY&Jɇ~VN_4cYY!&fCUU 9x vbܹn3jFج T$DQ<~qcWVV6n84/"~B!>vޝWQs3%ގg֬YI}Nal yyyy3ǵQ]NŋL&3=H&yUUYt)P(sä>0c5}\XSSsn/kKD"[y^-ٜ}yV+dp8ѣGltMv:::NG#X4{-ZT9NGplIR:DUUsbzLaDEI'Et.$AR)(((Mmm-ӦMl'|X:%Ik@ȉ^@&ڊ(,^ҋ 8r䈖2bJX"`2ÉDB>u0{EN*SYYɌ3(((d*o߾Q&+իWo:HgaOXTT_RRRP^^ns]dTWW`JKK=tD"!:"!@!}>=p(`r}}}cǎ#`[fϞ]?%WVV;jf]:qDYi0#u6$Ҿ. `,MMM7o|Μ9ӦMZSSS^YYYltgQ0ѿ̈j24t4A#L,YFqѢEfϷ4Ivk. 1q1)&ҎxQL7ȅwsAK4BNѿErA#;K .镚,/IENDB`lists.863c5eb855bc3d41de8ecde395d52733.png000077700000000000000000000000001325274564300460612lists.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconslists.png000077700000000000000000000000001325274564300414052lists.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconslists.png_documentation_1.9_start.html000066400000000000000000000115261325274564300407700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons icons:lists.png [LemonLDAP::NG] />

icons:lists.png

lists.png

lists.png

Date:
2016/07/19 12:15
Filename:
lists.png
Format:
PNG
Size:
4KB
Width:
64
Height:
64


Back to documentation:1.9:start

neotux.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000114511325274564300377340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqgAMAOX2tEXtSoftwareAdobe ImageReadyqe<IDATx[yy1}ҞX/D\Vp'@A#)LJ ]'.LDJP",ր#BIIj33=3ݝ{ӽծ]mt~o%4e>d|ɏ/?XD]JDT$˲'0 #DDKt(EP@JveuuZ9 !77tX hkkñc"ǏEm$C5ŋ]ܶmIBT $pܿϚ7pCm~y"%˲4&$gS>Xh$=}}}{s# щ e^ `砸)b8N''( ҒВ|H~юE"!|QQ*++A3~‘( #{-/'zhBPyƦz]K~D}jA1NF"]IN1%%(+/: 63be)Un XbŔ^p\8x`)%+O7D;:pu [Vd=N<{ìYƔ'p-kRӽ555T <2I7ybٳgef믿1Ch" ;#h$<ܛCZf ̞3[84%FBUT7]S1GVQE0N˚piFA'hbb?a@r֭[sύ%HYP0Lz}dl?`E"5 d6@%Q䗔#PЀʚ&aRĠ"!Jz= -Ji[ZR*o'xx=d Qzprr)Qڜ #wdZDQY3#`r6FQy|f7R[E}P@b;~ 6luDQuh+QB#*?_CNVuuQ#$"\!NL/&=Zy 60{g3 JF}g-A, o8 2vV9H*tkDr8m=߂Qx92>Y b^g9RO^q]wUCm :LȦ?'Gvϒ%K\<={\pR]PmNyxrB{ OzL ͤR^}~ݞkQ^'4aBd-Fu}]3 Q̃`3kg " رKf3Y's@ @\!J5h1CHHMFX4A81"BqH Щ&(\R[6%-0 y;_Xf͟ DwuYCN`/lNwkp{uG.]L=ե@W-Q*)֫x7DuŅL- 0bHQ<""ll41&'^sh=+d9XWp*J%%%sj{pǺ{kuy:;5nJf%鄪*WF?SHxYQE mho+B[h'XRg+|&ӾJo|?hll\+HEM\\vlr&D %C1I3P [BH)zl=c'l/ذ)?r˖-dZ;mWS۰iӦp4+^ O~;iL1vE3fƕß<t~+K#k|gFK/z:k} Eƍ_"xws #!t! Y`KDcm̻ж~ͻ ]>k1oZc=D BqSOŇe#E t–G\xi*1WF,!J|/T'h@eW.Ut!!4Hġ'FJjH&Z€4 5 ӦJ%&Tx(*Dmu>*+P_oNꂑ2\݃8"n!hR_*sҘ"^RbT;ݽ(*6`4F0?ӝ8>71[T;'iXԲeV@)Z])I'<}Gp8:$,bTp_D+~[:"&TΏqsuk㯲IC\y9="+13.Pb-Co1kd'ǯ!֦tB>/HQ/Cg@rs5<S([jh~2v 7kXڀ;`TL@r,&:0Fm>,%ㅙAI9 ƙ>$h4Ab˛E]4KEфJ˟Y:DzT\8Zޔ_o/m)V43'Rd5dny3No~}Ɵ.9y{f}%ИeB""/jϯܛPҸ|q4jx5m,[a`c1cHXfƢSRx46:&[zՎ9G%Kb |kFެ*1'%K.`̝>/4L%k{D0$䘆7hp8F#>{]SrgWIHn@rb,kI>XeCZec(ϚL\w*@ʢ5yx[^^p׼+jY\dԼ< .݂e\6Gow,Xboě=f5}4;Rrn\+ܷp@]qϭp8rh˦Hvp=䇇GOj{lo'k&bYwWE'Pß%(RBb|n5U+UuUjU* icons:neotux.png [LemonLDAP::NG] />

icons:neotux.png

neotux.png

neotux.png

Date:
2016/07/19 12:15
Filename:
neotux.png
Format:
PNG
Size:
5KB
Width:
64
Height:
64


Back to documentation:1.9:start

personal.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000110721325274564300402340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqIDATxidUޫ޷<KlLj(@DĖ/(( aGH@ C |EEvvݎ{v{]3x"qW}UpG^s!U}sYNMD.o~oF~?G[;_xl09UE,|Y~ICIm}fq/x݁Ҭ`Ah0'Jk 7bdb ޗ=xO[(qt{C♄dvl2tc((U|Q00ҡk/z齾(>!Ld,اo}Ǐõ{x[)=xS'WEc&n6X>|柭xy>'/ޥ+'Q-_jηXcP@C U%6r7 ;];Ϝ}{c#+WucGoqojbeziJÔ">Pf,)6 |XX>3 Ã8Ovxln0hw :ʼD (eLbe9cvo< nw]sˋ{|wxLcqq]P%7f0⢈E :]p/_! JL d6U[-VݛEGc {|wvin|8$8clyIBQ,D<w{Z3yEᢈ xWF51Q" $ؓ]]l0$@>"F" +c8!8']խx c"p-o8F BP1մT`Xgb86K*p$:)ʼ/E6I 6XJXbj?\!XK5]Xg^^'ji"bc b-X0f{A`` V*'"69Ir wOESBA:6znCAOy{\i1I0~vo7Μ8SPtK)#B'O`&ʳ8ʐܳ/@hPF3T=!(A-S\^.0>_&_yU]?uVq8l<<%K\9r(̷ϧpLh(눤63`)ԹQ_ʂy470q |G翭>y_^k}~ΟG.uS&9)L"^`~+vyLc O@L e%ځ.3B>Ln sKwUn`Uōh;+Qלfvf!n&bGlxӄ".sBb TbsB> 4vjy-yZsȑi/%x i).sUEl3y6Y=qh(!_VoϾ!K Yл6W?]`nFruǎzst׻cɍZƍxldy TWDDC%PP &FOٻp|s'FU_p_qׯ̉Ռ|{jȆP ČDAHL0WJX P XUuI( @m l"# k"3>1w}Qc\NxBWWnW 2驲?&/W*%Њ#4PS3LP[@+.ZB-ݱоx=EWXq^䑓)}MwsH6(3E\E`VOA#zRqv|n8O>%4`$ħ5`0P՛aΰc(a'ŸC{YUvWkCjTAWރ:]լ=##C}Q >AF\z p s86wR8Gbɳ".RgxMqlU۳}c .\* ~+;JAV  i%f#sU:Lw`~)הhP 9A *RE-`PB[Re"GZhƣ!y7^w#+&jظEGWjÑ7]5 WĆ %y5%R?@5T lHH`c+%9;ds+qm<0Qrn\ yaz¾9Rd\(gEx0יd[92E~/g8P28B(}Zk&Tn3H)A 2J6R  㚕q19Xv/\d*\#EL5cNp+eY͢ťŋWװA U`T)Әܠ$eÅgc1]mw=fuK\eTFRu!J͡бv{amFKHZU3O1L ;DIMw@malB9 m@(mګmzݒPzPmҼW|[_[^c"r%hG[7`=7/]G<߲W,9~5fIv' e^8o6R 4,5PJW4Hم\0F||QVbM6vj\M31ѭ6WV)zMRxEyV d<x_'bJ3/\J:{:.AT%k0xa9bpEP-Y9c>:ek۸VTNL"Z~ݺ%ޗ^u[1l1&"uecsOQ/"_+r}l0b !S]^<ؚuw] ⩋uUEfvblܢ^wD|Jt_늼 SvMd`bY;Z7DY.IY$F_5:*חh2~[ gu|I *3O1{K=?Hۧͺgׯ_|Ռs6{{ԜߟuΉqM0{mDsג4ӳ(u1+(>:}CǐjFfhg `x猛yICyNC`VoǶW+-BxogԁM?޽4B l^CU:讧}4S ~oBS T{œה9M`Aȏl[+xþCW♥kLal|i-`GCd; "0los۝;;̩/|) ~ ^IyE8@ ~e,iǭمR<5.ۤrTƭDI$Yiȋawauևu7VCٻ0!_׼g8; 7IENDB`personal.df16682a92a94b3a8f76c59c254128d1.png000077700000000000000000000000001325274564300470102personal.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconspersonal.png000077700000000000000000000000001325274564300425572personal.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconspersonal.png_documentation_1.9_start.html000066400000000000000000000115561325274564300414600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons icons:personal.png [LemonLDAP::NG] />

icons:personal.png

personal.png

personal.png

Date:
2016/07/19 12:15
Filename:
personal.png
Format:
PNG
Size:
5KB
Width:
64
Height:
64


Back to documentation:1.9:start

utilities.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000143051325274564300404260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqIDATxśwVՕ{{^*E,k";vy3ɼ8IƘQgZƉXb,ذŘD aA /S?.`|9S~k95-#k҅B-&([f@b$VQVUH/m7s6y\,"J ۛ~.{ ad'?8a3i)OԩV؝6|T 5JG.džu||).z?Xk6řlF̷H~s¿Ȧ( #γ=6Mլ9u;t]0OL* 8D1J L!RaZh4\/]1/M(``h"*6[0,}Ϣ e3'In |t.לsmG/к~P(-IY|vÆO>`Y=9[ٷ= ȈYrt| 03_hAI6l~~]Å v{{ 0E\ǁ#yK4j[ vƲ]‡x`Ҝm)a Ȉ lM/v-((6i+6F-ˡ/6r.c$a+ 0DGLˮ4(! +! ڿ[v6[}]1g{ɗn@$iS>oO9rQLfui"" c4A) l4nvq|Bk8~`YKZ+@ ۷vʑg1>hQ̝pe~!CR)]&v0s(mhܗgtºryf3 ouY='tN Ic8mʴ(d7@D6yG;j:: Y׽jq&c>t*; kP ;Oy0 t}m><.>ou/7xQJ9AWϺ`QZ{#3&1mJ'}90$ C no E#m$ATJ~Bе6aEa=[llDAvWggi-˘O9qn ~PQj )xE|t ?3K=[~-'t[q1q\G v E( -5Zk(: @c[(ˡr<+U_+Z$pN]fƼHhɰL :Bxkd4cqQJsҬgqO > t)z8<  )q]՟~Ɵ/b57rU7mEQS7o"CHNj x[xΠG B5+*bHe**en~М4 ~|l_ʼY\`0*ɻ,|o8q]40 4 6 RBaL/qBc +h]jIar8<|:f?XJuxb<~+Ցճ+רB @ڄmIP-`L&q]۶1 4&Zr ; JcEH86QbfjHP Ҿ@Hm)-֝كG MyxGuq(l+Mi\pW伯`1Ədؐv00 ro$"c ӟ/R}0bGMm9~! VC"uC_E|0 L4M,²au()z>5.=ԌͭPu~d2 ~_IJjjjuiqEQӬ3"8"HFC ,ϐ0@8PJoXRIaTa?^ .98<ڄaLLj2|N~xAm(+Vz;Df)bJc@ڶ `YQUK@1\K?dw^D¥-$yt<;%Z5JEUGT֪UfA!;^0J=ツF)TZ"ō3Bq=TrnE]v Sw^TqLuodG+-y`V1HCăo2feKjiH*o[Msy3N9+KJ-{#ł7j 7u@Zl!M*LuAe9,ۖ8藆,PoJ >{ W6aT{uK>Zdҥ-ų/OB*閳:u0> ϙEX* M<#-a>FfEmfVREH TP(x|\{ -u뾹oЖIUpn)V|۶ZT c}:}p;]J0$Dk#5 {N#UiPhV$H)1Xd~Kq j RϿ{t*YgXq۝O!GC^]s8jHZR iG *kc+4 0h%1?7f$RKkIJµYx97 $NgJNx02M~|S'APJ_Q ma|mx- ]RZãX1=u?;Y'\{/qeE0$mq۝OJ$()F0k65|*BX2Ē XYv]t ԮZ%Fj%_ݏbXiHK׽f\.k^Tᅪs^3 ֹC؆2(2L EŸ6°TU +DQkX 7µ[ö/{U=ۋi Е`bfs;d!LQ&2͖$ Tm2-#Ek"ekrU=Τ_~?li!7Xd!mG?DfJHrn(_iZ)8.+(LrYV?kz D\X>$ZJ0DI B4+N`ΩO'6 CR0$i4յKJmK_F&֒X Y]ug38dw[$6Il׉z7'v9n7 HbH@heҰZl^ʕ|f|(ʌ}J sC-XA;*@eI 4eGNǍqh<M# r ׍qHPҿO׭`e5k sG=R#$I<[*k3N/I+n9Ԍ44awNdR'馰m(!~/sަB~b g YbJr"AiD@P>ۥitp(FZD#$+Pq|(LnJtF~BܾpT"@H&%ʾ@jEQ1ƣh BM\Q s icons:utilities.png [LemonLDAP::NG] />

icons:utilities.png

utilities.png

utilities.png

Date:
2016/07/19 12:15
Filename:
utilities.png
Format:
PNG
Size:
6KB
Width:
64
Height:
64


Back to documentation:1.9:start

warehause.png000066400000000000000000000160471325274564300341210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iq pHYs  gAMA|Q cHRMz%u0`:o_FIDATxb?!@ 0g`cвehtᕔedbdbf/=x1@`p5Uc1baQ9üHXa] n9 +% `eb wn5+vm;)J0ьx3ֆ bV@=."& @6{xtC mC;Y\~pk Xy@ݩ ?=) 8#߿r K8SWwS~b4b e= @ 6iA/ ߞfPÿfIcFݖ  X(aJ@z1@;Ѓ?u ÿ_x% T ?½t*K gE@;2 ;0>= ؿo ,30s20qF~`` @AYSBG @*Ė@Qع+) Xx #e-b`_!@=| `fC$ !X,+@5ோ@OS13\ ,@1$@~R2=O30p(jB$A9;ʿ y .m;<& J|e;+3E@?=8#1#7 . 8ûU lbxy|.$p22{N> ^̢4po% DY11|A[A”VR.6F`c`'j -y0iKovg`df`g`Wae`J}`*dw   7}`xjg $7_2|7WPA\NA XE"x`e/?DV! @LfL/ t 3 !hp*y`?6Ç'Ug^pq`7/1|~}f`aЅ%tW]<7w_1Q^ Y T10JK[+k1/_.3|ؿu`'X~C-6ï`bx#I']A# ;/_no o|`7H7؝2vy)6Gj *NCff bb@1g翟ف?"Yo_gJq-@$@7Z//_ FGHX1p ?R[ظ3 !ͅ l`},DEoPw?İ@!EF4/ bo{ǏL*糲z~ H hf*GG]]/o230Y[30<{׏>&s`s{L bD.m,$0ݳלL ,a}] _@u+Eo1y\W H ": $c55˙Ŀod`{ )@X<\bXs'3?3X 1 LLຜ ͐W^a < >nKoSW@<<""g`&_66w;8`5{ ~ 2+0a;02p0S3W`\|a W[ } B 6X*j^jc<la ☄ s\P@ !vVV1ϟWp/`-=`!LN92?] a`{Y[As[ = ߿ zl`MMo`\A8CVoEk2dYX3X~àůȠ)5i PZ0W1'0 m1@@c&4QQ#Rxo@Os*6m_ɛ7 p ~ ߁e {w&f&`!e~7yS X`:~ dA,@Dp}`s1 `k0Ϟeq#\߫[n=ps.nQY~_ |aWh??r`wbWoAZL,@S$(q[~|AP9  B~lax}× A"P{CBI02Dq202|L Ao0K1@ ҵ L`fx )@D?/[47[63~Rm]`,}g336f`͵+Z3\BRpol o 5^ oΖ pL σ22#Ç{ q A_;™?ʑ̔ _ $ @(eh`(hd | D1</;7{';EW~:#L~y{<,÷^ C >0|y$ài`FJхx ;N_.MȠu.GD8M|\̂\{ e pn&8$Zu=}&if`"f| XE1|׋XVs Vf`db ïw ;]鮏 bTMfo;H̐'Ǿi4E9D]S*{36 1`fnH2x i ,0(0pg/<r`oP)>O|clpfz) ,_bx}'7&zQ?;KLQ)Iǻ\?$~30 37elص},Vm  ̲j T#o Ԇ@5,ꜝ׿@ 8F ff &`3?+/5E%M>ⷷ\ Ll n I>0"!?&fh|:xؿqJ331BR Rp1|O |ʠ4Mt@D@'}=_02pq B|(g=l%gP?Da~F&Lcj3?8NPj;Ïo?D%~ FlQ߯| lJ3 ?^.^>P,Pa1Ɉxك]=# #lB &I6l _>}, Bj) @H;>a:o`R8}G3cYF&½ {pcۏ43'?`4 " 4pO &J _|bw*'/F"(H0"ke=U. 3kS`-}%6,ە;O 0p9b|;MLr' pqP6#$q3F!I׿ c j7 @GA(?CDlCϳ~03p0H1s-C~` ՁJcP@S98k1A ;p @` `P8P@6n`Ao~Ll-A`~3pS3p 1Q|$# C@5ex|)8@?#ě 63@dU$XYQxT (d\"_Dt$P,^E?~'X gw5;'+8A`hA $`˒?+,g`e>yxDWDt|P mx. '%E0 6_2qq6]e*kx~S PTw ':)?*}->`r HY3~u؁mx&QtPe3 h.sd4 e?@&IseD#"ɂbl8-aAp*Wk#G̐,>R e Qf({:#3[p3 8weefdAo 0&?Eɿ@fڔSMl51R  돟(A]U`O`W#CkԀ럠E uE @) -o ة#OA |BBT Dt0AӿY0f2[KOXf;nn`@>8̈́ :8\| rh}H!l0B(,h! H H u``<XyY$yE8i2=@G:0,Pς< d33#jp 989$p6l,b)Vހ$e":]psTt*/ބ4{1J@\A'`c'R(:?9PP?| ӷ/,`2PA¸KlFyUp^}x *A!,@1&9)G.NI5E`sː/P*Xa [0Ie@oPbc3gP~Q6d3D|_s3 1&`!3u,Nǀb[HOfplbx|_ ` 9b7}p'@w^||O>>F،xTWAA%< -1CR+; .1c7p!N|ж(;)@Dw__~ArXj_F~X249F$f?+c`N *AJY/#}WV`ufP,A\Y<,CZDAjCZL ]~DRY`߿^{ ZSI|2 l@H4Xm AA.;I"}Lb,4#0 XĀ-5e`F|J{GoR&T  Az?3"6Ɵ ޽cZ?~C'IO/!eFĠ/Ru0A2['?1Aj?&L) %1o0|_Lϗ n?cP`&E5t6Np?df7tB#b%=QpSMY>?ןsWR XH({02y9ǀRf`ű0!|V9hB/g IENDB`xeyes.0fea6a13c52b4d4725368f24b045ca84.png000066400000000000000000000117121325274564300375470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsPNG  IHDR@@iqgAMA7IDATxyl]՝?=͎x}޲ dP $$ !@0IU0A0-hځb(HR!& PCJBH,6vxx{~wxgqL2JW=;\09as;m{ ۽fm*at]`ǎGF|Zx駗qam6[Q0 ߮OzR7 I @,ȿ+g/3Tl{EQ 0IH$e[o~x^D)OUU@4Mp|'??D}}}˷^Z([8$|ߧUU/Dz,^UUEunwUEEeVY dYfڵ,]tVLJee%@!Nv>̵c& ,Xwމnz0 v{MIIIcL;Iej*/^<[7.--ehh<O^aaa/+<[M ,..fݺuHnՅ9Ν`xxx4}$lڴiD"AUU555ߊ Aŋ躎?{n.bd͚58k#''P( n;7;;{jkk%M~,I^+V\Qne˖a۰a@<鬴l,[ s]|,_ǃ$Ilrn4fEt:0@yy9@qq=yyy34MbZPygi㬬?$MnYp8x<~+]A A|sϚtS`[dYќ7]Ĺxp8TVVxwki6it8<--- a,\p|n7``d4M;##&ޅaikk#… )***QTTD{{;oE-dffPUR!j8p#LRRRal6:k֬ᡇBUUE!//nrssWOL]֡yyyV5>>ή]8q999|>t]… (C=ĪUfy\x̜[oq @U[,vH"?9===lٲEiڤٳ~^xOvv6dee_dɢS@2}bEQDQ$;;U:u;wdmFnnc166Ƒ#Gعs'uuul۶튼\.<x cEQĬ+d2I&w}I3(u֑1']ױL驮.lmm%I2eYv$ILm4|gV:(Z=A,yh>innf߾}^QxNKK 6͢'A蠣_D"1IYQUáy+e٢ew]׉D"J~`qqeل(,I2My7Xx1ǎvp8p\8NKHw3$IUVViZm8Ym$1o<***!##Ӊ$fp]UUtkSqA0L?u!jjjxuA&) HDp\HD8kR\\luBQǍ GASU53 Md27oFN>M2ļC;@QY >ϔB̙3-Jy!|r-ZDSSϟCϟO" O7`MMM]:::t N.//"+̘(@wwF&awuWZ0q,OE.]-(b%Mؙ`GmP.{ǻ u;?S3 b1OaNrP;ݻ<XfFD"W<鏮===Mї_~mpp`yt/{-O:a3O8p)HLU@ccf3ssy{BӧO5777Vh3g` 6_c&fˬuh'Hu2y| o߾@iVAuO.]X6ST>H#NxG^liiyRU(֝bss믿~77R@WWWSdZ.wW 䱌;vԥFzZi;e8>KZ[>x'hbHL୷… ǀ{N<RrfaV ؿcǎOIm,Lihhh}w,*lJ0㱜ٳg}&_j!RN̘LLJn>H$ر6aݓ_js3]i7Lx<֜`0x'}kb```W\AQ%iKtl6Nm۳g?Gi'xn<{# H25@ `$Ik?Ft|͗t]L&F 0I|CCCF$Ig8x`iOMT(K,Y{̙zc 1c||܈D"F"4Fu㗿gV.Dg (ݻwkJ0DqcllF<ʪQWWמ<>/ItÇ?l022{ꩧ?2qxթiBʀXK/sp&h8x`mG` P !|=rh4:mmmO>GM|рb߿zkVZuCyyy,pk|N7 )?r-[֭_RɦO?޽{ON ymPC*.R)f w3CdR9؀oaHYo ݅HK0P&xPFf509as5z~| IENDB`xeyes.c3f847fb93674bd3b037fe7cc3f21b17.png000077700000000000000000000000001325274564300460502xeyes.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsxeyes.png000077700000000000000000000000001325274564300414032xeyes.0fea6a13c52b4d4725368f24b045ca84.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/iconsxeyes.png_documentation_1.9_start.html000066400000000000000000000115261325274564300407670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/icons icons:xeyes.png [LemonLDAP::NG] />

icons:xeyes.png

xeyes.png

xeyes.png

Date:
2016/07/19 12:15
Filename:
xeyes.png
Format:
PNG
Size:
5KB
Width:
64
Height:
64


Back to documentation:1.9:start

idpcas.html000066400000000000000000000157401325274564300324440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:idpcas

CAS server

Presentation

LL::NG can act as an CAS server, that can allow one to federate LL::NG with:

  • Another LL::NG system configured with CAS authentication
  • Any CAS consumer

LL::NG is compatible with the CAS protocol versions 1.0, 2.0 and part of 3.0 (attributes exchange).

Configuration

In the Manager, go in General Parameters » Issuer modules » CAS and configure:

  • Activation: set to On.
  • Path: keep ^/cas/ unless you have change Apache portal configuration file.
  • Use rule: a rule to allow user to use this module, set to 1 to always allow.
For example, to allow only users with a strong authentication level:
$authenticationLevel > 2
Rewrite rules must have been activated in Apache portal configuration or in Nginx portal configuration.

Then go in Options to define:

  • CAS login: the session key used to fill user login (value will be transmitted to CAS clients).
  • CAS attributes: list of attributes that will be transmitted in validate response. Keys are the name of attribute in the CAS response, values are the name of session key.
  • Access control policy: define if access control should be done on CAS service. Three options:
    • none: no access control, the server will answer without checking if the user is authorized for the service (this is the default)
    • error: if user has no access, an error is shown on the portal, the user is not redirected to CAS service
    • faketicket: if the user has no access, a fake ticket is built, and the user is redirected to CAS service. Then CAS service has to show a correct error when service ticket validation will fail.
  • CAS session module name and options: choose a specific module if you do not want to mix CAS sessions and normal sessions (see why).
If CAS login is not set, it uses General Parameters » Logs » REMOTE_USER data, which is set to uid by default
idpopenid.html000066400000000000000000000227101325274564300331470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:idpopenid

Table of Contents

  • Presentation
  • Configuration
    • Shared attributes (SREG)
    • Security

OpenID server

OpenID protocol is deprecated, you should now use OpenID Connect

Presentation

LL::NG can act as an OpenID 2.0 Server, that can allow one to federate LL::NG with:

  • Another LL::NG system configured with OpenID authentication
  • Any OpenID consumer

LL::NG is compatible with the OpenID Authentication protocol version 2.0 and version 1.0. It can be used just to share authentication or to share user's attributes following the OpenID Simple Registration Extension 1.0 (SREG) specification.

When LL::NG is configured as OpenID identity provider, users can share their authentication using [PORTAL]/openidserver/[login] where:

  • [PORTAL] is the portal URL
  • [login] is the user login (or any other session information, see below)

Example:

http://auth.example.com/openidserver/foo.bar

Configuration

In the Manager, go in General Parameters » Issuer modules » OpenID and configure:

  • Activation: set to On
  • Path: keep ^/openidserver/ unless you have change Apache portal configuration file.
  • Use rule: a rule to allow user to use this module, set to 1 to always allow.
For example, to allow only users with a strong authentication level:
$authenticationLevel > 2
Rewrite rules must have been activated in Apache portal configuration or in Nginx portal configuration.

Then go in Options to define:

  • Secret token: a secret token used to secure transmissions between OpenID client and server (see below).
  • OpenID login: the session key used to match OpenID login.
  • Authorized domains: white list or black list of OpenID client domains (see below).
  • SREG mapping: link between SREG attributes and session keys (see below).
If OpenID login is not set, it uses General Parameters » Logs » REMOTE_USER data, which is set to uid by default

Shared attributes (SREG)

SREG permit the share of 8 attributes:

  • Nick name
  • Email
  • Full name
  • Date of birth
  • Gender
  • Postal code
  • Country
  • Language
  • Timezone

Each SREG attribute will be associated to a user session key. A session key can be associated to more than one SREG attribute.

If the OpenID consumer ask for data, users will be prompted to accept or not the data sharing.

Security

  • LL::NG can be configured to restrict OpenID exchange using a white or a black list of domains.
  • If not set, the secret token is calculated using the general encryption key.
Note that SAML protocol is more secured than OpenID, so when your partners are known, prefer SAML.
idpopenidconnect.html000066400000000000000000000512331325274564300345230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:idpopenidconnect

Table of Contents

  • Presentation
  • Configuration
    • OpenID Connect Service
    • IssuerDB
    • Configuration of LL::NG in Relying Party
    • Configuration of Relying Party in LL::NG
      • Exported attributes
      • Options
      • Extra claims

OpenID Connect Provider

Presentation

OpenID Connect is a protocol based on REST, OAuth 2.0 and JOSE stacks. It is described here: http://openid.net/connect/.

LL::NG can act as an OpenID Connect Provider (OP). It will answer to OpenID Connect requests to give user identity (trough ID Token) and information (trough User Info end point).

As an OP, LL::NG supports a lot of OpenID Connect features:

  • Authorization Code, Implicit and Hybrid flows
  • Publication of JSON metadata and JWKS data (Discovery)
  • prompt, display, ui_locales, max_age parameters
  • Extra claims definition
  • Authentication context Class References (ACR)
  • Nonce
  • Dynamic registration
  • Access Token Hash generation
  • ID Token signature (HS256/HS384/HS512/RS256/RS384/RS512)
  • UserInfo end point, as JSON or as JWT
  • Request and Request URI
  • Session management

Configuration

OpenID Connect Service

See OpenID Connect service configuration chapter.

IssuerDB

Go in General Parameters » Issuer modules » OpenID Connect and configure:

  • Activation: set to On.
  • Path: keep ^/oauth2/ unless you need to use another path (in this case, you need to adapt Apache configuration)
  • Use rule: a rule to allow user to use this module, set to 1 to always allow.
For example, to allow only users with a strong authentication level:
$authenticationLevel > 2

Configuration of LL::NG in Relying Party

Each Relying Party has its own configuration way. LL::NG publish its OpenID Connect metadata to ease the configuration of client.

The metadata can be found at the standard “Well Known” URL: http://auth.example.com/.well-known/openid-configuration

An example of its content:

{
   "end_session_endpoint" : "http://auth.example.com/oauth2/logout",
   "jwks_uri" : "http://auth.example.com/oauth2/jwks",
   "token_endpoint_auth_methods_supported" : [
      "client_secret_post",
      "client_secret_basic"
   ],
   "token_endpoint" : "http://auth.example.com/oauth2/token",
   "response_types_supported" : [
      "code",
      "id_token",
      "id_token token",
      "code id_token",
      "code token",
      "code id_token token"
   ],
   "userinfo_signing_alg_values_supported" : [
      "none",
      "HS256",
      "HS384",
      "HS512",
      "RS256",
      "RS384",
      "RS512"
   ],
   "id_token_signing_alg_values_supported" : [
      "none",
      "HS256",
      "HS384",
      "HS512",
      "RS256",
      "RS384",
      "RS512"
   ],
   "userinfo_endpoint" : "http://auth.example.com/oauth2/userinfo",
   "request_uri_parameter_supported" : "true",
   "acr_values_supported" : [
      "loa-4",
      "loa-1",
      "loa-3",
      "loa-5",
      "loa-2"
   ],
   "request_parameter_supported" : "true",
   "subject_types_supported" : [
      "public"
   ],
   "issuer" : "http://auth.example.com/",
   "grant_types_supported" : [
      "authorization_code",
      "implicit",
      "hybrid"
   ],
   "authorization_endpoint" : "http://auth.example.com/oauth2/authorize",
   "check_session_iframe" : "http://auth.example.com/oauth2/checksession",
   "scopes_supported" : [
      "openid",
      "profile",
      "email",
      "address",
      "phone"
   ],
   "require_request_uri_registration" : "false",
   "registration_endpoint" : "http://auth.example.com/oauth2/register"
}

Configuration of Relying Party in LL::NG

Go in Manager and click on OpenID Connect Relying Parties, then click on Add OpenID Relying Party. Give a technical name (no spaces, no special characters), like “sample-rp”;

You can then access to the configuration of this RP.

Exported attributes

You can map here the attribute names from the LL::NG session to an OpenID Connect claim.

Claim name Type Example of corresponding LDAP attribute
sub string uid
name string cn
given_name string givenName
family_name string sn
middle_name string
nickname string
preferred_username string displayName
profile string labeledURI
picture string
website string
email string mail
email_verified boolean
gender string
birthdate string
zoneinfo string
locale string preferredLanguage
phone_number string telephoneNumber
phone_number_verified boolean
updated_at string
formatted string registeredAddress
street_address string street
locality string l
region string st
postal_code string postalCode
country string co

So you can define for example:

  • name ⇒ cn
  • family_name ⇒ sn
  • email ⇒ mail
The specific sub attribute is not defined here, but in User attribute parameter (see below).

You can also define extra claims and link them to attributes (see below). Then you just have to define the mapping of this new attributes, for example:

  • birthplace ⇒ l
  • birthcountry ⇒ co

Options

  • Authentication:
    • Client ID: Client ID for this RP
    • Client secret: Client secret for this RP (can be use for symmetric signature)
  • Display:
    • Display name: Name of the RP application
    • Logo: Logo of the RP application
  • User attribute: session field that with be used as main identifier (sub)
  • ID Token signature algorithm: Select one of none, HS256, HS384, HS512, RS256, RS384, RS512
  • ID Token expiration: Expiration time of ID Tokens
  • Access token expiration: Expiration time of Access Tokens
  • Redirection addresses: Space separated list of redirect addresses allowed for this RP
  • Bypass consent: Enable if you never want to display the scope sharing consent screen (consent will be accepted by default). Bypassing the consent is not compliant with OpenID Connect standard.

Extra claims

Associate attributes to extra claims if the RP request them, for example birth ⇒ birthplace birthcountry

idpsaml.html000066400000000000000000000422601325274564300326270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:idpsaml

Table of Contents

  • Presentation
  • Configuration
    • SAML Service
    • IssuerDB
    • Register LemonLDAP::NG on partner Service Provider
    • Register partner Service Provider on LemonLDAP::NG
      • Metadata
      • Exported attributes
      • Options

SAML Identity Provider

Presentation

LL::NG can act as an SAML 2.0 Identity Provider, that can allow one to federate LL::NG with:

  • Another LL::NG system configured with SAML authentication
  • Any SAML Service Provider, for example:
This requires to configure LL::NG as an SAML Identity Provider.
Google Apps Cornerstone SalesForce simpleSAMLphp
NextCloud ADFS Office365 AWS
logo_amazon_web_services.jpg
Gitlab

Configuration

SAML Service

See SAML service configuration chapter.

IssuerDB

Go in General Parameters » Issuer modules » SAML and configure:

  • Activation: set to On.
  • Path: keep ^/saml/ unless you have change SAML end points suffix in SAML service configuration.
  • Use rule: a rule to allow user to use this module, set to 1 to always allow access.
For example, to allow only users with a strong authentication level:
$authenticationLevel > 2

Register LemonLDAP::NG on partner Service Provider

After configuring SAML Service, you can export metadata to your partner Service Provider.

They are available at the EntityID URL, by default: http://auth.example.com/saml/metadata.

Register partner Service Provider on LemonLDAP::NG

In the Manager, select node SAML service providers and click on Add SAML SP.

The SP name is asked, enter it and click OK.

Now you have access to the SP parameters list.

Metadata

You must register SP metadata here. You can do it either by uploading the file, or get it from SP metadata URL (this require a network link between your server and the SP).

You can also edit the metadata directly in the textarea

Exported attributes

For each attribute, you can set:

  • Key name: name of the key in LemonLDAP::NG session
  • Name: SAML attribute name.
  • Friendly Name: optional, SAML attribute friendly name.
  • Mandatory: if set to “On”, then this attribute will be sent in authentication response. Else it just will be sent trough an attribute response, if explicitly requested in an attribute request.
  • Format: optional, SAML attribute format.

Options

Authentication response
  • Default NameID format: if no NameID format is requested, or the NameID format undefined, this NameID format will be used. If no value, the default NameID format is Email.
  • Force NameID session key: if empty, the NameID mapping defined in SAML service configuration will be used. You can force here another session key that will be used as NameID content.
  • One Time Use: set the OneTimeUse flag in authentication response (<Condtions>).
  • sessionNotOnOrAfter duration: Time in seconds, added to authentication time, to define sessionNotOnOrAfter value in SAML response (<AuthnStatement>):
<saml:AuthnStatement AuthnInstant="2014-07-21T11:47:08Z"
  SessionIndex="loVvqZX+Vja2dtgt/N+AymTmckGyITyVt+UJ6vUFSFkE78S8zg+aomXX7oZ9qX1UxOEHf6Q4DUstewSJh1uK1Q=="
  SessionNotOnOrAfter="2014-07-21T15:47:08Z">
  • notOnOrAfter duration: Time in seconds, added to authentication time, to define notOnOrAfter value in SAML response (<Condtions> and <SubjectConfirmationData>):
<saml:SubjectConfirmationData NotOnOrAfter="2014-07-21T12:47:08Z"
  Recipient="http://simplesamlphp.example.com/simplesamlphp/module.php/saml/sp/saml2-acs.php/default-sp"
  InResponseTo="_3cfa896ab05730ac81f413e1e13cc42aa529eceea1"/>
<saml:Conditions NotBefore="2014-07-21T11:46:08Z"
  NotOnOrAfter="2014-07-21T12:48:08Z">
There is a time tolerance of 60 seconds in <Conditions>
  • Force UTF-8: Activate to force UTF-8 decoding of values in SAML attributes. If set to 0, the value from the session is directly copied into SAML attribute.
Signature

These options override service signature options (see SAML service configuration).

  • Sign SSO message: sign SSO message
  • Check SSO message signature: check SSO message signature
  • Sign SLO message: sign SLO message
  • Check SLO message signature: check SLO message signature
Security
  • Encryption mode: set the encryption mode for this IDP (None, NameID or Assertion).
  • Enable use of IDP initiated URL: set to On to enable IDP Initiated URL on this SP.
The IDP Initiated URL is the SSO SAML URL with GET parameters:
  • IDPInitiated: 1
  • One of:
    • sp: SP entity ID
    • spConfKey: SP configuration key

For example: http://auth.example.com/saml/singleSignOn?IDPInitiated=1&spConfKey=simplesamlphp

installdeb.html000066400000000000000000000275201325274564300333210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:installdeb

Table of Contents

  • Organization
  • Get the packages
    • Official repository
    • LL::NG repository
    • Manual download
  • Package GPG signature
  • Install packages
    • With apt
    • With dpkg
  • First configuration steps
    • Change default DNS domain
    • Reload virtual host
    • Upgrade
    • DNS
  • File location
  • Build your packages

Installation on Debian/Ubuntu with packages

Organization

LemonLDAP::NG provides these packages:

  • lemonldap-ng: metapackage, contains no file but dependencies on other packages
  • lemonldap-ng-doc: contains HTML documentation and project docs (README, etc.)
  • lemonldap-ng-fastcgi-server: LL::NG FastCGI server (for Nginx)
  • lemonldap-ng-fr-doc: French translation for HTML documentation
  • liblemonldap-ng-common-perl: configuration and common files
  • liblemonldap-ng-handler-perl: Handler files
  • liblemonldap-ng-manager-perl: Manager files
  • liblemonldap-ng-portal-perl: Portal files

Get the packages

Official repository

If you run Debian testing or unstable, the packages are directly installable:

apt-cache search lemonldap-ng
Packages from Debian repository may not be up to date. Prefer then the other solutions (see below).

LL::NG repository

You can add this repository to have recent packages:

vi /etc/apt/sources.list.d/lemonldap-ng.list
# LemonLDAP::NG repository
deb     https://lemonldap-ng.org/deb stable main
deb-src https://lemonldap-ng.org/deb stable main
  • Use the oldstable repository to get packages from previous major version
  • Use the testing repository to get packages from next major version
  • Use the 1.9 repository to avoid upgrade to next major version

You may need to install this package to access HTTPS repositories:

apt install apt-transport-https

Manual download

Packages are available on the Download page.

Package GPG signature

The GPG key can be downloaded here: rpm-gpg-key-ow2

Install it to trust packages:

wget -O - https://lemonldap-ng.org/_media/rpm-gpg-key-ow2 | apt-key add -

Update cache:

apt update

Install packages

With apt

apt install lemonldap-ng

With dpkg

Before installing the packages, install dependencies.

Then:

dpkg -i liblemonldap-ng-* lemonldap-ng*

First configuration steps

Change default DNS domain

By default, DNS domain is example.com. You can change it quick with a sed command. For example, we change it to ow2.org:

sed -i 's/example\.com/ow2.org/g' /etc/lemonldap-ng/* /var/lib/lemonldap-ng/conf/lmConf-1.js /var/lib/lemonldap-ng/test/index.pl

Reload virtual host

To allow the manager to reload the configuration, register the reload virtual host name in the hosts of the server:

echo "127.0.0.1 reload.example.com" >> /etc/hosts
Adapt the reload virtual host name to the domain you configured.

Upgrade

If you upgraded LL::NG, check all upgrade notes.

DNS

Configure your DNS server to resolve names with your server IP.

For tests with example.com, launch the following :
cat /etc/lemonldap-ng/for_etc_hosts >> /etc/hosts

Follow the next steps

File location

  • Configuration is in /etc/lemonldap-ng
  • LemonLDAP::NG configuration (edited by the Manager) is in /var/lib/lemonldap-ng/conf/
  • All Perl modules are in the VENDOR perl directory (/usr/share/perl5/)
  • All Perl scripts/pages are in /var/lib/lemonldap-ng/
  • All lemonldap-ng tools are in /usr/share/lemonldap-ng/bin/
  • All static content (examples, CSS, images, etc.) is in /usr/share/lemonldap-ng/
  • Apache configuration files are in /etc/lemonldap-ng and linked in /etc/apache2/sites-available

Build your packages

You can also get the LemonLDAP::NG archive and make the package yourself:

tar xzf lemonldap-ng-*.tar.gz
cd lemonldap-ng-*
make debian-packages
installrpm.html000066400000000000000000000311421325274564300333600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:installrpm

Table of Contents

  • Organization
  • Get the packages
    • YUM repository
    • Manual download
  • Package GPG signature
  • Install packages
    • With YUM
    • With RPM
  • First configuration steps
    • Change default DNS domain
    • Reload virtual host
    • Upgrade
    • DNS
  • File location
  • Build your packages

Installation on Red Hat/CentOS

LL::NG requires at least Red Hat/CentOS 7

Organization

LemonLDAP::NG provides packages for Red Hat/Centos 7:

  • lemonldap-ng: metapackage, contains no file but dependencies on other packages
  • lemonldap-ng-doc: contains HTML documentation and project docs (README, etc.)
  • lemonldap-ng-fr-doc: French translation for documentation
  • lemonldap-ng-conf: contains default configuration (DNS domain: example.com)
  • lemonldap-ng-test: contains sample CGI test page
  • lemonldap-ng-handler: contains Apache Handler implementation (agent)
  • lemonldap-ng-manager: contains administration interface and session explorer
  • lemonldap-ng-portal: contains authentication portal and menu
  • lemonldap-ng-fastcgi-server: FastCGI server needed to use Nginx
  • perl-Lemonldap-NG-Common: CPAN - Shared modules
  • perl-Lemonldap-NG-Handler: CPAN - Handler modules
  • perl-Lemonldap-NG-Manager: CPAN - Manager modules
  • perl-Lemonldap-NG-Portal: CPAN - Portal modules

Get the packages

YUM repository

You can add this YUM repository to get recent packages:

vi /etc/yum.repos.d/lemonldap-ng.repo
[lemonldap-ng]
name=LemonLDAP::NG packages
baseurl=https://lemonldap-ng.org/redhat/stable/$releasever/noarch
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OW2
Replace stable by 1.9 to avoid upgrade to next major version

You may also need some extras packages, available here:

[lemonldap-ng-extras]
name=LemonLDAP::NG extra packages
baseurl=https://lemonldap-ng.org/redhat/extras/$releasever
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OW2

Run this to update packages cache:

yum update
You must also install the EPEL repository for non-core dependencies. See prerequisites and dependencies chapter for more.

Manual download

RPMs are available on the Download page.

Package GPG signature

The GPG key can be downloaded here: rpm-gpg-key-ow2

Install it to trust RPMs:

rpm --import rpm-gpg-key-ow2

Install packages

With YUM

If the packages are stored in a yum repository:

yum install lemonldap-ng

You can also use yum on local RPMs file:

yum localinstall lemonldap-ng-* perl-Lemonldap-NG-*

With RPM

Before installing the packages, install all dependencies.

You have then to install all the downloaded packages:

rpm -Uvh lemonldap-ng-* perl-Lemonldap-NG-*
You can choose to install only one component by choosing the package lemonldap-ng-portal, lemonldap-ng-handler or lemonldap-ng-manager.

Install the package lemonldap-ng-conf on all server which contains one of those packages.

First configuration steps

Change default DNS domain

By default, DNS domain is example.com. You can change it quick with a sed command. For example, we change it to ow2.org:

sed -i 's/example\.com/ow2.org/g' /etc/lemonldap-ng/* /var/lib/lemonldap-ng/conf/lmConf-1.js /var/lib/lemonldap-ng/test/index.pl

Reload virtual host

To allow the manager to reload the configuration, register the reload virtual host name in the hosts of the server:

echo "127.0.0.1 reload.example.com" >> /etc/hosts
Adapt the reload virtual host name to the domain you configured.

Upgrade

If you upgraded LL::NG, check all upgrade notes.

DNS

Configure your DNS server to resolve names with your server IP.

For tests with example.com, launch the following :
cat /etc/lemonldap-ng/for_etc_hosts >> /etc/hosts

Follow the next steps

File location

  • Configuration is in /etc/lemonldap-ng
  • LemonLDAP::NG configuration (edited by the Manager) is in /var/lib/lemonldap-ng/conf/
  • All Perl modules are in the VENDOR perl directory
  • All Perl scripts/pages are in /var/lib/lemonldap-ng/
  • All static content (examples, CSS, images, etc.) is in /usr/share/lemonldap-ng/

Build your packages

If you need it, you can rebuild RPMs:

  • Install rpm-build package
  • Install all build dependencies (see BuildRequires in lemonldap-ng.spec)
  • Put LemonLDAP::NG tarball in %_topdir/SOURCES
  • Edit ~/.rpmmacros and set your build parameters:
%_topdir /home/user/build
%dist .el7
%rhel 7
  • Go to %_topdir
  • Build:
rpmbuild -ta SOURCES/lemonldap-ng-VERSION.tar.gz
installsles.html000066400000000000000000000514411325274564300335340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:installsles

Table of Contents

  • Organization
  • Get the packages
    • Repositories
    • Manual download
  • Package GPG signature
  • Install packages
    • With ZYPPER
    • With RPM
  • First configuration steps
    • Enable Apache extensions
    • Change default DNS domain
    • Reload virtual host
    • Upgrade
    • DNS
  • File location
  • Build your packages

Installation on Suse Linux

LL::NG requires at least SLES 12 SP1 or equivalent

Organization

LemonLDAP::NG provides packages for SLES:

  • lemonldap-ng: metapackage, contains no file but dependencies on other packages
  • lemonldap-ng-doc: contains HTML documentation and project docs (README, etc.)
  • lemonldap-ng-fr-doc: French translation for documentation
  • lemonldap-ng-conf: contains default configuration (DNS domain: example.com)
  • lemonldap-ng-test: contains sample CGI test page
  • lemonldap-ng-handler: contains Apache Handler implementation (agent)
  • lemonldap-ng-manager: contains administration interface and session explorer
  • lemonldap-ng-portal: contains authentication portal and menu
  • lemonldap-ng-fastcgi-server: FastCGI server needed to use Nginx
  • perl-Lemonldap-NG-Common: CPAN - Shared modules
  • perl-Lemonldap-NG-Handler: CPAN - Handler modules
  • perl-Lemonldap-NG-Manager: CPAN - Manager modules
  • perl-Lemonldap-NG-Portal: CPAN - Portal modules

Get the packages

Repositories

This manual only refers to SLES 12 SP1. Installation may work on other platforms, with no guarantee.

Different repositories are necessary for LemonLDAP::NG dependencies:

  • Suse official repositories
  • 2 repositories on OpenSuse Build Service
  • Additional packages available on repository.linagora.org or lemonldap-ng.org
  • Suse SDK repository is advised for building packages (yast2 → Software → Software Repositories → Add –> Extensions and modules from Registration Server)

First, make sure the exploitation system is up to date:

zypper update

You can add the OpenSuse Build Service repositories with the following commands:

zypper addrepo http://download.opensuse.org/distribution/leap/42.1/repo/oss/suse/ leap42
zypper addrepo http://download.opensuse.org/repositories/devel:languages:perl/SLE_12/devel:languages:perl.repo
zypper refresh

Accept both signing keys each time.

You can add the additional dependency repository *and* the LemonLDAP::NG repository with either commands:

zypper addrepo http://lemonldap-ng.org/sles12 lemonldap-sles12-repository
zypper refresh

or

zypper addrepo http://repository.linagora.org/lemonldap-sles12-repository lemonldap-sles12-repository
zypper refresh
Only packages on SLES 12 SP1 are tested for now.

Manual download

RPMs are available on the Download page.

Package GPG signature

The GPG key can be downloaded here: rpm-gpg-key-ow2

Install it to trust RPMs:

rpm --import rpm-gpg-key-ow2

Install packages

With ZYPPER

If the packages are stored in a repository:

zypper install lemonldap-ng
59 new packages to install.
Total download size: 13.5 MiB. Already cached : 0 B. After operation, 30.7 MiB of supplementary disk space will be used.
Continue ? [y/n/? print all options] (y):

You can also use zypper on local RPMs file:

zypper install lemonldap-ng-* perl-Lemonldap-NG-*

With RPM

Before installing the packages, install all dependencies: (you need to get dependencies from previous repositories)

zypper install apache2 apache2-mod_perl apache2-mod_fcgid perl-ldap perl-XML-SAX perl-XML-NamespaceSupport perl-XML-Simple perl-XML-LibXML perl-Config-IniFiles perl-Digest-HMAC perl-Crypt-OpenSSL-RSA perl-Authen-SASL perl-Unicode-String gd perl-Regexp-Assemble perl-Authen-Captcha perl-Cache-Cache perl-Apache-Session perl-CGI-Session perl-IO-String perl-MIME-Lite perl-SOAP-Lite perl-XML-LibXSLT perl-String-Random perl-Email-Date-Format perl-Crypt-Rijndael perl-HTML-Template perl-JSON perl-Crypt-OpenSSL-X509 perl-Crypt-DES perl-Class-Inspector perl-Test-MockObject perl-Clone perl-Net-CIDR-Lite perl-ExtUtils-MakeMaker perl-CGI perl-CGI-Session perl-HTML-Template perl-SOAP-Lite perl-IPC-ShareLite perl-Error perl-HTML-Parser perl-libwww-perl perl-DBI perl-Cache-Memcached perl-Class-ErrorHandler perl-Convert-PEM perl-Crypt-DES_EDE3 perl-Digest-SHA perl-Env perl-Mouse perl-String-CRC32 perl-Plack perl-Regexp-Common perl-Crypt-OpenSSL-Bignum perl-FCGI-ProcManager

You have then to install all the downloaded packages:

rpm -Uvh lemonldap-ng-* perl-Lemonldap-NG-*
You can choose to install only one component by choosing the package lemonldap-ng-portal, lemonldap-ng-handler or lemonldap-ng-manager.

Install the package lemonldap-ng-conf on all server which contains one of those packages.

First configuration steps

Enable Apache extensions

These extensions are activated by default on Apache at LemonLDAP install:

a2enmod perl
a2enmod headers
a2enmod mod_fcgid
a2enmod ssl
a2enmod rewrite
a2enmod proxy
a2enmod proxy_http

If you decide to use SSL, you should also activate the appopriate flag:

sed -i 's/^APACHE_SERVER_FLAGS=.*/APACHE_SERVER_FLAGS="SSL"/' /etc/sysconfig/apache2

Change default DNS domain

By default, DNS domain is example.com. You can change it quick with a sed command. For example, we change it to ow2.org:

sed -i 's/example\.com/ow2.org/g' /etc/lemonldap-ng/{*.conf,*.ini,for_etc_hosts} /var/lib/lemonldap-ng/conf/lmConf-1 /var/lib/lemonldap-ng/test/index.pl

Check Apache configuration and restart:

apachectl configtest
apachectl restart

Reload virtual host

To allow the manager to reload the configuration, register the reload virtual host name in the hosts of the server:

echo "127.0.0.1 reload.example.com" >> /etc/hosts
Adapt the reload virtual host name to the domain you configured.

Upgrade

If you upgraded LL::NG, check all upgrade notes.

For apache configuration, you may have to remove the old symbolic link, if not done by the RPM:

rm -f /etc/apache2/vhosts.d/z-lemonldap-ng.conf

Your old Apache configuration should have been saved, you need to port your specificities in new Apache configuration files:

vi /etc/lemonldap-ng/apache2.conf.rpmsave

The upgrade process will also have migrate old configuration files into /etc/lemonldap-ng/lemonldap-ng.ini. This includes the application list which is now set in the applicationList parameter from [portal] section, for example:

[portal]
applicationList={ 'Menu' => { type => 'category', 'Example' => { type => 'category', 'test1' => { type => 'application', options => { name => 'Application Test 1', uri => 'http://test1.example.com/', description => 'A simple application displaying authenticated user', logo => 'wheels.png', display => 'auto',  }, },'test2' => { type => 'application', options => { name => 'Application Test 2', uri => 'http://test2.example.com/', description => 'The same simple application displaying authenticated user', logo => 'wheels.png', display => 'auto',  }, }, },'Administration' => { type => 'category', 'manager' => { type => 'application', options => { name => 'WebSSO Manager', uri => 'http://manager.example.com/', description => 'Configure LemonLDAP::NG WebSSO', logo => 'tools.png', display => 'on',  }, },'sessions' => { type => 'application', options => { name => 'Sessions explorer', uri => 'http://manager.example.com/sessions.pl', description => 'Explore WebSSO sessions', logo => 'tools.png', display => 'on',  }, }, },'Documentation' => { type => 'category', 'localdoc' => { type => 'application', options => { name => 'Local documentation', uri => 'http://manager.example.com/doc/', description => 'Documentation supplied with LemonLDAP::NG', logo => 'docs.png', display => 'on',  }, },'officialwebsite' => { type => 'application', options => { name => 'Offical Website', uri => 'http://wiki.lemonldap.objectweb.org/xwiki/bin/view/NG/Presentation', description => 'Official LemonLDAP::NG Website', logo => 'web.png', display => 'on',  }, }, }, }, }
You should now use the Manager to configure all applications and categories, and then comment or remove the applicationList parameter from /etc/lemonldap-ng/lemonldap-ng.ini.

DNS

Configure your DNS server to resolve names with your server IP.

For tests with example.com, launch the following :
cat /etc/lemonldap-ng/for_etc_hosts >> /etc/hosts

Follow the next steps

File location

  • Configuration is in /etc/lemonldap-ng
  • LemonLDAP::NG configuration (edited by the Manager) is in /var/lib/lemonldap-ng/conf/
  • All Perl modules are in the VENDOR perl directory
  • All Perl scripts/pages are in /var/lib/lemonldap-ng/
  • All static content (examples, CSS, images, etc.) is in /usr/share/lemonldap-ng/

Build your packages

If you need it, you can rebuild RPMs:

  • Install rpm-build package
  • Get the lemonldap source package from repository:
zypper source-install lemonldap-ng
cd /usr/src/packages/
ls SPECS/ SOURCES/
  • Install all build dependencies (see BuildRequires in lemonldap-ng.spec)
  • Build:
rpmbuild -ba SPECS/lemonldap-ng.spec

Alternatively, you can use the automatic script “create-lemonldap-packages.sh”, available in rpm-sles directory in the lemonldap svn repository. The automatic script can also generate intermediate dependencies. See README file in the same directory for more information.

installtarball.html000066400000000000000000000247661325274564300342210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:installtarball

Table of Contents

  • Get the tarball
  • Build the tarball from SVN
  • Extraction
  • Installation
  • Install cron jobs
  • DNS

Installation from the tarball

Get the tarball

Get the tarball from download page. You can also find on this page the SVN tarball if you want to test latest features.

The content of the SVN tarball is not the same as the official tarball. Please see the next chapter to learn how build an official tarball from SVN files.

Build the tarball from SVN

Either checkout or export the SVN repository, or extract the SVN tarball to get the SVN files on your disk.

Then go to trunk directory:

cd trunk

And run the “dist” target:

make dist

The generated tarball is in the current directory.

Extraction

Just run the tar command:

tar zxvf lemonldap-ng-*.tar.gz

Installation

First check and install the prerequisites.

For full install:

cd lemonldap-ng-*
make
make configure
make test
sudo make install PROD=yes
PROD=yes makes web interface use minified versions of CSS and JS files.

You can modify location of default storage configuration file in configure target:

sudo make configure STORAGECONFFILE=/etc/lemonldap-ng/lemonldap-ng.ini

You can choose other Makefile targets:

  • Perl libraries install :
    • install_libs (all Perl libraries)
    • install_portal_libs
    • install_manager_libs
    • install_handler_libs
  • Binaries install :
    • install_bin (/usr/local/lemonldap-ng/bin)
  • FastCGI server install (required for Nginx)
    • install_fastcgi_server (/usr/local/lemonldap-ng/sbin)
  • Web sites install :
    • install_site (all sites including install_doc_site)
    • install_portal_site (/usr/local/lemonldap-ng/htdocs/portal)
    • install_manager_site (/usr/local/lemonldap-ng/htdocs/manager)
    • install_handler_site (/usr/local/lemonldap-ng/handler)
  • Documentation install :
    • install_doc_site (/usr/local/lemonldap-ng/htdocs/doc)
    • install_fr_doc_site (/usr/local/lemonldap-ng/htdocs/fr-doc)
    • install_examples_site (/usr/local/lemonldap-ng/examples)

You can also pass parameters to the make install command, with this syntax:

sudo make install PARAM=VALUE PARAM=VALUE ...

Available parameters are:

  • ERASECONFIG: set to 0 if you want to keep your configuration files (default: 1)
  • DESTDIR: only for packaging, install the product in a jailroot (default: “”)
  • PREFIX: installation directory (default: /usr/local)
  • STORAGECONFFILE: location of default storage configuration file (default: /usr/local/lemonldap-ng/etc/lemonldap-ng.ini)
  • CRONDIR: Cronfile directory (default: $PREFIX/etc/lemonldap-ng/cron.d)
  • APACHEUSER: user running Apache
  • APACHEGROUP: group running Apache
  • DNSDOMAIN: Main DNS domain (default: example.com)
  • APACHEVERSION: Apache major version (default: 2)
  • VHOSTLISTEN: how listen parameter is configured for virtual hosts in Apache (default: *:80)
  • PROD: use minified JS and CSS files
  • USEDEBIANLIBS: use Debian packaged JS and CSS files (Note that this options isn't yet usable since Debian provides a too old AngularJS for now: LLNG manager needs at least version 1.4.0)
  • USEEXTERNALLIBS: use files from public CDN
For Debian/Ubuntu with Apache2, you can use:
make debian-install-for-apache
make ubuntu-install-for-apache

And with Nginx:

make debian-install-for-nginx
make ubuntu-install-for-nginx

See also Debian/Ubuntu installation documentation.

Install cron jobs

LL::NG use cron jobs to:

  • purge old sessions
  • clean Handler cache

To install them on system:

sudo ln -s /usr/local/lemonldap-ng/etc/cron.d/* /etc/cron.d/

DNS

Configure your DNS server to resolve names with your server IP.

For tests with the configured domain, launch the following :
cat /usr/local/lemonldap-ng/etc/lemonldap-ng/for_etc_hosts >> /etc/hosts

Follow the next steps.

issuerdbget.html000066400000000000000000000141731325274564300335200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:issuerdbget

Get parameters Provider

Presentation

For application not managing other provider protocols (CAS, OpenID Connect, SAML,…) it is possible to configure LL::NG as a provider of GET parameters:

  • An application can call LL::NG portal with a redirection url, such as http://auth.example.com/get/login?url=base64(application_url)
  • When computing redirection, LL::NG portal will transmit any GET parameter you have configured for this application. (session id for example)
Passing such sensitive information can be dangerous. Using other well-known secured protocols are advised.

There is also the possibility to trigger a logout action by passing the return url , such as http://auth.example.com/get/logout?url=base64(return_url)

Configuration

In the Manager, go in General Parameters » Issuer modules » GET and configure:

  • Activation: set to On.
  • Path: keep ^/get/ unless you have change Apache portal configuration file.
  • Use rule: a rule to allow user to use this module, set to 1 to always allow.
For example, to allow only users with a strong authentication level:
$authenticationLevel > 2
Rewrite rules must have been activated in Apache portal configuration or in Nginx portal configuration.

Then go in Get parameters to define variables to transmit:

  • Define a new virtual host,
  • Declare all get parameters you need. You have access to any variable or macro (but no perl expression).

For example:

"test1.example.com" => {
    "id" => "_session_id",
}
In the previous example, _session_id is quite sensitive, thus it is encouraged that the application revalidate _session_id using getCookie() SOAP call to avoid some security problems
If host is not already registered in virtual hosts, you need to declare it in trusted domains to allow redirection
jsonfileconfbackend.html000066400000000000000000000072041325274564300351640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:jsonfileconfbackend

JSON File configuration backend

This works like File backend, except that data are serialized in JSON.

This configuration storage can be shared between different hosts using:
  • SOAP configuration backend proxy
  • any files sharing system (NFS, NAS, SAN,…)

Configuration

You just have to configure a directory writable by Apache user and set it in [configuration] section in your lemonldap-ng.ini file:

[configuration]
type  = JSONFile
dirName = /var/lib/lemonldap-ng/conf
kerberos.html000066400000000000000000000665601325274564300330230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:kerberos

Table of Contents

  • Presentation
  • Prerequisites
    • Example values
    • Server time
    • DNS
    • AD accounts
    • Web browser configuration
      • Firefox
      • Internet Explorer
    • Apache Kerberos module installation
  • Single LL::NG Server / Single AD domain
    • Client Kerberos configuration
    • Obtain keytab file
    • Configuration of LemonLDAP::NG
    • Configuration of portal virtual host
    • Redirection script
  • LL::NG Cluster / Single AD domain
    • Client Kerberos configuration
    • Obtain keytab file
    • Configuration of LemonLDAP::NG
    • Configuration of portal virtual host
  • LL::NG Cluster / Two AD domains
    • Client Kerberos configuration
    • Obtain keytab file
    • Configuration of LemonLDAP::NG
    • Configuration of portal virtual host
  • Other resources

Kerberos

A backport of 2.0 new Kerberos authentication module has been done for 1.9.14. See Kerberos to see how to use it.

Presentation

This documentation will explain how to use Active Directory as Kerberos server, and provide transparent authentication to AD domain users to LL::NG.

We will present several architectures:

  • Single LL::NG server linked to one AD domain
  • LL::NG cluster linked to one AD domain
  • LL::NG cluster linked to two AD domains

Prerequisites

Example values

We will use the following values in our examples

  • EXAMPLE.COM: First AD domain
  • ACME.COM: Second AD domain
  • auth.example.com: DNS of the LL::NG portal
  • authpwd.example.com: DNS of the LL::NG portal (to failback to a form based authentication)
  • node1.example.com: DNS of the first LL::NG portal server (in cluster mode)
  • node2.example.com: DNS of the second LL::NG portal server (in cluster mode)
  • ad.example.com: DNS of First Active Directory
  • ad.acme.com: DNS of Second Active Directory
  • KERB_AUTH: AD account to generate the keytab for LL::NG server (in single mode)
  • KERB_NODE1: AD account to generate the keytab for the first LL::NG server (in cluster mode)
  • KERB_NODE2: AD account to generate the keytab for the second LL::NG server (in cluster mode)

Server time

It is mandatory that LL::NG servers and AD servers have the same time. It is recommended to use NTP to do this.

DNS

All names must be registered in the DNS server (which is Active Directory). The reverse DNS should also work for all the names.

AD accounts

It is recommended to create an AD account for each LL::NG server. Each account will hold the Service Principal Name (SPN) of the LL::NG server.

It should be possible to have the same account for all SPN, but this may require some manipulations on AD (command setspn) that are not documented here.

Web browser configuration

Firefox

Type about:config in a tab and search for trusted. Then edit the property network.negotiate-auth.trusted-uris and set value example.com.

Internet Explorer

Add https://auth.example.com as trusted site.

Check into security parameters that Kerberos authentication is allowed.

Apache Kerberos module installation

On CentOS/RHEL:

yum install mod_auth_kerb

On Debian/Ubuntu:

apt-get install libapache2-mod-auth-kerb

The module must be loaded by Apache (LoadModule directive).

Single LL::NG Server / Single AD domain

Client Kerberos configuration

On LL::NG server, edit /etc/krb5.conf:

[libdefaults]
 default_realm = EXAMPLE.COM
 dns_lookup_kdc = false
 dns_lookup_realm = no
 ticket_lifetime = 24h
 forwardable = yes
 renewable = true
 
[realms]
 EXAMPLE.COM = {
  kdc = ad.example.com
  admin_server = ad.example.com
 }
 
[domain_realm]
 .example.com = EXAMPLE.COM
 example.com = EXAMPLE.COM

You can check that Kerberos is working by trying to get a ticket for a user of the domain (for example coudot):

kinit coudot@EXAMPLE.COM

You should be prompted to enter password. Then list the tickets:

klist -e

You should see a krbtgt ticket:

Valid starting     Expires            Service principal
06/04/15 15:43:24  06/05/15 01:43:29  krbtgt/EXAMPLE.COM@EXAMPLE.COM
        renew until 06/05/15 15:43:24, Etype (skey, tkt): aes256-cts-hmac-sha1-96, aes256-cts-hmac-sha1-96

You can then close the Kerberos session:

kdestroy

Obtain keytab file

You have to run this command on Active Directory:

ktpass -princ HTTP/auth.example.com@EXAMPLE.COM -mapuser KERB_AUTH@EXAMPLE.COM -crypto DES-CBC-MD5 -ptype KRB5_NT_PRINCIPAL -mapOp set -pass <PASSWORD> -out c:\auth.keytab
The values passed in -crypto and -ptype depend on the Active Directory version and the windows version of the workstations. You can for example use RC4-HMAC-NT as crypto protocol if DES is not supported by workstations (this the case by default for Window 8 for example).

The file auth.keytab should then be copied (with a secure media) to the Linux server (for example in /etc/lemonldap-ng).

Change rights on keytab file:

chown apache /etc/lemonldap-ng/auth.keytab
chmod 600 /etc/lemonldap-ng/auth.keytab

You can check the validity of the keytab file by trying to request a service ticket, and compare the result with the keytab content.

Open a Kerberos session (like done in the previous step):

kinit coudot@example.com

Request a service ticket:

kvno HTTP/auth.example.com@EXAMPLE.COM

The result of the command should be:

HTTP/auth.example.com@EXAMPLE.COM: kvno = 3

Read the service ticket:

klist -e

You should see this kind of ticket:

06/04/15 16:28:49  06/05/15 02:28:11  HTTP/auth.example.com@EXAMPLE.COM
        renew until 06/05/15 16:28:07, Etype (skey, tkt): arcfour-hmac, arcfour-hmac

You can close the Kerberos session:

kdestroy

Now you can compare the above result with the same request done trough the keytab file:

klist -e -k -t /etc/lemonldap-ng/auth.keytab

The result of the command should be:

Keytab name: FILE:/etc/lemonldap-ng/auth.keytab
KVNO Timestamp         Principal
---- ----------------- --------------------------------------------------------
   3 01/01/70 01:00:00 HTTP/auth.example.com@EXAMPLE.COM (arcfour-hmac)

The important things to check are:

  • KVNO must be the same
  • Principal names must be the same
  • Encryption types must be the same

Configuration of LemonLDAP::NG

See Apache authentication module configuration.

Configuration of portal virtual host

First, copy the current portal virtual host definition into a new one. Use authpwd server name for this virtual host:

<VirtualHost *>
    ServerName authpwd.example.com
 
    ...
 
</VirtualHost>

This virtual host will be used by clients that fail to use the Kerberos protocol.

Then, modify the main portal virtual host to load the Apache Kerberos authentication module :

<VirtualHost *>
  ServerName auth.example.com
 
  DocumentRoot /var/lib/lemonldap-ng/portal/
 
  <Directory /var/lib/lemonldap-ng/portal/>
    Order allow,deny
    Allow from all
    Options +ExecCGI +FollowSymLinks
  </Directory>
 
  ErrorDocument 401 /login.pl
  <LocationMatch ^/(?!login.pl)>
    <IfModule auth_kerb_module>
      AuthType Kerberos
      KrbMethodNegotiate On
      KrbMethodK5Passwd Off
      KrbAuthRealms EXAMPLE.COM
      Krb5KeyTab /etc/lemonldap-ng/auth.keytab
      KrbVerifyKDC Off
      KrbServiceName HTTP/auth.example.com
      require valid-user
    </IfModule>
  </LocationMatch>
 
</VirtualHost>

Redirection script

Create a redirection script, called login.pl:

vi /var/lib/lemonldap-ng/portal/login.pl
#!/usr/bin/perl
use CGI ':cgi-lib';
use strict;
use CGI::Carp 'fatalsToBrowser';
my $uri = $ENV{"REQUEST_URI"};
print CGI::header(-Refresh => '0; URL=https://authpwd.example.com'.$uri);
exit(0);
The redirection script is needed if you use a failaback authentication. If not, you can just keep a single virtual host (the authentication will fail if Kerberos negotiation do not succeed).

LL::NG Cluster / Single AD domain

Client Kerberos configuration

The client Kerberos configuration is the same as a single LL::NG server.

Obtain keytab file

You need to get a keytab for each LL::NG node.

Commands on Active Directory will be:

ktpass -princ HTTP/node1.example.com@EXAMPLE.COM -mapuser KERB_NODE1@EXAMPLE.COM -crypto DES-CBC-MD5 -ptype KRB5_NT_PRINCIPAL -mapOp set -pass <PASSWORD> -out c:\authnode1.keytab
ktpass -princ HTTP/node2.example.com@EXAMPLE.COM -mapuser KERB_NODE2@EXAMPLE.COM -crypto DES-CBC-MD5 -ptype KRB5_NT_PRINCIPAL -mapOp set -pass <PASSWORD> -out c:\authnode2.keytab

Copy the generated keytab on each node (rename it as auth.keytab to have the same Apache configuration on each node).

Change rights on keytab file:

chown apache /etc/lemonldap-ng/auth.keytab
chmod 600 /etc/lemonldap-ng/auth.keytab
You can do the same check for the keytab as with the single LL::NG server. Just use node1.example.com and node2.example.com instead of auth.example.com.

Configuration of LemonLDAP::NG

The configuration is the same as a single LL::NG server.

Configuration of portal virtual host

The only change in Apache configuration is in the KrbServiceName, it should be set to Any:

    KrbServiceName Any

LL::NG Cluster / Two AD domains

Client Kerberos configuration

The two domains must be defined in /etc/krb5.conf:

[libdefaults]
 default_realm = EXAMPLE.COM
 dns_lookup_kdc = false
 dns_lookup_realm = no
 ticket_lifetime = 24h
 forwardable = yes
 renewable = true
 
[realms]
 EXAMPLE.COM = {
  kdc = ad.example.com
  admin_server = ad.example.com
  default_domain = EXAMPLE.COM
 }
 ACME.COM = {
  kdc = ad.acme.com
  admin_server = ad.acme.com
  }
 
[domain_realm]
 .example.com = EXAMPLE.COM
 example.com = EXAMPLE.COM
 .acme.com = ACME.COM
 acme.com = ACME.COM

You should then be able to open a Kerberos session on each domain:

kinit coudot@EXAMPLE.COM
klist -e
kdestroy
kinit coudot@ACME.COM
klist -e
kdestroy

Obtain keytab file

You need to obtain a keytab for each node on each domain. This means the ktpass commands should be run on both AD.

Then you will have 2 keytab files for each node, for example:

  • node1-example.keytab
  • node1-acme.keytab

You need to concatenate the keytab files, thanks to ktutil command:

ktutil
ktutil: read_kt node1-example.keytab
ktutil: read_kt node1-acme.keytab
ktutil: write_kt /etc/lemonldap-ng/auth.keytab
ktutil: quit

You can then remove the original keytab files and protect the final keytab file:

chown apache /etc/lemonldap-ng/auth.keytab
chmod 600 /etc/lemonldap-ng/auth.keytab

Configuration of LemonLDAP::NG

The configuration is the same as a single LL::NG server.

Configuration of portal virtual host

The configuration is the same as with a single AD domain.

Other resources

You can check these documentations to get more information:

  • http://modauthkerb.sourceforge.net/configure.html
  • http://www.grolmsnet.de/kerbtut/
ldapconfbackend.html000066400000000000000000000175261325274564300343030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:ldapconfbackend

Table of Contents

  • Presentation
  • Configuration
    • LDAP server
    • LemonLDAP::NG

LDAP configuration backend

Presentation

You can choose to store LemonLDAP::NG configuration in an LDAP directory.

Advantages:

  • Easy to share between servers with remote LDAP access
  • Easy to duplicate with LDAP synchronization services (like SyncRepl in OpenLDAP)
  • Security with SSL/TLS
  • Access control possible by creating one user for Manager (write) and another for portal and handlers (read)
  • Easy import/export through LDIF files

The configuration will be store under a specific branch, for example ou=conf,ou=applications,dc=example,dc=com.

Each configuration will be represented as an entry, which structural objectClass is by default applicationProcess. The configuration name is the same that files, so lmConf-1, lmConf-2, etc. This name is used in entry DN, for example cn=lmConf-1,ou=conf,ou=applications,dc=example,dc=com.

Then each parameter is one value of the attribute description, prefixed by its key. For example {ldapPort}389.

The LDIF view of such entry can be:

dn: cn=lmConf-1,ou=conf,ou=applications,dc=example,dc=com
objectClass: top
objectClass: applicationProcess
cn: lmConf-1
description: {globalStorage}'Apache::Session::File'
description: {cookieName}'lemonldap'
description: {whatToTrace}'$uid'
...

Configuration

LDAP server

Configuration objects use standard object class: applicationProcess. This objectClass allow attributes cn and description. If your LDAP server do not manage this objectClass, configure other objectclass and attributes (see below).

We advice to create a specific LDAP account with write access on configuration branch.

Next create the configuration branch where you want. Just remember its DN for LemonLDAP::NG configuration.

LemonLDAP::NG

Configure LDAP configuration backend in lemonldap-ng.ini, section [configuration]:

type = LDAP
ldapServer = ldap://localhost
ldapConfBase = ou=conf,ou=applications,dc=example,dc=com
ldapBindDN = cn=manager,dc=example,dc=com
ldapBindPassword = secret
ldapObjectClass = applicationProcess
ldapAttributeId = cn
ldapAttributeContent = description

Parameters:

  • ldapServer: LDAP URI of the server
  • ldapConfBase: DN of configuration branch
  • ldapBindDN: DN used to bind LDAP
  • ldapBindPassword: password used to bind LDAP
  • ldapObjectClass: structural objectclass of configuration entry (optional)
  • ldapAttributeId: RDN attribute of configuration entry (optional)
  • ldapAttributeContent: attribute used to store configuration values, must be multivalued (optional)
ldapminihowto.html000066400000000000000000000072241325274564300340550ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:ldapminihowto

Configure LemonLDAP::NG to use LDAP as main database

LL::NG use 2 internal databases to store its configuration and sessions.

Use LDAP for configuration

Steps:

  • Prepare the LDAP server and the LL::NG configuration file
  • Convert existing configuration
  • Restart all your Apache servers

Use LDAP for sessions

Steps:

  • Follow LDAP session backend doc
ldapsessionbackend.html000066400000000000000000000146531325274564300350370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:ldapsessionbackend

LDAP session backend

An Apache session module was created by LL::NG team to store sessions in an LDAP directory.

This module is not part of LL::NG distribution, and can be found on CPAN: Apache::Session::LDAP.
This module is also available on GitHub.

Sessions will be stored as LDAP entries, like this:

dn: cn=6fb7c4a170a04668771f03b0a4747f46,ou=sessions,dc=example,dc=com
objectClass: applicationProcess
cn: 6fb7c4a170a04668771f03b0a4747f46
description: [Base64 serialized data]

Setup

Go in the Manager and set the LDAP session module (Apache::Session::LDAP) in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

Required parameters
Name Comment Example
ldapServer URI of the server ldap://localhost
ldapConfBase DN of sessions branch ou=sessions,dc=example,dc=com
ldapBindDN Connection login cn=admin,dc=example,dc=password
ldapBindPassword Connection password secret
Optional parameters
Name Comment Default value
ldapObjectClass Objectclass of the entry applicationProcess
ldapAttributeId Attribute storing session ID cn
ldapAttributeContent Attribute storing session content description

Security

Restrict network access to the LDAP directory, and add specific ACL to session branch.

You can also use different user/password for your servers by overriding parameters globalStorage and globalStorageOptions in lemonldap-ng.ini file.

lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/000077500000000000000000000000001325274564300311315ustar00rootroot00000000000000exe/000077500000000000000000000000001325274564300316335ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/libcss.php.t.bootstrap3.css000066400000000000000000003037721325274564300363000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/exe@media screen{ a.interwiki{ background:transparent url(./../../lib/images/interwiki.png) 0 1px no-repeat; padding:1px 0 1px 16px; } a.iw_wp{ background-image:url(./../../lib/images/interwiki/wp.gif); } a.iw_wpfr{ background-image:url(./../../lib/images/interwiki/wpfr.gif); } a.iw_wpde{ background-image:url(./../../lib/images/interwiki/wpde.gif); } a.iw_wpes{ background-image:url(./../../lib/images/interwiki/wpes.gif); } a.iw_wppl{ background-image:url(./../../lib/images/interwiki/wppl.gif); } a.iw_wpjp{ background-image:url(./../../lib/images/interwiki/wpjp.gif); } a.iw_wpmeta{ background-image:url(./../../lib/images/interwiki/wpmeta.gif); } a.iw_doku{ background-image:url(./../../lib/images/interwiki/doku.gif); } a.iw_dokubug{ background-image:url(./../../lib/images/interwiki/dokubug.gif); } a.iw_amazon{ background-image:url(./../../lib/images/interwiki/amazon.gif); } a.iw_amazon_de{ background-image:url(./../../lib/images/interwiki/amazon.de.gif); } a.iw_amazon_uk{ background-image:url(./../../lib/images/interwiki/amazon.uk.gif); } a.iw_paypal{ background-image:url(./../../lib/images/interwiki/paypal.gif); } a.iw_phpfn{ background-image:url(./../../lib/images/interwiki/phpfn.gif); } a.iw_coral{ background-image:url(./../../lib/images/interwiki/coral.gif); } a.iw_sb{ background-image:url(./../../lib/images/interwiki/sb.gif); } a.iw_skype{ background-image:url(./../../lib/images/interwiki/skype.gif); } a.iw_user{ background-image:url(./../../lib/images/interwiki/user.png); } a.iw_callto{ background-image:url(./../../lib/images/interwiki/callto.gif); } .mediafile{ background:transparent url(./../../lib/images/fileicons/file.png) 0 1px no-repeat; padding-left:18px; padding-bottom:1px; } .mf_csv{ background-image:url(./../../lib/images/fileicons/csv.png); } .mf_deb{ background-image:url(./../../lib/images/fileicons/deb.png); } .mf_rb{ background-image:url(./../../lib/images/fileicons/rb.png); } .mf_cc{ background-image:url(./../../lib/images/fileicons/cc.png); } .mf_7z{ background-image:url(./../../lib/images/fileicons/7z.png); } .mf_sxc{ background-image:url(./../../lib/images/fileicons/sxc.png); } .mf_xlsx{ background-image:url(./../../lib/images/fileicons/xlsx.png); } .mf_sql{ background-image:url(./../../lib/images/fileicons/sql.png); } .mf_pl{ background-image:url(./../../lib/images/fileicons/pl.png); } .mf_mp4{ background-image:url(./../../lib/images/fileicons/mp4.png); } .mf_htm{ background-image:url(./../../lib/images/fileicons/htm.png); } .mf_zip{ background-image:url(./../../lib/images/fileicons/zip.png); } .mf_tgz{ background-image:url(./../../lib/images/fileicons/tgz.png); } .mf_webm{ background-image:url(./../../lib/images/fileicons/webm.png); } .mf_txt{ background-image:url(./../../lib/images/fileicons/txt.png); } .mf_swf{ background-image:url(./../../lib/images/fileicons/swf.png); } .mf_json{ background-image:url(./../../lib/images/fileicons/json.png); } .mf_css{ background-image:url(./../../lib/images/fileicons/css.png); } .mf_java{ background-image:url(./../../lib/images/fileicons/java.png); } .mf_lua{ background-image:url(./../../lib/images/fileicons/lua.png); } .mf_html{ background-image:url(./../../lib/images/fileicons/html.png); } .mf_ogg{ background-image:url(./../../lib/images/fileicons/ogg.png); } .mf_hpp{ background-image:url(./../../lib/images/fileicons/hpp.png); } .mf_bz2{ background-image:url(./../../lib/images/fileicons/bz2.png); } .mf_tar{ background-image:url(./../../lib/images/fileicons/tar.png); } .mf_ico{ background-image:url(./../../lib/images/fileicons/ico.png); } .mf_gif{ background-image:url(./../../lib/images/fileicons/gif.png); } .mf_conf{ background-image:url(./../../lib/images/fileicons/conf.png); } .mf_pptx{ background-image:url(./../../lib/images/fileicons/pptx.png); } .mf_diff{ background-image:url(./../../lib/images/fileicons/diff.png); } .mf_bash{ background-image:url(./../../lib/images/fileicons/bash.png); } .mf_sxd{ background-image:url(./../../lib/images/fileicons/sxd.png); } .mf_csh{ background-image:url(./../../lib/images/fileicons/csh.png); } .mf_xls{ background-image:url(./../../lib/images/fileicons/xls.png); } .mf_odi{ background-image:url(./../../lib/images/fileicons/odi.png); } .mf_asm{ background-image:url(./../../lib/images/fileicons/asm.png); } .mf_h{ background-image:url(./../../lib/images/fileicons/h.png); } .mf_jpeg{ background-image:url(./../../lib/images/fileicons/jpeg.png); } .mf_png{ background-image:url(./../../lib/images/fileicons/png.png); } .mf_odc{ background-image:url(./../../lib/images/fileicons/odc.png); } .mf_gz{ background-image:url(./../../lib/images/fileicons/gz.png); } .mf_js{ background-image:url(./../../lib/images/fileicons/js.png); } .mf_xml{ background-image:url(./../../lib/images/fileicons/xml.png); } .mf_odp{ background-image:url(./../../lib/images/fileicons/odp.png); } .mf_sxi{ background-image:url(./../../lib/images/fileicons/sxi.png); } .mf_odf{ background-image:url(./../../lib/images/fileicons/odf.png); } .mf_sh{ background-image:url(./../../lib/images/fileicons/sh.png); } .mf_php{ background-image:url(./../../lib/images/fileicons/php.png); } .mf_rpm{ background-image:url(./../../lib/images/fileicons/rpm.png); } .mf_ogv{ background-image:url(./../../lib/images/fileicons/ogv.png); } .mf_sxw{ background-image:url(./../../lib/images/fileicons/sxw.png); } .mf_py{ background-image:url(./../../lib/images/fileicons/py.png); } .mf_cs{ background-image:url(./../../lib/images/fileicons/cs.png); } .mf_odg{ background-image:url(./../../lib/images/fileicons/odg.png); } .mf_doc{ background-image:url(./../../lib/images/fileicons/doc.png); } .mf_rtf{ background-image:url(./../../lib/images/fileicons/rtf.png); } .mf_pas{ background-image:url(./../../lib/images/fileicons/pas.png); } .mf_wav{ background-image:url(./../../lib/images/fileicons/wav.png); } .mf_rar{ background-image:url(./../../lib/images/fileicons/rar.png); } .mf_ps{ background-image:url(./../../lib/images/fileicons/ps.png); } .mf_jpg{ background-image:url(./../../lib/images/fileicons/jpg.png); } .mf_ods{ background-image:url(./../../lib/images/fileicons/ods.png); } .mf_mp3{ background-image:url(./../../lib/images/fileicons/mp3.png); } .mf_pdf{ background-image:url(./../../lib/images/fileicons/pdf.png); } .mf_c{ background-image:url(./../../lib/images/fileicons/c.png); } .mf_odt{ background-image:url(./../../lib/images/fileicons/odt.png); } .mf_docx{ background-image:url(./../../lib/images/fileicons/docx.png); } .mf_ppt{ background-image:url(./../../lib/images/fileicons/ppt.png); } .mf_cpp{ background-image:url(./../../lib/images/fileicons/cpp.png); } } @media screen{ div.error,div.info,div.success,div.notify{ color:#000; background-repeat:no-repeat; background-position:8px 50%; border:1px solid; font-size:90%; margin:0 0 .5em; padding:.4em; padding-left:32px; overflow:hidden; border-radius:5px; } [dir=rtl] div.error,[dir=rtl] div.info,[dir=rtl] div.success,[dir=rtl] div.notify{ background-position:99% 50%; padding-left:.4em; padding-right:32px; } div.error{ background-color:#fcc; background-image:url(./../../lib/images/error.png); border-color:#ebb; } div.info{ background-color:#ccf; background-image:url(./../../lib/images/info.png); border-color:#bbe; } div.success{ background-color:#cfc; background-image:url(./../../lib/images/success.png); border-color:#beb; } div.notify{ background-color:#ffc; background-image:url(./../../lib/images/notify.png); border-color:#eeb; } .JSpopup,#link__wiz{ position:absolute; background-color:#fff; color:#000; z-index:20; overflow:hidden; } #link__wiz .ui-dialog-content{ padding-left:0; padding-right:0; } #media__popup_content button.button{ border:1px outset; } #media__popup_content button.selected{ border-style:inset; } .a11y{ position:absolute !important; left:-99999em !important; top:auto !important; width:1px !important; height:1px !important; overflow:hidden !important; } [dir=rtl] .a11y{ left:auto !important; right:-99999em !important; } .code .co0{ color:#666; font-style:italic; } .code .co4{ color:#c00; font-style:italic; } .code .es5{ color:#069; font-weight:bold; } .code .es6{ color:#093; font-weight:bold; } .code .kw2{ color:#000; font-weight:bold; } .code .kw5{ color:#008000; } .code .kw6{ color:#f08; font-weight:bold; } .code .me0{ color:#004000; } .code .nu0{ color:#c6c; } .code .re0{ color:#00f; } .code .re3{ color:#f33; font-weight:bold; } .code .re4{ color:#099; } .code .re5{ color:#603; } .code .sc-2{ color:#404040; } .code .sy3{ color:#000040; } .code .br0,.code .sy0{ color:#6c6; } .code .co1,.code .coMULTI,.code .sc-1{ color:#808080; font-style:italic; } .code .co2,.code .sy1{ color:#393; } .code .co3,.code .sy4{ color:#008080; } .code .es0,.code .es1,.code .esHARD{ color:#009; font-weight:bold; } .code .es2,.code .es3,.code .es4{ color:#609; font-weight:bold; } .code .kw1,.code .kw8{ color:#b1b100; } .code .kw10,.code .kw11,.code .kw12,.code .kw9{ color:#039; font-weight:bold; } .code .kw13,.code .kw14,.code .kw15,.code .kw16,.code .me1,.code .me2{ color:#060; } .code .kw3,.code .kw7,.code .sy2{ color:#006; } .code .kw4,.code .re2{ color:#933; } .code .re1,.code .st0,.code .st_h{ color:#f00; } .ui-helper-hidden{ display:none; } .ui-helper-hidden-accessible{ border:0; clip:rect(0 0 0 0); height:1px; margin:-1px; overflow:hidden; padding:0; position:absolute; width:1px; } .ui-helper-reset{ margin:0; padding:0; border:0; outline:0; line-height:1.3; text-decoration:none; font-size:100%; list-style:none; } .ui-helper-clearfix:before,.ui-helper-clearfix:after{ content:""; display:table; border-collapse:collapse; } .ui-helper-clearfix:after{ clear:both; } .ui-helper-clearfix{ min-height:0; } .ui-helper-zfix{ width:100%; height:100%; top:0; left:0; position:absolute; opacity:0; filter:Alpha(Opacity=0); } .ui-front{ z-index:100; } .ui-state-disabled{ cursor:default !important; } .ui-icon{ display:block; text-indent:-99999px; overflow:hidden; background-repeat:no-repeat; } .ui-widget-overlay{ position:fixed; top:0; left:0; width:100%; height:100%; } .ui-accordion .ui-accordion-header{ display:block; cursor:pointer; position:relative; margin:2px 0 0 0; padding:.5em .5em .5em .7em; min-height:0; font-size:100%; } .ui-accordion .ui-accordion-icons{ padding-left:2.2em; } .ui-accordion .ui-accordion-icons .ui-accordion-icons{ padding-left:2.2em; } .ui-accordion .ui-accordion-header .ui-accordion-header-icon{ position:absolute; left:.5em; top:50%; margin-top:-8px; } .ui-accordion .ui-accordion-content{ padding:1em 2.2em; border-top:0; overflow:auto; } .ui-autocomplete{ position:absolute; top:0; left:0; cursor:default; } .ui-button{ display:inline-block; position:relative; padding:0; line-height:normal; margin-right:.1em; cursor:pointer; vertical-align:middle; text-align:center; overflow:visible; } .ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{ text-decoration:none; } .ui-button-icon-only{ width:2.2em; } button.ui-button-icon-only{ width:2.4em; } .ui-button-icons-only{ width:3.4em; } button.ui-button-icons-only{ width:3.7em; } .ui-button .ui-button-text{ display:block; line-height:normal; } .ui-button-text-only .ui-button-text{ padding:.4em 1em; } .ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{ padding:.4em; text-indent:-9999999px; } .ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{ padding:.4em 1em .4em 2.1em; } .ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{ padding:.4em 2.1em .4em 1em; } .ui-button-text-icons .ui-button-text{ padding-left:2.1em; padding-right:2.1em; } input.ui-button{ padding:.4em 1em; } .ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{ position:absolute; top:50%; margin-top:-8px; } .ui-button-icon-only .ui-icon{ left:50%; margin-left:-8px; } .ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{ left:.5em; } .ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{ right:.5em; } .ui-buttonset{ margin-right:7px; } .ui-buttonset .ui-button{ margin-left:0; margin-right:-0.3em; } input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{ border:0; padding:0; } .ui-datepicker{ width:17em; padding:.2em .2em 0; display:none; } .ui-datepicker .ui-datepicker-header{ position:relative; padding:.2em 0; } .ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{ position:absolute; top:2px; width:1.8em; height:1.8em; } .ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{ top:1px; } .ui-datepicker .ui-datepicker-prev{ left:2px; } .ui-datepicker .ui-datepicker-next{ right:2px; } .ui-datepicker .ui-datepicker-prev-hover{ left:1px; } .ui-datepicker .ui-datepicker-next-hover{ right:1px; } .ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{ display:block; position:absolute; left:50%; margin-left:-8px; top:50%; margin-top:-8px; } .ui-datepicker .ui-datepicker-title{ margin:0 2.3em; line-height:1.8em; text-align:center; } .ui-datepicker .ui-datepicker-title select{ font-size:1em; margin:1px 0; } .ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{ width:49%; } .ui-datepicker table{ width:100%; font-size:.9em; border-collapse:collapse; margin:0 0 .4em; } .ui-datepicker th{ padding:.7em .3em; text-align:center; font-weight:bold; border:0; } .ui-datepicker td{ border:0; padding:1px; } .ui-datepicker td span,.ui-datepicker td a{ display:block; padding:.2em; text-align:right; text-decoration:none; } .ui-datepicker .ui-datepicker-buttonpane{ background-image:none; margin:.7em 0 0 0; padding:0 .2em; border-left:0; border-right:0; border-bottom:0; } .ui-datepicker .ui-datepicker-buttonpane button{ float:right; margin:.5em .2em .4em; cursor:pointer; padding:.2em .6em .3em .6em; width:auto; overflow:visible; } .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{ float:left; } .ui-datepicker.ui-datepicker-multi{ width:auto; } .ui-datepicker-multi .ui-datepicker-group{ float:left; } .ui-datepicker-multi .ui-datepicker-group table{ width:95%; margin:0 auto .4em; } .ui-datepicker-multi-2 .ui-datepicker-group{ width:50%; } .ui-datepicker-multi-3 .ui-datepicker-group{ width:33.3%; } .ui-datepicker-multi-4 .ui-datepicker-group{ width:25%; } .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{ border-left-width:0; } .ui-datepicker-multi .ui-datepicker-buttonpane{ clear:left; } .ui-datepicker-row-break{ clear:both; width:100%; font-size:0; } .ui-datepicker-rtl{ direction:rtl; } .ui-datepicker-rtl .ui-datepicker-prev{ right:2px; left:auto; } .ui-datepicker-rtl .ui-datepicker-next{ left:2px; right:auto; } .ui-datepicker-rtl .ui-datepicker-prev:hover{ right:1px; left:auto; } .ui-datepicker-rtl .ui-datepicker-next:hover{ left:1px; right:auto; } .ui-datepicker-rtl .ui-datepicker-buttonpane{ clear:right; } .ui-datepicker-rtl .ui-datepicker-buttonpane button{ float:left; } .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{ float:right; } .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{ border-right-width:0; border-left-width:1px; } .ui-dialog{ overflow:hidden; position:absolute; top:0; left:0; padding:.2em; outline:0; } .ui-dialog .ui-dialog-titlebar{ padding:.4em 1em; position:relative; } .ui-dialog .ui-dialog-title{ float:left; margin:.1em 0; white-space:nowrap; width:90%; overflow:hidden; text-overflow:ellipsis; } .ui-dialog .ui-dialog-titlebar-close{ position:absolute; right:.3em; top:50%; width:20px; margin:-10px 0 0 0; padding:1px; height:20px; } .ui-dialog .ui-dialog-content{ position:relative; border:0; padding:.5em 1em; background:none; overflow:auto; } .ui-dialog .ui-dialog-buttonpane{ text-align:left; border-width:1px 0 0 0; background-image:none; margin-top:.5em; padding:.3em 1em .5em .4em; } .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{ float:right; } .ui-dialog .ui-dialog-buttonpane button{ margin:.5em .4em .5em 0; cursor:pointer; } .ui-dialog .ui-resizable-se{ width:12px; height:12px; right:-5px; bottom:-5px; background-position:16px 16px; } .ui-draggable .ui-dialog-titlebar{ cursor:move; } .ui-draggable-handle{ -ms-touch-action:none; touch-action:none; } .ui-menu{ list-style:none; padding:0; margin:0; display:block; outline:none; } .ui-menu .ui-menu{ position:absolute; } .ui-menu .ui-menu-item{ position:relative; margin:0; padding:3px 1em 3px .4em; cursor:pointer; min-height:0; list-style-image:url(data:image/gif; base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); } .ui-menu .ui-menu-divider{ margin:5px 0; height:0; font-size:0; line-height:0; border-width:1px 0 0 0; } .ui-menu .ui-state-focus,.ui-menu .ui-state-active{ margin:-1px; } .ui-menu-icons{ position:relative; } .ui-menu-icons .ui-menu-item{ padding-left:2em; } .ui-menu .ui-icon{ position:absolute; top:0; bottom:0; left:.2em; margin:auto 0; } .ui-menu .ui-menu-icon{ left:auto; right:0; } .ui-progressbar{ height:2em; text-align:left; overflow:hidden; } .ui-progressbar .ui-progressbar-value{ margin:-1px; height:100%; } .ui-progressbar .ui-progressbar-overlay{ background:url(./../../lib/scripts/jquery/jquery-ui-theme/images/animated-overlay.gif); height:100%; filter:alpha(opacity=25); opacity:0.25; } .ui-progressbar-indeterminate .ui-progressbar-value{ background-image:none; } .ui-resizable{ position:relative; } .ui-resizable-handle{ position:absolute; font-size:.1px; display:block; -ms-touch-action:none; touch-action:none; } .ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{ display:none; } .ui-resizable-n{ cursor:n-resize; height:7px; width:100%; top:-5px; left:0; } .ui-resizable-s{ cursor:s-resize; height:7px; width:100%; bottom:-5px; left:0; } .ui-resizable-e{ cursor:e-resize; width:7px; right:-5px; top:0; height:100%; } .ui-resizable-w{ cursor:w-resize; width:7px; left:-5px; top:0; height:100%; } .ui-resizable-se{ cursor:se-resize; width:12px; height:12px; right:1px; bottom:1px; } .ui-resizable-sw{ cursor:sw-resize; width:9px; height:9px; left:-5px; bottom:-5px; } .ui-resizable-nw{ cursor:nw-resize; width:9px; height:9px; left:-5px; top:-5px; } .ui-resizable-ne{ cursor:ne-resize; width:9px; height:9px; right:-5px; top:-5px; } .ui-selectable{ -ms-touch-action:none; touch-action:none; } .ui-selectable-helper{ position:absolute; z-index:100; border:1px dotted black; } .ui-selectmenu-menu{ padding:0; margin:0; position:absolute; top:0; left:0; display:none; } .ui-selectmenu-menu .ui-menu{ overflow:auto; overflow-x:hidden; padding-bottom:1px; } .ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{ font-size:1em; font-weight:bold; line-height:1.5; padding:2px .4em; margin:.5em 0 0 0; height:auto; border:0; } .ui-selectmenu-open{ display:block; } .ui-selectmenu-button{ display:inline-block; overflow:hidden; position:relative; text-decoration:none; cursor:pointer; } .ui-selectmenu-button span.ui-icon{ right:.5em; left:auto; margin-top:-8px; position:absolute; top:50%; } .ui-selectmenu-button span.ui-selectmenu-text{ text-align:left; padding:.4em 2.1em .4em 1em; display:block; line-height:1.4; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .ui-slider{ position:relative; text-align:left; } .ui-slider .ui-slider-handle{ position:absolute; z-index:2; width:1.2em; height:1.2em; cursor:default; -ms-touch-action:none; touch-action:none; } .ui-slider .ui-slider-range{ position:absolute; z-index:1; font-size:.7em; display:block; border:0; background-position:0 0; } .ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{ filter:inherit; } .ui-slider-horizontal{ height:.8em; } .ui-slider-horizontal .ui-slider-handle{ top:-0.3em; margin-left:-0.6em; } .ui-slider-horizontal .ui-slider-range{ top:0; height:100%; } .ui-slider-horizontal .ui-slider-range-min{ left:0; } .ui-slider-horizontal .ui-slider-range-max{ right:0; } .ui-slider-vertical{ width:.8em; height:100px; } .ui-slider-vertical .ui-slider-handle{ left:-0.3em; margin-left:0; margin-bottom:-0.6em; } .ui-slider-vertical .ui-slider-range{ left:0; width:100%; } .ui-slider-vertical .ui-slider-range-min{ bottom:0; } .ui-slider-vertical .ui-slider-range-max{ top:0; } .ui-sortable-handle{ -ms-touch-action:none; touch-action:none; } .ui-spinner{ position:relative; display:inline-block; overflow:hidden; padding:0; vertical-align:middle; } .ui-spinner-input{ border:none; background:none; color:inherit; padding:0; margin:.2em 0; vertical-align:middle; margin-left:.4em; margin-right:22px; } .ui-spinner-button{ width:16px; height:50%; font-size:.5em; padding:0; margin:0; text-align:center; position:absolute; cursor:default; display:block; overflow:hidden; right:0; } .ui-spinner a.ui-spinner-button{ border-top:none; border-bottom:none; border-right:none; } .ui-spinner .ui-icon{ position:absolute; margin-top:-8px; top:50%; left:0; } .ui-spinner-up{ top:0; } .ui-spinner-down{ bottom:0; } .ui-spinner .ui-icon-triangle-1-s{ background-position:-65px -16px; } .ui-tabs{ position:relative; padding:.2em; } .ui-tabs .ui-tabs-nav{ margin:0; padding:.2em .2em 0; } .ui-tabs .ui-tabs-nav li{ list-style:none; float:left; position:relative; top:0; margin:1px .2em 0 0; border-bottom-width:0; padding:0; white-space:nowrap; } .ui-tabs .ui-tabs-nav .ui-tabs-anchor{ float:left; padding:.5em 1em; text-decoration:none; } .ui-tabs .ui-tabs-nav li.ui-tabs-active{ margin-bottom:-1px; padding-bottom:1px; } .ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{ cursor:text; } .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{ cursor:pointer; } .ui-tabs .ui-tabs-panel{ display:block; border-width:0; padding:1em 1.4em; background:none; } .ui-tooltip{ padding:8px; position:absolute; z-index:9999; max-width:300px; -webkit-box-shadow:0 0 5px #aaa; box-shadow:0 0 5px #aaa; } body .ui-tooltip{ border-width:2px; } .ui-widget{ font-size:1.1em; } .ui-widget .ui-widget{ font-size:1em; } .ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{ font-size:1em; } .ui-widget-content{ border:1px solid #aaa; background:#fff url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color:#222; } .ui-widget-content a{ color:#222; } .ui-widget-header{ border:1px solid #aaa; background:#ccc url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color:#222; font-weight:bold; } .ui-widget-header a{ color:#222; } .ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{ border:1px solid #d3d3d3; background:#e6e6e6 url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight:normal; color:#555; } .ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{ color:#555; text-decoration:none; } .ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{ border:1px solid #999; background:#dadada url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight:normal; color:#212121; } .ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{ color:#212121; text-decoration:none; } .ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{ border:1px solid #aaa; background:#fff url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight:normal; color:#212121; } .ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{ color:#212121; text-decoration:none; } .ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{ border:1px solid #fcefa1; background:#fbf9ee url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color:#363636; } .ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{ color:#363636; } .ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{ border:1px solid #cd0a0a; background:#fef1ec url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color:#cd0a0a; } .ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{ color:#cd0a0a; } .ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{ color:#cd0a0a; } .ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{ font-weight:bold; } .ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{ opacity:.7; filter:Alpha(Opacity=70); font-weight:normal; } .ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{ opacity:.35; filter:Alpha(Opacity=35); background-image:none; } .ui-state-disabled .ui-icon{ filter:Alpha(Opacity=35); } .ui-icon{ width:16px; height:16px; } .ui-icon,.ui-widget-content .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_222222_256x240.png); } .ui-widget-header .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_222222_256x240.png); } .ui-state-default .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_888888_256x240.png); } .ui-state-hover .ui-icon,.ui-state-focus .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_454545_256x240.png); } .ui-state-active .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_454545_256x240.png); } .ui-state-highlight .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_2e83ff_256x240.png); } .ui-state-error .ui-icon,.ui-state-error-text .ui-icon{ background-image:url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-icons_cd0a0a_256x240.png); } .ui-icon-blank{ background-position:16px 16px; } .ui-icon-carat-1-n{ background-position:0 0; } .ui-icon-carat-1-ne{ background-position:-16px 0; } .ui-icon-carat-1-e{ background-position:-32px 0; } .ui-icon-carat-1-se{ background-position:-48px 0; } .ui-icon-carat-1-s{ background-position:-64px 0; } .ui-icon-carat-1-sw{ background-position:-80px 0; } .ui-icon-carat-1-w{ background-position:-96px 0; } .ui-icon-carat-1-nw{ background-position:-112px 0; } .ui-icon-carat-2-n-s{ background-position:-128px 0; } .ui-icon-carat-2-e-w{ background-position:-144px 0; } .ui-icon-triangle-1-n{ background-position:0 -16px; } .ui-icon-triangle-1-ne{ background-position:-16px -16px; } .ui-icon-triangle-1-e{ background-position:-32px -16px; } .ui-icon-triangle-1-se{ background-position:-48px -16px; } .ui-icon-triangle-1-s{ background-position:-64px -16px; } .ui-icon-triangle-1-sw{ background-position:-80px -16px; } .ui-icon-triangle-1-w{ background-position:-96px -16px; } .ui-icon-triangle-1-nw{ background-position:-112px -16px; } .ui-icon-triangle-2-n-s{ background-position:-128px -16px; } .ui-icon-triangle-2-e-w{ background-position:-144px -16px; } .ui-icon-arrow-1-n{ background-position:0 -32px; } .ui-icon-arrow-1-ne{ background-position:-16px -32px; } .ui-icon-arrow-1-e{ background-position:-32px -32px; } .ui-icon-arrow-1-se{ background-position:-48px -32px; } .ui-icon-arrow-1-s{ background-position:-64px -32px; } .ui-icon-arrow-1-sw{ background-position:-80px -32px; } .ui-icon-arrow-1-w{ background-position:-96px -32px; } .ui-icon-arrow-1-nw{ background-position:-112px -32px; } .ui-icon-arrow-2-n-s{ background-position:-128px -32px; } .ui-icon-arrow-2-ne-sw{ background-position:-144px -32px; } .ui-icon-arrow-2-e-w{ background-position:-160px -32px; } .ui-icon-arrow-2-se-nw{ background-position:-176px -32px; } .ui-icon-arrowstop-1-n{ background-position:-192px -32px; } .ui-icon-arrowstop-1-e{ background-position:-208px -32px; } .ui-icon-arrowstop-1-s{ background-position:-224px -32px; } .ui-icon-arrowstop-1-w{ background-position:-240px -32px; } .ui-icon-arrowthick-1-n{ background-position:0 -48px; } .ui-icon-arrowthick-1-ne{ background-position:-16px -48px; } .ui-icon-arrowthick-1-e{ background-position:-32px -48px; } .ui-icon-arrowthick-1-se{ background-position:-48px -48px; } .ui-icon-arrowthick-1-s{ background-position:-64px -48px; } .ui-icon-arrowthick-1-sw{ background-position:-80px -48px; } .ui-icon-arrowthick-1-w{ background-position:-96px -48px; } .ui-icon-arrowthick-1-nw{ background-position:-112px -48px; } .ui-icon-arrowthick-2-n-s{ background-position:-128px -48px; } .ui-icon-arrowthick-2-ne-sw{ background-position:-144px -48px; } .ui-icon-arrowthick-2-e-w{ background-position:-160px -48px; } .ui-icon-arrowthick-2-se-nw{ background-position:-176px -48px; } .ui-icon-arrowthickstop-1-n{ background-position:-192px -48px; } .ui-icon-arrowthickstop-1-e{ background-position:-208px -48px; } .ui-icon-arrowthickstop-1-s{ background-position:-224px -48px; } .ui-icon-arrowthickstop-1-w{ background-position:-240px -48px; } .ui-icon-arrowreturnthick-1-w{ background-position:0 -64px; } .ui-icon-arrowreturnthick-1-n{ background-position:-16px -64px; } .ui-icon-arrowreturnthick-1-e{ background-position:-32px -64px; } .ui-icon-arrowreturnthick-1-s{ background-position:-48px -64px; } .ui-icon-arrowreturn-1-w{ background-position:-64px -64px; } .ui-icon-arrowreturn-1-n{ background-position:-80px -64px; } .ui-icon-arrowreturn-1-e{ background-position:-96px -64px; } .ui-icon-arrowreturn-1-s{ background-position:-112px -64px; } .ui-icon-arrowrefresh-1-w{ background-position:-128px -64px; } .ui-icon-arrowrefresh-1-n{ background-position:-144px -64px; } .ui-icon-arrowrefresh-1-e{ background-position:-160px -64px; } .ui-icon-arrowrefresh-1-s{ background-position:-176px -64px; } .ui-icon-arrow-4{ background-position:0 -80px; } .ui-icon-arrow-4-diag{ background-position:-16px -80px; } .ui-icon-extlink{ background-position:-32px -80px; } .ui-icon-newwin{ background-position:-48px -80px; } .ui-icon-refresh{ background-position:-64px -80px; } .ui-icon-shuffle{ background-position:-80px -80px; } .ui-icon-transfer-e-w{ background-position:-96px -80px; } .ui-icon-transferthick-e-w{ background-position:-112px -80px; } .ui-icon-folder-collapsed{ background-position:0 -96px; } .ui-icon-folder-open{ background-position:-16px -96px; } .ui-icon-document{ background-position:-32px -96px; } .ui-icon-document-b{ background-position:-48px -96px; } .ui-icon-note{ background-position:-64px -96px; } .ui-icon-mail-closed{ background-position:-80px -96px; } .ui-icon-mail-open{ background-position:-96px -96px; } .ui-icon-suitcase{ background-position:-112px -96px; } .ui-icon-comment{ background-position:-128px -96px; } .ui-icon-person{ background-position:-144px -96px; } .ui-icon-print{ background-position:-160px -96px; } .ui-icon-trash{ background-position:-176px -96px; } .ui-icon-locked{ background-position:-192px -96px; } .ui-icon-unlocked{ background-position:-208px -96px; } .ui-icon-bookmark{ background-position:-224px -96px; } .ui-icon-tag{ background-position:-240px -96px; } .ui-icon-home{ background-position:0 -112px; } .ui-icon-flag{ background-position:-16px -112px; } .ui-icon-calendar{ background-position:-32px -112px; } .ui-icon-cart{ background-position:-48px -112px; } .ui-icon-pencil{ background-position:-64px -112px; } .ui-icon-clock{ background-position:-80px -112px; } .ui-icon-disk{ background-position:-96px -112px; } .ui-icon-calculator{ background-position:-112px -112px; } .ui-icon-zoomin{ background-position:-128px -112px; } .ui-icon-zoomout{ background-position:-144px -112px; } .ui-icon-search{ background-position:-160px -112px; } .ui-icon-wrench{ background-position:-176px -112px; } .ui-icon-gear{ background-position:-192px -112px; } .ui-icon-heart{ background-position:-208px -112px; } .ui-icon-star{ background-position:-224px -112px; } .ui-icon-link{ background-position:-240px -112px; } .ui-icon-cancel{ background-position:0 -128px; } .ui-icon-plus{ background-position:-16px -128px; } .ui-icon-plusthick{ background-position:-32px -128px; } .ui-icon-minus{ background-position:-48px -128px; } .ui-icon-minusthick{ background-position:-64px -128px; } .ui-icon-close{ background-position:-80px -128px; } .ui-icon-closethick{ background-position:-96px -128px; } .ui-icon-key{ background-position:-112px -128px; } .ui-icon-lightbulb{ background-position:-128px -128px; } .ui-icon-scissors{ background-position:-144px -128px; } .ui-icon-clipboard{ background-position:-160px -128px; } .ui-icon-copy{ background-position:-176px -128px; } .ui-icon-contact{ background-position:-192px -128px; } .ui-icon-image{ background-position:-208px -128px; } .ui-icon-video{ background-position:-224px -128px; } .ui-icon-script{ background-position:-240px -128px; } .ui-icon-alert{ background-position:0 -144px; } .ui-icon-info{ background-position:-16px -144px; } .ui-icon-notice{ background-position:-32px -144px; } .ui-icon-help{ background-position:-48px -144px; } .ui-icon-check{ background-position:-64px -144px; } .ui-icon-bullet{ background-position:-80px -144px; } .ui-icon-radio-on{ background-position:-96px -144px; } .ui-icon-radio-off{ background-position:-112px -144px; } .ui-icon-pin-w{ background-position:-128px -144px; } .ui-icon-pin-s{ background-position:-144px -144px; } .ui-icon-play{ background-position:0 -160px; } .ui-icon-pause{ background-position:-16px -160px; } .ui-icon-seek-next{ background-position:-32px -160px; } .ui-icon-seek-prev{ background-position:-48px -160px; } .ui-icon-seek-end{ background-position:-64px -160px; } .ui-icon-seek-start{ background-position:-80px -160px; } .ui-icon-seek-first{ background-position:-80px -160px; } .ui-icon-stop{ background-position:-96px -160px; } .ui-icon-eject{ background-position:-112px -160px; } .ui-icon-volume-off{ background-position:-128px -160px; } .ui-icon-volume-on{ background-position:-144px -160px; } .ui-icon-power{ background-position:0 -176px; } .ui-icon-signal-diag{ background-position:-16px -176px; } .ui-icon-signal{ background-position:-32px -176px; } .ui-icon-battery-0{ background-position:-48px -176px; } .ui-icon-battery-1{ background-position:-64px -176px; } .ui-icon-battery-2{ background-position:-80px -176px; } .ui-icon-battery-3{ background-position:-96px -176px; } .ui-icon-circle-plus{ background-position:0 -192px; } .ui-icon-circle-minus{ background-position:-16px -192px; } .ui-icon-circle-close{ background-position:-32px -192px; } .ui-icon-circle-triangle-e{ background-position:-48px -192px; } .ui-icon-circle-triangle-s{ background-position:-64px -192px; } .ui-icon-circle-triangle-w{ background-position:-80px -192px; } .ui-icon-circle-triangle-n{ background-position:-96px -192px; } .ui-icon-circle-arrow-e{ background-position:-112px -192px; } .ui-icon-circle-arrow-s{ background-position:-128px -192px; } .ui-icon-circle-arrow-w{ background-position:-144px -192px; } .ui-icon-circle-arrow-n{ background-position:-160px -192px; } .ui-icon-circle-zoomin{ background-position:-176px -192px; } .ui-icon-circle-zoomout{ background-position:-192px -192px; } .ui-icon-circle-check{ background-position:-208px -192px; } .ui-icon-circlesmall-plus{ background-position:0 -208px; } .ui-icon-circlesmall-minus{ background-position:-16px -208px; } .ui-icon-circlesmall-close{ background-position:-32px -208px; } .ui-icon-squaresmall-plus{ background-position:-48px -208px; } .ui-icon-squaresmall-minus{ background-position:-64px -208px; } .ui-icon-squaresmall-close{ background-position:-80px -208px; } .ui-icon-grip-dotted-vertical{ background-position:0 -224px; } .ui-icon-grip-dotted-horizontal{ background-position:-16px -224px; } .ui-icon-grip-solid-vertical{ background-position:-32px -224px; } .ui-icon-grip-solid-horizontal{ background-position:-48px -224px; } .ui-icon-gripsmall-diagonal-se{ background-position:-64px -224px; } .ui-icon-grip-diagonal-se{ background-position:-80px -224px; } .ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{ border-top-left-radius:4px; } .ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{ border-top-right-radius:4px; } .ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{ border-bottom-left-radius:4px; } .ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{ border-bottom-right-radius:4px; } .ui-widget-overlay{ background:#aaa url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity:.3; filter:Alpha(Opacity=30); } .ui-widget-shadow{ margin:-8px 0 0 -8px; padding:8px; background:#aaa url(./../../lib/scripts/jquery/jquery-ui-theme/images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity:.3; filter:Alpha(Opacity=30); border-radius:8px; } div.dokuwiki div.gallery table{ border:none; } div.dokuwiki div.gallery table td{ padding:1em; text-align:center; vertical-align:middle; border:none; } div.dokuwiki div.gallery table img.tn{ padding:.4em; border:1px solid #ccc; max-width:none; } div.dokuwiki div.gallery{ clear:left; margin-bottom:1em; } div.dokuwiki div.gallery img.tn{ margin:9px; vertical-align:middle; padding:.4em; border:1px solid #000; } div.dokuwiki div.gallery_left{ float:left; } div.dokuwiki div.gallery div{ float:left; } div.dokuwiki div.gallery_right{ float:right; } div.dokuwiki div.gallery_center{ margin-left:auto; margin-right:auto; } div.dokuwiki div.gallery_center{ width:80%; text-align:center; } div.dokuwiki div.gallery div.gallery_pages{ float:none; text-align:left; } div.dokuwiki div.gallery table{ border:none; } div.dokuwiki div.gallery table td{ padding:1em; text-align:center; vertical-align:middle; border:none; } div.dokuwiki div.gallery table img.tn{ padding:.4em; border:1px solid #ccc; max-width:none; } div.dokuwiki div.gallery{ clear:left; margin-bottom:1em; } div.dokuwiki div.gallery img.tn{ margin:9px; vertical-align:middle; padding:.4em; border:1px solid #000; } div.dokuwiki div.gallery_left{ float:left; } div.dokuwiki div.gallery div{ float:left; } div.dokuwiki div.gallery_right{ float:right; } div.dokuwiki div.gallery_center{ margin-left:auto; margin-right:auto; } div.dokuwiki div.gallery_center{ width:80%; text-align:center; } div.dokuwiki div.gallery div.gallery_pages{ float:none; text-align:left; } div.pp_default .pp_top,div.pp_default .pp_top .pp_middle,div.pp_default .pp_top .pp_left,div.pp_default .pp_top .pp_right,div.pp_default .pp_bottom,div.pp_default .pp_bottom .pp_left,div.pp_default .pp_bottom .pp_middle,div.pp_default .pp_bottom .pp_right{ height:13px; } div.pp_default .pp_top .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) -78px -93px no-repeat; } div.pp_default .pp_top .pp_middle{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite_x.png) top left repeat-x; } div.pp_default .pp_top .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) -112px -93px no-repeat; } div.pp_default .pp_content .ppt{ color:#f8f8f8; } div.pp_default .pp_content_container .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite_y.png) -7px 0 repeat-y; padding-left:13px; } div.pp_default .pp_content_container .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite_y.png) top right repeat-y; padding-right:13px; } div.pp_default .pp_content{ background-color:#fff; } div.pp_default .pp_next:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite_next.png) center right no-repeat; cursor:pointer; } div.pp_default .pp_previous:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite_prev.png) center left no-repeat; cursor:pointer; } div.pp_default .pp_expand{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) 0 -29px no-repeat; cursor:pointer; width:28px; height:28px; } div.pp_default .pp_expand:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) 0 -56px no-repeat; cursor:pointer; } div.pp_default .pp_contract{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) 0 -84px no-repeat; cursor:pointer; width:28px; height:28px; } div.pp_default .pp_contract:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) 0 -113px no-repeat; cursor:pointer; } div.pp_default .pp_close{ width:30px; height:30px; background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) 2px 1px no-repeat; cursor:pointer; } div.pp_default #pp_full_res .pp_inline{ color:#000; } div.pp_default .pp_gallery ul li a{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/default_thumb.png) center center #f8f8f8; border:1px solid #aaa; } div.pp_default .pp_gallery ul li a:hover,div.pp_default .pp_gallery ul li.selected a{ border-color:#fff; } div.pp_default .pp_social{ margin-top:7px; } div.pp_default .pp_gallery a.pp_arrow_previous,div.pp_default .pp_gallery a.pp_arrow_next{ position:static; left:auto; } div.pp_default .pp_nav .pp_play,div.pp_default .pp_nav .pp_pause{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) -51px 1px no-repeat; height:30px; width:30px; } div.pp_default .pp_nav .pp_pause{ background-position:-51px -29px; } div.pp_default .pp_details{ position:relative; } div.pp_default a.pp_arrow_previous,div.pp_default a.pp_arrow_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) -31px -3px no-repeat; height:20px; margin:4px 0 0 0; width:20px; } div.pp_default a.pp_arrow_next{ left:52px; background-position:-82px -3px; } div.pp_default .pp_content_container .pp_details{ margin-top:5px; } div.pp_default .pp_nav{ clear:none; height:30px; width:110px; position:relative; } div.pp_default .pp_nav .currentTextHolder{ font-family:Georgia; font-style:italic; color:#999; font-size:11px; left:75px; line-height:25px; margin:0; padding:0 0 0 10px; position:absolute; top:2px; } div.pp_default .pp_close:hover,div.pp_default .pp_nav .pp_play:hover,div.pp_default .pp_nav .pp_pause:hover,div.pp_default .pp_arrow_next:hover,div.pp_default .pp_arrow_previous:hover{ opacity:0.7; } div.pp_default .pp_description{ font-size:11px; font-weight:bold; line-height:14px; margin:5px 50px 5px 0; } div.pp_default .pp_bottom .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) -78px -127px no-repeat; } div.pp_default .pp_bottom .pp_middle{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite_x.png) bottom left repeat-x; } div.pp_default .pp_bottom .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/sprite.png) -112px -127px no-repeat; } div.pp_default .pp_loaderIcon{ background:url(./../../lib/plugins/gallery/prettyPhoto/default/loader.gif) center center no-repeat; } div.light_rounded .pp_top .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -88px -53px no-repeat; } div.light_rounded .pp_top .pp_middle{ background:#fff; } div.light_rounded .pp_top .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -110px -53px no-repeat; } div.light_rounded .pp_content .ppt{ color:#000; } div.light_rounded .pp_content_container .pp_left,div.light_rounded .pp_content_container .pp_right{ background:#fff; } div.light_rounded .pp_content{ background-color:#fff; } div.light_rounded .pp_next:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/btnNext.png) center right no-repeat; cursor:pointer; } div.light_rounded .pp_previous:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/btnPrevious.png) center left no-repeat; cursor:pointer; } div.light_rounded .pp_expand{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -31px -26px no-repeat; cursor:pointer; } div.light_rounded .pp_expand:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -31px -47px no-repeat; cursor:pointer; } div.light_rounded .pp_contract{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) 0 -26px no-repeat; cursor:pointer; } div.light_rounded .pp_contract:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) 0 -47px no-repeat; cursor:pointer; } div.light_rounded .pp_close{ width:75px; height:22px; background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -1px -1px no-repeat; cursor:pointer; } div.light_rounded .pp_details{ position:relative; } div.light_rounded .pp_description{ margin-right:85px; } div.light_rounded #pp_full_res .pp_inline{ color:#000; } div.light_rounded .pp_gallery a.pp_arrow_previous,div.light_rounded .pp_gallery a.pp_arrow_next{ margin-top:12px !important; } div.light_rounded .pp_nav .pp_play{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -1px -100px no-repeat; height:15px; width:14px; } div.light_rounded .pp_nav .pp_pause{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -24px -100px no-repeat; height:15px; width:14px; } div.light_rounded .pp_arrow_previous{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) 0 -71px no-repeat; } div.light_rounded .pp_arrow_previous.disabled{ background-position:0 -87px; cursor:default; } div.light_rounded .pp_arrow_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -22px -71px no-repeat; } div.light_rounded .pp_arrow_next.disabled{ background-position:-22px -87px; cursor:default; } div.light_rounded .pp_bottom .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -88px -80px no-repeat; } div.light_rounded .pp_bottom .pp_middle{ background:#fff; } div.light_rounded .pp_bottom .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/sprite.png) -110px -80px no-repeat; } div.light_rounded .pp_loaderIcon{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/loader.gif) center center no-repeat; } div.dark_rounded .pp_top .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -88px -53px no-repeat; } div.dark_rounded .pp_top .pp_middle{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/contentPattern.png) top left repeat; } div.dark_rounded .pp_top .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -110px -53px no-repeat; } div.dark_rounded .pp_content_container .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/contentPattern.png) top left repeat-y; } div.dark_rounded .pp_content_container .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/contentPattern.png) top right repeat-y; } div.dark_rounded .pp_content{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/contentPattern.png) top left repeat; } div.dark_rounded .pp_next:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/btnNext.png) center right no-repeat; cursor:pointer; } div.dark_rounded .pp_previous:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/btnPrevious.png) center left no-repeat; cursor:pointer; } div.dark_rounded .pp_expand{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -31px -26px no-repeat; cursor:pointer; } div.dark_rounded .pp_expand:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -31px -47px no-repeat; cursor:pointer; } div.dark_rounded .pp_contract{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) 0 -26px no-repeat; cursor:pointer; } div.dark_rounded .pp_contract:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) 0 -47px no-repeat; cursor:pointer; } div.dark_rounded .pp_close{ width:75px; height:22px; background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -1px -1px no-repeat; cursor:pointer; } div.dark_rounded .pp_details{ position:relative; } div.dark_rounded .pp_description{ margin-right:85px; } div.dark_rounded .currentTextHolder{ color:#c4c4c4; } div.dark_rounded .pp_description{ color:#fff; } div.dark_rounded #pp_full_res .pp_inline{ color:#fff; } div.dark_rounded .pp_gallery a.pp_arrow_previous,div.dark_rounded .pp_gallery a.pp_arrow_next{ margin-top:12px !important; } div.dark_rounded .pp_nav .pp_play{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -1px -100px no-repeat; height:15px; width:14px; } div.dark_rounded .pp_nav .pp_pause{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -24px -100px no-repeat; height:15px; width:14px; } div.dark_rounded .pp_arrow_previous{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) 0 -71px no-repeat; } div.dark_rounded .pp_arrow_previous.disabled{ background-position:0 -87px; cursor:default; } div.dark_rounded .pp_arrow_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -22px -71px no-repeat; } div.dark_rounded .pp_arrow_next.disabled{ background-position:-22px -87px; cursor:default; } div.dark_rounded .pp_bottom .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -88px -80px no-repeat; } div.dark_rounded .pp_bottom .pp_middle{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/contentPattern.png) top left repeat; } div.dark_rounded .pp_bottom .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/sprite.png) -110px -80px no-repeat; } div.dark_rounded .pp_loaderIcon{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_rounded/loader.gif) center center no-repeat; } div.dark_square .pp_left,div.dark_square .pp_middle,div.dark_square .pp_right,div.dark_square .pp_content{ background:#000; } div.dark_square .currentTextHolder{ color:#c4c4c4; } div.dark_square .pp_description{ color:#fff; } div.dark_square .pp_loaderIcon{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/loader.gif) center center no-repeat; } div.dark_square .pp_expand{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) -31px -26px no-repeat; cursor:pointer; } div.dark_square .pp_expand:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) -31px -47px no-repeat; cursor:pointer; } div.dark_square .pp_contract{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) 0 -26px no-repeat; cursor:pointer; } div.dark_square .pp_contract:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) 0 -47px no-repeat; cursor:pointer; } div.dark_square .pp_close{ width:75px; height:22px; background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) -1px -1px no-repeat; cursor:pointer; } div.dark_square .pp_details{ position:relative; } div.dark_square .pp_description{ margin:0 85px 0 0; } div.dark_square #pp_full_res .pp_inline{ color:#fff; } div.dark_square .pp_gallery a.pp_arrow_previous,div.dark_square .pp_gallery a.pp_arrow_next{ margin-top:12px !important; } div.dark_square .pp_nav{ clear:none; } div.dark_square .pp_nav .pp_play{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) -1px -100px no-repeat; height:15px; width:14px; } div.dark_square .pp_nav .pp_pause{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) -24px -100px no-repeat; height:15px; width:14px; } div.dark_square .pp_arrow_previous{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) 0 -71px no-repeat; } div.dark_square .pp_arrow_previous.disabled{ background-position:0 -87px; cursor:default; } div.dark_square .pp_arrow_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/sprite.png) -22px -71px no-repeat; } div.dark_square .pp_arrow_next.disabled{ background-position:-22px -87px; cursor:default; } div.dark_square .pp_next:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/btnNext.png) center right no-repeat; cursor:pointer; } div.dark_square .pp_previous:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/dark_square/btnPrevious.png) center left no-repeat; cursor:pointer; } div.light_square .pp_left,div.light_square .pp_middle,div.light_square .pp_right,div.light_square .pp_content{ background:#fff; } div.light_square .pp_content .ppt{ color:#000; } div.light_square .pp_expand{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) -31px -26px no-repeat; cursor:pointer; } div.light_square .pp_expand:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) -31px -47px no-repeat; cursor:pointer; } div.light_square .pp_contract{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) 0 -26px no-repeat; cursor:pointer; } div.light_square .pp_contract:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) 0 -47px no-repeat; cursor:pointer; } div.light_square .pp_close{ width:75px; height:22px; background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) -1px -1px no-repeat; cursor:pointer; } div.light_square .pp_details{ position:relative; } div.light_square .pp_description{ margin-right:85px; } div.light_square #pp_full_res .pp_inline{ color:#000; } div.light_square .pp_gallery a.pp_arrow_previous,div.light_square .pp_gallery a.pp_arrow_next{ margin-top:12px !important; } div.light_square .pp_nav .pp_play{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) -1px -100px no-repeat; height:15px; width:14px; } div.light_square .pp_nav .pp_pause{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) -24px -100px no-repeat; height:15px; width:14px; } div.light_square .pp_arrow_previous{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) 0 -71px no-repeat; } div.light_square .pp_arrow_previous.disabled{ background-position:0 -87px; cursor:default; } div.light_square .pp_arrow_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/sprite.png) -22px -71px no-repeat; } div.light_square .pp_arrow_next.disabled{ background-position:-22px -87px; cursor:default; } div.light_square .pp_next:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/btnNext.png) center right no-repeat; cursor:pointer; } div.light_square .pp_previous:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_square/btnPrevious.png) center left no-repeat; cursor:pointer; } div.light_square .pp_loaderIcon{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/loader.gif) center center no-repeat; } div.facebook .pp_top .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -88px -53px no-repeat; } div.facebook .pp_top .pp_middle{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/contentPatternTop.png) top left repeat-x; } div.facebook .pp_top .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -110px -53px no-repeat; } div.facebook .pp_content .ppt{ color:#000; } div.facebook .pp_content_container .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/contentPatternLeft.png) top left repeat-y; } div.facebook .pp_content_container .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/contentPatternRight.png) top right repeat-y; } div.facebook .pp_content{ background:#fff; } div.facebook .pp_expand{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -31px -26px no-repeat; cursor:pointer; } div.facebook .pp_expand:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -31px -47px no-repeat; cursor:pointer; } div.facebook .pp_contract{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) 0 -26px no-repeat; cursor:pointer; } div.facebook .pp_contract:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) 0 -47px no-repeat; cursor:pointer; } div.facebook .pp_close{ width:22px; height:22px; background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -1px -1px no-repeat; cursor:pointer; } div.facebook .pp_details{ position:relative; } div.facebook .pp_description{ margin:0 37px 0 0; } div.facebook #pp_full_res .pp_inline{ color:#000; } div.facebook .pp_loaderIcon{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/loader.gif) center center no-repeat; } div.facebook .pp_arrow_previous{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) 0 -71px no-repeat; height:22px; margin-top:0; width:22px; } div.facebook .pp_arrow_previous.disabled{ background-position:0 -96px; cursor:default; } div.facebook .pp_arrow_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -32px -71px no-repeat; height:22px; margin-top:0; width:22px; } div.facebook .pp_arrow_next.disabled{ background-position:-32px -96px; cursor:default; } div.facebook .pp_nav{ margin-top:0; } div.facebook .pp_nav p{ font-size:15px; padding:0 3px 0 4px; } div.facebook .pp_nav .pp_play{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -1px -123px no-repeat; height:22px; width:22px; } div.facebook .pp_nav .pp_pause{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -32px -123px no-repeat; height:22px; width:22px; } div.facebook .pp_next:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/btnNext.png) center right no-repeat; cursor:pointer; } div.facebook .pp_previous:hover{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/btnPrevious.png) center left no-repeat; cursor:pointer; } div.facebook .pp_bottom .pp_left{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -88px -80px no-repeat; } div.facebook .pp_bottom .pp_middle{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/contentPatternBottom.png) top left repeat-x; } div.facebook .pp_bottom .pp_right{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/sprite.png) -110px -80px no-repeat; } div.pp_pic_holder a:focus{ outline:none; } div.pp_overlay{ background:#000; display:none; left:0; position:absolute; top:0; width:100%; z-index:9500; } div.pp_pic_holder{ display:none; position:absolute; width:100px; z-index:10000; } .pp_top{ height:20px; position:relative; } * html .pp_top{ padding:0 20px; } .pp_top .pp_left{ height:20px; left:0; position:absolute; width:20px; } .pp_top .pp_middle{ height:20px; left:20px; position:absolute; right:20px; } * html .pp_top .pp_middle{ left:0; position:static; } .pp_top .pp_right{ height:20px; left:auto; position:absolute; right:0; top:0; width:20px; } .pp_content{ height:40px; min-width:40px; } * html .pp_content{ width:40px; } .pp_fade{ display:none; } .pp_content_container{ position:relative; text-align:left; width:100%; } .pp_content_container .pp_left{ padding-left:20px; } .pp_content_container .pp_right{ padding-right:20px; } .pp_content_container .pp_details{ float:left; margin:10px 0 2px 0; } .pp_description{ display:none; margin:0; } .pp_social{ float:left; margin:0; } .pp_social .facebook{ float:left; margin-left:5px; width:55px; overflow:hidden; } .pp_social .twitter{ float:left; } .pp_nav{ clear:right; float:left; margin:3px 10px 0 0; } .pp_nav p{ float:left; margin:2px 4px; white-space:nowrap; } .pp_nav .pp_play,.pp_nav .pp_pause{ float:left; margin-right:4px; text-indent:-10000px; } a.pp_arrow_previous,a.pp_arrow_next{ display:block; float:left; height:15px; margin-top:3px; overflow:hidden; text-indent:-10000px; width:14px; } .pp_hoverContainer{ position:absolute; top:0; width:100%; z-index:2000; } .pp_gallery{ display:none; left:50%; margin-top:-50px; position:absolute; z-index:10000; } .pp_gallery div{ float:left; overflow:hidden; position:relative; } .pp_gallery ul{ float:left; height:35px; margin:0 0 0 5px; padding:0; position:relative; white-space:nowrap; } .pp_gallery ul a{ border:1px #000 solid; border:1px rgba(0,0,0,0.5) solid; display:block; float:left; height:33px; overflow:hidden; } .pp_gallery ul a:hover,.pp_gallery li.selected a{ border-color:#fff; } .pp_gallery ul a img{ border:0; } .pp_gallery li{ display:block; float:left; margin:0 5px 0 0; padding:0; } .pp_gallery li.default a{ background:url(./../../lib/plugins/gallery/prettyPhoto/facebook/default_thumbnail.gif) 0 0 no-repeat; display:block; height:33px; width:50px; } .pp_gallery li.default a img{ display:none; } .pp_gallery .pp_arrow_previous,.pp_gallery .pp_arrow_next{ margin-top:7px !important; } a.pp_next{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/btnNext.png) 10000px 10000px no-repeat; display:block; float:right; height:100%; text-indent:-10000px; width:49%; } a.pp_previous{ background:url(./../../lib/plugins/gallery/prettyPhoto/light_rounded/btnNext.png) 10000px 10000px no-repeat; display:block; float:left; height:100%; text-indent:-10000px; width:49%; } a.pp_expand,a.pp_contract{ cursor:pointer; display:none; height:20px; position:absolute; right:30px; text-indent:-10000px; top:10px; width:20px; z-index:20000; } a.pp_close{ position:absolute; right:0; top:0; display:block; line-height:22px; text-indent:-10000px; } .pp_bottom{ height:20px; position:relative; } * html .pp_bottom{ padding:0 20px; } .pp_bottom .pp_left{ height:20px; left:0; position:absolute; width:20px; } .pp_bottom .pp_middle{ height:20px; left:20px; position:absolute; right:20px; } * html .pp_bottom .pp_middle{ left:0; position:static; } .pp_bottom .pp_right{ height:20px; left:auto; position:absolute; right:0; top:0; width:20px; } .pp_loaderIcon{ display:block; height:24px; left:50%; margin:-12px 0 0 -12px; position:absolute; top:50%; width:24px; } #pp_full_res{ line-height:1 !important; } #pp_full_res .pp_inline{ text-align:left; } #pp_full_res .pp_inline p{ margin:0 0 15px 0; } div.ppt{ color:#fff; display:none; font-size:17px; margin:0 0 5px 15px; z-index:9999; } .noteclassic,.noteimportant,.notewarning,.notetip{ margin:2em; margin-left:auto; margin-right:auto; width:70% !important; min-height:40px; clear:both; text-align:justify; vertical-align:middle; border-collapse:collapse; padding:15px 20px 15px 80px; background-position:20px 50%; background-repeat:no-repeat; -moz-border-radius:20px; -khtml-border-radius:20px; border-radius:20px; } .noteclassic{ background-color:#eef; background-image:url(./../../lib/plugins/note/images/note.png); } .noteimportant{ background-color:#ffc; background-image:url(./../../lib/plugins/note/images/important.png); } .notewarning{ background-color:#fdd; background-image:url(./../../lib/plugins/note/images/warning.png); } .notetip{ background-color:#dfd; background-image:url(./../../lib/plugins/note/images/tip.png); } div.sortable thead tr.row0 th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after{ content:" \25B4\25BE"; } div.sortable thead tr.row0 th.sorttable_sorted:after{ content:" \25BE"; } div.sortable thead tr.row0 th.sorttable_sorted_reverse:after{ content:" \25B4"; } div.dokuwiki div.plugin_include_content div.secedit{ float:right; margin-left:1em; margin-top:0; } div.dokuwiki div.inclmeta{ border-top:1px dotted #ccc; padding-top:.2em; color:#666; font-size:80%; line-height:1.25; margin-bottom:2em; } div.dokuwiki div.inclmeta a.permalink{ background:transparent url(./../../lib/plugins/include/images/link.gif) 0 1px no-repeat; padding:1px 0 1px 16px; } div.dokuwiki div.inclmeta abbr.published{ background:transparent url(./../../lib/plugins/include/images/date.gif) 0 1px no-repeat; padding:1px 0 1px 16px; border-bottom:0; } div.dokuwiki div.inclmeta span.author{ background:transparent url(./../../lib/plugins/include/images/user.gif) 0 1px no-repeat; padding:1px 0 1px 16px; } div.dokuwiki div.inclmeta span.author a.interwiki{ background:none; padding:0; } div.dokuwiki div.inclmeta span.comment{ background:transparent url(./../../lib/plugins/include/images/comment.gif) 0 1px no-repeat; padding:1px 0 1px 16px; } div.dokuwiki div.inclmeta div.tags{ border-top:0; font-size:100%; float:right; clear:none; } div.dokuwiki p.include_readmore{ text-align:right; } #dokuwiki__detail{ padding:1em; } #dokuwiki__detail img{ float:left; margin:0 1.5em .5em 0; } [dir=rtl] #dokuwiki__detail div.content img{ float:right; margin-right:0; margin-left:1.5em; } #dokuwiki__detail div.img_detail{ float:left; } [dir=rtl] #dokuwiki__detail div.content div.img_detail{ float:right; } #dokuwiki__detail p.back{ clear:both; } html.popup{ overflow:auto; } #media__manager{ height:100%; overflow:hidden; } #mediamgr__aside{ width:30%; height:100%; overflow:auto; position:absolute; left:0; border-right:1px solid #ccc; } [dir=rtl] #mediamgr__aside{ left:auto; right:0; border-right-width:0; border-left:1px solid #ccc; } #mediamgr__aside .pad{ padding:.5em; } #mediamgr__content{ width:69.7%; height:100%; overflow:auto; position:absolute; right:0; } [dir=rtl] #mediamgr__content{ right:auto; left:0; } #mediamgr__content .pad{ padding:.5em; } #media__manager h1,#media__manager h2{ font-size:1.5em; margin-bottom:.5em; padding-bottom:.2em; border-bottom:1px solid #ccc; } #media__opts{ margin-bottom:.5em; } #media__opts input{ margin-right:.3em; } [dir=rtl] #media__opts input{ margin-right:0; margin-left:.3em; } #media__tree ul{ padding-left:.2em; } [dir=rtl] #media__tree ul{ padding-left:0; padding-right:.2em; } #media__tree ul li{ clear:left; list-style-type:none; list-style-image:none; margin-left:0; } [dir=rtl] #media__tree ul li{ clear:right; margin-right:0; } #media__tree ul li img{ float:left; padding:.5em .3em 0 0; } [dir=rtl] #media__tree ul li img{ float:right; padding:.5em 0 0 .3em; } #media__tree ul li div.li{ display:inline; } #media__tree ul li li{ margin-left:1.5em; } [dir=rtl] #media__tree ul li li{ margin-left:0; margin-right:1.5em; } #media__content div.upload{ font-size:.9em; margin-bottom:.5em; } #mediamanager__uploader{ margin-bottom:1em; } #mediamanager__uploader p{ margin-bottom:.5em; } #media__content img.load{ margin:1em auto; } #media__content .odd,#media__content .even{ padding:.5em; } #media__content .odd{ background-color:#eee; } #media__content #scroll__here{ border:1px dashed #ccc; } #media__content a.mediafile{ margin-right:1.5em; font-weight:bold; cursor:pointer; } [dir=rtl] #media__content a.mediafile{ margin-right:0; margin-left:1.5em; } #media__content img.btn{ vertical-align:text-bottom; } #media__content div.example{ color:#666; margin-left:1em; } #media__content div.detail{ padding:.2em 0; } #media__content div.detail div.thumb{ float:left; margin:0 .5em 0 18px; } [dir=rtl] #media__content div.detail div.thumb{ float:right; margin:0 18px 0 .5em; } #media__content div.detail div.thumb a{ display:block; cursor:pointer; } #media__content div.detail p{ margin-bottom:0; } #media__content form.meta div.metafield{ clear:left; margin-bottom:.5em; overflow:hidden; } [dir=rtl] #media__content form.meta div.metafield{ clear:right; } #media__content form.meta label{ display:block; width:25%; float:left; font-weight:bold; clear:left; } [dir=rtl] #media__content form.meta label{ float:right; clear:right; } #media__content form.meta .edit{ float:left; width:70%; margin:0; } [dir=rtl] #media__content form.meta .edit{ float:right; } #media__content form.meta textarea.edit{ max-width:70%; min-width:70%; } #media__content form.meta div.buttons{ clear:left; margin:.2em 0 0 25%; } [dir=rtl] #media__content form.meta div.buttons{ clear:right; margin:.2em 25% 0 0; } #mediamanager__page h1{ margin-bottom:.5em; } #mediamanager__page{ min-width:50em; width:100%; text-align:left; } #mediamanager__page .panel{ float:left; } #mediamanager__page .namespaces{ width:20%; min-width:10em; } #mediamanager__page .filelist{ width:50%; min-width:25em; } #mediamanager__page .file{ width:30%; min-width:15em; } #mediamanager__page .panelHeader{ background-color:#eee; margin:0 10px 10px 0; padding:10px 10px 8px; text-align:left; min-height:20px; overflow:hidden; } #mediamanager__page .panelContent{ overflow-y:auto; overflow-x:hidden; padding:0; margin:0 10px 10px 0; position:relative; } [dir=rtl] #mediamanager__page .panelContent{ text-align:right; } #mediamanager__page .file .panelHeader,#mediamanager__page .file .panelContent{ margin-right:0; } #mediamanager__page .ui-resizable-e{ width:6px; right:2px; background:transparent url(./../../lib/images/resizecol.png) center center no-repeat; } #mediamanager__page .ui-resizable-e:hover{ background-color:#eee; } #mediamanager__page dd{ margin:0; } #mediamanager__page .panelHeader h3{ float:left; font-weight:normal; font-size:1em; padding:0; margin:0 0 3px; } [dir=rtl] #mediamanager__page .namespaces{ text-align:right; } #mediamanager__page .namespaces h2{ font-size:1em; display:inline-block; padding:.3em .8em; margin:0 0 0 .3em; border-radius:.5em .5em 0 0; font-weight:normal; background-color:#eee; color:#333; border:1px solid #ccc; border-bottom-color:#eee; line-height:1.4em; position:relative; bottom:-1px; z-index:2; } * html #mediamanager__page .namespaces h2,*+html #mediamanager__page .namespaces h2{ display:inline; } [dir=rtl] #mediamanager__page .namespaces h2{ margin:0 .3em 0 0; position:relative; right:10px; } #mediamanager__page .namespaces .panelHeader{ border-top:1px solid #ccc; z-index:1; } #mediamanager__page .namespaces ul{ margin-left:.2em; margin-bottom:0; padding:0; list-style:none; } [dir=rtl] #mediamanager__page .namespaces ul{ margin-left:0; margin-right:.2em; } #mediamanager__page .namespaces ul ul{ margin-left:1em; } [dir=rtl] #mediamanager__page .namespaces ul ul{ margin-left:0; margin-right:1em; } #mediamanager__page .namespaces ul ul li{ margin:0; } #mediamanager__page .namespaces ul .selected{ background-color:#ff9; font-weight:bold; } #mediamanager__page .panelHeader form.options{ float:right; margin-top:-3px; } #mediamanager__page .panelHeader ul{ list-style:none; margin:0; padding:0; } #mediamanager__page .panelHeader ul li{ color:#333; float:left; line-height:1; padding-left:3px; } [dir=rtl] #mediamanager__page .panelHeader ul li{ margin-right:0; margin-left:.5em; } #mediamanager__page .panelHeader ul li.listType{ padding-left:30px; margin:0 0 0 5px; background:url(./../../lib/images/icon-list.png) 3px 1px no-repeat; } #mediamanager__page .panelHeader ul li.sortBy{ padding-left:30px; margin:0 0 0 5px; background:url(./../../lib/images/icon-sort.png) 3px 1px no-repeat; } #mediamanager__page .panelHeader form.options .ui-buttonset label{ font-size:90%; margin-right:-0.4em; } #mediamanager__page .panelHeader form.options .ui-buttonset .ui-button-text{ padding:.3em .5em; line-height:1; } #mediamanager__page .filelist ul{ padding:0; margin:0 10px 0 0; } [dir=rtl] #mediamanager__page .filelist ul{ margin:0 10px 0 0; } #mediamanager__page .filelist .panelContent ul li:hover{ background-color:#eee; } #mediamanager__page .filelist li dt a{ vertical-align:middle; display:table-cell; overflow:hidden; } * html #mediamanager__page .filelist .thumbs li dt a,*+html #mediamanager__page .filelist .thumbs li dt a{ display:block; } * html #mediamanager__page .filelist .rows li dt a,*+html #mediamanager__page .filelist .rows li dt a{ display:inline; } #mediamanager__page .filelist .thumbs li{ width:100px; min-height:130px; display:inline-block; display:-moz-inline-stack; margin:0 6px 10px 0; background-color:#ddd; color:#333; padding:5px; vertical-align:top; text-align:center; position:relative; line-height:1.2; } [dir=rtl] #mediamanager__page .filelist .thumbs li{ margin-right:0; margin-left:6px; } * html #mediamanager__page .filelist .thumbs li,*+html #mediamanager__page .filelist .thumbs li{ display:inline; zoom:1; } #mediamanager__page .filelist .thumbs li dt a{ width:100px; height:90px; } #mediamanager__page .filelist .thumbs li dt a img{ max-width:90px; max-height:90px; } #mediamanager__page .filelist .thumbs li .name,#mediamanager__page .filelist .thumbs li .size,#mediamanager__page .filelist .thumbs li .filesize,#mediamanager__page .filelist .thumbs li .date{ display:block; overflow:hidden; text-overflow:ellipsis; width:90px; white-space:nowrap; } #mediamanager__page .filelist .thumbs li .name{ padding:5px 0; font-weight:bold; } #mediamanager__page .filelist .thumbs li .date{ font-style:italic; white-space:normal; } #mediamanager__page .filelist .rows li{ list-style:none; display:block; position:relative; max-height:50px; margin:0; margin-bottom:3px; background-color:#fff; color:#333; overflow:hidden; } #mediamanager__page .filelist .rows li:nth-child(2n+1){ background-color:#ddd; } #mediamanager__page .filelist .rows li dt{ float:left; width:10%; height:40px; text-align:center; } #mediamanager__page .filelist .rows li dt a{ width:100px; height:40px; } #mediamanager__page .filelist .rows li dt a img{ max-width:40px; max-height:40px; } #mediamanager__page .filelist .rows li .name,#mediamanager__page .filelist .rows li .size,#mediamanager__page .filelist .rows li .filesize,#mediamanager__page .filelist .rows li .date{ overflow:hidden; text-overflow:ellipsis; float:left; margin-left:1%; white-space:nowrap; } #mediamanager__page .filelist .rows li .name{ width:30%; font-weight:bold; } #mediamanager__page .filelist .rows li .size,#mediamanager__page .filelist .rows li .filesize{ width:15%; } #mediamanager__page .filelist .rows li .date{ width:20%; font-style:italic; white-space:normal; } #mediamanager__page div.upload{ padding-bottom:.5em; } #mediamanager__page .file ul.actions{ text-align:center; margin:0 0 5px; padding:0; list-style:none; } #mediamanager__page .file ul.actions li{ display:inline; margin:0; } #mediamanager__page .file div.image{ margin-bottom:5px; text-align:center; } #mediamanager__page .file div.image img{ width:100%; } #mediamanager__page .file dl{ margin-bottom:0; } #mediamanager__page .file dl dt{ font-weight:bold; display:block; background-color:#eee; } #mediamanager__page .file dl dd{ display:block; background-color:#ddd; } #mediamanager__page form.meta div.row{ margin-bottom:5px; } #mediamanager__page form.meta label span{ display:block; } #mediamanager__page form.meta input{ width:50%; } #mediamanager__page form.meta input.button{ width:auto; } #mediamanager__page form.meta textarea.edit{ height:6em; width:95%; min-width:95%; max-width:95%; } #mediamanager__page #page__revisions ul{ margin-left:10px; padding:0; list-style-type:none; } #mediamanager__page #page__revisions ul li div.li div{ font-size:90%; color:#666; padding-left:18px; } #mediamanager__page #page__revisions ul li div.li input{ position:relative; top:1px; } #mediamanager__diff table{ table-layout:fixed; border-width:0; } #mediamanager__diff td,#mediamanager__diff th{ width:48%; margin:0 5px 10px 0; padding:0; vertical-align:top; text-align:left; border-color:#fff; } [dir=rtl] #mediamanager__diff td,[dir=rtl] #mediamanager__diff th{ text-align:right; } #mediamanager__diff th{ font-weight:normal; background-color:#fff; line-height:1.2; } #mediamanager__diff th a{ font-weight:bold; } #mediamanager__diff th span{ font-size:90%; } #mediamanager__diff dl dd strong{ background-color:#ff9; color:#333; font-weight:normal; } #mediamanager__page .file form.diffView{ margin-bottom:10px; display:block; } #mediamanager__diff div.slider{ margin:10px; width:95%; } #mediamanager__diff .imageDiff{ position:relative; } #mediamanager__diff .imageDiff .image2{ position:absolute; top:0; left:0; } #mediamanager__diff .imageDiff.opacity .image2{ opacity:0.5; } #mediamanager__diff .imageDiff.portions .image2{ border-right:1px solid red; overflow:hidden; } #mediamanager__diff .imageDiff.portions img{ float:left; } #mediamanager__diff .imageDiff img{ width:100%; max-width:none; } .qq-uploader{ position:relative; width:100%; } .qq-uploader .error{ color:#f00; background-color:#fff; } .qq-upload-button{ display:inline-block; text-decoration:none; font-size:100%; cursor:pointer; margin:1px 1px 5px; } * html .qq-upload-button,*+html .qq-upload-button{ display:inline; } .qq-upload-button-focus{ outline:1px dotted; } .qq-upload-drop-area{ position:absolute; top:0; left:0; width:100%; height:100%; min-height:70px; z-index:2; background:#ddd; color:#333; text-align:center; } .qq-upload-drop-area span{ display:block; position:absolute; top:50%; width:100%; margin-top:-8px; font-size:120%; } .qq-upload-drop-area-active{ background:#eee; } div.qq-uploader ul{ margin:0; padding:0; list-style:none; } .qq-uploader li{ margin:0 0 5px; color:#333; } .qq-uploader li span,.qq-uploader li input,.qq-uploader li a{ margin-right:5px; } .qq-upload-file{ display:block; font-weight:bold; } .qq-upload-spinner{ display:inline-block; background:url(./../../lib/images/throbber.gif); width:15px; height:15px; vertical-align:text-bottom; } .qq-upload-size,.qq-upload-cancel{ font-size:85%; } .qq-upload-failed-text{ display:none; } .qq-upload-fail .qq-upload-failed-text{ display:inline; } .qq-action-container *{ vertical-align:middle; } .qq-overwrite-check input{ margin-left:10px; } .dokuwiki .tabs > ul,.dokuwiki ul.tabs{ padding:0; margin:0; overflow:hidden; position:relative; } .dokuwiki .tabs > ul:after,.dokuwiki ul.tabs:after{ position:absolute; content:""; width:100%; bottom:0; left:0; border-bottom:1px solid #ccc; z-index:1; } .dokuwiki .tabs > ul li,.dokuwiki ul.tabs li{ float:left; padding:0; margin:0; list-style:none; } [dir=rtl] .dokuwiki .tabs > ul li,[dir=rtl] .dokuwiki ul.tabs li{ float:right; } .dokuwiki .tabs > ul li a,.dokuwiki ul.tabs li strong,.dokuwiki ul.tabs li a{ display:inline-block; padding:.3em .8em; margin:0 0 0 .3em; background-color:#ddd; color:#333; border:1px solid #ccc; border-radius:.5em .5em 0 0; position:relative; z-index:0; } [dir=rtl] .dokuwiki .tabs > ul li a,[dir=rtl] .dokuwiki ul.tabs li strong,[dir=rtl] .dokuwiki ul.tabs li a{ margin:0 .3em 0 0; } .dokuwiki ul.tabs li strong{ font-weight:normal; } .dokuwiki .tabs > ul li a:hover,.dokuwiki .tabs > ul li a:active,.dokuwiki .tabs > ul li a:focus,.dokuwiki .tabs > ul li .curid a,.dokuwiki .tabs > ul .active a,.dokuwiki ul.tabs li a:hover,.dokuwiki ul.tabs li a:active,.dokuwiki ul.tabs li a:focus,.dokuwiki ul.tabs li strong{ background-color:#eee; color:#333; text-decoration:none; font-weight:normal; } .dokuwiki .tabs > ul li .curid a,.dokuwiki .tabs > ul li .active a,.dokuwiki ul.tabs li strong{ z-index:2; border-bottom-color:#eee; } .dokuwiki a.wikilink2{ text-decoration:none; } .dokuwiki a.wikilink2:link,.dokuwiki a.wikilink2:visited{ border-bottom:1px dashed; } .dokuwiki a.wikilink2:hover,.dokuwiki a.wikilink2:active,.dokuwiki a.wikilink2:focus{ border-bottom-width:0; } .dokuwiki span.curid a{ font-weight:bold; } .dokuwiki a.urlextern,.dokuwiki a.windows,.dokuwiki a.mail,.dokuwiki a.mediafile,.dokuwiki a.interwiki{ background-repeat:no-repeat; background-position:0 center; padding:0 0 0 18px; } .dokuwiki a.urlextern{ background-image:url(./../../lib/images/external-link.png); } .dokuwiki a.windows{ background-image:url(./../../lib/images/unc.png); } .dokuwiki a.mail{ background-image:url(./../../lib/images/email.png); } [dir=rtl] .dokuwiki a.urlextern,[dir=rtl] .dokuwiki a.windows,[dir=rtl] .dokuwiki a.mail,[dir=rtl] .dokuwiki a.interwiki,[dir=rtl] .dokuwiki a.mediafile{ background-position:right center; padding:0 18px 0 0; display:inline-block; } #dw__toc{ float:right; margin:0 0 1.4em 1.4em; width:12em; background-color:#eee; color:inherit; } [dir=rtl] #dw__toc{ float:left; margin:0 1.4em 1.4em 0; } .dokuwiki h3.toggle{ padding:.2em .5em; font-weight:bold; } .dokuwiki .toggle strong{ float:right; margin:0 .2em; } [dir=rtl] .dokuwiki .toggle strong{ float:left; } #dw__toc > div{ padding:.2em .5em; } #dw__toc ul{ padding:0; margin:0; } #dw__toc ul li{ list-style:none; padding:0; margin:0; line-height:1.1; } #dw__toc ul li div.li{ padding:.15em 0; } #dw__toc ul ul{ padding-left:1em; } [dir=rtl] #dw__toc ul ul{ padding-left:0; padding-right:1em; } .dokuwiki ul.idx{ padding-left:0; } [dir=rtl] .dokuwiki ul.idx{ padding-right:0; } .dokuwiki ul.idx li{ list-style-image:url(./../../lib/images/bullet.png); } .dokuwiki ul.idx li.open{ list-style-image:url(./../../lib/images/open.png); } .dokuwiki ul.idx li.closed{ list-style-image:url(./../../lib/images/closed.png); } [dir=rtl] .dokuwiki ul.idx li.closed{ list-style-image:url(./../../lib/images/closed-rtl.png); } div.insitu-footnote{ max-width:40%; min-width:5em; } .dokuwiki div.footnotes{ border-top:1px solid #ccc; padding:.5em 0 0 0; margin:1em 0 0 0; clear:both; } .dokuwiki div.footnotes div.fn sup a.fn_bot{ font-weight:bold; } #dw__loading{ text-align:center; margin-bottom:1.4em; } .dokuwiki div.search_quickresult{ margin-bottom:1.4em; } .dokuwiki div.search_quickresult ul{ padding:0; } .dokuwiki div.search_quickresult ul li{ float:left; width:12em; margin:0 1.5em; } [dir=rtl] .dokuwiki div.search_quickresult ul li{ float:right; } .dokuwiki dl.search_results{ margin-bottom:1.2em; } .dokuwiki dl.search_results dt{ font-weight:normal; margin-bottom:.2em; } .dokuwiki dl.search_results dd{ color:#999; background-color:inherit; margin:0 0 1.2em 0; } .dokuwiki .search_hit{ color:#333; background-color:#ff9; } .dokuwiki .search_results strong.search_hit{ font-weight:normal; } .dokuwiki .search_results .search_sep{ color:#333; background-color:inherit; } .dokuwiki div.nothing{ margin-bottom:1.4em; } .dokuwiki form.search div.no{ position:relative; } .dokuwiki form.search div.ajax_qsearch{ position:absolute; top:0; left:-13.5em; width:12em; padding:.5em; font-size:.9em; z-index:20; text-align:left; display:none; } [dir=rtl] .dokuwiki form.search div.ajax_qsearch{ left:auto; right:-13.5em; text-align:right; } .dokuwiki form.search div.ajax_qsearch strong{ display:block; margin-bottom:.3em; } .dokuwiki form.search div.ajax_qsearch ul{ margin:0 !important; padding:0 !important; } .dokuwiki form.search div.ajax_qsearch ul li{ margin:0; padding:0; display:block !important; } .dokuwiki .changeType{ margin-bottom:.5em; } .dokuwiki form.changes ul li{ list-style:none; margin-left:0; } [dir=rtl] .dokuwiki form.changes ul li{ margin-right:0; } .dokuwiki form.changes ul li span,.dokuwiki form.changes ul li a{ vertical-align:middle; } .dokuwiki form.changes ul li span.user a{ vertical-align:bottom; } .dokuwiki form.changes ul li.minor{ opacity:.7; } .dokuwiki form.changes li a.diff_link{ vertical-align:baseline; } .dokuwiki form.changes li a.revisions_link{ vertical-align:baseline; } .dokuwiki form.changes li span.sum{ font-weight:bold; } .dokuwiki div.pagenav{ text-align:center; margin:1.4em 0; } .dokuwiki div.pagenav-prev,.dokuwiki div.pagenav-next{ display:inline; margin:0 .5em; } .dokuwiki table.diff{ width:100%; border-width:0; } .dokuwiki table.diff th,.dokuwiki table.diff td{ vertical-align:top; padding:0; border-width:0; background-color:#fff; color:#333; } .dokuwiki table.diff th{ border-bottom:1px solid #ccc; font-size:110%; font-weight:normal; } .dokuwiki table.diff th a{ font-weight:bold; } .dokuwiki table.diff th span.user{ font-size:.9em; } .dokuwiki table.diff th span.sum{ font-size:.9em; font-weight:bold; } .dokuwiki table.diff th.minor{ color:#999; } .dokuwiki table.diff_sidebyside th{ width:50%; } .dokuwiki table.diff .diff-lineheader{ width:.7em; text-align:right; } [dir=rtl] .dokuwiki table.diff .diff-lineheader{ text-align:left; } .dokuwiki table.diff .diff-lineheader,.dokuwiki table.diff td{ font-family:Consolas,"Andale Mono WT","Andale Mono","Bitstream Vera Sans Mono","Nimbus Mono L",Monaco,"Courier New",monospace; } .dokuwiki table.diff td.diff-blockheader{ font-weight:bold; } .dokuwiki table.diff .diff-addedline{ background-color:#cfc; color:inherit; } .dokuwiki table.diff .diff-deletedline{ background-color:#fdd; color:inherit; } .dokuwiki table.diff td.diff-context{ background-color:#eee; color:inherit; } .dokuwiki table.diff td.diff-addedline strong,.dokuwiki table.diff td.diff-deletedline strong{ color:#f00; background-color:inherit; font-weight:bold; } .dokuwiki .diffoptions form{ float:left; } .dokuwiki .diffoptions p{ float:right; } .dokuwiki table.diff_sidebyside td.diffnav{ padding-bottom:.7em; } .dokuwiki .diffnav a{ display:inline-block; vertical-align:middle; } .dokuwiki .diffnav a span{ display:none; } .dokuwiki .diffnav a:hover,.dokuwiki .diffnav a:active,.dokuwiki .diffnav a:focus{ background-color:#eee; text-decoration:none; } .dokuwiki .diffnav a:before{ display:inline-block; line-height:1; padding:.2em .4em; border:1px solid #ccc; border-radius:2px; color:#333; } .dokuwiki .diffnav a.diffprevrev:before{ content:'\25C0'; } .dokuwiki .diffnav a.diffnextrev:before,.dokuwiki .diffnav a.difflastrev:before{ content:'\25B6'; } .dokuwiki .diffnav a.diffbothprevrev:before{ content:'\25C0\25C0'; } .dokuwiki .diffnav a.diffbothnextrev:before{ content:'\25B6\25B6'; } .dokuwiki .diffnav select{ width:60%; min-width:9em; height:1.5em; } .dokuwiki .diffnav select option[selected]{ font-weight:bold; } .dokuwiki div.toolbar{ margin-bottom:.5em; } #draft__status{ float:right; color:#999; background-color:inherit; } [dir=rtl] #draft__status{ float:left; } #tool__bar{ float:left; } [dir=rtl] #tool__bar{ float:right; } div.picker{ width:300px; border:1px solid #ccc; background-color:#eee; color:inherit; } div.picker.pk_hl{ width:auto; } div.picker button.pickerbutton,div.picker button.toolbutton{ padding:.1em .35em; border-width:0; } .dokuwiki textarea.edit{ width:700px; min-width:100%; max-width:100%; margin-bottom:.5em; } .dokuwiki div.editBar{ overflow:hidden; margin-bottom:.5em; } #size__ctl{ float:right; } [dir=rtl] #size__ctl{ float:left; } #size__ctl img{ cursor:pointer; } .dokuwiki .editBar .editButtons{ display:inline; margin-right:1em; } [dir=rtl] .dokuwiki .editBar .editButtons{ margin-right:0; margin-left:1em; } .dokuwiki .editBar .summary{ display:inline; } .dokuwiki .editBar .summary label{ vertical-align:middle; white-space:nowrap; } .dokuwiki .editBar .summary label span{ vertical-align:middle; } .dokuwiki .editBar .summary input.missing{ color:#333; background-color:#fcc; } .dokuwiki div.preview{ border:dotted #ccc; border-width:.2em 0; padding:1.4em 0; margin-bottom:1.4em; } .dokuwiki .secedit{ float:right; margin-top:-1.4em; } [dir=rtl] .dokuwiki .secedit{ float:left; } .dokuwiki .secedit input.button{ font-size:75%; } .dokuwiki div.section_highlight{ margin:0 -1em; padding:0 .5em; border:solid #eee; border-width:0 .5em; } .dokuwiki .ui-widget{ font-size:100%; } [dir=rtl] #link__wiz_close{ float:left; } #link__wiz_result{ background-color:#fff; width:293px; height:193px; overflow:auto; border:1px solid #ccc; margin:3px auto; text-align:left; line-height:1; } [dir=rtl] #link__wiz_result{ text-align:right; } #link__wiz_result div{ padding:3px 3px 3px 0; } #link__wiz_result div a{ display:block; padding-left:22px; min-height:16px; background:transparent 3px center no-repeat; } [dir=rtl] #link__wiz_result div a{ padding:3px 22px 3px 3px; background-position:257px 3px; } #link__wiz_result div.type_u a{ background-image:url(./../../lib/images/up.png); } #link__wiz_result div.type_f a{ background-image:url(./../../lib/images/page.png); } #link__wiz_result div.type_d a{ background-image:url(./../../lib/images/ns.png); } #link__wiz_result div.even{ background-color:#ddd; } #link__wiz_result div.selected{ background-color:#eee; } #link__wiz_result span{ display:block; color:#666; margin-left:22px; } #media__popup{ display:none; } #media__popup_content p{ margin:0 0 .5em; } #media__popup_content label{ margin-right:.5em; cursor:default; } #media__popup_content .button{ margin-right:1px; cursor:pointer; } .dokuwiki form{ border:none; display:inline; } .dokuwiki label.block{ display:block; text-align:right; font-weight:bold; } [dir=rtl] .dokuwiki label.block{ text-align:left; } .dokuwiki label.simple{ display:block; text-align:left; font-weight:normal; } [dir=rtl] .dokuwiki label.simple{ text-align:right; } .dokuwiki label.block select,.dokuwiki label.block input.edit{ width:50%; } .dokuwiki label span{ vertical-align:middle; } .dokuwiki fieldset{ width:400px; text-align:center; border:1px solid #ccc; padding:.5em; margin:auto; } .dokuwiki input.edit,.dokuwiki select.edit{ vertical-align:middle; } .dokuwiki select.edit{ padding:.1em 0; } .dokuwiki input.button,.dokuwiki button.button{ vertical-align:middle; } #dw__login label[for="remember__me"]{ margin-left:50%; margin-bottom:1.4em; } #dw__login fieldset,#dw__resendpwd fieldset,#dw__register fieldset{ padding-bottom:.7em; } #dw__profiledelete{ display:block; margin-top:2.8em; } #subscribe__form{ display:block; width:400px; text-align:center; } #subscribe__form fieldset{ text-align:left; margin:.5em 0; } [dir=rtl] #subscribe__form fieldset{ text-align:right; } #subscribe__form label{ display:block; margin:0 .5em .5em; } .dokuwiki ul.admin_tasks{ float:left; width:40%; list-style-type:none; font-size:1.125em; } [dir=rtl] .dokuwiki ul.admin_tasks{ float:right; } .dokuwiki ul.admin_tasks li{ padding-left:35px; margin:0 0 1em 0; font-weight:bold; list-style-type:none; background:transparent none no-repeat scroll 0 0; color:inherit; } [dir=rtl] .dokuwiki ul.admin_tasks li{ padding-left:0; padding-right:35px; background-position:right 0; } .dokuwiki ul.admin_tasks li.admin_acl{ background-image:url(./../../lib/images/admin/acl.png); } .dokuwiki ul.admin_tasks li.admin_usermanager{ background-image:url(./../../lib/images/admin/usermanager.png); } .dokuwiki ul.admin_tasks li.admin_plugin{ background-image:url(./../../lib/images/admin/plugin.png); } .dokuwiki ul.admin_tasks li.admin_config{ background-image:url(./../../lib/images/admin/config.png); } .dokuwiki ul.admin_tasks li.admin_revert{ background-image:url(./../../lib/images/admin/revert.png); } .dokuwiki ul.admin_tasks li.admin_popularity{ background-image:url(./../../lib/images/admin/popularity.png); } .dokuwiki #admin__version{ clear:left; float:right; color:#666; background-color:inherit; } [dir=rtl] .dokuwiki #admin__version{ clear:right; float:left; } } div.clearer{ clear:both; font-size:0; line-height:0; height:0; overflow:hidden; } .group{ display:inline-block; } .group{ display:block; } .group:before,.group:after{ content:""; display:table; } .group:after{ clear:both; } div.no{ display:inline; margin:0; padding:0; } .hidden{ display:none; } .medialeft{ float:left; } .mediaright{ float:right; } .mediacenter{ display:block; margin-left:auto; margin-right:auto; } .leftalign{ text-align:left; } .centeralign{ text-align:center; } .rightalign{ text-align:right; } em.u{ font-style:normal; text-decoration:underline; } em em.u{ font-style:italic; } html.swipebox-html.swipebox-touch{ overflow:hidden !important; } #swipebox-overlay img{ border:none !important; } #swipebox-overlay{ width:100%; height:100%; position:fixed; top:0; left:0; z-index:99999 !important; overflow:hidden; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none; } #swipebox-container{ position:relative; width:100%; height:100%; } #swipebox-slider{ -webkit-transition:-webkit-transform 0.4s ease; transition:transform 0.4s ease; height:100%; left:0; top:0; width:100%; white-space:nowrap; position:absolute; display:none; cursor:pointer; } #swipebox-slider .slide{ height:100%; width:100%; line-height:1px; text-align:center; display:inline-block; } #swipebox-slider .slide:before{ content:""; display:inline-block; height:50%; width:1px; margin-right:-1px; } #swipebox-slider .slide img,#swipebox-slider .slide .swipebox-video-container,#swipebox-slider .slide .swipebox-inline-container{ display:inline-block; max-height:100%; max-width:100%; margin:0; padding:0; width:auto; height:auto; vertical-align:middle; } #swipebox-slider .slide .swipebox-video-container{ background:none; max-width:1140px; max-height:100%; width:100%; padding:5%; -webkit-box-sizing:border-box; box-sizing:border-box; } #swipebox-slider .slide .swipebox-video-container .swipebox-video{ width:100%; height:0; padding-bottom:56.25%; overflow:hidden; position:relative; } #swipebox-slider .slide .swipebox-video-container .swipebox-video iframe{ width:100% !important; height:100% !important; position:absolute; top:0; left:0; } #swipebox-slider .slide-loading{ background:url(./../../applications/img/loader.gif) no-repeat center center; } #swipebox-bottom-bar,#swipebox-top-bar{ -webkit-transition:0.5s; transition:0.5s; position:absolute; left:0; z-index:999; min-height:50px; width:100%; } #swipebox-bottom-bar{ bottom:-50px; } #swipebox-bottom-bar.visible-bars{ -webkit-transform:translate3d(0,-50px,0); transform:translate3d(0,-50px,0); } #swipebox-top-bar{ bottom:100%; } #swipebox-top-bar.visible-bars{ -webkit-transform:translate3d(0,100%,0); transform:translate3d(0,100%,0); } #swipebox-title{ display:block; width:100%; text-align:center; } #swipebox-prev,#swipebox-next,#swipebox-close{ background-image:url(./../../applications/img/icons.png); background-repeat:no-repeat; border:none !important; text-decoration:none !important; cursor:pointer; width:50px; height:50px; top:0; } #swipebox-arrows{ display:block; margin:0 auto; width:100%; height:50px; } #swipebox-prev{ background-position:-32px 13px; float:left; } #swipebox-next{ background-position:-78px 13px; float:right; } #swipebox-close{ top:0; right:0; position:absolute; z-index:9999; background-position:15px 12px; } .swipebox-no-close-button #swipebox-close{ display:none; } #swipebox-prev.disabled,#swipebox-next.disabled{ opacity:0.3; } .swipebox-no-touch #swipebox-overlay.rightSpring #swipebox-slider{ -webkit-animation:rightSpring 0.3s; animation:rightSpring 0.3s; } .swipebox-no-touch #swipebox-overlay.leftSpring #swipebox-slider{ -webkit-animation:leftSpring 0.3s; animation:leftSpring 0.3s; } .swipebox-touch #swipebox-container:before,.swipebox-touch #swipebox-container:after{ -webkit-backface-visibility:hidden; backface-visibility:hidden; -webkit-transition:all .3s ease; transition:all .3s ease; content:' '; position:absolute; z-index:999; top:0; height:100%; width:20px; opacity:0; } .swipebox-touch #swipebox-container:before{ left:0; -webkit-box-shadow:inset 10px 0 10px -8px #656565; box-shadow:inset 10px 0 10px -8px #656565; } .swipebox-touch #swipebox-container:after{ right:0; -webkit-box-shadow:inset -10px 0 10px -8px #656565; box-shadow:inset -10px 0 10px -8px #656565; } .swipebox-touch #swipebox-overlay.leftSpringTouch #swipebox-container:before{ opacity:1; } .swipebox-touch #swipebox-overlay.rightSpringTouch #swipebox-container:after{ opacity:1; } @-webkit-keyframes rightSpring{ 0%{ left:0; } 50%{ left:-30px; } 100%{ left:0; } } @keyframes rightSpring{ 0%{ left:0; } 50%{ left:-30px; } 100%{ left:0; } } @-webkit-keyframes leftSpring{ 0%{ left:0; } 50%{ left:30px; } 100%{ left:0; } } @keyframes leftSpring{ 0%{ left:0; } 50%{ left:30px; } 100%{ left:0; } } @media screen and (min-width:800px){ #swipebox-close{ right:10px; } #swipebox-arrows{ width:92%; max-width:800px; } } #swipebox-overlay{ background:#0d0d0d; } #swipebox-bottom-bar,#swipebox-top-bar{ text-shadow:1px 1px 1px black; background:#000; opacity:0.95; } #swipebox-top-bar{ color:white !important; font-size:15px; line-height:43px; font-family:Helvetica,Arial,sans-serif; } #swipebox-top-bar div.caption{ font-size:13px; line-height:20px; } #swipebox-slider .slide-loading{ background:url(./../../lib/plugins/gallery/swipebox/img/loader.gif) no-repeat center center; } #swipebox-prev,#swipebox-next,#swipebox-close{ background-image:url(./../../lib/plugins/gallery/swipebox/img/icons.png); } #swipebox-overlay{ background:rgba(0,0,0,0.95); } #swipebox-top-bar{ padding:10px; } #swipebox-top-bar div.title{ font-size:15px; line-height:1.5em; } #swipebox-top-bar div.caption{ font-size:13px; line-height:1.5em; } body{ font-size:small; } body.default.page-on-panel,body.optional.page-on-panel{ background:#FDFDFD; } input,textarea,select{ font-weight:400; } #dw__badges{ margin-top:50px; } #dw__breadcrumbs hr{ margin:5px 0; padding:0; } #dw__toc li{ list-style-type:none; } #dw__toc,#dw__toc h3{ font-size:.95em; } #dw__toc h3{ margin:0; padding:5px; } #dw__toc h3 span{ display:none; } #dw__toc h3 strong{ padding-right:5px; } #dw__toc .panel-body{ padding-left:0; width:16em; } #dw__toc{ margin-left:20px; background-color:#FFF; width:auto; } #dw__toc ul.toc{ padding-left:15px; } #dw__toc div{ padding-left:15px; } #config__manager .label{ padding:0 !important; } #config__manager label{ color:initial; white-space:initial; font-size:1.2em; font-weight:initial; } #config__manager fieldset{ background:initial !important; margin:0 !important; padding:0 !important; } #config__manager .outkey{ margin:0 !important; padding:0 !important; font-size:1.2em !important; background:transparent !important; } #dw__breadcrumbs .breadcrumb{ background:transparent; border:0 none; } #dw__breadcrumbs,.dw__sidebar{ font-size:.9em; } #dokuwiki__content .page-header{ margin-top:10px; } #dw__logo{ margin-right:10px; } #dw__title{ display:inline-block; } #dw__tagline{ font-size:.6em; display:block; line-height:1em; white-space:nowrap; } .dw__sidebar .nav li a{ padding:5px 10px; } .dw__sidebar .nav li a.urlextern{ padding-left:28px; background-position:8px center; } .dw__sidebar h1,.dw__sidebar h2,.dw__sidebar h3,.dw__sidebar h4 .dw__sidebar h5{ margin:10px 0 5px 0; } .dw__sidebar h1{ font-size:1.8em; } .dw__sidebar h2{ font-size:1.6em; } .dw__sidebar h3{ font-size:1.4em; } .dw__sidebar h4{ font-size:1.2em; } .dw__sidebar h5{ font-size:1em; } .dw__sidebar .panel-heading{ cursor:pointer; } .dw__sidebar .page-header{ margin-top:0; } .breadcrumb{ margin:0; padding:0; } .curid{ font-weight:bold; } .page fieldset{ border:none; } .dokuwiki ul.idx{ padding-left:15px; } .dokuwiki textarea.edit{ font-family:Consolas,"Andale Mono WT","Andale Mono","Bitstream Vera Sans Mono","Nimbus Mono L",Monaco,"Courier New",monospace; } .back-to-top{ position:fixed; bottom:10px; right:10px; opacity:.8; } .alert{ padding:5px; margin-bottom:10px; } .dokuwiki .diffnav select{ height:auto; } a.iw_user{ background-image:none; padding:0 !important; } @media print{ #dokuwiki__content{ width:100%; } } .img-responsive{ display:inline; } .dokuwiki img.media{ margin:.2em 0; } .dokuwiki img.medialeft{ float:left; margin:.2em 1em .2em 0; } .dokuwiki img.mediaright{ float:right; margin:.2em 0 .2em 1em; } .dokuwiki img.mediacenter{ display:block; margin:.2em auto; } td.tags a.label{ font-size:85%; } #dw__translation .flag img{ padding-right:5px; } div.dokuwiki div.comment_wrapper{ margin:50px 0 0 0; padding:0; background-color:inherit; } div.dokuwiki #discussion__section{ color:inherit; text-decoration:none; } div.dokuwiki div.comment_wrapper div.panel-heading{ margin-top:0; padding:5px; clear:none; } div.dokuwiki div.comment_wrapper div.comment_body{ border:0 hidden; } div.dokuwiki div.comment_wrapper div.comment_buttons{ float:none; margin-top:initial; padding:5px; } span.approval_action{ display:block; } span.approval_action a,span.approval_approved a{ color:inherit; text-decoration:underline; } html,body{ background:url(./../..) no-repeat center fixed; background-size:cover; font-size:14px; } .panel{ background-color:#fff; background-color:rgba(255,255,255,0.8); background-image:none; } h1{ text-align:center; } a:hover,a:focus,table a:not(.btn),.table a:not(.btn){ text-decoration:none; } @media print{ div.error,div.info,div.success,div.notify,.secedit,.a11y,.JSpopup,#link__wiz{ display:none; } } js.php.t.bootstrap3.js000066400000000000000000000033741325274564300357430ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/exevar DOKU_BASE='/';var DOKU_TPL='/lib/tpl/bootstrap3/';var DOKU_COOKIE_PARAM={"path":"\/","secure":true};var DOKU_UHN=0;var DOKU_UHC=0;LANG={"willexpire":"Your lock for editing this page is about to expire in a minute.\\nTo avoid conflicts use the preview button to reset the locktimer.","notsavedyet":"Unsaved changes will be lost.","searchmedia":"Search for files","keepopen":"Keep window open on selection","hidedetails":"Hide Details","mediatitle":"Link settings","mediadisplay":"Link type","mediaalign":"Alignment","mediasize":"Image size","mediatarget":"Link target","mediaclose":"Close","mediainsert":"Insert","mediadisplayimg":"Show the image.","mediadisplaylnk":"Show only the link.","mediasmall":"Small version","mediamedium":"Medium version","medialarge":"Large version","mediaoriginal":"Original version","medialnk":"Link to detail page","mediadirect":"Direct link to original","medianolnk":"No link","medianolink":"Do not link the image","medialeft":"Align the image on the left.","mediaright":"Align the image on the right.","mediacenter":"Align the image in the middle.","medianoalign":"Use no align.","nosmblinks":"Linking to Windows shares only works in Microsoft Internet Explorer.\\nYou still can copy and paste the link.","linkwiz":"Link Wizard","linkto":"Link to:","del_confirm":"Really delete selected item(s)?","restore_confirm":"Really restore this version?","media_diff":"View differences:","media_diff_both":"Side by Side","media_diff_opacity":"Shine-through","media_diff_portions":"Swipe","media_select":"Select files\u2026","media_upload_btn":"Upload","media_done_btn":"Done","media_drop":"Drop files here to upload","media_cancel":"remove","media_overwrt":"Overwrite existing files","plugins":{"gallery":{"addgal":"Add namespace as gallery"}}};var toolbar=[]; opensearch.html000066400000000000000000000010071325274564300346460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/exe LemonLDAP::NG https://lemonldap-ng.org/lib/tpl/bootstrap3/images/favicon.ico images/000077500000000000000000000000001325274564300323175ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/libadmin/000077500000000000000000000000001325274564300334075ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesacl.png000066400000000000000000000020621325274564300346540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/adminPNG  IHDRĴl;IDAT8˵[lTU}Ι(ޠ7( -h@Ԡ\>|i A1MHKH}JFk U @ h (tfN9g/Dio;_kk)NxʍO(P`y,]|>_)4Az_$ܤ-Y:Ts˒dA ]ԁͻcrܺ&CY箌3|pmV76Σ>tQ#kb Ų1 #ȷϥeOijzA0|SЎmIV> D.mwNl5aŇXL5]m]ܽbH2qV<25Iy*Ĝy$dB_|Ӫ^*rap :ꥌV ٙy߼g{< GWy%Y[-.)'O$' ج"JdP*$$$L6mDll3vss%vdŊUĆA (st66xN@???q^\\agUiÂf,C \266KLNz==ٻX?v^^~wiچ||H΁b*" jjn#Վ!$$U̚3goFS03hz6 NL<|{b(,,˧p?100aN}V/Ù.˓s@\|AAKΡ-1T А.Qٻ'4uvjݭ}v֐J26z/)#yyppXvvXJCilA[[/RZ7^]݊46GG022[Њ=z"5+TKnQ tհ`GǷ?rM}nAýhjEmm;{Nw1 m zKW)™j0`>k!z-zPUՆ::k QO??2b"oON, Lf4ݣLZ6**Pz!cr@AUdHN>1hf b|>0R ""75!.G&W(Bm:-hrϡ88 qV5kGz?6ja'S*J(NgDd„(WEt(\$DGڜs(QW1ę3[aT,_wL!BbFf7F{k?'ȽjXuۼoZ$0WKF!k]j0|1;4C 'qχߥG[k/=f ֶ_pNoczԄm5Wa|:Z|Qa.:P"Vm QZV)w ~j3KXp$Sۼ[3l0NE ja*"<>sRC/TVZVVRtgăF]QP@'0v=>h/C/8D Lיp<__KJ*i}@Mmb42S^d/*a0O_S`9dT:n̟i㱲b<˧&ug&&, l⫽ ZJK.s-7g3)8p9 [vO(LdKs␍JËRHPE7Ttt0| nJwei?*31l1Fz BW). "IENDB`plugin.png000066400000000000000000000021471325274564300354170ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/adminPNG  IHDRĴl;.IDAT8˵MlEsfЖ6hA ?Eŝ6@Pر0BBi75Ԩ w, Q[ h Db " %~wf\Z[]x&Yɛ396@VGJ1)g{T\=fzCd#BIʹoג5c3ʹԂeQ]lekT@DH]JGm׵J`l:JCugY*5U՜rR/?Dr+>`mZ.vѴl&Klc>/XOs3L(`,I, 9kL1FX1܁yĉf0 D9|!e<"2bNzY\oעz\(VeW%շ,j:_#87hߣ"JRJycRۆ-@)YŤt z #\ioŃxq*ӵYE6>aH-F 篟gKAELLATFq;@F|L^1Ѱx!((ElR\@d"bQQ`'?@TPա-ȨQ|OkFQ\p `3LlU g0 HJ je*Ґ"0 N@]u_؄3a㩍D)aٜe,r7>>1D!Pq՞E-zOņ"W`,o\>i }C%+xgOU ̨ƇP ABڶ˱&VR*?5OJ'~^H/[f&jd0w=q;Dn)6wF~aquQ(BD>"[qwư[Ps9x$^ڋQR)s @&@#@o3Dɜ|֯57% K>o~O&UPDB|7ڷ95Y?{rO߭V-2n$p( P_7EIENDB`popularity.png000066400000000000000000000022451325274564300363300ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/adminPNG  IHDRĴl;lIDAT8˵]lSe=lkX!0l|1t/ wzgHLLD ⥗D$$…1FAcC6غn]kO9 d%zIOΛy!<qE::>^b>_jFG_[&hZR Z‡k;YawX{]Wp@ӌr:x!I :~ nj:J:ZVp +z9~ӧ_ھ[2s;"I[Ȓ͂[ᑷ6՘&h*2Ֆ՗~ٞ55eg%SiּFc]nFZ&4ov4ԯ~_ϝ:LQR| P D"Cx:F0f"44kU{۶? Y|2eHDDH$FJQscQ^ƻle$lzENU%Wzk/^"4e:2,+Xp: "JZ-ԬcsQk$"pqi9ˀhdSijă)aIK«׳˄]ՠp(v N|>ꢣseKKp8p{<6hQśVb΀CmuNXݚ׊t? o)h4(@,k89Sϝƍ^,17~XS߆BUQOJZY|X@&WQ]zY*(\<iÁ,冢ƦuP"٫3)='H(JA,dR@V!-;6H6[ݮ|`QأD >s*$2^E`D<1hiCʪ¤lmp@ 6H%a F_~gVD* ]s ߛ\B(.‰ǂ* gjxY兯gecƗG3*mkϺ wF qnºmte;`j '_Qijϧ#4nF+|"5EHsE ½%Hj=/kgWp=U%d&-p6PC"1&+(}+(3sr8 c fy*r6Jhsq.a{:y 9r,<'qpmY2>e ֡(C MP?h,_. ϕ7~:pZBzN/$3i_ǴqhV g9ZrS%BlW\Ѣ=+]>%ʪ+J0BPOf6D^ӥ`38zBYڞ:L"و/lXAI jG]zDPQu:7Ou2D^S &mQݸ1 U6@:$+c ;/#>F) rv3ˏjXg'N~w#^{57c;'Iے_5U4KLxj^.^.c)LϾ E 6ۏ?ie$gM&^E7?B80[neiK۵nӎZ?k/RV,PZ&~pځWHb:4ؾ*lbϚ)NJ}hӸA(w0Q ufm]n[vvT$@ g h$ WcAF!nHnjQ&pz%' 0\;'IENDB`usermanager.png000066400000000000000000000026731325274564300364360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/adminPNG  IHDRĴl;IDAT{l]u9w=޾v]׮+k  B@p3NP|dF4 f!$0_8n݀ ec}s1? ?dl1&ս+7}uMM:*跆Ο=ʹ߾>iI͢0fqChT,ٳ=(Cimz·~?IXhIt~':KrJHX{/?=}) $D"\6WRY8.zk߬xy.pmE8}5?5,E 0hYBEe|f_~뾾vABp7瞾{?x]Y f O[)F bKT׮%0@FhОQl*d8JC7}_齫ŗ +0Xc'sh0@K:VqSGmC.cAED3[)Qe %yo>KcA10@EE( JFK[8@APV4,`ƅWg&w'uJGJ-,Ҡ oM! |ݒ6Wj\$'i{n7A苏R(\$#[ Juc_"]/}??:,+t|UM=O٨riu})XYKnΡjA<(Q(Z[6d4cHWHtUbx崃i3v L6=|_խ``filZn޻ 99*i,f`Q9m$1TA;aY=owYӔF |c˦?owSSOJc[N%3kc1 X-.7uIY %`A4cέN7<dZ+V") ki&Ld,/ۂCYB-NͲ;ɧOwoݲK8% :b|FQM^L aiPA-T-L|c~tFMnE*3i2FHp& LEJCWjB"oaaGĵj !-EoOoo.28 -&APxX,2ڿ{ /T{-`KH( 0YA4D=8}X{g. ;/`ٵ[w4n&][&0 )cJ<3\<~4?u/!2|Wzn|ʮ`XQB2Sp,dE~IENDB`bullet.png000066400000000000000000000001601325274564300343110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRPLTE~ tRNS@fIDATxu! ZHn)uIENDB`closed-rtl.png000066400000000000000000000001661325274564300351000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDR sPLTE~ tRNS@fIDATx= ̡P(kg)?{IENDB`closed.png000066400000000000000000000001671325274564300343020ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDR sPLTE~ tRNS@fIDATxEʱ 0 "աS~f@IENDB`email.png000066400000000000000000000012231325274564300341120ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRabKGD pHYs  tIME$ { IDAT8SjA}s;sT@BR$5)R T& _'D"\tL .\ w=8]BH `7`ooϿ0 HFhMe_ Rx|9"CymE>3oՓ*_.""tnl=}Ƙij]z bt.NNNBuLyoOf?ں3Ɛ186訚#f. k-9D)Eιd2FOqKѐht>LsRDDd9稜^o*wfie()>9MI}w_y(d\9Gk~a`("IIsPJIA`Z jX."z(P$!5jxp||,gggfXZ Db(Ύ@\__LC9= jZk`Uq?BD= >XIENDB`error.png000066400000000000000000000012101325274564300341500ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRaOIDAT8˥Ka SAoc6.PDC Dբ@Zxh1N"CN:vf}󶰾6uvyy1=!4ИvCv$ HS8d*P. M`+Sذnv~cHVr}ފOKW55Bb `l`yj`yk1AjE @EoqS2FE64(l=$6#1![?@Dw$-WfW&D$Fn>SRJuZNWc#di@% b_s Rۆ^t&:?!DmSQeJWeJÈqMT 'DB:RE_as3ȯC2Vz9W[9ŢwU*B4!B|zPJ fVcdEmZVw!Opřzp!MuS>x9f0Uއx8GHv=}uʡGBy=-Ka J8K+${?`vLЉ37ӿѯLjIENDB`external-link.png000066400000000000000000000014601325274564300356030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRabKGD pHYs  tIME ^hIDAT8˥OHqƟͽ/:KٻڜAYyP$vP#Cz2cC*!:NHH! lA FmA;ݻw{_6s>O߆~c2.pnƖi ht= _^FD.t:]H&2̷᙭CCC~1ֿ-~guV"Xb" % Wl6{K`p`dddKUշx ^26EyNDޗNsΛ<[ZZ|j# DEX*#IRA4jfP "+󨩩D"1":vccb|p8HQ+qHR3ccc=.DB.ssgEQ0@Dy4559$l '"ڟJE #.۫R1\.x|ήt]r󽽽Ѡitl`yyyeuuydb|X|mXt]G @0jll|wt:*“5 "b8Ckk+ IDTYu,˷nnnV"nC$477^zJDyL, ø  =s:JC~LIENDB`fileicons/000077500000000000000000000000001325274564300342725ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images7z.png000066400000000000000000000006371325274564300353460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRafIDAT8˝1N0Q`Ru`l.ނ;TjT`c)TMXyP5)/Y>OV$Sm>~Vd Yl4]eYDߍ4M9G<ee.cn6rV+.K. 9'I }ڥCk-14p^3"aHa$AtU +<IhZ3|߿3Txh4yCDn:H.ZJ\˵`;nuxyAPvӡڋ }:Aϫ<zAJR=[?%C ^J,^߅kHVn(,K+"iEx|Or`XͲir\rXPD89LviC֚jjb$"<ϙe(pu f&PUHBD``0hFk*|߇J)z=$I "ð68+63lbwn\8|;4<zKز>h9T3oE2KIENDB`cc.png000066400000000000000000000006331325274564300353670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRabIDAT8˥SN@ߙA&JA>A L ))DRф8>@1H+iGZ$<=m>Gd Y|4]fYDu).ErC821u咋ł"lhT|lXDhcZE00Iq t9W&<IhZf0Ta*RpAkF(9Dv]*Po/\\8p_wxrꊟϯm⣋/͝?o?nP^&?opɍwZ;fyUι _@-NbIENDB`conf.png000066400000000000000000000006221325274564300357250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaYIDAT8˥S;N@Z m(g@Je!RRaGHAMz VߑF>{ڄ$$sc)Fd u] "X5 E'p4M(˒ánvfp^sXt"ǣ]s9G=`VUŲ,iHNC]m[{h3#Mӛ)PJ!5@۶wSx9Z^~drc 21"V%HZ1ߝ ʲ؃PU˿_!9W'VkCp8MN u1 LyIENDB`cpp.png000066400000000000000000000006561325274564300355710ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRauIDAT8˝SKN@}$VDŶ1˙LrRpa`RpvlB8n\rJ*_uꮀ$ B|l1'%du|U<9P$ΊpirԾZ\.X,("L&H31Zchzf$",˘)(:px3\($!"֢l # _o sZj!Ie A}*ruZoJq׳ $ECEC]?:}$FS F:i0n|2+0JY^LlW89"hlZ{5s.kۿo{_ _jJIENDB`cs.png000066400000000000000000000006431325274564300354100ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRajIDAT8˝S1N@ߙA&JA@>~A H>@G&|9J+ivIx|!79dqIteVe7ie.c.Usf3NS' d}S1Zchb`E Ð"$I1 6sF Fdg_ SJB)֨jyCDl6?I6 pqZp;.\9jit/A/AƯot*pjm]2Qڽ~yd6z"Nz]M` ߿p|[Ʈ^k9soc[@N'IENDB`csh.png000066400000000000000000000006531325274564300355610ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRarIDAT8˝S;N@}5 *M"A \&܂; %$p:7qg(-ۄHٙfvV㑄y x%HA,˜pix|@fY8j*uXp>s6QD8N9LJjk"Bc 4r(!EI0cAp5sE N6 > L)U J)8砵FBE"nA$(ayw0 8dӕQT6; ]U*8s6QD8N9LJji"Bc 4r(!EI0cAp5se sZNSG* sZj!"yAi ⬵BsNp{t``T&/o *yxvYK^oo w%.k7q9u}F;6EM bnwOW^eOȝsI>@<IENDB`csv.png000066400000000000000000000006311325274564300355730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRa`IDAT8˥S;n@ZѹA)r22>F&)SC`z州-ہ|f# x'YHօiQ,Erp@ʲtYt:5v8("l6\VH{Ck-14x<2MS&IBa̲Q-x=W\ $!""2|߿0TT(i*H_&׾ֺׂM.G& j0DMS;AL69h4K8;\T>gs͵ yo־h~9{' v)b}IENDB`deb.png000066400000000000000000000006451325274564300355370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRalIDAT8˝SN0=ԥ~BCvP"fiq\0TRSG8׾IA"| @zEQ,˲"}( .yY62Z.\,f3FHs֚jjbL"<ϙe8ZuTUcjvEŧa蚊0 aR Fi*FpdWJNpp@ 8r~yMv>?:A~r=َ/~|z??ٯ <|uB?/KL<IENDB`diff.png000066400000000000000000000006511325274564300357120ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRapIDAT8˝S=O@}޵t](JЙ_@HIWI &xŝ$%'4oFc5IABlG d Y%yϊ"M9=#YQ>MS.f)' EYnLDhsZZk9ϙ$ 802iJc=`M{_ʲIshZ+)B)=h4HeYBDn T+_k+:֋-+'~='C/2E^ߍªro&Ba66tծVLՑ\)'wA\_yct}/l^kg/O%IENDB`doc.png000066400000000000000000000006321325274564300355460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaaIDAT8˥MN0E첩`*Tv\&H"wAeǢMi6M4q Д 3HgϳHB)gz2$ɰ1jS׵8L$;q]׾( v7 5WE鴅_m,"9Gk-nYQDX%`{&Is#F׻": 50Ơ#s4Mp8BsXc:q9[7]1ŃL0I:E␛Q4'٬-`lI<=i PHǠAcøSS/~8b~snn9h ;ǿEdk|WIENDB`docx.png000066400000000000000000000006371325274564300357430ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRafIDAT8˝MN0E첩`*;~.cH]0r6UiqX WMe=cHB)z2$ɰ0jS׵8L\$[뺮}Qv{l6\\V.KN=䰵k9Zkive̲"²,Y4}p:w>4 HBDCE-1zATh὇1~yi7 ani!M< vǘ//I$i3IЍC.1E# j6km8g#-d@C}1`)DMs9ŗǀ!@Xd}G% YH(Ҳ,ObHܔe,ph4MSv;n[7 eCnFDhc$ 80sfY(sIhR*RpAk`$IPUDa~t Z5aE<d @/s$ u%9EٌѨTji֚ZkV+q(("LӔI0 ;XkFQ 1Vk# . Z 8FQOḪVJmp?dl3ϛ_hl3WtutDUCo2;cQAN/1}~z*3VJ1ZaMf߫=}r>QH IENDB`h.png000066400000000000000000000006331325274564300352310ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRabIDAT8˝NAE0Z }` `E-,HAmn~ sI&;̟G  [d _$iD1:,^_ްn]֒8loks\z<8'?El)pt,mZ{^oBJ\n:~\U ~Y IENDB`htm.png000077700000000000000000000000001325274564300372352html.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconshtml.png000066400000000000000000000006411325274564300357450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRahIDAT8˝SN0bG-bRu``"'a o+JĻH CTTMX9c@P!'Ǿ# `@$YH ,y;&ɲh`"FpKժb|lFt:x;o/7^;fƘV{^wZy zWݡPIENDB`java.png000066400000000000000000000006441325274564300357250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRakIDAT8˝S1N@ߙA&JA@>~ˆ@RPt4!n('y($i=G6?#N2,^,˜pipx @fY8j*mXp>s6QD8N9J1Cc %(b&I8 W;@ V Ͼ-L)U,J)8砵F@E"v%R( _k]9ed\p?wNœ`Pz;wn+xSے~:Qu[񋮛xྞ[M^ LJ<=yko}Znm s.i6^;w~Q\N {rBQq-IENDB`jpeg.png000066400000000000000000000006341325274564300357300ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRacIDAT8˥MN0E첩`?]$0r6miqX &IcϞHB)'z8@2leʋ,9$[|nX\V\.sN&{ 9ZkifaeLӔ"¢(98~p:}$!"p!V3z嗆iCS}dY!". Cli14]c6>ݽ[,"C''jD!I>K<>|`ZM?Ix:trx+fϏj7;ƘJP{`p:y sH:cIENDB`jpg.png000077700000000000000000000000001325274564300372062jpeg.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsjs.png000066400000000000000000000006441325274564300354200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRakIDAT8˝SKNA}=… =Ht^nL`1B xwn0az:S=υ"I%I:呄yp Hf,˜pin{"YȲq|tdxLh4b[,v!"4ZKc 1faRD$ 8fwsn$!"֢V}rM0T!*RpAkJ(9Dzdh.|u).vp 5 <A.1b<hu7ASjs6$.|Wi,]4n!6y/4:|𖮱e}Z@-w%j[9ƮU@FsIENDB`json.png000066400000000000000000000006631325274564300357560ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRazIDAT8˝=n@wp*5.D2e[1`CܥFDq, c v,7vvD@@+d â(eY:K( +'yY.2zm[n6kW'I iv."4ZKc 1vLӔIPD9,cWN9WIz)PsZkt:i "O6o:vſpwhu?88B۷s|8r7!އj5~ٲ4a7%6_~ps8^ku*\v Bk =1h6HeYBDtS@Ƙ85``?XG5!GptF8z6uxY8b5V)b5WſO3 ]x:Wz) /}Q~s.2\QZZV_|9|"SIENDB`mp4.png000066400000000000000000000006431325274564300355030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRajIDAT8˝MN0E첩`*;~.cpvPv,!شͦƱ2cҀ03cHB)gzRo$kɰ1.r[U~eYr2<8I.ys7nl^)"\.N =ڥZKr12iJaQI<8W)u 8Eс^`Z *~,P5Dclj {wn݌/5mv}5_!B4䙚!fܬy;>X6jvF݆4c@WaxTx᜛cNZ/|1|H9!IENDB`odc.png000066400000000000000000000006271325274564300355520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRa^IDAT8˥SN@D&rAoPxL>@J G E7q<vJ79" \,$8]Q^D]yl`@l Lip8Թnl("\לHsZKr3IqLaeLӔQ=ʲIseF׻:2Lk] 50Ơ#Ie p8 TliՕ`!& \u鴾Uw;c$ŢEjU_-_cd(z9e~0j94Ɯb{ |ku+IENDB`odf.png000066400000000000000000000006331325274564300355520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRabIDAT8˝Mn0mUw٠,PEw *")uA A8Vy]TIґF=EJ)0$YHVd\5#,VyT6./GZsfCȲ߿px[._Tcos cVz`p:֞E98IENDB`odg.png000066400000000000000000000006331325274564300355530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRabIDAT8˝S;n@} JHPD|.1rHPq.9D0^<"c( i7o4$Rp`-N Mj9Zkip`$",˘)(z0RW}($!"p!^D0u%*~$IP%D P5WocL+{awazfNN[}|A0fXqUW7XL:dktqw<@{ azx[c.0Zsx. ]qIENDB`odi.png000066400000000000000000000006161325274564300355560ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaUIDAT8˭S;N@}%M"gP\cppaE8M7qg(x4" \{Ið,MUU^Dxʲx<~0IFUU<Ϲl^Z("\,L&{F"Bk-sZv,˘)EEQ0s&I`W>Q5HBDCE-1:֍Z{c .,C]O)n|cL+n9$;87q|bt-5!xU)±(~8|973Ɯ`{_zs|. P{ݚKIENDB`odp.png000066400000000000000000000006241325274564300355640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRa[IDAT8˝S1n@! )xH <&KAp<)"[HJ+i=<n tp"$ be!s'" ( q.IN=wE͆Ţ"6ch1Gq(("LӔI0 "xwU(@"k-|ot*ER 9hv1`0| JpyZ7⒰! <x6 Us: 8^W-#U{eH]3v٤!5mpIZ^tc߿x[_6YkWZ?7vkW"Bk-sZ~4M$ Ey32qB&QUHBDC3 aֺ6Zkxa`0@ "xjq}6tuEXVO-t\tډŢ#ju]-hrޘ~ gwub;߯pExYT7^:Ƙ?Vypxz9:|wIENDB`odt.png000066400000000000000000000006231325274564300355670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaZIDAT8˭SAn0*nzPEo ǔzȅ B+LUVUi;޵gkI(@#Fy"¯"sf)+dK`TOӔ㱎~nvKz|>EDZ:hÁI0c,ch JxBY sAt:7 ZWBk =1vHeYBD0 >DN*r7ƴJ!!գn98|WfP7jhZ,Z+j"li Y_ C _'rE>Ea=htHՆ8œ[G.%5UW 3 @ɏ~=/On?uqֆlrgyy/dMSw-7 EiRZk霣Z{&I8)"̲i2 Gcx׉,A" hZWBk =1HeYBD0?AUscZ ΅lt;jZfVє:=ߗLt9RQ0R"}.#;sŮHx:U+ːnFX=ι1l0ܪ:?P9IENDB`pas.png000066400000000000000000000006631325274564300355700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRazIDAT8˝S1n@1Tj)\"ȥHH ?.lK(= ̑Q p37 H"  Eyop"C+sSdC`\KӔ^햛͆"jbQEZki1ݎI0c,ch4h <8DY ^aFLR 9ht$ ʲᅧ*rZ7p%XNwOת"/ ѤQtW矿SoΣU]&hzǡjw |>m1VD_TlW82uƑVk}Ҿځ(sY۽ ZypVIENDB`pdf.png000066400000000000000000000006511325274564300355530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRapIDAT8˝n0?U#C E:6y-A@/ୋa-aCAAR#}G@U 30"n+~jĢS]ND-JW3ps6q:RD8L8 W1Cc 0dq(-B+Y$DZR3*)B)֨VYADh4HV rpZ✰ȝW.k42Rc+ūz[m:s=\ ,5vVcW) vwl\'GlW8=ne}Ǝ^kMZIsqVVƹP 9([IENDB`png.png000066400000000000000000000006411325274564300355650ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRahIDAT8˥S;N@}%M3 ))lqBGA&xMAQ$3c @~(b[:EQp6=8IvʲYq7grp^SDZ8viW"Bk-sZn4M$ Ey32FQ\^*9 :  50`8"MSTUx< nص`"p?`2b\<D6?/q:D+W CW|1 _rZx[cN@GTyrH>'қIENDB`ppt.png000066400000000000000000000006341325274564300356060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRacIDAT8˝Mn0_*v٠, ^cHaJw]CtdC!q^ȈǓ,{3# @@$YH~Q۲,(d 'Y ʲYqvfzp\r:!W4PkM5w4e$y,@kޭGGUU 1a֊Y|wEB)V4MQUDni\[+j}gO %pol盯V@ӓ, r3Ѩд/hVyquoŋw1sMiTYkv=9CHTIENDB`pptx.png000066400000000000000000000006361325274564300360000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaeIDAT8˥N1ƿ7.d|b&2<@LP^{a vAYD/Lg2fI(@@ +J7EQ˲ "SV'Kd0(2dYvzjrIbt:CKs9G7 4e$y,h}p!}*{nhFպ0uThB1vi*z;P_ƘF\9X{-~aaǏN_uX裬Xf y_GƁѣק_]>jGy_"<:+sʪBth_$z ox~ˆt4!n('y(-;8ius7+G.?#N2,i.,s"¦HӔ@"Y38Ͳqz]rb|>p6q2&է1Cc W(b&I8 a+)H"Pv ر)ͦƱ2cQ%JBTO7{I(@$H(Mue^Dfir:8IY8nK[\V\.. fRzZKr0"aHa$A8Q }9HBDC߯5\hֺh*n(9D`'(1^0gWx{?"y>anU4 X<v*m<>x?8N/*xcWkAOzޕj@ >PIENDB`rb.png000066400000000000000000000006451325274564300354100ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRalIDAT8˝SN@߅A&JA>A L ))(@ J:7q<Vl^+tz$yh$n$4MpS&I`p@,i(j*rXp>s6QD8N9 ui"Bc 4r0 Eq3"UFιeHBD`E,QξMR 9hQ!,j}T rp~Zꜰk.8ܩ*x1VfWmzwu[U^Wm7]nKojVANpa_ LJm<=OmYkֻF[97s_ @tzIENDB`rpm.png000066400000000000000000000006371325274564300356040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRafIDAT8˝NA%`!`'2 gx_#-] 2ٝ7]EJ)PaHA+MEe^D4eInY8ZJ[,fNS~ZKr\2"aHa$A<h-{_<IsͨT*w. 50ƠZ""yAlbo ` zxx4zz/hˣHk'}1^qӧp}P[sw('ZɅ@V>ϥIENDB`rtf.png000066400000000000000000000006421325274564300355750ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaiIDAT8˝1N@Ewѹ\PpAG29wFQCЄ8^HvUUֺ{`C<p?hXVO/tz$>>\?^~|Q4V/z};3GN=hLk' 3xn/SкWڥk'T;p8 z9EH?kIIENDB`sh.png000066400000000000000000000006461325274564300354200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRamIDAT8˝S1N@ߙA&JA@>~ˆ@R:7q<Ȗiu{=G6#A2,.ziγ,s"M) .ErE421eiٌ"dhTTK;ch1. FQ0 )"Lq3]&0vΕ *1/Y<3rDQ5 .+IM8U5 EkƕexZݎ"zrytv'"4ZKc 1<,ySDXU˲ds7QxwΝmۂ$DZqk`00o*RpAkp(ж-DֽO4M<x?4NZ^ThNIo6q4ҝB7չιj4=Dw%/x߇IENDB`swf.png000066400000000000000000000006401325274564300355770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRagIDAT8˝;n1}@ ܩ\ ,2:F |[R(H#h>STVPH"y_)CR $q[Uծigf:gUUi>?7@(p4\hNVFfzbqtS73eUʲ~WeJTf(繒$ynshIu]3N? /*a#"YѶ-fx<a?dG_G`?z2a\>Kݸ/Lk\ts7 nCvɗ@Lv7=;SX=nzEч?9W Ik {IlIENDB`sxc.png000066400000000000000000000006441325274564300356010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRakIDAT8˝1N0QزTz 2=wzPRƱ9JCA/YyI( @x#YH,}UUNDxel60<TU7nDZki1ÁY1MS`Ld `:\3Q5HBD`EE'K EEpAk^,P5DxkO!I<ce6s2'~ۍ߁(mZ,x6~y9jQ-''( 6BO~w۰fMQR^㵵vι߫sa IENDB`sxd.png000066400000000000000000000006501325274564300355770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaoIDAT8˝=n0Iҩ1TE%{v!!Y9Dj,ò(BC-4$1y$Rp` eIabZi/"<|>p @eYp8nvfp^sX!ݭ݉tZKk-=`VUŲ,3[5ڶIsW(O E{c0Pڶ`2 bcL1/?Ǐuy9~}S("/L9Ê*_O6I\>x[c.oZ}5T;'g@ $,M,EIENDB`tar.png000066400000000000000000000006411325274564300355670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRahIDAT8˝N0ƿU`Ru``P(LR;a(Hl,Yi+| $@I'컟g9 @@x#YH^e<ϝ+˲ Ey$fjrbp>s2 "Bc 4z8EEi2Iax $s(@"k-vFSÔRPJ95&8FQt:-~[ p PlٝÐUG)t-ݏÐJR?lmߪ\VPWR%o<δ?] @K[ }/@Y@ӧTIENDB`tgz.png000066400000000000000000000006411325274564300356050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRahIDAT8˝S;N@}k%HTi" Bsp FЁ.@G&xZ1;HoGHB)"|o WdHE,ҋyQF7h,}e\׵/K. sgq-,m "9Gk-\VLӔIPD9,c7TK{_UU$D9zaDQti`Z0Th὇1Ni*H[ Clxp sL'wQ z"`W ]x:3AqڸDo{" ~wibpxÝj\snbEk>vu{H,4hIENDB`txt.png000066400000000000000000000006271325274564300356240ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRa^IDAT8˥SKN0}uM ΀,\Bj dQj6M4q ( -'왧y=IDQz)75d ˲TUE,Kf{g=˪|vm6kV+%y#.RDhsZZkne4( yNc=#ou]$D9FfzO SJB)=Ȳ u]CD0?D!9{~lbƘ;n L&,c1l#ݵc8N*$ kpx$ 8>*48V DQ~5ιgWW {_ 3Γ$Ҙ,}ۍIENDB`wav.png000066400000000000000000000006371325274564300356030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRafIDAT8˝nAEA) t~#` ,?L֝ !ahIA$Nt5wsF$ ?$5Hj'ʲuL,5KKnYibh^kZ̴\.5nLEQ*E(n$)seYb\K04 03b8vz_0{ߚQIӔi03Fr h7yE~ <+p'į5i۷rME3!Wɚ :.zssk{:y[1邳߯p;몪(>}jM! w H~"q` IENDB`webm.png000066400000000000000000000006341325274564300357350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRacIDAT8˝1n0 Vp Gt@D8Vw%dٿYV$[#='@Z<)‹/<Ϲ\.&-( )OSݒ$p~pqZՐfjS9Zkixdnj",˘)1 {뉲,A"a^`ATEECk~8Q%DTXk30/<nka̓dp [~U7_ˠ{œV 2d1o۵j{Fk}sj l0T;\ID_#:1YIENDB`xls.png000066400000000000000000000006371325274564300356140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRafIDAT8˝S;0}${N"Şn?1B&Ns4XeQz) W$3o~EJ)` H!YH6,OUUyᐔerp @̫yrr:x<y8("v\V-I7ZK|>32iJaQI,ܪ+}$!"p! ^3 aZ릩Z{c F,C]L$ss7 aC<xO>nuיqm{ gG#3r2a߫E2Ի+_]v:91\6{_{u/@ؗIENDB`xlsx.png000066400000000000000000000006511325274564300360000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRapIDAT8˝S;n0}${N"E`. :F.d0IClcXeXEh"!+r83o>$I(@ϑO i]ۦipHl6p@<"hƗe~vfzMj|>?tKZk霣Zv;E<)"eY2˲{gG?m 80(n`Z0Th὇1EQm[&I 1H3dOp4)kiUf)b1l<9jPŇ׃f>D?*`U׷p$x^.T7;瞌1o|Wob=#~IENDB`xml.png000066400000000000000000000006201325274564300355760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaWIDAT8˝SN@}Kxp77П38`̓~J(n:Aڔ(d2;o&3;$Rp ӈ,$ˋAe<Ͻ;ɲ@&yD0'I~_vfzpZq6U$҆"Bk-sZn8EEi2I} xW(@" 8jFպ0uTh὇1vq( 'I .mcѹ$|s[x0\2xrF]7}u= }*Rn@9~D$^?/Bն9c.Uc{.!Nf9eefIENDB`zip.png000066400000000000000000000006421325274564300356040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/fileiconsPNG  IHDRaiIDAT8˝N0U`Ru``l^} R0 $x6,M4s~( )~$,+PJ1~Vd Yl 4]gYEM)'"21miՊ"btZB]tZKk-7 (b&I8UeVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3gAMA|Q cHRMz%u0`:o_F+IDATxڴVkSAGhT(DG/CX) zE.KA BBɡ'{i)x1}ژeK^_LH}{3;.Yk1NQX_ _D&-ןhLuK' Gum6SO)vkkϰ!p {yYF `eJ<1-εg 7qJD[>G7o]@V!4POH k-|eq4&061>K=Ω\..00  UR " "CD9Pe{{ә?//}=A0lvan*fH+@`(AįG$$^,y'4yV"P'(B"Kji\AZw}(h@#_OA`nm~kxi4Fjwd%B]g4WcgwݐIENDB`icon-sort.png000066400000000000000000000004741325274564300347470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDR*ԠbKGDIDATx1ADrO}*H\G!9 FPh(Ѹe8 o2f6̛Yx!Tr0&Gϒ2!CePe}.ȝh;jS\_dn6Ϯ@*+}L@hRzg$| "?@?QhK^"|ϳY}{B@N4[ Dؖ[E'Rg2h/`K*X^IENDB`info.png000066400000000000000000000013251325274564300337610ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRaIDAT8˥kW?wLdL&1 6Ѩ(hR,M!R;TA.\tm\ >VtZ΢043$BH|=/_>8U}T!su4-WNV8 (wOo^uŕr#ɞF֮`!r pzyeHnVZԜგ[C*³2??\S +K;EСzrc%5*cb]3_槻i4|vQ @hԎdÅ"@IzSlՒ,Ѿ1AֆFޟXq AǏd'bβE.r`o+)ȶ6P)G!wGCqnfG SJy8ux8q8+g~jnBs14({^&xqXxXƘ0 `~Mqrd;;?ln]-G "8:Z &V#_M_G_8T-y/LZOr_wnYf .m[/-q_1rdߪr^LJ&KӼ~-<]0(Œ1n+iU ' 4`)7 r珁s?w ?{Y!IENDB`interwiki.png000066400000000000000000000006731325274564300350400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDR(-SPLTE "!")>'0'+,),,!-9>_'7G%=S=AC/UuyN3 &dd& iF'*;amazon.gif000077700000000000000000000000001325274564300406362amazon.de.gifustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiamazon.uk.gif000077700000000000000000000000001325274564300412542amazon.de.gifustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikicallto.gif000066400000000000000000000002611325274564300362700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89a0+^ u*Xᗘľ! ,^9q!Zۡt|*ET)uaHbAbQ XESX u.p01X|)@Lac ;coral.gif000066400000000000000000000001251325274564300361110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89a1 ! ,&Y4eSZxBe(, -3b;doku.gif000066400000000000000000000002741325274564300357600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89aoGL+KN7'sǴī޶}!,iIc9I4TQ(h,EpMCK5"9r8\aa ` QHA@0 &x *_`0d+h 2V;dokubug.gif000066400000000000000000000003021325274564300364460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89aYYYiiiOOOrrrGGG!,o,4KI`Χ@%!`H 0 ĥh8(@6 %_2EA%05APp=Hf  ;paypal.gif000066400000000000000000000002131325274564300362750ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89aSSSdedxyx!,Pxz+@kr2FA Md @&RhO* @@&­F<^xb `` !6'Z$;phpfn.gif000066400000000000000000000002441325274564300361260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89a 0,6Q:DjEMiTfbjXjbtgx偔Վ!,QI8i M,L*"l!x{ Ha0,PxxM"a+ x.`"e@u;sb.gif000066400000000000000000000003031325274564300354130ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89aze[M0(t $4DUȷo|Ǎ֤۳!,pIk]hUGA&Zq<H '")naK6UA3q+ `Qi@0pXk Z  +/~h i;skype.gif000066400000000000000000000002161325274564300361450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiGIF89axxx]]]!,Sx0@ @x1XZ* "YVFbX2 a@(V,)$B e:I !8ʙd[SpG;user.png000066400000000000000000000013451325274564300360130ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/images/interwikiPNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<wIDAT8˥mHQ *ֶVj)_OХ9]m*Y떩e-h-QJiLF2LJE8\9r~s)k­PiP.I$"EwDtR][)t$ҐB 4BtTPMjq1s M*nFj 4#/8A>PSPΞ S}|뺆y=9{sܔ:`;3evq/# ;us fEf q<^ք(ss7(`%|< ǗiXD%/X`a6.05 V);nǁ dN>*^?aW+jza)Zq\M9=Y[ ZR (mDV2’Jb 26TD]HCpލ C<^28;+Cu f5 7n:eF`Q( >%C%s2٥hid3@ A+YaU,Z1<@UZ%ձc_9]f'ɳ;fMj} ?iTrP76(&%JLJnUws"Zؾ 91چyM}VӺb䎪EgO\Z,;[1ەWS<àe|q||s1}7 p*xp49 e @;ڧXas$̼Ǵ ܚHT'U*KSdwfj^0&c0VvFE t<^\x'25b \1ZJ>QJC~1>l-L[a">zC1X1"&삽w1LPw) :W1`4Tb2wLοV%` x0WG\D:IrSJwSh%ɴvq2_HvIENDB`ns.png000066400000000000000000000014401325274564300334440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRaIDAT8}Kh\u{g̝4L)B1>n\F)PAD QTWf!"؍` HC0ijC1ͳLfw(v!g}69)wyc/T4"Գ X8ѫ̛vכ*WGU}[E>7&tJiG+*rQE>{ `=9o/rorgaw=MЪUQVcƏ;}ź;qD>r 7jT1aU&m86l7KVio{Ǥ9sVԨ,O"aWūmptk.h-V}֯K*R\M- Юޡ'O'CU0)79Ms&3P#V 'w'ů N,op#LRi4Qj h]GS\;QƤBH`1@<Ѐd^{9] !"dlǁD @|0mp  Q a+kK7 "$ h$?$BrN#&Cif N7SV[6laL/HbF6tX=ֽɕد}GO|뻗zUC5rh8D>)4Vwͦ|"L\>2FΫ[^yjW?'q{gBio.ȧTctܲ^&IENDB`open.png000066400000000000000000000001661325274564300337710ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRPLTE~ tRNS@fIDATxeƱ °E 4ڤwKМIENDB`page.png000066400000000000000000000011061325274564300337370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRa IDAT8s>{SG3;1Ј.bc#61lIAKD"$X$ TDyUIޥwO69DBכY}o*>'o{t:& ﱋoGgpfS=3Og~jUiִe[ǫ;kҏxOT$4ls1 w}؝>``ʨ2*c*u˽=v̝* D)ke{Oy,0CB0(4;.ݓ3*J 4 PC%# 0C/$4 I2O!E6k3tGBT%`YCam)6Ӱl0C/щ*ER4\g ˲ZW7{谆$$|ṵu/ٮ+Jt(ݾW ϛJ߸Pd makD|=G Vn6[Įd桚(Pm.0Q`'Fb#&ܧ6aP׏Q12[+zi; ]C17оpI9̾jD}›?7ayze,hXAK^3*bk @+wQ=!}uXzq:g쯺n= :d+_GTA;Ր Jƣ.!P)5!H:epր"݂"Kyw|{H2!i~3z_X;okBZK* ^R:O(jF*^ȰS诿_ gЬycIENDB`throbber.gif000066400000000000000000000013521325274564300346160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesGIF89a 888&&&֨fff䘘HHHvvvVVV! NETSCAPE2.0!Built with GIF Movie Gear 4.0!Made by AjaxLoad.info! ,L0Il`it Deb8 6@mF!NdGAZQp(3X-' qnO"! , UrRfp d9 n2 3 !9H5(IB#htO b ӥ:p? !AyYԌ! , Sa(0# BDG' ܈vAe 22 -a+0 f2pC褃rP #! , FPI0FW(ѝ%IavbG7<鴊P(qb845@;uPd}! , TID#0"X8E7! M%ɀ(& qn$"f  `1HK-@W)j! , S) jefh 1,#,UNƒ$KbLX6aS k@ˠYI%2 :8n";unc.png000066400000000000000000000010511325274564300336070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/imagesPNG  IHDRabKGD pHYs  tIMEIDAT8Rqf]ŵm=k?!y<=0k]<ˊbХ] $CTpqjK=qM'Nu; 3|73{ZCnaou۶O3 @af'nj~~|UU5( @JAp xH$^.ROAUUfsTH&DP(ucMd25MFQqZ=b(_,2 R)03˥_VC4µ,;ݳ,lN3>`08Nn+yޅyEcZ,="ty)|c>?ǏG ]YAbqQ*^. `^ 38Om7BSMџW#iii& HIIJ2G x2J0V]]MUPj8S^VcTVVcPibbB)55:;;)33hmmMvk*)).`ʣօR~uu%,mϜ|d6??/Y < >sss"@OOO1TQ< %ܧ5)zDL/W|Bʚ1jIQOwwX6 `m>??΅D\ڴ-3B q b333A@ݳSZ^^u $TH{ooO!33{@ z[mKJJMIfքJr*//t $,=)Ûj,e;Fqdt̏(0 %ϖ$0~cc Hh چ-e[S<,$~ե^x2+ g6 y0eh_<یmlll7_f-`HѓCfLj0ج͂ X=+`Mh!>/`a522/_bX%mw ͛~?' Uj@?%e3}#ZD|`s͉? Ww=8DN(N Yd \ WEPHp" q$(MQ84*N8C~'}kk߁Kݪu|;í ]0><>>Zm֧'_^^P_gu퇦%u49#X#N'_ÀXVc[]떖?=[FGG4^\\0.y8A^𝊈w?o1>==K>@տSG>UVv9A:Kt~~N777L/3_3}}}®I)88AK~#jPS)I8B5'kGb8 S<3떵x?Q:>Wl޾`7yMU=幽=έJKKc*YYYRa)33uax###iss͈g.$ؐ [ONNXHu-EEnf 2QMM 1txx/III{p8\X yQWWZ\\d=E7^?8A333?a ӊ&ěnIDvdcc:;;I61Bll,-,,\ 4 C`IGZW 8 nQg]_* R3 GI? ˴v$ýj3!! , $0eZy0q PУW )";qX^D50 Ո%`rJ{ 1$ʈ!! , $@e6$Ơ` 3*=  P\"F`P-d5V"2|?n"!( )e4xyc?   3 #wyJ l% o^[b_0 V T[0m $4>'VZ c3$X%!! , $`e:D3 H0,'j0Qs L(2HMj#ȉB \Oi`u=YEVL=I  > suI WJm| \"_b0 BcV"d]*K1" H|@B?I4# S$-||!! , $4ea:D hI /K$W- 0(`3F=pf@tQ  {f~*yS*mg) enu E^Z^ g@ kw(b& -w#" xW"t ##%U$`to!! , $4ea:* 1v/Kdzk#  F Y" % E  Cb AI4$ (z:2 mI Ll## F##>F!! , $4ea:* 1v/KdVtKG227D"$)Qqp8 y l |~6zw2j# F " % VC ]6a$ Q :2 \  EF I&x "͓F4$]#x!! , $4eZi䠒J16e E,C\3 ^3[ S|?!;sprite.png000066400000000000000000000077541325274564300432250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/dark_roundedPNG  IHDRw\WtEXtSoftwareAdobe ImageReadyqe<IDATx]ml@ |!&wF||( JT%bH$jRa!*E cPI 0N0 !3'w{wwJ۝}gvgޯ+x!d)}vNj'OB˨̣2ʄ(>!vnv?P.Q4P9m۶555y3K aM(40РaeIF`yL\RKy iVa00X,>PQOpj 'pL(**M޹{@@ Ƴ޷o߸E $DvmjQ<>Iq BTd[s 1@<+g (޽{Ν;3IׁB;̥L.*ԩ怣#CA &D=)Z4hC~٧O9ǐ?ېR: 8f ߿?WϫaaQPƒP|XGGy=sG[n  xÇ? \6 xxb ^p555s׮]7n9s1cp!={3dǎ˞|I6vyr" opM=p@8|p.O> h s|&?Pt.yxX/!tĈlӦM\&x⋬-[UTTU3lٲdڵl͚5`Æ .0uTVVփ+VpM8M4m_T_9h_p!#CL\wcP74K/ąY[[gwccc/GS g3xǎ͛||uu5 =={p石MB]]f/UyI-0 I$&ч rtD6p@vA.aÆ{!\l{=zh~ s/^`U~i{fvv)>cAK,a3g@^0jПI fICAV&PF<ˋ P%$ $0QC-~6ͨUQ1QMHt@}ej!j@ L҃N2bs`6~IY8N@z7<C= 4xGU+ uL#?ּ["@B3͆4^& RBvK (f޸Qɫūt.^Th>BEECh]CFW+\2ծ%A]:`gݼ:KAW*tk@_>Ǧc-Wa:,rݻ -.v0!RtU5 *t^v%9TE`j n" K4L]NdG&!J!ϩ`8| b@gvua}k@b'QPD$R ~T cP6dAy/ɝD>A&ԙ;zL&,T͋HC! bxLbM)w,NkGpd w;b;Oytk)AHm;1m~#]K>i0Ahג2),]j.^dvU,d@.",Ez~FmK@J ֔r!@AW^_]D ϢYx5`J tR 6RWTFɋ(Tꨜ~,`:P0 ´h6^e=w"?QFP5lUg`d2&;3-^u?䈏IhON(ūiAe2 gB ˮd!OA. FJ45DhTL x9?f Ih#Ҹa/"uZ4݈.d}Yh 6Lf&[ %(vI&0i59F ~uTL7Q -^}ULїA0WUH;]IBVɁORO/(1&q+ ?MBkPFB1cua5iqmE@Д R/˳4aᏰ+ u 0%BBk"UPoc]R凯 vB`(t8F?omT6!?C@8)R{ZuCjvmԳ_Ĝ<ξCCxw&JJ}>r1!(>uݒWRh/ߨQGPdքVn^|uޞFG1!ؿ5TpX|#FlcL%0 oT}oç(!|r?}@qYܿkjjΫ4eZ-l$[O+a5;,ȶ5Eߘ\ ~vsN*P٨) 6=GD'ÿo,?SE #8_a)?p$zk'?"4Bhma{ubMQA'%% K@`d;9jUIENDB`dark_square/000077500000000000000000000000001325274564300410345ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhotobtnNext.png000077700000000000000000000000001325274564300501632../dark_rounded/btnNext.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/dark_squarebtnPrevious.png000077700000000000000000000000001325274564300517572../dark_rounded/btnPrevious.pngustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/dark_squareloader.gif000077700000000000000000000000001325274564300475752../dark_rounded/loader.gifustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/dark_squaresprite.png000066400000000000000000000066631325274564300430630ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/dark_squarePNG  IHDRLw*EtEXtSoftwareAdobe ImageReadyqe< UIDATx]il.` `@F}E2 "h#BvIEX o$?"-! PVd22`q`npWTMgzFJ]z_uxnٲ 6eTKZN%J(P߽{^zԷofy ')7nZyeUVVr?kС`XA >΃!HL+(`1PvSvv6YYY7mt Sotƥt}S0͍14ԋ/8Pk AO PmNrG$Xi+1`iӆQ'`sYL7Dp?Y,;I3v/_d---ܲT$HN ~׬YN:tj6+Oss3z% T(>ctܙӨ}ׯj !<'rja۷3{vvMYPA% *xNki hޱcG6c ĉUUKXSLGz>ߺuw2&&\ӧO(z5VWW&Mğ߿Ǐ'|>K.>ݺuӿ6|pbCCz*-c Vwrt>rdALB9~8{ilCx ~4Ld Z W^lʕ\Y( K,a>deee(h///ϋw̙3l1W<|0WրIXǪ:$hM ,i0.0ZV bWwެSN|=z} jA<ׯ+rvuWm۶[n/ .p @9s&;v,L̽{UN+A(Q`,ղԉ r@)ҿe,=ŀ3>2VG?kn&;J $ӧƍ<~"#`Ao;%`j3<sep1N@Yt @GE, >rpPv\ݢr10tqMܭ@=ulbeQ18|aq23nk ܚj¸)u9sIxz<DŽKw$9rv- r ``%PȮXHPY_(ȫje@,9%Xu}nݺ&h]-ӵ4( TY S Љ^r[/ߪ/:=W6l~VC@BL2T,&nu h?zu#[y[4 |ۢ=+wxCuB· aq_CBZx wV]tS7欥NdPrnaqHhZ傐0Un3TM0u@K.Z7޻lצVjmmBM:K(w-E/nuSU7MTʕڭnTS7.U(`Kt.f$pnV֤Xj]yNYiVM-_SkF`1K"tJM$q BokVPrJ+rIɵak?I\krJuRZg.߱K'ѹ0tLꤦn"QsxIeQ&nfS͗^ݤ"ڵ{w|M%CTYy^aa~~4~X*m:H8S!7a(j/;M0 ܾF#Kdx$M#}yKTu/. %V@c6+6UQ//>ɶN4x+ 닅ٹnA{ݓRLʗ@v+v>T̴!1[",Z!״"K;yR -t>qӖȂЫ#هv/nq mmU[[%$cկjUʕnWakZ-\k,4;q,yX&/ *TZ9WEy͈CT|JWkZ K,رc },ZNuh>Ou?=V nR7-,_~MT?[OEׂƨ$o&,J}Q-h4ƑHDWۈo:XbRTK5 Ze,&x'7pʕ(/7୥K,/|LFMX*]+ x ?&d |>we2i,Xjh"VVT B릨 ?&d|l "6 xI5"GG,  1MZǀ,5BW&2*rJfn _EЧg,R(ѶVd 47݌s͛7'o[nE)Q$V1: vj)87U- ~ uPU,'JC=,FMnG"_o#`aE0j 'm)Ҁy7C!+6 mfZG}% *E¯^Vi$~O_3?~5G5(+ t|?i]t~ϵ!,NI5XyXXe*bր,+ |Ld',r2+w49ߴbo{OU'Vq}^67L?-p",,W`mq߄_X0(Bu;6/Dn1IJ i?]kks1 @I0jp]T:6* 77o s_wMķ!` r s.M8&T7ca(R1zaQOR9Tg`OT'IޏqӾdeodjZB`V+A IENDB`default/000077500000000000000000000000001325274564300401575ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhotodefault_thumb.png000066400000000000000000000030011325274564300435020ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/defaultPNG  IHDR2!otEXtSoftwareAdobe ImageReadyqe<"iTXtXML:com.adobe.xmp JLuIDATxɊ"A۲]qD<"}C *۸7Ce;x?HUdD_t:}zz֛,l6l6WbZ].ԽtcvF#$ zFD.8-V ~IM5F^J"l6(q:SUD"a䢵\.e9^5|U"f J)hMS韜DPL&x<vz~䎊>Jrʘ`x$ PU+ːVK(c^ݎZtVKR.+ lO*&,gl0!#7} d2^*; Rs/iZsh7d2x u!D'l6SI$ZX8L$I` ʼn`BKr !jڤV@ @f- LC&ϧpSH"Oe2 g| vX乴~`EIi4y.tXE)Gzh1Lp`ՕdC!eNS8htx CS|OH!BӣfR{.w@ET<$nGv~O|z~ 0"glIENDB`loader.gif000066400000000000000000000142731325274564300421230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/defaultGIF89adfd|z|̤trtĜ섆Ԭlnl䄂Ԭljl|~|̤tvtĜ쌊ܴ! NETSCAPE2.0! ,@"<pZ05 PjeH4HF"0c=<$"BRFDRm"$ smC !Pl!  S  "Q!"%O}#k^m% #O% D% JhX BBF  a" lAT!U> l9N! ,@", NӐ@jP(B"Dj-@ CXh8 L 4"BRF {%^pnR_ C %$" `nP"o" DQ!"!Q OnOS }B%!~# %##_АKC&@  @@` +RE D 7Kf >|aA! ,@"4r(-Pq@R( uP%HCQ#ѸQN҅\rGDz\DQjB"Qi]"}jiO%% "N~DF~CRPGi!N~!  !$xj  $]6`   R> P`6Pd:tR !2C! ,ljl|~|̤tvtĜ쌊Ԭtrt䄆lnl䄂̬|z|Ĥ쌎ܴN"B hH*}RQB "Xكe1PZ 56hz|RB ^g{]w IPPD##D  |g]Q g $$ hhQ {O{$دB $C D #] T"DPՁ|h B@C"tDd 46f6p1 U! ,ljl|~|̤tvtĜ쌊Ԭtrt䄆lnl䄂̬|z|Ĥ쌎ܴN"B b*+PḔH2aHtNCu0Iީ() aqʡàA,*[QB#x\x rwO ]Dx  #Q ! vQw#gN$B$C xx$$r` p0 &0! D"A݆ 9@A@hPR! ,@""Bt(-P\,$EZ0` C! PC4((!BJ 'TZ ! ,@"<=ȴ8=͢(.D'QX:HAXhOOrAL DtlDܚ5Y}B$ i$"B[iC $ CO  ZZ$}#}%L #oG   {# YD0`( JP (0СB)h@!:tt@QD;sprite.png000066400000000000000000000150321325274564300421740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/defaultPNG  IHDRBtEXtSoftwareAdobe ImageReadyqe<fiTXtXML:com.adobe.xmp GIJIDATx] pEWc 0 Ȋ`I2z7E K|k|R|\ETJ p,^$Hx$DH$ϝ̙3'93鯪$3sӞ"fQQQcçz<:t5}^TJJ{x!(((0%+ 7`K=ߣgϞԩӒ;]ve,22R*A󬱱BYP^.//k-o߾V}5;0bϞ={R笻+ FEj18?щլܹsw&''i_*nJζK.>2Q9RdЕoP0(,/uch5zHnݻSBL)zڀ. xϮ]ٳ+111R'*(aj{m"_ѣGD8*ӡpf\֛J%hkesλ߻wVK@)t>\ >עfdBٓO>|MI'Ubhz4So_3<#=t٤^{:ueee֕!k֬1j'c4obj^z8.z 320 ֧~eggKA?d(Y4ǖ!F/F㏳{ѵgO>P~8J!q#sYlСChr5*W&l ..]xm۶g_!<H0KL>]{fFM{B y6GmȤu/$ 6?M6M7!<WX%KK Ty#\zh '8iii~?D4C#Q@ hj٤zS32}/bdҕG{in#yl,RO_pELZ ˒(ڰ.____qGr``;vLGѵTwB}ܬLZ@ x#GJǃ5+yKٳgѩ͟?JƟ9Woچ u ɌLJ5j[p(Ftd22@ĭIyDWȴQB8=̛7O꬇~X l~L+ |Yf]^^0pK㨃 Lڸq>y7_@QtKSwСL\`A,ݸq#|4_lٲIa4z!?קzj|zDzZ&&;X⵾m y=<*aٴitb)<=p駟C>4,&M c̽" St^zr=X&[oz_\`"L1!BhL}ݷ<++wizM2.aCKa/_ߟ+ɑԋ"$wT|َDߨxȺj7 Af#kqE))) )ؙB>60r 0|7H`:EEM_466Me;R5),XCY,n9zdI;vlmvklll*;qTh4u$Suuu_4m뫯?Y++ k2Z&TdE "\&E W8n.J)@X5xM<" {dɴijE[TEc5>jr4CaCAeC`C;6F&FE AB6"9kaPl%hkeޕWl߾4L 4..Sǎ7dggK8d:vXz+,B0V!lhG*BV`G`*&L`Lսk@ya ="P&ge˗Kt3*..f ReJJJS&k;s#0s(bqs999lРA5(64g `q =tP)L+Y0裏J̯6enLI*?O܄L+9{%3VJt7,@vnѢEj>9 ZɖY9U@iz;nD b[nɤ7"շY&[ pp{wKPt]weCkq Z`d"(F˹a #ߖZ걡-w*2YJVso%ps=%Ћ-fdJ %dv1LO+߿ۊ+ $%:9?8to S eA. ^v x#G,9bp%Y!E{-[]ٙ7I(Lyyywq;f `bƕ$Kxj҃sxƪUh\亅)2>===k)..5̬LIIIC-X T&&7-L蜕u;M ۂ9\gd lXjL^* 1GlaC۴V6k(oÇ mrvfuGR˙A>X&RƺEt)oEEE#ktt|NJ_hF>\pa77d:''`jO7[歂Ҕ7|f; L{00:*j2nv̢[)o7R(_|LIkd`-xUSJ\N|ǍwP#0zV&q`;z(ܹsun(o#zR or3 7\ax`5aSn ^тT%Hs"p&V@yJ'xB=̭]zK(o-P30"W,!.NnRaشD% G@@s"ðY#<" %7t(Ð!C$ jFze2e ;q2pt+=̕:? Ł F8ʇ O.^ 吷0uPuVkLyAT} [)o("nYZZ:f E|tΝ͙33PQkq7W~Q76P7P,TB2C(((h?5аn> "(Іy}*> x-> fDZhEOݟ6N/>yfbk4.nj`7n:ͺD[<Bڇ>M1~T*v5QT`VL&:QqAC6,bMOO)rɣD;r{55B}TC P/gS_*G{;H ʀvQ%Q F}t"vp}!ЭZ93fLo1!w( OUTT\lMs[S\}3f̈~%C |/5kV_3>ŋGFFv  .np)k@VQhF ڪU|n0{l54~#⧟~6UwCH{n!q˖-,11QE R ճ =qit qGh#M\@G 7z8q>K;6Z[VgE 4i> Eh؎A򖓓#qSSSMo'BG\P2DAAAwAmNJPXX;@&yx߾}`B($X,6K( l \RAZA0Mm2JE`*F0co%Aҥl&} mEGe„ ^F4/Ǎ @6|c5pyy `( у (`B:bPg Jҫ Gu) ]kv6ÄP% * @JHH7_{[=½hq# 8wff^9cP XF{":z`DpAzJ',Pj:ӊ HZdX5V^IšԞ'OF$X5VyL^IK.BMX'E`KKK,xF+ $XyV^}<--GfI(|/@{ F=~IDATx1k@$!U0S3FB _@NB6{Bd= u0Qw9.swI"D?0$z"z ۖ&DT$ZR!̧?xTޏ/"`@Si;@܀4?ڶVr 4/8']lV%sqv<  t%U$cj7z}q̏dq'(IENDB`sprite_prev.png000066400000000000000000000025401325274564300432300ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/defaultPNG  IHDRetEXtSoftwareAdobe ImageReadyqe<fiTXtXML:com.adobe.xmp H>IDATx엿jPƿA Q ˛yIDATxA T ] {B@[ݽ̀B@! B@! B@! B@! B@<B!#[ LUrIENDB`sprite_y.png000066400000000000000000000022121325274564300425200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/defaultPNG  IHDR=ItEXtSoftwareAdobe ImageReadyqe<fiTXtXML:com.adobe.xmp KJIDATxϱ 0 \:# (D}aYvuQUm,"-Vf^h VČ ]IENDB`facebook/000077500000000000000000000000001325274564300403045ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhotobtnNext.png000066400000000000000000000015151325274564300424360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDR9/YbtEXtSoftwareAdobe ImageReadyqe<IDATxܚ?hQߙH$j 퐡 .3t06A]2H!lH DZ2:TJ Wm$&^9!-꿻}~(td`XčJMWp ˲:gx |G;5N@OH|j7[@w%yߔ5 h>t\׈:p\%K.Fi֗m YB1<0̋T*K\$[R*ë3&iXH,J5L&LLLRժFqXH,F31c0j#N7D,$^.--, EݾI,dX܁$^TizbK O\|>@RV\!2jj}fkI)zz'g`]d2{DZzW>zh4gggD&h>y<ױXHGdHשׁ+,6Z0#+ o!Ho} W=/xc$B^WZmBn @{p yNcwܼm!9t H+h 9 |D#G\uoɬW[\|Q]3:IIENDB`btnPrevious.png000066400000000000000000000014741325274564300433400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDR9/YbtEXtSoftwareAdobe ImageReadyqe<IDATxܚ?hZAi5ZBmCXvPZ J M۬A]2Dpi 8XڪC*RВ1C bjIkUJRRHK{]n}yǍF#?8f6/mBin?k|۟xN+)@l RJHN3q@!65GidD9(4 yD"U*cM{Fߒ B~VCZBd ,ާ (*h4Vh% M$T*ՄBŀNs&>h4Dr3&ff") 3:??OZ''A VTX|b!-dTZ4 zVr&\\~h4g܏B&IlFTzC֐NLؖ-$Vw8V#[HZ@_]BbUվnvkssr^z ~@!}ooo_Veq* ;p|dǕZ˙`p [HT*XYYd `07GZ[[uX4@Z*$[H 𫀂~k$-F![$[Bn@b];8nHaW|&'Xĵ;V~0w6tT"5{ ~:;9ֽ0cUtT?_3˧'tBo/IENDB`contentPatternBottom.png000066400000000000000000000002161325274564300452060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDR tEXtSoftwareAdobe ImageReadyqe<0IDATxb TL TH`ٻwj 2jਁ@hIENDB`contentPatternLeft.png000066400000000000000000000002111325274564300446270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDR tEXtSoftwareAdobe ImageReadyqe<+IDATxbd``h` &*QG 5pQ$8|IENDB`contentPatternRight.png000066400000000000000000000002101325274564300450110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDR tEXtSoftwareAdobe ImageReadyqe<*IDATxb ##c#`QG 5pb @aIENDB`contentPatternTop.png000066400000000000000000000002161325274564300445040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDR tEXtSoftwareAdobe ImageReadyqe<0IDATxbd``h`"`b25p` ={NS@0)7 j1;TIENDB`default_thumbnail.gif000066400000000000000000000003431325274564300444620ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookGIF89a2!iii{{{ 000ZZZ333!!!HHHrrr!,2!` dihlp,tmx|9HbKx)5X`Kmb| 20i?% I!;loader.gif000066400000000000000000000047611325274564300422510ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookGIF89aqqq񿿿ת! NETSCAPE2.0!Created with ajaxload.info! , $AeZ <䠒ÌQ46<A ßHa:ID0Fa\xG3! O:-RjTJ*  t ~" ds]  )t-"i;H>nQg]_* R3 GI? ˴v$ýj3!! , $0eZy0q PУW )";qX^D50 Ո%`rJ{ 1$ʈ!! , $@e6$Ơ` 3*=  P\"F`P-d5V"2|?n"!( )e4xyc?   3 #wyJ l% o^[b_0 V T[0m $4>'VZ c3$X%!! , $`e:D3 H0,'j0Qs L(2HMj#ȉB \Oi`u=YEVL=I  > suI WJm| \"_b0 BcV"d]*K1" H|@B?I4# S$-||!! , $4ea:D hI /K$W- 0(`3F=pf@tQ  {f~*yS*mg) enu E^Z^ g@ kw(b& -w#" xW"t ##%U$`to!! , $4ea:* 1v/Kdzk#  F Y" % E  Cb AI4$ (z:2 mI Ll## F##>F!! , $4ea:* 1v/KdVtKG227D"$)Qqp8 y l |~6zw2j# F " % VC ]6a$ Q :2 \  EF I&x "͓F4$]#x!! , $4eZi䠒J16e E,C\3 ^3[ S|?!;sprite.png000066400000000000000000000102031325274564300423140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/prettyPhoto/facebookPNG  IHDRBtEXtSoftwareAdobe ImageReadyqe<%IDATx]}LWZ XZ"GԆZm㶉U?Rk~&nGv"bPiںZ[Je+*J({8̼3o>\޼73͹眹s'E ϖ-[\e4H(PXY+PNAIEgrD_C9d>HD" Լ#(~yD0~Լ#B`}ٳgxhhhСC2 "֮]tb7l0 ;^͏AhrXaaǨLiTX1 tT'I,MH" <w1GQ{uI(Y!P*h=*QA+ C{= &wBZJ*5Lx].lT:"uOm!|d׽/rC>C0 wT-Oo7XBN|9"V%^gdH :gmh<̙3f%x mc6̔Kj4"d\ JRJfDɫJvi*9=T$R"?iVGj C̪/cRdtW`He,M^5`howj\' 77w|C_<rJ+:(ἋRY.7v\ɫA;$.sPmlB˜E=}':.a.UPqLEB>y:V ʥ2R= 'M^5Sc]ƬU=>zDfh02T:s~j p]dJE 9fR6!AI$8DK=Tn,\'NdLh ^RDx;E/l\&[)0ӥ'ij~(m2yUdw $&NwŨ8]zz]aZ' -7/>'򥎧z={<}T)nԠԘj'j!%{dޡCq'L#---m֭[~?A r:,*5.M^S{^QQQjcc򌌌<'A pjP-Ӎ&wd/DGGGA{(Yp@zzzԸhni-fE|NM$:t&_ xCZ߀hh&Px"Vs)SF1 @CD&:>gP֥`,mc3(X2t:' _wX)a=CDsۄ5u}Ε.tY9à 36 L&0OPKE +R!!!,MD3*,jd+|ѢE<`:D`r.BCC|MN{8&ٛ6mz,6NL&wMX7&#FumDFF[YgScǎ} ++km|C>+Ǥ.k!n'zˊzIIIEFSWv1'Nw'MK^.((X*z>|'"455S7Q믿>N0F/sꬬS[n}tȐ!h>fJ}G;z źNV`d;]vuY/Xɪ"رuz 6rֽ^\]:*aժU/^xN-^!ÆSZ[[,P9}U]7noE}P/AxСvuul \CL Վ;nWTTډŌVeVwaiV?1o޼zͬs>iK/T WaĦmH)^V}}uz8),jSXc"xʬZ4}hb@F;BLDrpdd{Νϟruj6ڵk555u(΄h<\sw{?l؃( cϘ1#rF,u|{mپ}snOxxx/<3|e 0 #Fȣ[lyxť;2VdS}o޼ u3F ˗/_j'QԋD"?ٿX;)^}&!%%Ŗ]["x<쨸][",X;)^}&۶mi'Q47AC cvܹ{oY Yf5~fgg3fVuuu9AgNN·{ڬs"ɫl^֬Y:;;om flQ>AfKeff~1;qLϟ? , ^x jG~aaĚ?^_}U:/[`~駧,{7LNd|_~AR oذa^mm sLK#&&&Ԍm_T_5X( 2e (#CLiqb;`P7ji@ӧOEqeֲ^zє l?~s>cA> ;v,wرQ'X,P84k%AZ`$#kS J @K{38Wk|Œ2j(R@ euʩK α|zbFB 0 Lۖ@3௿_/++eO8%a%p׳K}I, {ݻ6q©tj<VLF)85YX|B1:OkTh/#(kqw͕I 0we ,ypdG>șdo>pa#G 1PVf;vpP:ȇS Y əU %`)A_Aa|(Ji@z r]ceB fB 2C'J(PeJs)V.eP̙3y$Tpq@sNkB)PCBU^TI `bu`U5hF͍ k&@Mdm!+S[Qb#tx:Q_:Ɉl IYM@z5<&EI!Gf@?222kgS(,?U104`@ co5_E0ߘcPZ('y?GO !db!-q#.ͫtn^TC`RgJ&s ]pV|Zuw,m٪` m^L5 WEg^K@POsL9kܫ2z kZ96mP1jA,!tU3 *t^jz|rb) &׋5Px]M^Wt*@4,yr0u8e(-^ 1cn# 0%z-H(k>z-Ǡ/fT:2 -x ~yM$W 6PԠLx)lLUέE`5eG&^OF8-cs!BLzd/܄y'to|7xOf1EZ`!ٻ~->)z-% m2~LkG0 F"(ZrBR6%c@W-ͫ΢ݼj&R΢4'Luڶj:bM)gktuMlWCXLJIQLZb{Xzw%'6b|kƕ+W~˚J1;Lf½nUsij>%/7B^77 ~"T&?33UC>X$q9S 7=E7 /x~]!v'̢KcaEĿ]'ՌtXO<>0!׋RT?=i6 '& 9,/-)9fAo^usO9$ԵXmS7MNfōD/Asi9B5])Al՛A [exFF ~:Ogt@C6$lі}fͫEHf纏 crIcD:' M~cN?hdH!b:%B'g1ͫiYsim/ finFY ;EU$fŜkuY ^T'xç9 [X`>6Z OQhc"|,++W(l*q]’!6e?pӆSCv9slZCt 7 a5dEf9ÄʑxQJA4[d}y$Jg~:@#ٲ4@Kh !ȱ.?Hm j%کbZ d9dШk`,ުY&*'+59-eGL"k_JpSQ /U0pѪmlpL \R9um(lDr\IENDB`loader.gif000066400000000000000000000050601325274564300405440ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/gallery/swipebox/imgGIF89a --->>>HHHQQQ&&&AAAWWW)))KKKSSSGGG222MMM+++888www]]]}}}666!Created with ajaxload.info! ! NETSCAPE2.0,@pH b$Ĩtx@$W@e8>S-k\'<\0f4` /yXg{w Q o X h Dd aeTyvkyBVevCpyCyFpQpGpPCpHpͫpIppJe ֝XϧepX%䀪ia6Ž'_S$jtEYI6-&(5f 1dx%OmmFaYQ$"-EYE2 I=jԄ#V7/H"EmF(a$ܗ ! ,@pH|$0 P ĨTqp*X, "ө-o]"fgv޿73;3+TIn~{{=;|(4ik=o |t֣H2pi1Vb'SNu,ߌi(8?//V0~N>..kK~dmd]HE )%"i,)n^?$TȾm[r8$P.B^Kc8np%ٞ@xf9aRGiN }#Ԩ0H[0}p9.j7n`CtLGTF!\AHLX5b I+qvNA|i!Of:" f%Xne"1M(\o;U*k)6ncaq9ԭ|݆b&Z_bUY_g_k]<:SQNGɲYQaFSS]# 6c֯Α3!BO )sP_W Dirh6@B Q95W/@kGm!UDR";g=cq=}AoΟLAN)E2LCcd/Vtr 4mM<dh{{7J"?V &:-Z}0عN~otz_ePsWVI|vy#įRNd&`u&|~JV)ow> 9tx,KGAZ,jQ-=ǥa꣗]5 Ze[A+>S]9_D?s9*t"MMۋhwk׵!R![15GjAV*|VX_@΀܉ UZ:ip/g8ޕUN ]ih=WO7d}Ϋ!OA:RzYL摮>Q%)@Pә/OlZk.{xhm-!ܿ($g0݋t8;W/kA&)g\_SY @ijN Q!WW*ĸVڥu5@Řι&X,t:Y|$S38ӓlƲ.ñ4%7.|9abnide. W{WN\ѧޢ7@8U6!!ʇװWњfbJcu Wz_;&H\ìk!"r,p͜VQE.%[+'-*F#1H:*s,η*sb7:X2zd6UG}tgqx\ZGJI*--KB \U0jRVmU&s X̞gJ]Eix%Ns .S㏝K*@L>ETq`3\'>O mAv8]Y胖; l@Z.EPVpQ!X[n0p8nf3d# HdªFfT`*gjn-&s*'QC6"1`HI[D:nO2۲W8Lx,L 2q>ycv2.m3u^5Tp)iPc6}XKWH83Q*3ŭ8W߰lcF2*W 6m2h)U8pN[Rc< ?-%5$o<;"VIENDB`note.png000066400000000000000000000047301325274564300364240ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/note/imagesPNG  IHDR00WbKGD pHYs  ~tIME :9@>tEXtCommentCreated with The GIMP (c) 2003 Jakub 'jimmac' Steiner'3X IDATxYWsU3,' fqv㐘(X@x@""@gx`y@B!   %$, Kl !=]uU=3 v!#u6r^hhhUF rU>Fj(NO297͗Fk-/| Ghgmn\6Wvċ @w-yW>~"`G_m/@IYšv,ĿMD03cz@NqAQp^ѵk1 ȟt"=I)ޗ&yNbzzZ@D8}1t{99qz_ ==5~r z{jdY"R#KYN3u:7t/>|=AG6P \W3Sg>~>C{k,T1WhhH(`iڡyѕ(ݫr\M1tp ;9X'O%Z.1+71`8LE$ t)P(@ġ>チw=zf?+Sٸ:p6Lє"*_Ʋ8M JZ4oⱩY߿z_^l̄=8Ϋ723Wly[(4 XP @@xOp޸|S<̾?ayu3[fxvMH-hRn*:JJ!рHH3^in_ޣ1S\P(`|E3l1cu shBBjGؽ2^y-yn\K׀3]@NHp΁8+)|Q(Nz-chpyQj8J N$ j@k5Ay Vv'$i(Rss$TI E'F%/S[Vi<0;;"8'$> (A4"O4j.& %MJQ1?C(RABHNRb 18uhZƍ:Sl߶ :0i^u KQ ?ꀪ"Zf xwܱy?uFh疡~^~| 7_Wu<Nhpα !/:  8PZ^Y؃y6@?1l#Dլ:27枟?>~X\~&F֬ G|ʾsD),aVfG6X72PNYU]Ȳ^PB=7_* ){ `]/Y3P_awClYYmIQŠ6ĢQ#9Rίmfff6 3B)Kr-Rt@NUfDU͐ /X?Rc("FbNU|߫F ;יaf$Iҝ,tml޵033GJ:ifK,bCFQiR[ F+SA\6#J `vv+4jZs'0=:AxbL8c1Ӡ k|ňڑ =!"vQ=jm̌gƏo~ MS ~{#:c'NEnYk97_^wúl3zV4X?2̦sŖKlq?2>щ<ã㌎$i~x1PyL4Ml6gggOEQ@R=]FZ5!939UeÆ.2;T׷*ggӍ%;lň]X/?̗Q*ݲ{y9/弜t0 ?IENDB`tip.png000066400000000000000000000055351325274564300362570ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/note/imagesPNG  IHDR00WbKGD IDATx͚yluμGR4)ы6,9*R-P*l,+i9AiQw Wiq Fn\^buE[ɒBUU17q7s9c#1e;lwhXaŠ ܆+\g-EX ( j:p5`fVX!Y mnݷ{w6omͺu;{mŤi >}C^W@@"'r`>~ "(*x8q_?s@qՒ2Сy__uNr/lWyUz"zy柿mRR*EJ(XkQD29``ͷnػNMMN w ؐ]ۻwߟ=~juQ[8΀fsUIhEv/j]~lo YJ*!sOlpSdž8QUS㜠\xQ+> ru߇ &! {ol۶ZxK~4rQ5(wr<Qjb^W=ΝQd(EU}edd]H=4jSBZ-Q[9^&50|^~."KI$E"f>go60f& I ϟ:y),)T{s-i;O/IB;uIf}o2c3;.ZRrQhx'UO*)w39~Ki\ {RiFhf ?^ eN@|駞{XeRMIq)mh;G4ɋlE,`EkG2F SXxӏ?4<2DI% #!E}T"Aw3f>0 C tN|hr!h#ǧ2QISng/`Ȫ`K7N ><4-GL/p_/g$S't{O ^p"Icyw3NUH6B*u6md]ЈwM ^-` hY"8w_|oZ&#gH}BwjD\2KJdR)”'#^s:"M`0XvE%^|޽ѯg7]`Q%(gM6ej<_k_3\@k_"q `HS%2-"ڈՍO9vy> _ ;r#uh\viō poB{&DYE}Xk R28CN%h"P kQ+bmȷSP~dL c $fvMl9&jtc_nyꗸ1f*$C\fc6h5W\m[zHq_*5Oyܰi ׾HC\B-?uxz_D+625z Q-|{V}VB$TkmZm*ӃLk j4fh* ı~ܹ]9z_jSu?VoE5Aõh FM'jKrʭL7mb'5  I;9O2:Qlʡ]/ PȵzWjҬѨMnHRG;qiAz$zM7_S`g] B2K8%*L a|L/i4?:rzSUQ٪#M*xTKP[W.~]~螮U+{pITk;VkmɊ<+@gM+`wq?l*^3::::ah-Q LB<7l_z8ccc9|8Yhf|<WadIit9\9䖿7Zyt9r7ˉ_mrYx/ڥ>1WIENDB`warning.png000066400000000000000000000062611325274564300371250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/plugins/note/imagesPNG  IHDR00WbKGD fIDATx՚{pUU$ Q4t0m(Z!)-i3ִ `V%GA@`(MmHDBHBnrs9??Vdٲedeem~Wꍄ7xwΝ MMMލx5߉Xi"dgge˖ɋ/V ˀPXf&ػw;v|cc#~;6t wwIZˉScRPP09''YTTt.xGcǎQ]]EY>aU0 !=3ܹsٺuO.]_|QD4eݿ,..޶m۶`+WPy ڵ Up)3ӧ1dƬ\n:e…=F@9r/BYQQ!['?e|{JXsQ^AN9r@;f4MScϗo?EKlgm߼ysܴGanOE1љ's}ئ0Qr+[_-c|<ΰYb3ev5%: > R[[KաChaɋNw׽ :I\ZI?J77FY7-=i2l,^y)-rPx[l|ee%}d8[x",Pl !LlKG nUub-=y2:ƍ|vmZTM6qرnRT1cD7 ;|_~~ N$E }W1>H B* i}|S~=yWzi T|fML;tPRRRF^5:,ˢ{iOJ F )BHawKaH!@8=)ᄿf=?<2AoWN''|wؖmY Hm[ٹg~8qzO@ O0*~\.W<*Ѻ]!c*^Im l!m+/^(Eahۚi5$x'g%@2|8'vՉ-T,D5A&{OԞז a@X&O*#n!ԁp8U]6u\(((JCIE іՙ@tLY;%#4 iCF1b*t FiiBSs3D R {vIo`!oȵfʏtPZ;KÅPUÃoq!vO4=a`SsӉLf,ZBkvD'%JlFPܨ@\.z:-Q{=DZ jO*& !_ #|?T+7|M `EAA)QvHKTkVhl|f9u5! i(/H `zN:BH) Iz(Sד P%FFۇMi[ \ 9gf8[ov_3v?RJ2e0t ] {!vQU5DwnzNkl+б4JGW'ЙgȜ934` TA( *t==p8T&h1zO|F"1 =[ zVSL+( sƌGg1}X6:z);TGJl+DH4l3w1wvԸc]]k;yN3f ř˹2\ o ]}JZ nwEPm0е zOcc-Ql};a\qYF;IK0RbFsEA%K._VV… cփ%u4Zϟ:ba~84 0jHmrk &ښv}ޡjѝ~c1g߾૪7oNZ uIfggjnMM %>Kmqqܚz¾jТFx@ƌqFJdggSRR*pT+B*pO@\3gl4Dee媬, @ 1i)-7|Y /]3gάOt:AիN'--JJh,g?cAXי6 DLx'e(2]׻.ߥAY\\,'M$`BO[ʰ:?䓉RS\FtK8јg/R^^I.uu}mu]}ÇOiiiƙD*uEb].yɜ9sn xEQ(,,ߦ4iȿt~k4{2ǃ<}Z} $1 H<
value = parseInt( elem.css( "zIndex" ), 10 ); if ( !isNaN( value ) && value !== 0 ) { return value; } } elem = elem.parent(); } } return 0; }, uniqueId: function() { return this.each(function() { if ( !this.id ) { this.id = "ui-id-" + (++uuid); } }); }, removeUniqueId: function() { return this.each(function() { if ( runiqueId.test( this.id ) ) { $( this ).removeAttr( "id" ); } }); } }); // selectors function focusable( element, isTabIndexNotNaN ) { var map, mapName, img, nodeName = element.nodeName.toLowerCase(); if ( "area" === nodeName ) { map = element.parentNode; mapName = map.name; if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } img = $( "img[usemap=#" + mapName + "]" )[0]; return !!img && visible( img ); } return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) && // the element and all of its ancestors must be visible visible( element ); } function visible( element ) { return $.expr.filters.visible( element ) && !$( element ).parents().addBack().filter(function() { return $.css( this, "visibility" ) === "hidden"; }).length; } $.extend( $.expr[ ":" ], { data: $.expr.createPseudo ? $.expr.createPseudo(function( dataName ) { return function( elem ) { return !!$.data( elem, dataName ); }; }) : // support: jQuery <1.8 function( elem, i, match ) { return !!$.data( elem, match[ 3 ] ); }, focusable: function( element ) { return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); }, tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), isTabIndexNaN = isNaN( tabIndex ); return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); } }); // support: jQuery <1.8 if ( !$( "" ).outerWidth( 1 ).jquery ) { $.each( [ "Width", "Height" ], function( i, name ) { var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], type = name.toLowerCase(), orig = { innerWidth: $.fn.innerWidth, innerHeight: $.fn.innerHeight, outerWidth: $.fn.outerWidth, outerHeight: $.fn.outerHeight }; function reduce( elem, size, border, margin ) { $.each( side, function() { size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; if ( border ) { size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; } if ( margin ) { size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; } }); return size; } $.fn[ "inner" + name ] = function( size ) { if ( size === undefined ) { return orig[ "inner" + name ].call( this ); } return this.each(function() { $( this ).css( type, reduce( this, size ) + "px" ); }); }; $.fn[ "outer" + name] = function( size, margin ) { if ( typeof size !== "number" ) { return orig[ "outer" + name ].call( this, size ); } return this.each(function() { $( this).css( type, reduce( this, size, true, margin ) + "px" ); }); }; }); } // support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) if ( $( "" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { $.fn.removeData = (function( removeData ) { return function( key ) { if ( arguments.length ) { return removeData.call( this, $.camelCase( key ) ); } else { return removeData.call( this ); } }; })( $.fn.removeData ); } // deprecated $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); $.support.selectstart = "onselectstart" in document.createElement( "div" ); $.fn.extend({ disableSelection: function() { return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + ".ui-disableSelection", function( event ) { event.preventDefault(); }); }, enableSelection: function() { return this.unbind( ".ui-disableSelection" ); } }); $.extend( $.ui, { // $.ui.plugin is deprecated. Use $.widget() extensions instead. plugin: { add: function( module, option, set ) { var i, proto = $.ui[ module ].prototype; for ( i in set ) { proto.plugins[ i ] = proto.plugins[ i ] || []; proto.plugins[ i ].push( [ option, set[ i ] ] ); } }, call: function( instance, name, args ) { var i, set = instance.plugins[ name ]; if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { return; } for ( i = 0; i < set.length; i++ ) { if ( instance.options[ set[ i ][ 0 ] ] ) { set[ i ][ 1 ].apply( instance.element, args ); } } } }, // only used by resizable hasScroll: function( el, a ) { //If overflow is hidden, the element might have extra content, but the user wants to hide it if ( $( el ).css( "overflow" ) === "hidden") { return false; } var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", has = false; if ( el[ scroll ] > 0 ) { return true; } // TODO: determine which cases actually cause this to happen // if the element doesn't have the scroll set, see if it's possible to // set the scroll el[ scroll ] = 1; has = ( el[ scroll ] > 0 ); el[ scroll ] = 0; return has; } }); })( jQuery ); (function( $, undefined ) { var uuid = 0, slice = Array.prototype.slice, _cleanData = $.cleanData; $.cleanData = function( elems ) { for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { try { $( elem ).triggerHandler( "remove" ); // http://bugs.jquery.com/ticket/8235 } catch( e ) {} } _cleanData( elems ); }; $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) proxiedPrototype = {}, namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } // create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] }); basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = (function() { var _super = function() { return base.prototype[ prop ].apply( this, arguments ); }, _superApply = function( args ) { return base.prototype[ prop ].apply( this, args ); }; return function() { var __super = this._super, __superApply = this._superApply, returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; })(); }); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName }); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); }); // remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); }; $.widget.extend = function( target ) { var input = slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? $.widget.extend.apply( null, [ options ].concat(args) ) : options; if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } }); } else { this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} )._init(); } else { $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "
", options: { disabled: false, // callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) // 1.9 BC for #7810 // TODO remove dual storage .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key, parts, curOption, i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( value === undefined ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( value === undefined ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } return this; }, enable: function() { return this._setOption( "disabled", false ); }, disable: function() { return this._setOption( "disabled", true ); }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^(\w+)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions, effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue(function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); }); } }; }); })( jQuery ); (function( $, undefined ) { var mouseHandled = false; $( document ).mouseup( function() { mouseHandled = false; }); $.widget("ui.mouse", { version: "1.10.3", options: { cancel: "input,textarea,button,select,option", distance: 1, delay: 0 }, _mouseInit: function() { var that = this; this.element .bind("mousedown."+this.widgetName, function(event) { return that._mouseDown(event); }) .bind("click."+this.widgetName, function(event) { if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { $.removeData(event.target, that.widgetName + ".preventClickEvent"); event.stopImmediatePropagation(); return false; } }); this.started = false; }, // TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { this.element.unbind("."+this.widgetName); if ( this._mouseMoveDelegate ) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); } }, _mouseDown: function(event) { // don't let more than one widget handle mouseStart if( mouseHandled ) { return; } // we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event)); this._mouseDownEvent = event; var that = this, btnIsLeft = (event.which === 1), // event.target.nodeName works around a bug in IE 8 with // disabled inputs (#7620) elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { return true; } this.mouseDelayMet = !this.options.delay; if (!this.mouseDelayMet) { this._mouseDelayTimer = setTimeout(function() { that.mouseDelayMet = true; }, this.options.delay); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(event) !== false); if (!this._mouseStarted) { event.preventDefault(); return true; } } // Click event may never have fired (Gecko & Opera) if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { $.removeData(event.target, this.widgetName + ".preventClickEvent"); } // these delegates are required to keep context this._mouseMoveDelegate = function(event) { return that._mouseMove(event); }; this._mouseUpDelegate = function(event) { return that._mouseUp(event); }; $(document) .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) .bind("mouseup."+this.widgetName, this._mouseUpDelegate); event.preventDefault(); mouseHandled = true; return true; }, _mouseMove: function(event) { // IE mouseup check - mouseup happened when mouse was out of window if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { return this._mouseUp(event); } if (this._mouseStarted) { this._mouseDrag(event); return event.preventDefault(); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(this._mouseDownEvent, event) !== false); (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); } return !this._mouseStarted; }, _mouseUp: function(event) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); if (this._mouseStarted) { this._mouseStarted = false; if (event.target === this._mouseDownEvent.target) { $.data(event.target, this.widgetName + ".preventClickEvent", true); } this._mouseStop(event); } return false; }, _mouseDistanceMet: function(event) { return (Math.max( Math.abs(this._mouseDownEvent.pageX - event.pageX), Math.abs(this._mouseDownEvent.pageY - event.pageY) ) >= this.options.distance ); }, _mouseDelayMet: function(/* event */) { return this.mouseDelayMet; }, // These are placeholder methods, to be overriden by extending plugin _mouseStart: function(/* event */) {}, _mouseDrag: function(/* event */) {}, _mouseStop: function(/* event */) {}, _mouseCapture: function(/* event */) { return true; } }); })(jQuery); (function( $, undefined ) { $.widget("ui.draggable", $.ui.mouse, { version: "1.10.3", widgetEventPrefix: "drag", options: { addClasses: true, appendTo: "parent", axis: false, connectToSortable: false, containment: false, cursor: "auto", cursorAt: false, grid: false, handle: false, helper: "original", iframeFix: false, opacity: false, refreshPositions: false, revert: false, revertDuration: 500, scope: "default", scroll: true, scrollSensitivity: 20, scrollSpeed: 20, snap: false, snapMode: "both", snapTolerance: 20, stack: false, zIndex: false, // callbacks drag: null, start: null, stop: null }, _create: function() { if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) { this.element[0].style.position = "relative"; } if (this.options.addClasses){ this.element.addClass("ui-draggable"); } if (this.options.disabled){ this.element.addClass("ui-draggable-disabled"); } this._mouseInit(); }, _destroy: function() { this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" ); this._mouseDestroy(); }, _mouseCapture: function(event) { var o = this.options; // among others, prevent a drag on a resizable-handle if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) { return false; } //Quit if we're not on a valid handle this.handle = this._getHandle(event); if (!this.handle) { return false; } $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() { $("
") .css({ width: this.offsetWidth+"px", height: this.offsetHeight+"px", position: "absolute", opacity: "0.001", zIndex: 1000 }) .css($(this).offset()) .appendTo("body"); }); return true; }, _mouseStart: function(event) { var o = this.options; //Create and append the visible helper this.helper = this._createHelper(event); this.helper.addClass("ui-draggable-dragging"); //Cache the helper size this._cacheHelperProportions(); //If ddmanager is used for droppables, set the global draggable if($.ui.ddmanager) { $.ui.ddmanager.current = this; } /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ //Cache the margins of the original element this._cacheMargins(); //Store the helper's css position this.cssPosition = this.helper.css( "position" ); this.scrollParent = this.helper.scrollParent(); this.offsetParent = this.helper.offsetParent(); this.offsetParentCssPosition = this.offsetParent.css( "position" ); //The element's absolute position on the page minus margins this.offset = this.positionAbs = this.element.offset(); this.offset = { top: this.offset.top - this.margins.top, left: this.offset.left - this.margins.left }; //Reset scroll cache this.offset.scroll = false; $.extend(this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, parent: this._getParentOffset(), relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); //Generate the original position this.originalPosition = this.position = this._generatePosition(event); this.originalPageX = event.pageX; this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); //Set a containment if given in the options this._setContainment(); //Trigger event + callbacks if(this._trigger("start", event) === false) { this._clear(); return false; } //Recache the helper size this._cacheHelperProportions(); //Prepare the droppable offsets if ($.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003) if ( $.ui.ddmanager ) { $.ui.ddmanager.dragStart(this, event); } return true; }, _mouseDrag: function(event, noPropagation) { // reset any necessary cached properties (see #5009) if ( this.offsetParentCssPosition === "fixed" ) { this.offset.parent = this._getParentOffset(); } //Compute the helpers position this.position = this._generatePosition(event); this.positionAbs = this._convertPositionTo("absolute"); //Call plugins and callbacks and use the resulting position if something is returned if (!noPropagation) { var ui = this._uiHash(); if(this._trigger("drag", event, ui) === false) { this._mouseUp({}); return false; } this.position = ui.position; } if(!this.options.axis || this.options.axis !== "y") { this.helper[0].style.left = this.position.left+"px"; } if(!this.options.axis || this.options.axis !== "x") { this.helper[0].style.top = this.position.top+"px"; } if($.ui.ddmanager) { $.ui.ddmanager.drag(this, event); } return false; }, _mouseStop: function(event) { //If we are using droppables, inform the manager about the drop var that = this, dropped = false; if ($.ui.ddmanager && !this.options.dropBehaviour) { dropped = $.ui.ddmanager.drop(this, event); } //if a drop comes from outside (a sortable) if(this.dropped) { dropped = this.dropped; this.dropped = false; } //if the original element is no longer in the DOM don't bother to continue (see #8269) if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) { return false; } if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) { $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() { if(that._trigger("stop", event) !== false) { that._clear(); } }); } else { if(this._trigger("stop", event) !== false) { this._clear(); } } return false; }, _mouseUp: function(event) { //Remove frame helpers $("div.ui-draggable-iframeFix").each(function() { this.parentNode.removeChild(this); }); //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003) if( $.ui.ddmanager ) { $.ui.ddmanager.dragStop(this, event); } return $.ui.mouse.prototype._mouseUp.call(this, event); }, cancel: function() { if(this.helper.is(".ui-draggable-dragging")) { this._mouseUp({}); } else { this._clear(); } return this; }, _getHandle: function(event) { return this.options.handle ? !!$( event.target ).closest( this.element.find( this.options.handle ) ).length : true; }, _createHelper: function(event) { var o = this.options, helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element); if(!helper.parents("body").length) { helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo)); } if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) { helper.css("position", "absolute"); } return helper; }, _adjustOffsetFromHelper: function(obj) { if (typeof obj === "string") { obj = obj.split(" "); } if ($.isArray(obj)) { obj = {left: +obj[0], top: +obj[1] || 0}; } if ("left" in obj) { this.offset.click.left = obj.left + this.margins.left; } if ("right" in obj) { this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; } if ("top" in obj) { this.offset.click.top = obj.top + this.margins.top; } if ("bottom" in obj) { this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; } }, _getParentOffset: function() { //Get the offsetParent and cache its position var po = this.offsetParent.offset(); // This is a special case where we need to modify a offset calculated on start, since the following happened: // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { po.left += this.scrollParent.scrollLeft(); po.top += this.scrollParent.scrollTop(); } //This needs to be actually done for all browsers, since pageX/pageY includes this information //Ugly IE fix if((this.offsetParent[0] === document.body) || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { po = { top: 0, left: 0 }; } return { top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) }; }, _getRelativeOffset: function() { if(this.cssPosition === "relative") { var p = this.element.position(); return { top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() }; } else { return { top: 0, left: 0 }; } }, _cacheMargins: function() { this.margins = { left: (parseInt(this.element.css("marginLeft"),10) || 0), top: (parseInt(this.element.css("marginTop"),10) || 0), right: (parseInt(this.element.css("marginRight"),10) || 0), bottom: (parseInt(this.element.css("marginBottom"),10) || 0) }; }, _cacheHelperProportions: function() { this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() }; }, _setContainment: function() { var over, c, ce, o = this.options; if ( !o.containment ) { this.containment = null; return; } if ( o.containment === "window" ) { this.containment = [ $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left, $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top, $( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left, $( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top ]; return; } if ( o.containment === "document") { this.containment = [ 0, 0, $( document ).width() - this.helperProportions.width - this.margins.left, ( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top ]; return; } if ( o.containment.constructor === Array ) { this.containment = o.containment; return; } if ( o.containment === "parent" ) { o.containment = this.helper[ 0 ].parentNode; } c = $( o.containment ); ce = c[ 0 ]; if( !ce ) { return; } over = c.css( "overflow" ) !== "hidden"; this.containment = [ ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ), ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) , ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right, ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top - this.margins.bottom ]; this.relative_container = c; }, _convertPositionTo: function(d, pos) { if(!pos) { pos = this.position; } var mod = d === "absolute" ? 1 : -1, scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent; //Cache the scroll if (!this.offset.scroll) { this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()}; } return { top: ( pos.top + // The absolute mouse position this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod ) ), left: ( pos.left + // The absolute mouse position this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod ) ) }; }, _generatePosition: function(event) { var containment, co, top, left, o = this.options, scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent, pageX = event.pageX, pageY = event.pageY; //Cache the scroll if (!this.offset.scroll) { this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()}; } /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ // If we are not dragging yet, we won't check for options if ( this.originalPosition ) { if ( this.containment ) { if ( this.relative_container ){ co = this.relative_container.offset(); containment = [ this.containment[ 0 ] + co.left, this.containment[ 1 ] + co.top, this.containment[ 2 ] + co.left, this.containment[ 3 ] + co.top ]; } else { containment = this.containment; } if(event.pageX - this.offset.click.left < containment[0]) { pageX = containment[0] + this.offset.click.left; } if(event.pageY - this.offset.click.top < containment[1]) { pageY = containment[1] + this.offset.click.top; } if(event.pageX - this.offset.click.left > containment[2]) { pageX = containment[2] + this.offset.click.left; } if(event.pageY - this.offset.click.top > containment[3]) { pageY = containment[3] + this.offset.click.top; } } if(o.grid) { //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950) top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY; pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX; pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; } } return { top: ( pageY - // The absolute mouse position this.offset.click.top - // Click offset (relative to the element) this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top + // The offsetParent's offset without borders (offset + border) ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) ), left: ( pageX - // The absolute mouse position this.offset.click.left - // Click offset (relative to the element) this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left + // The offsetParent's offset without borders (offset + border) ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) ) }; }, _clear: function() { this.helper.removeClass("ui-draggable-dragging"); if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) { this.helper.remove(); } this.helper = null; this.cancelHelperRemoval = false; }, // From now on bulk stuff - mainly helpers _trigger: function(type, event, ui) { ui = ui || this._uiHash(); $.ui.plugin.call(this, type, [event, ui]); //The absolute position has to be recalculated after plugins if(type === "drag") { this.positionAbs = this._convertPositionTo("absolute"); } return $.Widget.prototype._trigger.call(this, type, event, ui); }, plugins: {}, _uiHash: function() { return { helper: this.helper, position: this.position, originalPosition: this.originalPosition, offset: this.positionAbs }; } }); $.ui.plugin.add("draggable", "connectToSortable", { start: function(event, ui) { var inst = $(this).data("ui-draggable"), o = inst.options, uiSortable = $.extend({}, ui, { item: inst.element }); inst.sortables = []; $(o.connectToSortable).each(function() { var sortable = $.data(this, "ui-sortable"); if (sortable && !sortable.options.disabled) { inst.sortables.push({ instance: sortable, shouldRevert: sortable.options.revert }); sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page). sortable._trigger("activate", event, uiSortable); } }); }, stop: function(event, ui) { //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper var inst = $(this).data("ui-draggable"), uiSortable = $.extend({}, ui, { item: inst.element }); $.each(inst.sortables, function() { if(this.instance.isOver) { this.instance.isOver = 0; inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work) //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid" if(this.shouldRevert) { this.instance.options.revert = this.shouldRevert; } //Trigger the stop of the sortable this.instance._mouseStop(event); this.instance.options.helper = this.instance.options._helper; //If the helper has been the original item, restore properties in the sortable if(inst.options.helper === "original") { this.instance.currentItem.css({ top: "auto", left: "auto" }); } } else { this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance this.instance._trigger("deactivate", event, uiSortable); } }); }, drag: function(event, ui) { var inst = $(this).data("ui-draggable"), that = this; $.each(inst.sortables, function() { var innermostIntersecting = false, thisSortable = this; //Copy over some variables to allow calling the sortable's native _intersectsWith this.instance.positionAbs = inst.positionAbs; this.instance.helperProportions = inst.helperProportions; this.instance.offset.click = inst.offset.click; if(this.instance._intersectsWith(this.instance.containerCache)) { innermostIntersecting = true; $.each(inst.sortables, function () { this.instance.positionAbs = inst.positionAbs; this.instance.helperProportions = inst.helperProportions; this.instance.offset.click = inst.offset.click; if (this !== thisSortable && this.instance._intersectsWith(this.instance.containerCache) && $.contains(thisSortable.instance.element[0], this.instance.element[0]) ) { innermostIntersecting = false; } return innermostIntersecting; }); } if(innermostIntersecting) { //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once if(!this.instance.isOver) { this.instance.isOver = 1; //Now we fake the start of dragging for the sortable instance, //by cloning the list group item, appending it to the sortable and using it as inst.currentItem //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one) this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true); this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it this.instance.options.helper = function() { return ui.helper[0]; }; event.target = this.instance.currentItem[0]; this.instance._mouseCapture(event, true); this.instance._mouseStart(event, true, true); //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes this.instance.offset.click.top = inst.offset.click.top; this.instance.offset.click.left = inst.offset.click.left; this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left; this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top; inst._trigger("toSortable", event); inst.dropped = this.instance.element; //draggable revert needs that //hack so receive/update callbacks work (mostly) inst.currentItem = inst.element; this.instance.fromOutside = inst; } //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable if(this.instance.currentItem) { this.instance._mouseDrag(event); } } else { //If it doesn't intersect with the sortable, and it intersected before, //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval if(this.instance.isOver) { this.instance.isOver = 0; this.instance.cancelHelperRemoval = true; //Prevent reverting on this forced stop this.instance.options.revert = false; // The out event needs to be triggered independently this.instance._trigger("out", event, this.instance._uiHash(this.instance)); this.instance._mouseStop(event, true); this.instance.options.helper = this.instance.options._helper; //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size this.instance.currentItem.remove(); if(this.instance.placeholder) { this.instance.placeholder.remove(); } inst._trigger("fromSortable", event); inst.dropped = false; //draggable revert needs that } } }); } }); $.ui.plugin.add("draggable", "cursor", { start: function() { var t = $("body"), o = $(this).data("ui-draggable").options; if (t.css("cursor")) { o._cursor = t.css("cursor"); } t.css("cursor", o.cursor); }, stop: function() { var o = $(this).data("ui-draggable").options; if (o._cursor) { $("body").css("cursor", o._cursor); } } }); $.ui.plugin.add("draggable", "opacity", { start: function(event, ui) { var t = $(ui.helper), o = $(this).data("ui-draggable").options; if(t.css("opacity")) { o._opacity = t.css("opacity"); } t.css("opacity", o.opacity); }, stop: function(event, ui) { var o = $(this).data("ui-draggable").options; if(o._opacity) { $(ui.helper).css("opacity", o._opacity); } } }); $.ui.plugin.add("draggable", "scroll", { start: function() { var i = $(this).data("ui-draggable"); if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { i.overflowOffset = i.scrollParent.offset(); } }, drag: function( event ) { var i = $(this).data("ui-draggable"), o = i.options, scrolled = false; if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") { if(!o.axis || o.axis !== "x") { if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed; } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) { i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed; } } if(!o.axis || o.axis !== "y") { if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed; } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) { i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed; } } } else { if(!o.axis || o.axis !== "x") { if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); } } if(!o.axis || o.axis !== "y") { if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); } } } if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(i, event); } } }); $.ui.plugin.add("draggable", "snap", { start: function() { var i = $(this).data("ui-draggable"), o = i.options; i.snapElements = []; $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() { var $t = $(this), $o = $t.offset(); if(this !== i.element[0]) { i.snapElements.push({ item: this, width: $t.outerWidth(), height: $t.outerHeight(), top: $o.top, left: $o.left }); } }); }, drag: function(event, ui) { var ts, bs, ls, rs, l, r, t, b, i, first, inst = $(this).data("ui-draggable"), o = inst.options, d = o.snapTolerance, x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width, y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height; for (i = inst.snapElements.length - 1; i >= 0; i--){ l = inst.snapElements[i].left; r = l + inst.snapElements[i].width; t = inst.snapElements[i].top; b = t + inst.snapElements[i].height; if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) { if(inst.snapElements[i].snapping) { (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); } inst.snapElements[i].snapping = false; continue; } if(o.snapMode !== "inner") { ts = Math.abs(t - y2) <= d; bs = Math.abs(b - y1) <= d; ls = Math.abs(l - x2) <= d; rs = Math.abs(r - x1) <= d; if(ts) { ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top; } if(bs) { ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top; } if(ls) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left; } if(rs) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left; } } first = (ts || bs || ls || rs); if(o.snapMode !== "outer") { ts = Math.abs(t - y1) <= d; bs = Math.abs(b - y2) <= d; ls = Math.abs(l - x1) <= d; rs = Math.abs(r - x2) <= d; if(ts) { ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top; } if(bs) { ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top; } if(ls) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left; } if(rs) { ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left; } } if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) { (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item }))); } inst.snapElements[i].snapping = (ts || bs || ls || rs || first); } } }); $.ui.plugin.add("draggable", "stack", { start: function() { var min, o = this.data("ui-draggable").options, group = $.makeArray($(o.stack)).sort(function(a,b) { return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0); }); if (!group.length) { return; } min = parseInt($(group[0]).css("zIndex"), 10) || 0; $(group).each(function(i) { $(this).css("zIndex", min + i); }); this.css("zIndex", (min + group.length)); } }); $.ui.plugin.add("draggable", "zIndex", { start: function(event, ui) { var t = $(ui.helper), o = $(this).data("ui-draggable").options; if(t.css("zIndex")) { o._zIndex = t.css("zIndex"); } t.css("zIndex", o.zIndex); }, stop: function(event, ui) { var o = $(this).data("ui-draggable").options; if(o._zIndex) { $(ui.helper).css("zIndex", o._zIndex); } } }); })(jQuery); (function( $, undefined ) { function isOverAxis( x, reference, size ) { return ( x > reference ) && ( x < ( reference + size ) ); } $.widget("ui.droppable", { version: "1.10.3", widgetEventPrefix: "drop", options: { accept: "*", activeClass: false, addClasses: true, greedy: false, hoverClass: false, scope: "default", tolerance: "intersect", // callbacks activate: null, deactivate: null, drop: null, out: null, over: null }, _create: function() { var o = this.options, accept = o.accept; this.isover = false; this.isout = true; this.accept = $.isFunction(accept) ? accept : function(d) { return d.is(accept); }; //Store the droppable's proportions this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; // Add the reference and positions to the manager $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || []; $.ui.ddmanager.droppables[o.scope].push(this); (o.addClasses && this.element.addClass("ui-droppable")); }, _destroy: function() { var i = 0, drop = $.ui.ddmanager.droppables[this.options.scope]; for ( ; i < drop.length; i++ ) { if ( drop[i] === this ) { drop.splice(i, 1); } } this.element.removeClass("ui-droppable ui-droppable-disabled"); }, _setOption: function(key, value) { if(key === "accept") { this.accept = $.isFunction(value) ? value : function(d) { return d.is(value); }; } $.Widget.prototype._setOption.apply(this, arguments); }, _activate: function(event) { var draggable = $.ui.ddmanager.current; if(this.options.activeClass) { this.element.addClass(this.options.activeClass); } if(draggable){ this._trigger("activate", event, this.ui(draggable)); } }, _deactivate: function(event) { var draggable = $.ui.ddmanager.current; if(this.options.activeClass) { this.element.removeClass(this.options.activeClass); } if(draggable){ this._trigger("deactivate", event, this.ui(draggable)); } }, _over: function(event) { var draggable = $.ui.ddmanager.current; // Bail if draggable and droppable are same element if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { return; } if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { if(this.options.hoverClass) { this.element.addClass(this.options.hoverClass); } this._trigger("over", event, this.ui(draggable)); } }, _out: function(event) { var draggable = $.ui.ddmanager.current; // Bail if draggable and droppable are same element if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { return; } if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { if(this.options.hoverClass) { this.element.removeClass(this.options.hoverClass); } this._trigger("out", event, this.ui(draggable)); } }, _drop: function(event,custom) { var draggable = custom || $.ui.ddmanager.current, childrenIntersection = false; // Bail if draggable and droppable are same element if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) { return false; } this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() { var inst = $.data(this, "ui-droppable"); if( inst.options.greedy && !inst.options.disabled && inst.options.scope === draggable.options.scope && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance) ) { childrenIntersection = true; return false; } }); if(childrenIntersection) { return false; } if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { if(this.options.activeClass) { this.element.removeClass(this.options.activeClass); } if(this.options.hoverClass) { this.element.removeClass(this.options.hoverClass); } this._trigger("drop", event, this.ui(draggable)); return this.element; } return false; }, ui: function(c) { return { draggable: (c.currentItem || c.element), helper: c.helper, position: c.position, offset: c.positionAbs }; } }); $.ui.intersect = function(draggable, droppable, toleranceMode) { if (!droppable.offset) { return false; } var draggableLeft, draggableTop, x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height, l = droppable.offset.left, r = l + droppable.proportions.width, t = droppable.offset.top, b = t + droppable.proportions.height; switch (toleranceMode) { case "fit": return (l <= x1 && x2 <= r && t <= y1 && y2 <= b); case "intersect": return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half x2 - (draggable.helperProportions.width / 2) < r && // Left Half t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half y2 - (draggable.helperProportions.height / 2) < b ); // Top Half case "pointer": draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left); draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top); return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width ); case "touch": return ( (y1 >= t && y1 <= b) || // Top edge touching (y2 >= t && y2 <= b) || // Bottom edge touching (y1 < t && y2 > b) // Surrounded vertically ) && ( (x1 >= l && x1 <= r) || // Left edge touching (x2 >= l && x2 <= r) || // Right edge touching (x1 < l && x2 > r) // Surrounded horizontally ); default: return false; } }; /* This manager tracks offsets of draggables and droppables */ $.ui.ddmanager = { current: null, droppables: { "default": [] }, prepareOffsets: function(t, event) { var i, j, m = $.ui.ddmanager.droppables[t.options.scope] || [], type = event ? event.type : null, // workaround for #2317 list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack(); droppablesLoop: for (i = 0; i < m.length; i++) { //No disabled and non-accepted if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) { continue; } // Filter out elements in the current dragged item for (j=0; j < list.length; j++) { if(list[j] === m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } } m[i].visible = m[i].element.css("display") !== "none"; if(!m[i].visible) { continue; } //Activate the droppable if used directly from draggables if(type === "mousedown") { m[i]._activate.call(m[i], event); } m[i].offset = m[i].element.offset(); m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; } }, drop: function(draggable, event) { var dropped = false; // Create a copy of the droppables in case the list changes during the drop (#9116) $.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() { if(!this.options) { return; } if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) { dropped = this._drop.call(this, event) || dropped; } if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { this.isout = true; this.isover = false; this._deactivate.call(this, event); } }); return dropped; }, dragStart: function( draggable, event ) { //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003) draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() { if( !draggable.options.refreshPositions ) { $.ui.ddmanager.prepareOffsets( draggable, event ); } }); }, drag: function(draggable, event) { //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. if(draggable.options.refreshPositions) { $.ui.ddmanager.prepareOffsets(draggable, event); } //Run through all droppables and check their positions based on specific tolerance options $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() { if(this.options.disabled || this.greedyChild || !this.visible) { return; } var parentInstance, scope, parent, intersects = $.ui.intersect(draggable, this, this.options.tolerance), c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null); if(!c) { return; } if (this.options.greedy) { // find droppable parents with same scope scope = this.options.scope; parent = this.element.parents(":data(ui-droppable)").filter(function () { return $.data(this, "ui-droppable").options.scope === scope; }); if (parent.length) { parentInstance = $.data(parent[0], "ui-droppable"); parentInstance.greedyChild = (c === "isover"); } } // we just moved into a greedy child if (parentInstance && c === "isover") { parentInstance.isover = false; parentInstance.isout = true; parentInstance._out.call(parentInstance, event); } this[c] = true; this[c === "isout" ? "isover" : "isout"] = false; this[c === "isover" ? "_over" : "_out"].call(this, event); // we just moved out of a greedy child if (parentInstance && c === "isout") { parentInstance.isout = false; parentInstance.isover = true; parentInstance._over.call(parentInstance, event); } }); }, dragStop: function( draggable, event ) { draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" ); //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003) if( !draggable.options.refreshPositions ) { $.ui.ddmanager.prepareOffsets( draggable, event ); } } }; })(jQuery); (function( $, undefined ) { function num(v) { return parseInt(v, 10) || 0; } function isNumber(value) { return !isNaN(parseInt(value, 10)); } $.widget("ui.resizable", $.ui.mouse, { version: "1.10.3", widgetEventPrefix: "resize", options: { alsoResize: false, animate: false, animateDuration: "slow", animateEasing: "swing", aspectRatio: false, autoHide: false, containment: false, ghost: false, grid: false, handles: "e,s,se", helper: false, maxHeight: null, maxWidth: null, minHeight: 10, minWidth: 10, // See #7960 zIndex: 90, // callbacks resize: null, start: null, stop: null }, _create: function() { var n, i, handle, axis, hname, that = this, o = this.options; this.element.addClass("ui-resizable"); $.extend(this, { _aspectRatio: !!(o.aspectRatio), aspectRatio: o.aspectRatio, originalElement: this.element, _proportionallyResizeElements: [], _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null }); //Wrap the element if it cannot hold child nodes if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) { //Create a wrapper element and set the wrapper to the new current internal element this.element.wrap( $("
").css({ position: this.element.css("position"), width: this.element.outerWidth(), height: this.element.outerHeight(), top: this.element.css("top"), left: this.element.css("left") }) ); //Overwrite the original this.element this.element = this.element.parent().data( "ui-resizable", this.element.data("ui-resizable") ); this.elementIsWrapper = true; //Move margins to the wrapper this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") }); this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0}); //Prevent Safari textarea resize this.originalResizeStyle = this.originalElement.css("resize"); this.originalElement.css("resize", "none"); //Push the actual element to our proportionallyResize internal array this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" })); // avoid IE jump (hard set the margin) this.originalElement.css({ margin: this.originalElement.css("margin") }); // fix handlers offset this._proportionallyResize(); } this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" }); if(this.handles.constructor === String) { if ( this.handles === "all") { this.handles = "n,e,s,w,se,sw,ne,nw"; } n = this.handles.split(","); this.handles = {}; for(i = 0; i < n.length; i++) { handle = $.trim(n[i]); hname = "ui-resizable-"+handle; axis = $("
"); // Apply zIndex to all handles - see #7960 axis.css({ zIndex: o.zIndex }); //TODO : What's going on here? if ("se" === handle) { axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se"); } //Insert into internal handles object and append to element this.handles[handle] = ".ui-resizable-"+handle; this.element.append(axis); } } this._renderAxis = function(target) { var i, axis, padPos, padWrapper; target = target || this.element; for(i in this.handles) { if(this.handles[i].constructor === String) { this.handles[i] = $(this.handles[i], this.element).show(); } //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls) if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) { axis = $(this.handles[i], this.element); //Checking the correct pad and border padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth(); //The padding type i have to apply... padPos = [ "padding", /ne|nw|n/.test(i) ? "Top" : /se|sw|s/.test(i) ? "Bottom" : /^e$/.test(i) ? "Right" : "Left" ].join(""); target.css(padPos, padWrapper); this._proportionallyResize(); } //TODO: What's that good for? There's not anything to be executed left if(!$(this.handles[i]).length) { continue; } } }; //TODO: make renderAxis a prototype function this._renderAxis(this.element); this._handles = $(".ui-resizable-handle", this.element) .disableSelection(); //Matching axis name this._handles.mouseover(function() { if (!that.resizing) { if (this.className) { axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i); } //Axis, default = se that.axis = axis && axis[1] ? axis[1] : "se"; } }); //If we want to auto hide the elements if (o.autoHide) { this._handles.hide(); $(this.element) .addClass("ui-resizable-autohide") .mouseenter(function() { if (o.disabled) { return; } $(this).removeClass("ui-resizable-autohide"); that._handles.show(); }) .mouseleave(function(){ if (o.disabled) { return; } if (!that.resizing) { $(this).addClass("ui-resizable-autohide"); that._handles.hide(); } }); } //Initialize the mouse interaction this._mouseInit(); }, _destroy: function() { this._mouseDestroy(); var wrapper, _destroy = function(exp) { $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing") .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove(); }; //TODO: Unwrap at same DOM position if (this.elementIsWrapper) { _destroy(this.element); wrapper = this.element; this.originalElement.css({ position: wrapper.css("position"), width: wrapper.outerWidth(), height: wrapper.outerHeight(), top: wrapper.css("top"), left: wrapper.css("left") }).insertAfter( wrapper ); wrapper.remove(); } this.originalElement.css("resize", this.originalResizeStyle); _destroy(this.originalElement); return this; }, _mouseCapture: function(event) { var i, handle, capture = false; for (i in this.handles) { handle = $(this.handles[i])[0]; if (handle === event.target || $.contains(handle, event.target)) { capture = true; } } return !this.options.disabled && capture; }, _mouseStart: function(event) { var curleft, curtop, cursor, o = this.options, iniPos = this.element.position(), el = this.element; this.resizing = true; // bugfix for http://dev.jquery.com/ticket/1749 if ( (/absolute/).test( el.css("position") ) ) { el.css({ position: "absolute", top: el.css("top"), left: el.css("left") }); } else if (el.is(".ui-draggable")) { el.css({ position: "absolute", top: iniPos.top, left: iniPos.left }); } this._renderProxy(); curleft = num(this.helper.css("left")); curtop = num(this.helper.css("top")); if (o.containment) { curleft += $(o.containment).scrollLeft() || 0; curtop += $(o.containment).scrollTop() || 0; } //Store needed variables this.offset = this.helper.offset(); this.position = { left: curleft, top: curtop }; this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() }; this.originalPosition = { left: curleft, top: curtop }; this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() }; this.originalMousePosition = { left: event.pageX, top: event.pageY }; //Aspect Ratio this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1); cursor = $(".ui-resizable-" + this.axis).css("cursor"); $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor); el.addClass("ui-resizable-resizing"); this._propagate("start", event); return true; }, _mouseDrag: function(event) { //Increase performance, avoid regex var data, el = this.helper, props = {}, smp = this.originalMousePosition, a = this.axis, prevTop = this.position.top, prevLeft = this.position.left, prevWidth = this.size.width, prevHeight = this.size.height, dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0, trigger = this._change[a]; if (!trigger) { return false; } // Calculate the attrs that will be change data = trigger.apply(this, [event, dx, dy]); // Put this in the mouseDrag handler since the user can start pressing shift while resizing this._updateVirtualBoundaries(event.shiftKey); if (this._aspectRatio || event.shiftKey) { data = this._updateRatio(data, event); } data = this._respectSize(data, event); this._updateCache(data); // plugins callbacks need to be called first this._propagate("resize", event); if (this.position.top !== prevTop) { props.top = this.position.top + "px"; } if (this.position.left !== prevLeft) { props.left = this.position.left + "px"; } if (this.size.width !== prevWidth) { props.width = this.size.width + "px"; } if (this.size.height !== prevHeight) { props.height = this.size.height + "px"; } el.css(props); if (!this._helper && this._proportionallyResizeElements.length) { this._proportionallyResize(); } // Call the user callback if the element was resized if ( ! $.isEmptyObject(props) ) { this._trigger("resize", event, this.ui()); } return false; }, _mouseStop: function(event) { this.resizing = false; var pr, ista, soffseth, soffsetw, s, left, top, o = this.options, that = this; if(this._helper) { pr = this._proportionallyResizeElements; ista = pr.length && (/textarea/i).test(pr[0].nodeName); soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height; soffsetw = ista ? 0 : that.sizeDiff.width; s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) }; left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null; top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; if (!o.animate) { this.element.css($.extend(s, { top: top, left: left })); } that.helper.height(that.size.height); that.helper.width(that.size.width); if (this._helper && !o.animate) { this._proportionallyResize(); } } $("body").css("cursor", "auto"); this.element.removeClass("ui-resizable-resizing"); this._propagate("stop", event); if (this._helper) { this.helper.remove(); } return false; }, _updateVirtualBoundaries: function(forceAspectRatio) { var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b, o = this.options; b = { minWidth: isNumber(o.minWidth) ? o.minWidth : 0, maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity, minHeight: isNumber(o.minHeight) ? o.minHeight : 0, maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity }; if(this._aspectRatio || forceAspectRatio) { // We want to create an enclosing box whose aspect ration is the requested one // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension pMinWidth = b.minHeight * this.aspectRatio; pMinHeight = b.minWidth / this.aspectRatio; pMaxWidth = b.maxHeight * this.aspectRatio; pMaxHeight = b.maxWidth / this.aspectRatio; if(pMinWidth > b.minWidth) { b.minWidth = pMinWidth; } if(pMinHeight > b.minHeight) { b.minHeight = pMinHeight; } if(pMaxWidth < b.maxWidth) { b.maxWidth = pMaxWidth; } if(pMaxHeight < b.maxHeight) { b.maxHeight = pMaxHeight; } } this._vBoundaries = b; }, _updateCache: function(data) { this.offset = this.helper.offset(); if (isNumber(data.left)) { this.position.left = data.left; } if (isNumber(data.top)) { this.position.top = data.top; } if (isNumber(data.height)) { this.size.height = data.height; } if (isNumber(data.width)) { this.size.width = data.width; } }, _updateRatio: function( data ) { var cpos = this.position, csize = this.size, a = this.axis; if (isNumber(data.height)) { data.width = (data.height * this.aspectRatio); } else if (isNumber(data.width)) { data.height = (data.width / this.aspectRatio); } if (a === "sw") { data.left = cpos.left + (csize.width - data.width); data.top = null; } if (a === "nw") { data.top = cpos.top + (csize.height - data.height); data.left = cpos.left + (csize.width - data.width); } return data; }, _respectSize: function( data ) { var o = this._vBoundaries, a = this.axis, ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height), isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height), dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height, cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a); if (isminw) { data.width = o.minWidth; } if (isminh) { data.height = o.minHeight; } if (ismaxw) { data.width = o.maxWidth; } if (ismaxh) { data.height = o.maxHeight; } if (isminw && cw) { data.left = dw - o.minWidth; } if (ismaxw && cw) { data.left = dw - o.maxWidth; } if (isminh && ch) { data.top = dh - o.minHeight; } if (ismaxh && ch) { data.top = dh - o.maxHeight; } // fixing jump error on top/left - bug #2330 if (!data.width && !data.height && !data.left && data.top) { data.top = null; } else if (!data.width && !data.height && !data.top && data.left) { data.left = null; } return data; }, _proportionallyResize: function() { if (!this._proportionallyResizeElements.length) { return; } var i, j, borders, paddings, prel, element = this.helper || this.element; for ( i=0; i < this._proportionallyResizeElements.length; i++) { prel = this._proportionallyResizeElements[i]; if (!this.borderDif) { this.borderDif = []; borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")]; paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")]; for ( j = 0; j < borders.length; j++ ) { this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 ); } } prel.css({ height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0, width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0 }); } }, _renderProxy: function() { var el = this.element, o = this.options; this.elementOffset = el.offset(); if(this._helper) { this.helper = this.helper || $("
"); this.helper.addClass(this._helper).css({ width: this.element.outerWidth() - 1, height: this.element.outerHeight() - 1, position: "absolute", left: this.elementOffset.left +"px", top: this.elementOffset.top +"px", zIndex: ++o.zIndex //TODO: Don't modify option }); this.helper .appendTo("body") .disableSelection(); } else { this.helper = this.element; } }, _change: { e: function(event, dx) { return { width: this.originalSize.width + dx }; }, w: function(event, dx) { var cs = this.originalSize, sp = this.originalPosition; return { left: sp.left + dx, width: cs.width - dx }; }, n: function(event, dx, dy) { var cs = this.originalSize, sp = this.originalPosition; return { top: sp.top + dy, height: cs.height - dy }; }, s: function(event, dx, dy) { return { height: this.originalSize.height + dy }; }, se: function(event, dx, dy) { return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); }, sw: function(event, dx, dy) { return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); }, ne: function(event, dx, dy) { return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy])); }, nw: function(event, dx, dy) { return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy])); } }, _propagate: function(n, event) { $.ui.plugin.call(this, n, [event, this.ui()]); (n !== "resize" && this._trigger(n, event, this.ui())); }, plugins: {}, ui: function() { return { originalElement: this.originalElement, element: this.element, helper: this.helper, position: this.position, size: this.size, originalSize: this.originalSize, originalPosition: this.originalPosition }; } }); /* * Resizable Extensions */ $.ui.plugin.add("resizable", "animate", { stop: function( event ) { var that = $(this).data("ui-resizable"), o = that.options, pr = that._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName), soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height, soffsetw = ista ? 0 : that.sizeDiff.width, style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) }, left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null, top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null; that.element.animate( $.extend(style, top && left ? { top: top, left: left } : {}), { duration: o.animateDuration, easing: o.animateEasing, step: function() { var data = { width: parseInt(that.element.css("width"), 10), height: parseInt(that.element.css("height"), 10), top: parseInt(that.element.css("top"), 10), left: parseInt(that.element.css("left"), 10) }; if (pr && pr.length) { $(pr[0]).css({ width: data.width, height: data.height }); } // propagating resize, and updating values for each animation step that._updateCache(data); that._propagate("resize", event); } } ); } }); $.ui.plugin.add("resizable", "containment", { start: function() { var element, p, co, ch, cw, width, height, that = $(this).data("ui-resizable"), o = that.options, el = that.element, oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc; if (!ce) { return; } that.containerElement = $(ce); if (/document/.test(oc) || oc === document) { that.containerOffset = { left: 0, top: 0 }; that.containerPosition = { left: 0, top: 0 }; that.parentData = { element: $(document), left: 0, top: 0, width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight }; } // i'm a node, so compute top, left, right, bottom else { element = $(ce); p = []; $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); }); that.containerOffset = element.offset(); that.containerPosition = element.position(); that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) }; co = that.containerOffset; ch = that.containerSize.height; cw = that.containerSize.width; width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ); height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch); that.parentData = { element: ce, left: co.left, top: co.top, width: width, height: height }; } }, resize: function( event ) { var woset, hoset, isParent, isOffsetRelative, that = $(this).data("ui-resizable"), o = that.options, co = that.containerOffset, cp = that.position, pRatio = that._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = that.containerElement; if (ce[0] !== document && (/static/).test(ce.css("position"))) { cop = co; } if (cp.left < (that._helper ? co.left : 0)) { that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left)); if (pRatio) { that.size.height = that.size.width / that.aspectRatio; } that.position.left = o.helper ? co.left : 0; } if (cp.top < (that._helper ? co.top : 0)) { that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top); if (pRatio) { that.size.width = that.size.height * that.aspectRatio; } that.position.top = that._helper ? co.top : 0; } that.offset.left = that.parentData.left+that.position.left; that.offset.top = that.parentData.top+that.position.top; woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ); hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height ); isParent = that.containerElement.get(0) === that.element.parent().get(0); isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position")); if(isParent && isOffsetRelative) { woset -= that.parentData.left; } if (woset + that.size.width >= that.parentData.width) { that.size.width = that.parentData.width - woset; if (pRatio) { that.size.height = that.size.width / that.aspectRatio; } } if (hoset + that.size.height >= that.parentData.height) { that.size.height = that.parentData.height - hoset; if (pRatio) { that.size.width = that.size.height * that.aspectRatio; } } }, stop: function(){ var that = $(this).data("ui-resizable"), o = that.options, co = that.containerOffset, cop = that.containerPosition, ce = that.containerElement, helper = $(that.helper), ho = helper.offset(), w = helper.outerWidth() - that.sizeDiff.width, h = helper.outerHeight() - that.sizeDiff.height; if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) { $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); } if (that._helper && !o.animate && (/static/).test(ce.css("position"))) { $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h }); } } }); $.ui.plugin.add("resizable", "alsoResize", { start: function () { var that = $(this).data("ui-resizable"), o = that.options, _store = function (exp) { $(exp).each(function() { var el = $(this); el.data("ui-resizable-alsoresize", { width: parseInt(el.width(), 10), height: parseInt(el.height(), 10), left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10) }); }); }; if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) { if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); } else { $.each(o.alsoResize, function (exp) { _store(exp); }); } }else{ _store(o.alsoResize); } }, resize: function (event, ui) { var that = $(this).data("ui-resizable"), o = that.options, os = that.originalSize, op = that.originalPosition, delta = { height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0, top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0 }, _alsoResize = function (exp, c) { $(exp).each(function() { var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {}, css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"]; $.each(css, function (i, prop) { var sum = (start[prop]||0) + (delta[prop]||0); if (sum && sum >= 0) { style[prop] = sum || null; } }); el.css(style); }); }; if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) { $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); }); }else{ _alsoResize(o.alsoResize); } }, stop: function () { $(this).removeData("resizable-alsoresize"); } }); $.ui.plugin.add("resizable", "ghost", { start: function() { var that = $(this).data("ui-resizable"), o = that.options, cs = that.size; that.ghost = that.originalElement.clone(); that.ghost .css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 }) .addClass("ui-resizable-ghost") .addClass(typeof o.ghost === "string" ? o.ghost : ""); that.ghost.appendTo(that.helper); }, resize: function(){ var that = $(this).data("ui-resizable"); if (that.ghost) { that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width }); } }, stop: function() { var that = $(this).data("ui-resizable"); if (that.ghost && that.helper) { that.helper.get(0).removeChild(that.ghost.get(0)); } } }); $.ui.plugin.add("resizable", "grid", { resize: function() { var that = $(this).data("ui-resizable"), o = that.options, cs = that.size, os = that.originalSize, op = that.originalPosition, a = that.axis, grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid, gridX = (grid[0]||1), gridY = (grid[1]||1), ox = Math.round((cs.width - os.width) / gridX) * gridX, oy = Math.round((cs.height - os.height) / gridY) * gridY, newWidth = os.width + ox, newHeight = os.height + oy, isMaxWidth = o.maxWidth && (o.maxWidth < newWidth), isMaxHeight = o.maxHeight && (o.maxHeight < newHeight), isMinWidth = o.minWidth && (o.minWidth > newWidth), isMinHeight = o.minHeight && (o.minHeight > newHeight); o.grid = grid; if (isMinWidth) { newWidth = newWidth + gridX; } if (isMinHeight) { newHeight = newHeight + gridY; } if (isMaxWidth) { newWidth = newWidth - gridX; } if (isMaxHeight) { newHeight = newHeight - gridY; } if (/^(se|s|e)$/.test(a)) { that.size.width = newWidth; that.size.height = newHeight; } else if (/^(ne)$/.test(a)) { that.size.width = newWidth; that.size.height = newHeight; that.position.top = op.top - oy; } else if (/^(sw)$/.test(a)) { that.size.width = newWidth; that.size.height = newHeight; that.position.left = op.left - ox; } else { that.size.width = newWidth; that.size.height = newHeight; that.position.top = op.top - oy; that.position.left = op.left - ox; } } }); })(jQuery); (function( $, undefined ) { $.widget("ui.selectable", $.ui.mouse, { version: "1.10.3", options: { appendTo: "body", autoRefresh: true, distance: 0, filter: "*", tolerance: "touch", // callbacks selected: null, selecting: null, start: null, stop: null, unselected: null, unselecting: null }, _create: function() { var selectees, that = this; this.element.addClass("ui-selectable"); this.dragged = false; // cache selectee children based on filter this.refresh = function() { selectees = $(that.options.filter, that.element[0]); selectees.addClass("ui-selectee"); selectees.each(function() { var $this = $(this), pos = $this.offset(); $.data(this, "selectable-item", { element: this, $element: $this, left: pos.left, top: pos.top, right: pos.left + $this.outerWidth(), bottom: pos.top + $this.outerHeight(), startselected: false, selected: $this.hasClass("ui-selected"), selecting: $this.hasClass("ui-selecting"), unselecting: $this.hasClass("ui-unselecting") }); }); }; this.refresh(); this.selectees = selectees.addClass("ui-selectee"); this._mouseInit(); this.helper = $("
"); }, _destroy: function() { this.selectees .removeClass("ui-selectee") .removeData("selectable-item"); this.element .removeClass("ui-selectable ui-selectable-disabled"); this._mouseDestroy(); }, _mouseStart: function(event) { var that = this, options = this.options; this.opos = [event.pageX, event.pageY]; if (this.options.disabled) { return; } this.selectees = $(options.filter, this.element[0]); this._trigger("start", event); $(options.appendTo).append(this.helper); // position helper (lasso) this.helper.css({ "left": event.pageX, "top": event.pageY, "width": 0, "height": 0 }); if (options.autoRefresh) { this.refresh(); } this.selectees.filter(".ui-selected").each(function() { var selectee = $.data(this, "selectable-item"); selectee.startselected = true; if (!event.metaKey && !event.ctrlKey) { selectee.$element.removeClass("ui-selected"); selectee.selected = false; selectee.$element.addClass("ui-unselecting"); selectee.unselecting = true; // selectable UNSELECTING callback that._trigger("unselecting", event, { unselecting: selectee.element }); } }); $(event.target).parents().addBack().each(function() { var doSelect, selectee = $.data(this, "selectable-item"); if (selectee) { doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected"); selectee.$element .removeClass(doSelect ? "ui-unselecting" : "ui-selected") .addClass(doSelect ? "ui-selecting" : "ui-unselecting"); selectee.unselecting = !doSelect; selectee.selecting = doSelect; selectee.selected = doSelect; // selectable (UN)SELECTING callback if (doSelect) { that._trigger("selecting", event, { selecting: selectee.element }); } else { that._trigger("unselecting", event, { unselecting: selectee.element }); } return false; } }); }, _mouseDrag: function(event) { this.dragged = true; if (this.options.disabled) { return; } var tmp, that = this, options = this.options, x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY; if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; } if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; } this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1}); this.selectees.each(function() { var selectee = $.data(this, "selectable-item"), hit = false; //prevent helper from being selected if appendTo: selectable if (!selectee || selectee.element === that.element[0]) { return; } if (options.tolerance === "touch") { hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) ); } else if (options.tolerance === "fit") { hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2); } if (hit) { // SELECT if (selectee.selected) { selectee.$element.removeClass("ui-selected"); selectee.selected = false; } if (selectee.unselecting) { selectee.$element.removeClass("ui-unselecting"); selectee.unselecting = false; } if (!selectee.selecting) { selectee.$element.addClass("ui-selecting"); selectee.selecting = true; // selectable SELECTING callback that._trigger("selecting", event, { selecting: selectee.element }); } } else { // UNSELECT if (selectee.selecting) { if ((event.metaKey || event.ctrlKey) && selectee.startselected) { selectee.$element.removeClass("ui-selecting"); selectee.selecting = false; selectee.$element.addClass("ui-selected"); selectee.selected = true; } else { selectee.$element.removeClass("ui-selecting"); selectee.selecting = false; if (selectee.startselected) { selectee.$element.addClass("ui-unselecting"); selectee.unselecting = true; } // selectable UNSELECTING callback that._trigger("unselecting", event, { unselecting: selectee.element }); } } if (selectee.selected) { if (!event.metaKey && !event.ctrlKey && !selectee.startselected) { selectee.$element.removeClass("ui-selected"); selectee.selected = false; selectee.$element.addClass("ui-unselecting"); selectee.unselecting = true; // selectable UNSELECTING callback that._trigger("unselecting", event, { unselecting: selectee.element }); } } } }); return false; }, _mouseStop: function(event) { var that = this; this.dragged = false; $(".ui-unselecting", this.element[0]).each(function() { var selectee = $.data(this, "selectable-item"); selectee.$element.removeClass("ui-unselecting"); selectee.unselecting = false; selectee.startselected = false; that._trigger("unselected", event, { unselected: selectee.element }); }); $(".ui-selecting", this.element[0]).each(function() { var selectee = $.data(this, "selectable-item"); selectee.$element.removeClass("ui-selecting").addClass("ui-selected"); selectee.selecting = false; selectee.selected = true; selectee.startselected = true; that._trigger("selected", event, { selected: selectee.element }); }); this._trigger("stop", event); this.helper.remove(); return false; } }); })(jQuery); (function( $, undefined ) { /*jshint loopfunc: true */ function isOverAxis( x, reference, size ) { return ( x > reference ) && ( x < ( reference + size ) ); } function isFloating(item) { return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); } $.widget("ui.sortable", $.ui.mouse, { version: "1.10.3", widgetEventPrefix: "sort", ready: false, options: { appendTo: "parent", axis: false, connectWith: false, containment: false, cursor: "auto", cursorAt: false, dropOnEmpty: true, forcePlaceholderSize: false, forceHelperSize: false, grid: false, handle: false, helper: "original", items: "> *", opacity: false, placeholder: false, revert: false, scroll: true, scrollSensitivity: 20, scrollSpeed: 20, scope: "default", tolerance: "intersect", zIndex: 1000, // callbacks activate: null, beforeStop: null, change: null, deactivate: null, out: null, over: null, receive: null, remove: null, sort: null, start: null, stop: null, update: null }, _create: function() { var o = this.options; this.containerCache = {}; this.element.addClass("ui-sortable"); //Get the items this.refresh(); //Let's determine if the items are being displayed horizontally this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; //Let's determine the parent's offset this.offset = this.element.offset(); //Initialize mouse events for interaction this._mouseInit(); //We're ready to go this.ready = true; }, _destroy: function() { this.element .removeClass("ui-sortable ui-sortable-disabled"); this._mouseDestroy(); for ( var i = this.items.length - 1; i >= 0; i-- ) { this.items[i].item.removeData(this.widgetName + "-item"); } return this; }, _setOption: function(key, value){ if ( key === "disabled" ) { this.options[ key ] = value; this.widget().toggleClass( "ui-sortable-disabled", !!value ); } else { // Don't call widget base _setOption for disable as it adds ui-state-disabled class $.Widget.prototype._setOption.apply(this, arguments); } }, _mouseCapture: function(event, overrideHandle) { var currentItem = null, validHandle = false, that = this; if (this.reverting) { return false; } if(this.options.disabled || this.options.type === "static") { return false; } //We have to refresh the items data once first this._refreshItems(event); //Find out if the clicked node (or one of its parents) is a actual item in this.items $(event.target).parents().each(function() { if($.data(this, that.widgetName + "-item") === that) { currentItem = $(this); return false; } }); if($.data(event.target, that.widgetName + "-item") === that) { currentItem = $(event.target); } if(!currentItem) { return false; } if(this.options.handle && !overrideHandle) { $(this.options.handle, currentItem).find("*").addBack().each(function() { if(this === event.target) { validHandle = true; } }); if(!validHandle) { return false; } } this.currentItem = currentItem; this._removeCurrentsFromItems(); return true; }, _mouseStart: function(event, overrideHandle, noActivation) { var i, body, o = this.options; this.currentContainer = this; //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture this.refreshPositions(); //Create and append the visible helper this.helper = this._createHelper(event); //Cache the helper size this._cacheHelperProportions(); /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ //Cache the margins of the original element this._cacheMargins(); //Get the next scrolling parent this.scrollParent = this.helper.scrollParent(); //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { top: this.offset.top - this.margins.top, left: this.offset.left - this.margins.left }; $.extend(this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, parent: this._getParentOffset(), relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); // Only after we got the offset, we can change the helper's position to absolute // TODO: Still need to figure out a way to make relative sorting possible this.helper.css("position", "absolute"); this.cssPosition = this.helper.css("position"); //Generate the original position this.originalPosition = this._generatePosition(event); this.originalPageX = event.pageX; this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); //Cache the former DOM position this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way if(this.helper[0] !== this.currentItem[0]) { this.currentItem.hide(); } //Create the placeholder this._createPlaceholder(); //Set a containment if given in the options if(o.containment) { this._setContainment(); } if( o.cursor && o.cursor !== "auto" ) { // cursor option body = this.document.find( "body" ); // support: IE this.storedCursor = body.css( "cursor" ); body.css( "cursor", o.cursor ); this.storedStylesheet = $( "" ).appendTo( body ); } if(o.opacity) { // opacity option if (this.helper.css("opacity")) { this._storedOpacity = this.helper.css("opacity"); } this.helper.css("opacity", o.opacity); } if(o.zIndex) { // zIndex option if (this.helper.css("zIndex")) { this._storedZIndex = this.helper.css("zIndex"); } this.helper.css("zIndex", o.zIndex); } //Prepare scrolling if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { this.overflowOffset = this.scrollParent.offset(); } //Call callbacks this._trigger("start", event, this._uiHash()); //Recache the helper size if(!this._preserveHelperProportions) { this._cacheHelperProportions(); } //Post "activate" events to possible containers if( !noActivation ) { for ( i = this.containers.length - 1; i >= 0; i-- ) { this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); } } //Prepare possible droppables if($.ui.ddmanager) { $.ui.ddmanager.current = this; } if ($.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } this.dragging = true; this.helper.addClass("ui-sortable-helper"); this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position return true; }, _mouseDrag: function(event) { var i, item, itemElement, intersection, o = this.options, scrolled = false; //Compute the helpers position this.position = this._generatePosition(event); this.positionAbs = this._convertPositionTo("absolute"); if (!this.lastPositionAbs) { this.lastPositionAbs = this.positionAbs; } //Do scrolling if(this.options.scroll) { if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; } if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; } } else { if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); } if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); } } if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } } //Regenerate the absolute position used for position checks this.positionAbs = this._convertPositionTo("absolute"); //Set the helper position if(!this.options.axis || this.options.axis !== "y") { this.helper[0].style.left = this.position.left+"px"; } if(!this.options.axis || this.options.axis !== "x") { this.helper[0].style.top = this.position.top+"px"; } //Rearrange for (i = this.items.length - 1; i >= 0; i--) { //Cache variables and intersection, continue if no intersection item = this.items[i]; itemElement = item.item[0]; intersection = this._intersectsWithPointer(item); if (!intersection) { continue; } // Only put the placeholder inside the current Container, skip all // items form other containers. This works because when moving // an item from one container to another the // currentContainer is switched before the placeholder is moved. // // Without this moving items in "sub-sortables" can cause the placeholder to jitter // beetween the outer and inner container. if (item.instance !== this.currentContainer) { continue; } // cannot intersect with itself // no useless actions that have been done before // no action if the item moved is the parent of the item checked if (itemElement !== this.currentItem[0] && this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && !$.contains(this.placeholder[0], itemElement) && (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) ) { this.direction = intersection === 1 ? "down" : "up"; if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { this._rearrange(event, item); } else { break; } this._trigger("change", event, this._uiHash()); break; } } //Post events to containers this._contactContainers(event); //Interconnect with droppables if($.ui.ddmanager) { $.ui.ddmanager.drag(this, event); } //Call callbacks this._trigger("sort", event, this._uiHash()); this.lastPositionAbs = this.positionAbs; return false; }, _mouseStop: function(event, noPropagation) { if(!event) { return; } //If we are using droppables, inform the manager about the drop if ($.ui.ddmanager && !this.options.dropBehaviour) { $.ui.ddmanager.drop(this, event); } if(this.options.revert) { var that = this, cur = this.placeholder.offset(), axis = this.options.axis, animation = {}; if ( !axis || axis === "x" ) { animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft); } if ( !axis || axis === "y" ) { animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop); } this.reverting = true; $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { that._clear(event); }); } else { this._clear(event, noPropagation); } return false; }, cancel: function() { if(this.dragging) { this._mouseUp({ target: null }); if(this.options.helper === "original") { this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); } else { this.currentItem.show(); } //Post deactivating events to containers for (var i = this.containers.length - 1; i >= 0; i--){ this.containers[i]._trigger("deactivate", null, this._uiHash(this)); if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", null, this._uiHash(this)); this.containers[i].containerCache.over = 0; } } } if (this.placeholder) { //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! if(this.placeholder[0].parentNode) { this.placeholder[0].parentNode.removeChild(this.placeholder[0]); } if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { this.helper.remove(); } $.extend(this, { helper: null, dragging: false, reverting: false, _noFinalSort: null }); if(this.domPosition.prev) { $(this.domPosition.prev).after(this.currentItem); } else { $(this.domPosition.parent).prepend(this.currentItem); } } return this; }, serialize: function(o) { var items = this._getItemsAsjQuery(o && o.connected), str = []; o = o || {}; $(items).each(function() { var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); if (res) { str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); } }); if(!str.length && o.key) { str.push(o.key + "="); } return str.join("&"); }, toArray: function(o) { var items = this._getItemsAsjQuery(o && o.connected), ret = []; o = o || {}; items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); return ret; }, /* Be careful with the following core functions */ _intersectsWith: function(item) { var x1 = this.positionAbs.left, x2 = x1 + this.helperProportions.width, y1 = this.positionAbs.top, y2 = y1 + this.helperProportions.height, l = item.left, r = l + item.width, t = item.top, b = t + item.height, dyClick = this.offset.click.top, dxClick = this.offset.click.left, isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), isOverElement = isOverElementHeight && isOverElementWidth; if ( this.options.tolerance === "pointer" || this.options.forcePointerForContainers || (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) ) { return isOverElement; } else { return (l < x1 + (this.helperProportions.width / 2) && // Right Half x2 - (this.helperProportions.width / 2) < r && // Left Half t < y1 + (this.helperProportions.height / 2) && // Bottom Half y2 - (this.helperProportions.height / 2) < b ); // Top Half } }, _intersectsWithPointer: function(item) { var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), isOverElement = isOverElementHeight && isOverElementWidth, verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (!isOverElement) { return false; } return this.floating ? ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); }, _intersectsWithSides: function(item) { var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (this.floating && horizontalDirection) { return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); } else { return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); } }, _getDragVerticalDirection: function() { var delta = this.positionAbs.top - this.lastPositionAbs.top; return delta !== 0 && (delta > 0 ? "down" : "up"); }, _getDragHorizontalDirection: function() { var delta = this.positionAbs.left - this.lastPositionAbs.left; return delta !== 0 && (delta > 0 ? "right" : "left"); }, refresh: function(event) { this._refreshItems(event); this.refreshPositions(); return this; }, _connectWith: function() { var options = this.options; return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; }, _getItemsAsjQuery: function(connected) { var i, j, cur, inst, items = [], queries = [], connectWith = this._connectWith(); if(connectWith && connected) { for (i = connectWith.length - 1; i >= 0; i--){ cur = $(connectWith[i]); for ( j = cur.length - 1; j >= 0; j--){ inst = $.data(cur[j], this.widgetFullName); if(inst && inst !== this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); } } } } queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); for (i = queries.length - 1; i >= 0; i--){ queries[i][0].each(function() { items.push(this); }); } return $(items); }, _removeCurrentsFromItems: function() { var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); this.items = $.grep(this.items, function (item) { for (var j=0; j < list.length; j++) { if(list[j] === item.item[0]) { return false; } } return true; }); }, _refreshItems: function(event) { this.items = []; this.containers = [this]; var i, j, cur, inst, targetData, _queries, item, queriesLength, items = this.items, queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], connectWith = this._connectWith(); if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down for (i = connectWith.length - 1; i >= 0; i--){ cur = $(connectWith[i]); for (j = cur.length - 1; j >= 0; j--){ inst = $.data(cur[j], this.widgetFullName); if(inst && inst !== this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); this.containers.push(inst); } } } } for (i = queries.length - 1; i >= 0; i--) { targetData = queries[i][1]; _queries = queries[i][0]; for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { item = $(_queries[j]); item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) items.push({ item: item, instance: targetData, width: 0, height: 0, left: 0, top: 0 }); } } }, refreshPositions: function(fast) { //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change if(this.offsetParent && this.helper) { this.offset.parent = this._getParentOffset(); } var i, item, t, p; for (i = this.items.length - 1; i >= 0; i--){ item = this.items[i]; //We ignore calculating positions of all connected containers when we're not over them if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { continue; } t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; if (!fast) { item.width = t.outerWidth(); item.height = t.outerHeight(); } p = t.offset(); item.left = p.left; item.top = p.top; } if(this.options.custom && this.options.custom.refreshContainers) { this.options.custom.refreshContainers.call(this); } else { for (i = this.containers.length - 1; i >= 0; i--){ p = this.containers[i].element.offset(); this.containers[i].containerCache.left = p.left; this.containers[i].containerCache.top = p.top; this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); } } return this; }, _createPlaceholder: function(that) { that = that || this; var className, o = that.options; if(!o.placeholder || o.placeholder.constructor === String) { className = o.placeholder; o.placeholder = { element: function() { var nodeName = that.currentItem[0].nodeName.toLowerCase(), element = $( "<" + nodeName + ">", that.document[0] ) .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") .removeClass("ui-sortable-helper"); if ( nodeName === "tr" ) { that.currentItem.children().each(function() { $( " ", that.document[0] ) .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) .appendTo( element ); }); } else if ( nodeName === "img" ) { element.attr( "src", that.currentItem.attr( "src" ) ); } if ( !className ) { element.css( "visibility", "hidden" ); } return element; }, update: function(container, p) { // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified if(className && !o.forcePlaceholderSize) { return; } //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } } }; } //Create the placeholder that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); //Append it after the actual current item that.currentItem.after(that.placeholder); //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) o.placeholder.update(that, that.placeholder); }, _contactContainers: function(event) { var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating, innermostContainer = null, innermostIndex = null; // get innermost container that intersects with item for (i = this.containers.length - 1; i >= 0; i--) { // never consider a container that's located within the item itself if($.contains(this.currentItem[0], this.containers[i].element[0])) { continue; } if(this._intersectsWith(this.containers[i].containerCache)) { // if we've already found a container and it's more "inner" than this, then continue if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { continue; } innermostContainer = this.containers[i]; innermostIndex = i; } else { // container doesn't intersect. trigger "out" event if necessary if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", event, this._uiHash(this)); this.containers[i].containerCache.over = 0; } } } // if no intersecting containers found, return if(!innermostContainer) { return; } // move the item into the container if it's not there already if(this.containers.length === 1) { if (!this.containers[innermostIndex].containerCache.over) { this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } } else { //When entering a new container, we will find the item with the least distance and append our item near it dist = 10000; itemWithLeastDistance = null; floating = innermostContainer.floating || isFloating(this.currentItem); posProperty = floating ? "left" : "top"; sizeProperty = floating ? "width" : "height"; base = this.positionAbs[posProperty] + this.offset.click[posProperty]; for (j = this.items.length - 1; j >= 0; j--) { if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { continue; } if(this.items[j].item[0] === this.currentItem[0]) { continue; } if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) { continue; } cur = this.items[j].item.offset()[posProperty]; nearBottom = false; if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ nearBottom = true; cur += this.items[j][sizeProperty]; } if(Math.abs(cur - base) < dist) { dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; this.direction = nearBottom ? "up": "down"; } } //Check if dropOnEmpty is enabled if(!itemWithLeastDistance && !this.options.dropOnEmpty) { return; } if(this.currentContainer === this.containers[innermostIndex]) { return; } itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); this._trigger("change", event, this._uiHash()); this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); this.currentContainer = this.containers[innermostIndex]; //Update the placeholder this.options.placeholder.update(this.currentContainer, this.placeholder); this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } }, _createHelper: function(event) { var o = this.options, helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); //Add the helper to the DOM if that didn't happen already if(!helper.parents("body").length) { $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); } if(helper[0] === this.currentItem[0]) { this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; } if(!helper[0].style.width || o.forceHelperSize) { helper.width(this.currentItem.width()); } if(!helper[0].style.height || o.forceHelperSize) { helper.height(this.currentItem.height()); } return helper; }, _adjustOffsetFromHelper: function(obj) { if (typeof obj === "string") { obj = obj.split(" "); } if ($.isArray(obj)) { obj = {left: +obj[0], top: +obj[1] || 0}; } if ("left" in obj) { this.offset.click.left = obj.left + this.margins.left; } if ("right" in obj) { this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; } if ("top" in obj) { this.offset.click.top = obj.top + this.margins.top; } if ("bottom" in obj) { this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; } }, _getParentOffset: function() { //Get the offsetParent and cache its position this.offsetParent = this.helper.offsetParent(); var po = this.offsetParent.offset(); // This is a special case where we need to modify a offset calculated on start, since the following happened: // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { po.left += this.scrollParent.scrollLeft(); po.top += this.scrollParent.scrollTop(); } // This needs to be actually done for all browsers, since pageX/pageY includes this information // with an ugly IE fix if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { po = { top: 0, left: 0 }; } return { top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) }; }, _getRelativeOffset: function() { if(this.cssPosition === "relative") { var p = this.currentItem.position(); return { top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() }; } else { return { top: 0, left: 0 }; } }, _cacheMargins: function() { this.margins = { left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), top: (parseInt(this.currentItem.css("marginTop"),10) || 0) }; }, _cacheHelperProportions: function() { this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() }; }, _setContainment: function() { var ce, co, over, o = this.options; if(o.containment === "parent") { o.containment = this.helper[0].parentNode; } if(o.containment === "document" || o.containment === "window") { this.containment = [ 0 - this.offset.relative.left - this.offset.parent.left, 0 - this.offset.relative.top - this.offset.parent.top, $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top ]; } if(!(/^(document|window|parent)$/).test(o.containment)) { ce = $(o.containment)[0]; co = $(o.containment).offset(); over = ($(ce).css("overflow") !== "hidden"); this.containment = [ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top ]; } }, _convertPositionTo: function(d, pos) { if(!pos) { pos = this.position; } var mod = d === "absolute" ? 1 : -1, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); return { top: ( pos.top + // The absolute mouse position this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) ), left: ( pos.left + // The absolute mouse position this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) ) }; }, _generatePosition: function(event) { var top, left, o = this.options, pageX = event.pageX, pageY = event.pageY, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); // This is another very weird special case that only happens for relative elements: // 1. If the css position is relative // 2. and the scroll parent is the document or similar to the offset parent // we have to refresh the relative offset during the scroll so there are no jumps if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { this.offset.relative = this._getRelativeOffset(); } /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ if(this.originalPosition) { //If we are not dragging yet, we won't check for options if(this.containment) { if(event.pageX - this.offset.click.left < this.containment[0]) { pageX = this.containment[0] + this.offset.click.left; } if(event.pageY - this.offset.click.top < this.containment[1]) { pageY = this.containment[1] + this.offset.click.top; } if(event.pageX - this.offset.click.left > this.containment[2]) { pageX = this.containment[2] + this.offset.click.left; } if(event.pageY - this.offset.click.top > this.containment[3]) { pageY = this.containment[3] + this.offset.click.top; } } if(o.grid) { top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; } } return { top: ( pageY - // The absolute mouse position this.offset.click.top - // Click offset (relative to the element) this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) ), left: ( pageX - // The absolute mouse position this.offset.click.left - // Click offset (relative to the element) this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) ) }; }, _rearrange: function(event, i, a, hardRefresh) { a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); //Various things done here to improve the performance: // 1. we create a setTimeout, that calls refreshPositions // 2. on the instance, we have a counter variable, that get's higher after every append // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same // 4. this lets only the last addition to the timeout stack through this.counter = this.counter ? ++this.counter : 1; var counter = this.counter; this._delay(function() { if(counter === this.counter) { this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove } }); }, _clear: function(event, noPropagation) { this.reverting = false; // We delay all events that have to be triggered to after the point where the placeholder has been removed and // everything else normalized again var i, delayedTriggers = []; // We first have to update the dom position of the actual currentItem // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) if(!this._noFinalSort && this.currentItem.parent().length) { this.placeholder.before(this.currentItem); } this._noFinalSort = null; if(this.helper[0] === this.currentItem[0]) { for(i in this._storedCSS) { if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { this._storedCSS[i] = ""; } } this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); } else { this.currentItem.show(); } if(this.fromOutside && !noPropagation) { delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); } if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed } // Check if the items Container has Changed and trigger appropriate // events. if (this !== this.currentContainer) { if(!noPropagation) { delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); } } //Post events to containers for (i = this.containers.length - 1; i >= 0; i--){ if(!noPropagation) { delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); } if(this.containers[i].containerCache.over) { delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); this.containers[i].containerCache.over = 0; } } //Do what was originally in plugins if ( this.storedCursor ) { this.document.find( "body" ).css( "cursor", this.storedCursor ); this.storedStylesheet.remove(); } if(this._storedOpacity) { this.helper.css("opacity", this._storedOpacity); } if(this._storedZIndex) { this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); } this.dragging = false; if(this.cancelHelperRemoval) { if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); for (i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); } //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } this.fromOutside = false; return false; } if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); } //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! this.placeholder[0].parentNode.removeChild(this.placeholder[0]); if(this.helper[0] !== this.currentItem[0]) { this.helper.remove(); } this.helper = null; if(!noPropagation) { for (i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); } //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } this.fromOutside = false; return true; }, _trigger: function() { if ($.Widget.prototype._trigger.apply(this, arguments) === false) { this.cancel(); } }, _uiHash: function(_inst) { var inst = _inst || this; return { helper: inst.helper, placeholder: inst.placeholder || $([]), position: inst.position, originalPosition: inst.originalPosition, offset: inst.positionAbs, item: inst.currentItem, sender: _inst ? _inst.element : null }; } }); })(jQuery); (function($, undefined) { var dataSpace = "ui-effects-"; $.effects = { effect: {} }; /*! * jQuery Color Animations v2.1.2 * https://github.com/jquery/jquery-color * * Copyright 2013 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * Date: Wed Jan 16 08:47:09 2013 -0600 */ (function( jQuery, undefined ) { var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", // plusequals test for += 100 -= 100 rplusequals = /^([\-+])=\s*(\d+\.?\d*)/, // a set of RE's that can match strings and generate color tuples. stringParsers = [{ re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ], execResult[ 3 ], execResult[ 4 ] ]; } }, { re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, parse: function( execResult ) { return [ execResult[ 1 ] * 2.55, execResult[ 2 ] * 2.55, execResult[ 3 ] * 2.55, execResult[ 4 ] ]; } }, { // this regex ignores A-F because it's compared against an already lowercased string re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ], 16 ), parseInt( execResult[ 2 ], 16 ), parseInt( execResult[ 3 ], 16 ) ]; } }, { // this regex ignores A-F because it's compared against an already lowercased string re: /#([a-f0-9])([a-f0-9])([a-f0-9])/, parse: function( execResult ) { return [ parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ), parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ), parseInt( execResult[ 3 ] + execResult[ 3 ], 16 ) ]; } }, { re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/, space: "hsla", parse: function( execResult ) { return [ execResult[ 1 ], execResult[ 2 ] / 100, execResult[ 3 ] / 100, execResult[ 4 ] ]; } }], // jQuery.Color( ) color = jQuery.Color = function( color, green, blue, alpha ) { return new jQuery.Color.fn.parse( color, green, blue, alpha ); }, spaces = { rgba: { props: { red: { idx: 0, type: "byte" }, green: { idx: 1, type: "byte" }, blue: { idx: 2, type: "byte" } } }, hsla: { props: { hue: { idx: 0, type: "degrees" }, saturation: { idx: 1, type: "percent" }, lightness: { idx: 2, type: "percent" } } } }, propTypes = { "byte": { floor: true, max: 255 }, "percent": { max: 1 }, "degrees": { mod: 360, floor: true } }, support = color.support = {}, // element for support tests supportElem = jQuery( "

" )[ 0 ], // colors = jQuery.Color.names colors, // local aliases of functions called often each = jQuery.each; // determine rgba support immediately supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; // define cache name and alpha properties // for rgba and hsla spaces each( spaces, function( spaceName, space ) { space.cache = "_" + spaceName; space.props.alpha = { idx: 3, type: "percent", def: 1 }; }); function clamp( value, prop, allowEmpty ) { var type = propTypes[ prop.type ] || {}; if ( value == null ) { return (allowEmpty || !prop.def) ? null : prop.def; } // ~~ is an short way of doing floor for positive numbers value = type.floor ? ~~value : parseFloat( value ); // IE will pass in empty strings as value for alpha, // which will hit this case if ( isNaN( value ) ) { return prop.def; } if ( type.mod ) { // we add mod before modding to make sure that negatives values // get converted properly: -10 -> 350 return (value + type.mod) % type.mod; } // for now all property types without mod have min and max return 0 > value ? 0 : type.max < value ? type.max : value; } function stringParse( string ) { var inst = color(), rgba = inst._rgba = []; string = string.toLowerCase(); each( stringParsers, function( i, parser ) { var parsed, match = parser.re.exec( string ), values = match && parser.parse( match ), spaceName = parser.space || "rgba"; if ( values ) { parsed = inst[ spaceName ]( values ); // if this was an rgba parse the assignment might happen twice // oh well.... inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ]; rgba = inst._rgba = parsed._rgba; // exit each( stringParsers ) here because we matched return false; } }); // Found a stringParser that handled it if ( rgba.length ) { // if this came from a parsed string, force "transparent" when alpha is 0 // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0) if ( rgba.join() === "0,0,0,0" ) { jQuery.extend( rgba, colors.transparent ); } return inst; } // named colors return colors[ string ]; } color.fn = jQuery.extend( color.prototype, { parse: function( red, green, blue, alpha ) { if ( red === undefined ) { this._rgba = [ null, null, null, null ]; return this; } if ( red.jquery || red.nodeType ) { red = jQuery( red ).css( green ); green = undefined; } var inst = this, type = jQuery.type( red ), rgba = this._rgba = []; // more than 1 argument specified - assume ( red, green, blue, alpha ) if ( green !== undefined ) { red = [ red, green, blue, alpha ]; type = "array"; } if ( type === "string" ) { return this.parse( stringParse( red ) || colors._default ); } if ( type === "array" ) { each( spaces.rgba.props, function( key, prop ) { rgba[ prop.idx ] = clamp( red[ prop.idx ], prop ); }); return this; } if ( type === "object" ) { if ( red instanceof color ) { each( spaces, function( spaceName, space ) { if ( red[ space.cache ] ) { inst[ space.cache ] = red[ space.cache ].slice(); } }); } else { each( spaces, function( spaceName, space ) { var cache = space.cache; each( space.props, function( key, prop ) { // if the cache doesn't exist, and we know how to convert if ( !inst[ cache ] && space.to ) { // if the value was null, we don't need to copy it // if the key was alpha, we don't need to copy it either if ( key === "alpha" || red[ key ] == null ) { return; } inst[ cache ] = space.to( inst._rgba ); } // this is the only case where we allow nulls for ALL properties. // call clamp with alwaysAllowEmpty inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true ); }); // everything defined but alpha? if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { // use the default of 1 inst[ cache ][ 3 ] = 1; if ( space.from ) { inst._rgba = space.from( inst[ cache ] ); } } }); } return this; } }, is: function( compare ) { var is = color( compare ), same = true, inst = this; each( spaces, function( _, space ) { var localCache, isCache = is[ space.cache ]; if (isCache) { localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || []; each( space.props, function( _, prop ) { if ( isCache[ prop.idx ] != null ) { same = ( isCache[ prop.idx ] === localCache[ prop.idx ] ); return same; } }); } return same; }); return same; }, _space: function() { var used = [], inst = this; each( spaces, function( spaceName, space ) { if ( inst[ space.cache ] ) { used.push( spaceName ); } }); return used.pop(); }, transition: function( other, distance ) { var end = color( other ), spaceName = end._space(), space = spaces[ spaceName ], startColor = this.alpha() === 0 ? color( "transparent" ) : this, start = startColor[ space.cache ] || space.to( startColor._rgba ), result = start.slice(); end = end[ space.cache ]; each( space.props, function( key, prop ) { var index = prop.idx, startValue = start[ index ], endValue = end[ index ], type = propTypes[ prop.type ] || {}; // if null, don't override start value if ( endValue === null ) { return; } // if null - use end if ( startValue === null ) { result[ index ] = endValue; } else { if ( type.mod ) { if ( endValue - startValue > type.mod / 2 ) { startValue += type.mod; } else if ( startValue - endValue > type.mod / 2 ) { startValue -= type.mod; } } result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop ); } }); return this[ spaceName ]( result ); }, blend: function( opaque ) { // if we are already opaque - return ourself if ( this._rgba[ 3 ] === 1 ) { return this; } var rgb = this._rgba.slice(), a = rgb.pop(), blend = color( opaque )._rgba; return color( jQuery.map( rgb, function( v, i ) { return ( 1 - a ) * blend[ i ] + a * v; })); }, toRgbaString: function() { var prefix = "rgba(", rgba = jQuery.map( this._rgba, function( v, i ) { return v == null ? ( i > 2 ? 1 : 0 ) : v; }); if ( rgba[ 3 ] === 1 ) { rgba.pop(); prefix = "rgb("; } return prefix + rgba.join() + ")"; }, toHslaString: function() { var prefix = "hsla(", hsla = jQuery.map( this.hsla(), function( v, i ) { if ( v == null ) { v = i > 2 ? 1 : 0; } // catch 1 and 2 if ( i && i < 3 ) { v = Math.round( v * 100 ) + "%"; } return v; }); if ( hsla[ 3 ] === 1 ) { hsla.pop(); prefix = "hsl("; } return prefix + hsla.join() + ")"; }, toHexString: function( includeAlpha ) { var rgba = this._rgba.slice(), alpha = rgba.pop(); if ( includeAlpha ) { rgba.push( ~~( alpha * 255 ) ); } return "#" + jQuery.map( rgba, function( v ) { // default to 0 when nulls exist v = ( v || 0 ).toString( 16 ); return v.length === 1 ? "0" + v : v; }).join(""); }, toString: function() { return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); } }); color.fn.parse.prototype = color.fn; // hsla conversions adapted from: // https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021 function hue2rgb( p, q, h ) { h = ( h + 1 ) % 1; if ( h * 6 < 1 ) { return p + (q - p) * h * 6; } if ( h * 2 < 1) { return q; } if ( h * 3 < 2 ) { return p + (q - p) * ((2/3) - h) * 6; } return p; } spaces.hsla.to = function ( rgba ) { if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) { return [ null, null, null, rgba[ 3 ] ]; } var r = rgba[ 0 ] / 255, g = rgba[ 1 ] / 255, b = rgba[ 2 ] / 255, a = rgba[ 3 ], max = Math.max( r, g, b ), min = Math.min( r, g, b ), diff = max - min, add = max + min, l = add * 0.5, h, s; if ( min === max ) { h = 0; } else if ( r === max ) { h = ( 60 * ( g - b ) / diff ) + 360; } else if ( g === max ) { h = ( 60 * ( b - r ) / diff ) + 120; } else { h = ( 60 * ( r - g ) / diff ) + 240; } // chroma (diff) == 0 means greyscale which, by definition, saturation = 0% // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add) if ( diff === 0 ) { s = 0; } else if ( l <= 0.5 ) { s = diff / add; } else { s = diff / ( 2 - add ); } return [ Math.round(h) % 360, s, l, a == null ? 1 : a ]; }; spaces.hsla.from = function ( hsla ) { if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) { return [ null, null, null, hsla[ 3 ] ]; } var h = hsla[ 0 ] / 360, s = hsla[ 1 ], l = hsla[ 2 ], a = hsla[ 3 ], q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s, p = 2 * l - q; return [ Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ), Math.round( hue2rgb( p, q, h ) * 255 ), Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ), a ]; }; each( spaces, function( spaceName, space ) { var props = space.props, cache = space.cache, to = space.to, from = space.from; // makes rgba() and hsla() color.fn[ spaceName ] = function( value ) { // generate a cache for this space if it doesn't exist if ( to && !this[ cache ] ) { this[ cache ] = to( this._rgba ); } if ( value === undefined ) { return this[ cache ].slice(); } var ret, type = jQuery.type( value ), arr = ( type === "array" || type === "object" ) ? value : arguments, local = this[ cache ].slice(); each( props, function( key, prop ) { var val = arr[ type === "object" ? key : prop.idx ]; if ( val == null ) { val = local[ prop.idx ]; } local[ prop.idx ] = clamp( val, prop ); }); if ( from ) { ret = color( from( local ) ); ret[ cache ] = local; return ret; } else { return color( local ); } }; // makes red() green() blue() alpha() hue() saturation() lightness() each( props, function( key, prop ) { // alpha is included in more than one space if ( color.fn[ key ] ) { return; } color.fn[ key ] = function( value ) { var vtype = jQuery.type( value ), fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ), local = this[ fn ](), cur = local[ prop.idx ], match; if ( vtype === "undefined" ) { return cur; } if ( vtype === "function" ) { value = value.call( this, cur ); vtype = jQuery.type( value ); } if ( value == null && prop.empty ) { return this; } if ( vtype === "string" ) { match = rplusequals.exec( value ); if ( match ) { value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 ); } } local[ prop.idx ] = value; return this[ fn ]( local ); }; }); }); // add cssHook and .fx.step function for each named hook. // accept a space separated string of properties color.hook = function( hook ) { var hooks = hook.split( " " ); each( hooks, function( i, hook ) { jQuery.cssHooks[ hook ] = { set: function( elem, value ) { var parsed, curElem, backgroundColor = ""; if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { value = color( parsed || value ); if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { curElem = hook === "backgroundColor" ? elem.parentNode : elem; while ( (backgroundColor === "" || backgroundColor === "transparent") && curElem && curElem.style ) { try { backgroundColor = jQuery.css( curElem, "backgroundColor" ); curElem = curElem.parentNode; } catch ( e ) { } } value = value.blend( backgroundColor && backgroundColor !== "transparent" ? backgroundColor : "_default" ); } value = value.toRgbaString(); } try { elem.style[ hook ] = value; } catch( e ) { // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' } } }; jQuery.fx.step[ hook ] = function( fx ) { if ( !fx.colorInit ) { fx.start = color( fx.elem, hook ); fx.end = color( fx.end ); fx.colorInit = true; } jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) ); }; }); }; color.hook( stepHooks ); jQuery.cssHooks.borderColor = { expand: function( value ) { var expanded = {}; each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) { expanded[ "border" + part + "Color" ] = value; }); return expanded; } }; // Basic color names only. // Usage of any of the other color names requires adding yourself or including // jquery.color.svg-names.js. colors = jQuery.Color.names = { // 4.1. Basic color keywords aqua: "#00ffff", black: "#000000", blue: "#0000ff", fuchsia: "#ff00ff", gray: "#808080", green: "#008000", lime: "#00ff00", maroon: "#800000", navy: "#000080", olive: "#808000", purple: "#800080", red: "#ff0000", silver: "#c0c0c0", teal: "#008080", white: "#ffffff", yellow: "#ffff00", // 4.2.3. "transparent" color keyword transparent: [ null, null, null, 0 ], _default: "#ffffff" }; })( jQuery ); /******************************************************************************/ /****************************** CLASS ANIMATIONS ******************************/ /******************************************************************************/ (function() { var classAnimationActions = [ "add", "remove", "toggle" ], shorthandStyles = { border: 1, borderBottom: 1, borderColor: 1, borderLeft: 1, borderRight: 1, borderTop: 1, borderWidth: 1, margin: 1, padding: 1 }; $.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) { $.fx.step[ prop ] = function( fx ) { if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) { jQuery.style( fx.elem, prop, fx.end ); fx.setAttr = true; } }; }); function getElementStyles( elem ) { var key, len, style = elem.ownerDocument.defaultView ? elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : elem.currentStyle, styles = {}; if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { len = style.length; while ( len-- ) { key = style[ len ]; if ( typeof style[ key ] === "string" ) { styles[ $.camelCase( key ) ] = style[ key ]; } } // support: Opera, IE <9 } else { for ( key in style ) { if ( typeof style[ key ] === "string" ) { styles[ key ] = style[ key ]; } } } return styles; } function styleDifference( oldStyle, newStyle ) { var diff = {}, name, value; for ( name in newStyle ) { value = newStyle[ name ]; if ( oldStyle[ name ] !== value ) { if ( !shorthandStyles[ name ] ) { if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) { diff[ name ] = value; } } } } return diff; } // support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } $.effects.animateClass = function( value, duration, easing, callback ) { var o = $.speed( duration, easing, callback ); return this.queue( function() { var animated = $( this ), baseClass = animated.attr( "class" ) || "", applyClassChange, allAnimations = o.children ? animated.find( "*" ).addBack() : animated; // map the animated objects to store the original styles. allAnimations = allAnimations.map(function() { var el = $( this ); return { el: el, start: getElementStyles( this ) }; }); // apply class change applyClassChange = function() { $.each( classAnimationActions, function(i, action) { if ( value[ action ] ) { animated[ action + "Class" ]( value[ action ] ); } }); }; applyClassChange(); // map all animated objects again - calculate new styles and diff allAnimations = allAnimations.map(function() { this.end = getElementStyles( this.el[ 0 ] ); this.diff = styleDifference( this.start, this.end ); return this; }); // apply original class animated.attr( "class", baseClass ); // map all animated objects again - this time collecting a promise allAnimations = allAnimations.map(function() { var styleInfo = this, dfd = $.Deferred(), opts = $.extend({}, o, { queue: false, complete: function() { dfd.resolve( styleInfo ); } }); this.el.animate( this.diff, opts ); return dfd.promise(); }); // once all animations have completed: $.when.apply( $, allAnimations.get() ).done(function() { // set the final class applyClassChange(); // for each animated element, // clear all css properties that were animated $.each( arguments, function() { var el = this.el; $.each( this.diff, function(key) { el.css( key, "" ); }); }); // this is guarnteed to be there if you use jQuery.speed() // it also handles dequeuing the next anim... o.complete.call( animated[ 0 ] ); }); }); }; $.fn.extend({ addClass: (function( orig ) { return function( classNames, speed, easing, callback ) { return speed ? $.effects.animateClass.call( this, { add: classNames }, speed, easing, callback ) : orig.apply( this, arguments ); }; })( $.fn.addClass ), removeClass: (function( orig ) { return function( classNames, speed, easing, callback ) { return arguments.length > 1 ? $.effects.animateClass.call( this, { remove: classNames }, speed, easing, callback ) : orig.apply( this, arguments ); }; })( $.fn.removeClass ), toggleClass: (function( orig ) { return function( classNames, force, speed, easing, callback ) { if ( typeof force === "boolean" || force === undefined ) { if ( !speed ) { // without speed parameter return orig.apply( this, arguments ); } else { return $.effects.animateClass.call( this, (force ? { add: classNames } : { remove: classNames }), speed, easing, callback ); } } else { // without force parameter return $.effects.animateClass.call( this, { toggle: classNames }, force, speed, easing ); } }; })( $.fn.toggleClass ), switchClass: function( remove, add, speed, easing, callback) { return $.effects.animateClass.call( this, { add: add, remove: remove }, speed, easing, callback ); } }); })(); /******************************************************************************/ /*********************************** EFFECTS **********************************/ /******************************************************************************/ (function() { $.extend( $.effects, { version: "1.10.3", // Saves a set of properties in a data storage save: function( element, set ) { for( var i=0; i < set.length; i++ ) { if ( set[ i ] !== null ) { element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); } } }, // Restores a set of previously saved properties from a data storage restore: function( element, set ) { var val, i; for( i=0; i < set.length; i++ ) { if ( set[ i ] !== null ) { val = element.data( dataSpace + set[ i ] ); // support: jQuery 1.6.2 // http://bugs.jquery.com/ticket/9917 // jQuery 1.6.2 incorrectly returns undefined for any falsy value. // We can't differentiate between "" and 0 here, so we just assume // empty string since it's likely to be a more common value... if ( val === undefined ) { val = ""; } element.css( set[ i ], val ); } } }, setMode: function( el, mode ) { if (mode === "toggle") { mode = el.is( ":hidden" ) ? "show" : "hide"; } return mode; }, // Translates a [top,left] array into a baseline value // this should be a little more flexible in the future to handle a string & hash getBaseline: function( origin, original ) { var y, x; switch ( origin[ 0 ] ) { case "top": y = 0; break; case "middle": y = 0.5; break; case "bottom": y = 1; break; default: y = origin[ 0 ] / original.height; } switch ( origin[ 1 ] ) { case "left": x = 0; break; case "center": x = 0.5; break; case "right": x = 1; break; default: x = origin[ 1 ] / original.width; } return { x: x, y: y }; }, // Wraps the element around a wrapper that copies position properties createWrapper: function( element ) { // if the element is already wrapped, return it if ( element.parent().is( ".ui-effects-wrapper" )) { return element.parent(); } // wrap the element var props = { width: element.outerWidth(true), height: element.outerHeight(true), "float": element.css( "float" ) }, wrapper = $( "

" ) .addClass( "ui-effects-wrapper" ) .css({ fontSize: "100%", background: "transparent", border: "none", margin: 0, padding: 0 }), // Store the size in case width/height are defined in % - Fixes #5245 size = { width: element.width(), height: element.height() }, active = document.activeElement; // support: Firefox // Firefox incorrectly exposes anonymous content // https://bugzilla.mozilla.org/show_bug.cgi?id=561664 try { active.id; } catch( e ) { active = document.body; } element.wrap( wrapper ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { $( active ).focus(); } wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element // transfer positioning properties to the wrapper if ( element.css( "position" ) === "static" ) { wrapper.css({ position: "relative" }); element.css({ position: "relative" }); } else { $.extend( props, { position: element.css( "position" ), zIndex: element.css( "z-index" ) }); $.each([ "top", "left", "bottom", "right" ], function(i, pos) { props[ pos ] = element.css( pos ); if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { props[ pos ] = "auto"; } }); element.css({ position: "relative", top: 0, left: 0, right: "auto", bottom: "auto" }); } element.css(size); return wrapper.css( props ).show(); }, removeWrapper: function( element ) { var active = document.activeElement; if ( element.parent().is( ".ui-effects-wrapper" ) ) { element.parent().replaceWith( element ); // Fixes #7595 - Elements lose focus when wrapped. if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { $( active ).focus(); } } return element; }, setTransition: function( element, list, factor, value ) { value = value || {}; $.each( list, function( i, x ) { var unit = element.cssUnit( x ); if ( unit[ 0 ] > 0 ) { value[ x ] = unit[ 0 ] * factor + unit[ 1 ]; } }); return value; } }); // return an effect options object for the given parameters: function _normalizeArguments( effect, options, speed, callback ) { // allow passing all options as the first parameter if ( $.isPlainObject( effect ) ) { options = effect; effect = effect.effect; } // convert to an object effect = { effect: effect }; // catch (effect, null, ...) if ( options == null ) { options = {}; } // catch (effect, callback) if ( $.isFunction( options ) ) { callback = options; speed = null; options = {}; } // catch (effect, speed, ?) if ( typeof options === "number" || $.fx.speeds[ options ] ) { callback = speed; speed = options; options = {}; } // catch (effect, options, callback) if ( $.isFunction( speed ) ) { callback = speed; speed = null; } // add options to effect if ( options ) { $.extend( effect, options ); } speed = speed || options.duration; effect.duration = $.fx.off ? 0 : typeof speed === "number" ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default; effect.complete = callback || options.complete; return effect; } function standardAnimationOption( option ) { // Valid standard speeds (nothing, number, named speed) if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) { return true; } // Invalid strings - treat as "normal" speed if ( typeof option === "string" && !$.effects.effect[ option ] ) { return true; } // Complete callback if ( $.isFunction( option ) ) { return true; } // Options hash (but not naming an effect) if ( typeof option === "object" && !option.effect ) { return true; } // Didn't match any standard API return false; } $.fn.extend({ effect: function( /* effect, options, speed, callback */ ) { var args = _normalizeArguments.apply( this, arguments ), mode = args.mode, queue = args.queue, effectMethod = $.effects.effect[ args.effect ]; if ( $.fx.off || !effectMethod ) { // delegate to the original method (e.g., .show()) if possible if ( mode ) { return this[ mode ]( args.duration, args.complete ); } else { return this.each( function() { if ( args.complete ) { args.complete.call( this ); } }); } } function run( next ) { var elem = $( this ), complete = args.complete, mode = args.mode; function done() { if ( $.isFunction( complete ) ) { complete.call( elem[0] ); } if ( $.isFunction( next ) ) { next(); } } // If the element already has the correct final state, delegate to // the core methods so the internal tracking of "olddisplay" works. if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { elem[ mode ](); done(); } else { effectMethod.call( elem[0], args, done ); } } return queue === false ? this.each( run ) : this.queue( queue || "fx", run ); }, show: (function( orig ) { return function( option ) { if ( standardAnimationOption( option ) ) { return orig.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "show"; return this.effect.call( this, args ); } }; })( $.fn.show ), hide: (function( orig ) { return function( option ) { if ( standardAnimationOption( option ) ) { return orig.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "hide"; return this.effect.call( this, args ); } }; })( $.fn.hide ), toggle: (function( orig ) { return function( option ) { if ( standardAnimationOption( option ) || typeof option === "boolean" ) { return orig.apply( this, arguments ); } else { var args = _normalizeArguments.apply( this, arguments ); args.mode = "toggle"; return this.effect.call( this, args ); } }; })( $.fn.toggle ), // helper functions cssUnit: function(key) { var style = this.css( key ), val = []; $.each( [ "em", "px", "%", "pt" ], function( i, unit ) { if ( style.indexOf( unit ) > 0 ) { val = [ parseFloat( style ), unit ]; } }); return val; } }); })(); /******************************************************************************/ /*********************************** EASING ***********************************/ /******************************************************************************/ (function() { // based on easing equations from Robert Penner (http://www.robertpenner.com/easing) var baseEasings = {}; $.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) { baseEasings[ name ] = function( p ) { return Math.pow( p, i + 2 ); }; }); $.extend( baseEasings, { Sine: function ( p ) { return 1 - Math.cos( p * Math.PI / 2 ); }, Circ: function ( p ) { return 1 - Math.sqrt( 1 - p * p ); }, Elastic: function( p ) { return p === 0 || p === 1 ? p : -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 ); }, Back: function( p ) { return p * p * ( 3 * p - 2 ); }, Bounce: function ( p ) { var pow2, bounce = 4; while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); } }); $.each( baseEasings, function( name, easeIn ) { $.easing[ "easeIn" + name ] = easeIn; $.easing[ "easeOut" + name ] = function( p ) { return 1 - easeIn( 1 - p ); }; $.easing[ "easeInOut" + name ] = function( p ) { return p < 0.5 ? easeIn( p * 2 ) / 2 : 1 - easeIn( p * -2 + 2 ) / 2; }; }); })(); })(jQuery); (function( $, undefined ) { var uid = 0, hideProps = {}, showProps = {}; hideProps.height = hideProps.paddingTop = hideProps.paddingBottom = hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide"; showProps.height = showProps.paddingTop = showProps.paddingBottom = showProps.borderTopWidth = showProps.borderBottomWidth = "show"; $.widget( "ui.accordion", { version: "1.10.3", options: { active: 0, animate: {}, collapsible: false, event: "click", header: "> li > :first-child,> :not(li):even", heightStyle: "auto", icons: { activeHeader: "ui-icon-triangle-1-s", header: "ui-icon-triangle-1-e" }, // callbacks activate: null, beforeActivate: null }, _create: function() { var options = this.options; this.prevShow = this.prevHide = $(); this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) // ARIA .attr( "role", "tablist" ); // don't allow collapsible: false and active: false / null if ( !options.collapsible && (options.active === false || options.active == null) ) { options.active = 0; } this._processPanels(); // handle negative values if ( options.active < 0 ) { options.active += this.headers.length; } this._refresh(); }, _getCreateEventData: function() { return { header: this.active, panel: !this.active.length ? $() : this.active.next(), content: !this.active.length ? $() : this.active.next() }; }, _createIcons: function() { var icons = this.options.icons; if ( icons ) { $( "" ) .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) .prependTo( this.headers ); this.active.children( ".ui-accordion-header-icon" ) .removeClass( icons.header ) .addClass( icons.activeHeader ); this.headers.addClass( "ui-accordion-icons" ); } }, _destroyIcons: function() { this.headers .removeClass( "ui-accordion-icons" ) .children( ".ui-accordion-header-icon" ) .remove(); }, _destroy: function() { var contents; // clean up main element this.element .removeClass( "ui-accordion ui-widget ui-helper-reset" ) .removeAttr( "role" ); // clean up headers this.headers .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) .removeAttr( "role" ) .removeAttr( "aria-selected" ) .removeAttr( "aria-controls" ) .removeAttr( "tabIndex" ) .each(function() { if ( /^ui-accordion/.test( this.id ) ) { this.removeAttribute( "id" ); } }); this._destroyIcons(); // clean up content panels contents = this.headers.next() .css( "display", "" ) .removeAttr( "role" ) .removeAttr( "aria-expanded" ) .removeAttr( "aria-hidden" ) .removeAttr( "aria-labelledby" ) .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" ) .each(function() { if ( /^ui-accordion/.test( this.id ) ) { this.removeAttribute( "id" ); } }); if ( this.options.heightStyle !== "content" ) { contents.css( "height", "" ); } }, _setOption: function( key, value ) { if ( key === "active" ) { // _activate() will handle invalid values and update this.options this._activate( value ); return; } if ( key === "event" ) { if ( this.options.event ) { this._off( this.headers, this.options.event ); } this._setupEvents( value ); } this._super( key, value ); // setting collapsible: false while collapsed; open first panel if ( key === "collapsible" && !value && this.options.active === false ) { this._activate( 0 ); } if ( key === "icons" ) { this._destroyIcons(); if ( value ) { this._createIcons(); } } // #5332 - opacity doesn't cascade to positioned elements in IE // so we need to add the disabled class to the headers and panels if ( key === "disabled" ) { this.headers.add( this.headers.next() ) .toggleClass( "ui-state-disabled", !!value ); } }, _keydown: function( event ) { /*jshint maxcomplexity:15*/ if ( event.altKey || event.ctrlKey ) { return; } var keyCode = $.ui.keyCode, length = this.headers.length, currentIndex = this.headers.index( event.target ), toFocus = false; switch ( event.keyCode ) { case keyCode.RIGHT: case keyCode.DOWN: toFocus = this.headers[ ( currentIndex + 1 ) % length ]; break; case keyCode.LEFT: case keyCode.UP: toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; break; case keyCode.SPACE: case keyCode.ENTER: this._eventHandler( event ); break; case keyCode.HOME: toFocus = this.headers[ 0 ]; break; case keyCode.END: toFocus = this.headers[ length - 1 ]; break; } if ( toFocus ) { $( event.target ).attr( "tabIndex", -1 ); $( toFocus ).attr( "tabIndex", 0 ); toFocus.focus(); event.preventDefault(); } }, _panelKeyDown : function( event ) { if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { $( event.currentTarget ).prev().focus(); } }, refresh: function() { var options = this.options; this._processPanels(); // was collapsed or no panel if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { options.active = false; this.active = $(); // active false only when collapsible is true } else if ( options.active === false ) { this._activate( 0 ); // was active, but active panel is gone } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { // all remaining panel are disabled if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { options.active = false; this.active = $(); // activate previous panel } else { this._activate( Math.max( 0, options.active - 1 ) ); } // was active, active panel still exists } else { // make sure active index is correct options.active = this.headers.index( this.active ); } this._destroyIcons(); this._refresh(); }, _processPanels: function() { this.headers = this.element.find( this.options.header ) .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ); this.headers.next() .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) .filter(":not(.ui-accordion-content-active)") .hide(); }, _refresh: function() { var maxHeight, options = this.options, heightStyle = options.heightStyle, parent = this.element.parent(), accordionId = this.accordionId = "ui-accordion-" + (this.element.attr( "id" ) || ++uid); this.active = this._findActive( options.active ) .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) .removeClass( "ui-corner-all" ); this.active.next() .addClass( "ui-accordion-content-active" ) .show(); this.headers .attr( "role", "tab" ) .each(function( i ) { var header = $( this ), headerId = header.attr( "id" ), panel = header.next(), panelId = panel.attr( "id" ); if ( !headerId ) { headerId = accordionId + "-header-" + i; header.attr( "id", headerId ); } if ( !panelId ) { panelId = accordionId + "-panel-" + i; panel.attr( "id", panelId ); } header.attr( "aria-controls", panelId ); panel.attr( "aria-labelledby", headerId ); }) .next() .attr( "role", "tabpanel" ); this.headers .not( this.active ) .attr({ "aria-selected": "false", tabIndex: -1 }) .next() .attr({ "aria-expanded": "false", "aria-hidden": "true" }) .hide(); // make sure at least one header is in the tab order if ( !this.active.length ) { this.headers.eq( 0 ).attr( "tabIndex", 0 ); } else { this.active.attr({ "aria-selected": "true", tabIndex: 0 }) .next() .attr({ "aria-expanded": "true", "aria-hidden": "false" }); } this._createIcons(); this._setupEvents( options.event ); if ( heightStyle === "fill" ) { maxHeight = parent.height(); this.element.siblings( ":visible" ).each(function() { var elem = $( this ), position = elem.css( "position" ); if ( position === "absolute" || position === "fixed" ) { return; } maxHeight -= elem.outerHeight( true ); }); this.headers.each(function() { maxHeight -= $( this ).outerHeight( true ); }); this.headers.next() .each(function() { $( this ).height( Math.max( 0, maxHeight - $( this ).innerHeight() + $( this ).height() ) ); }) .css( "overflow", "auto" ); } else if ( heightStyle === "auto" ) { maxHeight = 0; this.headers.next() .each(function() { maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); }) .height( maxHeight ); } }, _activate: function( index ) { var active = this._findActive( index )[ 0 ]; // trying to activate the already active panel if ( active === this.active[ 0 ] ) { return; } // trying to collapse, simulate a click on the currently active header active = active || this.active[ 0 ]; this._eventHandler({ target: active, currentTarget: active, preventDefault: $.noop }); }, _findActive: function( selector ) { return typeof selector === "number" ? this.headers.eq( selector ) : $(); }, _setupEvents: function( event ) { var events = { keydown: "_keydown" }; if ( event ) { $.each( event.split(" "), function( index, eventName ) { events[ eventName ] = "_eventHandler"; }); } this._off( this.headers.add( this.headers.next() ) ); this._on( this.headers, events ); this._on( this.headers.next(), { keydown: "_panelKeyDown" }); this._hoverable( this.headers ); this._focusable( this.headers ); }, _eventHandler: function( event ) { var options = this.options, active = this.active, clicked = $( event.currentTarget ), clickedIsActive = clicked[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, toShow = collapsing ? $() : clicked.next(), toHide = active.next(), eventData = { oldHeader: active, oldPanel: toHide, newHeader: collapsing ? $() : clicked, newPanel: toShow }; event.preventDefault(); if ( // click on active header, but not collapsible ( clickedIsActive && !options.collapsible ) || // allow canceling activation ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { return; } options.active = collapsing ? false : this.headers.index( clicked ); // when the call to ._toggle() comes after the class changes // it causes a very odd bug in IE 8 (see #6720) this.active = clickedIsActive ? $() : clicked; this._toggle( eventData ); // switch classes // corner classes on the previously active header stay after the animation active.removeClass( "ui-accordion-header-active ui-state-active" ); if ( options.icons ) { active.children( ".ui-accordion-header-icon" ) .removeClass( options.icons.activeHeader ) .addClass( options.icons.header ); } if ( !clickedIsActive ) { clicked .removeClass( "ui-corner-all" ) .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); if ( options.icons ) { clicked.children( ".ui-accordion-header-icon" ) .removeClass( options.icons.header ) .addClass( options.icons.activeHeader ); } clicked .next() .addClass( "ui-accordion-content-active" ); } }, _toggle: function( data ) { var toShow = data.newPanel, toHide = this.prevShow.length ? this.prevShow : data.oldPanel; // handle activating a panel during the animation for another activation this.prevShow.add( this.prevHide ).stop( true, true ); this.prevShow = toShow; this.prevHide = toHide; if ( this.options.animate ) { this._animate( toShow, toHide, data ); } else { toHide.hide(); toShow.show(); this._toggleComplete( data ); } toHide.attr({ "aria-expanded": "false", "aria-hidden": "true" }); toHide.prev().attr( "aria-selected", "false" ); // if we're switching panels, remove the old header from the tab order // if we're opening from collapsed state, remove the previous header from the tab order // if we're collapsing, then keep the collapsing header in the tab order if ( toShow.length && toHide.length ) { toHide.prev().attr( "tabIndex", -1 ); } else if ( toShow.length ) { this.headers.filter(function() { return $( this ).attr( "tabIndex" ) === 0; }) .attr( "tabIndex", -1 ); } toShow .attr({ "aria-expanded": "true", "aria-hidden": "false" }) .prev() .attr({ "aria-selected": "true", tabIndex: 0 }); }, _animate: function( toShow, toHide, data ) { var total, easing, duration, that = this, adjust = 0, down = toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) ), animate = this.options.animate || {}, options = down && animate.down || animate, complete = function() { that._toggleComplete( data ); }; if ( typeof options === "number" ) { duration = options; } if ( typeof options === "string" ) { easing = options; } // fall back from options to animation in case of partial down settings easing = easing || options.easing || animate.easing; duration = duration || options.duration || animate.duration; if ( !toHide.length ) { return toShow.animate( showProps, duration, easing, complete ); } if ( !toShow.length ) { return toHide.animate( hideProps, duration, easing, complete ); } total = toShow.show().outerHeight(); toHide.animate( hideProps, { duration: duration, easing: easing, step: function( now, fx ) { fx.now = Math.round( now ); } }); toShow .hide() .animate( showProps, { duration: duration, easing: easing, complete: complete, step: function( now, fx ) { fx.now = Math.round( now ); if ( fx.prop !== "height" ) { adjust += fx.now; } else if ( that.options.heightStyle !== "content" ) { fx.now = Math.round( total - toHide.outerHeight() - adjust ); adjust = 0; } } }); }, _toggleComplete: function( data ) { var toHide = data.oldPanel; toHide .removeClass( "ui-accordion-content-active" ) .prev() .removeClass( "ui-corner-top" ) .addClass( "ui-corner-all" ); // Work around for rendering bug in IE (#5421) if ( toHide.length ) { toHide.parent()[0].className = toHide.parent()[0].className; } this._trigger( "activate", null, data ); } }); })( jQuery ); (function( $, undefined ) { // used to prevent race conditions with remote data sources var requestIndex = 0; $.widget( "ui.autocomplete", { version: "1.10.3", defaultElement: "", options: { appendTo: null, autoFocus: false, delay: 300, minLength: 1, position: { my: "left top", at: "left bottom", collision: "none" }, source: null, // callbacks change: null, close: null, focus: null, open: null, response: null, search: null, select: null }, pending: 0, _create: function() { // Some browsers only repeat keydown events, not keypress events, // so we use the suppressKeyPress flag to determine if we've already // handled the keydown event. #7269 // Unfortunately the code for & in keypress is the same as the up arrow, // so we use the suppressKeyPressRepeat flag to avoid handling keypress // events when we know the keydown event was used to modify the // search term. #7799 var suppressKeyPress, suppressKeyPressRepeat, suppressInput, nodeName = this.element[0].nodeName.toLowerCase(), isTextarea = nodeName === "textarea", isInput = nodeName === "input"; this.isMultiLine = // Textareas are always multi-line isTextarea ? true : // Inputs are always single-line, even if inside a contentEditable element // IE also treats inputs as contentEditable isInput ? false : // All other element types are determined by whether or not they're contentEditable this.element.prop( "isContentEditable" ); this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; this.isNewMenu = true; this.element .addClass( "ui-autocomplete-input" ) .attr( "autocomplete", "off" ); this._on( this.element, { keydown: function( event ) { /*jshint maxcomplexity:15*/ if ( this.element.prop( "readOnly" ) ) { suppressKeyPress = true; suppressInput = true; suppressKeyPressRepeat = true; return; } suppressKeyPress = false; suppressInput = false; suppressKeyPressRepeat = false; var keyCode = $.ui.keyCode; switch( event.keyCode ) { case keyCode.PAGE_UP: suppressKeyPress = true; this._move( "previousPage", event ); break; case keyCode.PAGE_DOWN: suppressKeyPress = true; this._move( "nextPage", event ); break; case keyCode.UP: suppressKeyPress = true; this._keyEvent( "previous", event ); break; case keyCode.DOWN: suppressKeyPress = true; this._keyEvent( "next", event ); break; case keyCode.ENTER: case keyCode.NUMPAD_ENTER: // when menu is open and has focus if ( this.menu.active ) { // #6055 - Opera still allows the keypress to occur // which causes forms to submit suppressKeyPress = true; event.preventDefault(); this.menu.select( event ); } break; case keyCode.TAB: if ( this.menu.active ) { this.menu.select( event ); } break; case keyCode.ESCAPE: if ( this.menu.element.is( ":visible" ) ) { this._value( this.term ); this.close( event ); // Different browsers have different default behavior for escape // Single press can mean undo or clear // Double press in IE means clear the whole form event.preventDefault(); } break; default: suppressKeyPressRepeat = true; // search timeout should be triggered before the input value is changed this._searchTimeout( event ); break; } }, keypress: function( event ) { if ( suppressKeyPress ) { suppressKeyPress = false; if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { event.preventDefault(); } return; } if ( suppressKeyPressRepeat ) { return; } // replicate some key handlers to allow them to repeat in Firefox and Opera var keyCode = $.ui.keyCode; switch( event.keyCode ) { case keyCode.PAGE_UP: this._move( "previousPage", event ); break; case keyCode.PAGE_DOWN: this._move( "nextPage", event ); break; case keyCode.UP: this._keyEvent( "previous", event ); break; case keyCode.DOWN: this._keyEvent( "next", event ); break; } }, input: function( event ) { if ( suppressInput ) { suppressInput = false; event.preventDefault(); return; } this._searchTimeout( event ); }, focus: function() { this.selectedItem = null; this.previous = this._value(); }, blur: function( event ) { if ( this.cancelBlur ) { delete this.cancelBlur; return; } clearTimeout( this.searching ); this.close( event ); this._change( event ); } }); this._initSource(); this.menu = $( "
    " ) .addClass( "ui-autocomplete ui-front" ) .appendTo( this._appendTo() ) .menu({ // disable ARIA support, the live region takes care of that role: null }) .hide() .data( "ui-menu" ); this._on( this.menu.element, { mousedown: function( event ) { // prevent moving focus out of the text field event.preventDefault(); // IE doesn't prevent moving focus even with event.preventDefault() // so we set a flag to know when we should ignore the blur event this.cancelBlur = true; this._delay(function() { delete this.cancelBlur; }); // clicking on the scrollbar causes focus to shift to the body // but we can't detect a mouseup or a click immediately afterward // so we have to track the next mousedown and close the menu if // the user clicks somewhere outside of the autocomplete var menuElement = this.menu.element[ 0 ]; if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { this._delay(function() { var that = this; this.document.one( "mousedown", function( event ) { if ( event.target !== that.element[ 0 ] && event.target !== menuElement && !$.contains( menuElement, event.target ) ) { that.close(); } }); }); } }, menufocus: function( event, ui ) { // support: Firefox // Prevent accidental activation of menu items in Firefox (#7024 #9118) if ( this.isNewMenu ) { this.isNewMenu = false; if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { this.menu.blur(); this.document.one( "mousemove", function() { $( event.target ).trigger( event.originalEvent ); }); return; } } var item = ui.item.data( "ui-autocomplete-item" ); if ( false !== this._trigger( "focus", event, { item: item } ) ) { // use value to match what will end up in the input, if it was a key event if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { this._value( item.value ); } } else { // Normally the input is populated with the item's value as the // menu is navigated, causing screen readers to notice a change and // announce the item. Since the focus event was canceled, this doesn't // happen, so we update the live region so that screen readers can // still notice the change and announce it. this.liveRegion.text( item.value ); } }, menuselect: function( event, ui ) { var item = ui.item.data( "ui-autocomplete-item" ), previous = this.previous; // only trigger when focus was lost (click on menu) if ( this.element[0] !== this.document[0].activeElement ) { this.element.focus(); this.previous = previous; // #6109 - IE triggers two focus events and the second // is asynchronous, so we need to reset the previous // term synchronously and asynchronously :-( this._delay(function() { this.previous = previous; this.selectedItem = item; }); } if ( false !== this._trigger( "select", event, { item: item } ) ) { this._value( item.value ); } // reset the term after the select event // this allows custom select handling to work properly this.term = this._value(); this.close( event ); this.selectedItem = item; } }); this.liveRegion = $( "", { role: "status", "aria-live": "polite" }) .addClass( "ui-helper-hidden-accessible" ) .insertBefore( this.element ); // turning off autocomplete prevents the browser from remembering the // value when navigating through history, so we re-enable autocomplete // if the page is unloaded before the widget is destroyed. #7790 this._on( this.window, { beforeunload: function() { this.element.removeAttr( "autocomplete" ); } }); }, _destroy: function() { clearTimeout( this.searching ); this.element .removeClass( "ui-autocomplete-input" ) .removeAttr( "autocomplete" ); this.menu.element.remove(); this.liveRegion.remove(); }, _setOption: function( key, value ) { this._super( key, value ); if ( key === "source" ) { this._initSource(); } if ( key === "appendTo" ) { this.menu.element.appendTo( this._appendTo() ); } if ( key === "disabled" && value && this.xhr ) { this.xhr.abort(); } }, _appendTo: function() { var element = this.options.appendTo; if ( element ) { element = element.jquery || element.nodeType ? $( element ) : this.document.find( element ).eq( 0 ); } if ( !element ) { element = this.element.closest( ".ui-front" ); } if ( !element.length ) { element = this.document[0].body; } return element; }, _initSource: function() { var array, url, that = this; if ( $.isArray(this.options.source) ) { array = this.options.source; this.source = function( request, response ) { response( $.ui.autocomplete.filter( array, request.term ) ); }; } else if ( typeof this.options.source === "string" ) { url = this.options.source; this.source = function( request, response ) { if ( that.xhr ) { that.xhr.abort(); } that.xhr = $.ajax({ url: url, data: request, dataType: "json", success: function( data ) { response( data ); }, error: function() { response( [] ); } }); }; } else { this.source = this.options.source; } }, _searchTimeout: function( event ) { clearTimeout( this.searching ); this.searching = this._delay(function() { // only search if the value has changed if ( this.term !== this._value() ) { this.selectedItem = null; this.search( null, event ); } }, this.options.delay ); }, search: function( value, event ) { value = value != null ? value : this._value(); // always save the actual value, not the one passed as an argument this.term = this._value(); if ( value.length < this.options.minLength ) { return this.close( event ); } if ( this._trigger( "search", event ) === false ) { return; } return this._search( value ); }, _search: function( value ) { this.pending++; this.element.addClass( "ui-autocomplete-loading" ); this.cancelSearch = false; this.source( { term: value }, this._response() ); }, _response: function() { var that = this, index = ++requestIndex; return function( content ) { if ( index === requestIndex ) { that.__response( content ); } that.pending--; if ( !that.pending ) { that.element.removeClass( "ui-autocomplete-loading" ); } }; }, __response: function( content ) { if ( content ) { content = this._normalize( content ); } this._trigger( "response", null, { content: content } ); if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { this._suggest( content ); this._trigger( "open" ); } else { // use ._close() instead of .close() so we don't cancel future searches this._close(); } }, close: function( event ) { this.cancelSearch = true; this._close( event ); }, _close: function( event ) { if ( this.menu.element.is( ":visible" ) ) { this.menu.element.hide(); this.menu.blur(); this.isNewMenu = true; this._trigger( "close", event ); } }, _change: function( event ) { if ( this.previous !== this._value() ) { this._trigger( "change", event, { item: this.selectedItem } ); } }, _normalize: function( items ) { // assume all items have the right format when the first item is complete if ( items.length && items[0].label && items[0].value ) { return items; } return $.map( items, function( item ) { if ( typeof item === "string" ) { return { label: item, value: item }; } return $.extend({ label: item.label || item.value, value: item.value || item.label }, item ); }); }, _suggest: function( items ) { var ul = this.menu.element.empty(); this._renderMenu( ul, items ); this.isNewMenu = true; this.menu.refresh(); // size and position menu ul.show(); this._resizeMenu(); ul.position( $.extend({ of: this.element }, this.options.position )); if ( this.options.autoFocus ) { this.menu.next(); } }, _resizeMenu: function() { var ul = this.menu.element; ul.outerWidth( Math.max( // Firefox wraps long text (possibly a rounding bug) // so we add 1px to avoid the wrapping (#7513) ul.width( "" ).outerWidth() + 1, this.element.outerWidth() ) ); }, _renderMenu: function( ul, items ) { var that = this; $.each( items, function( index, item ) { that._renderItemData( ul, item ); }); }, _renderItemData: function( ul, item ) { return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); }, _renderItem: function( ul, item ) { return $( "
  • " ) .append( $( "" ).text( item.label ) ) .appendTo( ul ); }, _move: function( direction, event ) { if ( !this.menu.element.is( ":visible" ) ) { this.search( null, event ); return; } if ( this.menu.isFirstItem() && /^previous/.test( direction ) || this.menu.isLastItem() && /^next/.test( direction ) ) { this._value( this.term ); this.menu.blur(); return; } this.menu[ direction ]( event ); }, widget: function() { return this.menu.element; }, _value: function() { return this.valueMethod.apply( this.element, arguments ); }, _keyEvent: function( keyEvent, event ) { if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { this._move( keyEvent, event ); // prevents moving cursor to beginning/end of the text field in some browsers event.preventDefault(); } } }); $.extend( $.ui.autocomplete, { escapeRegex: function( value ) { return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); }, filter: function(array, term) { var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); return $.grep( array, function(value) { return matcher.test( value.label || value.value || value ); }); } }); // live region extension, adding a `messages` option // NOTE: This is an experimental API. We are still investigating // a full solution for string manipulation and internationalization. $.widget( "ui.autocomplete", $.ui.autocomplete, { options: { messages: { noResults: "No search results.", results: function( amount ) { return amount + ( amount > 1 ? " results are" : " result is" ) + " available, use up and down arrow keys to navigate."; } } }, __response: function( content ) { var message; this._superApply( arguments ); if ( this.options.disabled || this.cancelSearch ) { return; } if ( content && content.length ) { message = this.options.messages.results( content.length ); } else { message = this.options.messages.noResults; } this.liveRegion.text( message ); } }); }( jQuery )); (function( $, undefined ) { var lastActive, startXPos, startYPos, clickDragged, baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", stateClasses = "ui-state-hover ui-state-active ", typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", formResetHandler = function() { var form = $( this ); setTimeout(function() { form.find( ":ui-button" ).button( "refresh" ); }, 1 ); }, radioGroup = function( radio ) { var name = radio.name, form = radio.form, radios = $( [] ); if ( name ) { name = name.replace( /'/g, "\\'" ); if ( form ) { radios = $( form ).find( "[name='" + name + "']" ); } else { radios = $( "[name='" + name + "']", radio.ownerDocument ) .filter(function() { return !this.form; }); } } return radios; }; $.widget( "ui.button", { version: "1.10.3", defaultElement: "").addClass(this._triggerClass). html(!buttonImage ? buttonText : $("").attr( { src:buttonImage, alt:buttonText, title:buttonText }))); input[isRTL ? "before" : "after"](inst.trigger); inst.trigger.click(function() { if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { $.datepicker._hideDatepicker(); } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { $.datepicker._hideDatepicker(); $.datepicker._showDatepicker(input[0]); } else { $.datepicker._showDatepicker(input[0]); } return false; }); } }, /* Apply the maximum length for the date format. */ _autoSize: function(inst) { if (this._get(inst, "autoSize") && !inst.inline) { var findMax, max, maxI, i, date = new Date(2009, 12 - 1, 20), // Ensure double digits dateFormat = this._get(inst, "dateFormat"); if (dateFormat.match(/[DM]/)) { findMax = function(names) { max = 0; maxI = 0; for (i = 0; i < names.length; i++) { if (names[i].length > max) { max = names[i].length; maxI = i; } } return maxI; }; date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? "monthNames" : "monthNamesShort")))); date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); } inst.input.attr("size", this._formatDate(inst, date).length); } }, /* Attach an inline date picker to a div. */ _inlineDatepicker: function(target, inst) { var divSpan = $(target); if (divSpan.hasClass(this.markerClassName)) { return; } divSpan.addClass(this.markerClassName).append(inst.dpDiv); $.data(target, PROP_NAME, inst); this._setDate(inst, this._getDefaultDate(inst), true); this._updateDatepicker(inst); this._updateAlternate(inst); //If disabled option is true, disable the datepicker before showing it (see ticket #5665) if( inst.settings.disabled ) { this._disableDatepicker( target ); } // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height inst.dpDiv.css( "display", "block" ); }, /* Pop-up the date picker in a "dialog" box. * @param input element - ignored * @param date string or Date - the initial date to display * @param onSelect function - the function to call when a date is selected * @param settings object - update the dialog date picker instance's settings (anonymous object) * @param pos int[2] - coordinates for the dialog's position within the screen or * event - with x/y coordinates or * leave empty for default (screen centre) * @return the manager object */ _dialogDatepicker: function(input, date, onSelect, settings, pos) { var id, browserWidth, browserHeight, scrollX, scrollY, inst = this._dialogInst; // internal instance if (!inst) { this.uuid += 1; id = "dp" + this.uuid; this._dialogInput = $(""); this._dialogInput.keydown(this._doKeyDown); $("body").append(this._dialogInput); inst = this._dialogInst = this._newInst(this._dialogInput, false); inst.settings = {}; $.data(this._dialogInput[0], PROP_NAME, inst); } extendRemove(inst.settings, settings || {}); date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); this._dialogInput.val(date); this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); if (!this._pos) { browserWidth = document.documentElement.clientWidth; browserHeight = document.documentElement.clientHeight; scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; scrollY = document.documentElement.scrollTop || document.body.scrollTop; this._pos = // should use actual width/height below [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; } // move input on screen for focus, but hidden behind dialog this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); inst.settings.onSelect = onSelect; this._inDialog = true; this.dpDiv.addClass(this._dialogClass); this._showDatepicker(this._dialogInput[0]); if ($.blockUI) { $.blockUI(this.dpDiv); } $.data(this._dialogInput[0], PROP_NAME, inst); return this; }, /* Detach a datepicker from its control. * @param target element - the target input field or division or span */ _destroyDatepicker: function(target) { var nodeName, $target = $(target), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } nodeName = target.nodeName.toLowerCase(); $.removeData(target, PROP_NAME); if (nodeName === "input") { inst.append.remove(); inst.trigger.remove(); $target.removeClass(this.markerClassName). unbind("focus", this._showDatepicker). unbind("keydown", this._doKeyDown). unbind("keypress", this._doKeyPress). unbind("keyup", this._doKeyUp); } else if (nodeName === "div" || nodeName === "span") { $target.removeClass(this.markerClassName).empty(); } }, /* Enable the date picker to a jQuery selection. * @param target element - the target input field or division or span */ _enableDatepicker: function(target) { var nodeName, inline, $target = $(target), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } nodeName = target.nodeName.toLowerCase(); if (nodeName === "input") { target.disabled = false; inst.trigger.filter("button"). each(function() { this.disabled = false; }).end(). filter("img").css({opacity: "1.0", cursor: ""}); } else if (nodeName === "div" || nodeName === "span") { inline = $target.children("." + this._inlineClass); inline.children().removeClass("ui-state-disabled"); inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). prop("disabled", false); } this._disabledInputs = $.map(this._disabledInputs, function(value) { return (value === target ? null : value); }); // delete entry }, /* Disable the date picker to a jQuery selection. * @param target element - the target input field or division or span */ _disableDatepicker: function(target) { var nodeName, inline, $target = $(target), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } nodeName = target.nodeName.toLowerCase(); if (nodeName === "input") { target.disabled = true; inst.trigger.filter("button"). each(function() { this.disabled = true; }).end(). filter("img").css({opacity: "0.5", cursor: "default"}); } else if (nodeName === "div" || nodeName === "span") { inline = $target.children("." + this._inlineClass); inline.children().addClass("ui-state-disabled"); inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). prop("disabled", true); } this._disabledInputs = $.map(this._disabledInputs, function(value) { return (value === target ? null : value); }); // delete entry this._disabledInputs[this._disabledInputs.length] = target; }, /* Is the first field in a jQuery collection disabled as a datepicker? * @param target element - the target input field or division or span * @return boolean - true if disabled, false if enabled */ _isDisabledDatepicker: function(target) { if (!target) { return false; } for (var i = 0; i < this._disabledInputs.length; i++) { if (this._disabledInputs[i] === target) { return true; } } return false; }, /* Retrieve the instance data for the target control. * @param target element - the target input field or division or span * @return object - the associated instance data * @throws error if a jQuery problem getting data */ _getInst: function(target) { try { return $.data(target, PROP_NAME); } catch (err) { throw "Missing instance data for this datepicker"; } }, /* Update or retrieve the settings for a date picker attached to an input field or division. * @param target element - the target input field or division or span * @param name object - the new settings to update or * string - the name of the setting to change or retrieve, * when retrieving also "all" for all instance settings or * "defaults" for all global defaults * @param value any - the new value for the setting * (omit if above is an object or to retrieve a value) */ _optionDatepicker: function(target, name, value) { var settings, date, minDate, maxDate, inst = this._getInst(target); if (arguments.length === 2 && typeof name === "string") { return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : (inst ? (name === "all" ? $.extend({}, inst.settings) : this._get(inst, name)) : null)); } settings = name || {}; if (typeof name === "string") { settings = {}; settings[name] = value; } if (inst) { if (this._curInst === inst) { this._hideDatepicker(); } date = this._getDateDatepicker(target, true); minDate = this._getMinMaxDate(inst, "min"); maxDate = this._getMinMaxDate(inst, "max"); extendRemove(inst.settings, settings); // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { inst.settings.minDate = this._formatDate(inst, minDate); } if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { inst.settings.maxDate = this._formatDate(inst, maxDate); } if ( "disabled" in settings ) { if ( settings.disabled ) { this._disableDatepicker(target); } else { this._enableDatepicker(target); } } this._attachments($(target), inst); this._autoSize(inst); this._setDate(inst, date); this._updateAlternate(inst); this._updateDatepicker(inst); } }, // change method deprecated _changeDatepicker: function(target, name, value) { this._optionDatepicker(target, name, value); }, /* Redraw the date picker attached to an input field or division. * @param target element - the target input field or division or span */ _refreshDatepicker: function(target) { var inst = this._getInst(target); if (inst) { this._updateDatepicker(inst); } }, /* Set the dates for a jQuery selection. * @param target element - the target input field or division or span * @param date Date - the new date */ _setDateDatepicker: function(target, date) { var inst = this._getInst(target); if (inst) { this._setDate(inst, date); this._updateDatepicker(inst); this._updateAlternate(inst); } }, /* Get the date(s) for the first entry in a jQuery selection. * @param target element - the target input field or division or span * @param noDefault boolean - true if no default date is to be used * @return Date - the current date */ _getDateDatepicker: function(target, noDefault) { var inst = this._getInst(target); if (inst && !inst.inline) { this._setDateFromField(inst, noDefault); } return (inst ? this._getDate(inst) : null); }, /* Handle keystrokes. */ _doKeyDown: function(event) { var onSelect, dateStr, sel, inst = $.datepicker._getInst(event.target), handled = true, isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); inst._keyEvent = true; if ($.datepicker._datepickerShowing) { switch (event.keyCode) { case 9: $.datepicker._hideDatepicker(); handled = false; break; // hide on tab out case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + $.datepicker._currentClass + ")", inst.dpDiv); if (sel[0]) { $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); } onSelect = $.datepicker._get(inst, "onSelect"); if (onSelect) { dateStr = $.datepicker._formatDate(inst); // trigger custom callback onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); } else { $.datepicker._hideDatepicker(); } return false; // don't submit the form case 27: $.datepicker._hideDatepicker(); break; // hide on escape case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? -$.datepicker._get(inst, "stepBigMonths") : -$.datepicker._get(inst, "stepMonths")), "M"); break; // previous month/year on page up/+ ctrl case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? +$.datepicker._get(inst, "stepBigMonths") : +$.datepicker._get(inst, "stepMonths")), "M"); break; // next month/year on page down/+ ctrl case 35: if (event.ctrlKey || event.metaKey) { $.datepicker._clearDate(event.target); } handled = event.ctrlKey || event.metaKey; break; // clear on ctrl or command +end case 36: if (event.ctrlKey || event.metaKey) { $.datepicker._gotoToday(event.target); } handled = event.ctrlKey || event.metaKey; break; // current on ctrl or command +home case 37: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); } handled = event.ctrlKey || event.metaKey; // -1 day on ctrl or command +left if (event.originalEvent.altKey) { $.datepicker._adjustDate(event.target, (event.ctrlKey ? -$.datepicker._get(inst, "stepBigMonths") : -$.datepicker._get(inst, "stepMonths")), "M"); } // next month/year on alt +left on Mac break; case 38: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, -7, "D"); } handled = event.ctrlKey || event.metaKey; break; // -1 week on ctrl or command +up case 39: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); } handled = event.ctrlKey || event.metaKey; // +1 day on ctrl or command +right if (event.originalEvent.altKey) { $.datepicker._adjustDate(event.target, (event.ctrlKey ? +$.datepicker._get(inst, "stepBigMonths") : +$.datepicker._get(inst, "stepMonths")), "M"); } // next month/year on alt +right break; case 40: if (event.ctrlKey || event.metaKey) { $.datepicker._adjustDate(event.target, +7, "D"); } handled = event.ctrlKey || event.metaKey; break; // +1 week on ctrl or command +down default: handled = false; } } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home $.datepicker._showDatepicker(this); } else { handled = false; } if (handled) { event.preventDefault(); event.stopPropagation(); } }, /* Filter entered characters - based on date format. */ _doKeyPress: function(event) { var chars, chr, inst = $.datepicker._getInst(event.target); if ($.datepicker._get(inst, "constrainInput")) { chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); } }, /* Synchronise manual entry and field/alternate field. */ _doKeyUp: function(event) { var date, inst = $.datepicker._getInst(event.target); if (inst.input.val() !== inst.lastVal) { try { date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), (inst.input ? inst.input.val() : null), $.datepicker._getFormatConfig(inst)); if (date) { // only if valid $.datepicker._setDateFromField(inst); $.datepicker._updateAlternate(inst); $.datepicker._updateDatepicker(inst); } } catch (err) { } } return true; }, /* Pop-up the date picker for a given input field. * If false returned from beforeShow event handler do not show. * @param input element - the input field attached to the date picker or * event - if triggered by focus */ _showDatepicker: function(input) { input = input.target || input; if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger input = $("input", input.parentNode)[0]; } if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here return; } var inst, beforeShow, beforeShowSettings, isFixed, offset, showAnim, duration; inst = $.datepicker._getInst(input); if ($.datepicker._curInst && $.datepicker._curInst !== inst) { $.datepicker._curInst.dpDiv.stop(true, true); if ( inst && $.datepicker._datepickerShowing ) { $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); } } beforeShow = $.datepicker._get(inst, "beforeShow"); beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; if(beforeShowSettings === false){ return; } extendRemove(inst.settings, beforeShowSettings); inst.lastVal = null; $.datepicker._lastInput = input; $.datepicker._setDateFromField(inst); if ($.datepicker._inDialog) { // hide cursor input.value = ""; } if (!$.datepicker._pos) { // position below input $.datepicker._pos = $.datepicker._findPos(input); $.datepicker._pos[1] += input.offsetHeight; // add the height } isFixed = false; $(input).parents().each(function() { isFixed |= $(this).css("position") === "fixed"; return !isFixed; }); offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; $.datepicker._pos = null; //to avoid flashes on Firefox inst.dpDiv.empty(); // determine sizing offscreen inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); $.datepicker._updateDatepicker(inst); // fix width for dynamic number of date pickers // and adjust position before showing offset = $.datepicker._checkOffset(inst, offset, isFixed); inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? "static" : (isFixed ? "fixed" : "absolute")), display: "none", left: offset.left + "px", top: offset.top + "px"}); if (!inst.inline) { showAnim = $.datepicker._get(inst, "showAnim"); duration = $.datepicker._get(inst, "duration"); inst.dpDiv.zIndex($(input).zIndex()+1); $.datepicker._datepickerShowing = true; if ( $.effects && $.effects.effect[ showAnim ] ) { inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); } else { inst.dpDiv[showAnim || "show"](showAnim ? duration : null); } if ( $.datepicker._shouldFocusInput( inst ) ) { inst.input.focus(); } $.datepicker._curInst = inst; } }, /* Generate the date picker content. */ _updateDatepicker: function(inst) { this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) instActive = inst; // for delegate hover events inst.dpDiv.empty().append(this._generateHTML(inst)); this._attachHandlers(inst); inst.dpDiv.find("." + this._dayOverClass + " a").mouseover(); var origyearshtml, numMonths = this._getNumberOfMonths(inst), cols = numMonths[1], width = 17; inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); if (cols > 1) { inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); } inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + "Class"]("ui-datepicker-multi"); inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + "Class"]("ui-datepicker-rtl"); if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { inst.input.focus(); } // deffered render of the years select (to avoid flashes on Firefox) if( inst.yearshtml ){ origyearshtml = inst.yearshtml; setTimeout(function(){ //assure that inst.yearshtml didn't change. if( origyearshtml === inst.yearshtml && inst.yearshtml ){ inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); } origyearshtml = inst.yearshtml = null; }, 0); } }, // #6694 - don't focus the input if it's already focused // this breaks the change event in IE // Support: IE and jQuery <1.9 _shouldFocusInput: function( inst ) { return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); }, /* Check positioning to remain on screen. */ _checkOffset: function(inst, offset, isFixed) { var dpWidth = inst.dpDiv.outerWidth(), dpHeight = inst.dpDiv.outerHeight(), inputWidth = inst.input ? inst.input.outerWidth() : 0, inputHeight = inst.input ? inst.input.outerHeight() : 0, viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; // now check if datepicker is showing outside window viewport - move to a better place if so. offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? Math.abs(offset.left + dpWidth - viewWidth) : 0); offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? Math.abs(dpHeight + inputHeight) : 0); return offset; }, /* Find an object's position on the screen. */ _findPos: function(obj) { var position, inst = this._getInst(obj), isRTL = this._get(inst, "isRTL"); while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { obj = obj[isRTL ? "previousSibling" : "nextSibling"]; } position = $(obj).offset(); return [position.left, position.top]; }, /* Hide the date picker from view. * @param input element - the input field attached to the date picker */ _hideDatepicker: function(input) { var showAnim, duration, postProcess, onClose, inst = this._curInst; if (!inst || (input && inst !== $.data(input, PROP_NAME))) { return; } if (this._datepickerShowing) { showAnim = this._get(inst, "showAnim"); duration = this._get(inst, "duration"); postProcess = function() { $.datepicker._tidyDialog(inst); }; // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); } else { inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); } if (!showAnim) { postProcess(); } this._datepickerShowing = false; onClose = this._get(inst, "onClose"); if (onClose) { onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); } this._lastInput = null; if (this._inDialog) { this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); if ($.blockUI) { $.unblockUI(); $("body").append(this.dpDiv); } } this._inDialog = false; } }, /* Tidy up after a dialog display. */ _tidyDialog: function(inst) { inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); }, /* Close date picker if clicked elsewhere. */ _checkExternalClick: function(event) { if (!$.datepicker._curInst) { return; } var $target = $(event.target), inst = $.datepicker._getInst($target[0]); if ( ( ( $target[0].id !== $.datepicker._mainDivId && $target.parents("#" + $.datepicker._mainDivId).length === 0 && !$target.hasClass($.datepicker.markerClassName) && !$target.closest("." + $.datepicker._triggerClass).length && $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { $.datepicker._hideDatepicker(); } }, /* Adjust one of the date sub-fields. */ _adjustDate: function(id, offset, period) { var target = $(id), inst = this._getInst(target[0]); if (this._isDisabledDatepicker(target[0])) { return; } this._adjustInstDate(inst, offset + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning period); this._updateDatepicker(inst); }, /* Action for current link. */ _gotoToday: function(id) { var date, target = $(id), inst = this._getInst(target[0]); if (this._get(inst, "gotoCurrent") && inst.currentDay) { inst.selectedDay = inst.currentDay; inst.drawMonth = inst.selectedMonth = inst.currentMonth; inst.drawYear = inst.selectedYear = inst.currentYear; } else { date = new Date(); inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); } this._notifyChange(inst); this._adjustDate(target); }, /* Action for selecting a new month/year. */ _selectMonthYear: function(id, select, period) { var target = $(id), inst = this._getInst(target[0]); inst["selected" + (period === "M" ? "Month" : "Year")] = inst["draw" + (period === "M" ? "Month" : "Year")] = parseInt(select.options[select.selectedIndex].value,10); this._notifyChange(inst); this._adjustDate(target); }, /* Action for selecting a day. */ _selectDay: function(id, month, year, td) { var inst, target = $(id); if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { return; } inst = this._getInst(target[0]); inst.selectedDay = inst.currentDay = $("a", td).html(); inst.selectedMonth = inst.currentMonth = month; inst.selectedYear = inst.currentYear = year; this._selectDate(id, this._formatDate(inst, inst.currentDay, inst.currentMonth, inst.currentYear)); }, /* Erase the input field and hide the date picker. */ _clearDate: function(id) { var target = $(id); this._selectDate(target, ""); }, /* Update the input field with the selected date. */ _selectDate: function(id, dateStr) { var onSelect, target = $(id), inst = this._getInst(target[0]); dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); if (inst.input) { inst.input.val(dateStr); } this._updateAlternate(inst); onSelect = this._get(inst, "onSelect"); if (onSelect) { onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback } else if (inst.input) { inst.input.trigger("change"); // fire the change event } if (inst.inline){ this._updateDatepicker(inst); } else { this._hideDatepicker(); this._lastInput = inst.input[0]; if (typeof(inst.input[0]) !== "object") { inst.input.focus(); // restore focus } this._lastInput = null; } }, /* Update any alternate field to synchronise with the main field. */ _updateAlternate: function(inst) { var altFormat, date, dateStr, altField = this._get(inst, "altField"); if (altField) { // update alternate field too altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); date = this._getDate(inst); dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); $(altField).each(function() { $(this).val(dateStr); }); } }, /* Set as beforeShowDay function to prevent selection of weekends. * @param date Date - the date to customise * @return [boolean, string] - is this date selectable?, what is its CSS class? */ noWeekends: function(date) { var day = date.getDay(); return [(day > 0 && day < 6), ""]; }, /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. * @param date Date - the date to get the week for * @return number - the number of the week within the year that contains this date */ iso8601Week: function(date) { var time, checkDate = new Date(date.getTime()); // Find Thursday of this week starting on Monday checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); time = checkDate.getTime(); checkDate.setMonth(0); // Compare with Jan 1 checkDate.setDate(1); return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; }, /* Parse a string value into a date object. * See formatDate below for the possible formats. * * @param format string - the expected format of the date * @param value string - the date in the above format * @param settings Object - attributes include: * shortYearCutoff number - the cutoff year for determining the century (optional) * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) * dayNames string[7] - names of the days from Sunday (optional) * monthNamesShort string[12] - abbreviated names of the months (optional) * monthNames string[12] - names of the months (optional) * @return Date - the extracted date value or null if value is blank */ parseDate: function (format, value, settings) { if (format == null || value == null) { throw "Invalid arguments"; } value = (typeof value === "object" ? value.toString() : value + ""); if (value === "") { return null; } var iFormat, dim, extra, iValue = 0, shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, year = -1, month = -1, day = -1, doy = -1, literal = false, date, // Check whether a format character is doubled lookAhead = function(match) { var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); if (matches) { iFormat++; } return matches; }, // Extract a number from the string value getNumber = function(match) { var isDoubled = lookAhead(match), size = (match === "@" ? 14 : (match === "!" ? 20 : (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), digits = new RegExp("^\\d{1," + size + "}"), num = value.substring(iValue).match(digits); if (!num) { throw "Missing number at position " + iValue; } iValue += num[0].length; return parseInt(num[0], 10); }, // Extract a name from the string value and convert to an index getName = function(match, shortNames, longNames) { var index = -1, names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { return [ [k, v] ]; }).sort(function (a, b) { return -(a[1].length - b[1].length); }); $.each(names, function (i, pair) { var name = pair[1]; if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { index = pair[0]; iValue += name.length; return false; } }); if (index !== -1) { return index + 1; } else { throw "Unknown name at position " + iValue; } }, // Confirm that a literal character matches the string value checkLiteral = function() { if (value.charAt(iValue) !== format.charAt(iFormat)) { throw "Unexpected literal at position " + iValue; } iValue++; }; for (iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === "'" && !lookAhead("'")) { literal = false; } else { checkLiteral(); } } else { switch (format.charAt(iFormat)) { case "d": day = getNumber("d"); break; case "D": getName("D", dayNamesShort, dayNames); break; case "o": doy = getNumber("o"); break; case "m": month = getNumber("m"); break; case "M": month = getName("M", monthNamesShort, monthNames); break; case "y": year = getNumber("y"); break; case "@": date = new Date(getNumber("@")); year = date.getFullYear(); month = date.getMonth() + 1; day = date.getDate(); break; case "!": date = new Date((getNumber("!") - this._ticksTo1970) / 10000); year = date.getFullYear(); month = date.getMonth() + 1; day = date.getDate(); break; case "'": if (lookAhead("'")){ checkLiteral(); } else { literal = true; } break; default: checkLiteral(); } } } if (iValue < value.length){ extra = value.substr(iValue); if (!/^\s+/.test(extra)) { throw "Extra/unparsed characters found in date: " + extra; } } if (year === -1) { year = new Date().getFullYear(); } else if (year < 100) { year += new Date().getFullYear() - new Date().getFullYear() % 100 + (year <= shortYearCutoff ? 0 : -100); } if (doy > -1) { month = 1; day = doy; do { dim = this._getDaysInMonth(year, month - 1); if (day <= dim) { break; } month++; day -= dim; } while (true); } date = this._daylightSavingAdjust(new Date(year, month - 1, day)); if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { throw "Invalid date"; // E.g. 31/02/00 } return date; }, /* Standard date formats. */ ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) COOKIE: "D, dd M yy", ISO_8601: "yy-mm-dd", RFC_822: "D, d M y", RFC_850: "DD, dd-M-y", RFC_1036: "D, d M y", RFC_1123: "D, d M yy", RFC_2822: "D, d M yy", RSS: "D, d M y", // RFC 822 TICKS: "!", TIMESTAMP: "@", W3C: "yy-mm-dd", // ISO 8601 _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), /* Format a date object into a string value. * The format can be combinations of the following: * d - day of month (no leading zero) * dd - day of month (two digit) * o - day of year (no leading zeros) * oo - day of year (three digit) * D - day name short * DD - day name long * m - month of year (no leading zero) * mm - month of year (two digit) * M - month name short * MM - month name long * y - year (two digit) * yy - year (four digit) * @ - Unix timestamp (ms since 01/01/1970) * ! - Windows ticks (100ns since 01/01/0001) * "..." - literal text * '' - single quote * * @param format string - the desired format of the date * @param date Date - the date value to format * @param settings Object - attributes include: * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) * dayNames string[7] - names of the days from Sunday (optional) * monthNamesShort string[12] - abbreviated names of the months (optional) * monthNames string[12] - names of the months (optional) * @return string - the date in the above format */ formatDate: function (format, date, settings) { if (!date) { return ""; } var iFormat, dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, // Check whether a format character is doubled lookAhead = function(match) { var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); if (matches) { iFormat++; } return matches; }, // Format a number, with leading zero if necessary formatNumber = function(match, value, len) { var num = "" + value; if (lookAhead(match)) { while (num.length < len) { num = "0" + num; } } return num; }, // Format a name, short or long as requested formatName = function(match, value, shortNames, longNames) { return (lookAhead(match) ? longNames[value] : shortNames[value]); }, output = "", literal = false; if (date) { for (iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === "'" && !lookAhead("'")) { literal = false; } else { output += format.charAt(iFormat); } } else { switch (format.charAt(iFormat)) { case "d": output += formatNumber("d", date.getDate(), 2); break; case "D": output += formatName("D", date.getDay(), dayNamesShort, dayNames); break; case "o": output += formatNumber("o", Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); break; case "m": output += formatNumber("m", date.getMonth() + 1, 2); break; case "M": output += formatName("M", date.getMonth(), monthNamesShort, monthNames); break; case "y": output += (lookAhead("y") ? date.getFullYear() : (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); break; case "@": output += date.getTime(); break; case "!": output += date.getTime() * 10000 + this._ticksTo1970; break; case "'": if (lookAhead("'")) { output += "'"; } else { literal = true; } break; default: output += format.charAt(iFormat); } } } } return output; }, /* Extract all possible characters from the date format. */ _possibleChars: function (format) { var iFormat, chars = "", literal = false, // Check whether a format character is doubled lookAhead = function(match) { var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); if (matches) { iFormat++; } return matches; }; for (iFormat = 0; iFormat < format.length; iFormat++) { if (literal) { if (format.charAt(iFormat) === "'" && !lookAhead("'")) { literal = false; } else { chars += format.charAt(iFormat); } } else { switch (format.charAt(iFormat)) { case "d": case "m": case "y": case "@": chars += "0123456789"; break; case "D": case "M": return null; // Accept anything case "'": if (lookAhead("'")) { chars += "'"; } else { literal = true; } break; default: chars += format.charAt(iFormat); } } } return chars; }, /* Get a setting value, defaulting if necessary. */ _get: function(inst, name) { return inst.settings[name] !== undefined ? inst.settings[name] : this._defaults[name]; }, /* Parse existing date and initialise date picker. */ _setDateFromField: function(inst, noDefault) { if (inst.input.val() === inst.lastVal) { return; } var dateFormat = this._get(inst, "dateFormat"), dates = inst.lastVal = inst.input ? inst.input.val() : null, defaultDate = this._getDefaultDate(inst), date = defaultDate, settings = this._getFormatConfig(inst); try { date = this.parseDate(dateFormat, dates, settings) || defaultDate; } catch (event) { dates = (noDefault ? "" : dates); } inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); inst.currentDay = (dates ? date.getDate() : 0); inst.currentMonth = (dates ? date.getMonth() : 0); inst.currentYear = (dates ? date.getFullYear() : 0); this._adjustInstDate(inst); }, /* Retrieve the default date shown on opening. */ _getDefaultDate: function(inst) { return this._restrictMinMax(inst, this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); }, /* A date may be specified as an exact value or a relative one. */ _determineDate: function(inst, date, defaultDate) { var offsetNumeric = function(offset) { var date = new Date(); date.setDate(date.getDate() + offset); return date; }, offsetString = function(offset) { try { return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), offset, $.datepicker._getFormatConfig(inst)); } catch (e) { // Ignore } var date = (offset.toLowerCase().match(/^c/) ? $.datepicker._getDate(inst) : null) || new Date(), year = date.getFullYear(), month = date.getMonth(), day = date.getDate(), pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, matches = pattern.exec(offset); while (matches) { switch (matches[2] || "d") { case "d" : case "D" : day += parseInt(matches[1],10); break; case "w" : case "W" : day += parseInt(matches[1],10) * 7; break; case "m" : case "M" : month += parseInt(matches[1],10); day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); break; case "y": case "Y" : year += parseInt(matches[1],10); day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); break; } matches = pattern.exec(offset); } return new Date(year, month, day); }, newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); if (newDate) { newDate.setHours(0); newDate.setMinutes(0); newDate.setSeconds(0); newDate.setMilliseconds(0); } return this._daylightSavingAdjust(newDate); }, /* Handle switch to/from daylight saving. * Hours may be non-zero on daylight saving cut-over: * > 12 when midnight changeover, but then cannot generate * midnight datetime, so jump to 1AM, otherwise reset. * @param date (Date) the date to check * @return (Date) the corrected date */ _daylightSavingAdjust: function(date) { if (!date) { return null; } date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); return date; }, /* Set the date(s) directly. */ _setDate: function(inst, date, noChange) { var clear = !date, origMonth = inst.selectedMonth, origYear = inst.selectedYear, newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); inst.selectedDay = inst.currentDay = newDate.getDate(); inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { this._notifyChange(inst); } this._adjustInstDate(inst); if (inst.input) { inst.input.val(clear ? "" : this._formatDate(inst)); } }, /* Retrieve the date(s) directly. */ _getDate: function(inst) { var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : this._daylightSavingAdjust(new Date( inst.currentYear, inst.currentMonth, inst.currentDay))); return startDate; }, /* Attach the onxxx handlers. These are declared statically so * they work with static code transformers like Caja. */ _attachHandlers: function(inst) { var stepMonths = this._get(inst, "stepMonths"), id = "#" + inst.id.replace( /\\\\/g, "\\" ); inst.dpDiv.find("[data-handler]").map(function () { var handler = { prev: function () { $.datepicker._adjustDate(id, -stepMonths, "M"); }, next: function () { $.datepicker._adjustDate(id, +stepMonths, "M"); }, hide: function () { $.datepicker._hideDatepicker(); }, today: function () { $.datepicker._gotoToday(id); }, selectDay: function () { $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); return false; }, selectMonth: function () { $.datepicker._selectMonthYear(id, this, "M"); return false; }, selectYear: function () { $.datepicker._selectMonthYear(id, this, "Y"); return false; } }; $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); }); }, /* Generate the HTML for the current state of the date picker. */ _generateHTML: function(inst) { var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, monthNames, monthNamesShort, beforeShowDay, showOtherMonths, selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, printDate, dRow, tbody, daySettings, otherMonth, unselectable, tempDate = new Date(), today = this._daylightSavingAdjust( new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time isRTL = this._get(inst, "isRTL"), showButtonPanel = this._get(inst, "showButtonPanel"), hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), numMonths = this._getNumberOfMonths(inst), showCurrentAtPos = this._get(inst, "showCurrentAtPos"), stepMonths = this._get(inst, "stepMonths"), isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), minDate = this._getMinMaxDate(inst, "min"), maxDate = this._getMinMaxDate(inst, "max"), drawMonth = inst.drawMonth - showCurrentAtPos, drawYear = inst.drawYear; if (drawMonth < 0) { drawMonth += 12; drawYear--; } if (maxDate) { maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { drawMonth--; if (drawMonth < 0) { drawMonth = 11; drawYear--; } } } inst.drawMonth = drawMonth; inst.drawYear = drawYear; prevText = this._get(inst, "prevText"); prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), this._getFormatConfig(inst))); prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? "" + prevText + "" : (hideIfNoPrevNext ? "" : "" + prevText + "")); nextText = this._get(inst, "nextText"); nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), this._getFormatConfig(inst))); next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? "" + nextText + "" : (hideIfNoPrevNext ? "" : "" + nextText + "")); currentText = this._get(inst, "currentText"); gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); currentText = (!navigationAsDateFormat ? currentText : this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); controls = (!inst.inline ? "" : ""); buttonPanel = (showButtonPanel) ? "
    " + (isRTL ? controls : "") + (this._isInRange(inst, gotoDate) ? "" : "") + (isRTL ? "" : controls) + "
    " : ""; firstDay = parseInt(this._get(inst, "firstDay"),10); firstDay = (isNaN(firstDay) ? 0 : firstDay); showWeek = this._get(inst, "showWeek"); dayNames = this._get(inst, "dayNames"); dayNamesMin = this._get(inst, "dayNamesMin"); monthNames = this._get(inst, "monthNames"); monthNamesShort = this._get(inst, "monthNamesShort"); beforeShowDay = this._get(inst, "beforeShowDay"); showOtherMonths = this._get(inst, "showOtherMonths"); selectOtherMonths = this._get(inst, "selectOtherMonths"); defaultDate = this._getDefaultDate(inst); html = ""; dow; for (row = 0; row < numMonths[0]; row++) { group = ""; this.maxRows = 4; for (col = 0; col < numMonths[1]; col++) { selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); cornerClass = " ui-corner-all"; calender = ""; if (isMultiMonth) { calender += "
    "; } calender += "
    " + (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers "
    " + ""; thead = (showWeek ? "" : ""); for (dow = 0; dow < 7; dow++) { // days of the week day = (dow + firstDay) % 7; thead += "= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + "" + dayNamesMin[day] + ""; } calender += thead + ""; daysInMonth = this._getDaysInMonth(drawYear, drawMonth); if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); } leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) this.maxRows = numRows; printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows calender += ""; tbody = (!showWeek ? "" : ""); for (dow = 0; dow < 7; dow++) { // create date picker days daySettings = (beforeShowDay ? beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); otherMonth = (printDate.getMonth() !== drawMonth); unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || (minDate && printDate < minDate) || (maxDate && printDate > maxDate); tbody += ""; // display selectable date printDate.setDate(printDate.getDate() + 1); printDate = this._daylightSavingAdjust(printDate); } calender += tbody + ""; } drawMonth++; if (drawMonth > 11) { drawMonth = 0; drawYear++; } calender += "
    " + this._get(inst, "weekHeader") + "
    " + this._get(inst, "calculateWeek")(printDate) + "" + // actions (otherMonth && !showOtherMonths ? " " : // display for other months (unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "")) + "
    " + (isMultiMonth ? "
    " + ((numMonths[0] > 0 && col === numMonths[1]-1) ? "
    " : "") : ""); group += calender; } html += group; } html += buttonPanel; inst._keyEvent = false; return html; }, /* Generate the month and year header. */ _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, secondary, monthNames, monthNamesShort) { var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, changeMonth = this._get(inst, "changeMonth"), changeYear = this._get(inst, "changeYear"), showMonthAfterYear = this._get(inst, "showMonthAfterYear"), html = "
    ", monthHtml = ""; // month selection if (secondary || !changeMonth) { monthHtml += "" + monthNames[drawMonth] + ""; } else { inMinYear = (minDate && minDate.getFullYear() === drawYear); inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); monthHtml += ""; } if (!showMonthAfterYear) { html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); } // year selection if ( !inst.yearshtml ) { inst.yearshtml = ""; if (secondary || !changeYear) { html += "" + drawYear + ""; } else { // determine range of years to display years = this._get(inst, "yearRange").split(":"); thisYear = new Date().getFullYear(); determineYear = function(value) { var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : parseInt(value, 10))); return (isNaN(year) ? thisYear : year); }; year = determineYear(years[0]); endYear = Math.max(year, determineYear(years[1] || "")); year = (minDate ? Math.max(year, minDate.getFullYear()) : year); endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); inst.yearshtml += ""; html += inst.yearshtml; inst.yearshtml = null; } } html += this._get(inst, "yearSuffix"); if (showMonthAfterYear) { html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; } html += "
    "; // Close datepicker_header return html; }, /* Adjust one of the date sub-fields. */ _adjustInstDate: function(inst, offset, period) { var year = inst.drawYear + (period === "Y" ? offset : 0), month = inst.drawMonth + (period === "M" ? offset : 0), day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); inst.selectedDay = date.getDate(); inst.drawMonth = inst.selectedMonth = date.getMonth(); inst.drawYear = inst.selectedYear = date.getFullYear(); if (period === "M" || period === "Y") { this._notifyChange(inst); } }, /* Ensure a date is within any min/max bounds. */ _restrictMinMax: function(inst, date) { var minDate = this._getMinMaxDate(inst, "min"), maxDate = this._getMinMaxDate(inst, "max"), newDate = (minDate && date < minDate ? minDate : date); return (maxDate && newDate > maxDate ? maxDate : newDate); }, /* Notify change of month/year. */ _notifyChange: function(inst) { var onChange = this._get(inst, "onChangeMonthYear"); if (onChange) { onChange.apply((inst.input ? inst.input[0] : null), [inst.selectedYear, inst.selectedMonth + 1, inst]); } }, /* Determine the number of months to show. */ _getNumberOfMonths: function(inst) { var numMonths = this._get(inst, "numberOfMonths"); return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); }, /* Determine the current maximum date - ensure no time components are set. */ _getMinMaxDate: function(inst, minMax) { return this._determineDate(inst, this._get(inst, minMax + "Date"), null); }, /* Find the number of days in a given month. */ _getDaysInMonth: function(year, month) { return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); }, /* Find the day of the week of the first of a month. */ _getFirstDayOfMonth: function(year, month) { return new Date(year, month, 1).getDay(); }, /* Determines if we should allow a "next/prev" month display change. */ _canAdjustMonth: function(inst, offset, curYear, curMonth) { var numMonths = this._getNumberOfMonths(inst), date = this._daylightSavingAdjust(new Date(curYear, curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); if (offset < 0) { date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); } return this._isInRange(inst, date); }, /* Is the given date in the accepted range? */ _isInRange: function(inst, date) { var yearSplit, currentYear, minDate = this._getMinMaxDate(inst, "min"), maxDate = this._getMinMaxDate(inst, "max"), minYear = null, maxYear = null, years = this._get(inst, "yearRange"); if (years){ yearSplit = years.split(":"); currentYear = new Date().getFullYear(); minYear = parseInt(yearSplit[0], 10); maxYear = parseInt(yearSplit[1], 10); if ( yearSplit[0].match(/[+\-].*/) ) { minYear += currentYear; } if ( yearSplit[1].match(/[+\-].*/) ) { maxYear += currentYear; } } return ((!minDate || date.getTime() >= minDate.getTime()) && (!maxDate || date.getTime() <= maxDate.getTime()) && (!minYear || date.getFullYear() >= minYear) && (!maxYear || date.getFullYear() <= maxYear)); }, /* Provide the configuration settings for formatting/parsing. */ _getFormatConfig: function(inst) { var shortYearCutoff = this._get(inst, "shortYearCutoff"); shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); return {shortYearCutoff: shortYearCutoff, dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; }, /* Format the given date for display. */ _formatDate: function(inst, day, month, year) { if (!day) { inst.currentDay = inst.selectedDay; inst.currentMonth = inst.selectedMonth; inst.currentYear = inst.selectedYear; } var date = (day ? (typeof day === "object" ? day : this._daylightSavingAdjust(new Date(year, month, day))) : this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); } }); /* * Bind hover events for datepicker elements. * Done via delegate so the binding only occurs once in the lifetime of the parent div. * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. */ function bindHover(dpDiv) { var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; return dpDiv.delegate(selector, "mouseout", function() { $(this).removeClass("ui-state-hover"); if (this.className.indexOf("ui-datepicker-prev") !== -1) { $(this).removeClass("ui-datepicker-prev-hover"); } if (this.className.indexOf("ui-datepicker-next") !== -1) { $(this).removeClass("ui-datepicker-next-hover"); } }) .delegate(selector, "mouseover", function(){ if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) { $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); $(this).addClass("ui-state-hover"); if (this.className.indexOf("ui-datepicker-prev") !== -1) { $(this).addClass("ui-datepicker-prev-hover"); } if (this.className.indexOf("ui-datepicker-next") !== -1) { $(this).addClass("ui-datepicker-next-hover"); } } }); } /* jQuery extend now ignores nulls! */ function extendRemove(target, props) { $.extend(target, props); for (var name in props) { if (props[name] == null) { target[name] = props[name]; } } return target; } /* Invoke the datepicker functionality. @param options string - a command, optionally followed by additional parameters or Object - settings for attaching new datepicker functionality @return jQuery object */ $.fn.datepicker = function(options){ /* Verify an empty collection wasn't passed - Fixes #6976 */ if ( !this.length ) { return this; } /* Initialise the date picker. */ if (!$.datepicker.initialized) { $(document).mousedown($.datepicker._checkExternalClick); $.datepicker.initialized = true; } /* Append datepicker main container to body if not exist. */ if ($("#"+$.datepicker._mainDivId).length === 0) { $("body").append($.datepicker.dpDiv); } var otherArgs = Array.prototype.slice.call(arguments, 1); if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { return $.datepicker["_" + options + "Datepicker"]. apply($.datepicker, [this[0]].concat(otherArgs)); } if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { return $.datepicker["_" + options + "Datepicker"]. apply($.datepicker, [this[0]].concat(otherArgs)); } return this.each(function() { typeof options === "string" ? $.datepicker["_" + options + "Datepicker"]. apply($.datepicker, [this].concat(otherArgs)) : $.datepicker._attachDatepicker(this, options); }); }; $.datepicker = new Datepicker(); // singleton instance $.datepicker.initialized = false; $.datepicker.uuid = new Date().getTime(); $.datepicker.version = "1.10.3"; })(jQuery); (function( $, undefined ) { var sizeRelatedOptions = { buttons: true, height: true, maxHeight: true, maxWidth: true, minHeight: true, minWidth: true, width: true }, resizableRelatedOptions = { maxHeight: true, maxWidth: true, minHeight: true, minWidth: true }; $.widget( "ui.dialog", { version: "1.10.3", options: { appendTo: "body", autoOpen: true, buttons: [], closeOnEscape: true, closeText: "close", dialogClass: "", draggable: true, hide: null, height: "auto", maxHeight: null, maxWidth: null, minHeight: 150, minWidth: 150, modal: false, position: { my: "center", at: "center", of: window, collision: "fit", // Ensure the titlebar is always visible using: function( pos ) { var topOffset = $( this ).css( pos ).offset().top; if ( topOffset < 0 ) { $( this ).css( "top", pos.top - topOffset ); } } }, resizable: true, show: null, title: null, width: 300, // callbacks beforeClose: null, close: null, drag: null, dragStart: null, dragStop: null, focus: null, open: null, resize: null, resizeStart: null, resizeStop: null }, _create: function() { this.originalCss = { display: this.element[0].style.display, width: this.element[0].style.width, minHeight: this.element[0].style.minHeight, maxHeight: this.element[0].style.maxHeight, height: this.element[0].style.height }; this.originalPosition = { parent: this.element.parent(), index: this.element.parent().children().index( this.element ) }; this.originalTitle = this.element.attr("title"); this.options.title = this.options.title || this.originalTitle; this._createWrapper(); this.element .show() .removeAttr("title") .addClass("ui-dialog-content ui-widget-content") .appendTo( this.uiDialog ); this._createTitlebar(); this._createButtonPane(); if ( this.options.draggable && $.fn.draggable ) { this._makeDraggable(); } if ( this.options.resizable && $.fn.resizable ) { this._makeResizable(); } this._isOpen = false; }, _init: function() { if ( this.options.autoOpen ) { this.open(); } }, _appendTo: function() { var element = this.options.appendTo; if ( element && (element.jquery || element.nodeType) ) { return $( element ); } return this.document.find( element || "body" ).eq( 0 ); }, _destroy: function() { var next, originalPosition = this.originalPosition; this._destroyOverlay(); this.element .removeUniqueId() .removeClass("ui-dialog-content ui-widget-content") .css( this.originalCss ) // Without detaching first, the following becomes really slow .detach(); this.uiDialog.stop( true, true ).remove(); if ( this.originalTitle ) { this.element.attr( "title", this.originalTitle ); } next = originalPosition.parent.children().eq( originalPosition.index ); // Don't try to place the dialog next to itself (#8613) if ( next.length && next[0] !== this.element[0] ) { next.before( this.element ); } else { originalPosition.parent.append( this.element ); } }, widget: function() { return this.uiDialog; }, disable: $.noop, enable: $.noop, close: function( event ) { var that = this; if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { return; } this._isOpen = false; this._destroyOverlay(); if ( !this.opener.filter(":focusable").focus().length ) { // Hiding a focused element doesn't trigger blur in WebKit // so in case we have nothing to focus on, explicitly blur the active element // https://bugs.webkit.org/show_bug.cgi?id=47182 $( this.document[0].activeElement ).blur(); } this._hide( this.uiDialog, this.options.hide, function() { that._trigger( "close", event ); }); }, isOpen: function() { return this._isOpen; }, moveToTop: function() { this._moveToTop(); }, _moveToTop: function( event, silent ) { var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length; if ( moved && !silent ) { this._trigger( "focus", event ); } return moved; }, open: function() { var that = this; if ( this._isOpen ) { if ( this._moveToTop() ) { this._focusTabbable(); } return; } this._isOpen = true; this.opener = $( this.document[0].activeElement ); this._size(); this._position(); this._createOverlay(); this._moveToTop( null, true ); this._show( this.uiDialog, this.options.show, function() { that._focusTabbable(); that._trigger("focus"); }); this._trigger("open"); }, _focusTabbable: function() { // Set focus to the first match: // 1. First element inside the dialog matching [autofocus] // 2. Tabbable element inside the content element // 3. Tabbable element inside the buttonpane // 4. The close button // 5. The dialog itself var hasFocus = this.element.find("[autofocus]"); if ( !hasFocus.length ) { hasFocus = this.element.find(":tabbable"); } if ( !hasFocus.length ) { hasFocus = this.uiDialogButtonPane.find(":tabbable"); } if ( !hasFocus.length ) { hasFocus = this.uiDialogTitlebarClose.filter(":tabbable"); } if ( !hasFocus.length ) { hasFocus = this.uiDialog; } hasFocus.eq( 0 ).focus(); }, _keepFocus: function( event ) { function checkFocus() { var activeElement = this.document[0].activeElement, isActive = this.uiDialog[0] === activeElement || $.contains( this.uiDialog[0], activeElement ); if ( !isActive ) { this._focusTabbable(); } } event.preventDefault(); checkFocus.call( this ); // support: IE // IE <= 8 doesn't prevent moving focus even with event.preventDefault() // so we check again later this._delay( checkFocus ); }, _createWrapper: function() { this.uiDialog = $("
    ") .addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " + this.options.dialogClass ) .hide() .attr({ // Setting tabIndex makes the div focusable tabIndex: -1, role: "dialog" }) .appendTo( this._appendTo() ); this._on( this.uiDialog, { keydown: function( event ) { if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && event.keyCode === $.ui.keyCode.ESCAPE ) { event.preventDefault(); this.close( event ); return; } // prevent tabbing out of dialogs if ( event.keyCode !== $.ui.keyCode.TAB ) { return; } var tabbables = this.uiDialog.find(":tabbable"), first = tabbables.filter(":first"), last = tabbables.filter(":last"); if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) { first.focus( 1 ); event.preventDefault(); } else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) { last.focus( 1 ); event.preventDefault(); } }, mousedown: function( event ) { if ( this._moveToTop( event ) ) { this._focusTabbable(); } } }); // We assume that any existing aria-describedby attribute means // that the dialog content is marked up properly // otherwise we brute force the content as the description if ( !this.element.find("[aria-describedby]").length ) { this.uiDialog.attr({ "aria-describedby": this.element.uniqueId().attr("id") }); } }, _createTitlebar: function() { var uiDialogTitle; this.uiDialogTitlebar = $("
    ") .addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix") .prependTo( this.uiDialog ); this._on( this.uiDialogTitlebar, { mousedown: function( event ) { // Don't prevent click on close button (#8838) // Focusing a dialog that is partially scrolled out of view // causes the browser to scroll it into view, preventing the click event if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) { // Dialog isn't getting focus when dragging (#8063) this.uiDialog.focus(); } } }); this.uiDialogTitlebarClose = $("") .button({ label: this.options.closeText, icons: { primary: "ui-icon-closethick" }, text: false }) .addClass("ui-dialog-titlebar-close") .appendTo( this.uiDialogTitlebar ); this._on( this.uiDialogTitlebarClose, { click: function( event ) { event.preventDefault(); this.close( event ); } }); uiDialogTitle = $("") .uniqueId() .addClass("ui-dialog-title") .prependTo( this.uiDialogTitlebar ); this._title( uiDialogTitle ); this.uiDialog.attr({ "aria-labelledby": uiDialogTitle.attr("id") }); }, _title: function( title ) { if ( !this.options.title ) { title.html(" "); } title.text( this.options.title ); }, _createButtonPane: function() { this.uiDialogButtonPane = $("
    ") .addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"); this.uiButtonSet = $("
    ") .addClass("ui-dialog-buttonset") .appendTo( this.uiDialogButtonPane ); this._createButtons(); }, _createButtons: function() { var that = this, buttons = this.options.buttons; // if we already have a button pane, remove it this.uiDialogButtonPane.remove(); this.uiButtonSet.empty(); if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) { this.uiDialog.removeClass("ui-dialog-buttons"); return; } $.each( buttons, function( name, props ) { var click, buttonOptions; props = $.isFunction( props ) ? { click: props, text: name } : props; // Default to a non-submitting button props = $.extend( { type: "button" }, props ); // Change the context for the click callback to be the main element click = props.click; props.click = function() { click.apply( that.element[0], arguments ); }; buttonOptions = { icons: props.icons, text: props.showText }; delete props.icons; delete props.showText; $( "", props ) .button( buttonOptions ) .appendTo( that.uiButtonSet ); }); this.uiDialog.addClass("ui-dialog-buttons"); this.uiDialogButtonPane.appendTo( this.uiDialog ); }, _makeDraggable: function() { var that = this, options = this.options; function filteredUi( ui ) { return { position: ui.position, offset: ui.offset }; } this.uiDialog.draggable({ cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", handle: ".ui-dialog-titlebar", containment: "document", start: function( event, ui ) { $( this ).addClass("ui-dialog-dragging"); that._blockFrames(); that._trigger( "dragStart", event, filteredUi( ui ) ); }, drag: function( event, ui ) { that._trigger( "drag", event, filteredUi( ui ) ); }, stop: function( event, ui ) { options.position = [ ui.position.left - that.document.scrollLeft(), ui.position.top - that.document.scrollTop() ]; $( this ).removeClass("ui-dialog-dragging"); that._unblockFrames(); that._trigger( "dragStop", event, filteredUi( ui ) ); } }); }, _makeResizable: function() { var that = this, options = this.options, handles = options.resizable, // .ui-resizable has position: relative defined in the stylesheet // but dialogs have to use absolute or fixed positioning position = this.uiDialog.css("position"), resizeHandles = typeof handles === "string" ? handles : "n,e,s,w,se,sw,ne,nw"; function filteredUi( ui ) { return { originalPosition: ui.originalPosition, originalSize: ui.originalSize, position: ui.position, size: ui.size }; } this.uiDialog.resizable({ cancel: ".ui-dialog-content", containment: "document", alsoResize: this.element, maxWidth: options.maxWidth, maxHeight: options.maxHeight, minWidth: options.minWidth, minHeight: this._minHeight(), handles: resizeHandles, start: function( event, ui ) { $( this ).addClass("ui-dialog-resizing"); that._blockFrames(); that._trigger( "resizeStart", event, filteredUi( ui ) ); }, resize: function( event, ui ) { that._trigger( "resize", event, filteredUi( ui ) ); }, stop: function( event, ui ) { options.height = $( this ).height(); options.width = $( this ).width(); $( this ).removeClass("ui-dialog-resizing"); that._unblockFrames(); that._trigger( "resizeStop", event, filteredUi( ui ) ); } }) .css( "position", position ); }, _minHeight: function() { var options = this.options; return options.height === "auto" ? options.minHeight : Math.min( options.minHeight, options.height ); }, _position: function() { // Need to show the dialog to get the actual offset in the position plugin var isVisible = this.uiDialog.is(":visible"); if ( !isVisible ) { this.uiDialog.show(); } this.uiDialog.position( this.options.position ); if ( !isVisible ) { this.uiDialog.hide(); } }, _setOptions: function( options ) { var that = this, resize = false, resizableOptions = {}; $.each( options, function( key, value ) { that._setOption( key, value ); if ( key in sizeRelatedOptions ) { resize = true; } if ( key in resizableRelatedOptions ) { resizableOptions[ key ] = value; } }); if ( resize ) { this._size(); this._position(); } if ( this.uiDialog.is(":data(ui-resizable)") ) { this.uiDialog.resizable( "option", resizableOptions ); } }, _setOption: function( key, value ) { /*jshint maxcomplexity:15*/ var isDraggable, isResizable, uiDialog = this.uiDialog; if ( key === "dialogClass" ) { uiDialog .removeClass( this.options.dialogClass ) .addClass( value ); } if ( key === "disabled" ) { return; } this._super( key, value ); if ( key === "appendTo" ) { this.uiDialog.appendTo( this._appendTo() ); } if ( key === "buttons" ) { this._createButtons(); } if ( key === "closeText" ) { this.uiDialogTitlebarClose.button({ // Ensure that we always pass a string label: "" + value }); } if ( key === "draggable" ) { isDraggable = uiDialog.is(":data(ui-draggable)"); if ( isDraggable && !value ) { uiDialog.draggable("destroy"); } if ( !isDraggable && value ) { this._makeDraggable(); } } if ( key === "position" ) { this._position(); } if ( key === "resizable" ) { // currently resizable, becoming non-resizable isResizable = uiDialog.is(":data(ui-resizable)"); if ( isResizable && !value ) { uiDialog.resizable("destroy"); } // currently resizable, changing handles if ( isResizable && typeof value === "string" ) { uiDialog.resizable( "option", "handles", value ); } // currently non-resizable, becoming resizable if ( !isResizable && value !== false ) { this._makeResizable(); } } if ( key === "title" ) { this._title( this.uiDialogTitlebar.find(".ui-dialog-title") ); } }, _size: function() { // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content // divs will both have width and height set, so we need to reset them var nonContentHeight, minContentHeight, maxContentHeight, options = this.options; // Reset content sizing this.element.show().css({ width: "auto", minHeight: 0, maxHeight: "none", height: 0 }); if ( options.minWidth > options.width ) { options.width = options.minWidth; } // reset wrapper sizing // determine the height of all the non-content elements nonContentHeight = this.uiDialog.css({ height: "auto", width: options.width }) .outerHeight(); minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); maxContentHeight = typeof options.maxHeight === "number" ? Math.max( 0, options.maxHeight - nonContentHeight ) : "none"; if ( options.height === "auto" ) { this.element.css({ minHeight: minContentHeight, maxHeight: maxContentHeight, height: "auto" }); } else { this.element.height( Math.max( 0, options.height - nonContentHeight ) ); } if (this.uiDialog.is(":data(ui-resizable)") ) { this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); } }, _blockFrames: function() { this.iframeBlocks = this.document.find( "iframe" ).map(function() { var iframe = $( this ); return $( "
    " ) .css({ position: "absolute", width: iframe.outerWidth(), height: iframe.outerHeight() }) .appendTo( iframe.parent() ) .offset( iframe.offset() )[0]; }); }, _unblockFrames: function() { if ( this.iframeBlocks ) { this.iframeBlocks.remove(); delete this.iframeBlocks; } }, _allowInteraction: function( event ) { if ( $( event.target ).closest(".ui-dialog").length ) { return true; } // TODO: Remove hack when datepicker implements // the .ui-front logic (#8989) return !!$( event.target ).closest(".ui-datepicker").length; }, _createOverlay: function() { if ( !this.options.modal ) { return; } var that = this, widgetFullName = this.widgetFullName; if ( !$.ui.dialog.overlayInstances ) { // Prevent use of anchors and inputs. // We use a delay in case the overlay is created from an // event that we're going to be cancelling. (#2804) this._delay(function() { // Handle .dialog().dialog("close") (#4065) if ( $.ui.dialog.overlayInstances ) { this.document.bind( "focusin.dialog", function( event ) { if ( !that._allowInteraction( event ) ) { event.preventDefault(); $(".ui-dialog:visible:last .ui-dialog-content") .data( widgetFullName )._focusTabbable(); } }); } }); } this.overlay = $("
    ") .addClass("ui-widget-overlay ui-front") .appendTo( this._appendTo() ); this._on( this.overlay, { mousedown: "_keepFocus" }); $.ui.dialog.overlayInstances++; }, _destroyOverlay: function() { if ( !this.options.modal ) { return; } if ( this.overlay ) { $.ui.dialog.overlayInstances--; if ( !$.ui.dialog.overlayInstances ) { this.document.unbind( "focusin.dialog" ); } this.overlay.remove(); this.overlay = null; } } }); $.ui.dialog.overlayInstances = 0; // DEPRECATED if ( $.uiBackCompat !== false ) { // position option with array notation // just override with old implementation $.widget( "ui.dialog", $.ui.dialog, { _position: function() { var position = this.options.position, myAt = [], offset = [ 0, 0 ], isVisible; if ( position ) { if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) { myAt = position.split ? position.split(" ") : [ position[0], position[1] ]; if ( myAt.length === 1 ) { myAt[1] = myAt[0]; } $.each( [ "left", "top" ], function( i, offsetPosition ) { if ( +myAt[ i ] === myAt[ i ] ) { offset[ i ] = myAt[ i ]; myAt[ i ] = offsetPosition; } }); position = { my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " + myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]), at: myAt.join(" ") }; } position = $.extend( {}, $.ui.dialog.prototype.options.position, position ); } else { position = $.ui.dialog.prototype.options.position; } // need to show the dialog to get the actual offset in the position plugin isVisible = this.uiDialog.is(":visible"); if ( !isVisible ) { this.uiDialog.show(); } this.uiDialog.position( position ); if ( !isVisible ) { this.uiDialog.hide(); } } }); } }( jQuery ) ); (function( $, undefined ) { var rvertical = /up|down|vertical/, rpositivemotion = /up|left|vertical|horizontal/; $.effects.effect.blind = function( o, done ) { // Create element var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "height", "width" ], mode = $.effects.setMode( el, o.mode || "hide" ), direction = o.direction || "up", vertical = rvertical.test( direction ), ref = vertical ? "height" : "width", ref2 = vertical ? "top" : "left", motion = rpositivemotion.test( direction ), animation = {}, show = mode === "show", wrapper, distance, margin; // if already wrapped, the wrapper's properties are my property. #6245 if ( el.parent().is( ".ui-effects-wrapper" ) ) { $.effects.save( el.parent(), props ); } else { $.effects.save( el, props ); } el.show(); wrapper = $.effects.createWrapper( el ).css({ overflow: "hidden" }); distance = wrapper[ ref ](); margin = parseFloat( wrapper.css( ref2 ) ) || 0; animation[ ref ] = show ? distance : 0; if ( !motion ) { el .css( vertical ? "bottom" : "right", 0 ) .css( vertical ? "top" : "left", "auto" ) .css({ position: "absolute" }); animation[ ref2 ] = show ? margin : distance + margin; } // start at 0 if we are showing if ( show ) { wrapper.css( ref, 0 ); if ( ! motion ) { wrapper.css( ref2, margin + distance ); } } // Animate wrapper.animate( animation, { duration: o.duration, easing: o.easing, queue: false, complete: function() { if ( mode === "hide" ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); } }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.bounce = function( o, done ) { var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "height", "width" ], // defaults: mode = $.effects.setMode( el, o.mode || "effect" ), hide = mode === "hide", show = mode === "show", direction = o.direction || "up", distance = o.distance, times = o.times || 5, // number of internal animations anims = times * 2 + ( show || hide ? 1 : 0 ), speed = o.duration / anims, easing = o.easing, // utility: ref = ( direction === "up" || direction === "down" ) ? "top" : "left", motion = ( direction === "up" || direction === "left" ), i, upAnim, downAnim, // we will need to re-assemble the queue to stack our animations in place queue = el.queue(), queuelen = queue.length; // Avoid touching opacity to prevent clearType and PNG issues in IE if ( show || hide ) { props.push( "opacity" ); } $.effects.save( el, props ); el.show(); $.effects.createWrapper( el ); // Create Wrapper // default distance for the BIGGEST bounce is the outer Distance / 3 if ( !distance ) { distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3; } if ( show ) { downAnim = { opacity: 1 }; downAnim[ ref ] = 0; // if we are showing, force opacity 0 and set the initial position // then do the "first" animation el.css( "opacity", 0 ) .css( ref, motion ? -distance * 2 : distance * 2 ) .animate( downAnim, speed, easing ); } // start at the smallest distance if we are hiding if ( hide ) { distance = distance / Math.pow( 2, times - 1 ); } downAnim = {}; downAnim[ ref ] = 0; // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here for ( i = 0; i < times; i++ ) { upAnim = {}; upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; el.animate( upAnim, speed, easing ) .animate( downAnim, speed, easing ); distance = hide ? distance * 2 : distance / 2; } // Last Bounce when Hiding if ( hide ) { upAnim = { opacity: 0 }; upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance; el.animate( upAnim, speed, easing ); } el.queue(function() { if ( hide ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); }); // inject all the animations we just queued to be first in line (after "inprogress") if ( queuelen > 1) { queue.splice.apply( queue, [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); } el.dequeue(); }; })(jQuery); (function( $, undefined ) { $.effects.effect.clip = function( o, done ) { // Create element var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "height", "width" ], mode = $.effects.setMode( el, o.mode || "hide" ), show = mode === "show", direction = o.direction || "vertical", vert = direction === "vertical", size = vert ? "height" : "width", position = vert ? "top" : "left", animation = {}, wrapper, animate, distance; // Save & Show $.effects.save( el, props ); el.show(); // Create Wrapper wrapper = $.effects.createWrapper( el ).css({ overflow: "hidden" }); animate = ( el[0].tagName === "IMG" ) ? wrapper : el; distance = animate[ size ](); // Shift if ( show ) { animate.css( size, 0 ); animate.css( position, distance / 2 ); } // Create Animation Object: animation[ size ] = show ? distance : 0; animation[ position ] = show ? 0 : distance / 2; // Animate animate.animate( animation, { queue: false, duration: o.duration, easing: o.easing, complete: function() { if ( !show ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); } }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.drop = function( o, done ) { var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ], mode = $.effects.setMode( el, o.mode || "hide" ), show = mode === "show", direction = o.direction || "left", ref = ( direction === "up" || direction === "down" ) ? "top" : "left", motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg", animation = { opacity: show ? 1 : 0 }, distance; // Adjust $.effects.save( el, props ); el.show(); $.effects.createWrapper( el ); distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2; if ( show ) { el .css( "opacity", 0 ) .css( ref, motion === "pos" ? -distance : distance ); } // Animation animation[ ref ] = ( show ? ( motion === "pos" ? "+=" : "-=" ) : ( motion === "pos" ? "-=" : "+=" ) ) + distance; // Animate el.animate( animation, { queue: false, duration: o.duration, easing: o.easing, complete: function() { if ( mode === "hide" ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); } }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.explode = function( o, done ) { var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3, cells = rows, el = $( this ), mode = $.effects.setMode( el, o.mode || "hide" ), show = mode === "show", // show and then visibility:hidden the element before calculating offset offset = el.show().css( "visibility", "hidden" ).offset(), // width and height of a piece width = Math.ceil( el.outerWidth() / cells ), height = Math.ceil( el.outerHeight() / rows ), pieces = [], // loop i, j, left, top, mx, my; // children animate complete: function childComplete() { pieces.push( this ); if ( pieces.length === rows * cells ) { animComplete(); } } // clone the element for each row and cell. for( i = 0; i < rows ; i++ ) { // ===> top = offset.top + i * height; my = i - ( rows - 1 ) / 2 ; for( j = 0; j < cells ; j++ ) { // ||| left = offset.left + j * width; mx = j - ( cells - 1 ) / 2 ; // Create a clone of the now hidden main element that will be absolute positioned // within a wrapper div off the -left and -top equal to size of our pieces el .clone() .appendTo( "body" ) .wrap( "
    " ) .css({ position: "absolute", visibility: "visible", left: -j * width, top: -i * height }) // select the wrapper - make it overflow: hidden and absolute positioned based on // where the original was located +left and +top equal to the size of pieces .parent() .addClass( "ui-effects-explode" ) .css({ position: "absolute", overflow: "hidden", width: width, height: height, left: left + ( show ? mx * width : 0 ), top: top + ( show ? my * height : 0 ), opacity: show ? 0 : 1 }).animate({ left: left + ( show ? 0 : mx * width ), top: top + ( show ? 0 : my * height ), opacity: show ? 1 : 0 }, o.duration || 500, o.easing, childComplete ); } } function animComplete() { el.css({ visibility: "visible" }); $( pieces ).remove(); if ( !show ) { el.hide(); } done(); } }; })(jQuery); (function( $, undefined ) { $.effects.effect.fade = function( o, done ) { var el = $( this ), mode = $.effects.setMode( el, o.mode || "toggle" ); el.animate({ opacity: mode }, { queue: false, duration: o.duration, easing: o.easing, complete: done }); }; })( jQuery ); (function( $, undefined ) { $.effects.effect.fold = function( o, done ) { // Create element var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "height", "width" ], mode = $.effects.setMode( el, o.mode || "hide" ), show = mode === "show", hide = mode === "hide", size = o.size || 15, percent = /([0-9]+)%/.exec( size ), horizFirst = !!o.horizFirst, widthFirst = show !== horizFirst, ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ], duration = o.duration / 2, wrapper, distance, animation1 = {}, animation2 = {}; $.effects.save( el, props ); el.show(); // Create Wrapper wrapper = $.effects.createWrapper( el ).css({ overflow: "hidden" }); distance = widthFirst ? [ wrapper.width(), wrapper.height() ] : [ wrapper.height(), wrapper.width() ]; if ( percent ) { size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ]; } if ( show ) { wrapper.css( horizFirst ? { height: 0, width: size } : { height: size, width: 0 }); } // Animation animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size; animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0; // Animate wrapper .animate( animation1, duration, o.easing ) .animate( animation2, duration, o.easing, function() { if ( hide ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.highlight = function( o, done ) { var elem = $( this ), props = [ "backgroundImage", "backgroundColor", "opacity" ], mode = $.effects.setMode( elem, o.mode || "show" ), animation = { backgroundColor: elem.css( "backgroundColor" ) }; if (mode === "hide") { animation.opacity = 0; } $.effects.save( elem, props ); elem .show() .css({ backgroundImage: "none", backgroundColor: o.color || "#ffff99" }) .animate( animation, { queue: false, duration: o.duration, easing: o.easing, complete: function() { if ( mode === "hide" ) { elem.hide(); } $.effects.restore( elem, props ); done(); } }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.pulsate = function( o, done ) { var elem = $( this ), mode = $.effects.setMode( elem, o.mode || "show" ), show = mode === "show", hide = mode === "hide", showhide = ( show || mode === "hide" ), // showing or hiding leaves of the "last" animation anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ), duration = o.duration / anims, animateTo = 0, queue = elem.queue(), queuelen = queue.length, i; if ( show || !elem.is(":visible")) { elem.css( "opacity", 0 ).show(); animateTo = 1; } // anims - 1 opacity "toggles" for ( i = 1; i < anims; i++ ) { elem.animate({ opacity: animateTo }, duration, o.easing ); animateTo = 1 - animateTo; } elem.animate({ opacity: animateTo }, duration, o.easing); elem.queue(function() { if ( hide ) { elem.hide(); } done(); }); // We just queued up "anims" animations, we need to put them next in the queue if ( queuelen > 1 ) { queue.splice.apply( queue, [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); } elem.dequeue(); }; })(jQuery); (function( $, undefined ) { $.effects.effect.puff = function( o, done ) { var elem = $( this ), mode = $.effects.setMode( elem, o.mode || "hide" ), hide = mode === "hide", percent = parseInt( o.percent, 10 ) || 150, factor = percent / 100, original = { height: elem.height(), width: elem.width(), outerHeight: elem.outerHeight(), outerWidth: elem.outerWidth() }; $.extend( o, { effect: "scale", queue: false, fade: true, mode: mode, complete: done, percent: hide ? percent : 100, from: hide ? original : { height: original.height * factor, width: original.width * factor, outerHeight: original.outerHeight * factor, outerWidth: original.outerWidth * factor } }); elem.effect( o ); }; $.effects.effect.scale = function( o, done ) { // Create element var el = $( this ), options = $.extend( true, {}, o ), mode = $.effects.setMode( el, o.mode || "effect" ), percent = parseInt( o.percent, 10 ) || ( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ), direction = o.direction || "both", origin = o.origin, original = { height: el.height(), width: el.width(), outerHeight: el.outerHeight(), outerWidth: el.outerWidth() }, factor = { y: direction !== "horizontal" ? (percent / 100) : 1, x: direction !== "vertical" ? (percent / 100) : 1 }; // We are going to pass this effect to the size effect: options.effect = "size"; options.queue = false; options.complete = done; // Set default origin and restore for show/hide if ( mode !== "effect" ) { options.origin = origin || ["middle","center"]; options.restore = true; } options.from = o.from || ( mode === "show" ? { height: 0, width: 0, outerHeight: 0, outerWidth: 0 } : original ); options.to = { height: original.height * factor.y, width: original.width * factor.x, outerHeight: original.outerHeight * factor.y, outerWidth: original.outerWidth * factor.x }; // Fade option to support puff if ( options.fade ) { if ( mode === "show" ) { options.from.opacity = 0; options.to.opacity = 1; } if ( mode === "hide" ) { options.from.opacity = 1; options.to.opacity = 0; } } // Animate el.effect( options ); }; $.effects.effect.size = function( o, done ) { // Create element var original, baseline, factor, el = $( this ), props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ], // Always restore props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ], // Copy for children props2 = [ "width", "height", "overflow" ], cProps = [ "fontSize" ], vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ], hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ], // Set options mode = $.effects.setMode( el, o.mode || "effect" ), restore = o.restore || mode !== "effect", scale = o.scale || "both", origin = o.origin || [ "middle", "center" ], position = el.css( "position" ), props = restore ? props0 : props1, zero = { height: 0, width: 0, outerHeight: 0, outerWidth: 0 }; if ( mode === "show" ) { el.show(); } original = { height: el.height(), width: el.width(), outerHeight: el.outerHeight(), outerWidth: el.outerWidth() }; if ( o.mode === "toggle" && mode === "show" ) { el.from = o.to || zero; el.to = o.from || original; } else { el.from = o.from || ( mode === "show" ? zero : original ); el.to = o.to || ( mode === "hide" ? zero : original ); } // Set scaling factor factor = { from: { y: el.from.height / original.height, x: el.from.width / original.width }, to: { y: el.to.height / original.height, x: el.to.width / original.width } }; // Scale the css box if ( scale === "box" || scale === "both" ) { // Vertical props scaling if ( factor.from.y !== factor.to.y ) { props = props.concat( vProps ); el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from ); el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to ); } // Horizontal props scaling if ( factor.from.x !== factor.to.x ) { props = props.concat( hProps ); el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from ); el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to ); } } // Scale the content if ( scale === "content" || scale === "both" ) { // Vertical props scaling if ( factor.from.y !== factor.to.y ) { props = props.concat( cProps ).concat( props2 ); el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from ); el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to ); } } $.effects.save( el, props ); el.show(); $.effects.createWrapper( el ); el.css( "overflow", "hidden" ).css( el.from ); // Adjust if (origin) { // Calculate baseline shifts baseline = $.effects.getBaseline( origin, original ); el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y; el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x; el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y; el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x; } el.css( el.from ); // set top & left // Animate if ( scale === "content" || scale === "both" ) { // Scale the children // Add margins/font-size vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps); hProps = hProps.concat([ "marginLeft", "marginRight" ]); props2 = props0.concat(vProps).concat(hProps); el.find( "*[width]" ).each( function(){ var child = $( this ), c_original = { height: child.height(), width: child.width(), outerHeight: child.outerHeight(), outerWidth: child.outerWidth() }; if (restore) { $.effects.save(child, props2); } child.from = { height: c_original.height * factor.from.y, width: c_original.width * factor.from.x, outerHeight: c_original.outerHeight * factor.from.y, outerWidth: c_original.outerWidth * factor.from.x }; child.to = { height: c_original.height * factor.to.y, width: c_original.width * factor.to.x, outerHeight: c_original.height * factor.to.y, outerWidth: c_original.width * factor.to.x }; // Vertical props scaling if ( factor.from.y !== factor.to.y ) { child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from ); child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to ); } // Horizontal props scaling if ( factor.from.x !== factor.to.x ) { child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from ); child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to ); } // Animate children child.css( child.from ); child.animate( child.to, o.duration, o.easing, function() { // Restore children if ( restore ) { $.effects.restore( child, props2 ); } }); }); } // Animate el.animate( el.to, { queue: false, duration: o.duration, easing: o.easing, complete: function() { if ( el.to.opacity === 0 ) { el.css( "opacity", el.from.opacity ); } if( mode === "hide" ) { el.hide(); } $.effects.restore( el, props ); if ( !restore ) { // we need to calculate our new positioning based on the scaling if ( position === "static" ) { el.css({ position: "relative", top: el.to.top, left: el.to.left }); } else { $.each([ "top", "left" ], function( idx, pos ) { el.css( pos, function( _, str ) { var val = parseInt( str, 10 ), toRef = idx ? el.to.left : el.to.top; // if original was "auto", recalculate the new value from wrapper if ( str === "auto" ) { return toRef + "px"; } return val + toRef + "px"; }); }); } } $.effects.removeWrapper( el ); done(); } }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.shake = function( o, done ) { var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "height", "width" ], mode = $.effects.setMode( el, o.mode || "effect" ), direction = o.direction || "left", distance = o.distance || 20, times = o.times || 3, anims = times * 2 + 1, speed = Math.round(o.duration/anims), ref = (direction === "up" || direction === "down") ? "top" : "left", positiveMotion = (direction === "up" || direction === "left"), animation = {}, animation1 = {}, animation2 = {}, i, // we will need to re-assemble the queue to stack our animations in place queue = el.queue(), queuelen = queue.length; $.effects.save( el, props ); el.show(); $.effects.createWrapper( el ); // Animation animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance; animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2; animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2; // Animate el.animate( animation, speed, o.easing ); // Shakes for ( i = 1; i < times; i++ ) { el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing ); } el .animate( animation1, speed, o.easing ) .animate( animation, speed / 2, o.easing ) .queue(function() { if ( mode === "hide" ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); }); // inject all the animations we just queued to be first in line (after "inprogress") if ( queuelen > 1) { queue.splice.apply( queue, [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) ); } el.dequeue(); }; })(jQuery); (function( $, undefined ) { $.effects.effect.slide = function( o, done ) { // Create element var el = $( this ), props = [ "position", "top", "bottom", "left", "right", "width", "height" ], mode = $.effects.setMode( el, o.mode || "show" ), show = mode === "show", direction = o.direction || "left", ref = (direction === "up" || direction === "down") ? "top" : "left", positiveMotion = (direction === "up" || direction === "left"), distance, animation = {}; // Adjust $.effects.save( el, props ); el.show(); distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ); $.effects.createWrapper( el ).css({ overflow: "hidden" }); if ( show ) { el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance ); } // Animation animation[ ref ] = ( show ? ( positiveMotion ? "+=" : "-=") : ( positiveMotion ? "-=" : "+=")) + distance; // Animate el.animate( animation, { queue: false, duration: o.duration, easing: o.easing, complete: function() { if ( mode === "hide" ) { el.hide(); } $.effects.restore( el, props ); $.effects.removeWrapper( el ); done(); } }); }; })(jQuery); (function( $, undefined ) { $.effects.effect.transfer = function( o, done ) { var elem = $( this ), target = $( o.to ), targetFixed = target.css( "position" ) === "fixed", body = $("body"), fixTop = targetFixed ? body.scrollTop() : 0, fixLeft = targetFixed ? body.scrollLeft() : 0, endPosition = target.offset(), animation = { top: endPosition.top - fixTop , left: endPosition.left - fixLeft , height: target.innerHeight(), width: target.innerWidth() }, startPosition = elem.offset(), transfer = $( "
    " ) .appendTo( document.body ) .addClass( o.className ) .css({ top: startPosition.top - fixTop , left: startPosition.left - fixLeft , height: elem.innerHeight(), width: elem.innerWidth(), position: targetFixed ? "fixed" : "absolute" }) .animate( animation, o.duration, o.easing, function() { transfer.remove(); done(); }); }; })(jQuery); (function( $, undefined ) { $.widget( "ui.menu", { version: "1.10.3", defaultElement: "
      ", delay: 300, options: { icons: { submenu: "ui-icon-carat-1-e" }, menus: "ul", position: { my: "left top", at: "right top" }, role: "menu", // callbacks blur: null, focus: null, select: null }, _create: function() { this.activeMenu = this.element; // flag used to prevent firing of the click handler // as the event bubbles up through nested menus this.mouseHandled = false; this.element .uniqueId() .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) .attr({ role: this.options.role, tabIndex: 0 }) // need to catch all clicks on disabled menu // not possible through _on .bind( "click" + this.eventNamespace, $.proxy(function( event ) { if ( this.options.disabled ) { event.preventDefault(); } }, this )); if ( this.options.disabled ) { this.element .addClass( "ui-state-disabled" ) .attr( "aria-disabled", "true" ); } this._on({ // Prevent focus from sticking to links inside menu after clicking // them (focus should always stay on UL during navigation). "mousedown .ui-menu-item > a": function( event ) { event.preventDefault(); }, "click .ui-state-disabled > a": function( event ) { event.preventDefault(); }, "click .ui-menu-item:has(a)": function( event ) { var target = $( event.target ).closest( ".ui-menu-item" ); if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { this.mouseHandled = true; this.select( event ); // Open submenu on click if ( target.has( ".ui-menu" ).length ) { this.expand( event ); } else if ( !this.element.is( ":focus" ) ) { // Redirect focus to the menu this.element.trigger( "focus", [ true ] ); // If the active item is on the top level, let it stay active. // Otherwise, blur the active item since it is no longer visible. if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { clearTimeout( this.timer ); } } } }, "mouseenter .ui-menu-item": function( event ) { var target = $( event.currentTarget ); // Remove ui-state-active class from siblings of the newly focused menu item // to avoid a jump caused by adjacent elements both having a class with a border target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" ); this.focus( event, target ); }, mouseleave: "collapseAll", "mouseleave .ui-menu": "collapseAll", focus: function( event, keepActiveItem ) { // If there's already an active item, keep it active // If not, activate the first item var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 ); if ( !keepActiveItem ) { this.focus( event, item ); } }, blur: function( event ) { this._delay(function() { if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { this.collapseAll( event ); } }); }, keydown: "_keydown" }); this.refresh(); // Clicks outside of a menu collapse any open menus this._on( this.document, { click: function( event ) { if ( !$( event.target ).closest( ".ui-menu" ).length ) { this.collapseAll( event ); } // Reset the mouseHandled flag this.mouseHandled = false; } }); }, _destroy: function() { // Destroy (sub)menus this.element .removeAttr( "aria-activedescendant" ) .find( ".ui-menu" ).addBack() .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" ) .removeAttr( "role" ) .removeAttr( "tabIndex" ) .removeAttr( "aria-labelledby" ) .removeAttr( "aria-expanded" ) .removeAttr( "aria-hidden" ) .removeAttr( "aria-disabled" ) .removeUniqueId() .show(); // Destroy menu items this.element.find( ".ui-menu-item" ) .removeClass( "ui-menu-item" ) .removeAttr( "role" ) .removeAttr( "aria-disabled" ) .children( "a" ) .removeUniqueId() .removeClass( "ui-corner-all ui-state-hover" ) .removeAttr( "tabIndex" ) .removeAttr( "role" ) .removeAttr( "aria-haspopup" ) .children().each( function() { var elem = $( this ); if ( elem.data( "ui-menu-submenu-carat" ) ) { elem.remove(); } }); // Destroy menu dividers this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); }, _keydown: function( event ) { /*jshint maxcomplexity:20*/ var match, prev, character, skip, regex, preventDefault = true; function escape( value ) { return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); } switch ( event.keyCode ) { case $.ui.keyCode.PAGE_UP: this.previousPage( event ); break; case $.ui.keyCode.PAGE_DOWN: this.nextPage( event ); break; case $.ui.keyCode.HOME: this._move( "first", "first", event ); break; case $.ui.keyCode.END: this._move( "last", "last", event ); break; case $.ui.keyCode.UP: this.previous( event ); break; case $.ui.keyCode.DOWN: this.next( event ); break; case $.ui.keyCode.LEFT: this.collapse( event ); break; case $.ui.keyCode.RIGHT: if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { this.expand( event ); } break; case $.ui.keyCode.ENTER: case $.ui.keyCode.SPACE: this._activate( event ); break; case $.ui.keyCode.ESCAPE: this.collapse( event ); break; default: preventDefault = false; prev = this.previousFilter || ""; character = String.fromCharCode( event.keyCode ); skip = false; clearTimeout( this.filterTimer ); if ( character === prev ) { skip = true; } else { character = prev + character; } regex = new RegExp( "^" + escape( character ), "i" ); match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { return regex.test( $( this ).children( "a" ).text() ); }); match = skip && match.index( this.active.next() ) !== -1 ? this.active.nextAll( ".ui-menu-item" ) : match; // If no matches on the current filter, reset to the last character pressed // to move down the menu to the first item that starts with that character if ( !match.length ) { character = String.fromCharCode( event.keyCode ); regex = new RegExp( "^" + escape( character ), "i" ); match = this.activeMenu.children( ".ui-menu-item" ).filter(function() { return regex.test( $( this ).children( "a" ).text() ); }); } if ( match.length ) { this.focus( event, match ); if ( match.length > 1 ) { this.previousFilter = character; this.filterTimer = this._delay(function() { delete this.previousFilter; }, 1000 ); } else { delete this.previousFilter; } } else { delete this.previousFilter; } } if ( preventDefault ) { event.preventDefault(); } }, _activate: function( event ) { if ( !this.active.is( ".ui-state-disabled" ) ) { if ( this.active.children( "a[aria-haspopup='true']" ).length ) { this.expand( event ); } else { this.select( event ); } } }, refresh: function() { var menus, icon = this.options.icons.submenu, submenus = this.element.find( this.options.menus ); // Initialize nested menus submenus.filter( ":not(.ui-menu)" ) .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" ) .hide() .attr({ role: this.options.role, "aria-hidden": "true", "aria-expanded": "false" }) .each(function() { var menu = $( this ), item = menu.prev( "a" ), submenuCarat = $( "" ) .addClass( "ui-menu-icon ui-icon " + icon ) .data( "ui-menu-submenu-carat", true ); item .attr( "aria-haspopup", "true" ) .prepend( submenuCarat ); menu.attr( "aria-labelledby", item.attr( "id" ) ); }); menus = submenus.add( this.element ); // Don't refresh list items that are already adapted menus.children( ":not(.ui-menu-item):has(a)" ) .addClass( "ui-menu-item" ) .attr( "role", "presentation" ) .children( "a" ) .uniqueId() .addClass( "ui-corner-all" ) .attr({ tabIndex: -1, role: this._itemRole() }); // Initialize unlinked menu-items containing spaces and/or dashes only as dividers menus.children( ":not(.ui-menu-item)" ).each(function() { var item = $( this ); // hyphen, em dash, en dash if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) { item.addClass( "ui-widget-content ui-menu-divider" ); } }); // Add aria-disabled attribute to any disabled menu item menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); // If the active item has been removed, blur the menu if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { this.blur(); } }, _itemRole: function() { return { menu: "menuitem", listbox: "option" }[ this.options.role ]; }, _setOption: function( key, value ) { if ( key === "icons" ) { this.element.find( ".ui-menu-icon" ) .removeClass( this.options.icons.submenu ) .addClass( value.submenu ); } this._super( key, value ); }, focus: function( event, item ) { var nested, focused; this.blur( event, event && event.type === "focus" ); this._scrollIntoView( item ); this.active = item.first(); focused = this.active.children( "a" ).addClass( "ui-state-focus" ); // Only update aria-activedescendant if there's a role // otherwise we assume focus is managed elsewhere if ( this.options.role ) { this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); } // Highlight active parent menu item, if any this.active .parent() .closest( ".ui-menu-item" ) .children( "a:first" ) .addClass( "ui-state-active" ); if ( event && event.type === "keydown" ) { this._close(); } else { this.timer = this._delay(function() { this._close(); }, this.delay ); } nested = item.children( ".ui-menu" ); if ( nested.length && ( /^mouse/.test( event.type ) ) ) { this._startOpening(nested); } this.activeMenu = item.parent(); this._trigger( "focus", event, { item: item } ); }, _scrollIntoView: function( item ) { var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; if ( this._hasScroll() ) { borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; scroll = this.activeMenu.scrollTop(); elementHeight = this.activeMenu.height(); itemHeight = item.height(); if ( offset < 0 ) { this.activeMenu.scrollTop( scroll + offset ); } else if ( offset + itemHeight > elementHeight ) { this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); } } }, blur: function( event, fromFocus ) { if ( !fromFocus ) { clearTimeout( this.timer ); } if ( !this.active ) { return; } this.active.children( "a" ).removeClass( "ui-state-focus" ); this.active = null; this._trigger( "blur", event, { item: this.active } ); }, _startOpening: function( submenu ) { clearTimeout( this.timer ); // Don't open if already open fixes a Firefox bug that caused a .5 pixel // shift in the submenu position when mousing over the carat icon if ( submenu.attr( "aria-hidden" ) !== "true" ) { return; } this.timer = this._delay(function() { this._close(); this._open( submenu ); }, this.delay ); }, _open: function( submenu ) { var position = $.extend({ of: this.active }, this.options.position ); clearTimeout( this.timer ); this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) .hide() .attr( "aria-hidden", "true" ); submenu .show() .removeAttr( "aria-hidden" ) .attr( "aria-expanded", "true" ) .position( position ); }, collapseAll: function( event, all ) { clearTimeout( this.timer ); this.timer = this._delay(function() { // If we were passed an event, look for the submenu that contains the event var currentMenu = all ? this.element : $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway if ( !currentMenu.length ) { currentMenu = this.element; } this._close( currentMenu ); this.blur( event ); this.activeMenu = currentMenu; }, this.delay ); }, // With no arguments, closes the currently active menu - if nothing is active // it closes all menus. If passed an argument, it will search for menus BELOW _close: function( startMenu ) { if ( !startMenu ) { startMenu = this.active ? this.active.parent() : this.element; } startMenu .find( ".ui-menu" ) .hide() .attr( "aria-hidden", "true" ) .attr( "aria-expanded", "false" ) .end() .find( "a.ui-state-active" ) .removeClass( "ui-state-active" ); }, collapse: function( event ) { var newItem = this.active && this.active.parent().closest( ".ui-menu-item", this.element ); if ( newItem && newItem.length ) { this._close(); this.focus( event, newItem ); } }, expand: function( event ) { var newItem = this.active && this.active .children( ".ui-menu " ) .children( ".ui-menu-item" ) .first(); if ( newItem && newItem.length ) { this._open( newItem.parent() ); // Delay so Firefox will not hide activedescendant change in expanding submenu from AT this._delay(function() { this.focus( event, newItem ); }); } }, next: function( event ) { this._move( "next", "first", event ); }, previous: function( event ) { this._move( "prev", "last", event ); }, isFirstItem: function() { return this.active && !this.active.prevAll( ".ui-menu-item" ).length; }, isLastItem: function() { return this.active && !this.active.nextAll( ".ui-menu-item" ).length; }, _move: function( direction, filter, event ) { var next; if ( this.active ) { if ( direction === "first" || direction === "last" ) { next = this.active [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) .eq( -1 ); } else { next = this.active [ direction + "All" ]( ".ui-menu-item" ) .eq( 0 ); } } if ( !next || !next.length || !this.active ) { next = this.activeMenu.children( ".ui-menu-item" )[ filter ](); } this.focus( event, next ); }, nextPage: function( event ) { var item, base, height; if ( !this.active ) { this.next( event ); return; } if ( this.isLastItem() ) { return; } if ( this._hasScroll() ) { base = this.active.offset().top; height = this.element.height(); this.active.nextAll( ".ui-menu-item" ).each(function() { item = $( this ); return item.offset().top - base - height < 0; }); this.focus( event, item ); } else { this.focus( event, this.activeMenu.children( ".ui-menu-item" ) [ !this.active ? "first" : "last" ]() ); } }, previousPage: function( event ) { var item, base, height; if ( !this.active ) { this.next( event ); return; } if ( this.isFirstItem() ) { return; } if ( this._hasScroll() ) { base = this.active.offset().top; height = this.element.height(); this.active.prevAll( ".ui-menu-item" ).each(function() { item = $( this ); return item.offset().top - base + height > 0; }); this.focus( event, item ); } else { this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() ); } }, _hasScroll: function() { return this.element.outerHeight() < this.element.prop( "scrollHeight" ); }, select: function( event ) { // TODO: It should never be possible to not have an active item at this // point, but the tests don't trigger mouseenter before click. this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); var ui = { item: this.active }; if ( !this.active.has( ".ui-menu" ).length ) { this.collapseAll( event, true ); } this._trigger( "select", event, ui ); } }); }( jQuery )); (function( $, undefined ) { $.ui = $.ui || {}; var cachedScrollbarWidth, max = Math.max, abs = Math.abs, round = Math.round, rhorizontal = /left|center|right/, rvertical = /top|center|bottom/, roffset = /[\+\-]\d+(\.[\d]+)?%?/, rposition = /^\w+/, rpercent = /%$/, _position = $.fn.position; function getOffsets( offsets, width, height ) { return [ parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) ]; } function parseCss( element, property ) { return parseInt( $.css( element, property ), 10 ) || 0; } function getDimensions( elem ) { var raw = elem[0]; if ( raw.nodeType === 9 ) { return { width: elem.width(), height: elem.height(), offset: { top: 0, left: 0 } }; } if ( $.isWindow( raw ) ) { return { width: elem.width(), height: elem.height(), offset: { top: elem.scrollTop(), left: elem.scrollLeft() } }; } if ( raw.preventDefault ) { return { width: 0, height: 0, offset: { top: raw.pageY, left: raw.pageX } }; } return { width: elem.outerWidth(), height: elem.outerHeight(), offset: elem.offset() }; } $.position = { scrollbarWidth: function() { if ( cachedScrollbarWidth !== undefined ) { return cachedScrollbarWidth; } var w1, w2, div = $( "
      " ), innerDiv = div.children()[0]; $( "body" ).append( div ); w1 = innerDiv.offsetWidth; div.css( "overflow", "scroll" ); w2 = innerDiv.offsetWidth; if ( w1 === w2 ) { w2 = div[0].clientWidth; } div.remove(); return (cachedScrollbarWidth = w1 - w2); }, getScrollInfo: function( within ) { var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ), overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ), hasOverflowX = overflowX === "scroll" || ( overflowX === "auto" && within.width < within.element[0].scrollWidth ), hasOverflowY = overflowY === "scroll" || ( overflowY === "auto" && within.height < within.element[0].scrollHeight ); return { width: hasOverflowY ? $.position.scrollbarWidth() : 0, height: hasOverflowX ? $.position.scrollbarWidth() : 0 }; }, getWithinInfo: function( element ) { var withinElement = $( element || window ), isWindow = $.isWindow( withinElement[0] ); return { element: withinElement, isWindow: isWindow, offset: withinElement.offset() || { left: 0, top: 0 }, scrollLeft: withinElement.scrollLeft(), scrollTop: withinElement.scrollTop(), width: isWindow ? withinElement.width() : withinElement.outerWidth(), height: isWindow ? withinElement.height() : withinElement.outerHeight() }; } }; $.fn.position = function( options ) { if ( !options || !options.of ) { return _position.apply( this, arguments ); } // make a copy, we don't want to modify arguments options = $.extend( {}, options ); var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, target = $( options.of ), within = $.position.getWithinInfo( options.within ), scrollInfo = $.position.getScrollInfo( within ), collision = ( options.collision || "flip" ).split( " " ), offsets = {}; dimensions = getDimensions( target ); if ( target[0].preventDefault ) { // force left top to allow flipping options.at = "left top"; } targetWidth = dimensions.width; targetHeight = dimensions.height; targetOffset = dimensions.offset; // clone to reuse original targetOffset later basePosition = $.extend( {}, targetOffset ); // force my and at to have valid horizontal and vertical positions // if a value is missing or invalid, it will be converted to center $.each( [ "my", "at" ], function() { var pos = ( options[ this ] || "" ).split( " " ), horizontalOffset, verticalOffset; if ( pos.length === 1) { pos = rhorizontal.test( pos[ 0 ] ) ? pos.concat( [ "center" ] ) : rvertical.test( pos[ 0 ] ) ? [ "center" ].concat( pos ) : [ "center", "center" ]; } pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; // calculate offsets horizontalOffset = roffset.exec( pos[ 0 ] ); verticalOffset = roffset.exec( pos[ 1 ] ); offsets[ this ] = [ horizontalOffset ? horizontalOffset[ 0 ] : 0, verticalOffset ? verticalOffset[ 0 ] : 0 ]; // reduce to just the positions without the offsets options[ this ] = [ rposition.exec( pos[ 0 ] )[ 0 ], rposition.exec( pos[ 1 ] )[ 0 ] ]; }); // normalize collision option if ( collision.length === 1 ) { collision[ 1 ] = collision[ 0 ]; } if ( options.at[ 0 ] === "right" ) { basePosition.left += targetWidth; } else if ( options.at[ 0 ] === "center" ) { basePosition.left += targetWidth / 2; } if ( options.at[ 1 ] === "bottom" ) { basePosition.top += targetHeight; } else if ( options.at[ 1 ] === "center" ) { basePosition.top += targetHeight / 2; } atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); basePosition.left += atOffset[ 0 ]; basePosition.top += atOffset[ 1 ]; return this.each(function() { var collisionPosition, using, elem = $( this ), elemWidth = elem.outerWidth(), elemHeight = elem.outerHeight(), marginLeft = parseCss( this, "marginLeft" ), marginTop = parseCss( this, "marginTop" ), collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width, collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height, position = $.extend( {}, basePosition ), myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); if ( options.my[ 0 ] === "right" ) { position.left -= elemWidth; } else if ( options.my[ 0 ] === "center" ) { position.left -= elemWidth / 2; } if ( options.my[ 1 ] === "bottom" ) { position.top -= elemHeight; } else if ( options.my[ 1 ] === "center" ) { position.top -= elemHeight / 2; } position.left += myOffset[ 0 ]; position.top += myOffset[ 1 ]; // if the browser doesn't support fractions, then round for consistent results if ( !$.support.offsetFractions ) { position.left = round( position.left ); position.top = round( position.top ); } collisionPosition = { marginLeft: marginLeft, marginTop: marginTop }; $.each( [ "left", "top" ], function( i, dir ) { if ( $.ui.position[ collision[ i ] ] ) { $.ui.position[ collision[ i ] ][ dir ]( position, { targetWidth: targetWidth, targetHeight: targetHeight, elemWidth: elemWidth, elemHeight: elemHeight, collisionPosition: collisionPosition, collisionWidth: collisionWidth, collisionHeight: collisionHeight, offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], my: options.my, at: options.at, within: within, elem : elem }); } }); if ( options.using ) { // adds feedback as second argument to using callback, if present using = function( props ) { var left = targetOffset.left - position.left, right = left + targetWidth - elemWidth, top = targetOffset.top - position.top, bottom = top + targetHeight - elemHeight, feedback = { target: { element: target, left: targetOffset.left, top: targetOffset.top, width: targetWidth, height: targetHeight }, element: { element: elem, left: position.left, top: position.top, width: elemWidth, height: elemHeight }, horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" }; if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { feedback.horizontal = "center"; } if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { feedback.vertical = "middle"; } if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { feedback.important = "horizontal"; } else { feedback.important = "vertical"; } options.using.call( this, props, feedback ); }; } elem.offset( $.extend( position, { using: using } ) ); }); }; $.ui.position = { fit: { left: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, outerWidth = within.width, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = withinOffset - collisionPosLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, newOverRight; // element is wider than within if ( data.collisionWidth > outerWidth ) { // element is initially over the left side of within if ( overLeft > 0 && overRight <= 0 ) { newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset; position.left += overLeft - newOverRight; // element is initially over right side of within } else if ( overRight > 0 && overLeft <= 0 ) { position.left = withinOffset; // element is initially over both left and right sides of within } else { if ( overLeft > overRight ) { position.left = withinOffset + outerWidth - data.collisionWidth; } else { position.left = withinOffset; } } // too far left -> align with left edge } else if ( overLeft > 0 ) { position.left += overLeft; // too far right -> align with right edge } else if ( overRight > 0 ) { position.left -= overRight; // adjust based on position and margin } else { position.left = max( position.left - collisionPosLeft, position.left ); } }, top: function( position, data ) { var within = data.within, withinOffset = within.isWindow ? within.scrollTop : within.offset.top, outerHeight = data.within.height, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = withinOffset - collisionPosTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, newOverBottom; // element is taller than within if ( data.collisionHeight > outerHeight ) { // element is initially over the top of within if ( overTop > 0 && overBottom <= 0 ) { newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset; position.top += overTop - newOverBottom; // element is initially over bottom of within } else if ( overBottom > 0 && overTop <= 0 ) { position.top = withinOffset; // element is initially over both top and bottom of within } else { if ( overTop > overBottom ) { position.top = withinOffset + outerHeight - data.collisionHeight; } else { position.top = withinOffset; } } // too far up -> align with top } else if ( overTop > 0 ) { position.top += overTop; // too far down -> align with bottom edge } else if ( overBottom > 0 ) { position.top -= overBottom; // adjust based on position and margin } else { position.top = max( position.top - collisionPosTop, position.top ); } } }, flip: { left: function( position, data ) { var within = data.within, withinOffset = within.offset.left + within.scrollLeft, outerWidth = within.width, offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, collisionPosLeft = position.left - data.collisionPosition.marginLeft, overLeft = collisionPosLeft - offsetLeft, overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, myOffset = data.my[ 0 ] === "left" ? -data.elemWidth : data.my[ 0 ] === "right" ? data.elemWidth : 0, atOffset = data.at[ 0 ] === "left" ? data.targetWidth : data.at[ 0 ] === "right" ? -data.targetWidth : 0, offset = -2 * data.offset[ 0 ], newOverRight, newOverLeft; if ( overLeft < 0 ) { newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset; if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { position.left += myOffset + atOffset + offset; } } else if ( overRight > 0 ) { newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft; if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { position.left += myOffset + atOffset + offset; } } }, top: function( position, data ) { var within = data.within, withinOffset = within.offset.top + within.scrollTop, outerHeight = within.height, offsetTop = within.isWindow ? within.scrollTop : within.offset.top, collisionPosTop = position.top - data.collisionPosition.marginTop, overTop = collisionPosTop - offsetTop, overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, top = data.my[ 1 ] === "top", myOffset = top ? -data.elemHeight : data.my[ 1 ] === "bottom" ? data.elemHeight : 0, atOffset = data.at[ 1 ] === "top" ? data.targetHeight : data.at[ 1 ] === "bottom" ? -data.targetHeight : 0, offset = -2 * data.offset[ 1 ], newOverTop, newOverBottom; if ( overTop < 0 ) { newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset; if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) { position.top += myOffset + atOffset + offset; } } else if ( overBottom > 0 ) { newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop; if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) { position.top += myOffset + atOffset + offset; } } } }, flipfit: { left: function() { $.ui.position.flip.left.apply( this, arguments ); $.ui.position.fit.left.apply( this, arguments ); }, top: function() { $.ui.position.flip.top.apply( this, arguments ); $.ui.position.fit.top.apply( this, arguments ); } } }; // fraction support test (function () { var testElement, testElementParent, testElementStyle, offsetLeft, i, body = document.getElementsByTagName( "body" )[ 0 ], div = document.createElement( "div" ); //Create a "fake body" for testing based on method used in jQuery.support testElement = document.createElement( body ? "div" : "body" ); testElementStyle = { visibility: "hidden", width: 0, height: 0, border: 0, margin: 0, background: "none" }; if ( body ) { $.extend( testElementStyle, { position: "absolute", left: "-1000px", top: "-1000px" }); } for ( i in testElementStyle ) { testElement.style[ i ] = testElementStyle[ i ]; } testElement.appendChild( div ); testElementParent = body || document.documentElement; testElementParent.insertBefore( testElement, testElementParent.firstChild ); div.style.cssText = "position: absolute; left: 10.7432222px;"; offsetLeft = $( div ).offset().left; $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11; testElement.innerHTML = ""; testElementParent.removeChild( testElement ); })(); }( jQuery ) ); (function( $, undefined ) { $.widget( "ui.progressbar", { version: "1.10.3", options: { max: 100, value: 0, change: null, complete: null }, min: 0, _create: function() { // Constrain initial value this.oldValue = this.options.value = this._constrainedValue(); this.element .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) .attr({ // Only set static values, aria-valuenow and aria-valuemax are // set inside _refreshValue() role: "progressbar", "aria-valuemin": this.min }); this.valueDiv = $( "
      " ) .appendTo( this.element ); this._refreshValue(); }, _destroy: function() { this.element .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" ) .removeAttr( "role" ) .removeAttr( "aria-valuemin" ) .removeAttr( "aria-valuemax" ) .removeAttr( "aria-valuenow" ); this.valueDiv.remove(); }, value: function( newValue ) { if ( newValue === undefined ) { return this.options.value; } this.options.value = this._constrainedValue( newValue ); this._refreshValue(); }, _constrainedValue: function( newValue ) { if ( newValue === undefined ) { newValue = this.options.value; } this.indeterminate = newValue === false; // sanitize value if ( typeof newValue !== "number" ) { newValue = 0; } return this.indeterminate ? false : Math.min( this.options.max, Math.max( this.min, newValue ) ); }, _setOptions: function( options ) { // Ensure "value" option is set after other values (like max) var value = options.value; delete options.value; this._super( options ); this.options.value = this._constrainedValue( value ); this._refreshValue(); }, _setOption: function( key, value ) { if ( key === "max" ) { // Don't allow a max less than min value = Math.max( this.min, value ); } this._super( key, value ); }, _percentage: function() { return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min ); }, _refreshValue: function() { var value = this.options.value, percentage = this._percentage(); this.valueDiv .toggle( this.indeterminate || value > this.min ) .toggleClass( "ui-corner-right", value === this.options.max ) .width( percentage.toFixed(0) + "%" ); this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate ); if ( this.indeterminate ) { this.element.removeAttr( "aria-valuenow" ); if ( !this.overlayDiv ) { this.overlayDiv = $( "
      " ).appendTo( this.valueDiv ); } } else { this.element.attr({ "aria-valuemax": this.options.max, "aria-valuenow": value }); if ( this.overlayDiv ) { this.overlayDiv.remove(); this.overlayDiv = null; } } if ( this.oldValue !== value ) { this.oldValue = value; this._trigger( "change" ); } if ( value === this.options.max ) { this._trigger( "complete" ); } } }); })( jQuery ); (function( $, undefined ) { // number of pages in a slider // (how many times can you page up/down to go through the whole range) var numPages = 5; $.widget( "ui.slider", $.ui.mouse, { version: "1.10.3", widgetEventPrefix: "slide", options: { animate: false, distance: 0, max: 100, min: 0, orientation: "horizontal", range: false, step: 1, value: 0, values: null, // callbacks change: null, slide: null, start: null, stop: null }, _create: function() { this._keySliding = false; this._mouseSliding = false; this._animateOff = true; this._handleIndex = null; this._detectOrientation(); this._mouseInit(); this.element .addClass( "ui-slider" + " ui-slider-" + this.orientation + " ui-widget" + " ui-widget-content" + " ui-corner-all"); this._refresh(); this._setOption( "disabled", this.options.disabled ); this._animateOff = false; }, _refresh: function() { this._createRange(); this._createHandles(); this._setupEvents(); this._refreshValue(); }, _createHandles: function() { var i, handleCount, options = this.options, existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ), handle = "", handles = []; handleCount = ( options.values && options.values.length ) || 1; if ( existingHandles.length > handleCount ) { existingHandles.slice( handleCount ).remove(); existingHandles = existingHandles.slice( 0, handleCount ); } for ( i = existingHandles.length; i < handleCount; i++ ) { handles.push( handle ); } this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) ); this.handle = this.handles.eq( 0 ); this.handles.each(function( i ) { $( this ).data( "ui-slider-handle-index", i ); }); }, _createRange: function() { var options = this.options, classes = ""; if ( options.range ) { if ( options.range === true ) { if ( !options.values ) { options.values = [ this._valueMin(), this._valueMin() ]; } else if ( options.values.length && options.values.length !== 2 ) { options.values = [ options.values[0], options.values[0] ]; } else if ( $.isArray( options.values ) ) { options.values = options.values.slice(0); } } if ( !this.range || !this.range.length ) { this.range = $( "
      " ) .appendTo( this.element ); classes = "ui-slider-range" + // note: this isn't the most fittingly semantic framework class for this element, // but worked best visually with a variety of themes " ui-widget-header ui-corner-all"; } else { this.range.removeClass( "ui-slider-range-min ui-slider-range-max" ) // Handle range switching from true to min/max .css({ "left": "", "bottom": "" }); } this.range.addClass( classes + ( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) ); } else { this.range = $([]); } }, _setupEvents: function() { var elements = this.handles.add( this.range ).filter( "a" ); this._off( elements ); this._on( elements, this._handleEvents ); this._hoverable( elements ); this._focusable( elements ); }, _destroy: function() { this.handles.remove(); this.range.remove(); this.element .removeClass( "ui-slider" + " ui-slider-horizontal" + " ui-slider-vertical" + " ui-widget" + " ui-widget-content" + " ui-corner-all" ); this._mouseDestroy(); }, _mouseCapture: function( event ) { var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle, that = this, o = this.options; if ( o.disabled ) { return false; } this.elementSize = { width: this.element.outerWidth(), height: this.element.outerHeight() }; this.elementOffset = this.element.offset(); position = { x: event.pageX, y: event.pageY }; normValue = this._normValueFromMouse( position ); distance = this._valueMax() - this._valueMin() + 1; this.handles.each(function( i ) { var thisDistance = Math.abs( normValue - that.values(i) ); if (( distance > thisDistance ) || ( distance === thisDistance && (i === that._lastChangedValue || that.values(i) === o.min ))) { distance = thisDistance; closestHandle = $( this ); index = i; } }); allowed = this._start( event, index ); if ( allowed === false ) { return false; } this._mouseSliding = true; this._handleIndex = index; closestHandle .addClass( "ui-state-active" ) .focus(); offset = closestHandle.offset(); mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" ); this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { left: event.pageX - offset.left - ( closestHandle.width() / 2 ), top: event.pageY - offset.top - ( closestHandle.height() / 2 ) - ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) - ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) + ( parseInt( closestHandle.css("marginTop"), 10 ) || 0) }; if ( !this.handles.hasClass( "ui-state-hover" ) ) { this._slide( event, index, normValue ); } this._animateOff = true; return true; }, _mouseStart: function() { return true; }, _mouseDrag: function( event ) { var position = { x: event.pageX, y: event.pageY }, normValue = this._normValueFromMouse( position ); this._slide( event, this._handleIndex, normValue ); return false; }, _mouseStop: function( event ) { this.handles.removeClass( "ui-state-active" ); this._mouseSliding = false; this._stop( event, this._handleIndex ); this._change( event, this._handleIndex ); this._handleIndex = null; this._clickOffset = null; this._animateOff = false; return false; }, _detectOrientation: function() { this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; }, _normValueFromMouse: function( position ) { var pixelTotal, pixelMouse, percentMouse, valueTotal, valueMouse; if ( this.orientation === "horizontal" ) { pixelTotal = this.elementSize.width; pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 ); } else { pixelTotal = this.elementSize.height; pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 ); } percentMouse = ( pixelMouse / pixelTotal ); if ( percentMouse > 1 ) { percentMouse = 1; } if ( percentMouse < 0 ) { percentMouse = 0; } if ( this.orientation === "vertical" ) { percentMouse = 1 - percentMouse; } valueTotal = this._valueMax() - this._valueMin(); valueMouse = this._valueMin() + percentMouse * valueTotal; return this._trimAlignValue( valueMouse ); }, _start: function( event, index ) { var uiHash = { handle: this.handles[ index ], value: this.value() }; if ( this.options.values && this.options.values.length ) { uiHash.value = this.values( index ); uiHash.values = this.values(); } return this._trigger( "start", event, uiHash ); }, _slide: function( event, index, newVal ) { var otherVal, newValues, allowed; if ( this.options.values && this.options.values.length ) { otherVal = this.values( index ? 0 : 1 ); if ( ( this.options.values.length === 2 && this.options.range === true ) && ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) ) ) { newVal = otherVal; } if ( newVal !== this.values( index ) ) { newValues = this.values(); newValues[ index ] = newVal; // A slide can be canceled by returning false from the slide callback allowed = this._trigger( "slide", event, { handle: this.handles[ index ], value: newVal, values: newValues } ); otherVal = this.values( index ? 0 : 1 ); if ( allowed !== false ) { this.values( index, newVal, true ); } } } else { if ( newVal !== this.value() ) { // A slide can be canceled by returning false from the slide callback allowed = this._trigger( "slide", event, { handle: this.handles[ index ], value: newVal } ); if ( allowed !== false ) { this.value( newVal ); } } } }, _stop: function( event, index ) { var uiHash = { handle: this.handles[ index ], value: this.value() }; if ( this.options.values && this.options.values.length ) { uiHash.value = this.values( index ); uiHash.values = this.values(); } this._trigger( "stop", event, uiHash ); }, _change: function( event, index ) { if ( !this._keySliding && !this._mouseSliding ) { var uiHash = { handle: this.handles[ index ], value: this.value() }; if ( this.options.values && this.options.values.length ) { uiHash.value = this.values( index ); uiHash.values = this.values(); } //store the last changed value index for reference when handles overlap this._lastChangedValue = index; this._trigger( "change", event, uiHash ); } }, value: function( newValue ) { if ( arguments.length ) { this.options.value = this._trimAlignValue( newValue ); this._refreshValue(); this._change( null, 0 ); return; } return this._value(); }, values: function( index, newValue ) { var vals, newValues, i; if ( arguments.length > 1 ) { this.options.values[ index ] = this._trimAlignValue( newValue ); this._refreshValue(); this._change( null, index ); return; } if ( arguments.length ) { if ( $.isArray( arguments[ 0 ] ) ) { vals = this.options.values; newValues = arguments[ 0 ]; for ( i = 0; i < vals.length; i += 1 ) { vals[ i ] = this._trimAlignValue( newValues[ i ] ); this._change( null, i ); } this._refreshValue(); } else { if ( this.options.values && this.options.values.length ) { return this._values( index ); } else { return this.value(); } } } else { return this._values(); } }, _setOption: function( key, value ) { var i, valsLength = 0; if ( key === "range" && this.options.range === true ) { if ( value === "min" ) { this.options.value = this._values( 0 ); this.options.values = null; } else if ( value === "max" ) { this.options.value = this._values( this.options.values.length-1 ); this.options.values = null; } } if ( $.isArray( this.options.values ) ) { valsLength = this.options.values.length; } $.Widget.prototype._setOption.apply( this, arguments ); switch ( key ) { case "orientation": this._detectOrientation(); this.element .removeClass( "ui-slider-horizontal ui-slider-vertical" ) .addClass( "ui-slider-" + this.orientation ); this._refreshValue(); break; case "value": this._animateOff = true; this._refreshValue(); this._change( null, 0 ); this._animateOff = false; break; case "values": this._animateOff = true; this._refreshValue(); for ( i = 0; i < valsLength; i += 1 ) { this._change( null, i ); } this._animateOff = false; break; case "min": case "max": this._animateOff = true; this._refreshValue(); this._animateOff = false; break; case "range": this._animateOff = true; this._refresh(); this._animateOff = false; break; } }, //internal value getter // _value() returns value trimmed by min and max, aligned by step _value: function() { var val = this.options.value; val = this._trimAlignValue( val ); return val; }, //internal values getter // _values() returns array of values trimmed by min and max, aligned by step // _values( index ) returns single value trimmed by min and max, aligned by step _values: function( index ) { var val, vals, i; if ( arguments.length ) { val = this.options.values[ index ]; val = this._trimAlignValue( val ); return val; } else if ( this.options.values && this.options.values.length ) { // .slice() creates a copy of the array // this copy gets trimmed by min and max and then returned vals = this.options.values.slice(); for ( i = 0; i < vals.length; i+= 1) { vals[ i ] = this._trimAlignValue( vals[ i ] ); } return vals; } else { return []; } }, // returns the step-aligned value that val is closest to, between (inclusive) min and max _trimAlignValue: function( val ) { if ( val <= this._valueMin() ) { return this._valueMin(); } if ( val >= this._valueMax() ) { return this._valueMax(); } var step = ( this.options.step > 0 ) ? this.options.step : 1, valModStep = (val - this._valueMin()) % step, alignValue = val - valModStep; if ( Math.abs(valModStep) * 2 >= step ) { alignValue += ( valModStep > 0 ) ? step : ( -step ); } // Since JavaScript has problems with large floats, round // the final value to 5 digits after the decimal point (see #4124) return parseFloat( alignValue.toFixed(5) ); }, _valueMin: function() { return this.options.min; }, _valueMax: function() { return this.options.max; }, _refreshValue: function() { var lastValPercent, valPercent, value, valueMin, valueMax, oRange = this.options.range, o = this.options, that = this, animate = ( !this._animateOff ) ? o.animate : false, _set = {}; if ( this.options.values && this.options.values.length ) { this.handles.each(function( i ) { valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100; _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); if ( that.options.range === true ) { if ( that.orientation === "horizontal" ) { if ( i === 0 ) { that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate ); } if ( i === 1 ) { that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); } } else { if ( i === 0 ) { that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate ); } if ( i === 1 ) { that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); } } } lastValPercent = valPercent; }); } else { value = this.value(); valueMin = this._valueMin(); valueMax = this._valueMax(); valPercent = ( valueMax !== valueMin ) ? ( value - valueMin ) / ( valueMax - valueMin ) * 100 : 0; _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); if ( oRange === "min" && this.orientation === "horizontal" ) { this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate ); } if ( oRange === "max" && this.orientation === "horizontal" ) { this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); } if ( oRange === "min" && this.orientation === "vertical" ) { this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate ); } if ( oRange === "max" && this.orientation === "vertical" ) { this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); } } }, _handleEvents: { keydown: function( event ) { /*jshint maxcomplexity:25*/ var allowed, curVal, newVal, step, index = $( event.target ).data( "ui-slider-handle-index" ); switch ( event.keyCode ) { case $.ui.keyCode.HOME: case $.ui.keyCode.END: case $.ui.keyCode.PAGE_UP: case $.ui.keyCode.PAGE_DOWN: case $.ui.keyCode.UP: case $.ui.keyCode.RIGHT: case $.ui.keyCode.DOWN: case $.ui.keyCode.LEFT: event.preventDefault(); if ( !this._keySliding ) { this._keySliding = true; $( event.target ).addClass( "ui-state-active" ); allowed = this._start( event, index ); if ( allowed === false ) { return; } } break; } step = this.options.step; if ( this.options.values && this.options.values.length ) { curVal = newVal = this.values( index ); } else { curVal = newVal = this.value(); } switch ( event.keyCode ) { case $.ui.keyCode.HOME: newVal = this._valueMin(); break; case $.ui.keyCode.END: newVal = this._valueMax(); break; case $.ui.keyCode.PAGE_UP: newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) ); break; case $.ui.keyCode.PAGE_DOWN: newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) ); break; case $.ui.keyCode.UP: case $.ui.keyCode.RIGHT: if ( curVal === this._valueMax() ) { return; } newVal = this._trimAlignValue( curVal + step ); break; case $.ui.keyCode.DOWN: case $.ui.keyCode.LEFT: if ( curVal === this._valueMin() ) { return; } newVal = this._trimAlignValue( curVal - step ); break; } this._slide( event, index, newVal ); }, click: function( event ) { event.preventDefault(); }, keyup: function( event ) { var index = $( event.target ).data( "ui-slider-handle-index" ); if ( this._keySliding ) { this._keySliding = false; this._stop( event, index ); this._change( event, index ); $( event.target ).removeClass( "ui-state-active" ); } } } }); }(jQuery)); (function( $ ) { function modifier( fn ) { return function() { var previous = this.element.val(); fn.apply( this, arguments ); this._refresh(); if ( previous !== this.element.val() ) { this._trigger( "change" ); } }; } $.widget( "ui.spinner", { version: "1.10.3", defaultElement: "", widgetEventPrefix: "spin", options: { culture: null, icons: { down: "ui-icon-triangle-1-s", up: "ui-icon-triangle-1-n" }, incremental: true, max: null, min: null, numberFormat: null, page: 10, step: 1, change: null, spin: null, start: null, stop: null }, _create: function() { // handle string values that need to be parsed this._setOption( "max", this.options.max ); this._setOption( "min", this.options.min ); this._setOption( "step", this.options.step ); // format the value, but don't constrain this._value( this.element.val(), true ); this._draw(); this._on( this._events ); this._refresh(); // turning off autocomplete prevents the browser from remembering the // value when navigating through history, so we re-enable autocomplete // if the page is unloaded before the widget is destroyed. #7790 this._on( this.window, { beforeunload: function() { this.element.removeAttr( "autocomplete" ); } }); }, _getCreateOptions: function() { var options = {}, element = this.element; $.each( [ "min", "max", "step" ], function( i, option ) { var value = element.attr( option ); if ( value !== undefined && value.length ) { options[ option ] = value; } }); return options; }, _events: { keydown: function( event ) { if ( this._start( event ) && this._keydown( event ) ) { event.preventDefault(); } }, keyup: "_stop", focus: function() { this.previous = this.element.val(); }, blur: function( event ) { if ( this.cancelBlur ) { delete this.cancelBlur; return; } this._stop(); this._refresh(); if ( this.previous !== this.element.val() ) { this._trigger( "change", event ); } }, mousewheel: function( event, delta ) { if ( !delta ) { return; } if ( !this.spinning && !this._start( event ) ) { return false; } this._spin( (delta > 0 ? 1 : -1) * this.options.step, event ); clearTimeout( this.mousewheelTimer ); this.mousewheelTimer = this._delay(function() { if ( this.spinning ) { this._stop( event ); } }, 100 ); event.preventDefault(); }, "mousedown .ui-spinner-button": function( event ) { var previous; // We never want the buttons to have focus; whenever the user is // interacting with the spinner, the focus should be on the input. // If the input is focused then this.previous is properly set from // when the input first received focus. If the input is not focused // then we need to set this.previous based on the value before spinning. previous = this.element[0] === this.document[0].activeElement ? this.previous : this.element.val(); function checkFocus() { var isActive = this.element[0] === this.document[0].activeElement; if ( !isActive ) { this.element.focus(); this.previous = previous; // support: IE // IE sets focus asynchronously, so we need to check if focus // moved off of the input because the user clicked on the button. this._delay(function() { this.previous = previous; }); } } // ensure focus is on (or stays on) the text field event.preventDefault(); checkFocus.call( this ); // support: IE // IE doesn't prevent moving focus even with event.preventDefault() // so we set a flag to know when we should ignore the blur event // and check (again) if focus moved off of the input. this.cancelBlur = true; this._delay(function() { delete this.cancelBlur; checkFocus.call( this ); }); if ( this._start( event ) === false ) { return; } this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); }, "mouseup .ui-spinner-button": "_stop", "mouseenter .ui-spinner-button": function( event ) { // button will add ui-state-active if mouse was down while mouseleave and kept down if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) { return; } if ( this._start( event ) === false ) { return false; } this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event ); }, // TODO: do we really want to consider this a stop? // shouldn't we just stop the repeater and wait until mouseup before // we trigger the stop event? "mouseleave .ui-spinner-button": "_stop" }, _draw: function() { var uiSpinner = this.uiSpinner = this.element .addClass( "ui-spinner-input" ) .attr( "autocomplete", "off" ) .wrap( this._uiSpinnerHtml() ) .parent() // add buttons .append( this._buttonHtml() ); this.element.attr( "role", "spinbutton" ); // button bindings this.buttons = uiSpinner.find( ".ui-spinner-button" ) .attr( "tabIndex", -1 ) .button() .removeClass( "ui-corner-all" ); // IE 6 doesn't understand height: 50% for the buttons // unless the wrapper has an explicit height if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) && uiSpinner.height() > 0 ) { uiSpinner.height( uiSpinner.height() ); } // disable spinner if element was already disabled if ( this.options.disabled ) { this.disable(); } }, _keydown: function( event ) { var options = this.options, keyCode = $.ui.keyCode; switch ( event.keyCode ) { case keyCode.UP: this._repeat( null, 1, event ); return true; case keyCode.DOWN: this._repeat( null, -1, event ); return true; case keyCode.PAGE_UP: this._repeat( null, options.page, event ); return true; case keyCode.PAGE_DOWN: this._repeat( null, -options.page, event ); return true; } return false; }, _uiSpinnerHtml: function() { return ""; }, _buttonHtml: function() { return "" + "" + "▲" + "" + "" + "▼" + ""; }, _start: function( event ) { if ( !this.spinning && this._trigger( "start", event ) === false ) { return false; } if ( !this.counter ) { this.counter = 1; } this.spinning = true; return true; }, _repeat: function( i, steps, event ) { i = i || 500; clearTimeout( this.timer ); this.timer = this._delay(function() { this._repeat( 40, steps, event ); }, i ); this._spin( steps * this.options.step, event ); }, _spin: function( step, event ) { var value = this.value() || 0; if ( !this.counter ) { this.counter = 1; } value = this._adjustValue( value + step * this._increment( this.counter ) ); if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) { this._value( value ); this.counter++; } }, _increment: function( i ) { var incremental = this.options.incremental; if ( incremental ) { return $.isFunction( incremental ) ? incremental( i ) : Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 ); } return 1; }, _precision: function() { var precision = this._precisionOf( this.options.step ); if ( this.options.min !== null ) { precision = Math.max( precision, this._precisionOf( this.options.min ) ); } return precision; }, _precisionOf: function( num ) { var str = num.toString(), decimal = str.indexOf( "." ); return decimal === -1 ? 0 : str.length - decimal - 1; }, _adjustValue: function( value ) { var base, aboveMin, options = this.options; // make sure we're at a valid step // - find out where we are relative to the base (min or 0) base = options.min !== null ? options.min : 0; aboveMin = value - base; // - round to the nearest step aboveMin = Math.round(aboveMin / options.step) * options.step; // - rounding is based on 0, so adjust back to our base value = base + aboveMin; // fix precision from bad JS floating point math value = parseFloat( value.toFixed( this._precision() ) ); // clamp the value if ( options.max !== null && value > options.max) { return options.max; } if ( options.min !== null && value < options.min ) { return options.min; } return value; }, _stop: function( event ) { if ( !this.spinning ) { return; } clearTimeout( this.timer ); clearTimeout( this.mousewheelTimer ); this.counter = 0; this.spinning = false; this._trigger( "stop", event ); }, _setOption: function( key, value ) { if ( key === "culture" || key === "numberFormat" ) { var prevValue = this._parse( this.element.val() ); this.options[ key ] = value; this.element.val( this._format( prevValue ) ); return; } if ( key === "max" || key === "min" || key === "step" ) { if ( typeof value === "string" ) { value = this._parse( value ); } } if ( key === "icons" ) { this.buttons.first().find( ".ui-icon" ) .removeClass( this.options.icons.up ) .addClass( value.up ); this.buttons.last().find( ".ui-icon" ) .removeClass( this.options.icons.down ) .addClass( value.down ); } this._super( key, value ); if ( key === "disabled" ) { if ( value ) { this.element.prop( "disabled", true ); this.buttons.button( "disable" ); } else { this.element.prop( "disabled", false ); this.buttons.button( "enable" ); } } }, _setOptions: modifier(function( options ) { this._super( options ); this._value( this.element.val() ); }), _parse: function( val ) { if ( typeof val === "string" && val !== "" ) { val = window.Globalize && this.options.numberFormat ? Globalize.parseFloat( val, 10, this.options.culture ) : +val; } return val === "" || isNaN( val ) ? null : val; }, _format: function( value ) { if ( value === "" ) { return ""; } return window.Globalize && this.options.numberFormat ? Globalize.format( value, this.options.numberFormat, this.options.culture ) : value; }, _refresh: function() { this.element.attr({ "aria-valuemin": this.options.min, "aria-valuemax": this.options.max, // TODO: what should we do with values that can't be parsed? "aria-valuenow": this._parse( this.element.val() ) }); }, // update the value without triggering change _value: function( value, allowAny ) { var parsed; if ( value !== "" ) { parsed = this._parse( value ); if ( parsed !== null ) { if ( !allowAny ) { parsed = this._adjustValue( parsed ); } value = this._format( parsed ); } } this.element.val( value ); this._refresh(); }, _destroy: function() { this.element .removeClass( "ui-spinner-input" ) .prop( "disabled", false ) .removeAttr( "autocomplete" ) .removeAttr( "role" ) .removeAttr( "aria-valuemin" ) .removeAttr( "aria-valuemax" ) .removeAttr( "aria-valuenow" ); this.uiSpinner.replaceWith( this.element ); }, stepUp: modifier(function( steps ) { this._stepUp( steps ); }), _stepUp: function( steps ) { if ( this._start() ) { this._spin( (steps || 1) * this.options.step ); this._stop(); } }, stepDown: modifier(function( steps ) { this._stepDown( steps ); }), _stepDown: function( steps ) { if ( this._start() ) { this._spin( (steps || 1) * -this.options.step ); this._stop(); } }, pageUp: modifier(function( pages ) { this._stepUp( (pages || 1) * this.options.page ); }), pageDown: modifier(function( pages ) { this._stepDown( (pages || 1) * this.options.page ); }), value: function( newVal ) { if ( !arguments.length ) { return this._parse( this.element.val() ); } modifier( this._value ).call( this, newVal ); }, widget: function() { return this.uiSpinner; } }); }( jQuery ) ); (function( $, undefined ) { var tabId = 0, rhash = /#.*$/; function getNextTabId() { return ++tabId; } function isLocal( anchor ) { return anchor.hash.length > 1 && decodeURIComponent( anchor.href.replace( rhash, "" ) ) === decodeURIComponent( location.href.replace( rhash, "" ) ); } $.widget( "ui.tabs", { version: "1.10.3", delay: 300, options: { active: null, collapsible: false, event: "click", heightStyle: "content", hide: null, show: null, // callbacks activate: null, beforeActivate: null, beforeLoad: null, load: null }, _create: function() { var that = this, options = this.options; this.running = false; this.element .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ) .toggleClass( "ui-tabs-collapsible", options.collapsible ) // Prevent users from focusing disabled tabs via click .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) { if ( $( this ).is( ".ui-state-disabled" ) ) { event.preventDefault(); } }) // support: IE <9 // Preventing the default action in mousedown doesn't prevent IE // from focusing the element, so if the anchor gets focused, blur. // We don't have to worry about focusing the previously focused // element since clicking on a non-focusable element should focus // the body anyway. .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() { if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { this.blur(); } }); this._processTabs(); options.active = this._initialActive(); // Take disabling tabs via class attribute from HTML // into account and update option properly. if ( $.isArray( options.disabled ) ) { options.disabled = $.unique( options.disabled.concat( $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { return that.tabs.index( li ); }) ) ).sort(); } // check for length avoids error when initializing empty list if ( this.options.active !== false && this.anchors.length ) { this.active = this._findActive( options.active ); } else { this.active = $(); } this._refresh(); if ( this.active.length ) { this.load( options.active ); } }, _initialActive: function() { var active = this.options.active, collapsible = this.options.collapsible, locationHash = location.hash.substring( 1 ); if ( active === null ) { // check the fragment identifier in the URL if ( locationHash ) { this.tabs.each(function( i, tab ) { if ( $( tab ).attr( "aria-controls" ) === locationHash ) { active = i; return false; } }); } // check for a tab marked active via a class if ( active === null ) { active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); } // no active tab, set to false if ( active === null || active === -1 ) { active = this.tabs.length ? 0 : false; } } // handle numbers: negative, out of range if ( active !== false ) { active = this.tabs.index( this.tabs.eq( active ) ); if ( active === -1 ) { active = collapsible ? false : 0; } } // don't allow collapsible: false and active: false if ( !collapsible && active === false && this.anchors.length ) { active = 0; } return active; }, _getCreateEventData: function() { return { tab: this.active, panel: !this.active.length ? $() : this._getPanelForTab( this.active ) }; }, _tabKeydown: function( event ) { /*jshint maxcomplexity:15*/ var focusedTab = $( this.document[0].activeElement ).closest( "li" ), selectedIndex = this.tabs.index( focusedTab ), goingForward = true; if ( this._handlePageNav( event ) ) { return; } switch ( event.keyCode ) { case $.ui.keyCode.RIGHT: case $.ui.keyCode.DOWN: selectedIndex++; break; case $.ui.keyCode.UP: case $.ui.keyCode.LEFT: goingForward = false; selectedIndex--; break; case $.ui.keyCode.END: selectedIndex = this.anchors.length - 1; break; case $.ui.keyCode.HOME: selectedIndex = 0; break; case $.ui.keyCode.SPACE: // Activate only, no collapsing event.preventDefault(); clearTimeout( this.activating ); this._activate( selectedIndex ); return; case $.ui.keyCode.ENTER: // Toggle (cancel delayed activation, allow collapsing) event.preventDefault(); clearTimeout( this.activating ); // Determine if we should collapse or activate this._activate( selectedIndex === this.options.active ? false : selectedIndex ); return; default: return; } // Focus the appropriate tab, based on which key was pressed event.preventDefault(); clearTimeout( this.activating ); selectedIndex = this._focusNextTab( selectedIndex, goingForward ); // Navigating with control key will prevent automatic activation if ( !event.ctrlKey ) { // Update aria-selected immediately so that AT think the tab is already selected. // Otherwise AT may confuse the user by stating that they need to activate the tab, // but the tab will already be activated by the time the announcement finishes. focusedTab.attr( "aria-selected", "false" ); this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); this.activating = this._delay(function() { this.option( "active", selectedIndex ); }, this.delay ); } }, _panelKeydown: function( event ) { if ( this._handlePageNav( event ) ) { return; } // Ctrl+up moves focus to the current tab if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { event.preventDefault(); this.active.focus(); } }, // Alt+page up/down moves focus to the previous/next tab (and activates) _handlePageNav: function( event ) { if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { this._activate( this._focusNextTab( this.options.active - 1, false ) ); return true; } if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { this._activate( this._focusNextTab( this.options.active + 1, true ) ); return true; } }, _findNextTab: function( index, goingForward ) { var lastTabIndex = this.tabs.length - 1; function constrain() { if ( index > lastTabIndex ) { index = 0; } if ( index < 0 ) { index = lastTabIndex; } return index; } while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { index = goingForward ? index + 1 : index - 1; } return index; }, _focusNextTab: function( index, goingForward ) { index = this._findNextTab( index, goingForward ); this.tabs.eq( index ).focus(); return index; }, _setOption: function( key, value ) { if ( key === "active" ) { // _activate() will handle invalid values and update this.options this._activate( value ); return; } if ( key === "disabled" ) { // don't use the widget factory's disabled handling this._setupDisabled( value ); return; } this._super( key, value); if ( key === "collapsible" ) { this.element.toggleClass( "ui-tabs-collapsible", value ); // Setting collapsible: false while collapsed; open first panel if ( !value && this.options.active === false ) { this._activate( 0 ); } } if ( key === "event" ) { this._setupEvents( value ); } if ( key === "heightStyle" ) { this._setupHeightStyle( value ); } }, _tabId: function( tab ) { return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); }, _sanitizeSelector: function( hash ) { return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; }, refresh: function() { var options = this.options, lis = this.tablist.children( ":has(a[href])" ); // get disabled tabs from class attribute from HTML // this will get converted to a boolean if needed in _refresh() options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { return lis.index( tab ); }); this._processTabs(); // was collapsed or no tabs if ( options.active === false || !this.anchors.length ) { options.active = false; this.active = $(); // was active, but active tab is gone } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { // all remaining tabs are disabled if ( this.tabs.length === options.disabled.length ) { options.active = false; this.active = $(); // activate previous tab } else { this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); } // was active, active tab still exists } else { // make sure active index is correct options.active = this.tabs.index( this.active ); } this._refresh(); }, _refresh: function() { this._setupDisabled( this.options.disabled ); this._setupEvents( this.options.event ); this._setupHeightStyle( this.options.heightStyle ); this.tabs.not( this.active ).attr({ "aria-selected": "false", tabIndex: -1 }); this.panels.not( this._getPanelForTab( this.active ) ) .hide() .attr({ "aria-expanded": "false", "aria-hidden": "true" }); // Make sure one tab is in the tab order if ( !this.active.length ) { this.tabs.eq( 0 ).attr( "tabIndex", 0 ); } else { this.active .addClass( "ui-tabs-active ui-state-active" ) .attr({ "aria-selected": "true", tabIndex: 0 }); this._getPanelForTab( this.active ) .show() .attr({ "aria-expanded": "true", "aria-hidden": "false" }); } }, _processTabs: function() { var that = this; this.tablist = this._getList() .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) .attr( "role", "tablist" ); this.tabs = this.tablist.find( "> li:has(a[href])" ) .addClass( "ui-state-default ui-corner-top" ) .attr({ role: "tab", tabIndex: -1 }); this.anchors = this.tabs.map(function() { return $( "a", this )[ 0 ]; }) .addClass( "ui-tabs-anchor" ) .attr({ role: "presentation", tabIndex: -1 }); this.panels = $(); this.anchors.each(function( i, anchor ) { var selector, panel, panelId, anchorId = $( anchor ).uniqueId().attr( "id" ), tab = $( anchor ).closest( "li" ), originalAriaControls = tab.attr( "aria-controls" ); // inline tab if ( isLocal( anchor ) ) { selector = anchor.hash; panel = that.element.find( that._sanitizeSelector( selector ) ); // remote tab } else { panelId = that._tabId( tab ); selector = "#" + panelId; panel = that.element.find( selector ); if ( !panel.length ) { panel = that._createPanel( panelId ); panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); } panel.attr( "aria-live", "polite" ); } if ( panel.length) { that.panels = that.panels.add( panel ); } if ( originalAriaControls ) { tab.data( "ui-tabs-aria-controls", originalAriaControls ); } tab.attr({ "aria-controls": selector.substring( 1 ), "aria-labelledby": anchorId }); panel.attr( "aria-labelledby", anchorId ); }); this.panels .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) .attr( "role", "tabpanel" ); }, // allow overriding how to find the list for rare usage scenarios (#7715) _getList: function() { return this.element.find( "ol,ul" ).eq( 0 ); }, _createPanel: function( id ) { return $( "
      " ) .attr( "id", id ) .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) .data( "ui-tabs-destroy", true ); }, _setupDisabled: function( disabled ) { if ( $.isArray( disabled ) ) { if ( !disabled.length ) { disabled = false; } else if ( disabled.length === this.anchors.length ) { disabled = true; } } // disable tabs for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) { if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { $( li ) .addClass( "ui-state-disabled" ) .attr( "aria-disabled", "true" ); } else { $( li ) .removeClass( "ui-state-disabled" ) .removeAttr( "aria-disabled" ); } } this.options.disabled = disabled; }, _setupEvents: function( event ) { var events = { click: function( event ) { event.preventDefault(); } }; if ( event ) { $.each( event.split(" "), function( index, eventName ) { events[ eventName ] = "_eventHandler"; }); } this._off( this.anchors.add( this.tabs ).add( this.panels ) ); this._on( this.anchors, events ); this._on( this.tabs, { keydown: "_tabKeydown" } ); this._on( this.panels, { keydown: "_panelKeydown" } ); this._focusable( this.tabs ); this._hoverable( this.tabs ); }, _setupHeightStyle: function( heightStyle ) { var maxHeight, parent = this.element.parent(); if ( heightStyle === "fill" ) { maxHeight = parent.height(); maxHeight -= this.element.outerHeight() - this.element.height(); this.element.siblings( ":visible" ).each(function() { var elem = $( this ), position = elem.css( "position" ); if ( position === "absolute" || position === "fixed" ) { return; } maxHeight -= elem.outerHeight( true ); }); this.element.children().not( this.panels ).each(function() { maxHeight -= $( this ).outerHeight( true ); }); this.panels.each(function() { $( this ).height( Math.max( 0, maxHeight - $( this ).innerHeight() + $( this ).height() ) ); }) .css( "overflow", "auto" ); } else if ( heightStyle === "auto" ) { maxHeight = 0; this.panels.each(function() { maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); }).height( maxHeight ); } }, _eventHandler: function( event ) { var options = this.options, active = this.active, anchor = $( event.currentTarget ), tab = anchor.closest( "li" ), clickedIsActive = tab[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, toShow = collapsing ? $() : this._getPanelForTab( tab ), toHide = !active.length ? $() : this._getPanelForTab( active ), eventData = { oldTab: active, oldPanel: toHide, newTab: collapsing ? $() : tab, newPanel: toShow }; event.preventDefault(); if ( tab.hasClass( "ui-state-disabled" ) || // tab is already loading tab.hasClass( "ui-tabs-loading" ) || // can't switch durning an animation this.running || // click on active header, but not collapsible ( clickedIsActive && !options.collapsible ) || // allow canceling activation ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { return; } options.active = collapsing ? false : this.tabs.index( tab ); this.active = clickedIsActive ? $() : tab; if ( this.xhr ) { this.xhr.abort(); } if ( !toHide.length && !toShow.length ) { $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); } if ( toShow.length ) { this.load( this.tabs.index( tab ), event ); } this._toggle( event, eventData ); }, // handles show/hide for selecting tabs _toggle: function( event, eventData ) { var that = this, toShow = eventData.newPanel, toHide = eventData.oldPanel; this.running = true; function complete() { that.running = false; that._trigger( "activate", event, eventData ); } function show() { eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); if ( toShow.length && that.options.show ) { that._show( toShow, that.options.show, complete ); } else { toShow.show(); complete(); } } // start out by hiding, then showing, then completing if ( toHide.length && this.options.hide ) { this._hide( toHide, this.options.hide, function() { eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); show(); }); } else { eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); toHide.hide(); show(); } toHide.attr({ "aria-expanded": "false", "aria-hidden": "true" }); eventData.oldTab.attr( "aria-selected", "false" ); // If we're switching tabs, remove the old tab from the tab order. // If we're opening from collapsed state, remove the previous tab from the tab order. // If we're collapsing, then keep the collapsing tab in the tab order. if ( toShow.length && toHide.length ) { eventData.oldTab.attr( "tabIndex", -1 ); } else if ( toShow.length ) { this.tabs.filter(function() { return $( this ).attr( "tabIndex" ) === 0; }) .attr( "tabIndex", -1 ); } toShow.attr({ "aria-expanded": "true", "aria-hidden": "false" }); eventData.newTab.attr({ "aria-selected": "true", tabIndex: 0 }); }, _activate: function( index ) { var anchor, active = this._findActive( index ); // trying to activate the already active panel if ( active[ 0 ] === this.active[ 0 ] ) { return; } // trying to collapse, simulate a click on the current active header if ( !active.length ) { active = this.active; } anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; this._eventHandler({ target: anchor, currentTarget: anchor, preventDefault: $.noop }); }, _findActive: function( index ) { return index === false ? $() : this.tabs.eq( index ); }, _getIndex: function( index ) { // meta-function to give users option to provide a href string instead of a numerical index. if ( typeof index === "string" ) { index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); } return index; }, _destroy: function() { if ( this.xhr ) { this.xhr.abort(); } this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ); this.tablist .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) .removeAttr( "role" ); this.anchors .removeClass( "ui-tabs-anchor" ) .removeAttr( "role" ) .removeAttr( "tabIndex" ) .removeUniqueId(); this.tabs.add( this.panels ).each(function() { if ( $.data( this, "ui-tabs-destroy" ) ) { $( this ).remove(); } else { $( this ) .removeClass( "ui-state-default ui-state-active ui-state-disabled " + "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" ) .removeAttr( "tabIndex" ) .removeAttr( "aria-live" ) .removeAttr( "aria-busy" ) .removeAttr( "aria-selected" ) .removeAttr( "aria-labelledby" ) .removeAttr( "aria-hidden" ) .removeAttr( "aria-expanded" ) .removeAttr( "role" ); } }); this.tabs.each(function() { var li = $( this ), prev = li.data( "ui-tabs-aria-controls" ); if ( prev ) { li .attr( "aria-controls", prev ) .removeData( "ui-tabs-aria-controls" ); } else { li.removeAttr( "aria-controls" ); } }); this.panels.show(); if ( this.options.heightStyle !== "content" ) { this.panels.css( "height", "" ); } }, enable: function( index ) { var disabled = this.options.disabled; if ( disabled === false ) { return; } if ( index === undefined ) { disabled = false; } else { index = this._getIndex( index ); if ( $.isArray( disabled ) ) { disabled = $.map( disabled, function( num ) { return num !== index ? num : null; }); } else { disabled = $.map( this.tabs, function( li, num ) { return num !== index ? num : null; }); } } this._setupDisabled( disabled ); }, disable: function( index ) { var disabled = this.options.disabled; if ( disabled === true ) { return; } if ( index === undefined ) { disabled = true; } else { index = this._getIndex( index ); if ( $.inArray( index, disabled ) !== -1 ) { return; } if ( $.isArray( disabled ) ) { disabled = $.merge( [ index ], disabled ).sort(); } else { disabled = [ index ]; } } this._setupDisabled( disabled ); }, load: function( index, event ) { index = this._getIndex( index ); var that = this, tab = this.tabs.eq( index ), anchor = tab.find( ".ui-tabs-anchor" ), panel = this._getPanelForTab( tab ), eventData = { tab: tab, panel: panel }; // not remote if ( isLocal( anchor[ 0 ] ) ) { return; } this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); // support: jQuery <1.8 // jQuery <1.8 returns false if the request is canceled in beforeSend, // but as of 1.8, $.ajax() always returns a jqXHR object. if ( this.xhr && this.xhr.statusText !== "canceled" ) { tab.addClass( "ui-tabs-loading" ); panel.attr( "aria-busy", "true" ); this.xhr .success(function( response ) { // support: jQuery <1.8 // http://bugs.jquery.com/ticket/11778 setTimeout(function() { panel.html( response ); that._trigger( "load", event, eventData ); }, 1 ); }) .complete(function( jqXHR, status ) { // support: jQuery <1.8 // http://bugs.jquery.com/ticket/11778 setTimeout(function() { if ( status === "abort" ) { that.panels.stop( false, true ); } tab.removeClass( "ui-tabs-loading" ); panel.removeAttr( "aria-busy" ); if ( jqXHR === that.xhr ) { delete that.xhr; } }, 1 ); }); } }, _ajaxSettings: function( anchor, event, eventData ) { var that = this; return { url: anchor.attr( "href" ), beforeSend: function( jqXHR, settings ) { return that._trigger( "beforeLoad", event, $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) ); } }; }, _getPanelForTab: function( tab ) { var id = $( tab ).attr( "aria-controls" ); return this.element.find( this._sanitizeSelector( "#" + id ) ); } }); })( jQuery ); (function( $ ) { var increments = 0; function addDescribedBy( elem, id ) { var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ); describedby.push( id ); elem .data( "ui-tooltip-id", id ) .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); } function removeDescribedBy( elem ) { var id = elem.data( "ui-tooltip-id" ), describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ), index = $.inArray( id, describedby ); if ( index !== -1 ) { describedby.splice( index, 1 ); } elem.removeData( "ui-tooltip-id" ); describedby = $.trim( describedby.join( " " ) ); if ( describedby ) { elem.attr( "aria-describedby", describedby ); } else { elem.removeAttr( "aria-describedby" ); } } $.widget( "ui.tooltip", { version: "1.10.3", options: { content: function() { // support: IE<9, Opera in jQuery <1.7 // .text() can't accept undefined, so coerce to a string var title = $( this ).attr( "title" ) || ""; // Escape title, since we're going from an attribute to raw HTML return $( "" ).text( title ).html(); }, hide: true, // Disabled elements have inconsistent behavior across browsers (#8661) items: "[title]:not([disabled])", position: { my: "left top+15", at: "left bottom", collision: "flipfit flip" }, show: true, tooltipClass: null, track: false, // callbacks close: null, open: null }, _create: function() { this._on({ mouseover: "open", focusin: "open" }); // IDs of generated tooltips, needed for destroy this.tooltips = {}; // IDs of parent tooltips where we removed the title attribute this.parents = {}; if ( this.options.disabled ) { this._disable(); } }, _setOption: function( key, value ) { var that = this; if ( key === "disabled" ) { this[ value ? "_disable" : "_enable" ](); this.options[ key ] = value; // disable element style changes return; } this._super( key, value ); if ( key === "content" ) { $.each( this.tooltips, function( id, element ) { that._updateContent( element ); }); } }, _disable: function() { var that = this; // close open tooltips $.each( this.tooltips, function( id, element ) { var event = $.Event( "blur" ); event.target = event.currentTarget = element[0]; that.close( event, true ); }); // remove title attributes to prevent native tooltips this.element.find( this.options.items ).addBack().each(function() { var element = $( this ); if ( element.is( "[title]" ) ) { element .data( "ui-tooltip-title", element.attr( "title" ) ) .attr( "title", "" ); } }); }, _enable: function() { // restore title attributes this.element.find( this.options.items ).addBack().each(function() { var element = $( this ); if ( element.data( "ui-tooltip-title" ) ) { element.attr( "title", element.data( "ui-tooltip-title" ) ); } }); }, open: function( event ) { var that = this, target = $( event ? event.target : this.element ) // we need closest here due to mouseover bubbling, // but always pointing at the same event target .closest( this.options.items ); // No element to show a tooltip for or the tooltip is already open if ( !target.length || target.data( "ui-tooltip-id" ) ) { return; } if ( target.attr( "title" ) ) { target.data( "ui-tooltip-title", target.attr( "title" ) ); } target.data( "ui-tooltip-open", true ); // kill parent tooltips, custom or native, for hover if ( event && event.type === "mouseover" ) { target.parents().each(function() { var parent = $( this ), blurEvent; if ( parent.data( "ui-tooltip-open" ) ) { blurEvent = $.Event( "blur" ); blurEvent.target = blurEvent.currentTarget = this; that.close( blurEvent, true ); } if ( parent.attr( "title" ) ) { parent.uniqueId(); that.parents[ this.id ] = { element: this, title: parent.attr( "title" ) }; parent.attr( "title", "" ); } }); } this._updateContent( target, event ); }, _updateContent: function( target, event ) { var content, contentOption = this.options.content, that = this, eventType = event ? event.type : null; if ( typeof contentOption === "string" ) { return this._open( event, target, contentOption ); } content = contentOption.call( target[0], function( response ) { // ignore async response if tooltip was closed already if ( !target.data( "ui-tooltip-open" ) ) { return; } // IE may instantly serve a cached response for ajax requests // delay this call to _open so the other call to _open runs first that._delay(function() { // jQuery creates a special event for focusin when it doesn't // exist natively. To improve performance, the native event // object is reused and the type is changed. Therefore, we can't // rely on the type being correct after the event finished // bubbling, so we set it back to the previous value. (#8740) if ( event ) { event.type = eventType; } this._open( event, target, response ); }); }); if ( content ) { this._open( event, target, content ); } }, _open: function( event, target, content ) { var tooltip, events, delayedShow, positionOption = $.extend( {}, this.options.position ); if ( !content ) { return; } // Content can be updated multiple times. If the tooltip already // exists, then just update the content and bail. tooltip = this._find( target ); if ( tooltip.length ) { tooltip.find( ".ui-tooltip-content" ).html( content ); return; } // if we have a title, clear it to prevent the native tooltip // we have to check first to avoid defining a title if none exists // (we don't want to cause an element to start matching [title]) // // We use removeAttr only for key events, to allow IE to export the correct // accessible attributes. For mouse events, set to empty string to avoid // native tooltip showing up (happens only when removing inside mouseover). if ( target.is( "[title]" ) ) { if ( event && event.type === "mouseover" ) { target.attr( "title", "" ); } else { target.removeAttr( "title" ); } } tooltip = this._tooltip( target ); addDescribedBy( target, tooltip.attr( "id" ) ); tooltip.find( ".ui-tooltip-content" ).html( content ); function position( event ) { positionOption.of = event; if ( tooltip.is( ":hidden" ) ) { return; } tooltip.position( positionOption ); } if ( this.options.track && event && /^mouse/.test( event.type ) ) { this._on( this.document, { mousemove: position }); // trigger once to override element-relative positioning position( event ); } else { tooltip.position( $.extend({ of: target }, this.options.position ) ); } tooltip.hide(); this._show( tooltip, this.options.show ); // Handle tracking tooltips that are shown with a delay (#8644). As soon // as the tooltip is visible, position the tooltip using the most recent // event. if ( this.options.show && this.options.show.delay ) { delayedShow = this.delayedShow = setInterval(function() { if ( tooltip.is( ":visible" ) ) { position( positionOption.of ); clearInterval( delayedShow ); } }, $.fx.interval ); } this._trigger( "open", event, { tooltip: tooltip } ); events = { keyup: function( event ) { if ( event.keyCode === $.ui.keyCode.ESCAPE ) { var fakeEvent = $.Event(event); fakeEvent.currentTarget = target[0]; this.close( fakeEvent, true ); } }, remove: function() { this._removeTooltip( tooltip ); } }; if ( !event || event.type === "mouseover" ) { events.mouseleave = "close"; } if ( !event || event.type === "focusin" ) { events.focusout = "close"; } this._on( true, target, events ); }, close: function( event ) { var that = this, target = $( event ? event.currentTarget : this.element ), tooltip = this._find( target ); // disabling closes the tooltip, so we need to track when we're closing // to avoid an infinite loop in case the tooltip becomes disabled on close if ( this.closing ) { return; } // Clear the interval for delayed tracking tooltips clearInterval( this.delayedShow ); // only set title if we had one before (see comment in _open()) if ( target.data( "ui-tooltip-title" ) ) { target.attr( "title", target.data( "ui-tooltip-title" ) ); } removeDescribedBy( target ); tooltip.stop( true ); this._hide( tooltip, this.options.hide, function() { that._removeTooltip( $( this ) ); }); target.removeData( "ui-tooltip-open" ); this._off( target, "mouseleave focusout keyup" ); // Remove 'remove' binding only on delegated targets if ( target[0] !== this.element[0] ) { this._off( target, "remove" ); } this._off( this.document, "mousemove" ); if ( event && event.type === "mouseleave" ) { $.each( this.parents, function( id, parent ) { $( parent.element ).attr( "title", parent.title ); delete that.parents[ id ]; }); } this.closing = true; this._trigger( "close", event, { tooltip: tooltip } ); this.closing = false; }, _tooltip: function( element ) { var id = "ui-tooltip-" + increments++, tooltip = $( "
      " ) .attr({ id: id, role: "tooltip" }) .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " + ( this.options.tooltipClass || "" ) ); $( "
      " ) .addClass( "ui-tooltip-content" ) .appendTo( tooltip ); tooltip.appendTo( this.document[0].body ); this.tooltips[ id ] = element; return tooltip; }, _find: function( target ) { var id = target.data( "ui-tooltip-id" ); return id ? $( "#" + id ) : $(); }, _removeTooltip: function( tooltip ) { tooltip.remove(); delete this.tooltips[ tooltip.attr( "id" ) ]; }, _destroy: function() { var that = this; // close open tooltips $.each( this.tooltips, function( id, element ) { // Delegate to close method to handle common cleanup var event = $.Event( "blur" ); event.target = event.currentTarget = element[0]; that.close( event, true ); // Remove immediately; destroying an open tooltip doesn't use the // hide animation $( "#" + id ).remove(); // Restore the title if ( element.data( "ui-tooltip-title" ) ) { element.attr( "title", element.data( "ui-tooltip-title" ) ); element.removeData( "ui-tooltip-title" ); } }); } }); }( jQuery ) ); jquery-ui.min.js000066400000000000000000007140151325274564300356230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/* jQuery UI - v1.10.3 - 2013-05-03 * http://jqueryui.com * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.effect.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ (function(b,f){var a=0,e=/^ui-id-\d+$/;b.ui=b.ui||{};b.extend(b.ui,{version:"1.10.3",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}});b.fn.extend({focus:(function(g){return function(h,i){return typeof h==="number"?this.each(function(){var j=this;setTimeout(function(){b(j).focus();if(i){i.call(j)}},h)}):g.apply(this,arguments)}})(b.fn.focus),scrollParent:function(){var g;if((b.ui.ie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){g=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(b.css(this,"position"))&&(/(auto|scroll)/).test(b.css(this,"overflow")+b.css(this,"overflow-y")+b.css(this,"overflow-x"))}).eq(0)}else{g=this.parents().filter(function(){return(/(auto|scroll)/).test(b.css(this,"overflow")+b.css(this,"overflow-y")+b.css(this,"overflow-x"))}).eq(0)}return(/fixed/).test(this.css("position"))||!g.length?b(document):g},zIndex:function(j){if(j!==f){return this.css("zIndex",j)}if(this.length){var h=b(this[0]),g,i;while(h.length&&h[0]!==document){g=h.css("position");if(g==="absolute"||g==="relative"||g==="fixed"){i=parseInt(h.css("zIndex"),10);if(!isNaN(i)&&i!==0){return i}}h=h.parent()}}return 0},uniqueId:function(){return this.each(function(){if(!this.id){this.id="ui-id-"+(++a)}})},removeUniqueId:function(){return this.each(function(){if(e.test(this.id)){b(this).removeAttr("id")}})}});function d(i,g){var k,j,h,l=i.nodeName.toLowerCase();if("area"===l){k=i.parentNode;j=k.name;if(!i.href||!j||k.nodeName.toLowerCase()!=="map"){return false}h=b("img[usemap=#"+j+"]")[0];return !!h&&c(h)}return(/input|select|textarea|button|object/.test(l)?!i.disabled:"a"===l?i.href||g:g)&&c(i)}function c(g){return b.expr.filters.visible(g)&&!b(g).parents().addBack().filter(function(){return b.css(this,"visibility")==="hidden"}).length}b.extend(b.expr[":"],{data:b.expr.createPseudo?b.expr.createPseudo(function(g){return function(h){return !!b.data(h,g)}}):function(j,h,g){return !!b.data(j,g[3])},focusable:function(g){return d(g,!isNaN(b.attr(g,"tabindex")))},tabbable:function(i){var g=b.attr(i,"tabindex"),h=isNaN(g);return(h||g>=0)&&d(i,!h)}});if(!b("").outerWidth(1).jquery){b.each(["Width","Height"],function(j,g){var h=g==="Width"?["Left","Right"]:["Top","Bottom"],k=g.toLowerCase(),m={innerWidth:b.fn.innerWidth,innerHeight:b.fn.innerHeight,outerWidth:b.fn.outerWidth,outerHeight:b.fn.outerHeight};function l(o,n,i,p){b.each(h,function(){n-=parseFloat(b.css(o,"padding"+this))||0;if(i){n-=parseFloat(b.css(o,"border"+this+"Width"))||0}if(p){n-=parseFloat(b.css(o,"margin"+this))||0}});return n}b.fn["inner"+g]=function(i){if(i===f){return m["inner"+g].call(this)}return this.each(function(){b(this).css(k,l(this,i)+"px")})};b.fn["outer"+g]=function(i,n){if(typeof i!=="number"){return m["outer"+g].call(this,i)}return this.each(function(){b(this).css(k,l(this,i,true,n)+"px")})}})}if(!b.fn.addBack){b.fn.addBack=function(g){return this.add(g==null?this.prevObject:this.prevObject.filter(g))}}if(b("").data("a-b","a").removeData("a-b").data("a-b")){b.fn.removeData=(function(g){return function(h){if(arguments.length){return g.call(this,b.camelCase(h))}else{return g.call(this)}}})(b.fn.removeData)}b.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());b.support.selectstart="onselectstart" in document.createElement("div");b.fn.extend({disableSelection:function(){return this.bind((b.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(g){g.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});b.extend(b.ui,{plugin:{add:function(h,j,l){var g,k=b.ui[h].prototype;for(g in l){k.plugins[g]=k.plugins[g]||[];k.plugins[g].push([j,l[g]])}},call:function(g,j,h){var k,l=g.plugins[j];if(!l||!g.element[0].parentNode||g.element[0].parentNode.nodeType===11){return}for(k=0;k0){return true}j[g]=1;i=(j[g]>0);j[g]=0;return i}})})(jQuery);(function(b,e){var a=0,d=Array.prototype.slice,c=b.cleanData;b.cleanData=function(f){for(var g=0,h;(h=f[g])!=null;g++){try{b(h).triggerHandler("remove")}catch(j){}}c(f)};b.widget=function(f,g,n){var k,l,i,m,h={},j=f.split(".")[0];f=f.split(".")[1];k=j+"-"+f;if(!n){n=g;g=b.Widget}b.expr[":"][k.toLowerCase()]=function(o){return !!b.data(o,k)};b[j]=b[j]||{};l=b[j][f];i=b[j][f]=function(o,p){if(!this._createWidget){return new i(o,p)}if(arguments.length){this._createWidget(o,p)}};b.extend(i,l,{version:n.version,_proto:b.extend({},n),_childConstructors:[]});m=new g();m.options=b.widget.extend({},m.options);b.each(n,function(p,o){if(!b.isFunction(o)){h[p]=o;return}h[p]=(function(){var q=function(){return g.prototype[p].apply(this,arguments)},r=function(s){return g.prototype[p].apply(this,s)};return function(){var u=this._super,s=this._superApply,t;this._super=q;this._superApply=r;t=o.apply(this,arguments);this._super=u;this._superApply=s;return t}})()});i.prototype=b.widget.extend(m,{widgetEventPrefix:l?m.widgetEventPrefix:f},h,{constructor:i,namespace:j,widgetName:f,widgetFullName:k});if(l){b.each(l._childConstructors,function(p,q){var o=q.prototype;b.widget(o.namespace+"."+o.widgetName,i,q._proto)});delete l._childConstructors}else{g._childConstructors.push(i)}b.widget.bridge(f,i)};b.widget.extend=function(k){var g=d.call(arguments,1),j=0,f=g.length,h,i;for(;j",options:{disabled:false,create:null},_createWidget:function(f,g){g=b(g||this.defaultElement||this)[0];this.element=b(g);this.uuid=a++;this.eventNamespace="."+this.widgetName+this.uuid;this.options=b.widget.extend({},this.options,this._getCreateOptions(),f);this.bindings=b();this.hoverable=b();this.focusable=b();if(g!==this){b.data(g,this.widgetFullName,this);this._on(true,this.element,{remove:function(h){if(h.target===g){this.destroy()}}});this.document=b(g.style?g.ownerDocument:g.document||g);this.window=b(this.document[0].defaultView||this.document[0].parentWindow)}this._create();this._trigger("create",null,this._getCreateEventData());this._init()},_getCreateOptions:b.noop,_getCreateEventData:b.noop,_create:b.noop,_init:b.noop,destroy:function(){this._destroy();this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(b.camelCase(this.widgetFullName));this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled");this.bindings.unbind(this.eventNamespace);this.hoverable.removeClass("ui-state-hover");this.focusable.removeClass("ui-state-focus")},_destroy:b.noop,widget:function(){return this.element},option:function(j,k){var f=j,l,h,g;if(arguments.length===0){return b.widget.extend({},this.options)}if(typeof j==="string"){f={};l=j.split(".");j=l.shift();if(l.length){h=f[j]=b.widget.extend({},this.options[j]);for(g=0;g=this.options.distance)},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);(function(a,b){a.widget("ui.draggable",a.ui.mouse,{version:"1.10.3",widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false,drag:null,start:null,stop:null},_create:function(){if(this.options.helper==="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}if(this.options.addClasses){this.element.addClass("ui-draggable")}if(this.options.disabled){this.element.addClass("ui-draggable-disabled")}this._mouseInit()},_destroy:function(){this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(c){var d=this.options;if(this.helper||d.disabled||a(c.target).closest(".ui-resizable-handle").length>0){return false}this.handle=this._getHandle(c);if(!this.handle){return false}a(d.iframeFix===true?"iframe":d.iframeFix).each(function(){a("
      ").css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")});return true},_mouseStart:function(c){var d=this.options;this.helper=this._createHelper(c);this.helper.addClass("ui-draggable-dragging");this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offsetParent=this.helper.offsetParent();this.offsetParentCssPosition=this.offsetParent.css("position");this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.offset.scroll=false;a.extend(this.offset,{click:{left:c.pageX-this.offset.left,top:c.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this.position=this._generatePosition(c);this.originalPageX=c.pageX;this.originalPageY=c.pageY;(d.cursorAt&&this._adjustOffsetFromHelper(d.cursorAt));this._setContainment();if(this._trigger("start",c)===false){this._clear();return false}this._cacheHelperProportions();if(a.ui.ddmanager&&!d.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,c)}this._mouseDrag(c,true);if(a.ui.ddmanager){a.ui.ddmanager.dragStart(this,c)}return true},_mouseDrag:function(c,e){if(this.offsetParentCssPosition==="fixed"){this.offset.parent=this._getParentOffset()}this.position=this._generatePosition(c);this.positionAbs=this._convertPositionTo("absolute");if(!e){var d=this._uiHash();if(this._trigger("drag",c,d)===false){this._mouseUp({});return false}this.position=d.position}if(!this.options.axis||this.options.axis!=="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!=="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,c)}return false},_mouseStop:function(d){var c=this,e=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){e=a.ui.ddmanager.drop(this,d)}if(this.dropped){e=this.dropped;this.dropped=false}if(this.options.helper==="original"&&!a.contains(this.element[0].ownerDocument,this.element[0])){return false}if((this.options.revert==="invalid"&&!e)||(this.options.revert==="valid"&&e)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,e))){a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){if(c._trigger("stop",d)!==false){c._clear()}})}else{if(this._trigger("stop",d)!==false){this._clear()}}return false},_mouseUp:function(c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});if(a.ui.ddmanager){a.ui.ddmanager.dragStop(this,c)}return a.ui.mouse.prototype._mouseUp.call(this,c)},cancel:function(){if(this.helper.is(".ui-draggable-dragging")){this._mouseUp({})}else{this._clear()}return this},_getHandle:function(c){return this.options.handle?!!a(c.target).closest(this.element.find(this.options.handle)).length:true},_createHelper:function(d){var e=this.options,c=a.isFunction(e.helper)?a(e.helper.apply(this.element[0],[d])):(e.helper==="clone"?this.element.clone().removeAttr("id"):this.element);if(!c.parents("body").length){c.appendTo((e.appendTo==="parent"?this.element[0].parentNode:e.appendTo))}if(c[0]!==this.element[0]&&!(/(fixed|absolute)/).test(c.css("position"))){c.css("position","absolute")}return c},_adjustOffsetFromHelper:function(c){if(typeof c==="string"){c=c.split(" ")}if(a.isArray(c)){c={left:+c[0],top:+c[1]||0}}if("left" in c){this.offset.click.left=c.left+this.margins.left}if("right" in c){this.offset.click.left=this.helperProportions.width-c.right+this.margins.left}if("top" in c){this.offset.click.top=c.top+this.margins.top}if("bottom" in c){this.offset.click.top=this.helperProportions.height-c.bottom+this.margins.top}},_getParentOffset:function(){var c=this.offsetParent.offset();if(this.cssPosition==="absolute"&&this.scrollParent[0]!==document&&a.contains(this.scrollParent[0],this.offsetParent[0])){c.left+=this.scrollParent.scrollLeft();c.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]===document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()==="html"&&a.ui.ie)){c={top:0,left:0}}return{top:c.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:c.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition==="relative"){var c=this.element.position();return{top:c.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:c.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0),right:(parseInt(this.element.css("marginRight"),10)||0),bottom:(parseInt(this.element.css("marginBottom"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,g,d,f=this.options;if(!f.containment){this.containment=null;return}if(f.containment==="window"){this.containment=[a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,a(window).scrollLeft()+a(window).width()-this.helperProportions.width-this.margins.left,a(window).scrollTop()+(a(window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];return}if(f.containment==="document"){this.containment=[0,0,a(document).width()-this.helperProportions.width-this.margins.left,(a(document).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];return}if(f.containment.constructor===Array){this.containment=f.containment;return}if(f.containment==="parent"){f.containment=this.helper[0].parentNode}g=a(f.containment);d=g[0];if(!d){return}e=g.css("overflow")!=="hidden";this.containment=[(parseInt(g.css("borderLeftWidth"),10)||0)+(parseInt(g.css("paddingLeft"),10)||0),(parseInt(g.css("borderTopWidth"),10)||0)+(parseInt(g.css("paddingTop"),10)||0),(e?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(g.css("borderRightWidth"),10)||0)-(parseInt(g.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(g.css("borderBottomWidth"),10)||0)-(parseInt(g.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=g},_convertPositionTo:function(f,g){if(!g){g=this.position}var e=f==="absolute"?1:-1,c=this.cssPosition==="absolute"&&!(this.scrollParent[0]!==document&&a.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent;if(!this.offset.scroll){this.offset.scroll={top:c.scrollTop(),left:c.scrollLeft()}}return{top:(g.top+this.offset.relative.top*e+this.offset.parent.top*e-((this.cssPosition==="fixed"?-this.scrollParent.scrollTop():this.offset.scroll.top)*e)),left:(g.left+this.offset.relative.left*e+this.offset.parent.left*e-((this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():this.offset.scroll.left)*e))}},_generatePosition:function(d){var c,i,j,f,e=this.options,k=this.cssPosition==="absolute"&&!(this.scrollParent[0]!==document&&a.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,h=d.pageX,g=d.pageY;if(!this.offset.scroll){this.offset.scroll={top:k.scrollTop(),left:k.scrollLeft()}}if(this.originalPosition){if(this.containment){if(this.relative_container){i=this.relative_container.offset();c=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else{c=this.containment}if(d.pageX-this.offset.click.leftc[2]){h=c[2]+this.offset.click.left}if(d.pageY-this.offset.click.top>c[3]){g=c[3]+this.offset.click.top}}if(e.grid){j=e.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/e.grid[1])*e.grid[1]:this.originalPageY;g=c?((j-this.offset.click.top>=c[1]||j-this.offset.click.top>c[3])?j:((j-this.offset.click.top>=c[1])?j-e.grid[1]:j+e.grid[1])):j;f=e.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/e.grid[0])*e.grid[0]:this.originalPageX;h=c?((f-this.offset.click.left>=c[0]||f-this.offset.click.left>c[2])?f:((f-this.offset.click.left>=c[0])?f-e.grid[0]:f+e.grid[0])):f}}return{top:(g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(this.cssPosition==="fixed"?-this.scrollParent.scrollTop():this.offset.scroll.top)),left:(h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():this.offset.scroll.left))}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");if(this.helper[0]!==this.element[0]&&!this.cancelHelperRemoval){this.helper.remove()}this.helper=null;this.cancelHelperRemoval=false},_trigger:function(c,d,e){e=e||this._uiHash();a.ui.plugin.call(this,c,[d,e]);if(c==="drag"){this.positionAbs=this._convertPositionTo("absolute")}return a.Widget.prototype._trigger.call(this,c,d,e)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});a.ui.plugin.add("draggable","connectToSortable",{start:function(d,f){var e=a(this).data("ui-draggable"),g=e.options,c=a.extend({},f,{item:e.element});e.sortables=[];a(g.connectToSortable).each(function(){var h=a.data(this,"ui-sortable");if(h&&!h.options.disabled){e.sortables.push({instance:h,shouldRevert:h.options.revert});h.refreshPositions();h._trigger("activate",d,c)}})},stop:function(d,f){var e=a(this).data("ui-draggable"),c=a.extend({},f,{item:e.element});a.each(e.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;e.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert){this.instance.options.revert=this.shouldRevert}this.instance._mouseStop(d);this.instance.options.helper=this.instance.options._helper;if(e.options.helper==="original"){this.instance.currentItem.css({top:"auto",left:"auto"})}}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",d,c)}})},drag:function(d,f){var e=a(this).data("ui-draggable"),c=this;a.each(e.sortables,function(){var g=false,h=this;this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){g=true;a.each(e.sortables,function(){this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this!==h&&this.instance._intersectsWith(this.instance.containerCache)&&a.contains(h.instance.element[0],this.instance.element[0])){g=false}return g})}if(g){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(c).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return f.helper[0]};d.target=this.instance.currentItem[0];this.instance._mouseCapture(d,true);this.instance._mouseStart(d,true,true);this.instance.offset.click.top=e.offset.click.top;this.instance.offset.click.left=e.offset.click.left;this.instance.offset.parent.left-=e.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=e.offset.parent.top-this.instance.offset.parent.top;e._trigger("toSortable",d);e.dropped=this.instance.element;e.currentItem=e.element;this.instance.fromOutside=e}if(this.instance.currentItem){this.instance._mouseDrag(d)}}else{if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",d,this.instance._uiHash(this.instance));this.instance._mouseStop(d,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();if(this.instance.placeholder){this.instance.placeholder.remove()}e._trigger("fromSortable",d);e.dropped=false}}})}});a.ui.plugin.add("draggable","cursor",{start:function(){var c=a("body"),d=a(this).data("ui-draggable").options;if(c.css("cursor")){d._cursor=c.css("cursor")}c.css("cursor",d.cursor)},stop:function(){var c=a(this).data("ui-draggable").options;if(c._cursor){a("body").css("cursor",c._cursor)}}});a.ui.plugin.add("draggable","opacity",{start:function(d,e){var c=a(e.helper),f=a(this).data("ui-draggable").options;if(c.css("opacity")){f._opacity=c.css("opacity")}c.css("opacity",f.opacity)},stop:function(c,d){var e=a(this).data("ui-draggable").options;if(e._opacity){a(d.helper).css("opacity",e._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(){var c=a(this).data("ui-draggable");if(c.scrollParent[0]!==document&&c.scrollParent[0].tagName!=="HTML"){c.overflowOffset=c.scrollParent.offset()}},drag:function(e){var d=a(this).data("ui-draggable"),f=d.options,c=false;if(d.scrollParent[0]!==document&&d.scrollParent[0].tagName!=="HTML"){if(!f.axis||f.axis!=="x"){if((d.overflowOffset.top+d.scrollParent[0].offsetHeight)-e.pageY=0;v--){s=g.snapElements[v].left;n=s+g.snapElements[v].width;m=g.snapElements[v].top;A=m+g.snapElements[v].height;if(wn+y||eA+y||!a.contains(g.snapElements[v].item.ownerDocument,g.snapElements[v].item)){if(g.snapElements[v].snapping){(g.options.snap.release&&g.options.snap.release.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=false;continue}if(q.snapMode!=="inner"){c=Math.abs(m-e)<=y;z=Math.abs(A-f)<=y;j=Math.abs(s-w)<=y;k=Math.abs(n-x)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m-g.helperProportions.height,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s-g.helperProportions.width}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n}).left-g.margins.left}}h=(c||z||j||k);if(q.snapMode!=="outer"){c=Math.abs(m-f)<=y;z=Math.abs(A-e)<=y;j=Math.abs(s-x)<=y;k=Math.abs(n-w)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A-g.helperProportions.height,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n-g.helperProportions.width}).left-g.margins.left}}if(!g.snapElements[v].snapping&&(c||z||j||k||h)){(g.options.snap.snap&&g.options.snap.snap.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=(c||z||j||k||h)}}});a.ui.plugin.add("draggable","stack",{start:function(){var c,e=this.data("ui-draggable").options,d=a.makeArray(a(e.stack)).sort(function(g,f){return(parseInt(a(g).css("zIndex"),10)||0)-(parseInt(a(f).css("zIndex"),10)||0)});if(!d.length){return}c=parseInt(a(d[0]).css("zIndex"),10)||0;a(d).each(function(f){a(this).css("zIndex",c+f)});this.css("zIndex",(c+d.length))}});a.ui.plugin.add("draggable","zIndex",{start:function(d,e){var c=a(e.helper),f=a(this).data("ui-draggable").options;if(c.css("zIndex")){f._zIndex=c.css("zIndex")}c.css("zIndex",f.zIndex)},stop:function(c,d){var e=a(this).data("ui-draggable").options;if(e._zIndex){a(d.helper).css("zIndex",e._zIndex)}}})})(jQuery);(function(b,c){function a(e,d,f){return(e>d)&&(e<(d+f))}b.widget("ui.droppable",{version:"1.10.3",widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e=this.options,d=e.accept;this.isover=false;this.isout=true;this.accept=b.isFunction(d)?d:function(f){return f.is(d)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};b.ui.ddmanager.droppables[e.scope]=b.ui.ddmanager.droppables[e.scope]||[];b.ui.ddmanager.droppables[e.scope].push(this);(e.addClasses&&this.element.addClass("ui-droppable"))},_destroy:function(){var e=0,d=b.ui.ddmanager.droppables[this.options.scope];for(;e=p&&n<=k)||(m>=p&&m<=k)||(nk))&&((f>=g&&f<=d)||(e>=g&&e<=d)||(fd));default:return false}};b.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(g,k){var f,e,d=b.ui.ddmanager.droppables[g.options.scope]||[],h=k?k.type:null,l=(g.currentItem||g.element).find(":data(ui-droppable)").addBack();droppablesLoop:for(f=0;f
      ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=k.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor===String){if(this.handles==="all"){this.handles="n,e,s,w,se,sw,ne,nw"}l=this.handles.split(",");this.handles={};for(f=0;f
      ");g.css({zIndex:k.zIndex});if("se"===j){g.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[j]=".ui-resizable-"+j;this.element.append(g)}}this._renderAxis=function(q){var n,o,m,p;q=q||this.element;for(n in this.handles){if(this.handles[n].constructor===String){this.handles[n]=c(this.handles[n],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){o=c(this.handles[n],this.element);p=/sw|ne|nw|se|n|s/.test(n)?o.outerHeight():o.outerWidth();m=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");q.css(m,p);this._proportionallyResize()}if(!c(this.handles[n]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!h.resizing){if(this.className){g=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}h.axis=g&&g[1]?g[1]:"se"}});if(k.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(k.disabled){return}c(this).removeClass("ui-resizable-autohide");h._handles.show()}).mouseleave(function(){if(k.disabled){return}if(!h.resizing){c(this).addClass("ui-resizable-autohide");h._handles.hide()}})}this._mouseInit()},_destroy:function(){this._mouseDestroy();var f,e=function(g){c(g).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){e(this.element);f=this.element;this.originalElement.css({position:f.css("position"),width:f.outerWidth(),height:f.outerHeight(),top:f.css("top"),left:f.css("left")}).insertAfter(f);f.remove()}this.originalElement.css("resize",this.originalResizeStyle);e(this.originalElement);return this},_mouseCapture:function(g){var f,h,e=false;for(f in this.handles){h=c(this.handles[f])[0];if(h===g.target||c.contains(h,g.target)){e=true}}return !this.options.disabled&&e},_mouseStart:function(g){var k,h,j,i=this.options,f=this.element.position(),e=this.element;this.resizing=true;if((/absolute/).test(e.css("position"))){e.css({position:"absolute",top:e.css("top"),left:e.css("left")})}else{if(e.is(".ui-draggable")){e.css({position:"absolute",top:f.top,left:f.left})}}this._renderProxy();k=b(this.helper.css("left"));h=b(this.helper.css("top"));if(i.containment){k+=c(i.containment).scrollLeft()||0;h+=c(i.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:k,top:h};this.size=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalSize=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalPosition={left:k,top:h};this.sizeDiff={width:e.outerWidth()-e.width(),height:e.outerHeight()-e.height()};this.originalMousePosition={left:g.pageX,top:g.pageY};this.aspectRatio=(typeof i.aspectRatio==="number")?i.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);j=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",j==="auto"?this.axis+"-resize":j);e.addClass("ui-resizable-resizing");this._propagate("start",g);return true},_mouseDrag:function(e){var k,g=this.helper,l={},i=this.originalMousePosition,m=this.axis,o=this.position.top,f=this.position.left,n=this.size.width,j=this.size.height,q=(e.pageX-i.left)||0,p=(e.pageY-i.top)||0,h=this._change[m];if(!h){return false}k=h.apply(this,[e,q,p]);this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey){k=this._updateRatio(k,e)}k=this._respectSize(k,e);this._updateCache(k);this._propagate("resize",e);if(this.position.top!==o){l.top=this.position.top+"px"}if(this.position.left!==f){l.left=this.position.left+"px"}if(this.size.width!==n){l.width=this.size.width+"px"}if(this.size.height!==j){l.height=this.size.height+"px"}g.css(l);if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}if(!c.isEmptyObject(l)){this._trigger("resize",e,this.ui())}return false},_mouseStop:function(h){this.resizing=false;var g,e,f,k,n,j,m,i=this.options,l=this;if(this._helper){g=this._proportionallyResizeElements;e=g.length&&(/textarea/i).test(g[0].nodeName);f=e&&c.ui.hasScroll(g[0],"left")?0:l.sizeDiff.height;k=e?0:l.sizeDiff.width;n={width:(l.helper.width()-k),height:(l.helper.height()-f)};j=(parseInt(l.element.css("left"),10)+(l.position.left-l.originalPosition.left))||null;m=(parseInt(l.element.css("top"),10)+(l.position.top-l.originalPosition.top))||null;if(!i.animate){this.element.css(c.extend(n,{top:m,left:j}))}l.helper.height(l.size.height);l.helper.width(l.size.width);if(this._helper&&!i.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",h);if(this._helper){this.helper.remove()}return false},_updateVirtualBoundaries:function(g){var i,h,f,k,e,j=this.options;e={minWidth:a(j.minWidth)?j.minWidth:0,maxWidth:a(j.maxWidth)?j.maxWidth:Infinity,minHeight:a(j.minHeight)?j.minHeight:0,maxHeight:a(j.maxHeight)?j.maxHeight:Infinity};if(this._aspectRatio||g){i=e.minHeight*this.aspectRatio;f=e.minWidth/this.aspectRatio;h=e.maxHeight*this.aspectRatio;k=e.maxWidth/this.aspectRatio;if(i>e.minWidth){e.minWidth=i}if(f>e.minHeight){e.minHeight=f}if(hj.width),n=a(j.height)&&g.minHeight&&(g.minHeight>j.height),f=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,i=/sw|nw|w/.test(m),e=/nw|ne|n/.test(m);if(h){j.width=g.minWidth}if(n){j.height=g.minHeight}if(p){j.width=g.maxWidth}if(k){j.height=g.maxHeight}if(h&&i){j.left=f-g.minWidth}if(p&&i){j.left=f-g.maxWidth}if(n&&e){j.top=l-g.minHeight}if(k&&e){j.top=l-g.maxHeight}if(!j.width&&!j.height&&!j.left&&j.top){j.top=null}else{if(!j.width&&!j.height&&!j.top&&j.left){j.left=null}}return j},_proportionallyResize:function(){if(!this._proportionallyResizeElements.length){return}var h,f,l,e,k,g=this.helper||this.element;for(h=0;h
      ");this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++f.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(f,e){return{width:this.originalSize.width+e}},w:function(g,e){var f=this.originalSize,h=this.originalPosition;return{left:h.left+e,width:f.width-e}},n:function(h,f,e){var g=this.originalSize,i=this.originalPosition;return{top:i.top+e,height:g.height-e}},s:function(g,f,e){return{height:this.originalSize.height+e}},se:function(g,f,e){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[g,f,e]))},sw:function(g,f,e){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[g,f,e]))},ne:function(g,f,e){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[g,f,e]))},nw:function(g,f,e){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[g,f,e]))}},_propagate:function(f,e){c.ui.plugin.call(this,f,[e,this.ui()]);(f!=="resize"&&this._trigger(f,e,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});c.ui.plugin.add("resizable","animate",{stop:function(h){var m=c(this).data("ui-resizable"),j=m.options,g=m._proportionallyResizeElements,e=g.length&&(/textarea/i).test(g[0].nodeName),f=e&&c.ui.hasScroll(g[0],"left")?0:m.sizeDiff.height,l=e?0:m.sizeDiff.width,i={width:(m.size.width-l),height:(m.size.height-f)},k=(parseInt(m.element.css("left"),10)+(m.position.left-m.originalPosition.left))||null,n=(parseInt(m.element.css("top"),10)+(m.position.top-m.originalPosition.top))||null;m.element.animate(c.extend(i,n&&k?{top:n,left:k}:{}),{duration:j.animateDuration,easing:j.animateEasing,step:function(){var o={width:parseInt(m.element.css("width"),10),height:parseInt(m.element.css("height"),10),top:parseInt(m.element.css("top"),10),left:parseInt(m.element.css("left"),10)};if(g&&g.length){c(g[0]).css({width:o.width,height:o.height})}m._updateCache(o);m._propagate("resize",h)}})}});c.ui.plugin.add("resizable","containment",{start:function(){var m,g,q,e,l,h,r,n=c(this).data("ui-resizable"),k=n.options,j=n.element,f=k.containment,i=(f instanceof c)?f.get(0):(/parent/.test(f))?j.parent().get(0):f;if(!i){return}n.containerElement=c(i);if(/document/.test(f)||f===document){n.containerOffset={left:0,top:0};n.containerPosition={left:0,top:0};n.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{m=c(i);g=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){g[p]=b(m.css("padding"+o))});n.containerOffset=m.offset();n.containerPosition=m.position();n.containerSize={height:(m.innerHeight()-g[3]),width:(m.innerWidth()-g[1])};q=n.containerOffset;e=n.containerSize.height;l=n.containerSize.width;h=(c.ui.hasScroll(i,"left")?i.scrollWidth:l);r=(c.ui.hasScroll(i)?i.scrollHeight:e);n.parentData={element:i,left:q.left,top:q.top,width:h,height:r}}},resize:function(f){var k,q,j,i,l=c(this).data("ui-resizable"),h=l.options,n=l.containerOffset,m=l.position,p=l._aspectRatio||f.shiftKey,e={top:0,left:0},g=l.containerElement;if(g[0]!==document&&(/static/).test(g.css("position"))){e=n}if(m.left<(l._helper?n.left:0)){l.size.width=l.size.width+(l._helper?(l.position.left-n.left):(l.position.left-e.left));if(p){l.size.height=l.size.width/l.aspectRatio}l.position.left=h.helper?n.left:0}if(m.top<(l._helper?n.top:0)){l.size.height=l.size.height+(l._helper?(l.position.top-n.top):l.position.top);if(p){l.size.width=l.size.height*l.aspectRatio}l.position.top=l._helper?n.top:0}l.offset.left=l.parentData.left+l.position.left;l.offset.top=l.parentData.top+l.position.top;k=Math.abs((l._helper?l.offset.left-e.left:(l.offset.left-e.left))+l.sizeDiff.width);q=Math.abs((l._helper?l.offset.top-e.top:(l.offset.top-n.top))+l.sizeDiff.height);j=l.containerElement.get(0)===l.element.parent().get(0);i=/relative|absolute/.test(l.containerElement.css("position"));if(j&&i){k-=l.parentData.left}if(k+l.size.width>=l.parentData.width){l.size.width=l.parentData.width-k;if(p){l.size.height=l.size.width/l.aspectRatio}}if(q+l.size.height>=l.parentData.height){l.size.height=l.parentData.height-q;if(p){l.size.width=l.size.height*l.aspectRatio}}},stop:function(){var k=c(this).data("ui-resizable"),f=k.options,l=k.containerOffset,e=k.containerPosition,g=k.containerElement,i=c(k.helper),n=i.offset(),m=i.outerWidth()-k.sizeDiff.width,j=i.outerHeight()-k.sizeDiff.height;if(k._helper&&!f.animate&&(/relative/).test(g.css("position"))){c(this).css({left:n.left-e.left-l.left,width:m,height:j})}if(k._helper&&!f.animate&&(/static/).test(g.css("position"))){c(this).css({left:n.left-e.left-l.left,width:m,height:j})}}});c.ui.plugin.add("resizable","alsoResize",{start:function(){var e=c(this).data("ui-resizable"),g=e.options,f=function(h){c(h).each(function(){var i=c(this);i.data("ui-resizable-alsoresize",{width:parseInt(i.width(),10),height:parseInt(i.height(),10),left:parseInt(i.css("left"),10),top:parseInt(i.css("top"),10)})})};if(typeof(g.alsoResize)==="object"&&!g.alsoResize.parentNode){if(g.alsoResize.length){g.alsoResize=g.alsoResize[0];f(g.alsoResize)}else{c.each(g.alsoResize,function(h){f(h)})}}else{f(g.alsoResize)}},resize:function(g,i){var f=c(this).data("ui-resizable"),j=f.options,h=f.originalSize,l=f.originalPosition,k={height:(f.size.height-h.height)||0,width:(f.size.width-h.width)||0,top:(f.position.top-l.top)||0,left:(f.position.left-l.left)||0},e=function(m,n){c(m).each(function(){var q=c(this),r=c(this).data("ui-resizable-alsoresize"),p={},o=n&&n.length?n:q.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];c.each(o,function(s,u){var t=(r[u]||0)+(k[u]||0);if(t&&t>=0){p[u]=t||null}});q.css(p)})};if(typeof(j.alsoResize)==="object"&&!j.alsoResize.nodeType){c.each(j.alsoResize,function(m,n){e(m,n)})}else{e(j.alsoResize)}},stop:function(){c(this).removeData("resizable-alsoresize")}});c.ui.plugin.add("resizable","ghost",{start:function(){var f=c(this).data("ui-resizable"),g=f.options,e=f.size;f.ghost=f.originalElement.clone();f.ghost.css({opacity:0.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof g.ghost==="string"?g.ghost:"");f.ghost.appendTo(f.helper)},resize:function(){var e=c(this).data("ui-resizable");if(e.ghost){e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})}},stop:function(){var e=c(this).data("ui-resizable");if(e.ghost&&e.helper){e.helper.get(0).removeChild(e.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(){var r=c(this).data("ui-resizable"),i=r.options,s=r.size,k=r.originalSize,n=r.originalPosition,t=r.axis,f=typeof i.grid==="number"?[i.grid,i.grid]:i.grid,p=(f[0]||1),m=(f[1]||1),h=Math.round((s.width-k.width)/p)*p,g=Math.round((s.height-k.height)/m)*m,l=k.width+h,e=k.height+g,j=i.maxWidth&&(i.maxWidthl),v=i.minHeight&&(i.minHeight>e);i.grid=f;if(q){l=l+p}if(v){e=e+m}if(j){l=l-p}if(u){e=e-m}if(/^(se|s|e)$/.test(t)){r.size.width=l;r.size.height=e}else{if(/^(ne)$/.test(t)){r.size.width=l;r.size.height=e;r.position.top=n.top-g}else{if(/^(sw)$/.test(t)){r.size.width=l;r.size.height=e;r.position.left=n.left-h}else{r.size.width=l;r.size.height=e;r.position.top=n.top-g;r.position.left=n.left-h}}}}})})(jQuery);(function(a,b){a.widget("ui.selectable",a.ui.mouse,{version:"1.10.3",options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var d,c=this;this.element.addClass("ui-selectable");this.dragged=false;this.refresh=function(){d=a(c.options.filter,c.element[0]);d.addClass("ui-selectee");d.each(function(){var e=a(this),f=e.offset();a.data(this,"selectable-item",{element:this,$element:e,left:f.left,top:f.top,right:f.left+e.outerWidth(),bottom:f.top+e.outerHeight(),startselected:false,selected:e.hasClass("ui-selected"),selecting:e.hasClass("ui-selecting"),unselecting:e.hasClass("ui-unselecting")})})};this.refresh();this.selectees=d.addClass("ui-selectee");this._mouseInit();this.helper=a("
      ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled");this._mouseDestroy()},_mouseStart:function(e){var d=this,c=this.options;this.opos=[e.pageX,e.pageY];if(this.options.disabled){return}this.selectees=a(c.filter,this.element[0]);this._trigger("start",e);a(c.appendTo).append(this.helper);this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0});if(c.autoRefresh){this.refresh()}this.selectees.filter(".ui-selected").each(function(){var f=a.data(this,"selectable-item");f.startselected=true;if(!e.metaKey&&!e.ctrlKey){f.$element.removeClass("ui-selected");f.selected=false;f.$element.addClass("ui-unselecting");f.unselecting=true;d._trigger("unselecting",e,{unselecting:f.element})}});a(e.target).parents().addBack().each(function(){var f,g=a.data(this,"selectable-item");if(g){f=(!e.metaKey&&!e.ctrlKey)||!g.$element.hasClass("ui-selected");g.$element.removeClass(f?"ui-unselecting":"ui-selected").addClass(f?"ui-selecting":"ui-unselecting");g.unselecting=!f;g.selecting=f;g.selected=f;if(f){d._trigger("selecting",e,{selecting:g.element})}else{d._trigger("unselecting",e,{unselecting:g.element})}return false}})},_mouseDrag:function(j){this.dragged=true;if(this.options.disabled){return}var g,i=this,e=this.options,d=this.opos[0],h=this.opos[1],c=j.pageX,f=j.pageY;if(d>c){g=c;c=d;d=g}if(h>f){g=f;f=h;h=g}this.helper.css({left:d,top:h,width:c-d,height:f-h});this.selectees.each(function(){var k=a.data(this,"selectable-item"),l=false;if(!k||k.element===i.element[0]){return}if(e.tolerance==="touch"){l=(!(k.left>c||k.rightf||k.bottomd&&k.righth&&k.bottome)&&(f<(e+g))}function c(e){return(/left|right/).test(e.css("float"))||(/inline|table-cell/).test(e.css("display"))}b.widget("ui.sortable",b.ui.mouse,{version:"1.10.3",widgetEventPrefix:"sort",ready:false,options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_create:function(){var e=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh();this.floating=this.items.length?e.axis==="x"||c(this.items[0].item):false;this.offset=this.element.offset();this._mouseInit();this.ready=true},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled");this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--){this.items[e].item.removeData(this.widgetName+"-item")}return this},_setOption:function(e,f){if(e==="disabled"){this.options[e]=f;this.widget().toggleClass("ui-sortable-disabled",!!f)}else{b.Widget.prototype._setOption.apply(this,arguments)}},_mouseCapture:function(g,h){var e=null,i=false,f=this;if(this.reverting){return false}if(this.options.disabled||this.options.type==="static"){return false}this._refreshItems(g);b(g.target).parents().each(function(){if(b.data(this,f.widgetName+"-item")===f){e=b(this);return false}});if(b.data(g.target,f.widgetName+"-item")===f){e=b(g.target)}if(!e){return false}if(this.options.handle&&!h){b(this.options.handle,e).find("*").addBack().each(function(){if(this===g.target){i=true}});if(!i){return false}}this.currentItem=e;this._removeCurrentsFromItems();return true},_mouseStart:function(h,j,f){var g,e,k=this.options;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(h);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};b.extend(this.offset,{click:{left:h.pageX-this.offset.left,top:h.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");this.originalPosition=this._generatePosition(h);this.originalPageX=h.pageX;this.originalPageY=h.pageY;(k.cursorAt&&this._adjustOffsetFromHelper(k.cursorAt));this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!==this.currentItem[0]){this.currentItem.hide()}this._createPlaceholder();if(k.containment){this._setContainment()}if(k.cursor&&k.cursor!=="auto"){e=this.document.find("body");this.storedCursor=e.css("cursor");e.css("cursor",k.cursor);this.storedStylesheet=b("").appendTo(e)}if(k.opacity){if(this.helper.css("opacity")){this._storedOpacity=this.helper.css("opacity")}this.helper.css("opacity",k.opacity)}if(k.zIndex){if(this.helper.css("zIndex")){this._storedZIndex=this.helper.css("zIndex")}this.helper.css("zIndex",k.zIndex)}if(this.scrollParent[0]!==document&&this.scrollParent[0].tagName!=="HTML"){this.overflowOffset=this.scrollParent.offset()}this._trigger("start",h,this._uiHash());if(!this._preserveHelperProportions){this._cacheHelperProportions()}if(!f){for(g=this.containers.length-1;g>=0;g--){this.containers[g]._trigger("activate",h,this._uiHash(this))}}if(b.ui.ddmanager){b.ui.ddmanager.current=this}if(b.ui.ddmanager&&!k.dropBehaviour){b.ui.ddmanager.prepareOffsets(this,h)}this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(h);return true},_mouseDrag:function(j){var g,h,f,l,k=this.options,e=false;this.position=this._generatePosition(j);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs}if(this.options.scroll){if(this.scrollParent[0]!==document&&this.scrollParent[0].tagName!=="HTML"){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-j.pageY=0;g--){h=this.items[g];f=h.item[0];l=this._intersectsWithPointer(h);if(!l){continue}if(h.instance!==this.currentContainer){continue}if(f!==this.currentItem[0]&&this.placeholder[l===1?"next":"prev"]()[0]!==f&&!b.contains(this.placeholder[0],f)&&(this.options.type==="semi-dynamic"?!b.contains(this.element[0],f):true)){this.direction=l===1?"down":"up";if(this.options.tolerance==="pointer"||this._intersectsWithSides(h)){this._rearrange(j,h)}else{break}this._trigger("change",j,this._uiHash());break}}this._contactContainers(j);if(b.ui.ddmanager){b.ui.ddmanager.drag(this,j)}this._trigger("sort",j,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(g,i){if(!g){return}if(b.ui.ddmanager&&!this.options.dropBehaviour){b.ui.ddmanager.drop(this,g)}if(this.options.revert){var f=this,j=this.placeholder.offset(),e=this.options.axis,h={};if(!e||e==="x"){h.left=j.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollLeft)}if(!e||e==="y"){h.top=j.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===document.body?0:this.offsetParent[0].scrollTop)}this.reverting=true;b(this.helper).animate(h,parseInt(this.options.revert,10)||500,function(){f._clear(g)})}else{this._clear(g,i)}return false},cancel:function(){if(this.dragging){this._mouseUp({target:null});if(this.options.helper==="original"){this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}for(var e=this.containers.length-1;e>=0;e--){this.containers[e]._trigger("deactivate",null,this._uiHash(this));if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",null,this._uiHash(this));this.containers[e].containerCache.over=0}}}if(this.placeholder){if(this.placeholder[0].parentNode){this.placeholder[0].parentNode.removeChild(this.placeholder[0])}if(this.options.helper!=="original"&&this.helper&&this.helper[0].parentNode){this.helper.remove()}b.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){b(this.domPosition.prev).after(this.currentItem)}else{b(this.domPosition.parent).prepend(this.currentItem)}}return this},serialize:function(g){var e=this._getItemsAsjQuery(g&&g.connected),f=[];g=g||{};b(e).each(function(){var h=(b(g.item||this).attr(g.attribute||"id")||"").match(g.expression||(/(.+)[\-=_](.+)/));if(h){f.push((g.key||h[1]+"[]")+"="+(g.key&&g.expression?h[1]:h[2]))}});if(!f.length&&g.key){f.push(g.key+"=")}return f.join("&")},toArray:function(g){var e=this._getItemsAsjQuery(g&&g.connected),f=[];g=g||{};e.each(function(){f.push(b(g.item||this).attr(g.attribute||"id")||"")});return f},_intersectsWith:function(q){var g=this.positionAbs.left,f=g+this.helperProportions.width,o=this.positionAbs.top,n=o+this.helperProportions.height,h=q.left,e=h+q.width,s=q.top,m=s+q.height,u=this.offset.click.top,k=this.offset.click.left,j=(this.options.axis==="x")||((o+u)>s&&(o+u)h&&(g+k)q[this.floating?"width":"height"])){return i}else{return(h0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return e!==0&&(e>0?"right":"left")},refresh:function(e){this._refreshItems(e);this.refreshPositions();return this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(l){var h,g,n,m,e=[],f=[],k=this._connectWith();if(k&&l){for(h=k.length-1;h>=0;h--){n=b(k[h]);for(g=n.length-1;g>=0;g--){m=b.data(n[g],this.widgetFullName);if(m&&m!==this&&!m.options.disabled){f.push([b.isFunction(m.options.items)?m.options.items.call(m.element):b(m.options.items,m.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),m])}}}}f.push([b.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):b(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(h=f.length-1;h>=0;h--){f[h][0].each(function(){e.push(this)})}return b(e)},_removeCurrentsFromItems:function(){var e=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=b.grep(this.items,function(g){for(var f=0;f=0;k--){p=b(n[k]);for(g=p.length-1;g>=0;g--){l=b.data(p[g],this.widgetFullName);if(l&&l!==this&&!l.options.disabled){h.push([b.isFunction(l.options.items)?l.options.items.call(l.element[0],e,{item:this.currentItem}):b(l.options.items,l.element),l]);this.containers.push(l)}}}}for(k=h.length-1;k>=0;k--){o=h[k][1];f=h[k][0];for(g=0,q=f.length;g=0;g--){h=this.items[g];if(h.instance!==this.currentContainer&&this.currentContainer&&h.item[0]!==this.currentItem[0]){continue}f=this.options.toleranceElement?b(this.options.toleranceElement,h.item):h.item;if(!e){h.width=f.outerWidth();h.height=f.outerHeight()}j=f.offset();h.left=j.left;h.top=j.top}if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this)}else{for(g=this.containers.length-1;g>=0;g--){j=this.containers[g].element.offset();this.containers[g].containerCache.left=j.left;this.containers[g].containerCache.top=j.top;this.containers[g].containerCache.width=this.containers[g].element.outerWidth();this.containers[g].containerCache.height=this.containers[g].element.outerHeight()}}return this},_createPlaceholder:function(f){f=f||this;var e,g=f.options;if(!g.placeholder||g.placeholder.constructor===String){e=g.placeholder;g.placeholder={element:function(){var i=f.currentItem[0].nodeName.toLowerCase(),h=b("<"+i+">",f.document[0]).addClass(e||f.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");if(i==="tr"){f.currentItem.children().each(function(){b(" ",f.document[0]).attr("colspan",b(this).attr("colspan")||1).appendTo(h)})}else{if(i==="img"){h.attr("src",f.currentItem.attr("src"))}}if(!e){h.css("visibility","hidden")}return h},update:function(h,i){if(e&&!g.forcePlaceholderSize){return}if(!i.height()){i.height(f.currentItem.innerHeight()-parseInt(f.currentItem.css("paddingTop")||0,10)-parseInt(f.currentItem.css("paddingBottom")||0,10))}if(!i.width()){i.width(f.currentItem.innerWidth()-parseInt(f.currentItem.css("paddingLeft")||0,10)-parseInt(f.currentItem.css("paddingRight")||0,10))}}}}f.placeholder=b(g.placeholder.element.call(f.element,f.currentItem));f.currentItem.after(f.placeholder);g.placeholder.update(f,f.placeholder)},_contactContainers:function(e){var l,h,p,m,n,r,f,s,k,o,g=null,q=null;for(l=this.containers.length-1;l>=0;l--){if(b.contains(this.currentItem[0],this.containers[l].element[0])){continue}if(this._intersectsWith(this.containers[l].containerCache)){if(g&&b.contains(this.containers[l].element[0],g.element[0])){continue}g=this.containers[l];q=l}else{if(this.containers[l].containerCache.over){this.containers[l]._trigger("out",e,this._uiHash(this));this.containers[l].containerCache.over=0}}}if(!g){return}if(this.containers.length===1){if(!this.containers[q].containerCache.over){this.containers[q]._trigger("over",e,this._uiHash(this));this.containers[q].containerCache.over=1}}else{p=10000;m=null;o=g.floating||c(this.currentItem);n=o?"left":"top";r=o?"width":"height";f=this.positionAbs[n]+this.offset.click[n];for(h=this.items.length-1;h>=0;h--){if(!b.contains(this.containers[q].element[0],this.items[h].item[0])){continue}if(this.items[h].item[0]===this.currentItem[0]){continue}if(o&&!a(this.positionAbs.top+this.offset.click.top,this.items[h].top,this.items[h].height)){continue}s=this.items[h].item.offset()[n];k=false;if(Math.abs(s-f)>Math.abs(s+this.items[h][r]-f)){k=true;s+=this.items[h][r]}if(Math.abs(s-f)this.containment[2]){g=this.containment[2]+this.offset.click.left}if(h.pageY-this.offset.click.top>this.containment[3]){f=this.containment[3]+this.offset.click.top}}if(k.grid){j=this.originalPageY+Math.round((f-this.originalPageY)/k.grid[1])*k.grid[1];f=this.containment?((j-this.offset.click.top>=this.containment[1]&&j-this.offset.click.top<=this.containment[3])?j:((j-this.offset.click.top>=this.containment[1])?j-k.grid[1]:j+k.grid[1])):j;i=this.originalPageX+Math.round((g-this.originalPageX)/k.grid[0])*k.grid[0];g=this.containment?((i-this.offset.click.left>=this.containment[0]&&i-this.offset.click.left<=this.containment[2])?i:((i-this.offset.click.left>=this.containment[0])?i-k.grid[0]:i+k.grid[0])):i}}return{top:(f-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+((this.cssPosition==="fixed"?-this.scrollParent.scrollTop():(l?0:e.scrollTop())))),left:(g-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+((this.cssPosition==="fixed"?-this.scrollParent.scrollLeft():l?0:e.scrollLeft())))}},_rearrange:function(j,h,f,g){f?f[0].appendChild(this.placeholder[0]):h.item[0].parentNode.insertBefore(this.placeholder[0],(this.direction==="down"?h.item[0]:h.item[0].nextSibling));this.counter=this.counter?++this.counter:1;var e=this.counter;this._delay(function(){if(e===this.counter){this.refreshPositions(!g)}})},_clear:function(f,g){this.reverting=false;var e,h=[];if(!this._noFinalSort&&this.currentItem.parent().length){this.placeholder.before(this.currentItem)}this._noFinalSort=null;if(this.helper[0]===this.currentItem[0]){for(e in this._storedCSS){if(this._storedCSS[e]==="auto"||this._storedCSS[e]==="static"){this._storedCSS[e]=""}}this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}if(this.fromOutside&&!g){h.push(function(i){this._trigger("receive",i,this._uiHash(this.fromOutside))})}if((this.fromOutside||this.domPosition.prev!==this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!==this.currentItem.parent()[0])&&!g){h.push(function(i){this._trigger("update",i,this._uiHash())})}if(this!==this.currentContainer){if(!g){h.push(function(i){this._trigger("remove",i,this._uiHash())});h.push((function(i){return function(j){i._trigger("receive",j,this._uiHash(this))}}).call(this,this.currentContainer));h.push((function(i){return function(j){i._trigger("update",j,this._uiHash(this))}}).call(this,this.currentContainer))}}for(e=this.containers.length-1;e>=0;e--){if(!g){h.push((function(i){return function(j){i._trigger("deactivate",j,this._uiHash(this))}}).call(this,this.containers[e]))}if(this.containers[e].containerCache.over){h.push((function(i){return function(j){i._trigger("out",j,this._uiHash(this))}}).call(this,this.containers[e]));this.containers[e].containerCache.over=0}}if(this.storedCursor){this.document.find("body").css("cursor",this.storedCursor);this.storedStylesheet.remove()}if(this._storedOpacity){this.helper.css("opacity",this._storedOpacity)}if(this._storedZIndex){this.helper.css("zIndex",this._storedZIndex==="auto"?"":this._storedZIndex)}this.dragging=false;if(this.cancelHelperRemoval){if(!g){this._trigger("beforeStop",f,this._uiHash());for(e=0;e")[0],d,o=r.each;e.style.cssText="background-color:rgba(1,1,1,.5)";p.rgba=e.style.backgroundColor.indexOf("rgba")>-1;o(m,function(s,t){t.cache="_"+s;t.props.alpha={idx:3,type:"percent",def:1}});function l(t,v,u){var s=q[v.type]||{};if(t==null){return(u||!v.def)?null:v.def}t=s.floor?~~t:parseFloat(t);if(isNaN(t)){return v.def}if(s.mod){return(t+s.mod)%s.mod}return 0>t?0:s.maxE.mod/2){B+=E.mod}else{if(B-A>E.mod/2){B-=E.mod}}}s[C]=l((A-B)*z+B,F)}});return this[v](s)},blend:function(v){if(this._rgba[3]===1){return this}var u=this._rgba.slice(),t=u.pop(),s=h(v)._rgba;return h(r.map(u,function(w,x){return(1-t)*s[x]+t*w}))},toRgbaString:function(){var t="rgba(",s=r.map(this._rgba,function(u,w){return u==null?(w>2?1:0):u});if(s[3]===1){s.pop();t="rgb("}return t+s.join()+")"},toHslaString:function(){var t="hsla(",s=r.map(this.hsla(),function(u,w){if(u==null){u=w>2?1:0}if(w&&w<3){u=Math.round(u*100)+"%"}return u});if(s[3]===1){s.pop();t="hsl("}return t+s.join()+")"},toHexString:function(s){var t=this._rgba.slice(),u=t.pop();if(s){t.push(~~(u*255))}return"#"+r.map(t,function(w){w=(w||0).toString(16);return w.length===1?"0"+w:w}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}});h.fn.parse.prototype=h.fn;function f(u,t,s){s=(s+1)%1;if(s*6<1){return u+(t-u)*s*6}if(s*2<1){return t}if(s*3<2){return u+(t-u)*((2/3)-s)*6}return u}m.hsla.to=function(v){if(v[0]==null||v[1]==null||v[2]==null){return[null,null,null,v[3]]}var t=v[0]/255,y=v[1]/255,z=v[2]/255,B=v[3],A=Math.max(t,y,z),w=Math.min(t,y,z),C=A-w,D=A+w,u=D*0.5,x,E;if(w===A){x=0}else{if(t===A){x=(60*(y-z)/C)+360}else{if(y===A){x=(60*(z-t)/C)+120}else{x=(60*(t-y)/C)+240}}}if(C===0){E=0}else{if(u<=0.5){E=C/D}else{E=C/(2-D)}}return[Math.round(x)%360,E,u,B==null?1:B]};m.hsla.from=function(x){if(x[0]==null||x[1]==null||x[2]==null){return[null,null,null,x[3]]}var w=x[0]/360,v=x[1],u=x[2],t=x[3],y=u<=0.5?u*(1+v):u+v-u*v,z=2*u-y;return[Math.round(f(z,y,w+(1/3))*255),Math.round(f(z,y,w)*255),Math.round(f(z,y,w-(1/3))*255),t]};o(m,function(t,v){var u=v.props,s=v.cache,x=v.to,w=v.from;h.fn[t]=function(C){if(x&&!this[s]){this[s]=x(this._rgba)}if(C===g){return this[s].slice()}var z,B=r.type(C),y=(B==="array"||B==="object")?C:arguments,A=this[s].slice();o(u,function(D,F){var E=y[B==="object"?D:F.idx];if(E==null){E=A[F.idx]}A[F.idx]=l(E,F)});if(w){z=h(w(A));z[s]=A;return z}else{return h(A)}};o(u,function(y,z){if(h.fn[y]){return}h.fn[y]=function(D){var F=r.type(D),C=(y==="alpha"?(this._hsla?"hsla":"rgba"):t),B=this[C](),E=B[z.idx],A;if(F==="undefined"){return E}if(F==="function"){D=D.call(this,E);F=r.type(D)}if(D==null&&z.empty){return this}if(F==="string"){A=k.exec(D);if(A){D=E+parseFloat(A[2])*(A[1]==="+"?1:-1)}}B[z.idx]=D;return this[C](B)}})});h.hook=function(t){var s=t.split(" ");o(s,function(u,v){r.cssHooks[v]={set:function(z,A){var x,y,w="";if(A!=="transparent"&&(r.type(A)!=="string"||(x=i(A)))){A=h(x||A);if(!p.rgba&&A._rgba[3]!==1){y=v==="backgroundColor"?z.parentNode:z;while((w===""||w==="transparent")&&y&&y.style){try{w=r.css(y,"backgroundColor");y=y.parentNode}catch(B){}}A=A.blend(w&&w!=="transparent"?w:"_default")}A=A.toRgbaString()}try{z.style[v]=A}catch(B){}}};r.fx.step[v]=function(w){if(!w.colorInit){w.start=h(w.elem,v);w.end=h(w.end);w.colorInit=true}r.cssHooks[v].set(w.elem,w.start.transition(w.end,w.pos))}})};h.hook(n);r.cssHooks.borderColor={expand:function(t){var s={};o(["Top","Right","Bottom","Left"],function(v,u){s["border"+u+"Color"]=t});return s}};d=r.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}})(jQuery);(function(){var e=["add","remove","toggle"],f={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(h,i){a.fx.step[i]=function(j){if(j.end!=="none"&&!j.setAttr||j.pos===1&&!j.setAttr){jQuery.style(j.elem,i,j.end);j.setAttr=true}}});function g(l){var i,h,j=l.ownerDocument.defaultView?l.ownerDocument.defaultView.getComputedStyle(l,null):l.currentStyle,k={};if(j&&j.length&&j[0]&&j[j[0]]){h=j.length;while(h--){i=j[h];if(typeof j[i]==="string"){k[a.camelCase(i)]=j[i]}}}else{for(i in j){if(typeof j[i]==="string"){k[i]=j[i]}}}return k}function d(h,j){var l={},i,k;for(i in j){k=j[i];if(h[i]!==k){if(!f[i]){if(a.fx.step[i]||!isNaN(parseFloat(k))){l[i]=k}}}}return l}if(!a.fn.addBack){a.fn.addBack=function(h){return this.add(h==null?this.prevObject:this.prevObject.filter(h))}}a.effects.animateClass=function(h,i,l,k){var j=a.speed(i,l,k);return this.queue(function(){var o=a(this),m=o.attr("class")||"",n,p=j.children?o.find("*").addBack():o;p=p.map(function(){var q=a(this);return{el:q,start:g(this)}});n=function(){a.each(e,function(q,r){if(h[r]){o[r+"Class"](h[r])}})};n();p=p.map(function(){this.end=g(this.el[0]);this.diff=d(this.start,this.end);return this});o.attr("class",m);p=p.map(function(){var s=this,q=a.Deferred(),r=a.extend({},j,{queue:false,complete:function(){q.resolve(s)}});this.el.animate(this.diff,r);return q.promise()});a.when.apply(a,p.get()).done(function(){n();a.each(arguments,function(){var q=this.el;a.each(this.diff,function(r){q.css(r,"")})});j.complete.call(o[0])})})};a.fn.extend({addClass:(function(h){return function(j,i,l,k){return i?a.effects.animateClass.call(this,{add:j},i,l,k):h.apply(this,arguments)}})(a.fn.addClass),removeClass:(function(h){return function(j,i,l,k){return arguments.length>1?a.effects.animateClass.call(this,{remove:j},i,l,k):h.apply(this,arguments)}})(a.fn.removeClass),toggleClass:(function(h){return function(k,j,i,m,l){if(typeof j==="boolean"||j===c){if(!i){return h.apply(this,arguments)}else{return a.effects.animateClass.call(this,(j?{add:k}:{remove:k}),i,m,l)}}else{return a.effects.animateClass.call(this,{toggle:k},j,i,m)}}})(a.fn.toggleClass),switchClass:function(h,j,i,l,k){return a.effects.animateClass.call(this,{add:j,remove:h},i,l,k)}})})();(function(){a.extend(a.effects,{version:"1.10.3",save:function(g,h){for(var f=0;f
    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),f={width:g.width(),height:g.height()},j=document.activeElement;try{j.id}catch(i){j=document.body}g.wrap(k);if(g[0]===j||a.contains(g[0],j)){a(j).focus()}k=g.parent();if(g.css("position")==="static"){k.css({position:"relative"});g.css({position:"relative"})}else{a.extend(h,{position:g.css("position"),zIndex:g.css("z-index")});a.each(["top","left","bottom","right"],function(l,m){h[m]=g.css(m);if(isNaN(parseInt(h[m],10))){h[m]="auto"}});g.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}g.css(f);return k.css(h).show()},removeWrapper:function(f){var g=document.activeElement;if(f.parent().is(".ui-effects-wrapper")){f.parent().replaceWith(f);if(f[0]===g||a.contains(f[0],g)){a(g).focus()}}return f},setTransition:function(g,i,f,h){h=h||{};a.each(i,function(k,j){var l=g.cssUnit(j);if(l[0]>0){h[j]=l[0]*f+l[1]}});return h}});function d(g,f,h,i){if(a.isPlainObject(g)){f=g;g=g.effect}g={effect:g};if(f==null){f={}}if(a.isFunction(f)){i=f;h=null;f={}}if(typeof f==="number"||a.fx.speeds[f]){i=h;h=f;f={}}if(a.isFunction(h)){i=h;h=null}if(f){a.extend(g,f)}h=h||f.duration;g.duration=a.fx.off?0:typeof h==="number"?h:h in a.fx.speeds?a.fx.speeds[h]:a.fx.speeds._default;g.complete=i||f.complete;return g}function e(f){if(!f||typeof f==="number"||a.fx.speeds[f]){return true}if(typeof f==="string"&&!a.effects.effect[f]){return true}if(a.isFunction(f)){return true}if(typeof f==="object"&&!f.effect){return true}return false}a.fn.extend({effect:function(){var h=d.apply(this,arguments),j=h.mode,f=h.queue,g=a.effects.effect[h.effect];if(a.fx.off||!g){if(j){return this[j](h.duration,h.complete)}else{return this.each(function(){if(h.complete){h.complete.call(this)}})}}function i(m){var n=a(this),l=h.complete,o=h.mode;function k(){if(a.isFunction(l)){l.call(n[0])}if(a.isFunction(m)){m()}}if(n.is(":hidden")?o==="hide":o==="show"){n[o]();k()}else{g.call(n[0],h,k)}}return f===false?this.each(i):this.queue(f||"fx",i)},show:(function(f){return function(h){if(e(h)){return f.apply(this,arguments)}else{var g=d.apply(this,arguments);g.mode="show";return this.effect.call(this,g)}}})(a.fn.show),hide:(function(f){return function(h){if(e(h)){return f.apply(this,arguments)}else{var g=d.apply(this,arguments);g.mode="hide";return this.effect.call(this,g)}}})(a.fn.hide),toggle:(function(f){return function(h){if(e(h)||typeof h==="boolean"){return f.apply(this,arguments)}else{var g=d.apply(this,arguments);g.mode="toggle";return this.effect.call(this,g)}}})(a.fn.toggle),cssUnit:function(f){var g=this.css(f),h=[];a.each(["em","px","%","pt"],function(j,k){if(g.indexOf(k)>0){h=[parseFloat(g),k]}});return h}})})();(function(){var d={};a.each(["Quad","Cubic","Quart","Quint","Expo"],function(f,e){d[e]=function(g){return Math.pow(g,f+2)}});a.extend(d,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return e===0||e===1?e:-Math.pow(2,8*(e-1))*Math.sin(((e-1)*80-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(g){var e,f=4;while(g<((e=Math.pow(2,--f))-1)/11){}return 1/Math.pow(4,3-f)-7.5625*Math.pow((e*3-2)/22-g,2)}});a.each(d,function(f,e){a.easing["easeIn"+f]=e;a.easing["easeOut"+f]=function(g){return 1-e(1-g)};a.easing["easeInOut"+f]=function(g){return g<0.5?e(g*2)/2:1-e(g*-2+2)/2}})})()})(jQuery);(function(d,e){var b=0,c={},a={};c.height=c.paddingTop=c.paddingBottom=c.borderTopWidth=c.borderBottomWidth="hide";a.height=a.paddingTop=a.paddingBottom=a.borderTopWidth=a.borderBottomWidth="show";d.widget("ui.accordion",{version:"1.10.3",options:{active:0,animate:{},collapsible:false,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var f=this.options;this.prevShow=this.prevHide=d();this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist");if(!f.collapsible&&(f.active===false||f.active==null)){f.active=0}this._processPanels();if(f.active<0){f.active+=this.headers.length}this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:!this.active.length?d():this.active.next(),content:!this.active.length?d():this.active.next()}},_createIcons:function(){var f=this.options.icons;if(f){d("").addClass("ui-accordion-header-icon ui-icon "+f.header).prependTo(this.headers);this.active.children(".ui-accordion-header-icon").removeClass(f.header).addClass(f.activeHeader);this.headers.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var f;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){if(/^ui-accordion/.test(this.id)){this.removeAttribute("id")}});this._destroyIcons();f=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){if(/^ui-accordion/.test(this.id)){this.removeAttribute("id")}});if(this.options.heightStyle!=="content"){f.css("height","")}},_setOption:function(f,g){if(f==="active"){this._activate(g);return}if(f==="event"){if(this.options.event){this._off(this.headers,this.options.event)}this._setupEvents(g)}this._super(f,g);if(f==="collapsible"&&!g&&this.options.active===false){this._activate(0)}if(f==="icons"){this._destroyIcons();if(g){this._createIcons()}}if(f==="disabled"){this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!g)}},_keydown:function(i){if(i.altKey||i.ctrlKey){return}var j=d.ui.keyCode,h=this.headers.length,f=this.headers.index(i.target),g=false;switch(i.keyCode){case j.RIGHT:case j.DOWN:g=this.headers[(f+1)%h];break;case j.LEFT:case j.UP:g=this.headers[(f-1+h)%h];break;case j.SPACE:case j.ENTER:this._eventHandler(i);break;case j.HOME:g=this.headers[0];break;case j.END:g=this.headers[h-1];break}if(g){d(i.target).attr("tabIndex",-1);d(g).attr("tabIndex",0);g.focus();i.preventDefault()}},_panelKeyDown:function(f){if(f.keyCode===d.ui.keyCode.UP&&f.ctrlKey){d(f.currentTarget).prev().focus()}},refresh:function(){var f=this.options;this._processPanels();if((f.active===false&&f.collapsible===true)||!this.headers.length){f.active=false;this.active=d()}else{if(f.active===false){this._activate(0)}else{if(this.active.length&&!d.contains(this.element[0],this.active[0])){if(this.headers.length===this.headers.find(".ui-state-disabled").length){f.active=false;this.active=d()}else{this._activate(Math.max(0,f.active-1))}}else{f.active=this.headers.index(this.active)}}}this._destroyIcons();this._refresh()},_processPanels:function(){this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all");this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide()},_refresh:function(){var j,h=this.options,g=h.heightStyle,i=this.element.parent(),f=this.accordionId="ui-accordion-"+(this.element.attr("id")||++b);this.active=this._findActive(h.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all");this.active.next().addClass("ui-accordion-content-active").show();this.headers.attr("role","tab").each(function(n){var o=d(this),m=o.attr("id"),k=o.next(),l=k.attr("id");if(!m){m=f+"-header-"+n;o.attr("id",m)}if(!l){l=f+"-panel-"+n;k.attr("id",l)}o.attr("aria-controls",l);k.attr("aria-labelledby",m)}).next().attr("role","tabpanel");this.headers.not(this.active).attr({"aria-selected":"false",tabIndex:-1}).next().attr({"aria-expanded":"false","aria-hidden":"true"}).hide();if(!this.active.length){this.headers.eq(0).attr("tabIndex",0)}else{this.active.attr({"aria-selected":"true",tabIndex:0}).next().attr({"aria-expanded":"true","aria-hidden":"false"})}this._createIcons();this._setupEvents(h.event);if(g==="fill"){j=i.height();this.element.siblings(":visible").each(function(){var l=d(this),k=l.css("position");if(k==="absolute"||k==="fixed"){return}j-=l.outerHeight(true)});this.headers.each(function(){j-=d(this).outerHeight(true)});this.headers.next().each(function(){d(this).height(Math.max(0,j-d(this).innerHeight()+d(this).height()))}).css("overflow","auto")}else{if(g==="auto"){j=0;this.headers.next().each(function(){j=Math.max(j,d(this).css("height","").height())}).height(j)}}},_activate:function(f){var g=this._findActive(f)[0];if(g===this.active[0]){return}g=g||this.active[0];this._eventHandler({target:g,currentTarget:g,preventDefault:d.noop})},_findActive:function(f){return typeof f==="number"?this.headers.eq(f):d()},_setupEvents:function(g){var f={keydown:"_keydown"};if(g){d.each(g.split(" "),function(i,h){f[h]="_eventHandler"})}this._off(this.headers.add(this.headers.next()));this._on(this.headers,f);this._on(this.headers.next(),{keydown:"_panelKeyDown"});this._hoverable(this.headers);this._focusable(this.headers)},_eventHandler:function(f){var n=this.options,i=this.active,j=d(f.currentTarget),l=j[0]===i[0],g=l&&n.collapsible,h=g?d():j.next(),k=i.next(),m={oldHeader:i,oldPanel:k,newHeader:g?d():j,newPanel:h};f.preventDefault();if((l&&!n.collapsible)||(this._trigger("beforeActivate",f,m)===false)){return}n.active=g?false:this.headers.index(j);this.active=l?d():j;this._toggle(m);i.removeClass("ui-accordion-header-active ui-state-active");if(n.icons){i.children(".ui-accordion-header-icon").removeClass(n.icons.activeHeader).addClass(n.icons.header)}if(!l){j.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top");if(n.icons){j.children(".ui-accordion-header-icon").removeClass(n.icons.header).addClass(n.icons.activeHeader)}j.next().addClass("ui-accordion-content-active")}},_toggle:function(h){var f=h.newPanel,g=this.prevShow.length?this.prevShow:h.oldPanel;this.prevShow.add(this.prevHide).stop(true,true);this.prevShow=f;this.prevHide=g;if(this.options.animate){this._animate(f,g,h)}else{g.hide();f.show();this._toggleComplete(h)}g.attr({"aria-expanded":"false","aria-hidden":"true"});g.prev().attr("aria-selected","false");if(f.length&&g.length){g.prev().attr("tabIndex",-1)}else{if(f.length){this.headers.filter(function(){return d(this).attr("tabIndex")===0}).attr("tabIndex",-1)}}f.attr({"aria-expanded":"true","aria-hidden":"false"}).prev().attr({"aria-selected":"true",tabIndex:0})},_animate:function(f,n,j){var m,l,i,k=this,o=0,p=f.length&&(!n.length||(f.index()",options:{appendTo:null,autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var f,d,g,i=this.element[0].nodeName.toLowerCase(),h=i==="textarea",e=i==="input";this.isMultiLine=h?true:e?false:this.element.prop("isContentEditable");this.valueMethod=this.element[h||e?"val":"text"];this.isNewMenu=true;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off");this._on(this.element,{keydown:function(j){if(this.element.prop("readOnly")){f=true;g=true;d=true;return}f=false;g=false;d=false;var k=a.ui.keyCode;switch(j.keyCode){case k.PAGE_UP:f=true;this._move("previousPage",j);break;case k.PAGE_DOWN:f=true;this._move("nextPage",j);break;case k.UP:f=true;this._keyEvent("previous",j);break;case k.DOWN:f=true;this._keyEvent("next",j);break;case k.ENTER:case k.NUMPAD_ENTER:if(this.menu.active){f=true;j.preventDefault();this.menu.select(j)}break;case k.TAB:if(this.menu.active){this.menu.select(j)}break;case k.ESCAPE:if(this.menu.element.is(":visible")){this._value(this.term);this.close(j);j.preventDefault()}break;default:d=true;this._searchTimeout(j);break}},keypress:function(j){if(f){f=false;if(!this.isMultiLine||this.menu.element.is(":visible")){j.preventDefault()}return}if(d){return}var k=a.ui.keyCode;switch(j.keyCode){case k.PAGE_UP:this._move("previousPage",j);break;case k.PAGE_DOWN:this._move("nextPage",j);break;case k.UP:this._keyEvent("previous",j);break;case k.DOWN:this._keyEvent("next",j);break}},input:function(j){if(g){g=false;j.preventDefault();return}this._searchTimeout(j)},focus:function(){this.selectedItem=null;this.previous=this._value()},blur:function(j){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching);this.close(j);this._change(j)}});this._initSource();this.menu=a("
      ").addClass("ui-autocomplete ui-front").appendTo(this._appendTo()).menu({role:null}).hide().data("ui-menu");this._on(this.menu.element,{mousedown:function(j){j.preventDefault();this.cancelBlur=true;this._delay(function(){delete this.cancelBlur});var k=this.menu.element[0];if(!a(j.target).closest(".ui-menu-item").length){this._delay(function(){var l=this;this.document.one("mousedown",function(m){if(m.target!==l.element[0]&&m.target!==k&&!a.contains(k,m.target)){l.close()}})})}},menufocus:function(k,l){if(this.isNewMenu){this.isNewMenu=false;if(k.originalEvent&&/^mouse/.test(k.originalEvent.type)){this.menu.blur();this.document.one("mousemove",function(){a(k.target).trigger(k.originalEvent)});return}}var j=l.item.data("ui-autocomplete-item");if(false!==this._trigger("focus",k,{item:j})){if(k.originalEvent&&/^key/.test(k.originalEvent.type)){this._value(j.value)}}else{this.liveRegion.text(j.value)}},menuselect:function(l,m){var k=m.item.data("ui-autocomplete-item"),j=this.previous;if(this.element[0]!==this.document[0].activeElement){this.element.focus();this.previous=j;this._delay(function(){this.previous=j;this.selectedItem=k})}if(false!==this._trigger("select",l,{item:k})){this._value(k.value)}this.term=this._value();this.close(l);this.selectedItem=k}});this.liveRegion=a("",{role:"status","aria-live":"polite"}).addClass("ui-helper-hidden-accessible").insertBefore(this.element);this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching);this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete");this.menu.element.remove();this.liveRegion.remove()},_setOption:function(d,e){this._super(d,e);if(d==="source"){this._initSource()}if(d==="appendTo"){this.menu.element.appendTo(this._appendTo())}if(d==="disabled"&&e&&this.xhr){this.xhr.abort()}},_appendTo:function(){var d=this.options.appendTo;if(d){d=d.jquery||d.nodeType?a(d):this.document.find(d).eq(0)}if(!d){d=this.element.closest(".ui-front")}if(!d.length){d=this.document[0].body}return d},_initSource:function(){var f,d,e=this;if(a.isArray(this.options.source)){f=this.options.source;this.source=function(h,g){g(a.ui.autocomplete.filter(f,h.term))}}else{if(typeof this.options.source==="string"){d=this.options.source;this.source=function(h,g){if(e.xhr){e.xhr.abort()}e.xhr=a.ajax({url:d,data:h,dataType:"json",success:function(i){g(i)},error:function(){g([])}})}}else{this.source=this.options.source}}},_searchTimeout:function(d){clearTimeout(this.searching);this.searching=this._delay(function(){if(this.term!==this._value()){this.selectedItem=null;this.search(null,d)}},this.options.delay)},search:function(e,d){e=e!=null?e:this._value();this.term=this._value();if(e.length").append(a("").text(e.label)).appendTo(d)},_move:function(e,d){if(!this.menu.element.is(":visible")){this.search(null,d);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term);this.menu.blur();return}this.menu[e](d)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,d){if(!this.isMultiLine||this.menu.element.is(":visible")){this._move(e,d);d.preventDefault()}}});a.extend(a.ui.autocomplete,{escapeRegex:function(d){return d.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(f,d){var e=new RegExp(a.ui.autocomplete.escapeRegex(d),"i");return a.grep(f,function(g){return e.test(g.label||g.value||g)})}});a.widget("ui.autocomplete",a.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(d){return d+(d>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var d;this._superApply(arguments);if(this.options.disabled||this.cancelSearch){return}if(e&&e.length){d=this.options.messages.results(e.length)}else{d=this.options.messages.noResults}this.liveRegion.text(d)}})}(jQuery));(function(f,b){var k,e,a,h,i="ui-button ui-widget ui-state-default ui-corner-all",c="ui-state-hover ui-state-active ",g="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",j=function(){var l=f(this);setTimeout(function(){l.find(":ui-button").button("refresh")},1)},d=function(m){var l=m.name,n=m.form,o=f([]);if(l){l=l.replace(/'/g,"\\'");if(n){o=f(n).find("[name='"+l+"']")}else{o=f("[name='"+l+"']",m.ownerDocument).filter(function(){return !this.form})}}return o};f.widget("ui.button",{version:"1.10.3",defaultElement:"").addClass(this._triggerClass).html(!h?l:e("").attr({src:h,alt:l,title:l})));j[k?"before":"after"](m.trigger);m.trigger.click(function(){if(e.datepicker._datepickerShowing&&e.datepicker._lastInput===j[0]){e.datepicker._hideDatepicker()}else{if(e.datepicker._datepickerShowing&&e.datepicker._lastInput!==j[0]){e.datepicker._hideDatepicker();e.datepicker._showDatepicker(j[0])}else{e.datepicker._showDatepicker(j[0])}}return false})}},_autoSize:function(o){if(this._get(o,"autoSize")&&!o.inline){var l,j,k,n,m=new Date(2009,12-1,20),h=this._get(o,"dateFormat");if(h.match(/[DM]/)){l=function(i){j=0;k=0;for(n=0;nj){j=i[n].length;k=n}}return k};m.setMonth(l(this._get(o,(h.match(/MM/)?"monthNames":"monthNamesShort"))));m.setDate(l(this._get(o,(h.match(/DD/)?"dayNames":"dayNamesShort")))+20-m.getDay())}o.input.attr("size",this._formatDate(o,m).length)}},_inlineDatepicker:function(i,h){var j=e(i);if(j.hasClass(this.markerClassName)){return}j.addClass(this.markerClassName).append(h.dpDiv);e.data(i,f,h);this._setDate(h,this._getDefaultDate(h),true);this._updateDatepicker(h);this._updateAlternate(h);if(h.settings.disabled){this._disableDatepicker(i)}h.dpDiv.css("display","block")},_dialogDatepicker:function(o,i,m,j,n){var h,r,l,q,p,k=this._dialogInst;if(!k){this.uuid+=1;h="dp"+this.uuid;this._dialogInput=e("");this._dialogInput.keydown(this._doKeyDown);e("body").append(this._dialogInput);k=this._dialogInst=this._newInst(this._dialogInput,false);k.settings={};e.data(this._dialogInput[0],f,k)}a(k.settings,j||{});i=(i&&i.constructor===Date?this._formatDate(k,i):i);this._dialogInput.val(i);this._pos=(n?(n.length?n:[n.pageX,n.pageY]):null);if(!this._pos){r=document.documentElement.clientWidth;l=document.documentElement.clientHeight;q=document.documentElement.scrollLeft||document.body.scrollLeft;p=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[(r/2)-100+q,(l/2)-150+p]}this._dialogInput.css("left",(this._pos[0]+20)+"px").css("top",this._pos[1]+"px");k.settings.onSelect=m;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);if(e.blockUI){e.blockUI(this.dpDiv)}e.data(this._dialogInput[0],f,k);return this},_destroyDatepicker:function(j){var k,h=e(j),i=e.data(j,f);if(!h.hasClass(this.markerClassName)){return}k=j.nodeName.toLowerCase();e.removeData(j,f);if(k==="input"){i.append.remove();i.trigger.remove();h.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else{if(k==="div"||k==="span"){h.removeClass(this.markerClassName).empty()}}},_enableDatepicker:function(k){var l,j,h=e(k),i=e.data(k,f);if(!h.hasClass(this.markerClassName)){return}l=k.nodeName.toLowerCase();if(l==="input"){k.disabled=false;i.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else{if(l==="div"||l==="span"){j=h.children("."+this._inlineClass);j.children().removeClass("ui-state-disabled");j.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",false)}}this._disabledInputs=e.map(this._disabledInputs,function(m){return(m===k?null:m)})},_disableDatepicker:function(k){var l,j,h=e(k),i=e.data(k,f);if(!h.hasClass(this.markerClassName)){return}l=k.nodeName.toLowerCase();if(l==="input"){k.disabled=true;i.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else{if(l==="div"||l==="span"){j=h.children("."+this._inlineClass);j.children().addClass("ui-state-disabled");j.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",true)}}this._disabledInputs=e.map(this._disabledInputs,function(m){return(m===k?null:m)});this._disabledInputs[this._disabledInputs.length]=k},_isDisabledDatepicker:function(j){if(!j){return false}for(var h=0;h-1)}},_doKeyUp:function(j){var h,k=e.datepicker._getInst(j.target);if(k.input.val()!==k.lastVal){try{h=e.datepicker.parseDate(e.datepicker._get(k,"dateFormat"),(k.input?k.input.val():null),e.datepicker._getFormatConfig(k));if(h){e.datepicker._setDateFromField(k);e.datepicker._updateAlternate(k);e.datepicker._updateDatepicker(k)}}catch(i){}}return true},_showDatepicker:function(i){i=i.target||i;if(i.nodeName.toLowerCase()!=="input"){i=e("input",i.parentNode)[0]}if(e.datepicker._isDisabledDatepicker(i)||e.datepicker._lastInput===i){return}var k,o,j,m,n,h,l;k=e.datepicker._getInst(i);if(e.datepicker._curInst&&e.datepicker._curInst!==k){e.datepicker._curInst.dpDiv.stop(true,true);if(k&&e.datepicker._datepickerShowing){e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])}}o=e.datepicker._get(k,"beforeShow");j=o?o.apply(i,[i,k]):{};if(j===false){return}a(k.settings,j);k.lastVal=null;e.datepicker._lastInput=i;e.datepicker._setDateFromField(k);if(e.datepicker._inDialog){i.value=""}if(!e.datepicker._pos){e.datepicker._pos=e.datepicker._findPos(i);e.datepicker._pos[1]+=i.offsetHeight}m=false;e(i).parents().each(function(){m|=e(this).css("position")==="fixed";return !m});n={left:e.datepicker._pos[0],top:e.datepicker._pos[1]};e.datepicker._pos=null;k.dpDiv.empty();k.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});e.datepicker._updateDatepicker(k);n=e.datepicker._checkOffset(k,n,m);k.dpDiv.css({position:(e.datepicker._inDialog&&e.blockUI?"static":(m?"fixed":"absolute")),display:"none",left:n.left+"px",top:n.top+"px"});if(!k.inline){h=e.datepicker._get(k,"showAnim");l=e.datepicker._get(k,"duration");k.dpDiv.zIndex(e(i).zIndex()+1);e.datepicker._datepickerShowing=true;if(e.effects&&e.effects.effect[h]){k.dpDiv.show(h,e.datepicker._get(k,"showOptions"),l)}else{k.dpDiv[h||"show"](h?l:null)}if(e.datepicker._shouldFocusInput(k)){k.input.focus()}e.datepicker._curInst=k}},_updateDatepicker:function(j){this.maxRows=4;c=j;j.dpDiv.empty().append(this._generateHTML(j));this._attachHandlers(j);j.dpDiv.find("."+this._dayOverClass+" a").mouseover();var l,h=this._getNumberOfMonths(j),k=h[1],i=17;j.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");if(k>1){j.dpDiv.addClass("ui-datepicker-multi-"+k).css("width",(i*k)+"em")}j.dpDiv[(h[0]!==1||h[1]!==1?"add":"remove")+"Class"]("ui-datepicker-multi");j.dpDiv[(this._get(j,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");if(j===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(j)){j.input.focus()}if(j.yearshtml){l=j.yearshtml;setTimeout(function(){if(l===j.yearshtml&&j.yearshtml){j.dpDiv.find("select.ui-datepicker-year:first").replaceWith(j.yearshtml)}l=j.yearshtml=null},0)}},_shouldFocusInput:function(h){return h.input&&h.input.is(":visible")&&!h.input.is(":disabled")&&!h.input.is(":focus")},_checkOffset:function(m,k,j){var l=m.dpDiv.outerWidth(),p=m.dpDiv.outerHeight(),o=m.input?m.input.outerWidth():0,h=m.input?m.input.outerHeight():0,n=document.documentElement.clientWidth+(j?0:e(document).scrollLeft()),i=document.documentElement.clientHeight+(j?0:e(document).scrollTop());k.left-=(this._get(m,"isRTL")?(l-o):0);k.left-=(j&&k.left===m.input.offset().left)?e(document).scrollLeft():0;k.top-=(j&&k.top===(m.input.offset().top+h))?e(document).scrollTop():0;k.left-=Math.min(k.left,(k.left+l>n&&n>l)?Math.abs(k.left+l-n):0);k.top-=Math.min(k.top,(k.top+p>i&&i>p)?Math.abs(p+h):0);return k},_findPos:function(k){var h,j=this._getInst(k),i=this._get(j,"isRTL");while(k&&(k.type==="hidden"||k.nodeType!==1||e.expr.filters.hidden(k))){k=k[i?"previousSibling":"nextSibling"]}h=e(k).offset();return[h.left,h.top]},_hideDatepicker:function(j){var i,m,l,h,k=this._curInst;if(!k||(j&&k!==e.data(j,f))){return}if(this._datepickerShowing){i=this._get(k,"showAnim");m=this._get(k,"duration");l=function(){e.datepicker._tidyDialog(k)};if(e.effects&&(e.effects.effect[i]||e.effects[i])){k.dpDiv.hide(i,e.datepicker._get(k,"showOptions"),m,l)}else{k.dpDiv[(i==="slideDown"?"slideUp":(i==="fadeIn"?"fadeOut":"hide"))]((i?m:null),l)}if(!i){l()}this._datepickerShowing=false;h=this._get(k,"onClose");if(h){h.apply((k.input?k.input[0]:null),[(k.input?k.input.val():""),k])}this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(e.blockUI){e.unblockUI();e("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(h){h.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(i){if(!e.datepicker._curInst){return}var h=e(i.target),j=e.datepicker._getInst(h[0]);if(((h[0].id!==e.datepicker._mainDivId&&h.parents("#"+e.datepicker._mainDivId).length===0&&!h.hasClass(e.datepicker.markerClassName)&&!h.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&!(e.datepicker._inDialog&&e.blockUI)))||(h.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==j)){e.datepicker._hideDatepicker()}},_adjustDate:function(l,k,j){var i=e(l),h=this._getInst(i[0]);if(this._isDisabledDatepicker(i[0])){return}this._adjustInstDate(h,k+(j==="M"?this._get(h,"showCurrentAtPos"):0),j);this._updateDatepicker(h)},_gotoToday:function(k){var h,j=e(k),i=this._getInst(j[0]);if(this._get(i,"gotoCurrent")&&i.currentDay){i.selectedDay=i.currentDay;i.drawMonth=i.selectedMonth=i.currentMonth;i.drawYear=i.selectedYear=i.currentYear}else{h=new Date();i.selectedDay=h.getDate();i.drawMonth=i.selectedMonth=h.getMonth();i.drawYear=i.selectedYear=h.getFullYear()}this._notifyChange(i);this._adjustDate(j)},_selectMonthYear:function(l,h,k){var j=e(l),i=this._getInst(j[0]);i["selected"+(k==="M"?"Month":"Year")]=i["draw"+(k==="M"?"Month":"Year")]=parseInt(h.options[h.selectedIndex].value,10);this._notifyChange(i);this._adjustDate(j)},_selectDay:function(m,k,h,l){var i,j=e(m);if(e(l).hasClass(this._unselectableClass)||this._isDisabledDatepicker(j[0])){return}i=this._getInst(j[0]);i.selectedDay=i.currentDay=e("a",l).html();i.selectedMonth=i.currentMonth=k;i.selectedYear=i.currentYear=h;this._selectDate(m,this._formatDate(i,i.currentDay,i.currentMonth,i.currentYear))},_clearDate:function(i){var h=e(i);this._selectDate(h,"")},_selectDate:function(l,h){var i,k=e(l),j=this._getInst(k[0]);h=(h!=null?h:this._formatDate(j));if(j.input){j.input.val(h)}this._updateAlternate(j);i=this._get(j,"onSelect");if(i){i.apply((j.input?j.input[0]:null),[h,j])}else{if(j.input){j.input.trigger("change")}}if(j.inline){this._updateDatepicker(j)}else{this._hideDatepicker();this._lastInput=j.input[0];if(typeof(j.input[0])!=="object"){j.input.focus()}this._lastInput=null}},_updateAlternate:function(l){var k,j,h,i=this._get(l,"altField");if(i){k=this._get(l,"altFormat")||this._get(l,"dateFormat");j=this._getDate(l);h=this.formatDate(k,j,this._getFormatConfig(l));e(i).each(function(){e(this).val(h)})}},noWeekends:function(i){var h=i.getDay();return[(h>0&&h<6),""]},iso8601Week:function(h){var i,j=new Date(h.getTime());j.setDate(j.getDate()+4-(j.getDay()||7));i=j.getTime();j.setMonth(0);j.setDate(1);return Math.floor(Math.round((i-j)/86400000)/7)+1},parseDate:function(x,s,z){if(x==null||s==null){throw"Invalid arguments"}s=(typeof s==="object"?s.toString():s+"");if(s===""){return null}var k,u,i,y=0,n=(z?z.shortYearCutoff:null)||this._defaults.shortYearCutoff,j=(typeof n!=="string"?n:new Date().getFullYear()%100+parseInt(n,10)),q=(z?z.dayNamesShort:null)||this._defaults.dayNamesShort,B=(z?z.dayNames:null)||this._defaults.dayNames,h=(z?z.monthNamesShort:null)||this._defaults.monthNamesShort,l=(z?z.monthNames:null)||this._defaults.monthNames,m=-1,C=-1,w=-1,p=-1,v=false,A,r=function(E){var F=(k+1-1){C=1;w=p;do{u=this._getDaysInMonth(m,C-1);if(w<=u){break}C++;w-=u}while(true)}A=this._daylightSavingAdjust(new Date(m,C-1,w));if(A.getFullYear()!==m||A.getMonth()+1!==C||A.getDate()!==w){throw"Invalid date"}return A},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(((1970-1)*365+Math.floor(1970/4)-Math.floor(1970/100)+Math.floor(1970/400))*24*60*60*10000000),formatDate:function(q,k,l){if(!k){return""}var s,t=(l?l.dayNamesShort:null)||this._defaults.dayNamesShort,i=(l?l.dayNames:null)||this._defaults.dayNames,o=(l?l.monthNamesShort:null)||this._defaults.monthNamesShort,m=(l?l.monthNames:null)||this._defaults.monthNames,r=function(u){var v=(s+112?h.getHours()+2:0);return h},_setDate:function(n,k,m){var h=!k,j=n.selectedMonth,l=n.selectedYear,i=this._restrictMinMax(n,this._determineDate(n,k,new Date()));n.selectedDay=n.currentDay=i.getDate();n.drawMonth=n.selectedMonth=n.currentMonth=i.getMonth();n.drawYear=n.selectedYear=n.currentYear=i.getFullYear();if((j!==n.selectedMonth||l!==n.selectedYear)&&!m){this._notifyChange(n)}this._adjustInstDate(n);if(n.input){n.input.val(h?"":this._formatDate(n))}},_getDate:function(i){var h=(!i.currentYear||(i.input&&i.input.val()==="")?null:this._daylightSavingAdjust(new Date(i.currentYear,i.currentMonth,i.currentDay)));return h},_attachHandlers:function(i){var h=this._get(i,"stepMonths"),j="#"+i.id.replace(/\\\\/g,"\\");i.dpDiv.find("[data-handler]").map(function(){var k={prev:function(){e.datepicker._adjustDate(j,-h,"M")},next:function(){e.datepicker._adjustDate(j,+h,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(j)},selectDay:function(){e.datepicker._selectDay(j,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this);return false},selectMonth:function(){e.datepicker._selectMonthYear(j,this,"M");return false},selectYear:function(){e.datepicker._selectMonthYear(j,this,"Y");return false}};e(this).bind(this.getAttribute("data-event"),k[this.getAttribute("data-handler")])})},_generateHTML:function(X){var A,z,S,K,l,ab,V,O,ae,I,ai,s,u,t,i,aa,q,D,ad,Q,aj,C,H,r,m,T,M,P,N,p,F,v,W,Z,k,ac,ag,L,w,Y=new Date(),B=this._daylightSavingAdjust(new Date(Y.getFullYear(),Y.getMonth(),Y.getDate())),af=this._get(X,"isRTL"),ah=this._get(X,"showButtonPanel"),R=this._get(X,"hideIfNoPrevNext"),G=this._get(X,"navigationAsDateFormat"),x=this._getNumberOfMonths(X),o=this._get(X,"showCurrentAtPos"),J=this._get(X,"stepMonths"),E=(x[0]!==1||x[1]!==1),j=this._daylightSavingAdjust((!X.currentDay?new Date(9999,9,9):new Date(X.currentYear,X.currentMonth,X.currentDay))),n=this._getMinMaxDate(X,"min"),y=this._getMinMaxDate(X,"max"),h=X.drawMonth-o,U=X.drawYear;if(h<0){h+=12;U--}if(y){A=this._daylightSavingAdjust(new Date(y.getFullYear(),y.getMonth()-(x[0]*x[1])+1,y.getDate()));A=(n&&AA){h--;if(h<0){h=11;U--}}}X.drawMonth=h;X.drawYear=U;z=this._get(X,"prevText");z=(!G?z:this.formatDate(z,this._daylightSavingAdjust(new Date(U,h-J,1)),this._getFormatConfig(X)));S=(this._canAdjustMonth(X,-1,U,h)?""+z+"":(R?"":""+z+""));K=this._get(X,"nextText");K=(!G?K:this.formatDate(K,this._daylightSavingAdjust(new Date(U,h+J,1)),this._getFormatConfig(X)));l=(this._canAdjustMonth(X,+1,U,h)?""+K+"":(R?"":""+K+""));ab=this._get(X,"currentText");V=(this._get(X,"gotoCurrent")&&X.currentDay?j:B);ab=(!G?ab:this.formatDate(ab,V,this._getFormatConfig(X)));O=(!X.inline?"":"");ae=(ah)?"
      "+(af?O:"")+(this._isInRange(X,V)?"":"")+(af?"":O)+"
      ":"";I=parseInt(this._get(X,"firstDay"),10);I=(isNaN(I)?0:I);ai=this._get(X,"showWeek");s=this._get(X,"dayNames");u=this._get(X,"dayNamesMin");t=this._get(X,"monthNames");i=this._get(X,"monthNamesShort");aa=this._get(X,"beforeShowDay");q=this._get(X,"showOtherMonths");D=this._get(X,"selectOtherMonths");ad=this._getDefaultDate(X);Q="";aj;for(C=0;C1){switch(r){case 0:M+=" ui-datepicker-group-first";T=" ui-corner-"+(af?"right":"left");break;case x[1]-1:M+=" ui-datepicker-group-last";T=" ui-corner-"+(af?"left":"right");break;default:M+=" ui-datepicker-group-middle";T="";break}}M+="'>"}M+="
      "+(/all|left/.test(T)&&C===0?(af?l:S):"")+(/all|right/.test(T)&&C===0?(af?S:l):"")+this._generateMonthYearHeader(X,h,U,n,y,C>0||r>0,t,i)+"
      ";P=(ai?"":"");for(aj=0;aj<7;aj++){N=(aj+I)%7;P+="=5?" class='ui-datepicker-week-end'":"")+">"+u[N]+""}M+=P+"";p=this._getDaysInMonth(U,h);if(U===X.selectedYear&&h===X.selectedMonth){X.selectedDay=Math.min(X.selectedDay,p)}F=(this._getFirstDayOfMonth(U,h)-I+7)%7;v=Math.ceil((F+p)/7);W=(E?this.maxRows>v?this.maxRows:v:v);this.maxRows=W;Z=this._daylightSavingAdjust(new Date(U,h,1-F));for(k=0;k";ac=(!ai?"":"");for(aj=0;aj<7;aj++){ag=(aa?aa.apply((X.input?X.input[0]:null),[Z]):[true,""]);L=(Z.getMonth()!==h);w=(L&&!D)||!ag[0]||(n&&Zy);ac+="";Z.setDate(Z.getDate()+1);Z=this._daylightSavingAdjust(Z)}M+=ac+""}h++;if(h>11){h=0;U++}M+="
      "+this._get(X,"weekHeader")+"
      "+this._get(X,"calculateWeek")(Z)+""+(L&&!q?" ":(w?""+Z.getDate()+"":""+Z.getDate()+""))+"
      "+(E?"
    "+((x[0]>0&&r===x[1]-1)?"
    ":""):"");H+=M}Q+=H}Q+=ae;X._keyEvent=false;return Q},_generateMonthYearHeader:function(l,j,t,n,r,u,p,h){var y,i,z,w,m,v,s,o,k=this._get(l,"changeMonth"),A=this._get(l,"changeYear"),B=this._get(l,"showMonthAfterYear"),q="
    ",x="";if(u||!k){x+=""+p[j]+""}else{y=(n&&n.getFullYear()===t);i=(r&&r.getFullYear()===t);x+=""}if(!B){q+=x+(u||!(k&&A)?" ":"")}if(!l.yearshtml){l.yearshtml="";if(u||!A){q+=""+t+""}else{w=this._get(l,"yearRange").split(":");m=new Date().getFullYear();v=function(D){var C=(D.match(/c[+\-].*/)?t+parseInt(D.substring(1),10):(D.match(/[+\-].*/)?m+parseInt(D,10):parseInt(D,10)));return(isNaN(C)?m:C)};s=v(w[0]);o=Math.max(s,v(w[1]||""));s=(n?Math.max(s,n.getFullYear()):s);o=(r?Math.min(o,r.getFullYear()):o);l.yearshtml+="";q+=l.yearshtml;l.yearshtml=null}}q+=this._get(l,"yearSuffix");if(B){q+=(u||!(k&&A)?" ":"")+x}q+="
    ";return q},_adjustInstDate:function(k,n,m){var j=k.drawYear+(m==="Y"?n:0),l=k.drawMonth+(m==="M"?n:0),h=Math.min(k.selectedDay,this._getDaysInMonth(j,l))+(m==="D"?n:0),i=this._restrictMinMax(k,this._daylightSavingAdjust(new Date(j,l,h)));k.selectedDay=i.getDate();k.drawMonth=k.selectedMonth=i.getMonth();k.drawYear=k.selectedYear=i.getFullYear();if(m==="M"||m==="Y"){this._notifyChange(k)}},_restrictMinMax:function(k,i){var j=this._getMinMaxDate(k,"min"),l=this._getMinMaxDate(k,"max"),h=(j&&il?l:h)},_notifyChange:function(i){var h=this._get(i,"onChangeMonthYear");if(h){h.apply((i.input?i.input[0]:null),[i.selectedYear,i.selectedMonth+1,i])}},_getNumberOfMonths:function(i){var h=this._get(i,"numberOfMonths");return(h==null?[1,1]:(typeof h==="number"?[1,h]:h))},_getMinMaxDate:function(i,h){return this._determineDate(i,this._get(i,h+"Date"),null)},_getDaysInMonth:function(h,i){return 32-this._daylightSavingAdjust(new Date(h,i,32)).getDate()},_getFirstDayOfMonth:function(h,i){return new Date(h,i,1).getDay()},_canAdjustMonth:function(k,m,j,l){var h=this._getNumberOfMonths(k),i=this._daylightSavingAdjust(new Date(j,l+(m<0?m:h[0]*h[1]),1));if(m<0){i.setDate(this._getDaysInMonth(i.getFullYear(),i.getMonth()))}return this._isInRange(k,i)},_isInRange:function(l,j){var i,o,k=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),p=null,m=null,n=this._get(l,"yearRange");if(n){i=n.split(":");o=new Date().getFullYear();p=parseInt(i[0],10);m=parseInt(i[1],10);if(i[0].match(/[+\-].*/)){p+=o}if(i[1].match(/[+\-].*/)){m+=o}}return((!k||j.getTime()>=k.getTime())&&(!h||j.getTime()<=h.getTime())&&(!p||j.getFullYear()>=p)&&(!m||j.getFullYear()<=m))},_getFormatConfig:function(h){var i=this._get(h,"shortYearCutoff");i=(typeof i!=="string"?i:new Date().getFullYear()%100+parseInt(i,10));return{shortYearCutoff:i,dayNamesShort:this._get(h,"dayNamesShort"),dayNames:this._get(h,"dayNames"),monthNamesShort:this._get(h,"monthNamesShort"),monthNames:this._get(h,"monthNames")}},_formatDate:function(k,h,l,j){if(!h){k.currentDay=k.selectedDay;k.currentMonth=k.selectedMonth;k.currentYear=k.selectedYear}var i=(h?(typeof h==="object"?h:this._daylightSavingAdjust(new Date(j,l,h))):this._daylightSavingAdjust(new Date(k.currentYear,k.currentMonth,k.currentDay)));return this.formatDate(this._get(k,"dateFormat"),i,this._getFormatConfig(k))}});function d(i){var h="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return i.delegate(h,"mouseout",function(){e(this).removeClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!==-1){e(this).removeClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!==-1){e(this).removeClass("ui-datepicker-next-hover")}}).delegate(h,"mouseover",function(){if(!e.datepicker._isDisabledDatepicker(c.inline?i.parent()[0]:c.input[0])){e(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");e(this).addClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!==-1){e(this).addClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!==-1){e(this).addClass("ui-datepicker-next-hover")}}})}function a(j,i){e.extend(j,i);for(var h in i){if(i[h]==null){j[h]=i[h]}}return j}e.fn.datepicker=function(i){if(!this.length){return this}if(!e.datepicker.initialized){e(document).mousedown(e.datepicker._checkExternalClick);e.datepicker.initialized=true}if(e("#"+e.datepicker._mainDivId).length===0){e("body").append(e.datepicker.dpDiv)}var h=Array.prototype.slice.call(arguments,1);if(typeof i==="string"&&(i==="isDisabled"||i==="getDate"||i==="widget")){return e.datepicker["_"+i+"Datepicker"].apply(e.datepicker,[this[0]].concat(h))}if(i==="option"&&arguments.length===2&&typeof arguments[1]==="string"){return e.datepicker["_"+i+"Datepicker"].apply(e.datepicker,[this[0]].concat(h))}return this.each(function(){typeof i==="string"?e.datepicker["_"+i+"Datepicker"].apply(e.datepicker,[this].concat(h)):e.datepicker._attachDatepicker(this,i)})};e.datepicker=new b();e.datepicker.initialized=false;e.datepicker.uuid=new Date().getTime();e.datepicker.version="1.10.3"})(jQuery);(function(c,d){var a={buttons:true,height:true,maxHeight:true,maxWidth:true,minHeight:true,minWidth:true,width:true},b={maxHeight:true,maxWidth:true,minHeight:true,minWidth:true};c.widget("ui.dialog",{version:"1.10.3",options:{appendTo:"body",autoOpen:true,buttons:[],closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:false,position:{my:"center",at:"center",of:window,collision:"fit",using:function(f){var e=c(this).css(f).offset().top;if(e<0){c(this).css("top",f.top-e)}}},resizable:true,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height};this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)};this.originalTitle=this.element.attr("title");this.options.title=this.options.title||this.originalTitle;this._createWrapper();this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog);this._createTitlebar();this._createButtonPane();if(this.options.draggable&&c.fn.draggable){this._makeDraggable()}if(this.options.resizable&&c.fn.resizable){this._makeResizable()}this._isOpen=false},_init:function(){if(this.options.autoOpen){this.open()}},_appendTo:function(){var e=this.options.appendTo;if(e&&(e.jquery||e.nodeType)){return c(e)}return this.document.find(e||"body").eq(0)},_destroy:function(){var f,e=this.originalPosition;this._destroyOverlay();this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach();this.uiDialog.stop(true,true).remove();if(this.originalTitle){this.element.attr("title",this.originalTitle)}f=e.parent.children().eq(e.index);if(f.length&&f[0]!==this.element[0]){f.before(this.element)}else{e.parent.append(this.element)}},widget:function(){return this.uiDialog},disable:c.noop,enable:c.noop,close:function(f){var e=this;if(!this._isOpen||this._trigger("beforeClose",f)===false){return}this._isOpen=false;this._destroyOverlay();if(!this.opener.filter(":focusable").focus().length){c(this.document[0].activeElement).blur()}this._hide(this.uiDialog,this.options.hide,function(){e._trigger("close",f)})},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(g,e){var f=!!this.uiDialog.nextAll(":visible").insertBefore(this.uiDialog).length;if(f&&!e){this._trigger("focus",g)}return f},open:function(){var e=this;if(this._isOpen){if(this._moveToTop()){this._focusTabbable()}return}this._isOpen=true;this.opener=c(this.document[0].activeElement);this._size();this._position();this._createOverlay();this._moveToTop(null,true);this._show(this.uiDialog,this.options.show,function(){e._focusTabbable();e._trigger("focus")});this._trigger("open")},_focusTabbable:function(){var e=this.element.find("[autofocus]");if(!e.length){e=this.element.find(":tabbable")}if(!e.length){e=this.uiDialogButtonPane.find(":tabbable")}if(!e.length){e=this.uiDialogTitlebarClose.filter(":tabbable")}if(!e.length){e=this.uiDialog}e.eq(0).focus()},_keepFocus:function(e){function f(){var h=this.document[0].activeElement,g=this.uiDialog[0]===h||c.contains(this.uiDialog[0],h);if(!g){this._focusTabbable()}}e.preventDefault();f.call(this);this._delay(f)},_createWrapper:function(){this.uiDialog=c("
    ").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo());this._on(this.uiDialog,{keydown:function(g){if(this.options.closeOnEscape&&!g.isDefaultPrevented()&&g.keyCode&&g.keyCode===c.ui.keyCode.ESCAPE){g.preventDefault();this.close(g);return}if(g.keyCode!==c.ui.keyCode.TAB){return}var f=this.uiDialog.find(":tabbable"),h=f.filter(":first"),e=f.filter(":last");if((g.target===e[0]||g.target===this.uiDialog[0])&&!g.shiftKey){h.focus(1);g.preventDefault()}else{if((g.target===h[0]||g.target===this.uiDialog[0])&&g.shiftKey){e.focus(1);g.preventDefault()}}},mousedown:function(e){if(this._moveToTop(e)){this._focusTabbable()}}});if(!this.element.find("[aria-describedby]").length){this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})}},_createTitlebar:function(){var e;this.uiDialogTitlebar=c("
    ").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog);this._on(this.uiDialogTitlebar,{mousedown:function(f){if(!c(f.target).closest(".ui-dialog-titlebar-close")){this.uiDialog.focus()}}});this.uiDialogTitlebarClose=c("").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:false}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar);this._on(this.uiDialogTitlebarClose,{click:function(f){f.preventDefault();this.close(f)}});e=c("").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar);this._title(e);this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(e){if(!this.options.title){e.html(" ")}e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=c("
    ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");this.uiButtonSet=c("
    ").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane);this._createButtons()},_createButtons:function(){var f=this,e=this.options.buttons;this.uiDialogButtonPane.remove();this.uiButtonSet.empty();if(c.isEmptyObject(e)||(c.isArray(e)&&!e.length)){this.uiDialog.removeClass("ui-dialog-buttons");return}c.each(e,function(g,h){var i,j;h=c.isFunction(h)?{click:h,text:g}:h;h=c.extend({type:"button"},h);i=h.click;h.click=function(){i.apply(f.element[0],arguments)};j={icons:h.icons,text:h.showText};delete h.icons;delete h.showText;c("",h).button(j).appendTo(f.uiButtonSet)});this.uiDialog.addClass("ui-dialog-buttons");this.uiDialogButtonPane.appendTo(this.uiDialog)},_makeDraggable:function(){var g=this,f=this.options;function e(h){return{position:h.position,offset:h.offset}}this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(h,i){c(this).addClass("ui-dialog-dragging");g._blockFrames();g._trigger("dragStart",h,e(i))},drag:function(h,i){g._trigger("drag",h,e(i))},stop:function(h,i){f.position=[i.position.left-g.document.scrollLeft(),i.position.top-g.document.scrollTop()];c(this).removeClass("ui-dialog-dragging");g._unblockFrames();g._trigger("dragStop",h,e(i))}})},_makeResizable:function(){var j=this,h=this.options,i=h.resizable,e=this.uiDialog.css("position"),g=typeof i==="string"?i:"n,e,s,w,se,sw,ne,nw";function f(k){return{originalPosition:k.originalPosition,originalSize:k.originalSize,position:k.position,size:k.size}}this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:h.maxWidth,maxHeight:h.maxHeight,minWidth:h.minWidth,minHeight:this._minHeight(),handles:g,start:function(k,l){c(this).addClass("ui-dialog-resizing");j._blockFrames();j._trigger("resizeStart",k,f(l))},resize:function(k,l){j._trigger("resize",k,f(l))},stop:function(k,l){h.height=c(this).height();h.width=c(this).width();c(this).removeClass("ui-dialog-resizing");j._unblockFrames();j._trigger("resizeStop",k,f(l))}}).css("position",e)},_minHeight:function(){var e=this.options;return e.height==="auto"?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");if(!e){this.uiDialog.show()}this.uiDialog.position(this.options.position);if(!e){this.uiDialog.hide()}},_setOptions:function(g){var h=this,f=false,e={};c.each(g,function(i,j){h._setOption(i,j);if(i in a){f=true}if(i in b){e[i]=j}});if(f){this._size();this._position()}if(this.uiDialog.is(":data(ui-resizable)")){this.uiDialog.resizable("option",e)}},_setOption:function(g,h){var f,i,e=this.uiDialog;if(g==="dialogClass"){e.removeClass(this.options.dialogClass).addClass(h)}if(g==="disabled"){return}this._super(g,h);if(g==="appendTo"){this.uiDialog.appendTo(this._appendTo())}if(g==="buttons"){this._createButtons()}if(g==="closeText"){this.uiDialogTitlebarClose.button({label:""+h})}if(g==="draggable"){f=e.is(":data(ui-draggable)");if(f&&!h){e.draggable("destroy")}if(!f&&h){this._makeDraggable()}}if(g==="position"){this._position()}if(g==="resizable"){i=e.is(":data(ui-resizable)");if(i&&!h){e.resizable("destroy")}if(i&&typeof h==="string"){e.resizable("option","handles",h)}if(!i&&h!==false){this._makeResizable()}}if(g==="title"){this._title(this.uiDialogTitlebar.find(".ui-dialog-title"))}},_size:function(){var e,g,h,f=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0});if(f.minWidth>f.width){f.width=f.minWidth}e=this.uiDialog.css({height:"auto",width:f.width}).outerHeight();g=Math.max(0,f.minHeight-e);h=typeof f.maxHeight==="number"?Math.max(0,f.maxHeight-e):"none";if(f.height==="auto"){this.element.css({minHeight:g,maxHeight:h,height:"auto"})}else{this.element.height(Math.max(0,f.height-e))}if(this.uiDialog.is(":data(ui-resizable)")){this.uiDialog.resizable("option","minHeight",this._minHeight())}},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var e=c(this);return c("
    ").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){if(this.iframeBlocks){this.iframeBlocks.remove();delete this.iframeBlocks}},_allowInteraction:function(e){if(c(e.target).closest(".ui-dialog").length){return true}return !!c(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(!this.options.modal){return}var f=this,e=this.widgetFullName;if(!c.ui.dialog.overlayInstances){this._delay(function(){if(c.ui.dialog.overlayInstances){this.document.bind("focusin.dialog",function(g){if(!f._allowInteraction(g)){g.preventDefault();c(".ui-dialog:visible:last .ui-dialog-content").data(e)._focusTabbable()}})}})}this.overlay=c("
    ").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo());this._on(this.overlay,{mousedown:"_keepFocus"});c.ui.dialog.overlayInstances++},_destroyOverlay:function(){if(!this.options.modal){return}if(this.overlay){c.ui.dialog.overlayInstances--;if(!c.ui.dialog.overlayInstances){this.document.unbind("focusin.dialog")}this.overlay.remove();this.overlay=null}}});c.ui.dialog.overlayInstances=0;if(c.uiBackCompat!==false){c.widget("ui.dialog",c.ui.dialog,{_position:function(){var f=this.options.position,g=[],h=[0,0],e;if(f){if(typeof f==="string"||(typeof f==="object"&&"0" in f)){g=f.split?f.split(" "):[f[0],f[1]];if(g.length===1){g[1]=g[0]}c.each(["left","top"],function(k,j){if(+g[k]===g[k]){h[k]=g[k];g[k]=j}});f={my:g[0]+(h[0]<0?h[0]:"+"+h[0])+" "+g[1]+(h[1]<0?h[1]:"+"+h[1]),at:g.join(" ")}}f=c.extend({},c.ui.dialog.prototype.options.position,f)}else{f=c.ui.dialog.prototype.options.position}e=this.uiDialog.is(":visible");if(!e){this.uiDialog.show()}this.uiDialog.position(f);if(!e){this.uiDialog.hide()}}})}}(jQuery));(function(b,d){var a=/up|down|vertical/,c=/up|left|vertical|horizontal/;b.effects.effect.blind=function(g,m){var h=b(this),q=["position","top","bottom","left","right","height","width"],n=b.effects.setMode(h,g.mode||"hide"),r=g.direction||"up",j=a.test(r),i=j?"height":"width",p=j?"top":"left",t=c.test(r),l={},s=n==="show",f,e,k;if(h.parent().is(".ui-effects-wrapper")){b.effects.save(h.parent(),q)}else{b.effects.save(h,q)}h.show();f=b.effects.createWrapper(h).css({overflow:"hidden"});e=f[i]();k=parseFloat(f.css(p))||0;l[i]=s?e:0;if(!t){h.css(j?"bottom":"right",0).css(j?"top":"left","auto").css({position:"absolute"});l[p]=s?k:e+k}if(s){f.css(i,0);if(!t){f.css(p,k+e)}}f.animate(l,{duration:g.duration,easing:g.easing,queue:false,complete:function(){if(n==="hide"){h.hide()}b.effects.restore(h,q);b.effects.removeWrapper(h);m()}})}})(jQuery);(function(a,b){a.effects.effect.bounce=function(m,l){var c=a(this),d=["position","top","bottom","left","right","height","width"],k=a.effects.setMode(c,m.mode||"effect"),j=k==="hide",v=k==="show",w=m.direction||"up",e=m.distance,h=m.times||5,x=h*2+(v||j?1:0),u=m.duration/x,p=m.easing,f=(w==="up"||w==="down")?"top":"left",n=(w==="up"||w==="left"),t,g,s,q=c.queue(),r=q.length;if(v||j){d.push("opacity")}a.effects.save(c,d);c.show();a.effects.createWrapper(c);if(!e){e=c[f==="top"?"outerHeight":"outerWidth"]()/3}if(v){s={opacity:1};s[f]=0;c.css("opacity",0).css(f,n?-e*2:e*2).animate(s,u,p)}if(j){e=e/Math.pow(2,h-1)}s={};s[f]=0;for(t=0;t1){q.splice.apply(q,[1,0].concat(q.splice(r,x+1)))}c.dequeue()}})(jQuery);(function(a,b){a.effects.effect.clip=function(f,i){var g=a(this),m=["position","top","bottom","left","right","height","width"],l=a.effects.setMode(g,f.mode||"hide"),p=l==="show",n=f.direction||"vertical",k=n==="vertical",q=k?"height":"width",j=k?"top":"left",h={},d,e,c;a.effects.save(g,m);g.show();d=a.effects.createWrapper(g).css({overflow:"hidden"});e=(g[0].tagName==="IMG")?d:g;c=e[q]();if(p){e.css(q,0);e.css(j,c/2)}h[q]=p?c:0;h[j]=p?0:c/2;e.animate(h,{queue:false,duration:f.duration,easing:f.easing,complete:function(){if(!p){g.hide()}a.effects.restore(g,m);a.effects.removeWrapper(g);i()}})}})(jQuery);(function(a,b){a.effects.effect.drop=function(d,h){var e=a(this),j=["position","top","bottom","left","right","opacity","height","width"],i=a.effects.setMode(e,d.mode||"hide"),l=i==="show",k=d.direction||"left",f=(k==="up"||k==="down")?"top":"left",m=(k==="up"||k==="left")?"pos":"neg",g={opacity:l?1:0},c;a.effects.save(e,j);e.show();a.effects.createWrapper(e);c=d.distance||e[f==="top"?"outerHeight":"outerWidth"](true)/2;if(l){e.css("opacity",0).css(f,m==="pos"?-c:c)}g[f]=(l?(m==="pos"?"+=":"-="):(m==="pos"?"-=":"+="))+c;e.animate(g,{queue:false,duration:d.duration,easing:d.easing,complete:function(){if(i==="hide"){e.hide()}a.effects.restore(e,j);a.effects.removeWrapper(e);h()}})}})(jQuery);(function(a,b){a.effects.effect.explode=function(s,r){var k=s.pieces?Math.round(Math.sqrt(s.pieces)):3,d=k,c=a(this),m=a.effects.setMode(c,s.mode||"hide"),w=m==="show",g=c.show().css("visibility","hidden").offset(),t=Math.ceil(c.outerWidth()/d),q=Math.ceil(c.outerHeight()/k),h=[],v,u,e,p,n,l;function x(){h.push(this);if(h.length===k*d){f()}}for(v=0;v
    ").css({position:"absolute",visibility:"visible",left:-u*t,top:-v*q}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:t,height:q,left:e+(w?n*t:0),top:p+(w?l*q:0),opacity:w?0:1}).animate({left:e+(w?0:n*t),top:p+(w?0:l*q),opacity:w?1:0},s.duration||500,s.easing,x)}}function f(){c.css({visibility:"visible"});a(h).remove();if(!w){c.hide()}r()}}})(jQuery);(function(a,b){a.effects.effect.fade=function(f,c){var d=a(this),e=a.effects.setMode(d,f.mode||"toggle");d.animate({opacity:e},{queue:false,duration:f.duration,easing:f.easing,complete:c})}})(jQuery);(function(a,b){a.effects.effect.fold=function(e,i){var f=a(this),n=["position","top","bottom","left","right","height","width"],k=a.effects.setMode(f,e.mode||"hide"),r=k==="show",l=k==="hide",t=e.size||15,m=/([0-9]+)%/.exec(t),s=!!e.horizFirst,j=r!==s,g=j?["width","height"]:["height","width"],h=e.duration/2,d,c,q={},p={};a.effects.save(f,n);f.show();d=a.effects.createWrapper(f).css({overflow:"hidden"});c=j?[d.width(),d.height()]:[d.height(),d.width()];if(m){t=parseInt(m[1],10)/100*c[l?0:1]}if(r){d.css(s?{height:0,width:t}:{height:t,width:0})}q[g[0]]=r?c[0]:t;p[g[1]]=r?c[1]:0;d.animate(q,h,e.easing).animate(p,h,e.easing,function(){if(l){f.hide()}a.effects.restore(f,n);a.effects.removeWrapper(f);i()})}})(jQuery);(function(a,b){a.effects.effect.highlight=function(h,c){var e=a(this),d=["backgroundImage","backgroundColor","opacity"],g=a.effects.setMode(e,h.mode||"show"),f={backgroundColor:e.css("backgroundColor")};if(g==="hide"){f.opacity=0}a.effects.save(e,d);e.show().css({backgroundImage:"none",backgroundColor:h.color||"#ffff99"}).animate(f,{queue:false,duration:h.duration,easing:h.easing,complete:function(){if(g==="hide"){e.hide()}a.effects.restore(e,d);c()}})}})(jQuery);(function(a,b){a.effects.effect.pulsate=function(c,g){var e=a(this),k=a.effects.setMode(e,c.mode||"show"),p=k==="show",l=k==="hide",q=(p||k==="hide"),m=((c.times||5)*2)+(q?1:0),f=c.duration/m,n=0,j=e.queue(),d=j.length,h;if(p||!e.is(":visible")){e.css("opacity",0).show();n=1}for(h=1;h1){j.splice.apply(j,[1,0].concat(j.splice(d,m+1)))}e.dequeue()}})(jQuery);(function(a,b){a.effects.effect.puff=function(j,c){var h=a(this),i=a.effects.setMode(h,j.mode||"hide"),f=i==="hide",g=parseInt(j.percent,10)||150,e=g/100,d={height:h.height(),width:h.width(),outerHeight:h.outerHeight(),outerWidth:h.outerWidth()};a.extend(j,{effect:"scale",queue:false,fade:true,mode:i,complete:c,percent:f?g:100,from:f?d:{height:d.height*e,width:d.width*e,outerHeight:d.outerHeight*e,outerWidth:d.outerWidth*e}});h.effect(j)};a.effects.effect.scale=function(c,f){var d=a(this),l=a.extend(true,{},c),g=a.effects.setMode(d,c.mode||"effect"),h=parseInt(c.percent,10)||(parseInt(c.percent,10)===0?0:(g==="hide"?0:100)),j=c.direction||"both",k=c.origin,e={height:d.height(),width:d.width(),outerHeight:d.outerHeight(),outerWidth:d.outerWidth()},i={y:j!=="horizontal"?(h/100):1,x:j!=="vertical"?(h/100):1};l.effect="size";l.queue=false;l.complete=f;if(g!=="effect"){l.origin=k||["middle","center"];l.restore=true}l.from=c.from||(g==="show"?{height:0,width:0,outerHeight:0,outerWidth:0}:e);l.to={height:e.height*i.y,width:e.width*i.x,outerHeight:e.outerHeight*i.y,outerWidth:e.outerWidth*i.x};if(l.fade){if(g==="show"){l.from.opacity=0;l.to.opacity=1}if(g==="hide"){l.from.opacity=1;l.to.opacity=0}}d.effect(l)};a.effects.effect.size=function(l,k){var q,i,j,c=a(this),p=["position","top","bottom","left","right","width","height","overflow","opacity"],n=["position","top","bottom","left","right","overflow","opacity"],m=["width","height","overflow"],g=["fontSize"],s=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],d=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],h=a.effects.setMode(c,l.mode||"effect"),r=l.restore||h!=="effect",v=l.scale||"both",t=l.origin||["middle","center"],u=c.css("position"),e=r?p:n,f={height:0,width:0,outerHeight:0,outerWidth:0};if(h==="show"){c.show()}q={height:c.height(),width:c.width(),outerHeight:c.outerHeight(),outerWidth:c.outerWidth()};if(l.mode==="toggle"&&h==="show"){c.from=l.to||f;c.to=l.from||q}else{c.from=l.from||(h==="show"?f:q);c.to=l.to||(h==="hide"?f:q)}j={from:{y:c.from.height/q.height,x:c.from.width/q.width},to:{y:c.to.height/q.height,x:c.to.width/q.width}};if(v==="box"||v==="both"){if(j.from.y!==j.to.y){e=e.concat(s);c.from=a.effects.setTransition(c,s,j.from.y,c.from);c.to=a.effects.setTransition(c,s,j.to.y,c.to)}if(j.from.x!==j.to.x){e=e.concat(d);c.from=a.effects.setTransition(c,d,j.from.x,c.from);c.to=a.effects.setTransition(c,d,j.to.x,c.to)}}if(v==="content"||v==="both"){if(j.from.y!==j.to.y){e=e.concat(g).concat(m);c.from=a.effects.setTransition(c,g,j.from.y,c.from);c.to=a.effects.setTransition(c,g,j.to.y,c.to)}}a.effects.save(c,e);c.show();a.effects.createWrapper(c);c.css("overflow","hidden").css(c.from);if(t){i=a.effects.getBaseline(t,q);c.from.top=(q.outerHeight-c.outerHeight())*i.y;c.from.left=(q.outerWidth-c.outerWidth())*i.x;c.to.top=(q.outerHeight-c.to.outerHeight)*i.y;c.to.left=(q.outerWidth-c.to.outerWidth)*i.x}c.css(c.from);if(v==="content"||v==="both"){s=s.concat(["marginTop","marginBottom"]).concat(g);d=d.concat(["marginLeft","marginRight"]);m=p.concat(s).concat(d);c.find("*[width]").each(function(){var w=a(this),o={height:w.height(),width:w.width(),outerHeight:w.outerHeight(),outerWidth:w.outerWidth()};if(r){a.effects.save(w,m)}w.from={height:o.height*j.from.y,width:o.width*j.from.x,outerHeight:o.outerHeight*j.from.y,outerWidth:o.outerWidth*j.from.x};w.to={height:o.height*j.to.y,width:o.width*j.to.x,outerHeight:o.height*j.to.y,outerWidth:o.width*j.to.x};if(j.from.y!==j.to.y){w.from=a.effects.setTransition(w,s,j.from.y,w.from);w.to=a.effects.setTransition(w,s,j.to.y,w.to)}if(j.from.x!==j.to.x){w.from=a.effects.setTransition(w,d,j.from.x,w.from);w.to=a.effects.setTransition(w,d,j.to.x,w.to)}w.css(w.from);w.animate(w.to,l.duration,l.easing,function(){if(r){a.effects.restore(w,m)}})})}c.animate(c.to,{queue:false,duration:l.duration,easing:l.easing,complete:function(){if(c.to.opacity===0){c.css("opacity",c.from.opacity)}if(h==="hide"){c.hide()}a.effects.restore(c,e);if(!r){if(u==="static"){c.css({position:"relative",top:c.to.top,left:c.to.left})}else{a.each(["top","left"],function(o,w){c.css(w,function(y,A){var z=parseInt(A,10),x=o?c.to.left:c.to.top;if(A==="auto"){return x+"px"}return z+x+"px"})})}}a.effects.removeWrapper(c);k()}})}})(jQuery);(function(a,b){a.effects.effect.shake=function(l,k){var c=a(this),d=["position","top","bottom","left","right","height","width"],j=a.effects.setMode(c,l.mode||"effect"),u=l.direction||"left",e=l.distance||20,h=l.times||3,v=h*2+1,q=Math.round(l.duration/v),g=(u==="up"||u==="down")?"top":"left",f=(u==="up"||u==="left"),t={},s={},r={},p,m=c.queue(),n=m.length;a.effects.save(c,d);c.show();a.effects.createWrapper(c);t[g]=(f?"-=":"+=")+e;s[g]=(f?"+=":"-=")+e*2;r[g]=(f?"-=":"+=")+e*2;c.animate(t,q,l.easing);for(p=1;p1){m.splice.apply(m,[1,0].concat(m.splice(n,v+1)))}c.dequeue()}})(jQuery);(function(a,b){a.effects.effect.slide=function(e,i){var f=a(this),k=["position","top","bottom","left","right","width","height"],j=a.effects.setMode(f,e.mode||"show"),m=j==="show",l=e.direction||"left",g=(l==="up"||l==="down")?"top":"left",d=(l==="up"||l==="left"),c,h={};a.effects.save(f,k);f.show();c=e.distance||f[g==="top"?"outerHeight":"outerWidth"](true);a.effects.createWrapper(f).css({overflow:"hidden"});if(m){f.css(g,d?(isNaN(c)?"-"+c:-c):c)}h[g]=(m?(d?"+=":"-="):(d?"-=":"+="))+c;f.animate(h,{queue:false,duration:e.duration,easing:e.easing,complete:function(){if(j==="hide"){f.hide()}a.effects.restore(f,k);a.effects.removeWrapper(f);i()}})}})(jQuery);(function(a,b){a.effects.effect.transfer=function(d,h){var f=a(this),k=a(d.to),n=k.css("position")==="fixed",j=a("body"),l=n?j.scrollTop():0,m=n?j.scrollLeft():0,c=k.offset(),g={top:c.top-l,left:c.left-m,height:k.innerHeight(),width:k.innerWidth()},i=f.offset(),e=a("
    ").appendTo(document.body).addClass(d.className).css({top:i.top-l,left:i.left-m,height:f.innerHeight(),width:f.innerWidth(),position:n?"fixed":"absolute"}).animate(g,d.duration,d.easing,function(){e.remove();h()})}})(jQuery);(function(a,b){a.widget("ui.menu",{version:"1.10.3",defaultElement:"
      ",delay:300,options:{icons:{submenu:"ui-icon-carat-1-e"},menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element;this.mouseHandled=false;this.element.uniqueId().addClass("ui-menu ui-widget ui-widget-content ui-corner-all").toggleClass("ui-menu-icons",!!this.element.find(".ui-icon").length).attr({role:this.options.role,tabIndex:0}).bind("click"+this.eventNamespace,a.proxy(function(c){if(this.options.disabled){c.preventDefault()}},this));if(this.options.disabled){this.element.addClass("ui-state-disabled").attr("aria-disabled","true")}this._on({"mousedown .ui-menu-item > a":function(c){c.preventDefault()},"click .ui-state-disabled > a":function(c){c.preventDefault()},"click .ui-menu-item:has(a)":function(c){var d=a(c.target).closest(".ui-menu-item");if(!this.mouseHandled&&d.not(".ui-state-disabled").length){this.mouseHandled=true;this.select(c);if(d.has(".ui-menu").length){this.expand(c)}else{if(!this.element.is(":focus")){this.element.trigger("focus",[true]);if(this.active&&this.active.parents(".ui-menu").length===1){clearTimeout(this.timer)}}}}},"mouseenter .ui-menu-item":function(c){var d=a(c.currentTarget);d.siblings().children(".ui-state-active").removeClass("ui-state-active");this.focus(c,d)},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(e,c){var d=this.active||this.element.children(".ui-menu-item").eq(0);if(!c){this.focus(e,d)}},blur:function(c){this._delay(function(){if(!a.contains(this.element[0],this.document[0].activeElement)){this.collapseAll(c)}})},keydown:"_keydown"});this.refresh();this._on(this.document,{click:function(c){if(!a(c.target).closest(".ui-menu").length){this.collapseAll(c)}this.mouseHandled=false}})},_destroy:function(){this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeClass("ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons").removeAttr("role").removeAttr("tabIndex").removeAttr("aria-labelledby").removeAttr("aria-expanded").removeAttr("aria-hidden").removeAttr("aria-disabled").removeUniqueId().show();this.element.find(".ui-menu-item").removeClass("ui-menu-item").removeAttr("role").removeAttr("aria-disabled").children("a").removeUniqueId().removeClass("ui-corner-all ui-state-hover").removeAttr("tabIndex").removeAttr("role").removeAttr("aria-haspopup").children().each(function(){var c=a(this);if(c.data("ui-menu-submenu-carat")){c.remove()}});this.element.find(".ui-menu-divider").removeClass("ui-menu-divider ui-widget-content")},_keydown:function(i){var d,h,j,g,f,c=true;function e(k){return k.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}switch(i.keyCode){case a.ui.keyCode.PAGE_UP:this.previousPage(i);break;case a.ui.keyCode.PAGE_DOWN:this.nextPage(i);break;case a.ui.keyCode.HOME:this._move("first","first",i);break;case a.ui.keyCode.END:this._move("last","last",i);break;case a.ui.keyCode.UP:this.previous(i);break;case a.ui.keyCode.DOWN:this.next(i);break;case a.ui.keyCode.LEFT:this.collapse(i);break;case a.ui.keyCode.RIGHT:if(this.active&&!this.active.is(".ui-state-disabled")){this.expand(i)}break;case a.ui.keyCode.ENTER:case a.ui.keyCode.SPACE:this._activate(i);break;case a.ui.keyCode.ESCAPE:this.collapse(i);break;default:c=false;h=this.previousFilter||"";j=String.fromCharCode(i.keyCode);g=false;clearTimeout(this.filterTimer);if(j===h){g=true}else{j=h+j}f=new RegExp("^"+e(j),"i");d=this.activeMenu.children(".ui-menu-item").filter(function(){return f.test(a(this).children("a").text())});d=g&&d.index(this.active.next())!==-1?this.active.nextAll(".ui-menu-item"):d;if(!d.length){j=String.fromCharCode(i.keyCode);f=new RegExp("^"+e(j),"i");d=this.activeMenu.children(".ui-menu-item").filter(function(){return f.test(a(this).children("a").text())})}if(d.length){this.focus(i,d);if(d.length>1){this.previousFilter=j;this.filterTimer=this._delay(function(){delete this.previousFilter},1000)}else{delete this.previousFilter}}else{delete this.previousFilter}}if(c){i.preventDefault()}},_activate:function(c){if(!this.active.is(".ui-state-disabled")){if(this.active.children("a[aria-haspopup='true']").length){this.expand(c)}else{this.select(c)}}},refresh:function(){var e,d=this.options.icons.submenu,c=this.element.find(this.options.menus);c.filter(":not(.ui-menu)").addClass("ui-menu ui-widget ui-widget-content ui-corner-all").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var h=a(this),g=h.prev("a"),f=a("").addClass("ui-menu-icon ui-icon "+d).data("ui-menu-submenu-carat",true);g.attr("aria-haspopup","true").prepend(f);h.attr("aria-labelledby",g.attr("id"))});e=c.add(this.element);e.children(":not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","presentation").children("a").uniqueId().addClass("ui-corner-all").attr({tabIndex:-1,role:this._itemRole()});e.children(":not(.ui-menu-item)").each(function(){var f=a(this);if(!/[^\-\u2014\u2013\s]/.test(f.text())){f.addClass("ui-widget-content ui-menu-divider")}});e.children(".ui-state-disabled").attr("aria-disabled","true");if(this.active&&!a.contains(this.element[0],this.active[0])){this.blur()}},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(c,d){if(c==="icons"){this.element.find(".ui-menu-icon").removeClass(this.options.icons.submenu).addClass(d.submenu)}this._super(c,d)},focus:function(d,c){var f,e;this.blur(d,d&&d.type==="focus");this._scrollIntoView(c);this.active=c.first();e=this.active.children("a").addClass("ui-state-focus");if(this.options.role){this.element.attr("aria-activedescendant",e.attr("id"))}this.active.parent().closest(".ui-menu-item").children("a:first").addClass("ui-state-active");if(d&&d.type==="keydown"){this._close()}else{this.timer=this._delay(function(){this._close()},this.delay)}f=c.children(".ui-menu");if(f.length&&(/^mouse/.test(d.type))){this._startOpening(f)}this.activeMenu=c.parent();this._trigger("focus",d,{item:c})},_scrollIntoView:function(f){var i,e,g,c,d,h;if(this._hasScroll()){i=parseFloat(a.css(this.activeMenu[0],"borderTopWidth"))||0;e=parseFloat(a.css(this.activeMenu[0],"paddingTop"))||0;g=f.offset().top-this.activeMenu.offset().top-i-e;c=this.activeMenu.scrollTop();d=this.activeMenu.height();h=f.height();if(g<0){this.activeMenu.scrollTop(c+g)}else{if(g+h>d){this.activeMenu.scrollTop(c+g-d+h)}}}},blur:function(d,c){if(!c){clearTimeout(this.timer)}if(!this.active){return}this.active.children("a").removeClass("ui-state-focus");this.active=null;this._trigger("blur",d,{item:this.active})},_startOpening:function(c){clearTimeout(this.timer);if(c.attr("aria-hidden")!=="true"){return}this.timer=this._delay(function(){this._close();this._open(c)},this.delay)},_open:function(d){var c=a.extend({of:this.active},this.options.position);clearTimeout(this.timer);this.element.find(".ui-menu").not(d.parents(".ui-menu")).hide().attr("aria-hidden","true");d.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(c)},collapseAll:function(d,c){clearTimeout(this.timer);this.timer=this._delay(function(){var e=c?this.element:a(d&&d.target).closest(this.element.find(".ui-menu"));if(!e.length){e=this.element}this._close(e);this.blur(d);this.activeMenu=e},this.delay)},_close:function(c){if(!c){c=this.active?this.active.parent():this.element}c.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false").end().find("a.ui-state-active").removeClass("ui-state-active")},collapse:function(d){var c=this.active&&this.active.parent().closest(".ui-menu-item",this.element);if(c&&c.length){this._close();this.focus(d,c)}},expand:function(d){var c=this.active&&this.active.children(".ui-menu ").children(".ui-menu-item").first();if(c&&c.length){this._open(c.parent());this._delay(function(){this.focus(d,c)})}},next:function(c){this._move("next","first",c)},previous:function(c){this._move("prev","last",c)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(f,d,e){var c;if(this.active){if(f==="first"||f==="last"){c=this.active[f==="first"?"prevAll":"nextAll"](".ui-menu-item").eq(-1)}else{c=this.active[f+"All"](".ui-menu-item").eq(0)}}if(!c||!c.length||!this.active){c=this.activeMenu.children(".ui-menu-item")[d]()}this.focus(e,c)},nextPage:function(e){var d,f,c;if(!this.active){this.next(e);return}if(this.isLastItem()){return}if(this._hasScroll()){f=this.active.offset().top;c=this.element.height();this.active.nextAll(".ui-menu-item").each(function(){d=a(this);return d.offset().top-f-c<0});this.focus(e,d)}else{this.focus(e,this.activeMenu.children(".ui-menu-item")[!this.active?"first":"last"]())}},previousPage:function(e){var d,f,c;if(!this.active){this.next(e);return}if(this.isFirstItem()){return}if(this._hasScroll()){f=this.active.offset().top;c=this.element.height();this.active.prevAll(".ui-menu-item").each(function(){d=a(this);return d.offset().top-f+c>0});this.focus(e,d)}else{this.focus(e,this.activeMenu.children(".ui-menu-item").first())}},_hasScroll:function(){return this.element.outerHeight()
    "),r=s.children()[0];e("body").append(s);q=r.offsetWidth;s.css("overflow","scroll");p=r.offsetWidth;if(q===p){p=s[0].clientWidth}s.remove();return(j=q-p)},getScrollInfo:function(t){var s=t.isWindow?"":t.element.css("overflow-x"),r=t.isWindow?"":t.element.css("overflow-y"),q=s==="scroll"||(s==="auto"&&t.width0?"right":"center",vertical:N<0?"top":Q>0?"bottom":"middle"};if(wk(o(Q),o(N))){M.important="horizontal"}else{M.important="vertical"}z.using.call(this,P,M)}}E.offset(e.extend(H,{using:L}))})};e.ui.position={fit:{left:function(t,s){var r=s.within,v=r.isWindow?r.scrollLeft:r.offset.left,x=r.width,u=t.left-s.collisionPosition.marginLeft,w=v-u,q=u+s.collisionWidth-x-v,p;if(s.collisionWidth>x){if(w>0&&q<=0){p=t.left+w+s.collisionWidth-x-v;t.left+=w-p}else{if(q>0&&w<=0){t.left=v}else{if(w>q){t.left=v+x-s.collisionWidth}else{t.left=v}}}}else{if(w>0){t.left+=w}else{if(q>0){t.left-=q}else{t.left=k(t.left-u,t.left)}}}},top:function(s,r){var q=r.within,w=q.isWindow?q.scrollTop:q.offset.top,x=r.within.height,u=s.top-r.collisionPosition.marginTop,v=w-u,t=u+r.collisionHeight-x-w,p;if(r.collisionHeight>x){if(v>0&&t<=0){p=s.top+v+r.collisionHeight-x-w;s.top+=v-p}else{if(t>0&&v<=0){s.top=w}else{if(v>t){s.top=w+x-r.collisionHeight}else{s.top=w}}}}else{if(v>0){s.top+=v}else{if(t>0){s.top-=t}else{s.top=k(s.top-u,s.top)}}}}},flip:{left:function(v,u){var t=u.within,z=t.offset.left+t.scrollLeft,C=t.width,r=t.isWindow?t.scrollLeft:t.offset.left,w=v.left-u.collisionPosition.marginLeft,A=w-r,q=w+u.collisionWidth-C-r,y=u.my[0]==="left"?-u.elemWidth:u.my[0]==="right"?u.elemWidth:0,B=u.at[0]==="left"?u.targetWidth:u.at[0]==="right"?-u.targetWidth:0,s=-2*u.offset[0],p,x;if(A<0){p=v.left+y+B+s+u.collisionWidth-C-z;if(p<0||p0){x=v.left-u.collisionPosition.marginLeft+y+B+s-r;if(x>0||o(x)y&&(q<0||q0){A=u.top-t.collisionPosition.marginTop+x+D+r-p;if((u.top+x+D+r)>v&&(A>0||o(A)10&&s<11;t.innerHTML="";v.removeChild(t)})()}(jQuery));(function(a,b){a.widget("ui.progressbar",{version:"1.10.3",options:{max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue();this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min});this.valueDiv=a("
    ").appendTo(this.element);this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow");this.valueDiv.remove()},value:function(c){if(c===b){return this.options.value}this.options.value=this._constrainedValue(c);this._refreshValue()},_constrainedValue:function(c){if(c===b){c=this.options.value}this.indeterminate=c===false;if(typeof c!=="number"){c=0}return this.indeterminate?false:Math.min(this.options.max,Math.max(this.min,c))},_setOptions:function(c){var d=c.value;delete c.value;this._super(c);this.options.value=this._constrainedValue(d);this._refreshValue()},_setOption:function(c,d){if(c==="max"){d=Math.max(this.min,d)}this._super(c,d)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var d=this.options.value,c=this._percentage();this.valueDiv.toggle(this.indeterminate||d>this.min).toggleClass("ui-corner-right",d===this.options.max).width(c.toFixed(0)+"%");this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate);if(this.indeterminate){this.element.removeAttr("aria-valuenow");if(!this.overlayDiv){this.overlayDiv=a("
    ").appendTo(this.valueDiv)}}else{this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":d});if(this.overlayDiv){this.overlayDiv.remove();this.overlayDiv=null}}if(this.oldValue!==d){this.oldValue=d;this._trigger("change")}if(d===this.options.max){this._trigger("complete")}}})})(jQuery);(function(b,c){var a=5;b.widget("ui.slider",b.ui.mouse,{version:"1.10.3",widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},_create:function(){this._keySliding=false;this._mouseSliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");this._refresh();this._setOption("disabled",this.options.disabled);this._animateOff=false},_refresh:function(){this._createRange();this._createHandles();this._setupEvents();this._refreshValue()},_createHandles:function(){var g,d,e=this.options,j=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),h="",f=[];d=(e.values&&e.values.length)||1;if(j.length>d){j.slice(d).remove();j=j.slice(0,d)}for(g=j.length;g
    ").appendTo(this.element);e="ui-slider-range ui-widget-header ui-corner-all"}else{this.range.removeClass("ui-slider-range-min ui-slider-range-max").css({left:"",bottom:""})}this.range.addClass(e+((d.range==="min"||d.range==="max")?" ui-slider-range-"+d.range:""))}else{this.range=b([])}},_setupEvents:function(){var d=this.handles.add(this.range).filter("a");this._off(d);this._on(d,this._handleEvents);this._hoverable(d);this._focusable(d)},_destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-widget ui-widget-content ui-corner-all");this._mouseDestroy()},_mouseCapture:function(f){var j,m,e,h,l,n,i,d,k=this,g=this.options;if(g.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();j={x:f.pageX,y:f.pageY};m=this._normValueFromMouse(j);e=this._valueMax()-this._valueMin()+1;this.handles.each(function(o){var p=Math.abs(m-k.values(o));if((e>p)||(e===p&&(o===k._lastChangedValue||k.values(o)===g.min))){e=p;h=b(this);l=o}});n=this._start(f,l);if(n===false){return false}this._mouseSliding=true;this._handleIndex=l;h.addClass("ui-state-active").focus();i=h.offset();d=!b(f.target).parents().addBack().is(".ui-slider-handle");this._clickOffset=d?{left:0,top:0}:{left:f.pageX-i.left-(h.width()/2),top:f.pageY-i.top-(h.height()/2)-(parseInt(h.css("borderTopWidth"),10)||0)-(parseInt(h.css("borderBottomWidth"),10)||0)+(parseInt(h.css("marginTop"),10)||0)};if(!this.handles.hasClass("ui-state-hover")){this._slide(f,l,m)}this._animateOff=true;return true},_mouseStart:function(){return true},_mouseDrag:function(f){var d={x:f.pageX,y:f.pageY},e=this._normValueFromMouse(d);this._slide(f,this._handleIndex,e);return false},_mouseStop:function(d){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(d,this._handleIndex);this._change(d,this._handleIndex);this._handleIndex=null;this._clickOffset=null;this._animateOff=false;return false},_detectOrientation:function(){this.orientation=(this.options.orientation==="vertical")?"vertical":"horizontal"},_normValueFromMouse:function(e){var d,h,g,f,i;if(this.orientation==="horizontal"){d=this.elementSize.width;h=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{d=this.elementSize.height;h=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}g=(h/d);if(g>1){g=1}if(g<0){g=0}if(this.orientation==="vertical"){g=1-g}f=this._valueMax()-this._valueMin();i=this._valueMin()+g*f;return this._trimAlignValue(i)},_start:function(f,e){var d={handle:this.handles[e],value:this.value()};if(this.options.values&&this.options.values.length){d.value=this.values(e);d.values=this.values()}return this._trigger("start",f,d)},_slide:function(h,g,f){var d,e,i;if(this.options.values&&this.options.values.length){d=this.values(g?0:1);if((this.options.values.length===2&&this.options.range===true)&&((g===0&&f>d)||(g===1&&f1){this.options.values[e]=this._trimAlignValue(h);this._refreshValue();this._change(null,e);return}if(arguments.length){if(b.isArray(arguments[0])){g=this.options.values;d=arguments[0];for(f=0;f=this._valueMax()){return this._valueMax()}var d=(this.options.step>0)?this.options.step:1,f=(g-this._valueMin())%d,e=g-f;if(Math.abs(f)*2>=d){e+=(f>0)?d:(-d)}return parseFloat(e.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var i,h,l,j,m,g=this.options.range,f=this.options,k=this,e=(!this._animateOff)?f.animate:false,d={};if(this.options.values&&this.options.values.length){this.handles.each(function(n){h=(k.values(n)-k._valueMin())/(k._valueMax()-k._valueMin())*100;d[k.orientation==="horizontal"?"left":"bottom"]=h+"%";b(this).stop(1,1)[e?"animate":"css"](d,f.animate);if(k.options.range===true){if(k.orientation==="horizontal"){if(n===0){k.range.stop(1,1)[e?"animate":"css"]({left:h+"%"},f.animate)}if(n===1){k.range[e?"animate":"css"]({width:(h-i)+"%"},{queue:false,duration:f.animate})}}else{if(n===0){k.range.stop(1,1)[e?"animate":"css"]({bottom:(h)+"%"},f.animate)}if(n===1){k.range[e?"animate":"css"]({height:(h-i)+"%"},{queue:false,duration:f.animate})}}}i=h})}else{l=this.value();j=this._valueMin();m=this._valueMax();h=(m!==j)?(l-j)/(m-j)*100:0;d[this.orientation==="horizontal"?"left":"bottom"]=h+"%";this.handle.stop(1,1)[e?"animate":"css"](d,f.animate);if(g==="min"&&this.orientation==="horizontal"){this.range.stop(1,1)[e?"animate":"css"]({width:h+"%"},f.animate)}if(g==="max"&&this.orientation==="horizontal"){this.range[e?"animate":"css"]({width:(100-h)+"%"},{queue:false,duration:f.animate})}if(g==="min"&&this.orientation==="vertical"){this.range.stop(1,1)[e?"animate":"css"]({height:h+"%"},f.animate)}if(g==="max"&&this.orientation==="vertical"){this.range[e?"animate":"css"]({height:(100-h)+"%"},{queue:false,duration:f.animate})}}},_handleEvents:{keydown:function(h){var i,f,e,g,d=b(h.target).data("ui-slider-handle-index");switch(h.keyCode){case b.ui.keyCode.HOME:case b.ui.keyCode.END:case b.ui.keyCode.PAGE_UP:case b.ui.keyCode.PAGE_DOWN:case b.ui.keyCode.UP:case b.ui.keyCode.RIGHT:case b.ui.keyCode.DOWN:case b.ui.keyCode.LEFT:h.preventDefault();if(!this._keySliding){this._keySliding=true;b(h.target).addClass("ui-state-active");i=this._start(h,d);if(i===false){return}}break}g=this.options.step;if(this.options.values&&this.options.values.length){f=e=this.values(d)}else{f=e=this.value()}switch(h.keyCode){case b.ui.keyCode.HOME:e=this._valueMin();break;case b.ui.keyCode.END:e=this._valueMax();break;case b.ui.keyCode.PAGE_UP:e=this._trimAlignValue(f+((this._valueMax()-this._valueMin())/a));break;case b.ui.keyCode.PAGE_DOWN:e=this._trimAlignValue(f-((this._valueMax()-this._valueMin())/a));break;case b.ui.keyCode.UP:case b.ui.keyCode.RIGHT:if(f===this._valueMax()){return}e=this._trimAlignValue(f+g);break;case b.ui.keyCode.DOWN:case b.ui.keyCode.LEFT:if(f===this._valueMin()){return}e=this._trimAlignValue(f-g);break}this._slide(h,d,e)},click:function(d){d.preventDefault()},keyup:function(e){var d=b(e.target).data("ui-slider-handle-index");if(this._keySliding){this._keySliding=false;this._stop(e,d);this._change(e,d);b(e.target).removeClass("ui-state-active")}}}})}(jQuery));(function(b){function a(c){return function(){var d=this.element.val();c.apply(this,arguments);this._refresh();if(d!==this.element.val()){this._trigger("change")}}}b.widget("ui.spinner",{version:"1.10.3",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:true,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max);this._setOption("min",this.options.min);this._setOption("step",this.options.step);this._value(this.element.val(),true);this._draw();this._on(this._events);this._refresh();this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var c={},d=this.element;b.each(["min","max","step"],function(e,f){var g=d.attr(f);if(g!==undefined&&g.length){c[f]=g}});return c},_events:{keydown:function(c){if(this._start(c)&&this._keydown(c)){c.preventDefault()}},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(c){if(this.cancelBlur){delete this.cancelBlur;return}this._stop();this._refresh();if(this.previous!==this.element.val()){this._trigger("change",c)}},mousewheel:function(c,d){if(!d){return}if(!this.spinning&&!this._start(c)){return false}this._spin((d>0?1:-1)*this.options.step,c);clearTimeout(this.mousewheelTimer);this.mousewheelTimer=this._delay(function(){if(this.spinning){this._stop(c)}},100);c.preventDefault()},"mousedown .ui-spinner-button":function(d){var c;c=this.element[0]===this.document[0].activeElement?this.previous:this.element.val();function e(){var f=this.element[0]===this.document[0].activeElement;if(!f){this.element.focus();this.previous=c;this._delay(function(){this.previous=c})}}d.preventDefault();e.call(this);this.cancelBlur=true;this._delay(function(){delete this.cancelBlur;e.call(this)});if(this._start(d)===false){return}this._repeat(null,b(d.currentTarget).hasClass("ui-spinner-up")?1:-1,d)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(c){if(!b(c.currentTarget).hasClass("ui-state-active")){return}if(this._start(c)===false){return false}this._repeat(null,b(c.currentTarget).hasClass("ui-spinner-up")?1:-1,c)},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var c=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton");this.buttons=c.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all");if(this.buttons.height()>Math.ceil(c.height()*0.5)&&c.height()>0){c.height(c.height())}if(this.options.disabled){this.disable()}},_keydown:function(d){var c=this.options,e=b.ui.keyCode;switch(d.keyCode){case e.UP:this._repeat(null,1,d);return true;case e.DOWN:this._repeat(null,-1,d);return true;case e.PAGE_UP:this._repeat(null,c.page,d);return true;case e.PAGE_DOWN:this._repeat(null,-c.page,d);return true}return false},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return"▲▼"},_start:function(c){if(!this.spinning&&this._trigger("start",c)===false){return false}if(!this.counter){this.counter=1}this.spinning=true;return true},_repeat:function(d,c,e){d=d||500;clearTimeout(this.timer);this.timer=this._delay(function(){this._repeat(40,c,e)},d);this._spin(c*this.options.step,e)},_spin:function(d,c){var e=this.value()||0;if(!this.counter){this.counter=1}e=this._adjustValue(e+d*this._increment(this.counter));if(!this.spinning||this._trigger("spin",c,{value:e})!==false){this._value(e);this.counter++}},_increment:function(c){var d=this.options.incremental;if(d){return b.isFunction(d)?d(c):Math.floor(c*c*c/50000-c*c/500+17*c/200+1)}return 1},_precision:function(){var c=this._precisionOf(this.options.step);if(this.options.min!==null){c=Math.max(c,this._precisionOf(this.options.min))}return c},_precisionOf:function(d){var e=d.toString(),c=e.indexOf(".");return c===-1?0:e.length-c-1},_adjustValue:function(e){var d,f,c=this.options;d=c.min!==null?c.min:0;f=e-d;f=Math.round(f/c.step)*c.step;e=d+f;e=parseFloat(e.toFixed(this._precision()));if(c.max!==null&&e>c.max){return c.max}if(c.min!==null&&e1&&decodeURIComponent(g.href.replace(f,""))===decodeURIComponent(location.href.replace(f,""))}c.widget("ui.tabs",{version:"1.10.3",delay:300,options:{active:null,collapsible:false,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var h=this,g=this.options;this.running=false;this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",g.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(i){if(c(this).is(".ui-state-disabled")){i.preventDefault()}}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){if(c(this).closest("li").is(".ui-state-disabled")){this.blur()}});this._processTabs();g.active=this._initialActive();if(c.isArray(g.disabled)){g.disabled=c.unique(g.disabled.concat(c.map(this.tabs.filter(".ui-state-disabled"),function(i){return h.tabs.index(i)}))).sort()}if(this.options.active!==false&&this.anchors.length){this.active=this._findActive(g.active)}else{this.active=c()}this._refresh();if(this.active.length){this.load(g.active)}},_initialActive:function(){var h=this.options.active,g=this.options.collapsible,i=location.hash.substring(1);if(h===null){if(i){this.tabs.each(function(j,k){if(c(k).attr("aria-controls")===i){h=j;return false}})}if(h===null){h=this.tabs.index(this.tabs.filter(".ui-tabs-active"))}if(h===null||h===-1){h=this.tabs.length?0:false}}if(h!==false){h=this.tabs.index(this.tabs.eq(h));if(h===-1){h=g?false:0}}if(!g&&h===false&&this.anchors.length){h=0}return h},_getCreateEventData:function(){return{tab:this.active,panel:!this.active.length?c():this._getPanelForTab(this.active)}},_tabKeydown:function(i){var h=c(this.document[0].activeElement).closest("li"),g=this.tabs.index(h),j=true;if(this._handlePageNav(i)){return}switch(i.keyCode){case c.ui.keyCode.RIGHT:case c.ui.keyCode.DOWN:g++;break;case c.ui.keyCode.UP:case c.ui.keyCode.LEFT:j=false;g--;break;case c.ui.keyCode.END:g=this.anchors.length-1;break;case c.ui.keyCode.HOME:g=0;break;case c.ui.keyCode.SPACE:i.preventDefault();clearTimeout(this.activating);this._activate(g);return;case c.ui.keyCode.ENTER:i.preventDefault();clearTimeout(this.activating);this._activate(g===this.options.active?false:g);return;default:return}i.preventDefault();clearTimeout(this.activating);g=this._focusNextTab(g,j);if(!i.ctrlKey){h.attr("aria-selected","false");this.tabs.eq(g).attr("aria-selected","true");this.activating=this._delay(function(){this.option("active",g)},this.delay)}},_panelKeydown:function(g){if(this._handlePageNav(g)){return}if(g.ctrlKey&&g.keyCode===c.ui.keyCode.UP){g.preventDefault();this.active.focus()}},_handlePageNav:function(g){if(g.altKey&&g.keyCode===c.ui.keyCode.PAGE_UP){this._activate(this._focusNextTab(this.options.active-1,false));return true}if(g.altKey&&g.keyCode===c.ui.keyCode.PAGE_DOWN){this._activate(this._focusNextTab(this.options.active+1,true));return true}},_findNextTab:function(h,i){var g=this.tabs.length-1;function j(){if(h>g){h=0}if(h<0){h=g}return h}while(c.inArray(j(),this.options.disabled)!==-1){h=i?h+1:h-1}return h},_focusNextTab:function(g,h){g=this._findNextTab(g,h);this.tabs.eq(g).focus();return g},_setOption:function(g,h){if(g==="active"){this._activate(h);return}if(g==="disabled"){this._setupDisabled(h);return}this._super(g,h);if(g==="collapsible"){this.element.toggleClass("ui-tabs-collapsible",h);if(!h&&this.options.active===false){this._activate(0)}}if(g==="event"){this._setupEvents(h)}if(g==="heightStyle"){this._setupHeightStyle(h)}},_tabId:function(g){return g.attr("aria-controls")||"ui-tabs-"+d()},_sanitizeSelector:function(g){return g?g.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var h=this.options,g=this.tablist.children(":has(a[href])");h.disabled=c.map(g.filter(".ui-state-disabled"),function(i){return g.index(i)});this._processTabs();if(h.active===false||!this.anchors.length){h.active=false;this.active=c()}else{if(this.active.length&&!c.contains(this.tablist[0],this.active[0])){if(this.tabs.length===h.disabled.length){h.active=false;this.active=c()}else{this._activate(this._findNextTab(Math.max(0,h.active-1),false))}}else{h.active=this.tabs.index(this.active)}}this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled);this._setupEvents(this.options.event);this._setupHeightStyle(this.options.heightStyle);this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1});this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"});if(!this.active.length){this.tabs.eq(0).attr("tabIndex",0)}else{this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0});this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})}},_processTabs:function(){var g=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist");this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1});this.anchors=this.tabs.map(function(){return c("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1});this.panels=c();this.anchors.each(function(n,l){var h,j,m,k=c(l).uniqueId().attr("id"),o=c(l).closest("li"),p=o.attr("aria-controls");if(b(l)){h=l.hash;j=g.element.find(g._sanitizeSelector(h))}else{m=g._tabId(o);h="#"+m;j=g.element.find(h);if(!j.length){j=g._createPanel(m);j.insertAfter(g.panels[n-1]||g.tablist)}j.attr("aria-live","polite")}if(j.length){g.panels=g.panels.add(j)}if(p){o.data("ui-tabs-aria-controls",p)}o.attr({"aria-controls":h.substring(1),"aria-labelledby":k});j.attr("aria-labelledby",k)});this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(g){return c("
    ").attr("id",g).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",true)},_setupDisabled:function(j){if(c.isArray(j)){if(!j.length){j=false}else{if(j.length===this.anchors.length){j=true}}}for(var h=0,g;(g=this.tabs[h]);h++){if(j===true||c.inArray(h,j)!==-1){c(g).addClass("ui-state-disabled").attr("aria-disabled","true")}else{c(g).removeClass("ui-state-disabled").removeAttr("aria-disabled")}}this.options.disabled=j},_setupEvents:function(h){var g={click:function(i){i.preventDefault()}};if(h){c.each(h.split(" "),function(j,i){g[i]="_eventHandler"})}this._off(this.anchors.add(this.tabs).add(this.panels));this._on(this.anchors,g);this._on(this.tabs,{keydown:"_tabKeydown"});this._on(this.panels,{keydown:"_panelKeydown"});this._focusable(this.tabs);this._hoverable(this.tabs)},_setupHeightStyle:function(g){var i,h=this.element.parent();if(g==="fill"){i=h.height();i-=this.element.outerHeight()-this.element.height();this.element.siblings(":visible").each(function(){var k=c(this),j=k.css("position");if(j==="absolute"||j==="fixed"){return}i-=k.outerHeight(true)});this.element.children().not(this.panels).each(function(){i-=c(this).outerHeight(true)});this.panels.each(function(){c(this).height(Math.max(0,i-c(this).innerHeight()+c(this).height()))}).css("overflow","auto")}else{if(g==="auto"){i=0;this.panels.each(function(){i=Math.max(i,c(this).height("").height())}).height(i)}}},_eventHandler:function(g){var p=this.options,k=this.active,l=c(g.currentTarget),j=l.closest("li"),n=j[0]===k[0],h=n&&p.collapsible,i=h?c():this._getPanelForTab(j),m=!k.length?c():this._getPanelForTab(k),o={oldTab:k,oldPanel:m,newTab:h?c():j,newPanel:i};g.preventDefault();if(j.hasClass("ui-state-disabled")||j.hasClass("ui-tabs-loading")||this.running||(n&&!p.collapsible)||(this._trigger("beforeActivate",g,o)===false)){return}p.active=h?false:this.tabs.index(j);this.active=n?c():j;if(this.xhr){this.xhr.abort()}if(!m.length&&!i.length){c.error("jQuery UI Tabs: Mismatching fragment identifier.")}if(i.length){this.load(this.tabs.index(j),g)}this._toggle(g,o)},_toggle:function(m,l){var k=this,g=l.newPanel,j=l.oldPanel;this.running=true;function i(){k.running=false;k._trigger("activate",m,l)}function h(){l.newTab.closest("li").addClass("ui-tabs-active ui-state-active");if(g.length&&k.options.show){k._show(g,k.options.show,i)}else{g.show();i()}}if(j.length&&this.options.hide){this._hide(j,this.options.hide,function(){l.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active");h()})}else{l.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active");j.hide();h()}j.attr({"aria-expanded":"false","aria-hidden":"true"});l.oldTab.attr("aria-selected","false");if(g.length&&j.length){l.oldTab.attr("tabIndex",-1)}else{if(g.length){this.tabs.filter(function(){return c(this).attr("tabIndex")===0}).attr("tabIndex",-1)}}g.attr({"aria-expanded":"true","aria-hidden":"false"});l.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(h){var g,i=this._findActive(h);if(i[0]===this.active[0]){return}if(!i.length){i=this.active}g=i.find(".ui-tabs-anchor")[0];this._eventHandler({target:g,currentTarget:g,preventDefault:c.noop})},_findActive:function(g){return g===false?c():this.tabs.eq(g)},_getIndex:function(g){if(typeof g==="string"){g=this.anchors.index(this.anchors.filter("[href$='"+g+"']"))}return g},_destroy:function(){if(this.xhr){this.xhr.abort()}this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible");this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role");this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId();this.tabs.add(this.panels).each(function(){if(c.data(this,"ui-tabs-destroy")){c(this).remove()}else{c(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}});this.tabs.each(function(){var g=c(this),h=g.data("ui-tabs-aria-controls");if(h){g.attr("aria-controls",h).removeData("ui-tabs-aria-controls")}else{g.removeAttr("aria-controls")}});this.panels.show();if(this.options.heightStyle!=="content"){this.panels.css("height","")}},enable:function(g){var h=this.options.disabled;if(h===false){return}if(g===e){h=false}else{g=this._getIndex(g);if(c.isArray(h)){h=c.map(h,function(i){return i!==g?i:null})}else{h=c.map(this.tabs,function(i,j){return j!==g?j:null})}}this._setupDisabled(h)},disable:function(g){var h=this.options.disabled;if(h===true){return}if(g===e){h=true}else{g=this._getIndex(g);if(c.inArray(g,h)!==-1){return}if(c.isArray(h)){h=c.merge([g],h).sort()}else{h=[g]}}this._setupDisabled(h)},load:function(i,m){i=this._getIndex(i);var l=this,j=this.tabs.eq(i),h=j.find(".ui-tabs-anchor"),g=this._getPanelForTab(j),k={tab:j,panel:g};if(b(h[0])){return}this.xhr=c.ajax(this._ajaxSettings(h,m,k));if(this.xhr&&this.xhr.statusText!=="canceled"){j.addClass("ui-tabs-loading");g.attr("aria-busy","true");this.xhr.success(function(n){setTimeout(function(){g.html(n);l._trigger("load",m,k)},1)}).complete(function(o,n){setTimeout(function(){if(n==="abort"){l.panels.stop(false,true)}j.removeClass("ui-tabs-loading");g.removeAttr("aria-busy");if(o===l.xhr){delete l.xhr}},1)})}},_ajaxSettings:function(g,j,i){var h=this;return{url:g.attr("href"),beforeSend:function(l,k){return h._trigger("beforeLoad",j,c.extend({jqXHR:l,ajaxSettings:k},i))}}},_getPanelForTab:function(g){var h=c(g).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+h))}})})(jQuery);(function(d){var b=0;function c(f,g){var e=(f.attr("aria-describedby")||"").split(/\s+/);e.push(g);f.data("ui-tooltip-id",g).attr("aria-describedby",d.trim(e.join(" ")))}function a(g){var h=g.data("ui-tooltip-id"),f=(g.attr("aria-describedby")||"").split(/\s+/),e=d.inArray(h,f);if(e!==-1){f.splice(e,1)}g.removeData("ui-tooltip-id");f=d.trim(f.join(" "));if(f){g.attr("aria-describedby",f)}else{g.removeAttr("aria-describedby")}}d.widget("ui.tooltip",{version:"1.10.3",options:{content:function(){var e=d(this).attr("title")||"";return d("").text(e).html()},hide:true,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:true,tooltipClass:null,track:false,close:null,open:null},_create:function(){this._on({mouseover:"open",focusin:"open"});this.tooltips={};this.parents={};if(this.options.disabled){this._disable()}},_setOption:function(e,g){var f=this;if(e==="disabled"){this[g?"_disable":"_enable"]();this.options[e]=g;return}this._super(e,g);if(e==="content"){d.each(this.tooltips,function(i,h){f._updateContent(h)})}},_disable:function(){var e=this;d.each(this.tooltips,function(h,f){var g=d.Event("blur");g.target=g.currentTarget=f[0];e.close(g,true)});this.element.find(this.options.items).addBack().each(function(){var f=d(this);if(f.is("[title]")){f.data("ui-tooltip-title",f.attr("title")).attr("title","")}})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var e=d(this);if(e.data("ui-tooltip-title")){e.attr("title",e.data("ui-tooltip-title"))}})},open:function(f){var e=this,g=d(f?f.target:this.element).closest(this.options.items);if(!g.length||g.data("ui-tooltip-id")){return}if(g.attr("title")){g.data("ui-tooltip-title",g.attr("title"))}g.data("ui-tooltip-open",true);if(f&&f.type==="mouseover"){g.parents().each(function(){var i=d(this),h;if(i.data("ui-tooltip-open")){h=d.Event("blur");h.target=h.currentTarget=this;e.close(h,true)}if(i.attr("title")){i.uniqueId();e.parents[this.id]={element:this,title:i.attr("title")};i.attr("title","")}})}this._updateContent(g,f)},_updateContent:function(j,i){var h,e=this.options.content,g=this,f=i?i.type:null;if(typeof e==="string"){return this._open(i,j,e)}h=e.call(j[0],function(k){if(!j.data("ui-tooltip-open")){return}g._delay(function(){if(i){i.type=f}this._open(i,j,k)})});if(h){this._open(i,j,h)}},_open:function(i,k,h){var j,g,f,l=d.extend({},this.options.position);if(!h){return}j=this._find(k);if(j.length){j.find(".ui-tooltip-content").html(h);return}if(k.is("[title]")){if(i&&i.type==="mouseover"){k.attr("title","")}else{k.removeAttr("title")}}j=this._tooltip(k);c(k,j.attr("id"));j.find(".ui-tooltip-content").html(h);function e(m){l.of=m;if(j.is(":hidden")){return}j.position(l)}if(this.options.track&&i&&/^mouse/.test(i.type)){this._on(this.document,{mousemove:e});e(i)}else{j.position(d.extend({of:k},this.options.position))}j.hide();this._show(j,this.options.show);if(this.options.show&&this.options.show.delay){f=this.delayedShow=setInterval(function(){if(j.is(":visible")){e(l.of);clearInterval(f)}},d.fx.interval)}this._trigger("open",i,{tooltip:j});g={keyup:function(m){if(m.keyCode===d.ui.keyCode.ESCAPE){var n=d.Event(m);n.currentTarget=k[0];this.close(n,true)}},remove:function(){this._removeTooltip(j)}};if(!i||i.type==="mouseover"){g.mouseleave="close"}if(!i||i.type==="focusin"){g.focusout="close"}this._on(true,k,g)},close:function(f){var e=this,h=d(f?f.currentTarget:this.element),g=this._find(h);if(this.closing){return}clearInterval(this.delayedShow);if(h.data("ui-tooltip-title")){h.attr("title",h.data("ui-tooltip-title"))}a(h);g.stop(true);this._hide(g,this.options.hide,function(){e._removeTooltip(d(this))});h.removeData("ui-tooltip-open");this._off(h,"mouseleave focusout keyup");if(h[0]!==this.element[0]){this._off(h,"remove")}this._off(this.document,"mousemove");if(f&&f.type==="mouseleave"){d.each(this.parents,function(j,i){d(i.element).attr("title",i.title);delete e.parents[j]})}this.closing=true;this._trigger("close",f,{tooltip:g});this.closing=false},_tooltip:function(e){var g="ui-tooltip-"+b++,f=d("
    ").attr({id:g,role:"tooltip"}).addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||""));d("
    ").addClass("ui-tooltip-content").appendTo(f);f.appendTo(this.document[0].body);this.tooltips[g]=e;return f},_find:function(e){var f=e.data("ui-tooltip-id");return f?d("#"+f):d()},_removeTooltip:function(e){e.remove();delete this.tooltips[e.attr("id")]},_destroy:function(){var e=this;d.each(this.tooltips,function(h,f){var g=d.Event("blur");g.target=g.currentTarget=f[0];e.close(g,true);d("#"+h).remove();if(f.data("ui-tooltip-title")){f.attr("title",f.data("ui-tooltip-title"));f.removeData("ui-tooltip-title")}})}})}(jQuery));jquery/000077500000000000000000000000001325274564300340605ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scriptsjquery-ui-theme/000077500000000000000000000000001325274564300371125ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jqueryimages/000077500000000000000000000000001325274564300403575ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-themeanimated-overlay.gif000066400000000000000000000033121325274564300443060ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesGIF89a((! NETSCAPE2.0! ,(( z KNY#7)zɭv[3ӵϰxPwEa؁FOfVYeΛ||/X\Wr݅o$m^K0>'$uf6G'Xg5Ȩ5)9):ZiYJyڪY! ,((}Q6Úa_y#ʩijK-|˱K3^Pw&KOә=7IfTzLMYhcdX\1iea }wl5CgGB)'hY9IHyȗ ʹYjZGh'j85P! ,(( mQ6,@o-`u$>Iz/ 69~[ޢՄ^Ot6Ac:vN?cUX|f&6ẍ́哲_~G(b8X%x7IXI9x(I:Y*XYvʚP! ,((oˁ;MZY|ƍ舝([99ږ1`P2!H>oQW^dsc2*Siy x[s^ݶVGWwgǸإx舙Y8IIyIZj)Xf):R! ,((CqMZYm5W(F~٩'-:Õ|ڒ1p?X1dFSLӨqne^ACjTpfԷUp|%ƌn]z~mHXx6X9)Hyi9ƈר)Z *Y! ,((˜CMZ5Yo}6-ʕz㒽h1C&'EfrtF9z&ۭ*V:&Tjeu~_}W5hbG6(iV((x:蹊JYY* P! ,((CMZՅl}'vexVZk 򮯱$bR3ƒHPGkBjymhXklvYvֻf7HXH(((9x3%txiY 9i*ZJzWP! ,((CMZՅl}'vexVZ%;ӨU{ZbQ0G͹SSRƆШ 2kYEV}v]xTpg7gvHX'㖨9)רe&) ji:hIZT;ui-bg_flat_0_aaaaaa_40x100.png000066400000000000000000000003731325274564300455010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDR(d5gAMA a cHRMz&u0`:pQ<bKGD3rIDAT8cX Q(sI. I/ZW%tEXtdate:create2014-06-26T14:15:30+02:00b>n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_flat_75_ffffff_40x100.png000066400000000000000000000003671325274564300456360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDR(dOgAMA a cHRMz&u0`:pQ<bKGD݊IDAT(c (IUʑ.{%tEXtdate:create2014-06-26T14:15:30+02:00b>n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_glass_55_fbf9ee_1x400.png000066400000000000000000000005661325274564300456550ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRAgAMA a cHRMz&u0`:pQ<bKGD XIDATH! AblA1{VY0ixxvDK_O9 aՔ}^JaȌ0bvBA$,Q"_44=SqcyEIW n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_glass_65_ffffff_1x400.png000066400000000000000000000003661325274564300457370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRG#7vgAMA a cHRMz&u0`:pQ<bKGD݊IDAT(ch`ph4i%tEXtdate:create2014-06-26T14:15:30+02:00b>n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_glass_75_dadada_1x400.png000066400000000000000000000004551325274564300457120ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRDgAMA a cHRMz&u0`:pQ<bKGD1HIDAT8cxa"[n{1qcpo"? 3}`xR1s?^^bxuu)뉣hW%R|%tEXtdate:create2014-06-26T14:15:30+02:00b>n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_glass_75_e6e6e6_1x400.png000066400000000000000000000004551325274564300455140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRDgAMA a cHRMz&u0`:pQ<bKGD1HIDAT8cx0Fѳg ύax1e&ë8! obަ2fx#3ǵ >QD @$.5o%tEXtdate:create2014-06-26T14:15:30+02:00b>n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_glass_95_fef1ec_1x400.png000066400000000000000000000005631325274564300456470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRAgAMA a cHRMz&u0`:pQ<bKGD XIDATHϱ a\!VJ#XЋD} .f>>Pկxxqжuɚqf+6[\‡כWT4r6:]V:, (Ŵ8yG-(d H%tEXtdate:create2014-06-26T14:15:30+02:00b>n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-bg_highlight-soft_75_cccccc_1x100.png000066400000000000000000000004771325274564300475250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRd2gAMA a cHRMz&u0`:pQ<bKGD1ZIDATcx|Nhã 2n%tEXtdate:modify2014-06-26T14:15:30+02:00cOIENDB`ui-icons_222222_256x240.png000066400000000000000000000155361325274564300444520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDREr@gAMA a cHRMz&u0`:pQ<bKGD"bTIDATxm%Uy $!,h ,%&@|IEMrݱD2;f ܉A^$5Ƣj^K@U-wWD`|q"t>ν3}S~9}<>|Dg7\J{4K 3tfZޱ`7u7Yɶ~f]<4m*<%%{^`FR l8<Oh8<Ok2n6cL`>]=u$,`&+hk6J_*|K0?@lxdom]L3ݞߌ0.;TE,;4tMOmPLUA&e+`8u]577~"h8<Oh8<]C8YsZ}iA ֠)ȞsW.!m. z[#R䨚 \R{v r R ?˝#[|f`y)A-QN 4wϮ:Kr{河3ZP!0t{l\YwSnPIA.Vm-R $ysI@;eغX.vYvB n]gWcVn*ôyT=Mhpt +3 '@ px4 '@ pC`=x?.jlk]R Q.ޫ5@P,[Zj6ƵKɟڕ[ ,P/5/thzG]Yn!wk9څR+@K tťa.mKu DM篂U!LgK\At$VrX˒`LoMe/R+X.X.]L]Nq%e7w"< ּp뻴ʩ@  uǤ OO7 '@ px4 PdrvM{wQve{Ȯ]0d1w%L,vQwqyyx`]jW.!WWl/D3,v܁6]iZ<:IݯtM~9UPi!snS!QyU緯lJ Ț!$(7-/ޮB¤F[^)F#*U$`7\!OUŪ3ǐ_J:𨃏ܽX6X!|.!}t$ePROQա/P[Cmb,=XA!tho14]Zh=Oh8<#@keEٲM&IKѢ&b04(.%thtIH2>Dgͳ]ݢ@NE]hɟ|T=m5@ .#BW2wQ׿¨alrvEb)n#ය݄F/N6%t]KX" nz"t.'`[A-y-LTi rgW=)lj&˺;YeF,6CG_0l;y1|5'@ px4SLT_ʿߐ:ADV\cK !e8L}F{=.y0~w,ݭ9^n gH]Y,b}H!0 La4-Uڣ$R|crQx/a _seRmDz$V\ 3<ɔ0㗹1VD#a=5!*,6~ ^#Jt Wg{ȃsd,o9X1 ?*uuB,=`޻'1l$[Gx4 '@ DΞ-z)E}:g˖ gÁyk>fiNa~  ɏk`.73PȸҜ;+ ZpMlbinFݬLپȓ4 :a.0JN.pk8NPx\lh݅x2iǹ?OH5M@.K4p\a/>ay%^zU25O'0VOsMR4"0VܞFqyYEL0])PK~Irx /Qs*SL1fn>6U4>i#\}cJjrjo_u8n,JFgyFk6a[0y|kS\6 + )L*g EDخq|Y_'-?Z";<Wϼ= [\IL}‹7OqX 4<z7=~CP6w'ƟL~*?7ȳg%npMBC{ L^O2;C!-c%DP]y_VaX1uҗEc_t'[PP> `4B "O7ՑF3 <5ϝ|B.0#bb],ҥK{/QWܯd0~.ꏜ}eK Y@3>?_"X,u5OԘ(xsG4DI 4 '@ p4XNIMs(,ѡކ<&R=>@]ZG^ _Hk|p7rFp,/^>G-B+7(z3d-A^'qzj z_`I:L0T|aɄ܇'/CTS̤ U (.2Rr ?< aGBKsRƔ[IlcylU_ɷxO QH1NcӺ#?5f4ߣ3lgGAnjԄ&"\r'VZvD)ykCbW;I[‰k!fuiQ*tWsN_~W< g)'8}ږjHzD66Dkx3?xFy znoO+v,bcxsLwxtnGĝlNn2ھIOΐlƟw g kг=ޜlAl)l; )"W2:-.H٥aq66$\0oqnjcC[Oh8<O":F@j1Xg dbݖ3zELHWW+wx0- pxk` px4 '@ pAܣDkH2u]AzwnPExFyAum 0F0bG:1;L6|CGICHRݿ6y~˘{o}@]Q~"h8<Oh8/~B P-d׻͒£"D ;R򠫹 `d] a{ -+PS#R3@O z-̒ ¿rvJQz`W_9HGx\EA2͌r͠&~U>wS[Σ- `9'C~ C@eCٹq_ XzN%tEXtdate:create2014-06-26T14:13:15+02:00=6n%tEXtdate:modify2013-02-04T13:03:47+01:00zatEXtSoftwareAdobe ImageReadyqe<IENDB`ui-icons_2e83ff_256x240.png000066400000000000000000000107671325274564300447150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDRIJgAMA a cHRMz&u0`:pQ<PLTE...........................................................................................:ZtRNSXG|"2wfZNz@eSFcaMhms}䁎]bp Ιi8*yѧȓد͐ǫʄbKGDHkIDATx] c۶H阒K8n&s6/^]umԦk:z ;LS:1ǀ5ԻBEDDDDDD nJXyO4'|Jf7ńU@ D!!~{=ɖsLBI`܂fhm,ףmV$=dc@.=siށG/BܽǷJI<\i 뷕#˜: HleF<\|Od1s9+3;-˟5ׄH,0n9o=DOH./H:ݩ۾ \dDDDDDDMEm=݌IJ] .Uր*lm.^NɊtoozQ?/OZ6'^{Å| xK,=#m[;'aK4k4jeNϷ؀tFkoNX{pd0 z`]t`ę1X LB $ KZpNy~>&"""""bԸܻ8wTȣ36Xn;g`Z/'ʎ;7}jmtxշd0O/!`//$3j^_pМ 7N@nH,0o'i  M}RY@;=[҉`Oa<1^CBk_DDDDFzod|U5i)bz_ip5RRWbT!l@R5Cf|Be:.3mG/t{߈ "gM`\X9A))SXb7t,iX;6*@+4tF#H M 21&C!O /+n}HFH@_t?""""f!S~B~[[Cn*7r`r\*f49qKE @gJqW8d(n '4*^QLWmREs C߶Tf+[uzI tUm5AZQiB 1D'YDJCc8]&{0TG$! &jI` CU\h<@{1{.J}LiR 7m >H UxWiJuyU>b.pK!/|oOׯ$@nr@I0pxx`_2 ?- x~9pT GDt'=!|/\ c&, ۟N&B} <9~Fu!y ;L`X\!-&-%b21v F.7N NU~#  V홛s0uW80-p%dC-Jtv7DDDD?=6m#AGmQ#vckyޞ[&kBdu@6?43ES&wZGJE?]yYi2=\kh5(3Tq;1cr7o_;9f~/_{Ao BA?6Bn”&HV_,\ ٯ.R:V3mt fO<}v6ʤ}g=^`Od|xS߷kg>80v(,` uh.l)cb,Cjj[Rߧi2DZ1@&Ƀ?/ HujgOc|(9hha0C#^$da#$ɟrwv__~Sz¼zuZZVwZ jP*ܓ@& wH8,StQsM?N{ 0>yAAQm=X rqQt (""""b i1\y[ U޻iA +·@W_ _)K?܇'O&hVM9̦/ a!Q ¿+,iӍؔ=d]:t3*AW1^ n_IrJ4M=ݷ=SP-#PNF *Rkgj>L Wu4K!Z?5Ci/e 0!2uPT)9-`>3^jMQi`ol_]f v%e(OH4p,DϞ?;u@-Rnfjġy&  +[}J|.Ȇs 8;S/A2|_5JGW{pPais fgggpMYs=a%G@jhm`i%cL0k؇?GaHHI=_"Q&˚ ի#$.W1`s2"""xwm7j@\\ͨ㶛5)uꎣ޵ ,aOřy,_/h]\h޴9`# M [z KuO_z˿Dܫ*kOJ(7v\e IT}aTna*baoۺHXaEzn NS&Sn4A@r8OW+&bo v, zh&T ǀa5:=SD0}b!pZpވXG:`?iYx60يKF>3ȬDP#^>@(0(RȠBFWmA|%CB6 &&UZHh " 07B L (?F3:&`f)!nE[cǀ|cw`~@DDDD͆9~ݔ^ \)\7UV?I@+3}T& ) s!N֫CњjE&n߆s?'5{Ov9(-o_HuKJGPZ)j\X_ThM<:{y a!)l?\ >WޠdܵrLuW^hzn*w}>.ϕox^V2U+3N_7]$邶_|]rSWp(?Og-?h_!\_LCV47L!~B@=ug#`BB-Ⳁ3Q6.}v)ASY2p>`ԚAPbt*U I맃Uh vڑHuڕJw#""""""<KG5$/ ?=7$L%7vD cM DDDDDDDl !5!hHDk@U@RPno/_ڵ7S_CuW_kU_ 8c@ZcA w1r}݇O%tEXtdate:create2014-06-26T14:13:15+02:00=6n%tEXtdate:modify2013-02-04T13:03:47+01:00zatEXtSoftwareAdobe ImageReadyqe<IENDB`ui-icons_454545_256x240.png000066400000000000000000000156371325274564300444730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDREr@gAMA a cHRMz&u0`:pQ<bKGDE;-IDATxk%Gum;!^pl0[HXȹH:w8;g1s]&<Clfq؅!,"·Wu׫{vGwn>UUuN=ΩSx4g : 6!Agb3 6+JD0&s؇N_Bs&&ڏ>~= c{vj%_DYEF qܸG RSơCEہ!' NP)~tϷ=;wהmeBH{s S&S6MCP6z̐!(hnE6VjնcO6y7iʶ~fKjڬT0<26+^;4~%pxh84^/ :2n6eLh`~=uprn0sOs횶WrGh _ Cmz-=)wFi#@u-cթPO{Ss˛^|BA'm TQn`.[ܫ |Եt7^p @pxh84^sC8UkN}A 栺*s%~aJYT}-1pTVP%%?p(>~PA.E ]1^([#\ֱWRЃ>4j"mhI@I>]yҙA6E:q7n05AuPy L8Pj K9 [EZ.Ȫ.Lu֛L;Xt0Ua29@às_!_ l84^/  @7R?~¥\4F>R7F{VCl9wT.[)?}ڕk Po5othz'\CVgskۥb%@6[\ܩ۶`6YsKݞs7jAAnX2G?1R6+u W@P"[U|~mm`3` { H+"ȤN=Mϯ4U `z~S=n0o粤= ̙hJ#[4ٗv4`5@bt n0w59-̩B(X'[67i3SP@  Ĥ/7^/  @:6U.EowsXE{LsUXcau7WvR҅l~٭Nv]h;Іi;MGC'{I79J*F ͦ> B\u}A窞o<ȟVaCHP"nLum߼]݅=eMFdUf;Il;:DCߪiŪ'Ƚǐ_VL:p s/5V.~{3R[IAH]$fCT5 Pہ(Ku^([ݬN4]&E@.`C5oJ @pxh84'Җ:6lB'.D Рxw5%1\U)F:Jm^[uRdTwOtI&~|Uͩzq;j~đ TsD_S>ܵ TG4m5'W:m" VH#t|dM‚ nz: um?bGA-<[ez#S/\хyDV{+ @u6y#L<"<]5d lj&˺'Y%iFbgJ AH} P=o2O౩pxh84$Jl_ҿAwp( UNQ߆'$kLe>Wƞ%ײ:RfQkb½?.ux8LPr07jNװOfX˼I\99Tn?S#Z֙kk@ g?mbiʘ,kċr]= JOE*We~ 4 -E,dN9wuʬqw>c3e|qEwKځ3%,y ]Y$p)CǢ yR<KۿK}AHD`!@}Enevp7/ 4shVTʉXJ`ג ~\#gᅑ./T!Ij|*<^T@+]uƜO eә"O>^(/檜}cݾ~["ɯx·z&$u7@L&ir=G g jw)ZLZsܗ~M+@>7pZOI'y#GSAS^zUK! ;4Og0͂_ ]%=e`d ԣ=K?RPz12Ӎ-x9uJZ[Ԛ[@byvp#ai.\wiC#Y. ǵꚸM*R6ȩ-~fZ>Ohզ:as|3|*f0S}a?~oVJ]@l,5}c×%ESԸՙ_T !co `_Xp j9C>|"qOꁾKt͝i' t #{P&rej5hv^M8GE h&]@X֔AfaJɂH*_VnX3ug<}hr PPv!o[@ [DF- 2)oCOSP{y#\_.1C|ܼ],ҥKY단Kssͅd w2L)߰O˾e+'HL$1 ?Ѽz>!0r>:xGtb*h8.pxh846VtU9Oyw莇!+IT .ZvZF "pS΃ѯ?!p !B|5CޞH(zzi8[97Q{ z_b:LXTO>٭E {R\)|er}o+BO#/}WH6gvs!Pf^3d"#Qgh8la0`XJ&ɂ`7;a E EO8SҿLD_:Z Qz2*T55XQ{8!WfS~!a89'JHSle'@&3YE,_ɷx1O WP1E#u3jkEyg\iQbp-; ;ť\ {ǔU5e)6ti,UJ6os&X!؉N3˔A! :)OFH*V3 9g[yTΞ,FU &_C<ҫ(_?(W1p? OW p!3J8ҨoWd'`9MYa,3˳|ClSŦ.׳wU!ܟzBļ:b ؑr@jvx_ E4X3|8Wi5gqqp/+3v+s+Q7].v )gp (6G,(6G ?Xd ou'p0M:nCfF/?>mO΅ؑT^rR[M)<#5Ќݤ>;rZs[{uY0c?]yUm-~/d|d7?C e_>DѡWg>iKݑX8ƫ "Jfqvqxh8 p_]|-Yw :o\+ >%W7 :r.\+tW#*Oԃؗq!TFQu~rÁ &(_=q~}`_ƳCN%EHs hkQEϬ6\PO6/I m!ƹClg>҅ 1O 6X}G.M.W@@,TKD6k,s?ofR"vޣMt2'zlY=@'Ӥ>U?Rω;}]q?ko)\LXfӜ4X jJĽAonLٮg{SaDVf2n2tdcj˚k$ Ongyv'C46ާ~4=MI3dF9ݑW= dH!R u}8o=-p`$ ^/Oe4VF/X[II V=zd1abXC :oFE,(`p4lbZD'OWA]4RCC-yЍis'/US$C C#\r=hLݒï6^/  @Ñ[D*-> Mk_>zlIТ3,ruX(+d<dBqv%^IgPY=:-*%]~E#grboncLE8ȝ6ΰO;㚌u5>zAC &C&gIZP bsb/p.o'G+o^_n\! 噻/fvq=fJ>yOeĢHES|fD<`gizI>7p5SZت!{y{~=4@#˹5"p@yU8ZSq ?*66x{ϖ/  @Ñ <п8~o>U8/yu67s|Wٿpo.P<]ץƟ/Doz7hz ! 6*Uyjr6~`oK?B?o˿9iic.ͅ%wvms%]\7}U]7%~T_|JOvh務Ѹ[鄚{ [@+r.~ٳuĭO"2#f`Ͽ=MMv7Q}WLZnJߔ B &+$U= ;T=(r/Kj +oد~jI8&e 1Uyz;6~hoK?B?o˿)~yUY: uRXX ^fNK)Fi޲i~o8KX/pu 97:dس~o! 6^/  @*l;ݣdmxr ]1t(XnM^ j!!/6nt1P;FXQA^qƍtCTxDqۜBk 3||9/`/  @pl]h^@l!=mV$! 3hZ`꠳9 `x_ akݞ=k.|!,!ثØ~A+l[U?d0ğrB0_S<ҥI_~?CWS|3ȇɲ_>Nք(az X>E=<Y=kuo UX0%tEXtdate:create2014-06-26T14:13:15+02:00=6n%tEXtdate:modify2013-02-04T13:03:47+01:00zatEXtSoftwareAdobe ImageReadyqe<IENDB`ui-icons_888888_256x240.png000066400000000000000000000156641325274564300445200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/scripts/jquery/jquery-ui-theme/imagesPNG  IHDREr@gAMA a cHRMz&u0`:pQ<bKGDI( IDATx{eE}?H(bX!²[R`JH!wMbݱFѨh ڥXT#D T;"baRqw}G)b}N9νsOsϯOw~8<3 @@F3 $ +\$ Ook؞0Z ""`JyQzB7iQ'.qIvCT2mk5e[7Gt ͩk!HL^?3dJ[ fZS0Ms}`T8t US˴ @1a`fekï6^/  @px{|AQ"iO=]}=\z\mc-ܷ>(DЯ'L6=;㴓l ۧ=M/>ڠ̓6(ڬ0$%GI_km+$.q W7~!pxh84^/ ƐQG8UkV}A 栺*s%~aJYT}-c*vۭ.J=C `+~P}\=. 43%4 Ph F,_\ֱWRЃ>4j"mhi@IzJҼy tsY@ee'C뱅rjm܍C&[LM%m{e^.%flNźuѶd{$*"nK9(ufc/ӯ<-!LUi{&0lWW/  @pxh8͡phxѮO)LQ.ޫU@P--[jj6ƵKǎɟ 5r 7phۊ{]Zn!w3ڹRzz[ ta.m[uD,i9ХcwnϹCzƠ Gm,o}RfNaÔ Juˁ ]oۯ-Mu`f]gJ(U@E N=Mϯ,U `z~S=n0o粤= ̙hJC,KJs /&޸咲C9Uhuav^u9&mtjc(0 H @ᗂ/  @k R]y{L*k ,CV= &*aTW;uMB-b5߮ `bp]Ӵy4ttClhbٔއA/(qW\;ȊG'+? U@낢sBd * Ȱ$=U!zӊUO{'!`5͔:KU=Œ@mRal{IB%b5}ZƀT7 셲jDe!014]Fh4^/  @1|Ef /nl&tBnȫؠ ʹwSLODHZpU]g@*y;i-JӅR޽HCt5jNާr Ls_S>ܵG]ϗ- 򩸹h_Y=^4Y kNջNc>BGA"I՚L@<4AG<!s"=;a[@ȼUw0b?X =0Hjr~zV(U&v$>R@P骁lO$H!`;pdvY$4m^U_B`ڑyP0l'z&m0Oౡpxh84$Jl_ҿAw`\qcM !OJe8|z==[K<.iǸ"0kޘW =r)0O1%^q&l ;GX)[ʝυc CUrH=Ž1eq3PRN%F./TDUC|'ȔՇx ;w1ws:?r^fOE1~6 Y\U h&( 80h'yW'w(:* yMjmv/ {pnx/ (?_iRz5^z,f\ ,OsxYM`/*6;xg2Ë %sW2ia l!ަ)9ϣՕAE~5]f"G"z ^`[[UE0C!f ,h5c5Cm:X@`IbSdshye QwB~+`$;\lc#%V]I%uB59ů9\ @LVIT5lΜoavY~/zq,b/쇗/&oMޮPH:`oaLc$1>a_~EvQ5yK wہ]V\MbL} n{7_$_f@M?g|vhЇOiT=wx96MRD?Pd3ddco/\=Zy*N֫Ig/7؜(86~bC2XSɇv>G[UBuS f%&?g.n^[$CA9D`}O&R5jQ|O c]z@hd} vnAsw$S&qNw8BHH.al/.uS?-¯[ Qߍ0~-ӗ쏌}dK@3>Lb,㺪k,OxAJL Q 4^/ yU4TtUm`:!E0ݮu\k_A>>^Ż 1y(u.pnf>NȭJǹ"o͐g(m!dz5p휝=Nن~Y/1 YP,DO>٩E {Q\)|uz}(BOU#/}WH6gvs!Pf^3dB3uBJ|ye0 ,p%dANؙ"oԧE~)_Fxq/O^_V=gOw V.+"8<g''d颹)cRDzIZXE|7F(S@;fqYw\Sc^sgqƕ-vE}ta;K3!yo`10wUMcʢ òZ]!N p⿖tz[8]L.IH⯀H1EU?~Ιl6ngs9;uW0QK|#h'_iDu5F4xVFt?@TC7Cln6p6UvE9V9rf 1k `[6"AvE'xxhQ Js998_Sq/\ALvIl_DN3t& c,{ou'p2M1>JZ˿5F-?>oʅؖT^r ZJjhFnm9zޭν:y0c ?]yUm-~C/d|l7? e_>DѡW_ִ%XZ5,./ Gt4^ _Agܣ7ˁsE!ܧW*Agޣ>p9rNٟнQ $=f?qb_fƅd &(_5q^ϒ`_ƳCNEDs @Xd6\hl^͍B,19M2 ^G -WCf[X.I!n?.3_Ppǹ@}d@'3d>U?RI:}]k<`\LXfSX Y  E㶎ho~scv=C`x+!bc;y6SiqRN, :WqUw;9~8cS_\w'^xq'c :QFf߷~,%}ϠW';[=Z6^$!E$'-vs+sU(tgMÁ,Qh<1cXIxm`pxh84^Xo26nQHAI4;F[ՒaA*jdҦa=4tX/,~RJ*o-O]UŪiS +8³*dބC)^H~M?f{lC ]GG %П֡ΨYT O BٯyXR >F/XכI,*YS/ (`}D,(KXgp4bZD'OWh+RCCMyЍYs'!nJC C#\r=hLݔï6^/  @Ñ[93\;4[C#=G=6!hMݏ^NW9:,so0Cd!8;2&s\KtŽl:-*%A$-G94?B8=:5FTcw[8͖?kxkj/~}cn"]o.0x+>`/]ޭ5.`+g("Fers"bۍ+"xxp=NKLBg [y E?Ga*J$su̟2$JLqW3[77CD0~ķx%>1 X)Ϲ G`j!gFoprpxh82PR ^[ ~F:.f~o8\#u]y K |@4dmSQ\WӣeWC|[V~[OItMt1RDU͕lw؃zwp]Wv` 鑫K~-ְ|1t40wq>PsߜIdhE%u]\1{ΘM`OpV$ /#=65ك|S~XB!~_1j])}S/G" C@4 F_&Dxv{\(rݯHj$ +oد~jIDpT@+yz;6~hoK?B?o˿)~yUY: uRD"GYNK.F޶a~os8KGY/pu}l42AHᵁ  @pxh8Deta{T O)Cץ{ C@=PSqHO3`cΗwC鑇=B_nPQ!`B*rls گMLS:1ǀ5ԻBEDDDDDD nJXyO4'|Jf7ńU@ D!!~{=ɖsLBI`܂fhm,ףmV$=dc@.=siށG/BܽǷJI<\i 뷕#˜: HleF<\|Od1s9+3;-˟5ׄH,0n9o=DOH./H:ݩ۾ \dDDDDDDMEm=݌IJ] .Uր*lm.^NɊtoozQ?/OZ6'^{Å| xK,=#m[;'aK4k4jeNϷ؀tFkoNX{pd0 z`]t`ę1X LB $ KZpNy~>&"""""bԸܻ8wTȣ36Xn;g`Z/'ʎ;7}jmtxշd0O/!`//$3j^_pМ 7N@nH,0o'i  M}RY@;=[҉`Oa<1^CBk_DDDDFzod|U5i)bz_ip5RRWbT!l@R5Cf|Be:.3mG/t{߈ "gM`\X9A))SXb7t,iX;6*@+4tF#H M 21&C!O /+n}HFH@_t?""""f!S~B~[[Cn*7r`r\*f49qKE @gJqW8d(n '4*^QLWmREs C߶Tf+[uzI tUm5AZQiB 1D'YDJCc8]&{0TG$! &jI` CU\h<@{1{.J}LiR 7m >H UxWiJuyU>b.pK!/|oOׯ$@nr@I0pxx`_2 ?- x~9pT GDt'=!|/\ c&, ۟N&B} <9~Fu!y ;L`X\!-&-%b21v F.7N NU~#  V홛s0uW80-p%dC-Jtv7DDDD?=6m#AGmQ#vckyޞ[&kBdu@6?43ES&wZGJE?]yYi2=\kh5(3Tq;1cr7o_;9f~/_{Ao BA?6Bn”&HV_,\ ٯ.R:V3mt fO<}v6ʤ}g=^`Od|xS߷kg>80v(,` uh.l)cb,Cjj[Rߧi2DZ1@&Ƀ?/ HujgOc|(9hha0C#^$da#$ɟrwv__~Sz¼zuZZVwZ jP*ܓ@& wH8,StQsM?N{ 0>yAAQm=X rqQt (""""b i1\y[ U޻iA +·@W_ _)K?܇'O&hVM9̦/ a!Q ¿+,iӍؔ=d]:t3*AW1^ n_IrJ4M=ݷ=SP-#PNF *Rkgj>L Wu4K!Z?5Ci/e 0!2uPT)9-`>3^jMQi`ol_]f v%e(OH4p,DϞ?;u@-Rnfjġy&  +[}J|.Ȇs 8;S/A2|_5JGW{pPais fgggpMYs=a%G@jhm`i%cL0k؇?GaHHI=_"Q&˚ ի#$.W1`s2"""xwm7j@\\ͨ㶛5)uꎣ޵ ,aOřy,_/h]\h޴9`# M [z KuO_z˿Dܫ*kOJ(7v\e IT}aTna*baoۺHXaEzn NS&Sn4A@r8OW+&bo v, zh&T ǀa5:=SD0}b!pZpވXG:`?iYx60يKF>3ȬDP#^>@(0(RȠBFWmA|%CB6 &&UZHh " 07B L (?F3:&`f)!nE[cǀ|cw`~@DDDD͆9~ݔ^ \)\7UV?I@+3}T& ) s!N֫CњjE&n߆s?'5{Ov9(-o_HuKJGPZ)j\X_ThM<:{y a!)l?\ >WޠdܵrLuW^hzn*w}>.ϕox^V2U+3N_7]$邶_|]rSWp(?Og-?h_!\_LCV47L!~B@=ug#`BB-Ⳁ3Q6.}v)ASY2p>`ԚAPbt*U I맃Uh vڑHuڕJw#""""""<KG5$/ ?=7$L%7vD cM DDDDDDDl !5!hHDk@U@RPno/_ڵ7S_CuW_kU_ 8c@ZcA w1r}݇O%tEXtdate:create2014-06-26T14:13:15+02:00=6n%tEXtdate:modify2013-02-04T13:03:47+01:00zatEXtSoftwareAdobe ImageReadyqe<IENDB`tpl/000077500000000000000000000000001325274564300316515ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/libbootstrap3/000077500000000000000000000000001325274564300337515ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tplassets/000077500000000000000000000000001325274564300352535ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3bootstrap/000077500000000000000000000000001325274564300372705ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3/assetsjs/000077500000000000000000000000001325274564300377045ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3/assets/bootstrapbootstrap.min.js000066400000000000000000001061571325274564300430530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3/assets/bootstrap/js/*! * Bootstrap v3.3.4 (http://getbootstrap.com) * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('
    ').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'

    '}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);images/000077500000000000000000000000001325274564300352165ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3apple-touch-icon.png000066400000000000000000000425001325274564300410740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3/imagesPNG  IHDRrr݅}sRGBbKGD pHYs B(xtIME-V IDATxw]U^{$FH$t B"ظbC(WDEE\\)wwHO&Nʔ3sz9sʜ3$>9s>{>ۿߵa@k=h28& R Vؑw1 ͒Ɍ7*t]^`PּZl7ㅠUմ`4MX,f5w_om c–% g/n[$>䌐:.Gveݛ`W`:vZTE>Smv a 4bgYۍ&cEE+%IrJ(S[~%`~9 xKY̦5 ޅd9E3J׼3Ak}#`s_sw-_Rpy &x-_5fU> ZP(` % &J%`VfZXTAiYmg{Ӥ-!n^z62Ӏt-5TXLcv8ئjZkQQ|:dInG!j_n LƆ[ Ab66фz*55t"MOv㊛'y&t +9M|[~gi $IRM0 Di\Cժz.50T3.^78B 8 uDسAgn|n~&<Fj DQ62~/"` 8?`;1.4?̮DI5JLu!iwkYܑ)ˎi;z|.a/uXT80$CuT 6(D~4US}?^FڜӴƎXŌF(`ߟ34 #n#u5˸pB.8ɓڎD:BD1 űYظf_ƹCh2XOXwN1V`ǏVT<y#>Mͱ bs91!͞I47/Q֬sOȗ[̜4VSo؆VG2uf$,~ueygG$F%Fb`{a;rtHkFmm!W|k9+.tI޿]_h^&} eV+\6mTZ & g^$s* ;ݕfWwtV%VInx<6>"v1:qg,BJ,f6M'!ڿ0?lF+jjڌL7 @'F.GFh:QDET[:sߓ&Q?̪i3\uz͝iZωd36 >ԔqΦu\T8x׋ zp~G`yZ k( $I9u,Zfui [{zýkGi:̪:[u %ͫ$*>6](5v`{sJ`$*V;[⥶B\ykqf!7QwӦ I)m/Zq'xj2хv,c"c4Npz8t:N6drӧXgϘ:yav,sw9EMA*h9EGcB.cd܆u+V.zWǻ+M)c]:M/u'G<̝d_o dNH+orSs'l02Spi;,ۛ5;?s8^"ZkpLBw>&OOk88}^v>xT :TYdV'QbbVN~otNI^|7Ɓ۝aq==:#S*>ޤRVZΚŵ%jg6xFA+ϙXg^8% U'SpXJ@˲j[o%|CrBdHgGpaC9EL(Ҟ^~o'dObm޶Sop4,pOo_lD6(B.p˽-T9kj:3Rj%>IbBXMRLz`ޤ"nwV脉6ƇJ~::blD &3Zfxc9ycs} S\l2 hfv^ \f%14@?X#h-zhÙkX!1tHB "YZ;J%[ﺳ[dN, M P S u pB^3'Ou)+_QtJi3X`~6wOtwsJk> ŗKhET[D팂_\t[׬H7꯬]k#mUJf:Yf^g#:k~OM@u%s Ln)}@w&cfNU"M>cWhH ܼ?wR>Xjl$ڟ C_rH?;k5^g^@؈NTC jSCr+͟(s {2t5OI,s&X]Izb%%a4 kYO¿||%h`]ʲ̔0nT4}z\ +8U>7ApW\Qb t*ۯhY>1p^"}16b-E&Z*2jﮕMYh]Ԫ8wGGtN3Tp'OsfWL^cݮHR}?6Bt]GԓN󌊕z}lkߏ[wEciE O7*ڝ5w9* =԰֜^?Pe&S)U0bUExʕsVicJJCz_ȿhI8,^z7obDƌSclilkhӁ&r?ݝPCݯulQ u2T(1n_ōc,hp `0YAWU^{ XlhߛUd?AsM#R]۶gFf l9"jrZ]0^LdzYqt)C"|s"\GdfX']v0:gnd/7^*+n}υM-Zi>!zqXVHwEMGXVծTm"J 42 V huJs"T}=pE⒅>Ns6Ֆ06`c7cg6wK.<<&(3&jX]Ρ$V4??>R#WTB-w/?w02QYNQ1Q8ce9p3g;9{ Ij=͢pq¡3(3}dGvl)+ݞ)Ɍ&S{Q!=cKvNԩU}  vBn5Q4S$bh jz>ԙDĬ9p,z?^#kR-=򛜪: m5$]d ѯw) w>-*~M|~짿>xkv+3gObj6QJx3_"(Z 42աQ E.U# R?eYj4vO* ޜF)o%72bnjy=NfΜ̋B67?gǞE?+ܼeӏqyer%uOH⇽^M不7F0e\S~V ,!?JieqZevv̨G^nG{/LMskZ^&LlE:"þQJQܔ*Cyn'\v!0 L2Y]zcdQQv 9Xen 2kƤ!Aa૗qdYk8 GkڜjuW!h1AƶFL$.zvw"W┏\K ִ]lccDzlXV[w,?hoƄ Lm:Gkz,A,>}ҫ\76xk[y^M!g˻]d_4n#UdF}ٱE/[K0 3$ 7Ng/^ RYf6^N(YM*hޝW@SqcۈG~3)=k6FGl ࣻ;O(e}Qi3#}1;Such-fI-#LuΝ{ٶV &Y"};_ERSo$w scd'җڏDgϩG<66bggK\ (g7Ƕ[?edPXC{6X_Q /?²Ω-u=NsD*4 ByY7;ҾQ(g֞y.-rF}}MNfDkP J}N';+P<{O}h,QGlG @!&4kgj3Ee.B缗(~C8psMLO[g̥k/ʯRAĀg°D ',uTMGy&Dca&`O iq,“Hٱ{?JAij~i:<-䕧@[[ay饽/`7߹8>{T@vM>1i_ӱ͢ϘVQcZ0u* U y1:H_@P$t೛GJF8p09+M(Q8qb6ʹN( }B.8*;mڒ QV5UWU1-?i,t Y3¡56Ͻ@̲!;v5bQaǾnPJ;3ESO_{MFKiӘV#sѴBtI<˥"fn7-H͋VMMu8%d24ʱe_)/ <lAk9J0৷72p?kڷ*u7;gNG2|(͢ Acb#_xu!Έ@CfZNݚYggӮC0]}E:x㭧oB0/ hg(I Y6|oܴ54 9YC`)p lDm7'P| E6ngd37d=s]!@Q5::{쎢\+5~U`< %B~/ 3NP,.>b uY^Pgݳ'V6a#U8jVhD>qDq+ zQUE%P^, DVHGl߶dWnCdbhcڙ>Ú'p 9jSE۟7A˯ 7POεl3&տ+'MЀYGdG3kGs­NB,d~[5[0i8 m-ӠO!PT46˗QHBTI vuOX.>!?3t{Y8ftM#_tN99c x^=82*n7 sڈ#uMFB8$hΎ[G m|iǩ,d:3^< yxtIy\)猀m{ M0G*S!4tIBh% 4Ch%3{B^p6N,f#KJe\N+03tlًtڳf Y)!!}DC:}a_on݁m'GW$afE-Hv+ml݊dوYA (AWtP|{mYȁTGHH!!a4q6A0#QC.?bg6u%.sf{{ҿ['M;qZ"O刣uyLq9LK]G;puhQBL=E8t\r.I Pj $a8y6k.EM#Qt]''l6k)zXGd,X{?J ܳO|كu} CE+_qMb؃sEe3[yͷO>* UUzEcǴHcېƶUgFxf3It=EOg˶ IDAT2 gIBhحX>yE*S(@PZ%͑ 86N'Z9~ Ap0Uәؽ5NE yz3o_ܽfeT鍾i˶O;hK"8u (X22|ɜl ,,݃]׈^X0:W m( U,(RUH2$SYV v \h͋7eƌPejZd*iz ~T dZGK8DooQVLW]00-4]W,*H*QThQ^zE TVrD&l6i#mrEÂ(árL4]]/7, Z57z{E,{h$J)Ҥ"*+৻o V,4xqr˱uaUEP5AHRյH PQX*T]i؈P5d!U'k*4u^=JOz]I.6e>:g}0HrGhuCn!H }CЗV*tV`3Fbɏ Ir^%[tY@PYN4]]T|O,[̪L=\0:"i7|@k7mӛ,&jXbQi`OnՁܿ޻T1[w|Sp#Q҅ 2N~Z^zW(QqX/bnt0LX fJɼ%s'+>' U/R A{&,eaBda,Y,<2j ºUk}#w^)`%8 U }:~^j^ڃ|9BJ:D Dl,7dU]#OLje!P4?oxT1] UZ;'2=p>¥ ز=;)_2Ahm 6|vD ^PE# HmRѮE|c I}I2dDs1Q\D!YYSA^ɓ/&c:ewWӚDldw '2ej<:UӖD"q͆2Lom{<S3Da}+Z Qb8lx!I4?#^ӟ($P5r+ݠ %ʋbTtn6929iLL$k;~aA}x=s4tH̡T*3e8oX7L}Րu(Ƿks $Ĉ⤊5qdeK? kMh JBcq 7݇I2umCF==L6mX.`n5lᝎ[/=ît ˕Y@<̂Kz+Gη׭HDP約Cn~5a uovBhqdDҶڠPjT M#~ŋ!h y Xx^?Vb ƚ=Op|dyyk Ķy 磯8Cά3N;l6{H }*|^A(6-tڥYLwtwBO]=9=9- zN ~fevdmtx[-</Ջerُ샪B4P?ЦjEEh0i#~kֳ{Z|LM.BP*fev}Æ3)o9 {۟ - V"ul~mht.6sRY-L>Ǟ <ˁB| Nג}+sNe)AvK'`1{PUD2U*&YPybk@`*,iP}z5ӛꥠ{ϼ#@с@M[fxEm۶/x"= [BĚ`yn3=LpڐFdwtA>ƒv,^d髯kϥ-eYl2WRq UI3MWN}38B_KvW 1ɀHo ֮WH5Ah E2ڗ9N" \7^_6G{b6 ,+/_ϿW~gU{:BF<+Umx Jm=ėN\ZQ'oTW[BC' DO<|>?G do$m;k AP&뚎e1'w=!{]!l5yp]MC\),w[s]e s=ewj)jKTeڌj򄄎)M0O7F;HSUJԖ'yah`F̞ F d4kM j tN4p'`JA!]5cIz)vr㲙&Mi׬ߌJo0CtuGQ{MG$vW]u~O!s}+ S,KxNÄ\^,xQJOg_ďqzT'Uef*<=!t^p4-hp8w;7u+;w%);|`S&ң2DRfUS KQ2@_(q#B 1cu m@ϲNz3/|_zMU ~W:.qi oRwSMx`߹o.DsW V7Q'̚EXXG $Tl^Boo_E4b!a1[SW˿؅!%s(<,F3c\~fc Riv?ql~x/8tF6*hbrS*,^ԇi@HSIH\tyl%0$H7|@?F[Ԯ7ėg=SJf8  [{$[lP. __[&D3ghth(\.-jT^$#& "/XO=.YNBlxӁx6ŎOQP#P` .mLG&pYVw>Ă r֊yʙ{(q5&{z$ ^*/xaH)۟#[+ \.+0f42x"aXp W0w ᕜ,.L,?ϧ)$3sؕb6>$T 0a)4d5ьqZnLFYvmRdBs 5Khγn~O.gN]HRD1<ޑmr|劻vV^Yukk>~}GkA_,'8T:K4QD}Hd9sC+.PXb*ΩBhwòVY|~O!Ɇ~vzPklv 7q*߼+,_~A5s6;Y-^|T1UJ+SɎhfv2=ZO\_KFigVNi,t.8lt]UU# pɣ)Ec ~v/^nE=1$66A.ٔ:Ⱥ~Jn؉TFbZ\h>$:vDvސݘѸ_*b)}x< X"h{WKK> f\NgMtA*!K7uj㛿 #sp}7 Tes0ݞl$a,rO↱gqEh>5YV_q7wFd38¥#_:Ea}6oF$駝LF#|m9@%[1h1KZmȞik-ؓOSK qO= ͣYV۠x)h4}YۨXkwwW7ٜp0ȡC=u6X uBKbpLPP,8q./|~G7 :<ٕ %{,Dt&qKbY`[pɇZͷҢ~y%Qtuume_6$nH lT~w8b +ʹfQx.gg?ͣ?~s.Sh3=ͮt/SaGkI5;ݼA\cQd1T>|\o ihQUUV^?^~O&m.Y,FKϒrZdK즫3lUCUj<#69Y\y .Yqa~zoȜl v鰀nIu=qV4X Prl{g.qM Y[s &3Doo/7P(TNTMA˯=իWKGLSy;{X f˛}~J_j-5DM%5zcx HPNh r׿o~@< OT ׋ n^x?w'r98Y)AvceGjdsp;<1nIM+~È}=9=pkZقb<2qQ, ᖭΞ{:ix%¸=nnd^rx )[¹*k&!'a@D*Ǘo{g?VֱR>')“\19"qӧ1axZZxVKR$28H& ,>uVۮα^SX륒!IX0Dn{ :$lRu]o%vq'$PЊE6M(L6زW>p X)͏UU]Ee ـ$I]iO>D>\V6NYjv3jk訚zX% N_>ω<QGrZY)IdEeզ|N+pAB2OosQ~ߖ2M/#< ( {;{>#DdZvul&Ee1j`>^'VR IDATXEu]fx“O=ڽ{,TU% |:T8\4,v3#\(Q`@6ۭΚDJUGI"VAoA-u7$vٛ2|}{,PWU.]|uf J2O}c;7lhX,b|>)Hԓ_J]ϝ<9χ<(㓤]9N@2*_kr!`4qǽ}@d6?O$] `2X[om۾#J&T:D4aWo:`+Ѓ;qBYy{3uAR&*5q6 /UѸ;>d:δCMJ.-&ƝMFBkMI脤 lHtu &B;T1@@4cN}ͼwYH%5j@YsrN2[Rw{ɱ;^`6%W;50y}}3vh 8ccc8,2ϑ?akx{6W3JKի՛;7j?ZG hO}q (o'"jL-CG*’{ >ۋ7!#4>ۣ͍3ՏOoO !$JkvztttnqqQE)QK2H?G,$` rYr\AWW9r v42{>Caru*GS>`SҪT; ZԸ+ijr)W=ͬW)L)VEbJVVb%0Y`*6`! OU)()qX\R\Ii3Ajaz>ßJ͛U6<-U'oݦGږ4})5F-N+fhB8}ÐATV#]q\ϯҥ)1#aDq+-JF0g\LB >;՚ J$O<8'=iD_ʠ3y?vNҨZ)VNH`Q4N;3d1դC&i31cB$0 }0*숻d2z$d}YM{M+a:J-3I'i.f1W͝0orJ]NY(؆FpTQūG$}>^G+A hN `8d;d03K,G'ӽ:ӺI*1?rը--LNMMcS◺rxn0`>T-Vf=}jYX{5*iǠsȫͿ*m8rK1ץhUY皎mKɒw A ;K1֓H 3= lG?Je6QypDp*8?"[qy@VG5}n UDPJ=j+fJXU1?y,͌WGT4QR)l,Q+v/⯣U)2#;QM GpJsEE DHCC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222U " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?1KJ+sLREKE(Rъ(bQKb'z)h%P (QKE%())h(QE J((J^CEJ(%-%PQE%Q@Q@!(((()J)h((( ( (aEP!  (E4KILAEPIKE6J(BPh4QF)h(PIKERLBR)RR@Ĥ4 ( JZ(mJJv)AEw(R@ EPQKE%Q@%-J(P1bQ@ AE-QE1QHbQږmXJZ(4(BwJZ(bwEbRRRSILAHih%aF8Z1@XLQKڊRR@(PQK@(J((@R@ҌP11F)hBb((sGz1E ZJZ))hvQJ(ZAKEb) ( KEZ))hRc& ZJZ@)i(BQE)A0Q@ Z;Gz(ii;R0(ihRQ@E0 Z(IK@ )i- (P Z (QKHAKIK@Š(E--%- ZJZ(JZ(IKHbIK@Q@ (Q@ E%-/zJ(E-QHbER ZC)QE-P)RRKIE--%QE( ZJ)PQ@ KIE-Q@ŢJZZJ)QE- Z)(Z))h)RvZ))hQIEPEP ()hZJ())hE0Q@%-J( E-%%J)h \QKL(bZ1@(.( 1KH(h(K)QHaE-RLBQ\Q@R(J)qE0((ZJ(J(())h1%-%%))hm`%Q@(1( E-%%Q@((`%QEJ)i(RR@ EP (Q@AAJJ\Q@%PE (J( (;@&)hBQE ;QEb(RRLaEPIKI@Q@&)i(bEPQER E%-R@ E-%%SQE%);QK@ Eb CKE)hR%QށQ@ĢR@ E)Z)R(!(-bQޖJS(- QE(JNPEPPM)( (QE ((E )hPERR@REP (QJ:PEPR(( KIKހ (PJ(aEP!hQE%-ԴJZ(bK@Q@-%-QE-- R1ERR (Eb (-P(RQEZZm-!KIK@ KEQE-QE( (-%QK@Q@ (RR0(RREPE-Q@-%PE Z)()RQ@ ZJ--%)(@—&ih KIE!KIE-(sEP1h3KIE--%Z(-%Z(-%QEPRHEQE (f(JZ(!Z%Q@Š(AIKHh)RRR4SRP EPU.()hbRK@ E-bZ(--7P!(E/j(RQEQEb(`6Q@Q@ E%Q@%-%QE%Q@ E-% (%-&((%Q@jJZJ`QE LRL((J)i(Q@QE0 JZJ())i(((ZJ))i(QE%QEQE%%-QE!jJZ((J)i)QEwAIKEQA((4PQފ(GjJ(! E J(4P (QE!E PRR@v%-!AEP1(AI-J)hQ@ )hQ@%-% JZ((ZJJ)h (qIAQEN4 JJZ(QEQ@ E-)(b)h)i(hIKEZ((-QEQGz(;J);Ҋ(R֊LsKEQKE)q@RE!R ()h(hQ@)iRQE!E QGjP()GZ1EahZ8Q@Ţ( ZJZ(F((ii.(Q@KIK@–Z()hQK@Q)h Z(QE(ZQ@-%---% Z( QIK@QHQ@Š)h(hQ@Š(AKIK@waERh( ZJ(J( EPE-RP(iiRR1hZZJ3HJZZ))hE% Z);RKIE @ EP1hJZ)i){RZ( ( ( ( (E-% ( ( ((IKI@Q@ E-%Z(/RA!KE ((PEPE--PEb ( JZ1@Q@%-J)i)QE%S((J)i((I@ ES(J(J(J((AIKE(BQE(@QL ((AEP0@i)i()RQEQE%Q@ EP ((Q@ E-%QE (( PE(()h E-% ( ))h))hQKE%Q@RPEPQKI@4QE%)(P!((bQK( KIK@ EPQKE%%-))qI@Q@ ҊZ(R@ E-)hBb֜i1LB )qF9bQKE%%-QK@KE! ESKbRH,&(PAKE0 (E-QF)(JZ1E J\Q@ J)qF(Z%i(QKHbQKGzbZZ@QK@Q@ţ ((P(RE!-%-QK@REQEQE Z(R Z@RKIKQEQފ(R1h)hQ@(QE()RR0(--%-Q@-%-!-%- QI@(hRREPE )i)iQIK@RRQEQ@Š(AKIE-QHaKE%04QE--%Z))hi(((@Ţ KJ(ZZ(Rh-RRKIE-QH(LŢ ((@-Q@Š((:Q@Q@(bRI@ EPQKI@(Q@ INKL((Fe.(JZ)qH`%-(Q@PbZ(( (E-J)i(J)h ( KE%SQE((ԔQE%0 (%-%1%(((( JZJS))h(R@ EP0%QL(PEPQKI@Q@ E-!AIZ(())i()QE袖PEP EZ(())i(QERR@ E/I@%-%0 (Q@Q@ ES(((Pii %RZJ(QE JZJ))hBQE-ڊ(J)i((AEPEwQ@ GzZ1@ E)I@hAEZJ`Qڒ EbZJJ;Rb(RRPIKE0Z(1H%-QE (J)qE JZ?JZ()q@P1( EP& )haEP@E-)h (E (E (RRE Z(EQK@Š( JQ@)Q@R QE%-BQޗQ@ ZQE Z(()E%- )h(aKEQE-(E-1K@-%-RQ@ EPKIK@Q@)i)i JZ( ( ZJZ(( (– ( ZJZ)i((h(aKIK@- ZZJ)P1hZ;QE-R 3@Ţ)RREPIK@Q@ ERQE- (bEQEQEQEQEQERRPEP (4RRL%-E-!KEQE )h((aJ)(RHBQKE0QE-%Q@Q@Q@Q@ E-%QE JZ)R@KI@((`%Q@ (ZJJ( ( E-% JZ((J(J((((AEvaHii(RRL(ZJ( ()hQE JZJQE%-%(PQKE%QERR@ EP!(())i(E%QKI@Q@ ES((%RIKE( v1(Q@ĢZJ` (RڀRE-&)))hJ)i(Q@%-ZJ-!IKE1 KE@h)EvQEZJR@QKIځE-(P1;Q@(6R@R1E-% (P(BP(P)h(bQK(QHR1( (Q@bZ(@ KE ZJZ)iQ@!--PKڒ)hQE- ;QKE%: ( (E ZJu%- )h-PEb Z(1@QE-- Q@-%- (PEPE-RP(h ( ( Z() ( ))hR@ KIK@-%-PQ@ EPEPERQ@ EP1h(Z))h( KIE--% Z(LIK@Q@ ERRRJ)( ZJ(QE!Q@ E% ZJ(AKIK@Q@ŠCK@Q@ E-)(( RR@ EP}($)EPE- J)h((()h((AESQ@JZJZJ( JZ((`QGj@% EP!(J(6Z())hQE(JJZ)J( JZ(())i( EPIZ(QEZJ`RRJ( J( (((ZLPEPIKE%襤AEPEbE-%1Q@ EPEPQKI@%-RъAE mP!())i(S(@bR@ E-%%-J)hBQKI@(RRPE-%%P (AEP1(b (KE%Q@4R@Q@Ġb E.(P1(P PINvӈ&%bbmZ()hbZJ( Z(Jv))1GjQҊC (bڒ1)h(@ZQ@ĢQAJZ1E-Q@–( ()hE- )i()h (1)ESQKHbREQ@(KE Z(IK@j()h)hQK@Š(KE-R@RRER ZQE-Q@ KIEQ@- 0 ZJZ(R0 (-QE (J( EP0(QEQE(EPKEJ)h(QE-Q@RE -Q@Q@-%-QER(RR@RQ@Ţ(hPEZ(E%-QE/j))h4QHZ(()(RR (( ((;E(J((QKE (J)h(((QEQER@%-RLJ((RPEPQKILJ(%QE JJZ(QE%PQKI@ E-%0Z(RPQKI@%-RP 1(J(%-RPIKHiRQE ( JZ %Q@ EwIKE%BQE (AIKE%%)ZJ((J((A((ER EPEPQER@ E-%QEZJb!(`RRQE J((J:QEQE%w(R@(KEQE JZ))J1KE0ZJ(BRR@1((((JJv)(b@%-LS(.(h1E-%%-PEPE (-RPE-RR@ E.(1 J((\QEXN- (F((h(-%-Z)QLQHaIKF)R (.(ZC1@/j( ;K@ (aE ( Z(KE @-P0(hZ()EP!hR@-%- Z)i)hE)E%- 1EQEQڊZ(E (ZZJ)QE-RC (--%Z))hR((Z)( ( (((J-Q@Z(JZQEQE JZZ)(bERR -% ZZm- JZPIK@-%QEQEP()Q@Q@-% Z((((%%-QE0(E (ZJ)h%PREQE (%-QF(J)h)RR@ EP!(%RRh(bZLPIKEZJJ)i(QEQEQEQE0 JZ())hBQE-%%-ZJJ)i(AEPQERRLE%QEPhAEP1 PQE(@%P1(QEQKI@ E-%-'zZ))i((J)i)QKI@%-%0 JZ( P!(QE% E-% JZ((AESRRъ@Ph@%PRS)RwXJ)h1RъKEE- ))haIKE!jCKAbb(!){QE%QEQ@QLaJZ(w)h(vJ(Phcih7P!)E'zZ( ))qEQ@P1;QK1@ E-QI@9(RIZ(1KE ZJQ@RQE(-%- ){PERE Z(E-%PK@QE-QE-R0Eh)i)hSHbP:@PEPKEQKE (\Z(PEPEREQHaZ)h)h )i(h)h((bEQE- (%@ @Ģb ( ZJQ@ŠJ(hQEP1h@QE-QERREPEPKE (BE )i)h(hZ)(Q@ EPIK@Š((1hZ( ZJ(h(@QK@Q@’(QE-%P0((%(J)i(P (QEQAJ)h(EQER@JZ(bQE())i)RRP (mS(J(%-QE%R@ IKEQERRPi)hQE J(((RRPIKE%R@RLKE%%: (ZJ('z(%-RL(AEPIKE%%-R@ EP!(aE-% JZJ(@ K(JQ@%/z)PQKE%QERR@ EJ)hKE(ERwSQERQE (RP!i( ))Ԇ-J%-P KE J)h4QEZ`%QHaIKE1 EP QEQڊ(QE-PEPE-(E(RQKGj@JZ(wE-b (QE-P)htP1( (PKER Z(ZZJ(hKIK@(QE-%(@RQ@ EP(Z%P (-Q@Z()Q@-PEQE- (-P0)(Q@Q@Z(REPKIK@%-(QE0 )i) )h (QHJZ(PE ((((((ERQ@Ţ(h)i)hhťJZZ)(((h ( ( ( Z(E ((QE CKI@((Q@QH(EPEPEQK@ EP Z%PQE-%Q@%Q@Q@ E-%1%-%QE!((%-QEZJ( (RPQA ((E0ZJQEP1(%PQEQEZ((J(%-% S(BQKIހ (%-%%Z((J( ((RR(QEQ@Ģ%-P1))hAF((IږJ(Q@Q@4RPIKE JZ(R@ IKE%PQKI@%-%1J:RLAA m-Q@ E- J)h (%-J(Z(RRP0J)qI@'zZ(Q@ E-h(RI@( ;E% P; )hbZ)6wER@Q@ (bRъ((1J(QK@ EPR(Z1E-QK@ E-v N%- ZJZJQF)hRaE(\Q(QE- )h-P `(Z(bK@(Z(RKEQGz ZQE((C QE(()i)h (aEPKIK@Q@-%-RRER((((ZJZ((hJZ((Z(EPEPERRP!hZ(E%-J)QE-f (((EPIK@Š(QEQEQEJJZ(QETZ@%-%-0 JZ(((Q@Q@ KE(RPEPF(Z())h4J( JZJ`%P!(J)i((J)hKE0(PRRPIKE( (KI@%-%R)hQKI@ E-J(ZJJ)i( JZJ(%%QLAE-% KILJJu%%AEPIKE%QL(%: ERPIKEQE%Q@%.(J)i((AE-%wQ@4bP!(aAPIKF8)q%%ZJ))h4R@ JZ(QGjJRP!(RE!E-% (P1))h(Q))hJ)h()h((E-QE )i QEQE )h(%hZ(bөb) --%-%-QE%-P( )hbc1KފQK@ E-QE- ( )i(RJZ)h1)h )hRbZQK@RE ()h)h ()h)h Z(Z) J\QE \QE (AE-P))hQE( Z( ZPE (Q@Z()h )hbRE ((aE-((Z( ( ( )i(QE-%PEPEPEP (aEQEQEQE-%PERvKIE-Q@–(h)i() )i(JZZJ(((EPEP0IE Z)(QEQKI@*RE (RQKE (QLAEPREQEQEPQKI@RRPQKI@(QER@ IKHhRb))i(((4Ԕ(J( EPIERR@ EPEPQEJSE'j( ( EPQKI@NR@Ĥ-J( JZ())h(R@ ESZJ)1KE%(Bb\Q@Ģ%-QEQEQK@Ĥ )qIL E-J)i((E/z(Q@’J1ERS %P!( RR@Ģ)(4Q@ EPQK((RPQKE%bZNb((J( (IKI@%.( (QE襤(AIKF(%- )h(wJ)h(Bc\QF(QZ((%-Q@PF(Q@(RRE QE ^ԔKEbZ)J)ha(EPE-AKE-QK@ KEbKEQE-Q@!h-Q@PE-Q@-P0(QGz)i)h- F()hQEQK@PKIK@RKIK@Š(( Z( Z((bE )hRE0 ( )i(h@QEQE )i)hQEQEQE Z( ( ( ( (-%- ( )h((((@-Rf-%QE-RQ@ @-QHJZ-P1٤Z)()RQ@ E%-QE (Q@ E% ZJ(AEPIGj(%-((((Q@((J)hQKE%Q@%-%QE%J)hZJJ)i)R@ IKE( JZ 0( JZJ(BQKI@Q@ĢQEZJJ)h( (B J(aES(AIKE%Q@%-)h%Q@ ފZ(RQERQޖ ))hRPQK(RPEPEPP)hbQJi(RRPE-%0 (E ZJ`%PIޖR@ Gz(@ EPEPQKE%PQEwQE0 JZ)))hQERPKIK@ IKE%PQKE%Q@Q@%-(b 1E-!E-%%QER@`%QKE% ( ()hBR((aEPE-R@(aE-Rъ()hRQKI@ EPEBRPKIK@Š)iQE (%-QK(((ZJZ(((aE.(()RRKE LQK@@)h (QKE (()hQE )h 01L)hQE-!RRE ()hQ@ ER((hQE-PEPEQE%-Q@Q@–()h ( ((PE-%R@ KEQEQEQE-PERE ( ZJZ)i(4Q@ŠRRKIK@Q@-%/z(C (JZZ)( ZJ(hKIJ(R0(((( (IKI@ KEQKI@ŠZ(( EQ@Š((%Q@ KEJ(IKI@Q@ E-%))h4PQEQE1%-%RPQERPIKE%))i(QERRP0(QEPvRQERPQKE%Q@%-SRJ))h )i(J(Q@%-R@ Eb E-RPQAQ@ KE J)hBQKEĢ JZ)ZJ(LREQE JZ)CE-%%-mRR@ @E%Q@Q@ KE%(4RR@ ESRRHQ@Q@ KERb ( ( JZ((PEPIKE!(()h\Q@ EJ)i1@Š(!( JZ(KEQ@%-((%-)i)iRR@ E-QE Z(E-QEbPRE%-Q@QE-Q@-%- (( (K@ J(@R4()h- \QK@Ģ ZAJ(QފQE-&)h (ZJ)i)hhRQEBE-(QZ(-%- (-PEPފ)h((PE-RR@(Z%Q@1K@ĥ(@Q@Q@ IEQERRIKE袊()i)h((h-'jQ@ IEQE ( @Z%- ZJZ(Z)(ER(IK@(bEZ)(1h@PJ(BIK@Q@ŠJZQEQ@袊@QK@Q@Š(Z(( ( (Z((4))h)b JZ(( (((PES(RR@ EPQKE%%-RRS1@ E-((J)i(Z(R@ E-%PRR)J)h%%-))h((JJZ(4QE J\RR! PQEQE0 (Q@ E'J(%.)( QEJ("JSF)hR@ EJ(Ģ%Q@Q@%-RPb(KE4KF()hRRPIKE%J)h(1E-% (%-%((b%-J)qI))ԔQF(aIKF(^bPLRъJ)h(QK@ E (%))PQKHh1@-PEPESQE.(EQ@(%P Z%-%-QK@Q@)hR0((( (1)h-P1h( ZJZ(E-%(@-QK@RRRZJZC ( )h ĥ-%-RHaESQEQE-QHaKIE-Q@Q@Q@ EPF((()h(QE-%-QEQEQEQEQE(((RPKIEQEQEQEZ( ZAK@Š((AKIE-.i((fZ(JQ@-%- (Q@RR0J(ii(KIE-Q@Q@ IERRIEQE Š)h)h ( ( ( (J( JZ((QL(SI@%-QE%Q@%-QE%Q@ (ZJ`R@(ZJ))h JZJ))h!))haIKE%Q@ EP ((((((ZJ`PIKE `%Q@(J)i(((AIKE%PQERR@ ES)RR@ A"%-PhZJ;Gz(%Q@ E-&)QK)QE (Ģ%%-((Q@ EP (()){RPERPIKE%Ph KF(bQZ(b (%-b`%(`Qڊ%ZJ(E-J)i(QK(RPR@ĥ((4Q@Q@PEPEPKERR0((QE (-PE ()h (K@v ( ZJZ@QEQ@–(QE- ZJZ(( ()i)hRRKHQ@Š(!hC )hAEPE (P(Z( KEQEQKHaGPKEQEQE )h(((K@Gz((ZJ)i(((Q@QEP Z(( (%-((@-Q@Š((Q@ EP0IK@-PEPKIK@-%-Q@QE-QEQE!-%QI@Z( (Q@AK@bZC)h)qERR@ KERR@Q@Q@Q@ E-PQKI@%-%QE(QKE%Q@ E-%QI@Q@ E-%ZJQE CKI@%-J(%PIKI@Q@Q@ EPEPQERR@ ES(J(%-%PIKE%b(CE-%RP( ( JSE%SRPQKڌPQERRh(%%QERR@ E-QERbފZJb ( (\QJ1ER@ E-%P Q@ĢJ QE LRh)hQKI@Q@ KERPF)i(KF(;QE-%RP ZJ)i(QE( )hQ@Q@’JJZ(QE J)h(QZ1@%-QKm-RRъJ^b))EQE-% Z1@%;(QE-(( (PE-QE (AEP0( KEwP0 )i(ZQ@-PKIK@Q)hRE ZJQH)h;PE-QE)h (bRK@ E-QE-!Q@-%Z)haEPRъ(h( ( (E (BE (Eb(Q@%-QEvQEQE-%REPIKI@ EPIKEQEQEQE-Q@Q@QH (Q@(((RRE%-QERRKIK@Š(AKIE-RQEZ))h(E%-0 ;E J(QAAE((0(((J)i((((((4P!())i)QEQEPRRPEP!(IKE%QEQEZ(%%-J(%-%QE%PQERR@Ģ(RS((EQ(LPii QE&()h(J)h-%%P!((SQEQ(ZJ%PQEQKHh1E-J)hch(4RRS(h(IL(%-QEQE%RQKڒ(Rb E-J)h@%)h!(0RSR@ E-QEZ1@XJ)i(QJ%b(1E-%RP0E (bRu4SRJ)hbQE-%P!1KE (J)hbZw1((C ;SR@QK@ (PKIK@Ģb ((KE(EwaKERER@Q@-P)i)hZJZ(Z@%- AKEQE)hbREQK@ KIK@b)i)hQKE ((QK@ KEQKEQEQK@ EQEQE-QE (%-P1h((() (%)h(((((((IE-QH(EREQE ( (-% Z(vEP!haE%-PQ@ŢJZ( (JZ@QEQE-QH)iQEPEP (KE!E-R@’QEQE (%-R@ EPQKE%Q@ E-%J(E-JJZJ(LPIKE%RR@ IKE%RJZJQE%RL(4PQKE%%-RP  JNP KF(J)i)R@J)h%%-))h%P!R@ IKEPQKI@ Z(AIKE(J)h%QL ()hBQE-E)^ԀJ)h((%%) E-J)i)QERR@ EQEPQE))hbQKE%PQGz(( R %(Eh(ZJ(J)qE%PQKI@wKE%)hE JZ(( P1( E-bZJJZ(Jh KE(c ^Q@ŠZ(Q@RQKI@ EP;K@PEPh(h(RZ(Z))hZ)RQR) 1KEQE-P)h)h)h)hR(-( (REQEQE-QK@Š((AE-b)h( J(EQE-QKH((QEQERE(ZJ(`QE )i()QE-%QL)iRSZ(@QK@ EQEQE%((((h(Q@-%- ZJ()i)h(RQ@ ER(JZ()i)h)QE3KIKH(aEPE-() (Eb(QERR@%-% ( ( ( CKI@Q@%-%QE JZ1@ĢQ@ INBQKILJ)i((((R(J)hbRR@EPIKE0KE(Š(!((aIKE(4RъJ)i((Š(!)h)hQE%PQZ(Z)(JJZ(1E-KERw((AIZ)J)hQKILAIKE%b))hQKI@RJ;EP (J(Z((ZJ`PQEJ)hQE%()h)i);KE QK(SE0ZJ(4P1((J)hBQK@Z((Z(()h)hQ@()i)h( (QK@Ģ(QKE%(@ IKF()hER@Š(AE-RRE )hZ()EP( )h-! ZJZ(QK@-%-QKHbREQKLQ@((( )h-%REPKEQKE (b1H(E-R@ KE- Q@Q@-PEP0 (RQKEPQKE%-RREPREQEQE%-PEPEP0 ((aEPEP bREQE ){Q@Š((((QEf(QERRaEPEPEPKIE-RQH(-QEQE(\PQEQKI@QL(AEPIKEPQKI@Ģ E-%QE%QERPEPQKI@%-%SRPEPQERPREZJPQKI@Q@ EP!((J)i((EPEP!((aEPQKI@J(Z)R@Q@Q@ĢJ)i((-J)hbRR@JJZ((((J)i((RR@ ESZ((-R@ IKE&(-R@ E-R@ Ju!%(Q@%- JJ\Q@`%Q@%-R@ E-%QE%P!(EJ%S(Ģ)RS()hQEPREZ(Z(){QEJZ(PQKE%%: KF((%-P Q@ŒR@R1((RQKEQH(KE(Z% ( ( Z()h- ( KEQKER@ KERER@Q@-PEPEQKEQEQEQ@-%-%-PEPR@Q@Š()h(QEQERQKERRE J(Q@%-%-Q@Q@Š(AEP0(QKI@Q@Q@ (h))i(hP((%-%- (Q@-%-Q@ EPEPE )i( ( IKE ( ZJ(QEPEPKIE-PE()i(J^QRPQ@Q@Q@Q@Q@ E-R@ EPES(%Q@%-%w))i((EQE%(P0(BQE))i((4QERPIKEQE E-%QE%QEZ)J(4S((aEP )h))h(CZ((J)h4J)hRb`%PQKE%%) EQEQE%%-RъJ)hE KEQ%QJ(E%R!(((J)i)QER@ E-%%P!(HhR@%Q@4Rv(QKF(Z %Q@%QEQF(J((aEP!1IJhZ(R@ E-QK@Q@P!hQE%-P (h(ERP(QE (RZ(@QK@ KERE 1E( (E J( EQA- EJZ(QE Z(()hZ(1EQE- (KIKHAESQKE )h)h(ZJQEQERQ@Š)hQE (QE (b(QE-%PQE- JZ((E-R@(Z%((()i)h(QEQF(AEPIK@Š((()h)h ( (JZZ)(aKESQ@ E%-!Q@4QE-Q@Q@Q@QHZ(((*J )hJ)hJ((((bJ(ZJ())h((4RR@ EPPih%R@(QKފJJZ(I@ E-RPQKI@ZJ))hZ(RP0(( EPEP0(EP!(( ( ( JZ((P0 JZJ))i)( ފ(QKEP1(AҒ%QEPRE%Q@Q@bJ(Q@%-(ZJQE JZ())Ԕ))h,%( JZ(J)i((AEPEPQKI`-%-J -%% bRHbQKIL(J(QEQERъJ)i 0 )hHJ(LQNQE((QK) (J1KKLBQKE!E-J)hB Z(0JZ(E-'jZ(QE-EQK@(aKEQE- (P0(Z@%-PKE (Q@–(Z()i1K@-%-QK@ KEQKER@ĥ)hQ@RKIK@RQKE%-PE-QEQE-ZJQEP (REQEQE-%-R@ KIK@b(((((((AEP0(Z(QE ((((( ( ( (EQK@ KE%-Q@Q@ E%--R)QEQIHBE (Z((Z(Q@Q@ IEQI@*J ( ( )i(( JZ(((J( JZ)QEPQEJ( ((K@hJ)h((J( E-%QE0Z((@((I@ EPIKE%P!(LQKE%Q@ E-R@ EPQKEQERS((ZJ(QK@ E-%R@(R@KE0 JZ(R@ A%QER@ E-ZJ())h4QK@ EPIZ((QE ( F)hP1()hBRRLPQE-!4QE(% E-Q@PQE;RRހQE(ER EPIKE&)hRPE-R@ EPR@ E-QEQEQF)h(((J)h(((R@)hb@QE QEZ(Q@K@ĢQ@RE(( ( (QK@Š(EQEQEQK@Q@ EPKIK@Q@-&)ERP0ZQE Z((AEP0 Z((%-%i)h(((Z@%Z))h(ZJ(RP (J( ( 1E((ZJZJ(1E-h ( ))h(JZ))i(-Q@((QE(E J( E%-QE ( ZJ((((4PEQE)h(JZ(((RPEPwJ(Z((J)i)QERR@ IKEQEJ( JZ((J)h(((BQKI@(J)i(QERSRR@%-% ())hQNQ@ E-QE%-P!(%PQEQ@%-J( (Z(((4Z(ER((IK((-R@ PQKI@Q@L\RPQKE% )hKF((Z((ER@(aF)i(Q@%-%Q((`%Q@%P11E-%&( E.)(%QERP R1(%bZ(QE1 Z(@ E (J)hQKҊ&(@SQ@RQKI@REPRER@Š(AE- ((QC (4QE )hBR((QKE%-P aEQEQEQE-%-PEPE)h-PKIEQ@Q@-PE(RR0Z(Z( ( )haEPE-%Q@%-QERE!%-%1-b )hvPRE ( (Q@QH((RE%RREZJQK@QEQEQEQERR@Q@Q@Q@-%-QEQEQEQE (QEQފ-PE%((( (4PEQEZ)(QKPPQE0 ( ( ( ()h())i((%-%J)i1@%-R@ EQE))i(QEQE((J)i((( ( JZJ(QERR@ ES&)h)(%(aIJh(()hZ((%PQKI@Q@%-Pb(bQKE(QKF((ZJ))hJ)i((Z())hBP-b (%QLJ)h-%QK@ E-%%PRR@Q@ EPQKE!)(@PQKIL(QEQEQE%Ph KIK@Q@’J( 1EJ)h(ĢJ)i)P0)Rъ%S)hQL(aE-JZ( (Š)hQEQ@Qڀ ( )h(((hQ@-P0QKI@-P@Q@–)QE)i)hKIK@ KERJZ(-Q@ EPEPEQE((QE-QERR0(AEP0@QEQE0 )h@QK@ģPQE- (Q@)h((ĢQKLŠ( () ))h ()h ( ( ( ( PEP0(QEQEQERQ@ E&is@Q@-%- (Q@Q@Q@Š(B@(QEQEQE-%PEPE%-RQ@ EPERZ((ZJ(((J)i((((((4QE(IKE%P!(P( JZ(())i((%-QEJ(ZLP (E-% ( (%-% JZ(((IKE (IKE JZ((( (QEQEQ@%-RPEPEPIKE(BQKIL)h((J)hJJZ(R@ E-% (%-RPEPQE-0ZJ(J)hJJSE%( )i(QF(aE-%b F( QERR@Ģ)(PQKE((P0(RRъ%QEQEQEEvZ(R@ KIK@RihZ()EP!( ( (aEb (R@ Z((Z()h Z(()h(-Q@Š(Q@REPKIK@QH )h(((bIK@ EPE (()hQK@ĥ( ( (REPKEQEQE )h ( ( (-P1( KIK@%-bRE ( (ZJ`-QH(Pii)RRQE(((((ZJ\RPEPKIE-Q@ EPEP ;P1h)i( (  )(S-RQERQ@Ţ(PJSI@ KERPKER@Q@Q@EAEPESZ1@ EPEPQKI@Q@RPEPQKE%Q@%-QE(ES!(0E J)i(Qފ(())h(ZJ(P1((J(%-RPIKE%(IKE0Z(w(Z(RPEPQKE%RR@ EJ)i(((J)i(J(((EPIKE%(PQKE%%PQEQE(J;RPEPIKE%%-R@ EPEPE-'jJ)i(ZJRP0(1(@%S(0)J)hR@J)i(QE-%QE-%R )hJ)h(QE )qE%-PEPE-R@(Q@Q^QE-%-QE-%-PKE (@- )i(h@Q@Q@ EPKIK@P0(QKE ((QEREQE-Q@-P0(RE(@QEQ@Q@ EPKE ()i)h(((J;R@Q@Q@)h(KERR@ EP Q@QEQERbJZ(`%R!()h E-(%Q@() JZ( ((ZJZ(0 (RRQEQEQE((KEQEQEQERRHJ^RPQEPTQEPQKI@QLBQKE%Q@%-RPEPQEQERPIKE%QERRPEP EPEPQE%PRR@%PEPQKI@Q@ KEJ)i(QKI@%-% (Q@(Z((LQޖJ)qE%%:0 ( E.))QEJ)h1 E- J( JZ(RPEQE%PQKE%Q@J)h((ZJ`QE (Ji)RRHKE%)1KE&(ZNQ@BRR@4RLBQKI@’@%SRP1( EPIKE%c4QEPQKE%Z((J)h%%-R(b )i(ZJ(-bQK(RPEQE-%qE%.( JZ((ZJ)hbQKE ( (QER@ KEQE)haEQEZJQE%SZ@QK@ KEQKEZ%-PKIK@Q@-PEQEQEQEQKE(@QKL( KEQE-QEQK@Q@– QKE%RREQK@(()hZ)R@Q@QH(EPEPEPIKEQI@ E%-%QEQEQEQE)hQ@Š((!( (Š(AES) ZNԴ)i( ( ( ZJZ@QEQEQE ZJ(QEQEQEQE ( (ZJ((( 袊Š)q@ EPE(`QEPQKI@!4QEb((((%QE% (Z1@ EPQKE%Ph( E’@%QLAIKI@ EPRR@%-Rb ( JZ((;Q@ E-J)i(RR@ ( JZ(( J(J)i((Q@ E-RPEPi)h1LAEPEP1(J)i(((%PQERP ((ZJ(P0 E-%0 JZ(b%Q@ KERS()h))h(ZLRbJ(bJ)hQE))ԔQ@Ģ%PQKI@ E-R@ E.(%:ZJ`RH1IZ)RRLZ@%S( )h1E-J)hQE-0Z()hP 1@ŠZ)RS1E (R QZJZ(REPGjZ(Q@ZJZ((Q@PEPKIK@RRERPE-!(ERQEQEQEZ(Q@jZ((EP@R0((h@QE0 Z((Kڒ JZA@ EPER@RPQE0 ( )h ( ( ( ( JZ(;Q@Q@Q@QEQEQE((RPRъJ)hZ`%-PER)QE ( ((((((EPIޖ)(4S()i( 4VeQ@%-%QE JZ((aESRR@ E-%!%-%Q@Q@ EP0 EPEPQKE%Q@%-QEPQKI@%-Z((J)hZ%%-R@ EPIKIL((J)h((QEb E- J)i((b%P!((.(R@ E-%QE%`QE J((AEPRE0 JZ) J)i)))hZJ(\Q@ EQ@%-R@ EPQZ(E )h(J(RR@ E-%ZJJSE%Q@;RQEb%Q@%-RLQEZ)J)h((((Z((JJu%0 )q@J)i(hNQK@ E-QK@ E.(0Z)Q@RQE-%-PEPE-Q@REPEPKEQEQE- (((QKI@-PKIJ(RRQKEQEQ@-%-QE Z(Z(Q@ E%-QE ZJZQEQE.(((R@Q@QH((Z)i)h ))hZJ( Z(((((())i(i(( EPEPIKEQE%PQKI@Q@)hR@ KEQEQ@Q@Q@Q@Q@Q@Q@Q@PEPEPEPF(()(h((JZ(C( )QE0,EaEPIKEPQERR@ EP (aEPIKE(Q@Q@ EPEPEPQEQERPIKE%PQEZ((QERQK@(QER⒁QLJ)hQL ((QEQE%PQE(%-P (RPEPIKE%Q@SI@ E-R@ Eb CKE%PPh JZ(( EP %-J)i((Z((E-(%PQE))h(`%Q@E J\QL(-R@ E-!J)hJ(Q@ŒQE0I@))hI@ĢQ@Q@ E-QEQK@ ER@Ģ%()h()h (J)i(hw(((Z(JZ-%P0ZJZ((Z((PEQEQE- (-%- ((R0((ZJZ(((R@)h0((h()h ( (((((((%PQKI@)h(ZN ))hAEPRE`%R( EPGz((@ KEQEQEQEQEQEQEQEQEQEQKI@REPEPEPE(Z( ))h() (%-S) J(Q@ ES%-bQKE%Q@Q@%-QE%P!())hE ( JZJ(%.( EPIKE%PQERR@ EPEPQKJ(NQA(J( JZ(R@ĢJ( (%-%QE1%-%!QL(J)i( JZ((((AIKEZ(())h (%-RJ((J)hPQK((Z(;E-%Q@ E-RJQK@ JZ)RRP)i(RR@J)i(1@Q@ AKE%R@(RP1( EJJZ(((((LQKI@%-R@ E-%0()QEJ(%; (J)i(QEQGz(Z(( ER@Q@Q@Q@RREQER@ KEQGj()hRQE-QEQE!-QL)RR0(REQGzQ@( 1E-(()i)hZQEQERP!h(KE )h QK@Q@PEPEPEQE )i(BE (P1i(K@ E/zJZ( (Q@’QE'j)hbQE-%Q@((`%Q@Q@Q@RQKI@Q@R((((((JZ((((((PRE%-PEPERQE((J( (IKH(QPXQER@ E-!(PQERR@A((Q@%-%RS)RRbŠ(BQKE%Q@Q@ E-%PQKE%( (R@%-%QF(QE J)h%%-RR@ E-((J)i)QERR@ E-BQKEZJ(J)hRQA%-RP0(Q@ EQEQEPQKI@Q@Q@ E-RPIKE JZJRJQEZ)J( ( (%-PhAEPQKI@Q@Q@ KEHh( ES JZ((A%PQKI@’%J)hZ((b ( (Q@Q@Q@(aEQE )h))h(P(((E((RQEZ(–(1@-PEPKERJ(hQE Z(aKEQER( ( (-Q@R((RREP0R (RQ@ EPEPEQE-%-PERP1h(ZJZJZ( ))h((((QE ())h(((((ZJ)hEPEPES)i((( JZJ(H(i)i(((JZ((((Z( ( (Q@Š(Aڊ(aEPIKE(((,J)hQF((EPQEQEJ)hbQKF(())h%Q@P1(PQKI@Q@%- J(J)i(E%QERR@ E-&(((()1KE%Q@Q@ E-J)i(%-J)h((((EPQKI@Q@Q@%-J( (%-RPEP (%(((J((J)h(4QEPQKE%Q@(J)h)Rh))hAEP0ZJ((J)i(mP!(ZJQEZ)QEQK@ bZ((ZJ(RPIKE1(ER(RR@Ģb(QEJZ(GzQE JZ()h ( (-Q@Z(R@ Kފ((R@R(REPEQ@-P0ZJ()iQLRE 1E-%S1KEvC ((((((\Q@Q@RREQKڀ(Z%-P ZQEQEQEQK@Q@Q@Q@Q@Q@PQE((QEQE (%-( ( JZ(((((()QE(%QLAEPEPEPKIE-Q@RPEQE%Q@Q@Z(((@QFh(QE())i(QEn( (%-%RPEPIZ((J)i((((J( QER@ EP!(ZJJ)hQKIH`%((((( ( JZ((ZJ))h-QEQEZJ(ZJQE((J)hJ)i()RPEPQKEh%-% (Q@%-QEQފ())hBQKE%Q@%-RQERPEPQKEPQKI@%-J)hb%PIKIL(@c(AIKE%Q@QE ))hBQZ((J)hQ@(-R@ A(EPIZJ))h(JZ((JZ(((QEQ@ KEQ@Q@PQKEQEQK@%QEREQERPEQE-QE Z((AKIGz-Q@-%-QE (EPIږQLaE(Z( ( E%-QK@Š((Z()h ( )h( ( )hbREQE-Q@RQKE%-PEPEPEPEPERPKEQK@(QEQE))h(( ( ( ( ( ( (Q@Q@QH IKI@Q@ IKI@Q@ E%-%-%QEQEQEQEQE-QE((((())i((( tRY%QE(PQKI@Q@1EQE%PQERR@ EPIKE%QER@ EPEPQKE%Q@ E-J;PIKE%(J)i1@Q@ KERR@ EPEPEPQKI@Q@ E-R@ E-%QEPQKE(aEP!(Z(QE JZ(ZJ(J)i((%-RPRE (E-R@ EP (E-% )i(((%Q@Q@Q@ E-QE JZ(EQE%-S EPIKE1 E-%QEQERRQKI@Q@%-QK@ EP Q@ E-R@ IKE(EPEPIKEQEPQKE%)h((E%PEPE-%QK@%QEQKI@ IKEQEQK@((( ( Z((Z(QE(((aKE((ZJ(bRE ZAK@Q@w((QEQE-QEQEQ@((Z(QEQK@(aEPKIK@Q@Q@RIKE%RREQEQE%-%- JZ(PRPEP (((QEJ(( ( ( (Q@Q@Q@Q@%-%QޖQ@’(()i((( (Q@Q@Q@Q@QL(1(QE[+2Š(J)i)))hBQKE%Q@Q@ E(CE-%QEQE%QER@ EPIKI@(aEPIKE%b(%Q@J)i)QEQEJ)h)QERR@ EPEPQKE%Q@Q@Q@ E.)((Z(R@Ģ(QERPEQEQE%PQERR@ E-QE1 KE 1IKE%Q@Z(QE\Q@ E-%RP0(( EQEbZ%Q@J)hIZ(1E-R@ F)h(()h))hBQKE%Q@’%QL(AEP1(Z((QEZJ((J)i)QE(C JZ(RL ( (QE-0RQEQEQKEQK@ģQځ ޖ(){Eh(QKE(AKEQE-Z(QE )h (IKE0 )hQE-0(-PEJZ(ER()i)h(REQEQEb((Z((Q@REP0(QE-%-PEP0JZ((((((h(((((((J)h E%-%Q@Q@RPEPR@Q@ EPE- J)i(QEQEQE(((((((((((((()QEQEQEQڊ(((E-bQKI@Q@QL(((J)h(((%Q@%-%QEP)h1@%-RPEPIKE%Q@ E.(BQKJ((((PQKI@Q@ E-J( (ZJQ(E-% (@Ģ(((J)h((QEJ( ( ((ZJ(J\QEQE(@%P!(`QE!%-J)hQE ))h%QE ))i(QE-%Q@Q@ E-QEQE%PQKI@Q@Q@ES))qEZ()h1E%%-QGjJ)i(((ZJ((QEZJQEQE0 ( (RQKE(BRE0 )h (Q@ ޖ(P(-%-%RE%R☄C)b)QE 1E-QEPE (-Q@(Q@(aKE()hQ@RQE (( (Q@ KEQE-QE (-%-QEQ@Š(AEP0%-QEQEw ( ( (BRE ( (Q@QL((Z((())hAIKE%P1( EPEPEP0( ( JZ(QEQK@%-%RPESZ@%RIKI@R)hŠ(BQKE0(RP!h(EPIE-%- J(Q@Q@ E-QE\+2Š(J)zRP0(QE JZ)RH JZ)RPEPIKE%Q@Q@ E-QE(KGjJ( ((J)i(%-RPE-%(J(Z((())hJ)i((( EPEPQKE%b(P1(%PQE((%())hJ)h( JZ((Z((())h)QEJ)i((((Z(((J)hQKEPQKI@RSQE-!E-%QEQEZ)J(QKILAIKI@PRR@ZJ)hbwZ(RR@ KEQE (PEPEP (QE-%PREPIKF)JZZ) J)hJZ(aKE (QERPKEQE-QE (BP)hbRE ZJZ(E-QE-%P (QKE%PER@Q@-PEQEQE ^Q@Q@REQE ( Z(0()R()iRL((QLAERQKE%RE&)h JZ)R@Q@%-J(RZJ@QE0 )i1@Z((ZJ( J((AEbKEQEQEQEQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@)hR@ KEQE%-%-%-%-QEQE%(bQEQE (;RRPEPEP ((bQKILAEPIZ((())h((((J( (Z((())h(QE J)hBQEQE ( (Z(())hJ)h((ZLPEPEPIKI@QLJ)i1@Z(QE JZ()hE-&((QEQEQF())hZJ)h( JZ((QEPQKE%QLB(RLZ(RRPEPEPQKE%QERR@Ģ%Q@RR@%.(LQKI@Q@ E-%RPIKE1 )hPQKI@((QE )h1@Q@()hR@Q@( E-(Q@-PF( (JZ()h)h (((Z(ZJZJZ(Z()hRE!Q@)i)h(Z%-PZ(bb()hZ) ()hQEQEQEQEQKI@-PER (Q@(((Q@(aEP (QE(((QE (Q@(Q@Q@%-QEQERPKIK@ E-!((Q@((((QEEPEPEPEPIK@Q@Q@ KEQE%QEQK@ AEVeQ@ ER⒀ (Z((P (RR@ E-QE%P!(PAJ(QKJ)1KE RRL (Z(()1KE%RR@ E-R@ EPIKE&(J)hPRR@%-QE ( JZ((Z1@ E.)((ZJ((J)h((Z)QE (\Q`%Q@Q@(bQKIހ (Q@Q@ E-R((J1KE%PQE (J)hBQKI@ KEQERRPE-%QE%-PQKE%Q@ E-J)hLQKI@%-R@ EP (R@J( (R@ )hJ)i( JZ( E-J)i)QEQKE Z((4PE-%QE0 PRZ(^R@ Z(QE Z(( EQڊ(ZJ()h)h (ŠZ(()Z(Š)hZJZJ(h(( ( )h(AEP1hZJZJ( ( ((E-'zZJZ(((Z))hZ%w(AEPEP0Z%-P1)h (Q@Š(AEP0(((QEQE ((Z((%(QKE%Q@Q@Q@Q@%-R@%-%RPEPE-QEQF(((J( )i QEQE)h(Z))h)h ( ( ( JZJ(( VeE-%QEQE%PQERR@ E-RPEPbZJ(())h ( E-%QEQE&((QKE%R(0 E-RPi)hJ(4PIKE'J)h (Z(((JZ((QEQER@ EP0P! PQKE% (ZJ(QER@ F(Q@ 1KE%Q@QEQERL(()h)hZ((J)i((QEQER@ EPEPEPQKE%Q@Q@ E-%RL@%Q@ E-J( ( JZ((AE-%QEQE%PQKI@Q@Q@Q@ƒEQGzQERPEPEPEQEQEQEQEQE0 ZJZC((R0((QEQE)hQ@ŒRLAERQE-QEQEQE-%-QEQEQEQE-&)h )i(()h ( )h(QE-(Z((((QE-&(-Q@Q@R!)hKEQE(QEQE ZJZ%Pw(((())i(( ( ( (Q@ KEQERR@ E-%0 ( ( ( ;EQE%PEPIKE%PIE-QERREPQKI@ IKI@Q@Q@ EPEPEPEPEPEPEPEPQEv1EdXQERRL((((4QEQERPEPIKE%((J( 1ERR@ EP (;QKE%Q@Q@))hQ@%-RS(( E-%0 JZ((1E%PQKI@Q@Q@ EQEPQKEZ(QEQE%P!(( )i(ZJJZ(((J)hJ)i(R@ E-J( (Q@ E-%0 (Z((ZJ(QE%PQKE(AEPQKE%PQKE%Q@%-QK@ EPQKE%-((AIKEZ((PQKI@Q@JZ(bQEQKI@())h(E JZ(J(BQKEQEQK@ĥLREQEQ@QE( (P0ZJZ((Z( (((((Z( (KIK@Q@-P0(%-PE ((((( Z((QE%PQKE%-PERPE ((((((((((((Z(()h(AEPEP0()h (()i((EPEPEP ()h ( ( ( ( ( JZNQE袊((((((((@]+2ĢPQEQERRPEPEPQKE0((J)h%Q@%-R@ EPEPQKF(())hJ( JZ((ZJ(P!(ZJ(J)i)QE((PQKF((PEPQKE0)h(1IZ(((J)i((()h))h%R⁉E-%QE JZ(R@(((((@Q@ E-QE%PQKE%Q@Q@%-QE0 ( ((((((J)qI@(aEPIKEZJ((E-RR@ EPE-%PQKE0ZJ((@QE0Z(( E-Z\RSQ@’))h(ER@ KER@ E-RR@ KEQKE%PEPERR@Q@ KEP(((Z(((()hR@Š(AKIږ%-Q(KE (bQKI@Q@Š(((Z(()h)h ( (((Z( ( ( )h(( ZJZ(((()i)h((((((()(h )i((((J-Q@ĥ )i()((aEP (((QE((((((((4PIKHIE0 ( ;EQE .EaEPEPbZ((()1KI@Š(AEPEPQKE%Q@Q@ E-QE0 (ZJ()1KE%QEQERR@ EPEP EP ((EPEP ((ZJ(J)i(QERR@ E-RPQKE%(ER)iQAIKE0Z((()1KGzJ)h ( JZ(RbQ@%-J)h( JZ(((%P!((RE JZ(((`PQEQEPhZ((AE-% ( (Z((((J)hBQKI@QEQEPREPQEJZ(Z`PQKE JZJ( KER@ E-QEQEQEQERREPEP0(PEPF(aEP (R@Q@.i)h(QEQK@Q@Q@(QERREPKIK@Q@Q@-%(( ( ( ( )i((ZJ)h ( )E%QK@Q@Q@RQKI@Q@QEQERRRQ@ E%-PQKE%-PIKE%Q@Q@Q@Q@Q@ E-QE )h(( )QE ( )i()(’ (((1E (Q@Q@Q@Q@Q@Q@ )(((ъZJ( ( ;E!)hZ(((J)i(QE (%PQKJ())hbQE-%Q@ E-J)h (Z(((`%-R(%Q@J)h%%-R@ E-QE%-P!(-RъJJZ(QERR@ E-%QEP!((J)i(((((`QE JZ(((`QE ZJ(S(Z((PER( EP EZ(QK@ EPEP0(((BQKE%QEQEQER@ Eb (%-RH)QEQERPEPEPRE%QEQEQEJZ;Q@ E- J)i( ( (ZJRS(Š)h(QE0 ( ( ( )hQE-0Z(( ((aEP!( ((()h(Z(QK@Q@Q@ Z((( ( ((1KEQE- (( (QL)h((ZJZ(0)(QE%-PE@QKL 1KE )RR@ E-Q@ KIK@ E-Q@Q@Q@Q@Q@Q@%-%QERPEPREQEQEPQKI@RR@Q@%-R@%RQEQEQE1Q@ IE-%PA`QEQE ( (Q@(((ĢE ( ( ( ( JZ(((PQJi((((ZJ()1KEZJ((J)h%-J)h (RPEPF( )i(QE ())hBQEQKI@Š(J)h(PQKE%Q@(PQKE%%-QERbJ)i(Z(((`&( KIK@ E-%QE%PQEQERR@ E-%QE0 JZ((QEQE (((J)i(QERPE-RPEPEPIKE%QEQEQEQEQEQE%S((QEZ(((Z((J(E (ZJQE%.(Q@Š(JZ(&){QE1KEQEQE0 (bQZ()hQHaEPEPEQLAEJ)h)h)hŠ)i Z(()QKڒ((KE(@Q@Q@-%-%-SQH(-Q@ KE(HQK@ KEQEQE-QEQE (RIKEQE0 ( ZJZC JZ(w((QEQEQEQE (Q@Q@Q@Q@Q@Q@Q@ E-PZ((((((()((((J((((( ) (b (E ( KE%-Q@Q@Š(AERQEQE-%-!ES)QE EVe%-RPEPEPQKE%Q@Q@Q@%-QEQEPQKE%Q@(PQF(aEP!(Q1@Š(BQKE%Q@Q@%-RJ`QE JZ(((ER ERR)R@ EPwJ)hJ(%-QހZ((J)hJ(Q@J)i(%Q@J( ( JZ((ZJ(J)h ( KEv E-&(ZLS E- J)h(QEQ@ E-QEQEQE(AEPEPIKE%RQEQE(Q@Q@Q@Q@Q@ E-%QE (Q@ E-%R@%-Q@Q@ EQEQER@RPIK@Q@Q@RQEQE-%RREQEQE-((Z((E%-PEPEPKIKL(KEQEQEREQE-Q@A)h(ZJ(Q@Q@-PEPEPE((( JZ(Q@Q@Q@Q@Q@Q@–(RLJ)i((i)i((((()h ( JZ((QEQEQEQEf))i(((((((QE )hK@ EPڊZCL((AKH)hRRQE0(QERQ@ EPQE(QE_aEPEPIKE%PQEQERъJ)i(( JZJ(())hJ( (SI@Q@(aڒJ)qI@(J)hbQEJZ( ( LR@ E-RPES)J( ( JZ(((i)hBREZ1@ EPEP)i( (( )i((P0ZJQEPQEQEQER@ EPEPRE%PQKI@Q@%-RPIKE%PQA%-))hQE-(Q@ĢZ%Q@Q@%-R@ EPEPREPIE-%Q@%-RPE-J(QLJZ((@ EPEPEPEPEPEPEPEPEPEP@)h ( (((((KE%-PE-R@ KEQKEQEQER@ KEQE-%-PE Z( JZ(QK@ ( )hQEQ)h(((Q@Q@Q@Q@(QERRъJ)hAEP0(( PEQERR@ KEQ@Q@Q@Š(`QE%Q@(aEPEPEP ((((( )h (R@ KEQ@ ERRPRPE-QE0 (-RQ@ EPQKI@-%Q@Q@Q@Q@VEE-!EPEPEPQKI@Q@Q@Q@ E-RPEPIKE%Q(()1KE%Q@Q@Q@h((AIKE%Q@Q@P!((JZ(J( LR@ E-R@ Eb ( JZ((((PQKI@Q@Q@jJZ(((ER( EPIKEZJ())hBQKI@(QE%Q@Q@Q@ E-QEQE%PQKI@Q@ E- ( JZ((ZJ(bZ(QE%PQKE RRPEP0(QERQEQK@ EPEP0 E-%QEQEPIKEԴ( ER)Q((J)hJZ(JZ(()h(( ( JZ((Z(( (QERRE J;R@ KE(((QE-%PEP ZQEQE(Z))h(AEP1qEQE ( 1E )hQ@Q@(QE ZJ(QEQEQE (Q@PEPEPEPEPEPEPEPEPEPEPEPEP `P0(i( ( (Q@-P Q@Š((BQEQEQERR@ KERRPEPEPEPEPފ( (K@RPEPEPE/j((J( J)h((Т+"Š(J)i1@QL(@%S(((J)qI@Q@Q@ E-R⒀ JZ(QE%b(( ( (Z((())hQEQEJ)h )qI@Q@’%Q@Q@Q@!E-J)i(QE))h ( ( RR@ E ( JZ(((J)h((ZJb ( (E-J)i(1@(J)i((ZJ((J)h4RPEPIKE%PihZ((`QEJ)i QEQE(RR@ E-%QEQE%(aEQE (%-LQKE%-PQKI@R1(@R!(`QEQEQ@%-QEQE)hQ@Ģ(QKI@PRER@Q@-%- ( )i((( ( (QKE0 ( ZJZ((Z( ()i)h(ZJ(QEQE()h ( (aEPRE ( ZJ)h(((((()h(Z()hZ((QE ( ( ((((EP (QEQEQEQE'zQERPEPEPEPQKE%QEQEQEQERw ((((((((( JZ+"ĢJ( ( ()h(((( ( (bJ(QE(@`%(`ZJ((J)h((&)hPQEQER@ E-%QE \QE&(aEPEPIKE((aEPIKE&((AEPIKI@Q@Q@Q@%-Q(((1E%.(Z(((b Z) J(Q@Q@%-%QE (%QE))hJ)qI@Q@ E-%RPEPEPQERR@ )i( ( JZ((ZJ(`PQKI@Q@Q@Q@Q@%-bJ( ( ( JZ(Q@Q@ĥ())h(AE@Š(AEP0(QEQEQE0 (RE (-R()QEQE &)h(EPE(EPESQ@((((PEPEQڊ()h ((;EQKEQEQEQEQK@Q@ KEQEQEQEQKE))h1EPREQEQEQE%Q@Q@Q@Q@Q@Q@'zZ()h(QE%Q@Q@QL(AIKE ( E- J)i(((QERRPEPEPE J(4PEP (((J^RREPQKI@ EPEPEQI@(F( (ZJ((((J)i(((((ZJ((J)i(QEQER@ EPEPIKE%QERRLJ( ( 1ER@ E.)((Z((QEQEZJ(;R@ E-PhAE.)(QER@ EP (Q(BQKI@(PQF(((PQKJ(J)h@ IKEP!)hE-QE%PQKI@Q@ Kڊ(((AIKEZ)J( ( JZ)RPES(ZJ(4PQKI@RQEQERR@ E/z((PES ( E-%0 )i(((( JZ(%-%QJh(Z(( JZ((((((Z))h(QEQKJZ((ZJZ()h )h@ E-QEQE((((JZ(((((()h((()RR((^((@QEQE0 (@QE0 ( ( ( ( ( ( ( ( ( (QEQEQEQEQERR@ KE%QE0 (Q@QL(((EPEP JZ(()i(aEP (QE%QEPKERRPKIE-%-%-RPQEQEhKIYQEQEQEPQE (QEQE%Q@Q@Q@z(Z(((((PQEQERR@ EPEPIKE%Q@Q@%-Q@ E-QEQE0 LRHJ( (%-RH)QE(`PQKF((()( ( ( J^PQE((%PQKI@%-LREPQEQERR@ E-%QEPbZJ(ZJb( ( (R@ E-%QE%PQEQERP)hQ@ E-%%SQEQEQEQE%QKI@%-QER@ EQEQEQEPQE(((((((((ZJ()h(QE(( ( ( ZJ((Z()i)h(h(( ( ((()h((Z(EQEQEQEQEQK@Q@Q@Q@ EPEPEPEPE)iQEQފ((((((((((JZ))h(((()(@%PEPESQEQEQE0 (Fh J)h(()-%(JZ%Q@(((aE((ZJ(()h((Z(((Ң+#@1K@ EPEPQKE&(QF(QE ())h(QE (QE%QEQE ())hBQKE%Q@Q@J)qI@(1IKE%QERR@ E-RPEPIKE%QEQERR@ E-R@ EPEPEPbZ1@ EPEPbZ)QL(QE((PQEQE))hbQKI((QEQEQEb%QEJ( ( JZ(((J(QERbJ( (Z(((% (`QE Z)J)i1@Q@ KERR@ E-QEQEQEQEPQKE0RRRZJ(Z( ( ( ( (ŤE-J)i((Z))h((hQE ( (((((REQE ( ( Z((aEP ((PEPEPEP(()h( ( ( ( ((h(((`QEQEQEQEQEQEQEQEQEwJ(EQEf((((4PREQEQEQEQE%-QL ( (QI@ZJ)i(bQE ( (%-J( ( ( ( ))h(((Q@Q@Q@(J(((J( ( ( (Z((())hJ( ( ( CKE%QEQEQERR@Q@))h(((((((J( ( ( (ZJ(((J)h(( JZ(((J)hQKIH((J)i()RR@ E-QEQEQE(%PQE((QE%QE(%Q@Q@( ( ((())h ( JZJ((J)i()QEJ)i)(((()( ( ((((((()QE((( ;EQE0 ( (-Q@Q@((((-Q@(QE-QEQE((AEQEQERQ@P1)hQ@-PEPEPEP(((((((Q@-%-PEQEQE0 ( ( (QL(((((( ( ( ( ( ( ( (ZJ((())i -Si( ( (J)((((aEPIKE%Q@(((4QEQEQEQE%-PE%-Q@%) :)i+#@(((ZJ(()1KE%PQA ( ( JZ((QEQERQ@((((J( ( ( E-Q((-RPE-%QEQE%PQEQEQEQER@ E-%QEPQE-%Q@Q@%-RPEPEPIKE%PQEQER@ E-%QEPQKI@Q@%-J)i((( E-&()RR@ E-QEQE%P!((`i)hBQKE%RbQEJ)i(((((J( ( ( 3E%)h(JZ(QE(QE (ZJ(((((QE ( ( ( ((QEQE1Q@Q@Q@ EPREQE-Q@Q@REP0(QEQEQEQEQERRREQEQK@Q@Q@Q@ EPEPKIK@(((((((Z( ( ( ( ( (QH()hQE (Q@Q@Q@Q@Q@%-%QEQEQEQI@ ERR@J(JZ( ( NR(EPEPEPQE(((QE PE%- J(AEPIERRIKE%-PEPEPIKI@Q@Q@-%v(R(J)qI@Q@Q@Q@ E-%QEQEQEPQEQEQEQERR@ E-%QEQEQE%Q@Q@Q@Q@%-RъJ( ( JZ(RJ((QEZLPEPEPEPR((QEQEQEPQKJ((1IKHh((ZJ(((1IKE%Q@Q@ E-%QEQEQE%QEQEQEQEQEQKJ(())i)QE())i)QE((J( (ԴQERR@%-&( (Z(((J)i(((Rb (Q@Š(AIKE%PQKI@PJZJ(()h(()h ( ( ( ( ( Z(`QE-QH((EPKIKH(Q@Q@-%-QEQE)h(((()h(QE)i(((((h((((Z( ))h((((((4Q@Š(AES((((J((((((((((((((EP JZJQE ( (hBRR0(AEPQKI@REPEPEPIKI@Q@Q@Q@Q@RPQYQ@%-QEQEQEPQKI@QH(EPIKE%Q@Q@Q@Q@Q@ )M%QEQEQEQEQEQEQERPEPEPIږJZ(BbZ(QEQE JZ(R@ EP (((((((J( ( JZ((())h4R@ E-%QEQEPQEQER@ EPEPIKE%RQEQERR@ E-%QEQEPQEQE(@%QERb%Q@ŽQLAIKE%Q@Q@%-R(())M%RPEPwJ( ( ((%-P1(-QEQE1Q@Q@ KEQEQEh( ( ( ( ( )h((((QEQE(Q@Q@RQKE1Q@(Gz((- JZ(QE (Q@REPEP0(h((QEPE (QEQE ZJZ(QEQE (Q@Q@Q@Š(AEPEP0EQE (QH(ER)RJ((((JZ)(((ZJ( ( ( (())ZJ((( QE%-%-QE ))i((RRPR@)h(Z)(((((Ԣ+@((ZJ((())h(()RRHJ)i)QE((J)i)QE(`QEPQKI@Q@Q@Q@ E-((((J( ( (Z(((())h ( ( JZ((PEPIKE%QEQEQEJZJ((J)h(( JZ(())h ( JZ((((J)i(((%-%QEPQEQE(J)hQE(`P1(AEPIޖJ)i((( JZ1@ EPQKE%Ph ((:Q@((aEPEP (QE((QE (P ((hQE (RREPEPEP (QEQ@Q@Q@-Z))iQE- Z(QE)i(E((QK@Q@Š( ( ( (RREP0(QEQE-QE(((((%-PQKE%Q@(( (Q@Q@Q@Q@%-%0 ((QE((@ EPE%RPEPEPEPQEQEQEQEQK@ EPES(QE ()( ( (Q@Q@’J(-%-%Q@Š((AEP1i(%P%-%bhQEQEQEQE%QEQEQERR@ E.)((((((QEQE%-PIKGjJ( ( ( JZ(((((`R(%QEQEQEQE%EPEP ) (b\RP0(QEQERRPEPEPF(ZJ(((J)h((Z((@%-R@ EPEPEPQKI@Q@Q@))h((((())i((ZJ(JZ(J)M%QEQE%QE(%Q@(`%QEQER@ EPEPQKI@Q@Q@ KERPRPE-R@ EPE-RR@ EQ@ EQIK@Q@Q@QL()Q@Q@QLZ(((i((((((((Z((((Q@QLZ)QEQEQE(Z((((((((()hZJZ((( JZ(()E%(RQEQEQEQEQE(QE0 JZJ(QE%))h((QECE-%QK@ EPEPEPIKI@Q@Q@RSh ( ( ( ))h(J( J( ( (RP1hBQEEV&IKE%Q@Q@Q@Q@J( ( ( ( JZJ(((())i(((((%-%QEQEQEQE%QEQEQER@ EPEPEPIZ(((((AEPRE% (((J(Q@QL%Q@((J)i((Z(((J( ( ( JZJ((J)h((4PQEQEQEv RR@ EPEPEPQERR@ E-%Rh())h LR@ E-%RPEPIKE0(AEPIKE%Q@Q@ ((QEQEQEQEQE ( (R1(((Z((QKEPQKEQE (QE1QH(EQE ^ԝ((QEQEQE Z(Q@Q@–((( EPEPEP0Z%-P0(Q@(((Z)((((Z);EPREb(%-(((((Q@ EP (RRLBRRIKI@Q@Q@Q@Š((AIKI@Ť((J( ( ( ((EPEPEPE (%Q@Q@(%-PIEQEEV%Q@%-RъJ( ( ( (ZJ((((J)h%Q@Q@Q@Q@ E ( ( ( JZ((%QEQEQEPQEQEQEQER@ EPEPEPEPEPQKI@Q@Q@Q@Q@%-QK@ EPEPIKE%QEQER@ E-PhAEPIKI@Q@Q@%-RR@ĢQ@(QKE%RR@QHc(AEPڒJ)i((Z(())h ( JZ(((( ( (Z((J)h (Z(((CE-RS(RHbQKI@uQL(((((-%Q@REPEPEPEPKIK@Q@RP!hQE-Q@Q@Q@RREQEQE ((KIK@QHZJ)h (Q@-PEPEPEPKEQފ(((Q@Q@Q@Q@Q@t (((EZ( (%PEPEPEP0 ( JZJ(QEQEQEQEQEQER@)(4PEPQEQERQ@Q@Q@Q@(((((( EPEPEPE%-%Q@-%ZJQE JZJZ( (I@ (5K ( ( ( JZ(((((J( ( ( ( JZ((((((J)i((((1E-QEQEQEQEPQEQEQERR@ E-%QEQEQEQEPQEQEQEQEQE (R@ E-%QEZ((())h(((((ERPEPIZ(R@Ģ(( JZ((QESQE (’J((0(( IKEb)QERR@ E J)i(QER@ E-% (ZJQEQE%PQEQEQEQEQE ( KIK@%-R@ E-Rh(`-%-JZ(EPEPEPEPIK@Q@w ( ((((((((h((Z( ( (JZ(()i)h(()RREPEPEPE-QEQEQEQEQEQ@ EPE%-袊((`QEQE%-P (QE(PE%Q@((QEQER@ EPEPEPEPQڊ(()QEQE%-RQEQEQEPh((QE )(-% (QE (PQEQE0 ( (RP!i( ZJZ)( ( (5袊ı(((aEPAJ)hBQE ((())h(((` E)(((J( ( (Q@ ES)QEQERR@ ES)QEQEQE)){HEPEPHihJ)h%Q@QL((PQKE%Q@Q@Q@ E-QEQEQE%PQKLPEPF(((J)i(((( JZ((@%-R@ E-%%-PIKE%Q@Q@!ZJ())h( JZ(((JZ( ( (Z((`%(( ( (Q@(QE((QE (Q@((((\RPE-% )i(RES((Z(((Z( ( (KIK@Q@Q@REPEf((((()h ( Z((((EPR((((((h((((((((`QEQERRE%QEQEQE(4PQKI@ E% (QEQEQEQEQEQE3E0 ( ZJ(QE%)(%QEQEQI@Q@-%QEQEQE%QE (Q@SRQ@ E%-%(`QE ( J(QXQE (((((J)qIH(((( ( ( ( JZ((((())h((((ZJ(((%-QEQE (Q@Q@ E-QE ( ( ( JZ(1EQEQEQEQE%QEQEQERR@ EPEPEPQKIL((QKE0\RR)QEQEQEQE%-PQKI@Q@ E-RPEPEPQKI@Q@Q@ EPEPEPQEQEQERPEPIKEPQEQERh((J(QH`%QEQER@ E-%QKJ)h(((QEQEQEQI@– ( ((QE-Q@Q@Š(AEQE ( ( ZJZ(()i(QEQE((AEPEP))h((i( Z((((EPRE0 ( ( ( ( (JZJZ(Z(JZ((( (RQH((EPEPEPQKI@Q@Q@Q@Q@ ڊZ(((((@ EP ((RRLBQEQERw((((%-%((QE (QE (QEQE%-%QE(آ+#@()QER@ E(((J)h(((( QEQEQEbPQEQEQEQ( ( ( ( (QEQEQEQER@ EPEPEPEPJZ(((((((%QE ((J)i( (Q@%-Ph((J( ( (IKI@Q@Q@ E-%QEQEQE(Q@ E-%QEPQKIL(@%((@(( ( ( PQEQERPEPEPE ( ( JZJ((((JZ(EJ)hEPQKE0Z((QIK@Q@Q@Q@Q@Q@RREQEQEZ((QEQE ZJZ(`QEQK@Q@Q@ EPEPEPEP ((()i)h(QE(Z(4QEQEQEQEQEQEPKIE0 (QEQE-Q@ EPEPJ)(f J(aEP ((J(`QEQEQEQERQ@Q@Q@RPR@ EPEPEPIEQE%1 ER(aEPES(QEQE JZ((ZJ((AEP1)i(BIEL@Q@I@ IEڤ ((((J)i(((((((((J)i QEQEQEPQEQEQEQERPEPEPEPEPEPQKI@Q@Q@Q@Q@%-QEQEQEQEQEQEQEQEQEQEQEQEQEPEPQAQEQE (\Q%Q@Q@ E-J)i(QEQEQE ( ( JZ(RPEP (((((QERRъ%Q@Q@%-RPEP0(1E-%QE JZJ(QEZJ`QE (ZJ`QEPQKE%Q@%-QEQEQEQEQE (Q@Q@Q@ KEQE(((@QE0 )iQER@Q@Q@Q@ E%-QGz(()i)h ( Z)((ZJZ))h()QEQEQEQEQEQEQEQEQERQ@ EPEPEP (QEQEQERQ@Q@Q@ E%QEQEP0((QEQEQEQERRJ(QE ( (Q@ (Q@RPQEQEQERQ@Q@RPESQE-%PEPIEZJ(()(Q@Q@Q@RPIEPE%QEQI@-P!i( (TQE`hPQEQEQEQERR@ EPEPEPEPEPQKI@Q@Q@Q@Q@ E- J( (Q@’%QE ((PQKI@Š(AEPEP0(RPEP0((QE QEQEQEQEQER@ EPEPEPEPIKI@Q@Q@Q@Q@Q@%-RPEPEPIKE%Q@Q@&)h%-%QEQE JZ(QEQE(J( ( (((RPEPEPIKE%Q@Q@ E-%QEQE%QERR@ EPEPIKI@Q@Q@ E-% (Q@ E-%0 JZ(R⒀ ((%-SQEQEQE0 ( ( ( ( (Q@Š(((()QEQE)i)h(((haEP ((h((Z( ( ZJ( Z)(h(EPKIE-PEQERQ@ E%-RQ@ E%-QEQEQEQEQEQEQEQEQILBEQEQE ((((((aE%- (PKIE1Q@Q@PEPEPRQ@Q@RPIE-%PES)RQEQE0 (IFhZJ( E%QJZ( E%Z)(-%P JZ)()(hE%nQEQEbJ)i((((RR@Q@Q@Q@Q@%-%QEQEQEQE%b((((PQEQEQEQERPEPEPEPIJh((((ZJ(QE (Q@Q@’J)i(QE (())i:PEP ((QEQE(QEQEQEPQEQE (J( (Q@(QEQEQE(aEP (RRhQEQEQEQERRPEPEPQڔPEPIKE%Q@Q@ E-%QEPIEQEQERPEPIE-%Q@Q@ EPEP0(QE  ( ) QI@ZJ` ( (Q@((Z( ( ( (JZ`QE ( ZJ(((( ( (4 Z(-Q@Q@ IERfZ)3E- ()RQ@ E ( ( ( ( ( ERQ@ E%Q((@(cZJPIEL@ EQERQ@Q@()(sIERQ@ IEQEQE( ( ( ))q@%PEPFi((QLJ(4RP!hQE-PEPIA ZJ(J-Si( (RQ@ E%QEQ@Q@PIEQE Ef)(EVEPEPEPQEQEQEQEQERPEPEPEPEPQKE%Q@Q@Q@Q@ E-QEQEQEQE%QEQEQEQERPEPER)QEQEQE (Q@Q@%-% ( ( (Q@(((EPIKE0 JZ(((@QE0 ( (Z((((@QL(( ( ( (%S()RL()RRS((((((Ԕ(QE1%-%QEQI@Q@Q@4RQ@Q@RPQEQI@ EQEQERQEQE ( ( (%PKEQEQERPEPKIE Z(((E%-QEQEI@Ť-Q@Q@Q@S(P!i(J(h()i(QEQE(PIE(AEPKޒ`-Q@QEQERQ@ E%-RQ@ E%QEQEQEQEQEQE ((QE(`QE!QLAEPEPEPEPEQEQE(@QIL3@PEPIKHhIEQERQ@ IEQE%)Q@Q@ EPEP ((QE%-%(`QE%QEQEQEQEQEQEQERQE;Hܢ+P(((ZJ((((J)i((((( QEQEQEQE))i:EPEPEPIKE%Q@Q@Q@Q@%-QEQEQEQEPQEQEQEQEPh(4QEQEQEQERR@hPEPEPEPEPJZLEPEPEPEPE! ( ( ( J(((( ( (((()(((QEQE((JZJ((((J(( (QH)(() %PEP0(QE JZJ(QEQE )()h(4RQ(((QEQEQERQ@ )( (Q@Q@Q@ E%RQ@ EPEPIE-PEQEQE (J(RREP0(4QERfQ@-%4PIK@Q@Q@ E%RQ@ ES((((h(((((QE ( (!hC JZ)JZ(J-%-%QEQEQE0JZ)3EQEQEQEQ@%-%-%P (QER((AEPIKIL(J(PE%- JZ(PEPEPEPIKJ((`QE (RRi(QBL@Š(LњZ3IEvSIXQ@Q@Q@Q@%-%QEQEQEQE JZJ(((((J)i(((((((((EPEPEPEPQKI@Q@Q@Q@Q@%-%QEQEQEQEPQEQEQEQEQERQEQEQEQEQEQE%Q@Q@Q@Q@ E-%QEQEQHh(((J(())i(((C@ IEQEPh(( ( (Q@Q@RPQEQEQERRPEPEPIEQE(`QEQIH(AEP1(`QEQEQI@Q@Q@Q@Q@Q@Q@((aEP (((QE)h ))h(QE(QK@Q@(((((()QEQEQE ( (Q@-%-QEQEQEZ(((`RPEQEQEQEQ@-%RQ@ E%Z(Q@Q@J^Rf-Q@Š(AE%QEQEQEQEQIK@%S((JZJ(()((-P0(JQEQEQE0 CEQE%-RP0Q@(-IEQIE-4RQ@-%(RQ@Eh%RQ@ IEfJ(4QEQEQE())h4RQEQEQEQEQE%QEQEQEQERR@ EPEPEPEPEPQEQEQEQERRPEPEPEPF(((((@Q@ EPEPEPEPEPQEQEQEQEQERRPEPEPEPEPIA ( ( ( PQEQEQER@ EPEPEPQKE%Q@Q@%-QEQEQEQERR(((((Q@Q@Q@%( ( (4QEQERPEPIKI@RPIEQERQ@ IEQE%-PEPERQ@ EPE%-QI@-S()i)h(((((((((((bZ((-(4PEQEQEQEQE0 ( ( (%R!)hQ@Q@Q@JZ( (Q@ IERQZ(I@- ( ( ( ( ( ( ((((((((J( ( (PQEQEQFh(J(Q@P1h (BQEQEQIL(((JZ)(4QE(RRE%4SsIEQJ(sFi(ZJ(()(EPKIE-%( %-%sQ@Q@Q@Q@Q@ E-%QEQEQEQE% ( ( ( ( (SI@Q@Q@Q@Q@(((%Q@Q@(aEPQJi((((((((((J)M%QEQEQEQE%Q@Q@Š(((BQE (QEQEQEQEQE%QEQE (%-%QEQEQA(((QE (%-%QEPQEQEQE%QEQEqIE (4 ;QEQI@ E%QE%-PEQ@RPQEQEwJ( ( JZ((((PEP0()i)hRP1i( (Z()i)h ( ( ( ( ( ( ZJZ(((QE0 (((Z)((((RQ@ŢbJ(hQE ZJ((h4PEPEPES(hJ(h((AEP0(Q@ŢZJ3EJ(hQI@4 `-% ZJ(hPIFhh((( ( (E0 )((()(3@%PEP(%P J-%Si(BIE-4QE%-PQI@PQEQE1RPEPQEQEQI@ IKI@ IEQERP(AKIK@’ (PEIKI\EPEPEPEPEPIKE%Q@Q@Q@Q@Q@Ģ((((QE ))M% (Q@Q@Q@%PEPEPEPEPQEQEQEQERRPEPEPEPEPIKF((((()((((((((()((((((())i(((J((( ( ( )(4QIZ)(%Z3IEQLh4f E%f)(h EQIE-%PEPQEQERRPEPEPQKI@Q@Q@%-%QEQEPQKI@Q@((JZ))h))i(J)h%)sIK@Q@Q@Q@Q@Q@Q@(4PQE-P0)f(4QE0J(h(((Z)(`-QERQ@Z)( (Q@ E%RQ@ EPESQ@Q@(4-%Z))hQIEJ(袊QE ( (Q@QL((((((EQI@ E%QE()( ( ( J((((JQE (AES(J(())M%-RPIKI@ E%Z3IE (!h)(((PE%-Q(QI@ E%((::(5l%Q@Q@Q@Q@Q@Q@Ģ ( ( ( (((((( ( ( ( ( JZLPEPEPEPEPIKI@Q@Q@Q@Q@ EPEPEPEfPEPEPEPE J((((3I@ IEQEQE3I@ IEQEQEQE(h(4Q@ IEQE%-PQERQ@ IJ(hJ(RPJ(Q((((((QEQEQE (RPE ( ((((J)i1@%-R@ EPINJ( (Q@%-%PEPERQ@ E%ZJ)(h&h4RRKIFhњLњuLњ\Q3@-&h-4RfI3@ E&hBIE.h%MbQL4P!h(((IK@Q@QL-P1hQE-PEPEQEQEQEQ@ E%-0-RREPEPEPKIE-%-QE-PIE(aIKEQEQE (SI@Ť4PEPE%QE(( ))i(QEQE%1Q@RPEPEPIKI@Q@Q@(J)((4Q@j)( ( ( ( J(4(QI@ E&i(h)Z)43Fi(hB搚JZ( FhIEtnQE (Q@Q@Q@!(((()((((((4Q@QH((((((((((( ( ( ( (((()((((RQE ( ( ( (ZJ(3FhPI3@ E74J(h&i3@J(sIEQE%-%PE 3E% EQERR@ E-%QEPQK1@ E ( JZ((Z((PQKE\Q@ E-% ( E-RL-J1KE%-(`QI@Q@4(444ILILfњnis@ )3FhsIIE.h%Z)(4Q@Q@Q@Q@ţ4Q(( EP1i() JZ( (((()i)h((h((RQ@–-)i(IE0J(h E%RRQE ZJ(RQ@Ţ-%QE()i(Z)(JZ)(Z))h(((((QEQE%1E%RQ@PIEQE (RPIE-%%(QI@-%3@h)3E%(EQ@RPIE--f E%@ )i4fMa\\IL3M&if44f4ɸњm4PJ(h4w(aIEQE%P#+ ( ( ( ( ( JZ(((EPEPEPi)i((((JSIEQEQEQI((4fJ((((4 ZLEQE (Q@f(QE (IE(E%4fJ3@ E74fL@ E%RPњJ(sI(&h4fQJ)qF((QKE Z)R@ 1EZ(((Q@ E-R@ E-bR@ E-R@ F)hQ@ )h(fLњZLRfIL)3I:nha٤&i3@Qni3@fњfha34fњ4d$&i4d3Q!fhf4PQ@h))h()v EPES@QILZ(-P!hJZ((((((((()(hZ)(`-fJ(QFh4RQ@ E&h4f(ZZJ(PIE-Sh(IE.h Jvi(J(h(4PIEJZ)i(E0 ERQ@ E%-QEQEQEP((Z)(( 3@h Z3IE(JZ3IEIE-Si(- (%-%\I(hQIE0J(i(%%f&h &i(4ff \fњ.h&i(4RP!sFi(QERQ@Q@('JZ)(K@ KIK@j(Z(Q@ ; \3+(((@QEQIZ)3EQEQEQEQIZ)()3@ E&h-њuњ@-4IL  E&h4PEPE&h4Pf(((LIFhhQ3I@ E%4fbbPFh.((bE:m(bE7bE&)1KE&(-b(()((((((((())h )hR⒘Q@%-%QERQHsFh٤&i3@4њnh.h6vi3II@&i(44(4P3I1@4fRJ)qF((Rb E-PQ)h1EPEPIKI@Q@b))h(Z((Q1@ E.(%%;bN&(RP!))آbC)h!)h JZ(()hJ( JZ((J( )(-P1h)(h EPEP ( ZJ(h(ZJ()h (3KIE-Q@(Z)(E(QE%(((`-((`-%PQE-PQE-fJ((QE(J(JZ( )(P))Rf3EP w E%Q))i)QE%-(QEJZ(QE JZJ(`Q@PQEQE( (Z( E%Z)(E%-PJ(RRRPFi(-Rf4f 3M3M.hfsFkMfsK\њLdPFip,;4fRna4Ѻ7Q.3Ma٣4њ.3M;4f3@&i3Fhh&i3@&i3E-fJ(hJ(h \Q@h((PI\Rb KF((&(PQE-6S(1K3FhQ(4fE&i3@4P!sFiPњJ(h43EPEPEPEPEPEPEPIKE%RQF(QF(QER@%-R@ E4QFi3@ E&i3@&hԙњvh6\f\њJ(4Q@i)h%Rb\Q%%;bE;BQK1@ RPQK(()h))qE%QE&hi( ( ( J\Q@ E-%0 ( (ZJ()( ( )(4PE&ihPFh E%.h%3Fh1sE%Z)(-I3@ II3@ E&hZ(-Q@ )(\IE-PIE-PIE-PE()i(-PIE-Q@-&h(Z)( (QI)RQ@ E%-QE (:J(h E%-`(h4Rf4QIE-RfZ)(-PIE-PE%0JZ)i(4f(-%3EPEZ)(1i(1hPEPEPIKI@b )(L@ E ( E%Q@SJ(J((i(f ZJ)(RQZJ)3@ IFy1 E&hPE&h.hFi4њvi3I3@ 3M4f4BfI3@ ILIbFif3II@Fh&4 h݊oz ¸fh¸斛E.h%f4( 6(v 1K1@Q@ Q@ RPbRъJ)qE&(:Phbm(S6ZZmP!( -1EQE ( (Q@RPIRQL4vh6\њJ((J -R@ E-Q(P ( Q(IKE%PQKE%PQKI((J9@-Z`-@ 3IE.h%fE((QEQEQ@ Iv)((-:n(:n(:n(;PR((-(((( (4RfhSsFhsE&i3@ LfZ(QKEZ((QEQIEQI@ IEQE%0 ( ((( ( )(EPQEQEQEQEQE04QE (Fii((f(fb\њJ(hJ(hI(ii4RQ@ E4QERQ@ E%fZJ3FhhQE1 E%bI3@4fL@ KMcII3@3@4@ Fi(Z3IK@.i(BIE4f.ii(IE-ShJZ@-Sh%Z))h ZJ(RRE%-0 ))h((RQ@Š(AEPEPESLEQIE-%PEZ)(QI@Q@PQEQE(Q@)(i( (J(f4\E.h%(sFx4fњJJu%-%SQI@%P!i QILQ@)({QFh4P(aEP -NSh!v(Nv (QmJ1F( J(0Z)(Q3FhBQN m8CqE-R@ E-QE0 ( ( 1ERR@ IN1SIJ)qE7S6v(6\QJ)qF((b\Rb ( ( ( )()sIE.i3E3KIK@Q@ KE))hQKE0-&(PQKE&(Z((%%PQKE%PQKE%%-R@ E.)((Q@ )hLQKI@Q@&)h((IEQE%QE%Q@%PE )(4RPi3Ea3I1HBbSsFi(fhf4(٣4(sFi(4P!h 3IE-Q@ţ4P!sII\E(4Q@ E&h-%&h-:4f4fI3@ IFi3@ KMRRfh3@ E&3H4):4fLњu%&i3@њvh74f3M4њnihsFi3M3L3IE3MfhCFi(Q@Z)(Z(3FhRfZ))s@J)QE Z))h(h E%Z( ((AEQ@-%()i((QEQEQE (QEQE0 JZ(((((J)QE)( (%-%-%)(i( ( )(hf)QIE 3LJ(RQ@E%RQ@%3I@ E&hRI`-CIސ@4RQ@(;QL)(i)i(4RQ@Jf)(L!h)RQڀQGz(QF((QE%Q)1KL1Fh Q(bZ`%b RRъJ)hJ)hZJ)1KE&)h)h(1F(QLRъ(bJ)h@%Q))hPQKE%PbPbR@ 1KE&(-Q1F)i(1F)hQE%PQKE%QE(@%PQKE%Q@Q@PEPEZ)(!E%QERRP (QE%04f3Fi(IE.hњu%&h0LfLњZ)3Iu%&i3@4њ@.h74f4Rf4f (&hJ(@QE0 ( ( ( 3IE.h%%.h&h4RRњJZ)3E-&i3Fi 4f4ILf3MfsFh٤74f3LfsI~i3L 3If7PFj=n7Q~hG3@R3LFi4f3@Fi4vi3I(4f4RfQKMu@4(SsK@ii(PEQEQERQ@ ES4Q@ E%4bF(AE%-Q1@- (Q1@BRLPE.(BRE (-%-QERE ( (()h)hQ@RE'jZQE ){RPEPhQEQEQE0 ((4Q@%SRQ@Q@’3@(%-%PJZJ3EZ)(((LIE(Q@Q@%%Z)( EQJ)QIE ERQEJ %-%1 Hii(i;ފ(`f(IE(EQE-((QEQERRLP b`PMRw CKI@Q@eQ\'`Q(&h.( )أ1NQuS(((((((((J)h()QEQEQEQEQEQHh@Q@Q@Q@Q@Q@ E-QEQEPEPQKE%((((C@Q@Q@Š)(i( E%-(4fLњZJL@ E%QEQI(h4bQL!ii4Rf4 vi)3FhRf4 Z)3Ivi)3Fhi3I3@ Fi44h;43L,;4fLLsIFi4њfh@4ffIni3@fIni3L,?4f3@Xvh34n3In4~i3Ly4~h3414њvhM;u4vi3MIn3@&hf4fsFhsFi@FiQ@Ť4Z)3E-Si( (f444RPiiqF(SF(Z]b E;bP!(.(SKn(bPiqK\PqF)أR❊1@ bPbSE7bE\RL.)hb)PbQ@ 1KERbR@P11KE((Q@Q@QLAKEQERE ( QE()i)h)h (-%-%-P (QEQE((((-%-Q@Q@%PE-%QE1 EPEPEPIEQE(@ Ew (QERQ@ ( %Q@%P 4Q@ EP1(%%-J(%-%QI@Q@))RQ@ EP0E%)hQE (%bQEZ(P0ZJ&(KE0ER Q@(gYEWQEQEPQEQE (((())i()QEQEQEQEQERR@ EPEP0(((((((()(i)i)QEQEE4QI3HLњ`-:nh E74fJnhaSsFh 3@4њvh74ff3@Xu@ 3IE-fL@Q(f4P!sIJ(fQ@h&h.i3I3@ 3Mf4Ѻ3LI7S3I~hL&hu34fLsFh٤74fLsFiњfh3LCFi?4vh64њm%?4nQLLLQQL34PFif3MfhFiRfJvh6`.h&(4bPJZ1@ KEP!1I}&(SF(SF(b\P1b1@SF(?bKv(01NqKK1@ .(&(;b 1KE&(--7 "SQ@ .)hbb(J)hJZ(AE J)hBQKI@bZ)QH(EPE-R@ E-JZ()h)h ( (QKEQEQE ((`QK@()i)h((h((`QEQEPEPEPEPEPEQE(QI@Q@QL))h(QEQE(CEPQEJ)h 0 (( 3 ފNbKE%%-%Q@ GjZ((%RRP)h RR@ EP)h(-Qu J)hShQހZ(Q@ί4f3\']f444\,-ёEè7"pњnhEóE7u;4na٢\,:n2( 3H,:sFh &i3Fh %74fњnhaf3@Xvh)7PE3uL((X}7.$)|EoX} Q)( 4nf7Qf3Fh Lf4њmE`h4fQ`4bR3Fh EQE%.h (LfFi?44f(٣4L1٣u74Im;u&i3I@nu6Fm vi3IE.i3IE44PQEJ1KE6v)1@ĤbPh;Dx4`Ԙ3b1Lb&dX.)1@F*LQb1@ F}.(-jLRP{hRQ@mmKILmqF)M.(0"+Im&bI#"mK(KmG*Mm*]h-?o4i)hHchb&(.(E;b Qn(bP!SF()h%J)qF((h1F)qF(1F)أQN1N1NQv(I@ E-R@%SRR@(()i( ( (Q@Š(AERS(((( KEQEQERREPES(((((AE(E Z))h(QEQE((((((( ( ((bQE((QE%QLBQKI@(bQERRP J())i)QEJ(IKIڀ ()QE(BQKI@Ph())i)(EQAb ( (Q@ %-QEZ&ihb J))hvJZJ(%))h)Iڀ:L1Eypdњ1E斊`KQI3@( b4f(4b3Fh@ E-RPEfPh,;4fEasFi(,QI@Qn(\RъLR`F(:fM.iؤ'4RNiy((1Kړ4((4RQ@ KMu%&MMQMQnh-ѺE34fInh)4LfI3@ IKL(J)i(J(Z1@ Iv(6RQ\RqF)h7SF)RbE7bn(QmRPbZ1@ E.(P1(&(QF(1F((bLPQNhbPqF)أRb1@ bPqF)آ1N1NbRHFZ)ݴmm.h&(.i3@(P &)h4 n(KK@ Fuf6SFh(OJn6Ө1KE&(bbZ&(PbR@@ INbmR@ )hJ)qE%P!))Ph()QEQEPh(((((Z((AES)QEZJ( )(-(KI(RQ@J`-Q@ E%-QEQEPIK@( ( (PEP ( EPERPQEQE-%PEPEPEZ((EPERQ@%-%0 ( EPEQE% (QIL EPi)i(J();ҚJ`Š(AIKE%Pih JZJ;QGj(:QKE1 E ZJ QEJZ(Š(9AEBQE- ⒗ (ǵ%1 KF(M%aIږPvPQJE%%LP(Eu4QEpIEQJ9b9bhcqF)P1N.7b\R QL.7bIH.%P11IKE7)h((R@(1EPF)h8PbR@ b1@ъv(6\QJ)qF((((JZ)(%f 1I(LEPLR@ JZ(( JJZ((PfbPъ1@7Rb(wQn)q@ 44b4fbuF(u&(.h4`3IF .hfъ1@ 3K1@ 3K1@ E.(0\Q@ EPEPEPE%qIEnhbhII(hE%((`Ph (4QERQEQI@ IEQEQE0 ( JZ((4QE3EQFh4Rf4Rf\њLњZ)Ƞ4fњ`-%&h-4Rf!h4fnh-4RfI3@ E&i3@4fnh.isMBLњZ)IFhh4SsE;4RfZ))(RQ@ FiIE%;4RQ@ E%RQ@PIE-RSh4Q@ Fi(4PњJZ(Z(Q@-%-0 ))h(QEQE-%Sh((Z)( IE-Q@fZJ( :EQEQEQEQI@ JZJQE1j( ( (EQERQE0 ))h)( E-%i(t)RR JZC@sI@ IEQEQڊ`%:%SQERR@ Eb%-bb((vE.(VڊumP1(bP!bj)ERSI`%bX8bNQn(;)\,0NhQq)Eab1@ 4Ji{P+.((P1E 1F(bJ)M%QE Lњ(Q@ E- J(%:mP;PhQm%?bE?b)QN.61@\mQm-.(PQZ(1F)hPbR@ bbb)أQOFeb1OFRb6Ԙ mMIEEѴԔP{hO&f6bJMM.rMWчx]QX0ȥi6mm.h&6\њM4E&Nݴm4:n(-RPQKI(RQ@Š1I@ )1E()QER@ 1KE%Q@&)h%-R@ EPEPIE(PE%Z3IE-RRQZ)3E.i3E%-PQILZJ)h(&i3K1@csFiأfMRb& ?bG4Qf 4Qf)0jJJf @ If(>SуO0iyQ`3)أ)ԔR@ E.()h(((PbR@%-%RPEJ( (Q@ KF()h(RQK1@ E-R@ E.(JZ1EQE%SQEQEQEREb(QE-ZJ-Q@RREQE0 Z((Z(QIKL(ZJ)i)h(((QEQE)RQ@ IERQ@-%-QI@-%RRRR!((`RREPEPh`!i)i(QIE( (-%PEP֊(E%- b(JZ(( (@ IKI@^Ԕf(Fh%EJ\RR @ ތQތE (QE'J( Ҋ3Fh))i(i(4Z) PRRnSh74@\u%74gqh:8YjQsᤋ1{)؆:#$4RSY@ntJEiԺ҅ix'f>1zvb6@ ;ueNsVN)YFXfJzQ4w M80jDҰh\-BL(bP!LQQF((Z((Z(bLQZ(PbR@ 1Fh@ E;P11@\mQ,+Q,;bQ`Q\Q,bbR@%-!8LOJi/Zu'rtrJѷhChZγ>VZHQE! x = Wy۩0)pnŴ!V&TZ.|땑rMFv||e 36>ƭk)h؂ v1hl\ẻ) /*FF튧4fSdni2;sɹ;^5^H$U-a%œ֥\fI僆7*3si,:2Ep{|zO(L6EMs1iBUݩ-2 Kj~+( ( (BIE-%PEP0(RRE%QEQERLPbZJ(&((Z()1KE%PbJJ)i)))qF)bbE;RSF(SF(SF(RPQK1@ E.6RSF))iv)qNFe.)h7SF)mbSQn(;bLSIn(;b1Nb~(3b1@ %;bLS1@ Qn))bLSE0E: F)hI@ LS1@ K)hPQKF(S1@ 1K(1KE%Q@b`QE (Q@Q@Q@Q@Q@Q@(aEPEP (()QERQ@ ;REPIEJZ()i(EQERQ@ EPњJ(4-њZ)(-PIE-E6 )3FiufLњZ)(-J(hRfZ)(BњJ(h`-%RQ\IEQI`-P 4CEQEN Z)3FhIZ)3ERfQE&h/zZmLf/jfh14g;4SsIHi4E4)(Ӱ4њvh74fRwFhOZ3I4u曚3@tFi?Z)ii(;4CȦ\y4SI..h 4QLfz21E4߅&ya\4f{җz,${ѿ,%!=Ȭȡ bTLP-93MB#)HSM?TO)ncϵRD9 /Pb*cz1Ȫ1zڴaKd nR`Ú1Eh\؎Bg:I)ֶx 3 Gʳ+~vThHIWֱtٺ"VUXj,h.(bQI%TϗP!Qǭ oɻvj$/̼:4(`Fzf#4I%'4Q@2v(6v(6v)1@ĢLSSF(bPh;PhQmQ@ Q@ Q@ Q@ F)PqI}bSF(RbF(Q@ bSLPqF)أRb1@ Qn(;b1N-3bE7bE7QLSZ(RPQKE% ))h(JJZ((Z(1F)sE6u%0I@6ZJ1F( E-QEb3@(4Pbf (&hh4(&i(&hh!h4RfI3@ 3I(h4fZIIE03@Ԕ4u3@4PIE-4ZZm-04RQH`-Q@ ELE-%SRQ@4P њJ^f.h`(4QEQEQEQEJ(QEQI@(i3EQE%04QERP(iifQI3@ E&hLc4f4f GjLњMx<JLMqMnWH~ P智4rLIfRnøF,$MQ9Nܗw4fF(\4v,z`%Ij7S\4nڢhh\u&Q@A9v*nZv16FqϽbbب33LZjL`%FTY"Ļw9& (Qf4X.MbyNM󚄚311z7T&1>43@EyyZMX97q 4X9o뚈3; }&Qgas E|h\"y"LE9杅qzѻ=< -FҙJ FF)%qf}.y`Ѹ7FyW$jfI1ckF5 `1\ԤTerqLp"Ejm*7J"cUݹޡaHd*g<OJK+MLc#=h" {P" "&E D\U7ZFRPiA9 4vʬ8:TLH 4 tLPS \x}j3Gz2 (52[qTކj-25WLt:nǨV}*x58$;85}lQ*< 5؈ĀÒ#{>Vi.YzqgUb '4R4THqK56;UwL)&mr U5h"fLc` VMMQ:L7 2Xl)A$) YQMd⭲3Gz4ˊqLi dԴR݋]:W#ԭ?:>ɬ5US[[ΞKc:zKE4!F3icL^je8S!@7eied5.EUĹrF5 7ddT.18 # dS Td cڙN"HN)"i7`❺ 䡨3ޜ !H 8 K)4f@Ÿq@"ҜS^1U|(SbHM fn4XW&YJZv1bJe oZ= Ą$#^H2EKХ[kԢ5s`+e*M&)b2BnSlycҀ&)vu!4iPhA@zc6i 4Ӱ6)o&i &)Bu% vꎗq Si)Ԇ %-!XwQTHVթ v)3RE 1l)fF@ iDP3i5aQ1yI yLߑF}W$Fi(I34Lg4vh#AO Қ\{ 鉁a@!ssH.yO֢Teﴎ(7H7Z7QaܷO 3HAbyp!&iybg܏Z*6c ;rWR{Xsb0fmЉ@T&\=(]1F*ph )jpuhZZ_ZiUSs*\da\E08= .hJij@E48-pdQޓ_Z3_ZC*/<=Lfy(Iܿj~ LLYsV ) A%b%4GZҏ/0T&~wMj?JC2gW|lýNe_Jiu=#T[FM!U0&[znTU3N(ԙ$ǭQo+zѸz:;ދ1{4f ;R-X|ɖsIQyˎ(׽+薊^y\CGNtKH\ 0¹`4*i1N,y< B(c"\4l& >=(U$҈hk"T EH= Hpp@n&Q*2r\њy4r}7ZYi֋{ǭǭS,}i 1sxi u5OsҨs֝Z}*4ZxAH5He4jQe(Ӷ.Ki){P1ii4PIE-RPIK@(REQF(zR⒘ IFh/z)(4f4f@Š)(sE%Z(Rfi3FhB4PI3@ ҁI;PE64SsFh (-79nf.iidJLњ^(I2(hi3K(bAjC iwR☂41F)ip+枩w +PFC)i!9gS]j"بvY,)Wh}x.rboKݑE&QM;a  eaqi@QFAJZVUPf *3TM։1 52;R/64c^q"QސHd1&O8.Cfnq㊴lLԊz  dX*4.= O)\ŜZ@\OEɦbqw9&yITWgLLM;J4gҋ11n7<(UL搚m2[⒐dxNi9L4CZLMF)Kq{RpM =hK JBOAI h)> 0i)R"bh(bj4 C@#ޗzΐЇ&)ޝzP0 pASIFWIy;(4FE7q d =ҞSFzRl4'5w(=)*yjY Hx81֗;-S-O/\p9 7q\=&.)qEs ):Ӆ0 ia4*j"YSiM%Qb(4RqLsLBM' %2F51^w8 D[$SUXg'4 ??J6i42Lɦ@K7C&J B ɧ41dBi@)HM&k5H9sP14U74yƀciqZ]PE+H`S|GJøGoSO. aSh2LӲ7ByC+g.tR?1h}iSSLfu}&Q(wqIbLQa\̣i).M{ ,+&PFi.KI:\vFM \ޛ.hXBN .i<4 !HAHiEV!ojn-.hi(q{f4fҌ)2#&R!94,s!؏xxc {҆9*(8p RbCFi x 5;ю*/74yDQEe+sQң$$KeIN;4XW$FD[ڑHʤMIE[:⤢Ӕ4ffxurLLF>njTp(4s>:ƨ=M#W/0%)q@=q@QH_F^&.0*%RZ@X BsNm 4RGsS1=@Ɨv<7bNE֌@5"qEH4f٠.I3L3Fhni3@34gހLsFh \h!٣4sE4@ KIEsEq٣4 h%7<њq.q—4&9Iސ)M2n;4f)s@\vi3&h4dSrh Iz( i3ޛJq4\晚)WaQQz,%/ME(ZV1/i|jMX;7櫓K ,ۆi7T98$,ĄnQ(\s7j<Ӱҍh!"riu hM6$\)4֐srh/S\bb yVKA< BtVc<) _J4 3c׭0"e_JTCƜe&00ivIz_jfiȢd2RJcؠRcq,9j2ǟ'}jMsI`P.dX`ӷqCD2~80oIZz۳cfȹn3ɥ̇ʝ歋eM;G:f]8Cl:fd͔Nh5oMk| \R秭Jb5ZbBf#]JQ?oji&֗qzԋOJM"J2GBj[*p*\T[i {Ot84J(}$| T[9)ͳɫ;he*q![N>rLF'4nvF(~NSA~Եd\gنz*LZL9ɤ6˞T4s0EwӚ>HS|̞DW;>zdՊLÑ}oG4uLLG9mVGҝQƠ9ʊNyOjyA|(q@8589B{)5"Z'(ES5 Jt+p(h'h+7c<*JWiPRk&:/ZtQLEDwP=R!m9T@&) +$cLkɴ?PUM=q^ڄBH%Wv5FʢƎ)qD?f y^3Abb)٣/Jx)R xO 1A4LG&* @6FiTRcb"4ER֊`b8MNDVQKRb>ih6iEF <CSM=iSI馘F*SQ12QmާnEGL͌MnCң~Tx7׊77dF߃׊mSG)\xT@9_4Xu9 R RG,qT]Xb*gܹ&pXW-uR7aj I2I-N[\sUkvA5'hJ|ۗjZDŷr}j9SBIwKq+=X0e5G;#k5?1vt4V3sRlu5un_QX踜$YVQM hWq}jl_19jc9wFz,ĞaT,sMbnZM'3UfX3hX9 L2ILZ,+7iH@WcITdRcޝH^HT`%(*-kP+mfw$&i(b4ܚ3@曚3@.i4wi( Fh\4@;p~;LiM>f;ޫnɢY!2EfOR Qas))ց\Rb \Rm6CiVJE&jx .RmGEWc4AEÕzU$pej*ϓHa\9YZ Ry EfC7ϭ!(Yf&O!NM&iYcځfM!S@X3Fi962@o5("_0ZUsӊ \Ȕ }02qEs"CbEF.j8#4r_V©4KLP{ĥp@>]ܰ:ޝ}^G&F&U[:S@Ee`g]*ڬ{Z#MmzҼGi<ٜy[D^)Kyҗ1eճZD+q ֚IR[eVZ?Z9Pg]*=Fi򋝖RiuhRF=rh89hJEy4C#4bnbs4biX‴iԔ41NMPN4&4SIYH֤ZR*67jk [q,㚌*M!)Z;P1cLP!\v+(8fx:SL$w9O=Щ0ՙ÷SI..(J2q(B(Qk 1EizS)4&s*,JT ipMLӚM ~tRHd(9PFiW,T5TJzjV*嵐֦Y U9#DQOA@dH՞G bz^qTtˋCX=@uRld OQPs;ӡ*RxS{k]"ռ^9T[&vǭwfp|xVb-ĎsԳjTJr v/Z'+1\*#q(0*uZv)<]=nEv> kʮ|YNp :,&S۟Z6I7sɴor\ 5)d#!{orVzlaq^b5. Vx*Fngntng<1`3(\~;Xfqx7Jk $ְM)'z;VqzY uM^eUn>CWςmC@#ftl:F,ljqeObQ[:otsgN Tl55 eEtaA\SGi 4T{@84JZZ56-!jMR!+L#`Picn)n98@(FijcFP 0iTfaTd,inJIZa)v ךGӱ7,rzSe`NzUliX|Ű=jMV`<92J0G 5'Lxw9-00}k)x5ja`IIh8J7`q* ikH[`Ylul7MlnpK1=MNXHu!4R*E@\ZNiwLNi}o4`p޴M;}'aM.oTÑHkR?,-?ڊ ݴm IA4f R3Ib LњL %S$)i(ERzզbL)hniI2 TEI7RqR2FKr) j2՗%H +ʸ*PT]M;bE8 MO&L 4sJ,rKV=e֌UPl0cڮP)/4◽-'zp#ZWEG5n+o5۽Z{ )s!6f*SB8>$SK|jPғ"aU9rduDGH5Fh$1K|wpgҤ&Aʩ04\P=jO-4IBJQ2;҃w !>)$Ҝ1i FAd(OwHJz|8յU>TpL`~,}3J+Т]_Pkۙqi3Zl eMR} Q֌sZ7ZY_JQ.=ROc9Aq))M)&9K@(NN@)?SbzP4/)8bHpQR_Jh SRC=)@qM 1KyӃQg4sH.Ku0e]AܼM%) 5iAdisE%V~eg,5/10ȫD2&)pi*J3h!fƵ#&1SWڑDFFjao+ 4岐E;Y.ꁓm:F< iҤ= (PˤCO J:RN`{MMkc5Āc"#>,!uSMعSW1tB 1dm1H , xr Tz¤PRhˣ(Dȧ5%aVSsTc$am-Do¡HI*mgw Կ2k}FYzoTSfRp}=*4H%^%ki^4P8K}⣭llPqJøPYYV) .rث2܁)zQKq5mn,#lc&&գkxpC#jnehTzU2"ԒNjb5׊Q)4 Zi4E%%9V)<Ģ293ɠ vU q1W (Rj8@"qr|jS0$ y_=U!ՠJ5!{4˫_B^kSE :CDGJ5{_ ɵNW ζfuFh$ ʰ4)SF(LcOQIHLR44.MJp)ҁ 5+PLaޝR1I^AAVd]ޭj>iAJi !8iHESO)38&`We4̓>ghM)ܛʚ&ɦS-y5tRa1VkB-A'[^lnqUr,ңg ⥖6NU$%OhZnOA֐*`E2S95<ԁcF5 E4@.z J^qNs\O&} CE)r8槎sX=j}")inH4ֽa޳Vr}i+aUʇ4Q<) 2 -59a޳6SEQ@jfa#NYy Dn#5Y85.F !y!AC1IrEoG(({TJ6.倠v gM76e$^F3TafWVh$H(;QA8@v_mFEHY.5?sMyIh4M4yLW&֚YIIҝRGcWXf4S+nV<).hVZ< ^j hL,Bsޮ ;hҁ H5Q%d2.1g2;\[JjvZd\>ph) 4qz``hȦTnJ~h¾T=i8nBT‸=*~jCN #ڐOQJ94>|T т{yR1̨AM{U iAM1rSk"4t­*(\` \P)c5xxJ9 \5|$ 9Ʃ"[Hji]3xKe*qB5J *Y8?=(MQhrWk ulͫERbBz ӂ7pE^i\S'~PCt/aHB6`exgphG`M@6B)n]Qpwmpڝim,ļT;ю56wE[m2α3(C!O\`2x8bB4>IiIٱL $ʫ3~$!2R41AxՊ H R 6x8@iR~e,@H*=EZ 0@?Z*͝zU">s Jz^Z4@1dP5ԸT++MSӢ,2HUdy"VVe 9o.y4!0Hw23?]ǧҭB35^3h9R*NjPؤI1q.)`S|$љ.bo0GC*1ԂPbP2XӍRUi6;/lq^վg޹}uUh<#Q&wL$)g}jяK<:i>1ך|^E|r)@&sNTI AhƝ)6 \+`3VKi5G0r28g9E#VBpojQA@)\vcw PH3#VHyQ^@J0(zExuLQMmF+\2عUxZ9?2FV3g"wJ]{gf7q6ZhFz 6C:~҃qZE\.4@E9[<ҞMep*GޕW! r:S>#.,9T<`=MhbV 8 CV+QZT黍E٥ 0h幥iM)J¡ Tr; C3LDB;a۱Nט "i jVgi+N*ʚ[GZqj*sTUF>L^a"*,TDg&+a@D*qTvV -DZ^yT Dn'$]y} FBr RiaffUrmopkZe]¹ *IV\ :)s5vVqr4듎k.[(5)\;A8-b 0cVRR*ƪ\=QS72 $ [3] yV(X#UHr%=x؂vTnE$9;k0=Q.ci}taRX(M[`ԸE4f289e[S7+(TcN j4zk铀H[̧ qJL֌TBZz=iXw0q cA8^4SW ej'u14VHV<i'ڤ@*.h ԝR(\dS[lҝh'j/ ެ[| ċBI4X+>l+ @|ўEG,fJOj' zS-ҮJk.M@뎔+t9.vUn0yŏaQI.:HlE 8ȧS%#9T R&p|T@\09*1@\xU|QLF-焬{ ʑqz})uoYk9Kh?vCsm[H&iXfr!]޹qxT6#i61h"s᫘lu+Bvch^CzbfB1ڬJD *e❑M& IJ[;H`s\Uw7>t 6o 8.y=zWyitۇH_G#cΉ$1K>ߥv5Rg'*cU*CUs+Zqg!o&KNW;36=8.yTQq, -E:Chbn=iqI@ 0)hINTԞZTP6JC=EIEA.sQF(?, POъubiP\f4Rf4 ^)sL-I y47Rn 7Rn@&4nsQuKPT[J[wZL4n 34fBICRnHMI}GL7T{7PQLD [ .F_ҙZ,+0KO,EERn c&'YsRMʸ"Cay Rp{P+hUCS!0*Wbhi btxQO/ޗ2lSk\]G0r"B}*E|)M*Ru58(OqUqRGsKy}4\|s֫Px{х$r [4ׅVf1\niesš0i>8uyUl jub@r*+ZHCsڭy _,H2 )Ls$N 8_cwS1uMxZ(9M8`s,j"9w'4w?n )\,dԥS'ެE2旲ۓHP1AY5m ORM)bN +tXd(0&8WyS ) 0zT\W bqjGȤ1o!.dDTt9W#̹FZ0>jT֧d?JH涺g=/G8e 8権`p3WbbpT'r$wsVy >&5iYtvErG\* Oyr87qȑy$NM"Ϋw.'LTsn1Pͽ)q0^ghАMQ}M;Ȅeg)lqZFL~UߥM;]+4 OSble_VC\ڠMNKvcԺWV?*ebǫJZSؕP9,rzTb"C-grq dCg'P+lg\W?zJ;[oc TS\FOeu%FjDŽ9jT XANZ(h`'*͋zqS8*-;H2K(j-bI'E E$ҡscܜQ@f9HeݜJk^M5X{T/ҪțL͂xh.OTDQ8楗1qig^"ީ.I>-臋N\gY)B9G aϽ6iJrJkpqT[R-O+zU2+\ EOoOmu:dO%VKIxU^Sv ]Ɨvp9n}*nɪe)>ޱ^>✓ Ҳ@Z $@AE.}*MUK9]WH&fbJ jT+ኂDqW8 h^8QS(>"sޮGhlI X*(QR~ZW*+b 4 iAHiN)) jhPfE8 R50Ҏiրj7R0jg594\RjrG4lq|PB. 9P4W}* \+A(byqEsW!i@V68A'Ah7x3I,;%P)  M ajw)DxqNǭ<.)1JøR JW4Ƌ=).@fBu -TАAՄއ;4IT&9kjMa/9\roi=v=:RMi6Z;Q/CN=Meh*7$qEhGbO9],r>y\Ma2۹ *y+EBXu b=[$sY6k7F*CxSrN«҃(ː9ݑP},4xG(s 'C,{P,wW࿏,8ܨٽJwa\e'qE>AsijNpjbztיzSQp޹'ڣiI 檅#~>e,y9W l}j؜T3X$ '5qI!#2Rn\?hoZMI* t%'dܟZT"G ֞vJfbt݀ 硛i䚰.FECgo89[r²x2\wJ\˗7>i")㧵QbXS9Ns{aR.k120k luӗ1l LɚezVLKBÞRHH-^$!⢖d\qQT#Ov$-#8`TNMh٦˞*|dfdr99[F70TTAC2xZQ(6SOcJtܡӹrMQ5r5[oT=II(#( d S&ƥxkgOH 9XB%@85#@Ou \5EJb:җ4ii)h1VG*xmҫJ dRN>MZD6E&2{w=*Ċ 8嫺F|gC!fsӁ]Tl>SdzticG:̗hb1O Ρd& lQaZũ# Pe~DXD4-gFF)A㚒fۭ794րB)1OXYByh5<:v6Fh 6dH~nN~&n֣G~vGC29ihhgUO*ԓ3qi4VMdNJcRDxcCv5Q(9n"Ɵ\ag-r5R{[Z@/ T1C1 ; STx}ipO'HVm޹EG;4TqwVr@9+U»*?Jg ܎(#9 ?*SC7K]L2}wHwkا!(}(A|!?εڋhȍ*AQ<ؾޜֵ%v=T(NuB%ԫi[ZȀZe7sDvh05.hisL(4f4NPh(%+,8'}fQacU[FD}Q98|g(g8ITDri-+C1/%9j%;̠b~hi61e)b|fB;J̓V>uJҨiz3NM#Č4Kb➷ 9ŏO*&7/uHr;fßyfqѢ ç~M_Tp6.*X)RиߨU&OҴg4 +2&8{WE%QUΏ ϽZ2q} c{Bƙ>$!KkN;?JS)šbȸ#&NzUS fX.PҪKi \ #4ɌUeҶqMhu%ܐ\zU Auh֫KZ27%]==su*jxx1;،tы#1*Akąy5,, =G@H栳S7\JbB+"EVU䁚5TSMf* 1S1S=N:MxytzUR:tXȹ'ڭOv0c [i.PasJ[scr9iTLͥZ[\k nT~tkQ}f?`Jp# בZ%;VqkszQLG\oZxsAǭ4Cw# qژQ$Ux AS* f/rXeO$YzS91 ](ىkQcD2S"n 4 (*kAXRXUwL BI I<1HtɎ1H/cLAtⵘU>Oz<* дHUqL^q7$ _CU{B}N73`յkqH#OJ0*-S*%( *hv楥SsDTE袔3}4!a}(2s֓X.ON5[L`,H@ob#FqZwOB\Q ԨWvk)?Ÿt)<`5U';)E*67 zb9榊ThXmQd$(rL /fqF(ri4)+Ǚxo `)nF)ihl ']]ɂ8"!`D0>Ac8fis@}iHZ`2lV) j1`+AG쐞:7rTl8 x*J R▔P11F)PqF)h44%!4f ILњRK(i*J(ݕ9;C JEӃXdwT94ɷRI䛩7S(f4QIE-Z3IIZ)3Fh-h&h04f3@ 3Mf3Fh34n3I~hGu$&j=ԛ]ԛ=QnGPhJLўhFi3@ҴP!iTJeMX"AխcI&1SLTi:]&DhM`er)5d'(@O Y|.]jחJi|RmXhE)6՝ҏ.]{՟,zQڋPEKQ.;pmf)8Rf4vi )sL @0H5FU)kZ<{Jd4e3~y3w_񩥈ҫ4NqҩJ5[,Rnw-yJ3*@.G!ɫhVxnIǵ&4aiw)wR|@4 n)PjqFwr .3TZwV Rbax4Xw.TI8IEl585T).iXw,uA} ,n*%'rFU yh[7JC%;/4U oFOzO2g hUQQ`g&ZyX.YF{Q`kTIEm;̢r7Uo2X.YݚaPåE$ )w)rO5*3OCҁMF~isLF}%7u1M&Zi4݊BiѺF ۩w!wP4 qHZ-IB&Pj i7"ϙF٪JC% *O3&2_u`IGU\X.O9}Wi n}"_r]֚qPg6X9GT=iwҝtH}h8U>;`yQKЊ,;|Gf˻C U/;ޔMZ,.oު E.z,. zwa?J,;~ 8\ -E3aEjzgY_iyWNirRy՝MP^Ɨ3֐T>ўZVˡT<23KEs@Iw=kVcl0h\7#iTѻHx&C lq޷-y$@J0vJ: `]q4hCҏ,PIb> ɶTnH֫}=qLM Sv=*󍦥; y4$FO'5"ǵ [6=ZW,-$=ji)O=( $VdUIXzi ԑS% A5s9МңcS&?w4f,ysSgEm HD5gE+c YviNADpN4 H1hG*:"QO(M;4dsFh xQ5]4fIQQLD۩|TE:W=i7 V^y &hs-LYcOzQ>cD1֢2b֡K 7T{).Pj]X J$5_4nrؔzԂ\wAw╆|MOVxz_0aug K,8SqYg=iDX9?KiD9@5\xֳ_ʥ=Xw/y󊢯jPṣ̪f94Oz4 m@M4icͣͪeaca\e4Z4G9=谛.?ZcGϵ睚<|L.h'Z֓+GZW9AJù{~)'5I' Ve#hhi]Y$C/$V%KEN$\)ICf<\;WxmnTa\Vgq۴ӛ"ҭGJ!v skQQP v GII2)|G(W%$Q*/23,"l8wҙ(\J̤(OEA*Q(+ 6*!(=($ܓhNھK=)T!@dRF }0 MԛR智x 3I!ju45zi~hm@dXz@_ygw},WI,yRs!(:}uOsM2󊀿ZilqM- O#M '&yuG\!I&-&Fj-nPo@\uwZ] &ڔ6{T! ր,f! S@S{:[@ɃSTEnXW,n?57`c})C``1U<̊,;7QK۩C;wP۩wT;wQao6j Ի\4wR RwѾI}&hGu&hGԆI)wP4&q٤ݚnh3Mbb\QHN!\PQ1IL(Mԙ!Fa4ɤL&4&hG7PIni3@&i3Ivi3Hi M&C@Qmњm(Q@ EP(R&9Q@ @R&)S()QZ(RN4E7J`7RPM4Ӎ7ibIOhLDy 4fڍgj=x"Em!iCÜԇFDM4jFў11P rsM@&I<Q8+aLJ=g&83S*)^;*3[.GJW N)pz֏ TO'&ۺR@>$ir}A-4倶xVELҥdU+]jPj1җV+uUoNWYqڗˤ]ҚT%)}'DRW=x광TS% c1犈>ǥ7jCϵ1oA2 cj2N) x=sJfޫgj ދ 74sSc\C/S2q]瞾X.ZY}8KT4'?z, |z =Ď`ueKg< qA|zQ1ƨn9GX49&sY^vzzRO?zx֝hsi玵'NqEi߭(WGF+ RO}yZvcin8=*U (S3irn gd OK4Cݚi5H\qK})X.Y&,s֣Zo@+1sQ3P&J T; r%sڀARbnOctiX2bΨIJ)؛Z|4w{!{|  uTDďJ={Jr٘He*iW,yzϥ@j&c7L3Ui1ޘFQaS!i41Lzr3M.vcCe7)DOqi?`q@D=)q.A'yN hh\`<\:!8y"OZp8k;lsNXI"g(izUǥ5`x&֐P0j!Z7քi%Tu8}NGT`}vshsU0"Hv(,9LQ>> iRoM /֐A⫗@v |:TJ Hwr:DL(}D'8b,>20t|R㨠`#1.ii<̚_-})c3KԻ~zP4сFL-̪*nn?1\M5LF:cҀD 5:Pi N@8"Y BM aHn+<2i6;`CK>^sA4Xg;#Nii=J9JR5G=k(\S'EsO~:gӼg+ 3HMGY|};ޏ:G aBCJ'8wQ jR)cN5Hd:J29iXi 1i|U%+ sK z)X.[@I~u\=Xx҉3U⌚,7O7=OnsE;ZO79xҰ^n>7jvE1 Ry^4+<pi٤;on7sցܛ}.GA7{"}ZO3j igg}7FM}EށDZG3?vz[0xѸ3FxnQL'$f-H[A4cJ 0(H3LϥLC#4jB)PsIR)4J?4`-QH`iSqLN&(F)qE'j(RZJ((N(1))أZ\QJ)qF()h )(-%&i3@&i0Jnh)44&s@f E&h-%!4f4)47Pn4.h1 \R@ 4AEw Ud+Dl92dgi 8Z5S<3ZҐ@F8rG\,RX8Jme/('jA<qSbMh'=(f2zTjqӭh~ \Lkmy*JpAJXRLSqJЀvg;4@l֎&hڣ)V4RYHG4bGQ4S +nsQHRh ;@ZO^ZH%-ӓ%h*?ʨyW5;.*68SQLǎ{X~4XBZF= g0m>F*Nqր+99'`P) >yiUo7N[5fJ`N$922jcF,җ}*lKciXL:Uwc(@&[sIs֪/h*7O7N0 !4 嵟3g瞴 OBxNƀ 5e=+.0EH'siaQa[RsK44;rj~^iSd1T!R.I&KE0E0:wZcޝPqHv3Dl*Ao{֏87h #+Ol[+_|4\,c-8Ys֭14ǐNkumOLU1RTcA&AۏJd@TqZkQu+\Y gTtx7o+?d\i iNdgjobcⴀ84]JN i4S[Ÿ)25&(LkYa* O"92^34v[Z+ ~UqbP29l@ҚieO5ShR,8?8]0={sY:|ɹDw}߭d=B:ұIj}e hai}=ZRE5~ќ='9=+)XZl !f=*Pr3)ÎNҕIXӄ5icTP3ԶRE%*E\t0+?0+D~PA 5ݣh)63Xi*AG=3) z_qM GlS$8a'8>F+ cYx'g=zQ`ڞ.zzp)EùdֳEϡF?.4 Ryꏞ)n:,3@OIYp^8cEs@L1h5.3,>sOHnPk3yKa9>w# is>ve'jO0c5L͜:gxfSVo:xLGBzP&z3Z>,+fs;}fn6E ̌TnAynJO7,;ieg'4'&P(.3OqpQy>ԢadozC5A {TUih7- #d*8b^|_'4\63;"?ʨiD`r.zQ1֘wg zԦ؎0P13FƓp)'#.<7"0);a8y0J#HOh3;i۟gMP!ѓh(g!pz@J1\T;$isޡ/o S 4uAR P/L.O_̣ͥaܱe`r}fZM14iJnblJfQqM"u4#**wlji2TUJCYV\i chf"dx&i BhӚ?zMz昉zM/^ӮE ~qށM١{;{z7E{xPҗ砠hw4r=qQ9̠c֬R3`R*g\Q|Tw2B *09ւJR׿J,yPx\tDȟpb;UNXI x`*nOE{D88@֥EJ'2d?JU;*Znh$1;(FEXT-c;qSJ*LRb)"V# R'=(+L5.iꙠUI1]A,|q@R֖dZORFmlAj$E&씽W=XOǷ'}h@;p~_ʳɮ7U X'5Iֳ2d֒[cH:j/`IUes^q96ӥR.r+kpJ S)AMUhV䐒;}wjO<08=?*-'5DlM3֤FV?o zRPH{B3Ƭ=ƫKm" _?':f29I)؛Ĺ\gj^ryTP&Hw/Mz3'zh~shrI^`@<;,O5Og@<qw1֨3ޔIu{$yGcT| J'"^x3t*qA)֝r`ީyz3Rߙ'TsHe4r9J'9ךQ/NLF~h2g<ӖlM! ԂBOSVY2Er~ѻPg93HcĞԢCt>"4H[aQM:tcJe4f= ]WE.bLxv+Z@H {c*[.1vjpӔqO R]Usڀ9RcBeH.) *.)D*x(qivI@ F)P!1HRAd%;f<ՊCڀeFJ8)<%@Ңoy@: w)gi my4SoGG[5.ES `m1jHCZ(,g"zHx|Ӱ)kN)wZP ̩R,=* ӂTiq@&ڔ(@Ȉ1R @E簤0U)E*e) \mMG(GZMH1Z+HE[f)6U b*Vi *⛎*P)i)*R( Oi oš} !HM!9 0z튌h:MÎp 3}7oʍ0@=iw`݁Qo1L iCqU}܌yh$$iCzU?8F)C-}S]iw<XaM2ϙZ7UpRLDg4T;pb,i2v}zpΐ>Mݚ]&L87C`S@n?T) nCx4 ӃwASq!ҡ i@NH` /&X}Abzv#ajW !)RN 8 \Sc@;b \R@ )h(QE%(aEP"h%%!i@ F())bRPQ)hS(.)q@(R1KE%.)E-P1;QKI%-K 4qO&(Rbb "EHiPf&"aLNaQ.BئL-NJ^M3@\i7TEvI)~J3M/J^_8Q"mK;n8Kv̨L/E7yntrכGZy4XW,:sUJh~XMh2fo&w42~i|ʨGZx@X~hLXW-ڏ4U_22ܴ$H8g=i-yRyU7҇9rߙKsTßÚ_0Xw.yyZdyr̪"^( ,+zQ'5G7j,/yyK/b[ O!MˢSmQQQ`{{q'Ag4>CVpRhll L$kظU)Kw881&{T;)7 / 㩩^Հ Z\J[CtnH5ֱ0*8P6@}hc#vt:o}ҪO@1M14d@Hd"e+URցm֓v:>)7MɼNU.qLdB\!qMs֓JAn1@fW/&┷hcIm R2VnzK/ޙ 9*b(n(D=izjA'l~4XWԸiC5Y_ZS!HptCiNǽ!*7dz $?(8r} (=OjdKIE٧rjPݨ)ϥ5@<)8Ԫ3cV8;,dm1S,c)\v3ExO(bbSNOJ:ҸXX[ T8Z&ic=mNjZۭ\X\v*pjE>PF#&)q@ F}-7m(LRE (J(Q@Š(((AE ZJ(ELњfsFi ~h( ( ZJ(h ZJZ(b(h(PZ(J)i( JZJ))ԔRPRKI@ !mFE0ieDTl `O[d3LLL$e#9X8" L/ic4finZi3ǖM/ۥ4i64p}i7rh*wR? Srm"9Zphr&O949qL0C l_E)9.Nd_wzMya\m2*JRReoo0)u¹owz][x< dFJ#[aRi@3u:SwS C%!hINM!lSKSԄ␵0I i5BLjf/@Fir1U޸X.Z=iƓf}&:%!n!z&㞴n*ӗ%@*Lt6Ry|T}(z;Y^VX8M^y Kl碚orz]7֞ 95\\b$akmahY F>w& R >zR0LzoFi,Lw^sUIkZ)\vL>&,9JE9H1<zz0ӁZ]9@J KԮW-o<i-k25F9p(g3mϥFcqƀ:S#^83)N* <.)\!;T\=1Q9W1@ZY-iRi HjRH NKT{K7T[7uCu&ꄿJM ѺFzar`“}CB }&1sH_ޡ) >GPoM-@>`V-7ց|<޼R'=h oU̞g=:`Z=3ު'l?IZ% ErߙJ$橉9.[ ]} 7pz$ǭ8HqE4ɪO>HO;qAJvg zM#}M'壞2Z$ v88* R2lixhA/ru|R5aGADJ=\qM47Z7qzP8vB0|iU;~H0%@MZQǽExQI8RN*q$iW"ړ*{fG;Qoj,FҞ pZC4: 1@X`JpZv)Rb v)q@;K@RaEP!h)i(bIE(((((((4\ӳL4&hfysFi)(Gz(f-Q@ E%Z))hJ3@ EPE%QMfFhh:4њu%&i3@ E&hQJB)hM+fhɦ@#Q4>HPS< c[jlpm4դc63 D) pO@d1>r{P$8sUh-cJ%jl i{P4uZ2iwr[ZKQSK9HX?YNi M4oŨvFigց\oPyoL`Y27緵@im'2g[9ɪC4}kw"V0) {2ϙdTzJ LbO֪F:_ʕrِf?Nz?3@~?Zv/ohǭQss֋rULfiؖ[iVY3S)(9Zg;H#"> h O8U@>D=1S=-cOpqjc4#W"{xTqMǭ+i}PF9 ;zS6? '> " 9=Id9" <`e!Rm96qR,zSM@36|Ud+Xۖ<ҋ3M2Lc$VĜm^܁қ`DFUjhec-<~4yg=)>2iczC׵z~uFqMZhLh^2?yLD-EC:Kjf= v۽;CqH[5_}.iɷ]T9H N>2;4,g2IdԁMJ#4ѷ֥1@#"M4BS4‡5>)0(D;qQY"2̄fRx?ʡdATHa}M&NXXKRqb *UUV&Ƣg bqKC޴^';-n@j m*6ESGaBsV[JgNbGD5`CG\daiSN CF)(W:ڍ xL 3=MoRnrm٤Qn@\~noDZwz7P2\s֣/InE1&ʡ/H^n}Bf&ZMP4=}(&v&KrmԛqP9q0&ZB*-7iiMޝqďٲ})JzS|ٳ/Ə!=)WqL)O­|sH =ӂO PL9i G)  "DZh5(%UV9JdCFj1=Z9K=sHiɣu4 ].Mi)ցJZwb&/H_ޡ/4~G4gj7c'NE:t 4AoU/Z794X !R7u)X.K8&2zP@)pj/) A8haPImLWwiC 2=j"Nh$79PgQ>឴nzHd.YKߵU:w}BQ֦P9Sqש\TBޜ8ixR͟/Ni,(Xd;}*r8Hb8xXőү(qW#c\TSTSPRJ!RS Z(@-Q@jZJZ( ))h(QIE-PEP (RQZ)((((34gPi~h74\њL@f4\J GPh&h34n 3Fi;4f3@sFhSsFh٣4i3@4њfh?4n4њfi7P$&i@&i P3LI4Qњf7PFj=n7Rn搵3u!4&i3I`?u!4f&Bޔǚm7u&iR)RHM41SɦFWc%JM4@"LjrsQ֘2Zn9S7K"9R7ң>zl{Ԇ&di;b{f2&\Q2T9<Zddc~e֫vBddzSw1{Lp4 r=FA=0D(J8<!cU|Fe6ɑqUYߊC!z2r9i<uSK怱dq֐zUR3Ap2=j) bɗJZZEX*{V< c@3 =[n"c'OmmӥF`sZ[z.+Ki9TomVmA)\v0؃VV;U҆Qײ +sMSqҋF{Q 9K?QENkG(rmmk]C .3:ǁүM1=)ܛ]1Q04$MM6jYGS?Ӹ~"GJ2O<xK*ajr(?!j*?*Q+= HE:E4?%GRl!4DWښPTxqiZabbe@2V iD{SXF%0@ )TYnh0p5T9 @|1l7oJp!Jdq#*p`P2S,Dv+Jxb{Tbg^)=iPBRNd{9O.(=m1HE "( @VM"A*8GSBe!TX/5#{Tf3(n( &N `擞??a8Fhi8ZSC"}(F[4ZB:v}yC(B~@ ;ѷឝ@Exdxb"HCr1@XqK=9MvPH'Qҕ">2Bzb zUǚah#P"8jN(XqNیscqJb0qҔd^ ӕ@N)@ P)OFhBˎ1 #Sm$\S$_Ha*BǷLDG&Ɓ恢l Kw'x4\E} Z ӭ*۱< }i }j$E9jAlij6+~e`qSF*P74[y#ZKRXbC$cVLmi2dNH1+mͶOJw!xQcu3>.Vb؊C$г)e 9\|4Sk0zR ;Qp($Y=Sby 6on vj.>S:Hs1ڙ.dg|P`ȣ9Q6 bJTO\])Y4s) 4uЋ5E ؒ?:l.~ҘM$95rw3[Te pr9S9PyIBNZ/\SD4S${-Ui`2Sp 6$zWb`~5gz@*XȢXbG*dL5:@Ha<- O^OҘQ5j=**x&L*Qa"` Bu RE- b Z)i)h)RRE )i)h (-Q@Š)(hQE ( ( ( ((T@.i1Kfis@f4FhLњ`4PE\$.hQ@fy?4fh٣43L&h &i@Fu&!j!j`K5BԂ䙣uGMnKڀpJ)u!0InHM4vBi-Q(ME @7naniBE7>4:O^If3Q0IƘئ ipjWj&I qQsQ֣#5!)~)x4b?VnqU8㊤C ns_D߭2F\x'=(RnNO?JF$JiVcrxoE5=@_ޘϑ7kg5"P2=R&ç$¯Cc\zIF0F)RCҺ5U\=9✖m.bL(^uǶ+y`۵byLxZ2DG;q*6u+ښ`a8?) ڴwt8C4@FO@=)V2Ywlć9@Ԩ{W\XELDEHTN N9 JRnRRQ@ F}ZgS\v t(=*|RVxPSŀ! \ .;"v1J>.)SN-7m.)hHch43KIGzZu6ڐ’@N4a.EJzSM0*I5G]qM+߽2S1ӚbG*)1@ytuKab0J1H r*pZbDHm(Pb(i6F(i6Ԕb#Fʓb#K1@( Mi]x=´2&WϛK=+h#=*C.7dn0 :R#)lL[fGҋ)l6kY n}ԢhrܖL#֣X]3[Q5v\w)G կڥ,Rǁҧ-<-")ER ZC)i--QH(QK@Q@(hQE 1E%QK@ EPKEQEQE%RP0((QE J(Yni38&iLCFi-4晚\E&h%-Sf'44@fRM&hQI3@ILRn搚qjMi3L4iS3K;h3u&hL\Lnnƚcq`4i4F֐1M'L&RHM;u&i:e8`l Z:g)'z"M(֞(rbH[L0/5<(bȞTc l4Gӧ4/gPc G֤[5 V\ԢqI|SZ)t~U؋66 Ns?:QK|5sxhqϵtibj tR9߰/Jx<|jjO+:؎>ZmbڧK`;R2pk_y#ҋ)-$mҜ!Qڋ)$ʁs?t^RRcҎ`G:J?Z>T ~ Ie6?)⳧WBtۍ[%ILyN\yՔj>' ӷZ3w8훎1VVm/aOFlqϵN۝jdnA[Bu{,.vjѡN*A dqB U=jvq[tbV^؊ V0=*[)"vý[H)qNIv R@)qJ- 1NS)sJ(4 Q-%-QK@R-%-Q3@4P)3Fhi)3I(٤&3@i Q@ E&h4:Lњ@:sFhfBhRf3@LFi}%43Hfiѻ`:L3MIFi :I^Ԇ4LSI&]n撁gp4u7m(i6)ݴmRPJP{(AO R▌P0)h0)E%- ( ( ((((%Q@Z)(((RQ@(ǦSML(!0)(QIJ4SF)h )أab( ICHiؤ+@ ѴS ␊ !M*sLBQF=ivJ@6iM/h:*_,&i3@f SLC)픻(bhfu0ijI7Ty&$FYnwZ~i#fZ5 9K`ƙJM7K2*-ndOE!j3QnuK3QurLњ}(j]hHdQݚ仩 Ty!f \IG" :Ҏ3)ݩ҃}.iO@R4-&yWFi7PIni7PM!sJ 3wn33u)@tu0zњn3H4Ss3@K-42(4斀4fsG4)4 )B⁉bn(?b\SIn(;b^qF(\Sъ6v(6v)1@j(EQJE!R !4f4f4&E74f)(-!4 Z)ѻZu.qSwRy4Ku.8Si7Qap>ܚn};PQH:v@SO @ƁJmx4xBd[h3RhSf)vԸ3o.N4-< P)q(-.)R \PKZ(IK@ť%QE (BREQE-fJ(haEP))hZ(JZ)i)hPIE-JZ(PKIE Fi((JJZJ(J PIEQI@ IE)i)hE-%-f(њ(bњJZZ( (Q@Š(AEPE%QE( ( ((((J43S4 \SJ4\b@)qNP1QLRSF(Rҁ@ 6Ӏ3mjLfP[*\RbKRbhAKSE3mi7hH@ҕ-4+L@L,DijFiȦbiiғu!4ԅ7 PқB7Z i 4ޝi4;jQzP$0)vTb؋+0 CRɦL5)DqVY= G=J䞔lO$S&nHy튰mbƑ ,pSKebOaa$TʱY :VR,TT98- Xj\ E!Q@ (&h Z3IIvh4LFi;4f3@f5hI3Q-@7RHM$&j<ѺBi@&ifyL@ aP:ՃQ54&W+())MҊm.hӅ34fnisH(iI4 x!ԴhڌsI?4.IZ6IRn$HZE&z,$I-HZ\}7}DZhwRn"ԛ n␶* 7uEޓ~)s6MJW&K1I9j7T&JO4urR7O}@_!zRoWh;7cA P[cu귙J$<`guXw%F*=g&nuE Q.KuGPNh(9Z} Z3IHM-&ijijqCq֡N489ZMH qKFE\un¹!jB*="]nw{ѺKzMT[7P"]=ԙ-Qf4 仨 Qn@nh-@\0uAMX.OPn @\u sҜ:cRs;81M O @P(7@NT@ N4 KEZ( QKE&9Q@ E%QI@ F(b`%.())h4a 1 4Sih)@wKRM撊\IO"biji8LL@z wsL⁒o Zp@3;i8VjQr*V|ۥ,QpX)*L#..; O.(04QEPi((SSbRRK@f()i)hh ZJ(BIFhi(RP(ii)hQERR!h.hEPI3@ E&h-%PIE(aEPIK@) ZJZ((J -!Rb E-MPQK1@ IK()h-%-(bQ@ EPER Z@( )i)iQEQE (wRR@%PEPEPIEQE%QEQE%RFڗnivaij@\,G1HcqF)أ1N⒟ILӀibݴQ&(.)q@ -;QJi(( (HhZCLCN4@4O0DTSiBj6aQb#4ibI=}) z2)3!@ [RM &xqM-HM!=晟JUԊp}i z҂*1]š[7q+4ILLdTAcxJ.;;UJ)\v(y ՈU&"cX[qOUԢ@SbP( J\hLhsILf4gfњvh34f)4fM%03L&i-@Aj4 nuDZ3IaozMԛ 2i3L.HZƓ4h␚L!h74f;4tg 9Fq@FQurLFP+n57P2Piw u s@\u&j-wQFh1jM w2Bԅ=7s֘7Rn怹1~)7d%o.Mi5@&-O;g&M?e47}44Ҵ]䎴3L$M&q@w2 {T Ӱ\4OΫ>EOU}7!cM/׽>g!WߊBɠ.YA|UmJrbѿ\,UKriw* SzwP;?9?Ɛ֊2i3/jQMP1E< h  )fSs3@ M4R-`)4(BiJdN Qfqx)z҃ExC\P2BuQfwA@K]7wErm(P0&F@5Cލ.7{%s@ws,gހ$@<(QJRP(SR)Rx ( S) QK@ QIK-PIEJ3@ E&hLfL@ )4њLњvh4sK`.h&i3Hf44 4撁 h(bZLf>@m悆 R;)vP(4ULY'<[ [bvQp)<=JPƑY`QM)qZ H+ғ7q֗vx})?O@I)yF:ԠcR(Hڞ &ޖ)G4><U<ZvT[vs@7R-.@nuF=i&ڒ8@ 8-84n)03Ji3@i)9`QE:Ԁ҃@iҟM>fN9!)OƦQDA *L|~ny4ɹ6P$a\dRO 1S"qBFGZVHyR8'b(0Z3@ E&h@3@ޔfsFh٤74f3M!٣4њvhf4њFi4~yu34fFiFi;4fs@4u4 u8 h @-(E-b \QFh(\QE 4ZZh% \h;44(RQ@Z(PM&)أFE)1@ IP ANwCKm-3H(4 vh6&i f4昇fhcFxњvii.i(!E%--%:KJ(h-%--bRf (4PKIEJZ--P1h(((((QE (((QE--7Rъ%--Q@ZQE-REPER(JZJ)h)i)s@ EPE)3@Q@PQEf(4PFi(E-%QEQERhZ)((J((JJ)) RPEQEJ(S(CJi 1R'Q`d*TUh4(Z8Z@"-> \P1bP1⟁IҊ@.hȦ搚~x74 +Fydo֪H:Uɨ<\rǜܚڒ:U&KFsQdgҶ ΖKDғi4ڀ:S@}qO^ir?Z )S7cB1ށ8ښy w4џzO.8qNNJ2zSLӁ8Or* 8HcўiFy@0i;SUI) ENEJhqHu" z(iSҚhsN%6v3I` NO@4/4Ӏ.)McH"Er*r)1fO֚h@w)ܛ>jUbEhEaDSr!b< 1Nb)){RRf)3A4 ZJL@RQNfLsK@ њ&i3I 3Mϥw3MfsFh34f3LFi;4Ln4LfP(ԹN) v((-/j((4RP4J\i4Q@-PEPEPQKI@(QE-PRbEE4 .~(m(QKAPh CN")ؤ(Q@ Ew-%---%-!E3II@f4PI(ii4PKIK@ڀ ZJZ()i)hE-%(aKE(-QEQEQEQE(QK@ E-QK@(aEPEPE-Q@)hQEQEQE(((((J( ( J(J(i()(i)(Z)(E EQGzRJJu%&(&(--7SF(LSbLP!bJm!bm%;SPE JJv(E;iCN!!өP %8bE!m0j29 0hIyc ZFF1L)123J(Ni@"0=)qƀ!#U\j&A(@LZC1KIs@ iF)hRh4-(.i;4Zvh34P1ۨ&MC3L&4f3HbFiffsFxb4nWni3@-IM74&qM&ZZ47wl3M-L'B{ѻfxZb$rh)?&N(Ҁh(ƀuJo'&J:f1ioJLSX3SϭCn)鈐yLϽ0P2`ݿJ785 !oJa9hIHO4 [P$/=)閪}j+ކڮEAq-QMQR C QZ) P)hRvIO&b8 v\PqK\qK@ KzSGzAb"IHi^Tt+Y DH';Vܰ $اrZ3JZa4+1"4R-34bI3HQ3ނhSsFq@݃I iFi &ECT/59M"ThG0j=iv 3[*)LC i: tLcbTDd<@4Lez՝?ƀ*us J|) ҮNMJ2c D*qm D)XH"i"H/;.(4PRb)(1I֗AqO"e;q@ N N@S/z 4f Iތf!4!i !4( ^P)@`fh@\BQP3L^i7P7u3pqMzi4~21❚`K@-74f\3@4%/jfi3!fj=ZhF[H[rLM,)j7Svj=n`&hGޗuq5]٠cFj=  3F})RfI{Rfz3HWM ja4cdRgfri7P+gLg=A0 4O9@CSRg ){RQ@pQM@SiE:Lњ--74P&h.h4Z)3E:nh>h;4M3Mf4њvh74ffsFh4)4њb\3FhG\Fi恎4fA4њ;fPh &i4 \ўi)3LCFx4s!4ўizFi3M4 \ړ4P8\3N@Q@Ҋ\E-.h\sFi uE;4RfFi3FhSihh Z))hh(()QEQE ZJ((AEPKIEPI@bEJ(i((J3Fi3E-&h%-&i3E)(RQRQ( ))(i(! QI@QE%-%()i((Z(JZZ)(I3@ E&hfJ3L (LњZ(4@ E&haERRPQKELS(>1IPqIv)LSI`7qOP1IDx&(GP)LqOh´E!RO4"uȦ0EFUZ)4PyGjb<ҚN iɤqL? j)ILAڎE i)ƌzSԝϥ;LP!!Ja=96)BflqiPHOOjBi')3zi>KRnsFx!žsޛpr(hB~sҀwNyёj h x#c>g9l@ 8c-3=4{3@ /&(Ը@/5'G恒nɥ T9㩣8 7T[JR6xњvE2bnsB[H&ӷU}Ӄ@>zw: ozRCv7r)Ahmԛ=ؠ7J@H[4I,:R@t*24%nG7 g4o~J_ހ8!?87zRM*O8NjJUOjx\eb>̚Q{RVٚpC"Qڋ)[aeX)M)E8⒥+M)LьJbi'ҵFM11w{n3zSwcA7$-H[֘X4;u3!nh fuGM"mÎ(& J D54.?=1N=)HҀʴ*R1HQ@RriؠKJ PRE8 LRKKJZP)hR@ E)R1 4F(4LB4K@&i3@f4њsI֐gLsFb)3L&15y?4f@g)3Fhԙ&Ɠ4.i٦w> ~x4!fqKh<3K~y.hG4 viAgvh٣4@4 qIL\ړ4 -RfGsFi3@h.i3GzZ)(:NP֛҃Kf1gK;<3NbFhL C4Rf`:J3@QI)))hRRIK@ KM (JZQEQE-QIE-Q@Š)(RREPEP0JZ)(@RQ`QERPQI@ I(4( J3E (AE4 )3E-%Q))7j))jJJ(-RR((Z)(-P4P斛E14@P1RRP!٢3IE.h%4fJ(EPEQKE%!E-%%!RPH:JJv)1@ )qER@%%: ފZJCKA!)aO" qUU^AL@G4ޜG4iSzbsI@ HE;MRb(,f~tv@TRb 4bV)I4)M88I>Ïʀ# Sya!ziӈM=M1 'L=\Nzg1@}hLP=(i=(F 7<>ޗi Uҥ8]bۃL``[ 7gZBErRMS4 4GHxNqFNztԄqHA ps@jf}9%9T)1jHc;ӃTy'õ;4JY9Ju"M䛩wsL'4ҁpɧ㋁c1#5(ZrjxZ xJzHDҞ#ҁCwҊLRbI@MgF=ǑQQJz'"4ҟ7ҁCȦ'8@4❊a<{S=x'i ցLC'<}Z<CH 4=*0yzqG控)i 0Ih$:qIi! ~y74ڐÓ ژXRɦn{M7"i rh 'LjF8)$`&i3ژisҀ4♚By$y8ғw3#֐whGTy> v;n=30zT)c҂Y&I>T`(=P>j }ӇLyTTO֑DўcɧnĒ37"~ w4Ҙ?CV2}i r3OT'YXxbHQb皙SHccNi-:f4FiA4ifI4M4&O4u4P:}(@ Iڂx})Ɛ!sI }&(RR!M!HOg@2)Hy2"v m7)eȨc@]֢nqQ0)E/9TG9 '=yiy5(=T,qBt"89+JR RRO J8bK@QK1KIK@Ţ\њnis@f4њ8QM4 \FiKfh?4f3@Fi4:(h J;RPEw8mi863aFi{Ӎ&(\{Қ&?psJNtE4tO1*Py*6AdX8M*CiFԘCLa 4 9݁L$}j6ϮiZLP2}3A#Rp><~dL' s@S.:J?֬IG0*LgM#򠡘ɤ#րcqI48>/i\'GLA,SS0M'zo`ʓ4h٣>i LI8ozLњ-њwjJLnb43uuO4&FnqFh٤xϽ 1IԝscHz␎(sW6B4!5"; #b=!!8(;@Tʸ p h) ZZAK@ťK-4vyK-&i(4SsJ mcFifҌ4@KڀIE J)K@Z^FhsIFi(J(@ INi:k/1S1LJ0jR)1@P8O+I(iR@\b:AK.p)A4^4 њLњ;#p)EqA4SE'E?Rg2hޘ)٠R/@ );us@ E&h:i3@&i3I({M.iu x4`4g4(P$;4f43.h<\{C 4K)(٥3@f\!v4bIGjuf .iZniEKm8RRfufIERQ@ Fi(QIE.hJ(Rf:m-!h%\QE )(њJ3@E;4\fZ)((%vAK@b(@i)M4i(=h(bw (BEJ(RRF( ( RQ@ EPIKI@Q@Q@QL(QIE-QEZ(((J;Fi(J(f J(4QE&h4)(QEfM4((i@bEbOcI➫ J6U"b?`KacRaJs2zъޓHiQ@ ڝړ4i:l=oLuc)4QLCOLf&Ң-(%'43v3L^)'&rzT{ =q٦H?zT)UsJT𸧪Wڞ(R(LqE-7)3AAʌwf4f[L␚nhњ;4wZvi ޛO&ihGn/1\xl,h=( nQhGq@\=hG]}hL֗ҙΔ@M@f5iޗ֛J (J\P1杊P;R /jP)i wzQMJ(ԹS@t3!h74f3I3@ 3M I4-6\Eg\I3@ \sI@:Q@4ZQI3@\\IE—8BEғ)(qA@ 3Iژ&њ)@ RSրFiiZ'JZC3@!ԠS;ӇJ(◭4SL/zhw4wKړP!h'ޒ4B>\!isM/@ŢE-.i;qG404gޓQPIi3ځ4ff.Zh@v8)()p4\1 L vR1ӳ0Shӳ@ (iz1RQڐ E R@(P1ASiiKLbڊ@.hP!h4QERKIE(њCEJ3@ SMBhh&ihERfbHhŠ;RP!M&h&1sE%((@RfFi 4J()i;E((%QHhJ(ii((JZ(((Q@ E%-1-%-! KEJ( ZJ(h@Q@ EPEQEQI@ IFi3@44RQ@ Fi()(i( 3I3L!4(%&hi3Fi3@%PEQ@4њLi( HihRR@ M4iF¥4@*v12M=i7ƞE&1ڀtRbCޔw1KE1&3Nړ=)j Ԙ"-+F0#GJP)h @i+"ۚLrj\qI"(6 * ImG)@S@} 5>̊BS5!'7u&{Q\ gj3L!ܐ70)zp'4OhS 84?)iE 4 :Ҟњu/қ3@4h\{s@sFE?4fLKfh4њfih٣Mg483M?4R恋3I3@48Rih(ݨ'j(hv4fњZZnh-4_-?( Gn(ⓠ(4v⁁RȠLNh-&>^b7'|ӱI@ǵ)APif'ތihsN֘Ͻ.yfh Ͻ.ih٣4 FhsJO >H !fu!4gMZ:s@M.iќ vi3&itq{g (4R(;8斀B4fJњz;AɥP:@4^riE%?>Լr!0Pz!҃M)Zvj0i‚晚\i )Q@\{E:E74)2()3FhRQLBњ(Zm.hQE-4 )((&h4QJZZJLsIEQK@PQE-P1(I@ ^ޖ\IEPIKHi )1KIޘ (@%-74Z)3Fhz(AEP(Q@ EPQEQEQEQE 1EQE )hKERE(QҊ(KEREQE-Q@Q@Q@i( J(((((%%S&(4J(AES Q@@(E0(( sEcCE(EFT-h90E tPzCQLn0E QE-(Q@`Q(Aъ(b;S(1QEFiq` Q9@h1E Rzʊ(gW&(6QQϵP"DqH (bSP\b)-(4Q@ aڊ)3(2"x5p( <ikJ\sEcOhQD(&5L'SGEn8϶hESHE ) cPFjJ2(H (ɔb:)(( KE 1@2x(FxL (4PEqMh>8 E Bx6r9m8ϵS})2IE/4Q@1sKQ@OJPya@w QE\F~﹢;8J9&(*wEP1iÜQE R(cE.iQHbf)QQH4f(s@&(sFzQEњ(BȢu3(=8--PFhҗ4Q@hZZ(3Ev4Q@3PI(aE4(E4QEQ@MZZ(QE4tQ@!E 3ފ(cO($C@h cR4w=袘 :QEg`)IQE bhA@QE/hiAE;R(R(#4E '|QE1138E<Š) 袀P!M E (uttQ@֔QE g((aKEQHgQ@8PQHbڊ({f4Q@<(aJ9;(94Q@(ih((( Q@Q@%PQEQE%(ZQEZ( Bh((bMPf((-@QE%`CQE`zR(!);QE)favicon.ico000066400000000000000000005034361325274564300373520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current/lib/tpl/bootstrap3/images ( 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333444wMyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}+333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222yTyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyz0222333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333444l{6xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx{zC{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{O{Oy9777444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O444O000yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy444T3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333335550z,{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4{4|%66611141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114111411141114... loginhistory.html000066400000000000000000000107571325274564300337360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:loginhistory logoutforward.html000066400000000000000000000100701325274564300340660ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:logoutforward

    Logout forward

    Presentation

    Even if LL:NG can catch logout URL trough virtual host rules, you can have the need to forward a logout to other applications, to close their local sessions.

    LL::NG has a logout forward mechanism, that will add a step in logout process, to send logout requests (indeed, GET requests on application logout URL) inside hidden iframes.

    The logout request will be sent even if the user did not use the application.

    Configuration

    Go in Manager, General parameters » Advanced parameters » Logout forward and click on Add a key, then fill:

    • Key: application name
    • Value: application logout URL
    The request on logout URL will be sent after user is disconnected, so you should unprotect this URL if it is protected by an LL::NG Handler.
    logs.html000066400000000000000000000133221325274564300321370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:logs

    Table of Contents

    • Apache logging
    • Syslog
    • Override logging functions

    Logs

    Apache logging

    By default, LemonLDAP::NG uses Apache logs to store user actions and other messages:

    • Error log: all messages emitted by the program, depending on the configured log level
    • Access log: the issuer of each request is identified

    The log level can be set with Apache LogLevel parameter. It can be configured globally, or inside a virtual host.

    See http://httpd.apache.org/docs/current/mod/core.html#loglevel for more information.

    To configure the user identifier in access log, go in Manager, General Parameters > Logging > REMOTE_USER.

    You can also hide sensitive values in logs (session content can be displayed in logs in debug loglevel). Go in Manager, General Parameters > Logging > Hidden attributes and set a list of attributes to hide (space separated).

    Syslog

    LemonLDAP::NG can also use syslog (only for user actions).

    In Manager, set syslog facility in General Parameters > Logging > Syslog facility.

    The messages are stored with the levels :

    • info for user actions
    • notice for good authentications or external exchange (SAML, OpenID,…)
    • warn for failed authentications

    Override logging functions

    You can customize logs by redefining userNotice() and userError() methods, directly in lemonldap-ng.ini

    Example:

    [portal]
    userError = sub { my ($self, $message) = @_; ... }
    userNotice = sub { my ($self, $message) = @_; ... }
    managerprotection.html000066400000000000000000000174401325274564300347210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:managerprotection

    Manager protection

    When installing LL::NG, the Manager can only be accessed with the demo account dwho. This How To explains how change this default behavior to protect Manager with other rules.

    Apache based protection

    Apache based protection allows one to be independent from WebSSO, so Manager will always be reachable even if WebSSO configuration is corrupted.

    The configuration can be changed in etc/manager-apache2.conf, for example to restrict the IP allowed to access the Manager:

        <Directory /usr/local/lemonldap-ng/htdocs/manager/>
            Order deny,allow
            Deny from all
            Allow from 127.0.0.0/8 192.168.100.0/32
            Options +ExecCGI
        </Directory>

    But you will rather prefer to use an Apache authentication module, like for example LDAP authentication module:

        <Directory /usr/local/lemonldap-ng/htdocs/manager/>
            AuthzLDAPAuthoritative On
            AuthName "LL::NG Manager"
            AuthType Basic
            AuthBasicProvider ldap
            AuthLDAPBindDN "ou=websso,ou=applications,dc=example,dc=com"
            AuthLDAPBindPassword "secret"
            AuthLDAPURL ldap://localhost:389/ou=users,dc=example,dc=com???(objectClass=inetOrgPerson) TLS
            Require ldap-user coudot xguimard tchemineau
            Options +ExecCGI
        </Directory>
    You need to disable default Manager protection in lemonldap-ng.ini to rely only on Apache:
    [manager]
    ;protection = manager

    LL::NG based protection

    Before enabling Manager protection by LL::NG, you must have configured how users authenticate on Portal, and test that you can log in without difficulties. Else, you will lock access to Manager and will never access it anymore.

    By default, you will have a manager virtual host define in configuration. If not Go on Manager, and declare Manager as a new virtual host, for example manager.example.com. You can then set the access rule. No headers are needed.

    The default rule is:

    $uid eq "dwho"

    You have to change it to match your admin user (or use other conditions like group membership, or any other rule based on a session variable).

    Save the configuration and exit the Manager.

    The next time you will access Manager, it will be trough LL::NG.

    Enable protection on Manager, by editing lemonldap-ng.ini:

    [manager]
    protection = manager

    You can also adapt Apache access control:

        <Directory /usr/local/lemonldap-ng/htdocs/manager/>
            Order deny,allow
            Allow from all
            Options +ExecCGI
        </Directory>

    Restart Apache and try to log on Manager. You should be redirected to LL::NG Portal.

    You can then add the Manager as an application in the menu.

    If for an obscure reason, the WebSSO is not working and you want to access the Manager, remove the protection in lemonldap-ng.ini. Add an Apache access control to avoid other access.
    memcachedsessionbackend.html000066400000000000000000000123641325274564300360220ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:memcachedsessionbackend

    Memcached session backend

    Memcached can be used with LL::NG, but some features will not work since Memcached doesn't provide any parsing system:
    • Session expiration: sessions will never expire (server side)
    • Session explorer will not work
    • Session restrictions will not work

    To keep Memcached performance level and LL::NG features, you can replace Memcached by Redis using NoSQL session backend.

    Setup

    Install and launch a Memcached server.

    In the manager: set Apache::Session::Memcached in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

    Required parameters
    Name Comment Example
    Servers Memcached servers 10.0.0.1:20000 10.0.0.2:20000

    See Apache::Session::Memcached for optional parameters.

    mongodbconfbackend.html000066400000000000000000000161511325274564300350010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:mongodbconfbackend

    MongoDB configuration backends

    MongoDB is a NoSQL database that can be used both for storing configuration and sessions. You need to install Perl MongoDB module to be able to use this backend.

    See how to change configuration backend to change your configuration database.

    Configuration

    To use a MongoDB backend, configure your lemonldap-ng.ini file (section configuration) :

    • Choose MongoDB as type
    • Set dbName and collectionName parameters if different than default values (llConfDB and configuration)
    • Set host and if needed db_name username, password and ssl fields as follow.

    Example :

    [configuration]
    type = MongoDB
    dbName = llConfDB
    collectionName = configuration
    host = 127.0.0.1:27017
    ssl = 1
    ; authentication parameters
    db_name = admin
    user = lluser
    password = llpassword
    Optional parameters (see MongoDB::MongoClient man page)
    Name Comment Example
    db_name Admin database (default: admin) admin
    auth_mechanism Authentication mechanism PLAIN
    auth_mechanism_properties
    connect_timeout Connection timeout 10000
    ssl Boolean or hash ref (default: 0) 1
    username Username to use to connect lluser
    password Password llpassword

    Mini MongoDB howto

    Just some commands needed to create collection and user:

    $ mongo
    connecting to: test
    > use configuration
    switched to db configuration
    > db.createCollection("configuration")
    ...
    > db.addUser({user:"lluser",pwd:"llpassword",roles:["readWrite"]})
    ...
    > exit
    bye
    $
    mongodbsessionbackend.html000066400000000000000000000155411325274564300355410ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:mongodbsessionbackend

    MongoDB session backend

    Apache::Session::MongoDB is a faster shareable session backend

    Setup

    Install and launch a MongoDB server. Install Apache::Session::MongoDB Perl module (version ⩾ 0.15 required). You also need a recent version of Perl MongoDB client (version ⩾ 1.00 required).

    In the manager: set Apache::Session::MongoDB in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

    Optional parameters
    Name Comment Example
    host MongoDB server (default: 127.0.0.1:27017) 127.0.0.1:27017
    db_name Session database (default: sessions) llconfdb
    collection Collection (default: sessions) sessions
    auth_mechanism Authentication mechanism PLAIN
    auth_mechanism_properties
    connect_timeout Connection timeout 10000
    ssl Boolean or hash ref (default: 0) 1
    username Username to use to connect lluser
    password Password llpassword

    Security

    Restrict network access to the MongoDB server. For remote servers, you can use SOAP session backend in cunjunction to increase security for remote server that access through an unsecure network

    mrtg.html000066400000000000000000000075471325274564300321600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:mrtg

    MRTG monitoring

    The status page can be read by MRTG using the script lmng-mrtg that can be found in manager example directory.

    MRTG configuration example:

    ######################################################################
    # Multi Router Traffic Grapher -- Sample Configuration File
    ######################################################################
    # This file is for use with mrtg-2.5.4c
     
    # Global configuration
    WorkDir: /var/www/mrtg
    WriteExpires: Yes
     
    Title[^]: Traffic Analysis for
     
    # 128K leased line
    # ----------------
    #Title[leased]: a 128K leased line
    #PageTop[leased]: <H1>Our 128K link to the outside world</H1>
    #Target[leased]: 1:public@router.localnet
    #MaxBytes[leased]: 16000
    Target[test.example.com]: `/etc/mrtg/lmng-mrtg 172.16.1.2 https://test.example.com/status OK OK`
    Options[test.example.com]: nopercent, growright, nobanner, perminute
    PageTop[test.example.com]: <h1>Requests OK from test.example.com</h1>
    MaxBytes[test.example.com]: 1000000
    YLegend[test.example.com]: hits/minute
    ShortLegend[test.example.com]: &nbsp; hits/mn
    LegendO[test.example.com]: Hits:
    LegendI[test.example.com]: Hits:
    Legend2[test.example.com]: Hits per minute
    Legend4[test.example.com]: Hits max per minute
    Title[test.example.com]: Hits per minute
    WithPeak[test.example.com]: wmy
    mysqlminihowto.html000066400000000000000000000103651325274564300343020ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:mysqlminihowto

    Configure LemonLDAP::NG to use MySQL as main database

    LL::NG use 2 internal databases to store its configuration and sessions.

    Use MySQL for Lemonldap::NG configuration

    Steps:

    • Prepare the database and the LL::NG configuration file
    • Convert existing configuration
    • Restart all your Apache servers

    Use MySQL for Lemonldap::NG sessions

    Steps:

    • Choose one of the following:
      • Using Apache::Session::Browseable::MySQL (recommended for best performances)
      • Using Apache::Session::MySQL (if you choose this option, then read how to increase MySQL performances)
    nosqlsessionbackend.html000066400000000000000000000110741325274564300352450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:nosqlsessionbackend

    Redis session backend

    Apache::Session::Redis is the faster shareable session backend

    Setup

    Install and launch a Redis server. Install Apache::Session::Redis Perl module.

    In the manager: set Apache::Session::Redis in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

    Required parameters
    Name Comment Example
    server Redis server 127.0.0.1:6379

    Security

    Restrict network access to the redis server. For remote servers, you can use SOAP session backend in cunjunction to increase security for remote server that access through an unsecure network

    notifications.html000066400000000000000000000626601325274564300340550ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:notifications

    Table of Contents

    • Installation
      • Activation
      • Storage
        • File
        • DBI
        • LDAP
      • Wildcard
      • Custom XSLT file
    • Using notification system
      • Notification format
      • Create new notifications with notifications explorer
      • Notifications trough SOAP
        • Insertion example in Perl
        • Deletion example in Perl
      • Test notification

    Notifications system

    Since version 0.9.4, LemonLDAP::NG can be used to notify some messages to users: if a user has a message, the message will be displayed when he will access to the portal. If the message contains check boxes, the user has to check all of them else he can not access to the portal and get his session cookie.

    Since 1.1.0, a notification explorer is available in Manager, and notifications can be done for all users, with the possibility to display conditions. When the user accept the notification, the reference is stored in his persistent session.

    Installation

    Activation

    You just have to activate Notifications in the Manager (General Parameters > Advanced Parameters > Notifications > Activation) or in lemonldap-ng.ini:

    [portal]
    notification = 1

    Storage

    By default, notifications will be stored in the same database as configuration:

    • if you use “File” system and your “dirName” is set to /usr/local/lemonldap-ng/conf/, the notifications will be stored in /usr/local/lemonldap-ng/notifications/
    • if you use “CDBI” or “RDBI” system, the notifications will be stored in the same database as configuration and in a table called “notifications”.
    • if you use “LDAP” system, the notifications will be stored in the same directory as configuration and in a branch called “notifications”.

    You can change default parameters using the “notificationStorage” and “notificationStorageOptions” parameters with the same syntax as configuration storage parameters. To do this in Manager, go in General Parameters > Advanced Parameters > Notifications.

    File

    Parameters for File backend are the same as File configuration backend.

    You need to create yourself the directory and set write access to Apache user. For example:
    mkdir /usr/local/lemonldap-ng/notifications/
    chown www-data /usr/local/lemonldap-ng/notifications/
    The file name default separator is _, this can be a problem if you register notifications for users having _ in their login. You can change the separator with the fileNameSeparator option, and set another value, for example @.

    To summary available options:

    • dirName: directory where notifications are stored.
    • fileNameSeparator: file name separator.

    DBI

    Parameters for DBI backend are the same as DBI configuration backend.

    You have to create the table by yourself:
    CREATE TABLE notifications (
      DATE datetime NOT NULL,
      uid VARCHAR(255) NOT NULL,
      REF VARCHAR(255) NOT NULL,
      cond VARCHAR(255) DEFAULT NULL,
      xml longblob NOT NULL,
      done datetime DEFAULT NULL,
      PRIMARY KEY (DATE, uid,REF)
    )

    To summary available options:

    • dbiChain: DBI connection.
    • dbiUser: DBI user.
    • dbiPassword: DBI password.
    • table: Notifications table name.

    LDAP

    Parameters for LDAP backend are the same as LDAP configuration backend.

    You have to create the branch by yourself

    To summary available options:

    • ldapServer: LDAP URL.
    • ldapBindDN: LDAP user.
    • ldapBindPassword: LDAP password.
    • ldapConfBase: Notifications branch DN.

    Wildcard

    The notifications module uses a wildcard to manage notifications for all users. The default value of this wildcard is allusers, but you can change it if allusers is a known identifier in your system.

    To change it, go in General Parameters > Advanced Parameters > Notifications > Wildcard for all users, and set for example alluserscustom.

    Then creating a notification for alluserscustom will display the notification for all users.

    Custom XSLT file

    The transformation between notification XML content and HTML display is done with XSLT. The default XSLT file is in portal/skins/common/notification.xsl. You can create your own XSLT file and store in another place, for example /etc/lemonldap-ng. Then just configure the new XSLT file path in Manager, go in General Parameters > Advanced Parameters > Notifications > Custom XSLT file and set for example /etc/lemonldap-ng/notification.xsl.

    Using notification system

    Notification format

    Notifications are XML files containing:

    • <notification> element(s) :
      • Required attributes:
        • date: creation date (format YYYY-MM-DD)
        • ref: a reference that can be used later to know what has been notified and when
        • uid: the user login (it must correspond to the attribute set in whatToTrace parameter, uid by default), or the wildcard string (by default: allusers) if the notification should be displayed for every user.
      • Optional attributes:
        • condition: condition to display the notification, can use all session variables.
      • Sub elements:
        • <title>: title to display: will be inserted in HTML page enclosed in <h2 class=“notifText”>…</h2>
        • <subtitle>: subtitle to display: will be inserted in HTML page enclosed in <h2 class=“notifText”>…</h2>
        • <text>: paragraph to display: will be inserted in HTML page enclosed in <p class=“notifText”>…</p>
        • <check>: paragraph to display with a checkbox: will be inserted in HTML page enclosed in <p class=“notifCheck”><input type=“checkbox” />…</p>
    All other elements will be removed including HTML elements like <b>.
    One notification XML document can contain several notifications messages.

    Example :

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <root>
    <notification uid="foo.bar" date="2009-01-27" reference="ABC">
    <title>You have new authorizations</title>
    <subtitle>Application 1</subtitle>
    <text>You have been granted to access to appli-1</text>
    <subtitle>Application 2</subtitle>
    <text>You have been granted to access to appli-2</text>
    <subtitle>Acceptation</subtitle>
    <check>I know that I can access to appli-1 </check>
    <check>I know that I can access to appli-2 </check>
    </notification>
    <notification uid="allusers" date="2009-01-27" reference="disclaimer" condition="$ipAddr =~ /^192/">
    <title>This is your first access on this system</title>
    <text>Be a nice user and do not break it please.</text>
    <check>Of course I am not evil!</check>
    </notification>
    </root>

    Create new notifications with notifications explorer

    In Manager, click on Notifications and then on the Create button.

    Then fill all inputs to create the notification. Only the condition is not mandatory.

    When all is ok, click on Save.

    Notifications trough SOAP

    New notifications can be insert using SOAP request (described in the WSDL file generated by buildPortalWSDL tool). To activate SOAP on the portal:

    • Enable SOAP in General parameters » Advanced parameters » SOAP
    • Enable Notifications SOAP service in Apache configuration:
    # SOAP functions for notification insertion (disabled by default)
    <Location /index.pl/notification>
        Order deny,allow
        Deny from all
        Allow from 192.168.2.0/24
    </Location>

    Insertion example in Perl

    #!/usr/bin/perl
     
    use SOAP::Lite;
    use utf8;
     
    my $lite = SOAP::Lite
            ->uri('urn:Lemonldap::NG::Common::CGI::SOAPService')
            ->proxy('http://auth.example.com/index.pl/notification');
     
     
    $r = $lite->newNotification(
    '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <root>
    <notification uid="foo.bar" date="2009-01-27" reference="ABC">
    <text> You have been granted to access to appli-1 </text>
    <text> You have been granted to access to appli-2 </text>
    <check> I know that I can acces to appli-1 </check>
    <check> I know that I can acces to appli-2 </check>
    </notification>
    </root>
    ');
     
    if ( $r->fault ) {
        print STDERR "SOAP Error: " . $r->fault->{faultstring};
    }
    else {
        my $res = $r->result();
        print "$res notification(s) have been inserted\n";
    }

    You can also delete some notifications with SOAP, once SOAP is activated:

    Deletion example in Perl

    #!/usr/bin/perl
     
    use SOAP::Lite;
    use utf8;
     
    my $lite = SOAP::Lite
            ->uri('urn:Lemonldap::NG::Common::CGI::SOAPService')
            ->proxy('http://auth.example.com/index.pl/notification');
     
     
    $r = $lite->deleteNotification('foo.bar', 'ABC');
     
    if ( $r->fault ) {
        print STDERR "SOAP Error: " . $r->fault->{faultstring};
    }
    else {
        my $res = $r->result();
        print "$res notification(s) have been deleted\n";
    }

    Test notification

    You've simply to insert a notification and connect to the portal using the same UID. You will be prompted.

    Try also to create a global notification (to the uid “allusers”), and connect with any user, the message will be prompted.

    openidconnectclaims.html000066400000000000000000000135451325274564300352230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:openidconnectclaims

    OpenID Connect claims

    Claim name Type Example of corresponding LDAP attribute
    sub string uid
    name string cn
    given_name string givenName
    family_name string sn
    middle_name string
    nickname string
    preferred_username string displayName
    profile string labeledURI
    picture string
    website string
    email string mail
    email_verified boolean
    gender string
    birthdate string
    zoneinfo string
    locale string preferredLanguage
    phone_number string telephoneNumber
    phone_number_verified boolean
    updated_at string
    formatted string registeredAddress
    street_address string street
    locality string l
    region string st
    postal_code string postalCode
    country string co
    openidconnectservice.html000066400000000000000000000242131325274564300354050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:openidconnectservice

    Table of Contents

    • Rewrite rules
      • Apache
      • Nginx
    • Service configuration
      • Issuer identifier
      • End points
      • Authentication context
      • Security
      • Sessions
    • Key rotation script
    • Session management

    OpenID Connect service configuration

    Rewrite rules

    Apache

    Be sure that mod_rewrite is installed and that OpenID Connect rewrite rules are activated in Apache portal configuration:

        # OpenID Connect Issuer
        <IfModule mod_rewrite.c>
            RewriteEngine On
            #RewriteCond %{HTTP:Authorization} .
            #RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
            RewriteRule ^/oauth2/.* /index.pl
            RewriteRule ^/.well-known/openid-configuration$ /openid-configuration.pl
        </IfModule>
    You need to uncomment rewrite rule on Authorization header if you only have CGI enabled in your Apache server.

    Nginx

    Be sure that OpenID Connect rewrite rules are activated Nginx portal configuration:

      # OpenID Connect Issuer
      rewrite ^/oauth2/.* /index.pl last;
      rewrite ^/.well-known/openid-configuration$ /openid-configuration.pl last;

    Service configuration

    Go in Manager and click on OpenID Connect Service node.

    Issuer identifier

    Set the issuer identifier, which should be the portal URL.

    For example: http://auth.example.com

    End points

    Name of different OpenID Connect endpoints. You can keep the default values unless you have a specific need to change them.

    • Authorization
    • Token
    • User Info
    • JWKS
    • Registration
    • End of session
    • Check Session
    The end points are published inside JSON metadata.

    Authentication context

    You can associate here an authentication context to an authentication level.

    Security

    • Keys : define public/private key pair to do asymmetric signature
    • Signing Key ID: ID of signing key
    • Dynamic Registration: Set to 1 to allow clients to register themselves. This may be a security risk as this will create a new configuration in the backend per registration request. You can limit this by protecting in the WebServer the registration end point with an authentication module, and give the credentials to clients.
    • Authorization Code flow: Set to 1 to allow Authorization Code flow
    • Implicit flow: Set to 1 to allow Implicit flow
    • Hybrid flow: Set to 1 to allow Hybrid flow

    Sessions

    It is recommended to use a separate sessions storage for OpenID Connect sessions, else they will stored in the main sessions storage.

    Key rotation script

    OpenID Connect specification let the possibility to rotate keys to improve security. LL::NG provide a script to do this, that should be put in a cronjob.

    The script is /usr/share/lemonldap-ng/bin/rotateOidcKeys. It can be run for example each week:

    5 5 * * 6 www-data /usr/share/lemonldap-ng/bin/rotateOidcKeys
    Set the correct Apache user, else generated configuration will not be readable by LL::NG.

    Session management

    LL::NG implements the change notification as defined here: http://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification

    A changed state will be sent if the user is disconnected from LL::NG portal (or has destroyed its SSO cookie). Else the unchanged state will be returned.

    To work, the LL::NG cookie must not be protected against javascript (httpOnly option should be set to 0).
    parameterlist.html000066400000000000000000001316341325274564300340560ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:parameterlist

    Parameter list

    Click on a column header to sort table. The attribute key name can be used directly in lemonldap-ng.ini or in Perl scripts to override configuration parameters (see configuration location).

    Main parameters

    Full name Key name Portal Handler Manager
    Activate auto accept timer activeTimer ✔
    Apache authentication level apacheAuthnLevel ✔
    Choice modules authChoiceModules ✔
    Choice URL parameter authChoiceParam ✔
    Authentication backend authentication ✔
    LDAP authentication search filter AuthLDAPFilter ✔
    CAS authentication level CAS_authnLevel ✔
    CAS CA file CAS_CAFile ✔
    CAS force gateway authentication CAS_gateway ✔
    CAS PGT temporary file CAS_pgtFile ✔
    CAS proxied services CAS_proxiedServices ✔
    CAS force authentication renewal CAS_renew ✔
    CAS server URL CAS_url ✔
    CAS attribute for login casAttr ✔
    CAS access control policy casAccessControlPolicy ✔
    CAS Session backend casStorage ✔
    CAS Session backend options casStorageOptions ✔
    CDA activation cda ✔ ✔
    Configuration backend configStorage ✔ ✔ ✔
    Cookie expiration cookieExpiration ✔ ✔
    Name of the cookie cookieName ✔ ✔
    Custom functions customFunctions ✔ ✔ ✔
    Custom SOAP Services CustomSOAPServices ✔
    DBI Connection chain dbiAuthChain ✔
    DBI Login column dbiAuthLoginCol ✔
    DBI authentication level dbiAuthnLevel ✔
    DBI Connection password dbiAuthPassword ✔
    DBI Password column dbiAuthPasswordCol ✔
    DBI Password hash dbiAuthPasswordHash ✔
    DBI Authentication table dbiAuthTable ✔
    DBI Connection user dbiAuthUser ✔
    DBI Mail column dbiPasswordMailCol ✔
    DBI UserDB connection chain dbiUserChain ✔
    DBI UserDB connection password dbiUserPassword ✔
    DBI UserDB table dbiUserTable ✔
    DBI UserDB connection user dbiUserUser ✔
    Main DNS domain domain ✔ ✔
    Attributes exported in SOAP exportedAttr ✔
    Headers sent exportedHeaders ✔
    Attributes from user backend exportedVars ✔
    Session backend globalStorage ✔ ✔
    Session backend options globalStorageOptions ✔ ✔
    Rule for session granting grantSessionRule ✔
    Local groups groups ✔
    Force HTTPS in redirection https ✔
    LDAP authentication level ldapAuthnLevel ✔
    LDAP search base ldapBase ✔
    LDAP change password as user ldapChangePasswordAsUser ✔
    LDAP main search filter LDAPFilter ✔
    LDAP groups member attribute ldapGroupAttributeName ✔
    LDAP group link attribute name ldapGroupAttributeNameGroup ✔
    LDAP groups name attribute ldapGroupAttributeNameSearch ✔
    LDAP groups member link value ldapGroupAttributeNameUser ✔
    LDAP groups base ldapGroupBase ✔
    LDAP groups objectClass ldapGroupObjectClass ✔
    LDAP activate recursive groups ldapGroupRecursive ✔
    LDAP Port ldapPort ✔
    LDAP password policy control ldapPpolicyControl ✔
    LDAP password encoding ldapPwdEnc ✔
    LDAP binary attributes ldapRaw ✔
    LDAP server or Net::LDAP connexion string ldapServer ✔
    LDAP extended SetPassword modify ldapSetPassword ✔
    LDAP timeout ldapTimeout ✔
    LDAP version ldapVersion ✔
    LDAP modify password reset attribute ldapUsePasswordResetAttribute ✔
    LDAP password reset attribute name ldapPasswordResetAttribute ✔
    LDAP password reset attribute true value ldapPasswordResetAttributeValue ✔
    Cache backend localStorage ✔ ✔ ✔
    Local cache localStorage ✔ ✔ ✔
    Cache backend options localStorageOptions ✔ ✔ ✔
    Local cache parameters localStorageOptions ✔ ✔ ✔
    Access rules locationRules ✔
    Macros macros ✔
    Body for password mail mailBody ✔
    Body for confirmation mail mailConfirmBody ✔
    Subject for confirmation mail mailConfirmSubject ✔
    Mail From address mailFrom ✔
    Reply address mailReplyTo ✔
    Mail charset mailCharset ✔
    LDAP mail search filter mailLDAPFilter ✔
    Subject for password mail mailSubject ✔
    Mail reset request timeout mailTimeout ✔
    URL for mail reset mailUrl ✔
    Session key containing email address mailSessionKey ✔
    LDAP Bind DN managerDn ✔
    LDAP Bind Password managerPassword ✔
    Multi overridden parameters multi ✔
    Multi values separator multiValuesSeparator ✔ ✔ ✔
    Notification activation notification ✔ ✔
    Notification backend notificationStorage ✔ ✔
    Notification backend options notificationStorageOptions ✔ ✔
    Notification uid for all users notificationWildcard ✔ ✔
    Display deleted sessions notifyDeleted ✔
    Display other sessions notifyOther ✔
    Null authentication level nullAuthnLevel ✔
    OpenID authentication level openIdAuthnLevel ✔
    OpenID allowed domains openIdIDPList ✔
    OpenID secret token openIdSecret ✔
    Password backend passwordDB ✔
    Force port in redirection port ✔
    Portal URL portal ✔ ✔
    Anti frame protection portalAntiFrame ✔
    Display applications list portalDisplayAppslist ✔
    Display change password module portalDisplayChangePassword ✔
    Display logout module portalDisplayLogout ✔
    Display reset password form portalDisplayResetPassword ✔
    Open links in new window portalOpenLinkInNewWindow ✔
    Ping interval portalPingInterval ✔
    Require old password (change) portalRequireOldPassword ✔
    Skin name portalSkin ✔
    User name session field portalUserAttr ✔
    Protection scheme protection ✔ ✔
    Regular expression for random password randomPasswordRegexp ✔
    Delay between check of local configuration reloadTime ✔
    Remote cookie name remoteCookieName ✔
    Proxy cookie name remoteCookieName ✔
    Remote Session backend remoteGlobalStorage ✔
    Remote Session backend options remoteGlobalStorageOptions ✔
    Remote portal remotePortal ✔
    SAML Session backend samlStorage ✔
    SAML Session backend options samlStorageOptions ✔
    Cookie security securedCookie ✔ ✔
    Delete other session if IP differs singleIP ✔
    Delete other session singleSession ✔
    Do not allow several users for 1 IP singleUserByIP ✔
    SMTP server SMTPServer ✔
    SMTP user SMTPAuthUser ✔
    SMTP password SMTPAuthPass ✔
    SOAP activation Soap ✔
    Proxy portal URL soapAuthService ✔
    Proxy session SOAP end point soapSessionService ✔
    SSL authentication level SSLAuthnLevel ✔
    SSL user field in certificate SSLVar ✔
    Status module activation status ✔
    Store password in session storePassword ✔
    Sympa mail session key sympaMailKey ✔
    Sympa shared secret sympaSecret ✔
    Syslog facility syslog ✔
    Session lifetime for cronjob timeout ✔
    Trusted domains trustedDomains ✔
    Twitter application name twitterAppName ✔
    Twitter authentication level twitterAuthnLevel ✔
    Twitter application key twitterKey ✔
    Twitter application secret twitterSecret ✔
    User backend userDB ✔
    Use redirect on error useRedirectOnError ✔
    Use Safe Jail useSafeJail ✔ ✔
    DBI Pivot from user table userPivot ✔
    Use XForwardedFor for IP useXForwardedForIP ✔
    Data to store as REMOTE_USER (used also in Apache logs) whatToTrace ✔ ✔
    Zimbra account session key zimbraAccountKey ✔
    Zimbra account type zimbraBy ✔
    Zimbra preauthentication key zimbraPreAuthKey ✔
    Zimbra local SSO URL pattern zimbraSsoUrl ✔
    Zimbra preauthentication URL zimbraUrl ✔
    Yubikey client ID yubikeyClientID ✔
    Yubikey secret key yubikeySecretKey ✔
    Yubikey public ID size yubikeyPublicIDSize ✔
    Yubikey authentication level yubikeyAuthnLevel ✔
    Hide old password in reset form hideOldPassword ✔
    Secure Token allow requests in error secureTokenAllowOnError ✔
    Secure Token attribute secureTokenAttribute ✔
    Secure Token expiration secureTokenExpiration ✔
    Secure Token header secureTokenHeader ✔
    Secure Token Memcached servers secureTokenMemcachedServers ✔
    Secure Token protected URLs secureTokenUrls ✔
    Cookie Javascript protection httpOnly ✔ ✔
    Send mail on password change mailOnPasswordChange ✔
    Radius authentication level radiusAuthnLevel ✔
    Radius server radiusServer ✔
    Radius secret radiusSecret ✔
    Check XSS Attacks checkXSS ✔
    Maintenance mode maintenance ✔
    Persistent Session backend persistentStorage ✔
    Persistent Session backend options persistentStorageOptions ✔

    Configuration backend parameters

    Full name Key name Configuration backend
    DBI connection string dbiChain CDBI / RDBI
    DBI user dbiUser
    DBI password dbiPassword
    DBI table name dbiTable
    Storage directory dirName File
    LDAP server ldapServer LDAP
    LDAP port ldapPort
    LDAP base ldapConfBase
    LDAP bind dn ldapBindDN
    LDAP bind password ldapBindPassword
    Certificate authorities file caFile
    Certificate authorities directory caPath
    SOAP server location (URL) proxy SOAP
    LWP::UserAgent parameters proxyOptions
    passwordstore.html000066400000000000000000000107551325274564300341210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:passwordstore

    Table of Contents

    • Presentation
    • Configuration
    • Usage

    Store user password in session

    Presentation

    Password is not a common attribute. Indeed, in most of the cases, it is not stored in clear text in the backend (LDAP or database).

    So, to keep user password in session, you cannot just export the password variable in session. To bypass this, LL::NG can remember what password was given by user on authentication phase.

    • As this may be a security hole, password store in session is not activated by default
    • This mechanism can only work with authentication backends using a login/password form (LDAP, DBI, …)

    Configuration

    Go in Manager, General Parameters » Sessions » Store user password in session data and set to On.

    Usage

    User password is now available in $_password variable. For example, to send it in an header:

    Auth-Password => $_password
    For security reasons, the password is not shown in sessions explorer.
    performances.html000066400000000000000000000542571325274564300336730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:performances

    Table of Contents

    • Global performance
    • Handler performance
      • Macros and groups
      • Local macros
    • Portal performances
      • General performances
      • Configuration access
      • Starting performances
      • Apache::Session performances
        • Replace MySQL by Apache::Session::Flex
        • Use Apache::Session::Browseable
      • LDAP performances
    • Manager performances
      • Disable unused modules
      • Use static HTML files

    Performances

    LemonLDAP::NG is designed to be very performant. In particular, it use Apache2 threads capabilities so to optimize performances, prefer using mpm-worker.

    Global performance

    On Linux, by default, there is no DNS cache and LemonLDAP::NG portal request DNS at every connexions on LDAP or DB. Under heavy loads, that can generated hundred of DNS queries and many errors on LDAP connexions (timed out) from IO::Socket.

    To bypass this, you can:

    • Use IP in configuration to avoid DNS resolution
    • Install a DNS cache like nscd, netmask or bind

    Handler performance

    Handlers check rights and calculate headers for each HTTP hit. So to improve performances, avoid too complex rules by using the macro or the groups or local macros.

    Macros and groups

    Macros and groups are calculated during authentication process by the portal:

    • macros are used to extend (or rewrite) exported variables. A macro is stored as attributes: it can contain boolean results or any string
    • groups are stored as space-separated strings in the special attribute “groups”: it contains the names of groups whose rules were returned true for the current user
    • You can also get groups in $hGroups which is a Hash Reference of this form:
    $hGroups = {
              'group3' => {
                            'description' => [
                                               'Service 3',
                                               'Service 3 TEST'
                                             ],
                            'cn' => [
                                      'group3'
                                    ],
                            'name' => 'group3'
                          },
              'admin' => {
                           'name' => 'admin'
                         }
            }

    Example for macros:

    # boolean macro
    isAdmin -> $uid eq 'foo' or $uid eq 'bar'
    # other macro 
    displayName -> $givenName." ".$surName
     
    # Use a boolean macro in a rule
    ^/admin -> $isAdmin
    # Use a string macro in a HTTP header
    Display-Name -> $displayName

    Example for groups:

    # group
    admin -> $uid eq 'foo' or $uid eq 'bar'
     
    # Use a group in a rule
    ^/admin -> $groups =~ /\badmin\b/
     
    # Or with hGroups
    ^/admin -> defined $hGroups->{'admin'}
    Groups are computed after macros, so a group rule may involve a macro value.
    Macros and groups are computed in alphanumeric order, that is, in the order they are displayed in the manager. For example, macro “macro1” will be computed before macro “macro2”: so, expression of macro2 may involve value of macro1. As same for groups: a group rule may involve another, previously computed group.

    Local macros

    Macros and groups are stored in session database. Local macros is a special feature of handler that permits one to have macros useable localy only. Those macros are calculated only at the first usage and stored in the local session cache (only for this server) and only if the user access to the related applications. This avoid to have to many datas stored.

    # rule
    admin -> $admin ||= ($uid eq 'foo' or $uid eq 'bar')
    # header
    Display-Name -> $displayName ||= $givenName." ".$surName
    Note that this feature is interesting only for the Lemonldap::NG systems protecting a high number of applications

    Portal performances

    General performances

    The portal is the biggest component of Lemonldap::NG. It is recommended to use ModPerl::Registry instead of using cgi-script as described in Apache configuration file example (portal-apache2.conf):

    <Files *.pl>
        SetHandler perl-script
        PerlResponseHandler ModPerl::Registry
    </Files>

    You can also use a FastCGI server using index.fcgi given in portal examples.

    In production environment for network performance, prefer using minified versions of javascript and css libs: use make install PROD=yes. This is done by default in RPM/DEB packages.

    Configuration access

    If you set useLocalConf to 1 in lemonldap-ng.ini (section [Portal]), the portal will use only a cached configuration. To refresh it, you have to set an handler on the same server to use the refresh mechanism or to restart the server after each configuration change.

    Starting performances

    To make the portal start faster when the server is relaunched, add those lines in Apache configuration file (as described in portal-apache2.conf):

    <Perl>
        require Lemonldap::NG::Portal::SharedConf;
        Lemonldap::NG::Portal::SharedConf->compile(
            qw(delete header cache read_from_client cookie redirect unescapeHTML));
        # Uncomment this line if you use Lemonldap::NG menu
        require Lemonldap::NG::Portal::Menu;
        # Uncomment this line if you use portal SOAP capabilities
        require SOAP::Lite;
    </Perl>

    Apache::Session performances

    Lemonldap::NG handlers use a local cache to store sessions (for 10 minutes). So Apache::Session module is not a problem for handlers. It can be a brake for the portal:

    1. When you use the multiple sessions restriction parameters, sessions are parsed for each authentication unless you use an Apache::Session::Browseable module.
    2. Since MySQL does not have always transaction feature, Apache::Session::MySQL has been designed to use MySQL locks. Since MySQL performances are very bad using this, if you want to store sessions in a MySQL database, prefer one of the following

    Replace MySQL by Apache::Session::Flex

    In “Apache::Session module” field, set “Apache::Session::Flex” and use the following parameters:

    Store      -> MySQL
    Lock       -> Null
    Generate   -> MD5
    Serialize  -> Storable
    DataSource -> dbi:mysql:sessions;host=...
    UserName   -> ...
    Password   -> ...
    Since version 1.90 of Apache::Session, you can use Apache::Session::MySQL::NoLock instead

    Use Apache::Session::Browseable

    Apache::Session::Browseable is a wrapper for other Apache::Session modules that add the capability to manage indexes. To use it (with MySQL for example), choose “Apache::Session::Browseable::MySQL” as “Apache::Session module” and use the following parameters:

    DataSource -> dbi:mysql:sessions;host=...
    UserName   -> user
    Password   -> password
    Index      -> ipAddr uid

    Note that Apache::Session::Browseable::MySQL doesn't use MySQL locks.

    A Apache::Session::Browseable::Redis has been created, it is the faster (except for session explorer, defeated by Apache::Session::Browseable::DBI/LDAP >= 1.0)
    Some Apache::Session module are not fully usable by Lemonldap::NG such as Apache::Session::Memcached since this modules do not offer capability to browse sessions. They does not allow one to use sessions explorer neither manage one-off sessions.

    LDAP performances

    LDAP server can be a brake when you use LDAP groups recovery. You can avoid this by setting “memberOf” fields in your LDAP scheme:

    dn: uid=foo,dmdName=people,dc=example,dc=com
    ...
    memberOf: cn=admin,dmdName=groups,dc=example,dc=com
    memberOf: cn=su,dmdName=groups,dc=example,dc=com

    So instead of using LDAP groups recovery, you just have to store “memberOf” field in your exported variables. With OpenLDAP, you can use the memberof overlay to do it automatically.

    Don't forget to create an index on the field used to find users (uid by default)
    To avoid having group dn stored in sessions datas, you can use a macro to rewrite memberOf:
    • Exported variables
    ldapgroups -> memberOf

    For now, ldapgroups contains “cn=admin,dmdName=groups,dc=example,dc=com cn=su,dmdName=groups,dc=example,dc=com”

    • A little macro:
    ldapgroups -> join(" ",($ldapgroups =~ /cn=(.*?),/g))

    Now ldapgroups contains “admin su”

    Manager performances

    Disable unused modules

    In lemonldap-ng.ini, set only modules that you will use. By default, configuration, sessions explorer and notifications explorer are enabled. Example:

    [manager]
    enabledModules = conf, sessions

    Use static HTML files

    Once Manager is installed, browse enabled modules (configuration, sessions, notifications) and save the web pages respectively under manager.html, sessions.html and notifications.html in the DocumentRoot directory. Then replace this in Manager file of Apache configuration:

    RewriteRule "^/$" "/psgi/manager-server.fcgi" [PT]
    # DirectoryIndex manager.html
    # RewriteCond "%{REQUEST_FILENAME}" "!\.html$"
    RewriteCond "%{REQUEST_FILENAME}" "!^/(?:static|doc|fr-doc|lib).*"
    RewriteRule "^/(.+)$" "/psgi/manager-server.fcgi/$1" [PT]

    by:

    # RewriteRule "^/$" "/psgi/manager-server.fcgi" [PT]
    DirectoryIndex manager.html
    RewriteCond "%{REQUEST_FILENAME}" "!\.html$"
    RewriteCond "%{REQUEST_FILENAME}" "!^/(?:static|doc|fr-doc|lib).*"
    RewriteRule "^/(.+)$" "/psgi/manager-server.fcgi/$1" [PT]

    So manager HTML templates will be no more generated by Perl but directly given by the web server.

    portal.html000066400000000000000000000215561325274564300325040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:portal

    The portal

    The portal is the main component of LL::NG. It provides many features:

    • Authentication service of course
      • Web based for normal users:
        • using own database (LDAP, SQL, …)
        • using Apache authentication system (used for SSL, Kerberos, HTTP basic authentication, …)
        • using external identity provider (SAML, OpenID, CAS, Twitter, other LL::NG system, …)
        • all together (based on user choice, rules, …)
      • SOAP based for client-server software, specific development, …
    • Identity provider: LL::NG is able to provide identity service using:
      • SAML
      • OpenID
      • CAS
    • Identity provider proxy: LL::NG can be used as proxy translator between systems talking SAML, OpenID, CAS, …
    • Internal SOAP server used by SOAP configuration backend and usable for specific development (see SOAP services for more)
    • Interactive management of user passwords:
      • Password change form (in menu)
      • Self service reset (send a mail to the user with a to change the password)
      • Force password change with LDAP password policy password reset flag
    • Application menu: display authorized applications in categories
    • Notifications: prompt users with a message if found in the notification database

    Functioning

    LL::NG portal is a modular component. It needs 4 modules to work:

    • Authentication: how check user credentials
    • User database: where collect user information
    • Password database: where change password
    • Identity provider: how forward user identity
    Each module can be disabled using the Null backend.

    Kinematics

    1. Check if URL asked is valid
    2. Check if user is already authenticated
      • If not authenticated (or authentication is forced) try to find it (userDB module) and to authenticate it (auth module), create session, calculate groups and macros and store them. In 1.3, LL::NG have a captcha feature which is used in this case.
    3. Modify password if asked
    4. Provides identity if asked
    5. Build cookie(s)
    6. Redirect user to the asked URL or display menu
    See also general kinematics presentation.
    portalcustom.html000066400000000000000000000342641325274564300337370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:portalcustom

    Table of Contents

    • Skin
      • Default skin
      • Skin background
      • Skin rules
      • Skin files
      • Skin customization
      • Messages
      • Template parameters
    • Buttons
    • Password management
    • Other parameters

    Portal customization

    The portal is the visible part of LemonLDAP::NG, all user interactions are displayed on it.

    Skin

    LemonLDAP::NG is shipped with 4 skins:

    • pastel
    • impact
    • dark
    • bootstrap
    It is recommended to use bootstrap skin, as other may be deprecated in the future.

    But you can make your own, see Skin customization below.

    Default skin

    You can change the default skin in Manager: General Parameters > Portal > Customization > Default skin.

    Select the Custom skin, then set the name of the skin you want to use in the input below.

    Skin background

    Go in General Parameters > Portal > Customization > Skin background. You can define a background by selecting one of the available image. Use None to use the default skin background configuration.

    Skin rules

    You might want to display different skin depending on the URL that was called before being redirected to the portal, or the IP address of the user.

    To achieve this, you can create a rule in the Manager: select General Parameters > Portal > Customization > Skin display rules on click on “New key”. Then fill the two fields;

    • Rule: a Perl expression (you can use %ENV hash to get environment variables, or $_url to get URL called before redirection, or $ipAddr to use user IP address). If the rule evaluation is true, the corresponding skin is applied.
    • Skin: the name of the skin to use.

    Skin files

    A skin is composed of different files:

    • .tpl: Perl HTML::Template files, for HTML content
    • .css: CSS (styles)
    • .js: Javascript
    • images and other media files

    A skin will often refer to the common skin, which is not a real skin, but shared skin objects (like scripts, images and CSS).

    Skin customization

    If you modify directly the skin files, your modifications will certainly be erased on the next upgrade. The best is to create your own skin, based on an existing skin.

    Here we explain how to create a new skin, named myskin, from the bootstrap skin.

    cd /usr/share/lemonldap-ng/portal-skins/
    mkdir myskin
    cd myskin/
    cp -a ../bootstrap/fonts/ .
    cp -a ../bootstrap/js/ .
    cp -a ../bootstrap/css/ .
    mkdir images

    Then create symbolic links on template files, as you might not want to rewrite all HTML code (else, do as you want).

    ln -s ../bootstrap/*.tpl .

    We include some template files that can be customized:

    • customhead.tpl : HTML header markups (like CSS, js inclusion)
    • customheader.tpl : HTML code int the header div
    • customfooter.tpm : HTML code in the footer div

    To use custom files, delete links and copy them into your skin folder:

    rm -f custom*
    cp ../bootstrap/custom* .

    Create a symlink in main skin directory:

    ln -s /usr/share/lemonldap-ng/portal-skins/myskin /var/lib/lemonldap-ng/portal/skins/

    Then you only have to edit JS/CSS and add your media to myskin/images. Put all custom HTML code in the custom template files.

    To configure your new skin in Manager, select the custom skin, and enter your skin name in the configuration field.

    Messages

    Messages are defined in source code. If they really do not please you, override them! You just need to know the ID of the message (look at Portal/Simple.pm) and then add to lemonldap-ng.ini:

    [portal]
     
    # Custom error messages
    error_0 = Big brother is watching you, authenticated user
     
    # Custom standard messages
    msg_22 = Your last connections
    You can alse define messages in several languages:
    [portal]
    error_en_0 = Big brother is watching you, authenticated user
    error_fr_0 = Souriez vous êtes surveillés !

    Template parameters

    Template parameters are defined in source code. If you need to add a template parameter for your customization, then add to lemonldap-ng.ini:

    [portal]
     
    # Custom template parameters
    tpl_myparam = world

    Then you will be able to use it in your template like this:

    Hello <TMPL_VAR NAME="myparam">!

    Buttons

    This node allows one to enable/disable buttons on the login page:

    • Check last logins: displays a checkbox on login form, allowing user to check his login history right after opening session
    • Reset password: display a link to reset your password page (for password based authentication backends)
    • Register: display a link to register page (for password based authentication backends)

    Password management

    • Require old password: used only in the password changing module of the menu, will check the old password before updating it
    • Hide old password: used only if the password need to be reset by the user (LDAP password policy), will hide the old password input
    • Send mail on password change: send a mail if the password is changed from the Menu, or from forced password reset (LDAP password policy)

    Other parameters

    • User attribute: which session attribute will be used to display Connected as in the menu
    • New window: open menu links in new window
    • Anti iframe protection: will kill parent frames to avoid some well known attacks
    • Ping interval: Number of milliseconds between each ping (Ajax request) on the portal menu. Set to 0 to dismiss checks.
    • Show error on expired session: Display the error “Session expired”, which stops the authentication process. This is enabled by default but can be disabled to prevent transparent authentication (like SSL or Kerberos) to be stopped.
    • Show error on mail not found: Display error if provided mail is not found in password reset by mail process. Disabled by default to prevent mail enumeration from this page.
    portalmenu.html000066400000000000000000000140321325274564300333600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:portalmenu

    Portal menu

    The menu is displayed if authentication is successful.

    Menu modules

    LemonLDAP::NG portal menu has 4 modules:

    • Application list: display categories and applications allowed for the user
    • Password change: form to change the password
    • Login history: display user's last logins and last failed logins
    • Logout: logout button

    Each module can be activated trough a rule, using user session information. These rules can be set trough Manager: General Parameters > Portal > Menu > Modules activation.

    You can use 0 or 1 to disable/enable the module, or use a more complex rule. For example, to display the password change form only for user authenticated trough LDAP or DBI:

    $_auth eq LDAP or $_auth eq DBI

    Categories and applications

    Configuring the virtual hosts is not sufficient to display an application in the menu. Indeed, a virtual host can contain several applications (http://vhost.example.com/appli1, http://vhost.example.com/appli2).

    In Manager, you can configure categories and applications in General Parameters > Portal > Menu > Categories and applications.

    Application parameters:

    • Name: display text
    • Address: URL of application
    • Description
    • Logo: file name to use as logo
    • Display:
      • auto: display only if the user can access it
      • on: always display
      • off: never display
    Categories and applications are displayed in alphabetical order.

    The chosen logo file must be in portal applications logos directory (portal/skins/common/apps/). You can set a custom logo by setting the logo file name directly in the field, and copy the logo file in portal applications logos directory
    prereq.html000066400000000000000000000306521325274564300324760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:prereq

    Table of Contents

    • Web Server
    • Perl
      • Core
      • SAML2
      • CAS (authentication module)
      • OpenID
      • Twitter
      • POD unit tests
      • SMTP / Reset password by mail
    • Other
    • Install dependencies on your system
      • APT-GET
      • YUM

    Prerequisites and dependencies

    Web Server

    To use LemonLDAP::NG, you have the choice of the Web Server :

    • Apache 2 with mod_perl
    • Nginx with fastcgi

    For Apache2, you can use all workers mpm-worker, mpm-prefork and mpm-event. Mpm-worker works faster and LemonLDAP::NG use the thread system for best performance. If you have to use mpm-prefork (for example if you use PHP), LemonLDAP::NG will work anyway.

    Perl

    Here is the list of Perl modules used in LemonLDAP::NG. Core modules must be installed on the system. Other modules must be installed only if you planned to use the related feature.

    Core

    • Apache::Session
    • Net::LDAP
    • MIME::Base64
    • CGI
    • LWP::UserAgent
    • Cache::Cache
    • DBI
    • XML::Simple
    • CGI::Session
    • Regexp::Assemble
    • Regexp::Common
    • XML::LibXML
    • Crypt::Rijndael
    • IO::String
    • XML::LibXSLT
    • HTML::Template
    • SOAP::Lite
    • Config::IniFiles
    • JSON
    • Digest::HMAC
    • Digest::SHA
    • Crypt::OpenSSL::RSA
    • Crypt::OpenSSL::X509
    • Crypt::OpenSSL::Bignum
    • Convert::PEM
    • Clone
    • Net::CIDR
    • Unicode::String
    • Log::Log4perl::Logger
    • Net::CIDR::Lite
    • Cache::Memcached
    • Mouse
    • Plack::Handler
    • Authen::Captcha

    SAML2

    • Lasso
    • GLib

    CAS (authentication module)

    • AuthCAS

    OpenID

    • Net::OpenID::Consumer > 1.00
    • Net::OpenID::Server > 1.00

    Twitter

    • Net::OAuth

    POD unit tests

    • Test::POD
    • Test::MockObject

    SMTP / Reset password by mail

    • MIME::Lite
    • Email::Date::Format
    • String::Random
    • Net::SMTP
    • MIME::Base64
    • Authen::SASL

    Other

    • Jquery (javascript framework) is included in tarball and RPMs, but is a dependency on Debian official releases
    • OmegaT is needed to translate offline documentation and build fr-doc packages

    Install dependencies on your system

    APT-GET

    Perl dependencies:

    apt-get install libapache-session-perl libnet-ldap-perl libcache-cache-perl libdbi-perl perl-modules libwww-perl libxml-simple-perl libsoap-lite-perl libhtml-template-perl libregexp-assemble-perl libregexp-common-perl libjs-jquery libxml-libxml-perl libcrypt-rijndael-perl libio-string-perl libxml-libxslt-perl libconfig-inifiles-perl libjson-perl libstring-random-perl libemail-date-format-perl libmime-lite-perl libcrypt-openssl-rsa-perl libdigest-hmac-perl libdigest-sha-perl libclone-perl libauthen-sasl-perl libnet-cidr-lite-perl libcrypt-openssl-x509-perl libauthcas-perl libtest-pod-perl libtest-mockobject-perl libauthen-captcha-perl libnet-openid-consumer-perl libnet-openid-server-perl libunicode-string-perl libconvert-pem-perl libmoose-perl libplack-perl

    For Apache:

    apt-get install apache2 libapache2-mod-perl2 libapache2-mod-fcgid

    For Nginx:

    apt-get install nginx nginx-extras

    YUM

    You need EPEL repository. See how you can activate this repository: http://fedoraproject.org/wiki/EPEL/FAQ#howtouse

    Perl dependencies:

    yum install perl-Apache-Session perl-LDAP perl-XML-SAX perl-XML-NamespaceSupport perl-HTML-Template perl-Regexp-Assemble perl-Regexp-Common perl-Error perl-IPC-ShareLite perl-Cache-Cache perl-FreezeThaw perl-XML-Simple perl-version perl-CGI-Session perl-DBD-Pg perl-XML-LibXML-Common perl-BSD-Resource perl-XML-LibXML perl-Crypt-Rijndael perl-IO-String perl-XML-LibXSLT perl-SOAP-Lite perl-Config-IniFiles perl-JSON perl-Digest-HMAC perl-Digest-SHA perl-String-Random perl-MIME-Lite perl-Email-Date-Format perl-Crypt-OpenSSL-RSA perl-Crypt-OpenSSL-X509 perl-Clone perl-Authen-SASL perl-Log-Log4perl perl-Unicode-String perl-Net-CIDR-Lite perl-Cache-Memcached perl-Convert-PEM perl-Mouse perl-Plack perl-Authen-Captcha

    For Apache:

    yum install httpd mod_perl mod_fcgid

    For Nginx:

    yum install nginx
    As you need a recent version of Nginx, the best is to install Nginx official packages.
    public_pages.html000066400000000000000000000135511325274564300336340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:public_pages

    Table of Contents

    • Presentation
    • Configuration
    • Page creation

    Public pages

    Public pages are available since version 1.9.8.

    Presentation

    Public pages are an easy way to build pages based on LL::NG portal skin. You can for example create a landing page or customize error pages with it.

    A public page is just a template created in portal/skins/yourskin/public/ directory, for example test.tpl. This page can then be displayed with this URL: http://auth.example.com/public?page=test

    Configuration

    Just be sure that Apache or Nginx rewrite rule is set:

        # Public pages
        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteRule ^/public* /public.pl
        </IfModule>
      # Public pages
      rewrite ^/public.* /public.pl;

    Page creation

    Create the public/ directory :

    mkdir /var/lib/lemonldap-ng/portal/skins/bootstrap/public

    Create the new page:

    vi /var/lib/lemonldap-ng/portal/skins/bootstrap/public/test.tpl
    <TMPL_INCLUDE NAME="../header.tpl">
     
    <div class="container">
      <div class="alert alert-success">
        TEST
      </div>
    </div>
     
    <TMPL_INCLUDE NAME="../footer.tpl">

    Display the page: http://auth.example.com/public?page=test

    rbac.html000066400000000000000000000212451325274564300321050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:rbac

    Table of Contents

    • Presentation
    • Configuration
      • Roles as simple values of a user attribute
      • Roles as entries in the directory
        • Gather roles in session
        • Restrict access to application
        • Send role to application

    RBAC model

    Presentation

    RBAC stands for Role Based Access Control. It means that you manage authorizations to access applications by checking the role(s) of the user, and provide this role to the application.

    As the definition of access rules is free in LemonLDAP::NG, you can implement an RBAC model if you need.

    Configuration

    Roles as simple values of a user attribute

    Imagine you've set your directory schema to store roles as values of an attribute of the user, for example “description”. This is simple because you can send the role to the application by creating a HTTP header (for example Auth-Role) with the concatenated values (';' is the concatenation string):

    Auth-Roles => $description

    If the user has these values inside its entry:

    description: user
    description: admin

    Then you got this value inside the Auth-Roles header:

    user; admin

    Roles as entries in the directory

    Now imagine the following DIT:

    • dc=example,dc=com
      • ou=users
        • uid=coudot
      • ou=roles
        • ou=aaa
          • cn=admin
          • cn=user
        • ou=bbb
          • cn=admin
          • cn=user

    Roles are entries, below branches representing applications. We can use the standard LDAP objectClass organizationalRole to maintain roles, for example:

    dn: cn=admin,ou=aaa,ou=roles,dc=example,dc=com
    objectClass: organizationalRole
    objectClass: top
    cn: admin
    ou: aaa
    roleOccupant: uid=coudot,ou=users,dc=example,dc=com

    A user is attached to a role if its DN is in roleOccupant attribute. We add the attribute ou to allow LL::NG to know which application is concerned by this role.

    So imagine the user coudot is “user” on application “BBB” and “admin” on application “AAA”.

    Gather roles in session

    Use the LDAP group configuration to store roles as groups in the user session:

    • Base: ou=roles,dc=example,dc=com
    • Object class: organizationalRole
    • Target attribute: roleOccupant
    • Searched attributes: cn ou

    Restrict access to application

    We configure LL::NG to authorize people on an application only if they have a role on it. For this, we use the $hGroups variable.

    • For application AAA:
    default => groupMatch($hGroups, 'ou', 'aaa')
    • For application BBB:
    default => groupMatch($hGroups, 'ou', 'bbb')

    Send role to application

    It is done by creating the correct HTTP header:

    • For application AAA:
    Auth-Roles => ((grep{/aaa/} split(';',$groups))[0] =~ /([a-zA-Z]+?)/)[0]
    • For application BBB:
    Auth-Roles => ((grep{/bbb/} split(';',$groups))[0] =~ /([a-zA-Z]+?)/)[0]
    redirections.html000066400000000000000000000163311325274564300336700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:redirections

    Handler Redirections

    When a user access a Handler without a cookie, he is redirected on portal, and the target URL is encoded in redirection URL (to redirect user after authentication process).

    Protocol and port

    To encode the redirection URL, the handler will use some Apache environment variables and also configuration settings:

    • HTTPS: use https as protocol
    • Port: port of the application (by default, 80 for http, 443 for https)

    These parameters can be configured in Manager, in General Parameters > Advanced parameters > Handler redirections.

    These settings can be overridden per virtual host, see virtual host management.

    Forbidden and Server error

    Handler use the default Apache error code for the following cases:

    • User has no access authorization: FORBIDDEN (403)
    • An error occurs on server side: SERVER_ERROR (500)
    • The application is in maintenance: HTTP_SERVICE_UNAVAILABLE (503)

    These errors can be catch trough Apache ErrorDocument directive or Nginx error_page directive, to redirect user on a specific page:

    # Apache: Common error page and security parameters
    ErrorDocument 403 http://auth.example.com/?lmError=403
    ErrorDocument 500 http://auth.example.com/?lmError=500
    ErrorDocument 503 http://auth.example.com/?lmError=503
    # Nginx: Common error page and security parameters
    error_page 403 http://auth.example.com/?lmError=403;
    error_page 500 http://auth.example.com/?lmError=500;
    error_page 503 http://auth.example.com/?lmError=503;

    It is also possible to redirect the user without using ErrorDocument: the Handler will not returnV 403, 500, 503 code, but code 302 (REDIRECT).

    The user will be redirected on portal URL with error in the lmError URL parameter.

    These parameters can be configured in Manager, in General Parameters > Advanced parameters > Handler redirections:

    • Redirect on forbidden: use 302 instead 403
    • Redirect on error: use 302 instead 500 or 503

    Portal Redirections

    If a user is redirected from handler to portal for authentication and once he is authenticated, portal redirects him to the redirection URL.
    • Redirection message: The redirection from portal can be done either with code 303 (See Other), or with a JavaScript redirection. Often the redirection takes some time because it is user's first access to the protected app, so a new app session has to be created : JavaScript redirection improves user experience by informing that authentication is performed, and by preventing from clicking again on the button because it is too slow.
    • Keep redirections for Ajax: By default, when an Ajax request is done on the portal for an unauthenticated user (after a redirection done by the handler), a 401 code will be sentwith a WWW-Authenticate header containing “SSO <portal-URL>”. Set this option to 1 to keep the old behavior (return of HTML code).
    register.html000066400000000000000000000105401325274564300330160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:register

    Register a new account

    Presentation

    This feature is a page that allows a user to create an account. The steps are the following:

    1. User click on the button “Create a new account”
    2. He enters first name, last name and email
    3. He gets a mail with a confirmation link
    4. After clicking, his entry is added
    5. He gets a mail with his login and his password

    Configuration

    You can enable the “Create your account” button in portal customization parameters.

    Then, go in Portal > Advanced parameters > Register new account:

    • Module: Choose the backend to use to create the new account.
    • Page URL: URL of register page
    • Validity time of a register request: duration in seconds of a new account request. The request will be deleted after this time if user do not click on the link.
    • Subject for confirmation mail: Subject of the mail containing the confirmation link
    • Subject for done mail: Subject of the mail giving login and password
    resetpassword.html000066400000000000000000000207451325274564300341070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:resetpassword

    Reset password by mail

    Presentation

    LL::NG can propose a password reset form, for users who loose their password (this kind of application is also called a self service password interface).

    Kinematics:

    • User clicks on the link Reset my password
    • User enters his email (or another information) in the password reset form
    • LL::NG try to find the user in users database with the given information
    • A mail with a token is sent to user
    • The user click on the link in the mail
    • LL::NG validate the token and propose a password change form
    • The user can choose a new password or ask to generate one
    • The new password is sent to user by mail if user ask to generate one, else the mail only confirm that the password was changed
    If LDAP backend is used, and LDAP password policy is enabled, the 'password reset flag is set to true when password is generated, so that the user is forced to change his password on next connection. This feature can be disabled in LDAP configuration.
    If the user do a new password reset request but there is already a request pending, the user can ask the confirmation mail to be resent. The request validity time is a configuration parameter.

    Configuration

    The reset password link must be activated, see portal customization.

    Then go in Manager, General Parameters » Advanced Parameters » Password management:

    • SMTP:
      • SMTP Server: IP or hostname of the SMTP server
      • SMTP User: SMTP user if authentication is required
      • SMTP Password: SMTP password if authentication is required
    • If no SMTP server is configured, the mail will be sent via the local sendmail program. Else, Net::SMTP module is required to use the SMTP server
    • The SMTP server value can hold the port, for example: mail.example.com:25
    • If authentication is configured, Authen::SASL and MIME::Base64 modules are required
    • Mail headers:
      • Mail sender: address seen in the “From” field (default: noreply@[DOMAIN])
      • Reply address: address seen in the “Reply-To” field
      • Mail charset: Charset used for the body of the mail (default: utf-8)
    • Mail content:
      • Success mail subject: Subject of mail sent when password is changed (default: [LemonLDAP::NG] Your new password)
      • Success mail content (optional): Content of mail sent when password is changed
      • Confirmation mail subject: Subject of mail sent when password change is asked (default: [LemonLDAP::NG] Password reset confirmation)
      • Confirmation mail content (optional): Content of mail sent when password change is asked
    By default, mail content are empty in order to use HTML templates:
    • portal/skins/common/mail_confirm.tpl
    • portal/skins/common/mail_password.tpl

    If you define mail contents in Manager, HTML templates will not be used.

    • Other:
      • Page URL: URL of password reset page (default: [PORTAL]/mail.pl)
      • Regexp for password generation: Regular expression used to generate the password (default: [A-Z]{3}[a-z]{5}.\d{2})
      • Validity time of a password reset request: number of seconds for password reset request validity. During this period, user can ask the confirmation mail to be resent (default: session timeout value)
      • Session key containing mail address: name of the session key containing email address. This value will be used to know to which recipient the has to be sent (default: mail).
    rpm-gpg-key-ow2000066400000000000000000000032361325274564300330770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.5 (GNU/Linux) mQGiBEpOEcERBACHzHP7ICtjmsG4YgwlstQw0ubp6154i57BN45siMoovioQ1nP5 kXNR+fZjEW5BRqtJExQoWLdXTFL1gvsdW5V+zx7B7DIlP6H+oz1PFh8hGXUmnqb9 pL1A0WUrhbye6nlzpxt9jhGn6ymbilAi8iIWSrFxC09GONGwBGCLwbbp5wCg/75n DHecwFtSwEt7o3YV5B6k9WcD/RcPtY3mwa3RfaC+rsGdaqmni/jy6P1OrgmQX59C Zm813j/JnXYoeV+xIdCs144xPvzrCH+k/czVFBjvcA3xr2F/kuW7Kn8F+u8Ma3lb EghlG6CdJpCeXwiou5lPfPURIM7n7TDi2zVktRxGUnIa3fyBC9Orar/HbWgDGSYR 1R+vBACEcHOknp09FT8UB2YY/98cG4n5RaiBiUb6Znwd6MrEtdBC0x8PdR6PPrWf ujUZ1dgUlKUtTN2V7OC8Ql3fls8TlxLY3L2ql6PrjuF5/zhC/1lEl7QzS+tCHAzU FlDMbb3F5o+EZwxxK3Lrdf+SbmKiYq7gqv79+BJbPiLkQvLfTbQqQ2xlbWVudCBP VURPVCAoT1cyKSA8Y2xlbS5vdWRvdEBnbWFpbC5jb20+iGAEExECACAFAkpOEcEC GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRBUixe/gfGOej2rAKD/mzoSicDh f2fhAEA3t+8qkJlwgACgvLUn30yj81bNjOo84p3NjEzpt6W5Ag0ESk4RwhAIAILk DF6M5GglCqysxF6gmO4RB24nkJELOmYAfknM0qmZPED3f//wgWFfYC3t2Hsic1HM 9Dq1fQc9ziFfL7Ntt2oCu0YDoT4lrRL7eWwRn+H5sPmBisyfpTohZlObnNDOuGUZ jWZDP+7bIiNuj32TuR1Gl9q9hygm5rzjg/7d0eQfgMMSJ5D1x8FAcDRIgtF9dfQ0 XLXF1SBuPqp6E7Q92rNxWlryifnGBIcOvVIYgayyxqgLf4+hkCOi47GDVlS+E4FQ Xc5DVHuhH8JJrMsBAd14m435c1uM9gTYhOtmpgDPocPUr5APSOd/zhV+b/8t+PDm ySa5qHVmShC/NFziyY8AAwUH/jBiZQ+qOyXaanAgIz2/uiqpJxO1MR+S6m+cazvk X4nXD9N8rsUYKnXxU6bNX731t6P2StG8kfkV84xkaPBTkssDBfQIFSwYFUuyBr/m 6V8ulebig/6XHp7dVJ96DvQu8HHiLZ8YXeOVImCoEXp5fa8HgyhxVSLbVsAENYOd IEY7G4Lh/RAyrkRaLSGZuHnwXk3ioNQHCHB4m48q8tmQ2v4U8FJhXhxCmyKPKAru PPIKQ9kjPzX92NADmZc+n8RxzyBa9fppQ3z0v8mJ9SjoJ3qAO9ks+yQADLiZ8HsN jNS3Nf35jqQ5bKFF/uAygMLPzhi8iQtcBF1Q+3NDk/DRFfSISQQYEQIACQUCSk4R wgIbDAAKCRBUixe/gfGOekmNAKC4jduVjzzfeLDyH3Hnkz3G0MIFsACffY2Wv6ef bH9spStkLDt2jxvJ42Y= =6pG1 -----END PGP PUBLIC KEY BLOCK----- safejail.html000066400000000000000000000100101325274564300327400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:safejail

    Safe jail

    Presentation

    LemonLDAP::NG uses Safe jail to evaluate all expressions:

    • Access rule
    • Header
    • Form replay parameters
    • Macros
    • Groups
    • Conditions:
      • Menu modules display
      • Multi modules display
      • IssuerDB use
      • Session opening

    More information about Safe on CPAN

    Disabling Safe jail

    Safe can be very annoying when we use extended functions or custom functions. In this case, you might want to disabling it.

    To do this, go in Manager > General Parameters > Advanced Parameters > Security > Use Safe Jail and disable it.

    samlservice.html000066400000000000000000000651361325274564300335220ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:samlservice

    Table of Contents

    • Presentation
    • Prerequisites
      • Lasso
        • Debian/Ubuntu
        • RHEL/CentOS/Fedora
        • Other
      • Rewrite rules
        • Apache
        • Nginx
    • Service configuration
      • Entry Identifier
      • Security parameters
      • NameID formats
      • Authentication contexts
      • Organization
      • Service Provider
        • General options
        • Single Logout
        • Assertion Consumer
        • Artifact Resolution
      • Identity Provider
        • General parameters
        • Single Sign On
        • Single Logout
        • Artifact Resolution
      • Attribute Authority
        • Attribute Service
      • Advanced
        • SAML sessions module name and options
        • Common Domain Cookie

    SAML service configuration

    SAML service configuration is a common step to configure LL::NG as SAML SP or SAML IDP.

    Presentation

    This documentation explains how configure SAML service in LL::NG, in particular:

    • Install prerequisites
    • Import or generate security keys
    • Set SAML end points
    Service configuration will be used to generate LL::NG SAML metadata, that will be shared with other providers. It means that if you modify some settings here, you will have to share again the metadata with other providers. In other words, take the time to configure this part before sharing metadata.

    Prerequisites

    Lasso

    SAML2 implementation is based on Lasso. You will need a very recent version of Lasso (>= 2.3.0).

    Debian/Ubuntu

    There are packages available here: http://deb.entrouvert.org/.

    You will only need to install liblasso-perl package:

    sudo apt-get install liblasso-perl

    RHEL/CentOS/Fedora

    RPMs are available in LL::NG RPM repository (see yum_repository)

    Then install lasso and lasso-perl packages:

    yum install lasso lasso-perl
    Only EL6 64bits and EL7 64bits package are available.

    Other

    Download the Lasso tarball and compile it on your system.

    Rewrite rules

    Apache

    Be sure that mod_rewrite is installed and that SAML2 rewrite rules are activated in Apache portal configuration:

    <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteRule ^/saml/metadata /metadata.pl
            RewriteRule ^/saml/.* /index.pl
    </IfModule>

    Nginx

    Be sure that SAML2 rewrite rules are activated in Nginx portal configuration:

      # SAML2 Issuer
      rewrite ^/saml/metadata /metadata.pl last;
      rewrite ^/saml/.* /index.pl last;

    Service configuration

    Go in Manager and click on SAML 2 Service node.

    You can use #PORTAL# in values to replace the portal URL.

    Entry Identifier

    Your EntityID, often use as metadata URL, by default #PORTAL#/saml/metadata.

    The value will be use in metadata main markup:
    <EntityDescriptor entityID="http://auth.example.com/saml/metadata">
      ...
    </EntityDescriptor>
    If you modify /saml/metadata suffix you have to change corresponding Apache rewrite rule.

    Security parameters

    You can define keys for SAML message signature and encryption. If no encryption keys are defined, signature keys are used for signature and encryption.

    To define keys, you can:

    • import your own private and public keys (Replace by file input)
    • generate new public and private keys (New keys button)
    You can enter a password to protect private key with a password. It will be prompted if you generate keys, else you can set it in the Private key password.

    You can import a certificate containing the public key instead the raw public key. However, certificate will not be really validated by other SAML components (expiration date, common name, etc.), but will just be a public key wrapper.

    You can force LL::NG to use this certificate in SAML responses by enabling Use certificate in response option.

    You can easily generate a certificate to replace your public key by saving the private key in a file, and use openssl commands to issue a self-signed certificate:
    $ openssl req -new -key private.key -out cert.csr
    $ openssl x509 -req -days 3650 -in cert.csr -signkey private.key -out cert.pem

    NameID formats

    SAML can use different NameID formats. The NameID is the main user identifier, carried in SAML messages. You can configure here which field of LL::NG session will be associated to a NameID format.

    This parameter is used by SAML IDP to fill the NameID in authentication responses.

    Customizable NameID formats are:

    • Email
    • X509
    • Windows
    • Kerberos
    For example, if you are using AD as authentication backend, you can use sAMAccountName for the Windows NameID format.

    Other NameID formats are automatically managed:

    • Transient: NameID is generated
    • Persistent: NameID is restored from previous sessions
    • Undefined: Default NameID format is used

    Authentication contexts

    Each LL::NG authentication module has an authentication level, which can be associated to an SAML authentication context.

    This parameter is used by SAML IDP to fill the authentication context in authentication responses. It will use the authentication level registered in user session to match the SAML authentication context. It is also used by SAML SP to fill the authentication level in user session, based on authentication response authentication context.

    Customizable NameID formats are:

    • Password
    • Password protected transport
    • TLS client
    • Kerberos

    Organization

    This concerns all parameters for the Organization metadata section:
    <Organization>
      <OrganizationName xml:lang="en">Example</OrganizationName>
      <OrganizationDisplayName xml:lang="en">Example</OrganizationDisplayName>
      <OrganizationURL xml:lang="en">http://www.example.com</OrganizationURL>
    </Organization>
    • Display Name: should be displayed on IDP, this is often your society name
    • Name: internal name
    • URL: URL of your society

    Service Provider

    This concerns all parameters for the Service Provider metadata section:
    <SPSSODescriptor>
      ...
    </SPSSODescriptor>

    General options

    • Signed Authentication Request: set to On to always sign authentication request.
    • Want Assertions Signed: set to On to require that received assertions are signed.
    These options can then be overridden for each Identity Provider.

    Single Logout

    For each binding you can set:

    • Location: Access Point for SLO request.
    • Response Location: Access Point for SLO response.

    Available bindings are:

    • HTTP Redirect
    • HTTP POST
    • HTTP SOAP

    Assertion Consumer

    For each binding you can set:

    • Default: will this binding be used by default for authentication response.
    • Location: Access Point for SSO request and response.

    Available bindings are:

    • HTTP Artifact
    • HTTP POST

    Artifact Resolution

    The only authorized binding is SOAP. This should be set as Default.

    Identity Provider

    This concerns all parameters for the Service Provider metadata section:
    <IDPSSODescriptor>
      ...
    </IDPSSODescriptor>

    General parameters

    • Want Authentication Request Signed: set to On to require that received authentication request are signed.
    This option can then be overridden for each Service Provider.

    Single Sign On

    For each binding you can set:

    • Location: Access Point for SSO request.
    • Response Location: Access Point for SSO response.

    Available bindings are:

    • HTTP Redirect
    • HTTP POST
    • HTTP Artifact
    • HTTP SOAP

    Single Logout

    For each binding you can set:

    • Location: Access Point for SLO request.
    • Response Location: Access Point for SLO response.

    Available bindings are:

    • HTTP Redirect
    • HTTP POST
    • HTTP SOAP

    Artifact Resolution

    The only authorized binding is SOAP. This should be set as Default.

    Attribute Authority

    This concerns all parameters for the Attribute Authority metadata section
    <AttributeAuthorityDescriptor>
      ...
    </AttributeAuthorityDescriptor>

    Attribute Service

    This is the only service to configure, and it accept only the SOAP binding.

    Response Location should be empty, as SOAP responses are directly returned (synchronous binding).

    Advanced

    These parameters are not mandatory to run SAML service, but can help to customize it:

    • IDP resolution cookie name: by default, it's the LL::NG cookie name suffixed by idp, for example: lemonldapidp.
    • UTF8 metadata conversion: set to On to force partner's metadata conversion.

    SAML sessions module name and options

    By default, the main session module is used to store SAML temporary data (like relay-states), but SAML sessions need to use a session module compatible with the sessions restrictions feature.

    This is not the case of Memcached for example. In this case, you can choose a different module to manage SAML sessions.

    You can also choose a different session module to split SSO sessions and SAML sessions.
    • RelayState session timeout: timeout for RelayState sessions. By default, the RelayState session is deleted when it is read. This timeout allows one to purge sessions of lost RelayState.
    • Use specific query_string method: the CGI query_string method may break invalid URL encoded signatures (issued for example by ADFS). This option allows one to use a specific method to extract query string, that should be compliant with non standard URL encoded parameters.

    Common Domain Cookie

    Common Domain Cookie is also know as WAYF Service.

    The common domain is used by SAML SP to find an Identity Provider for the user, and by SAML IDP to register itself in user's IDP list.

    Configuration parameters are:

    • Activation: Set to On to enable Common Domain Cookie support.
    • Common domain: Name of the common domain (where common cookie is available).
    • Reader URL: URL used by SAML SP to read the cookie. Leave blank to deactivate the feature.
    • Writer URL: URL used by SAML IDP to write the cookie. Leave blank to deactivate the feature.
    securetoken.html000066400000000000000000000156531325274564300335330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:securetoken

    Table of Contents

    • Presentation
    • Configuration
      • Virtual host
        • Apache
        • Nginx
      • Handler parameters

    Secure Token Handler

    Presentation

    The Secure Token Handler is a special Handler that create a token for each request and send it to the protected application. The real user identifier is stored in a Memcached server and the protected application can the request the Memcached server to get user identifier.

    This mechanism allows one to do SSO on application with an unsafe link between Handler and the application, but with a safe link with the Memcached server.

    Configuration

    Virtual host

    Apache

    Configure the virtual host like other protected virtual host but use Secure Token Handler instead of default Handler.

    PerlModule Lemonldap::NG::Handler::Specific::SecureToken
    <VirtualHost *:80>
           ServerName secure.example.com
     
           # Load SecureToken Handler
           PerlHeaderParserHandler Lemonldap::NG::Handler::Specific::SecureToken
     
           ...
     
    </VirtualHost>

    Nginx

    This module uses Apache2 Filter and is not compatible with Nginx.

    Handler parameters

    SecureToken parameters are the following:

    • Memcached servers: addresses of Memcached servers, separated with spaces.
    • Token expiration: time in seconds for token expiration (remove from Memcached server).
    • Attribute to store: the session key that will be stored in Memcached.
    • Protected URLs: Regexp of URLs for which the secure token will be sent, separated by spaces
    • Header name: name of the HTTP header carrying the secure token.
    • Allow requests in error: allow a request that has generated an error in token generation to be forwarded to the protected application without secure token (default: yes)
    Due to Handler API change in 1.9, you need to set these attributes in lemonldap-ng.ini and not in Manager, for example:
    [handler]
    secureTokenMemcachedServers = 127.0.0.1:11211
    secureTokenExpiration = 60
    secureTokenAttribute = uid
    secureTokenUrls = .*
    secureTokenHeader = Auth-Token
    secureTokenAllowOnError = 1
    security.html000066400000000000000000000502711325274564300330460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:security

    Table of Contents

    • Secure configuration access
    • Protect the Manager
      • Protect the Manager by Apache
      • Protect the Manager by LL::NG
    • Write good rules
      • Order your rules
      • Be careful with URL parameters
      • Encoded characters
    • Secure reverse-proxies
    • Configure security settings
    • Fail2ban
    • Sessions identifier

    Security recommendation

    Secure configuration access

    Configuration can be stored in several formats (SQL, File, LDAP) but must be shared over the network if you use more than 1 server. If some of your servers are not in the same (secured) network than the database, it is recommended to use SOAP access for those servers.

    You can use different type of access: SQL, File or LDAP for servers in secured network and SOAP for remote servers.

    Next, you have to configure the SOAP access as described here since SOAP access is denied by default.

    Protect the Manager

    By default, the manager is restricted to the user 'dwho' (default backend is Demo). To protect the manager, you have to choose one or both of :

    • protect the manager by Apache configuration
    • protect the manager by LL::NG

    Protect the Manager by Apache

    You can use any of the mechanisms proposed by Apache: SSL, Auth-Basic, Kerberos,… Example

    <VirtualHost *:443>
        ServerName manager.example.com
        # SSL parameters
        ...
        # DocumentRoot
        DocumentRoot /var/lib/lemonldap-ng/manager/
        <Location />
            AuthType Basic
            AuthName "Lemonldap::NG manager"
            AuthUserFile /usr/local/apache/passwd/passwords
            Require user rbowen
            Order allow,deny
            Deny from all
            Allow from 192.168.142.0/24
            Options +ExecCGI
        </Location>
    </VirtualHost>

    Protect the Manager by LL::NG

    To protect the manager by LL::NG, you just have to set this in lemonldap-ng.ini configuration file (section [manager]):

    [manager]
    protection = manager
    Before, you have to create the virtual host manager.your.domain in the manager and set a rules, else access to the manager will be denied.

    Write good rules

    Order your rules

    Rules are applied in alphabetical order (comment and regular expression). The first rule that matches is applied.

    The “default” rule is only applied if no other rule match

    The Manager let you define comments in rules, to order them:

    For example, if these rules are used without comments:

    Regular expression Rule Comment
    ^/pub/admin/ $uid eq “root”
    ^/pub/ accept

    Then the second rule will be applied first, so every authenticated user will access to /pub/admin directory.

    Use comment to correct this:

    Regular expression Rule Comment
    ^/pub/admin/ $uid eq “root” 1_admin
    ^/pub/ accept 2_pub
    • Reload the Manager to see the order that will be used
    • Use rule comments to order your rules

    Be careful with URL parameters

    You can write rules matching any component of URL to protect including GET parameters, but be careful.

    For example with this rule on the access parameter:

    Regular expression Rule Comment
    ^/index.php\?.*access=admin $groups =~ /\badmin\b/
    default accept

    Then a user that try to access to one of the following will be granted !

    • /index.php?access=admin&access=other
    • /index.php?Access=admin

    You can use the following rules instead:

    Regular expression Rule Comment
    ^/(?i)index.php\?.*access.*access deny 0_bad
    ^/(?i)index.php\?.*access=admin $groups =~ /\badmin\b/ 1_admin
    default accept
    (?i) means case no sensitive.
    Remember that rules written on GET parameters must be tested.

    Encoded characters

    Some characters are encoded in URLs by the browser (such as space,…). To avoid problems, LL::NG decode them using http://search.cpan.org/perldoc?Apache2::URI#unescape_url. So write your rules using normal characters.

    Secure reverse-proxies

    LL::NG can protect any Apache hosted application including Apache reverse-proxy mechanism. Example:

    PerlOptions +GlobalRequest
    PerlRequire /var/lib/lemonldap-ng/handler/MyHandler.pm
    <VirtualHost *:443>
        SSLEngine On
        ... other SSL parameters ...
        PerlInitHandler My::Handler
        ServerName appl1.example.com
        ProxyPass / http://hiddenappl1.example.com/
        ProxyPassReverse / http://hiddenappl1.example.com/
        ProxyPassReverseCookieDomain / http://hiddenappl1.example.com/
    </VirtualHost>

    See mod_proxy and mod_rewrite documentation for more about configuring Apache reverse-proxies.

    Such configuration can have some security problems:

    • if a user can access directly to the hidden application, it can bypass LL::NG protection
    • if many hidden applications are on the same private network, if one is corrupted (by SQL injection, or another attack), the hacker will be able to access to other applications without using reverse-proxies so it can bypass LL::NG protection

    It is recommended to secure the channel between reverse-proxies and application to be sure that only request coming from the LL::NG protected reverse-proxies are allowed. You can use one or a combination of:

    • firewalls (but be careful if more than 1 server is behind the firewall)
    • server based restriction (like Apache “allow/deny” mechanism)
    • SSL client certificate for the reverse-proxy (see SSLProxy* parameters in mod_ssl documentation)

    Configure security settings

    Go in Manager, General parameters » Advanced parameters » Security:

    • Username control: Regular expression used to check user login syntax.
    • Force authentication: set to 'On' to force authentication when user connects to portal, even if he has a valid session
    • Force authentication interval: time interval (in seconds) when a authentication renewal cannot be forced, used to prevent to loose the current authentication during the main process. If you experience slow network performances, you can increase this value.
    • Encryption key: key used to crypt some data, should not be known by other applications
    • Trusted domains: domains on which the user can be redirected after login on portal. Set '*' to accept all.
    • Use Safe jail: set to 'Off' to disable Safe jail. Safe module is used to eval expressions in headers, rules, etc. Disabling it can lead to security issues.
    • Check XSS Attacks: Set to 'Off' to disable XSS checks. XSS checks will still be done with warning in logs, but this will not prevent the process to continue.
    • LWP::UserAgent SSL options: insert here options to pass to LWP::UserAgent object (used by SAML or OpenID-Connect to query partners). Example: verify_hostname ⇒ 0, SSL_verify_mode ⇒ 0

    Fail2ban

    For block brute force attack with fail2ban

    Edit /etc/fail2ban/jail.conf

    [lemonldap-ng]
    enabled = true
    port    = http,https
    filter  = lemonldap
    action   = iptables-multiport[name=lemonldap, port="http,https"]
    logpath = /var/log/apache*/error*.log
    maxretry = 3

    and edit /etc/fail2ban/filter.d/lemonldap.conf

    # Fail2Ban configuration file
    #
    # Author: Adrien Beudin
    #
    # $Revision: 2 $
    #
    
    [Definition]
    
    # Option:  failregex
    # Notes.:  regex to match the password failure messages in the logfile. The
    #          host must be matched by a group named "host". The tag "<HOST>" can
    #          be used for standard IP/hostname matching and is only an alias for
    #          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
    # Values:  TEXT
    #
    failregex = Lemonldap\:\:NG \: .* was not found in LDAP directory \(<HOST>\)
                Lemonldap\:\:NG \: Bad password for .* \(<HOST>\)
    
    # Option:  ignoreregex
    # Notes.:  regex to ignore. If this regex matches, the line is ignored.
    # Values:  TEXT
    #
    ignoreregex =

    Restart fail2ban

    Sessions identifier

    You can change the module used for sessions identifier generation. To do, add generateModule key in the configured session backend options.

    We recommend the use of Lemonldap::NG::Common::Apache::Session::Generate::SHA256.

    selfmadeapplication.html000066400000000000000000000205501325274564300352000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:selfmadeapplication

    Table of Contents

    • Presentation
    • Code snippet
      • Perl
      • PHP
    • Perl auto-protected CGI

    Protect your application

    Presentation

    Your application can know the connected user using:

    • REMOTE_USER environment variable (with local Handler or SetEnvIf trick)
    • HTTP header (in all cases)

    To get more information on user (name, mail, etc.), you have to read HTTP headers.

    If your application is based on Perl CGI package, you can simply replace CGI by Lemonldap::NG::Handler::CGI

    Code snippet

    Examples with a configured header named 'Auth-User':

    Perl

    print "Connected user: ".$ENV{HTTP_AUTH_USER};

    PHP

    print "Connected user: ".$_SERVER["HTTP_AUTH_USER"];

    Perl auto-protected CGI

    Using this feature, you don't have to use virtual host protection: protection is embedded in Lemonldap::NG::Handler::CGI.

    The protection parameter must be set when calling the new() method:

    • none: no protection
    • authenticate: check authentication but do not manage authorization
    • manager: rely on virtual host configuration in Manager
    • rule: xxx: apply a specific rule

    Example:

    • Code to replace:
    my $cgi = new CGI;
    ...
    • New code:
    my $cgi = Lemonldap::NG::Handler::CGI->new ({ protection => 'authenticate' });
     
    print $cgi->header;
    print $cgi->start_html;
    ...

    Then you can access to user datas

    # Get attributes (or macros)
    my $cn = $cgi->user->{cn}
     
    # Test if user is member of a Lemonldap::NG group (or LDAP mapped group)
    if( $cgi->group('admin') ) {
      # special html code for admins
    }
    else {
      # another HTML code
    }
    selinux.html000066400000000000000000000102071325274564300326610ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:selinux

    Table of Contents

    • Disk cache (sessions an configuration)
    • LDAP
    • Databases
    • Memcache
    • Proxy HTTP

    SELinux

    To make LemonLDAP::NG work with SELinux, you may need to set up some options.

    Disk cache (sessions an configuration)

    chcon -R -t httpd_sys_rw_content_t /tmp

    To persist the rule:

    semanage fcontext -a -t http_sys_content_t /tmp

    LDAP

    setsebool -P httpd_can_connect_ldap 1

    Databases

    setsebool -P httpd_can_network_connect_db 1

    Memcache

    setsebool -P httpd_can_network_memcache 1

    Proxy HTTP

    setsebool -P httpd_can_network_relay 1
    sessions.html000066400000000000000000000135431325274564300330460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:sessions

    Sessions

    LL::NG rely on a session mechanism with the session ID as a shared secret between the user (in SSO cookie) and the session database.

    To configure sessions, go in Manager, General Parameters » Sessions:

    • Store user password in session data: see password store documentation.
    • Sessions timeout: Maximum lifetime of a session. Old sessions are deleted by a cron script.
    • Sessions activity timeout: Maximum inactivity duration.
    • Sessions update interval: Minimum interval used to update session when activity timeout is set.
    Session activity timeout requires Handlers to have a write access to sessions database.
    • Opening conditions: rules which are evaluated before granting session. If a user does not comply with any condition, he is prompted a customized message. That message can contain session data as user attributes or macros. The conditions are checked in alphabetical order of comments.
    • Sessions Storage: you can define here which session backend to use, with the backend options. See sessions database configuration to know which modules you can use. Here are some global options that you can use with all sessions backends:
      • generateModule: allows one to override the default module that generates sessions identifiers. For security reasons, we recommend to use Lemonldap::NG::Common::Apache::Session::Generate::SHA256
      • IDLength: length of sessions identifiers. Max is 32 for MD5 and 64 for SHA256
    • Multiple sessions, you can restrict the number of open sessions:
      • One session only by user: a user can not open 2 sessions with the same account.
      • One IP only by user: a user can not open 2 sessions with different IP.
      • One user by IP address: 2 users can not open a session with the same IP.
      • Display deleted sessions: display deleted sessions on authentication phase.
      • Display other sessions : display other sessions on authentication phase, with a link to delete them.
    Note that since HTTP protocol is not connected, restrictions are not applied to the new session: the oldest are destroyed.
    soapconfbackend.html000066400000000000000000000134451325274564300343210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:soapconfbackend

    Table of Contents

    • Configuration
      • First, configure your real backend
      • Next, configure SOAP for your remote servers

    SOAP configuration backend

    You can share your configuration over the network using SOAP proxy system.

    Note that SOAP is not a real configuration backend, but just a proxy system to access to your configuration over the network

    Configuration

    First, configure your real backend

    • On your main server, configure a File, SQL or LDAP backend
    • Set SOAP parameter to true in the configuration using the manager: the portal will become a SOAP server
    • Configure Apache to allow remote access: in portal-apache2.conf, remote SOAP access is disabled by default. Change it:
    # SOAP functions for configuration access (disabled by default)
    <Location /index.pl/config>
        Order deny,allow
        Deny from all
        Allow from 192.168.2.0/24
    </Location>

    Next, configure SOAP for your remote servers

    Change configuration in lemonldap-ng.ini :

    type         = SOAP
    proxy        = https://auth.example.com/index.pl/config

    You can also add some other parameters

    User         = lemonldap
    Password     = mypassword
    # LWP::UserAgent parameters
    proxyOptions = { timeout => 5 }
    soapminihowto.html000066400000000000000000000103261325274564300340740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:soapminihowto

    Configure LemonLDAP::NG to use SOAP proxy mechanism

    LL::NG use 2 internal databases to store its configuration and sessions. It can be configured to use SOAP instead of direct access to those databases (for remote servers).

    This mechanism can be used to secure access for remote servers that cross an unsecured network to access to LL::NG databases.

    Use SOAP for Lemonldap::NG configuration

    Steps:

    • Choose and configure your main configuration storage system
    • Follow SOAP configuration backend page
    • Restart all your remote Apache servers

    Use SOAP for Lemonldap::NG sessions

    Steps:

    • Choose and configure your main sessions storage system
    • Follow SOAP sessions backend page
    soapservices.html000066400000000000000000000167031325274564300337070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:soapservices

    SOAP services

    Lemonldap::NG provides 2 SOAP servers :

    • the portal
    • the manager (for internal use only)

    Portal SOAP services

    SOAP functions are not accessible by network by default. SOAP functions are protected by Apache, you can change this in Apache portal configuration:

        # SOAP functions for sessions management (disabled by default)
        <Location /index.pl/adminSessions>
            Order deny,allow
            Allow from all
        </Location>
     
        # SOAP functions for sessions access (disabled by default)
        <Location /index.pl/sessions>
            Order deny,allow
            Allow from all
        </Location>
     
        # SOAP functions for configuration access (disabled by default)
        <Location /index.pl/config>
            Order deny,allow
            Allow from all
        </Location>
     
        # SOAP functions for notification insertion (disabled by default)
        <Location /index.pl/notification>
            Order deny,allow
            Allow from all
        </Location>
    You can create a SOAP only portal by setting “soapOnly = 1” in lemonldap-ng.ini (section PORTAL)
    • Read-only functions (index.pl/sessions or index.pl/adminSessions paths):
      • getCookies(user,password): authentication system. Returns cookie(s) name and values
      • getAttributes(cookieValue): get elements stored in session
      • isAuthorizedURI(cookieValue,url): check if user is granted to access to the function
      • getMenuApplications(cookieValue): return a list of authorizated applications (based on menu calculation)
    • Read/Write functions (index.pl/adminSessions paths):
      • setAttributes(cookieValue,hashtable): update a session
      • newSession: create a session (return attributes)
      • deleteSession: delete a session
      • get_key_from_all_sessions: list all sessions and return asked keys
    • Notification send function (index.pl/notification):
      • newNotification(xmlString): insert a notification for a user (see Notifications system for more)
    • Notification delete function:
      • deleteNotification: delete notification(s) for a user (see Notifications system for more)
    When you use SOAP sessions backend, it is recommended to use read-only URL (http://portal/index.pl/sessions). Write session path is needed only if you use a remote session explorer or a remote portal

    WSDL file

    When portal is installed, a file named portal.wsdl is created. It can be upgraded using buildPortalWSDL script.

    soapsessionbackend.html000066400000000000000000000207741325274564300350620ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:soapsessionbackend

    Table of Contents

    • Setup
      • Manager
      • Apache
      • Real session backend

    SOAP session backend

    LL::NG portal provides SOAP end points for sessions management:

    • sessions/: read only access to sessions (enough for distant Handlers)
    • adminSessions/: read/write access to sessions (required for distant Portal, distant Manager or distant Handlers which modify sessions)

    This session backend can be used to share sessions stored in a non-network backend (like file session backend) or in a network backend protected with a firewall that only accepts HTTP flows.

    Most of the time, SOAP session backend is used by Handlers installed on external servers.

    To configure it, SOAP session backend will be set trough Manager in global configuration (used by all Hanlders), and the real session backend will be configured for local components in lemonldap-ng.ini.

    Setup

    Manager

    First, active SOAP in General parameters » Advanced parameters » SOAP.

    Then, set Lemonldap::NG::Common::Apache::Session::SOAP in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

    Required parameters
    Name Comment Example
    proxy URL of sessions SOAP end point http://auth.example.com/index.pl/sessions
    Use /adminSessions if the Handler need to modify the session, for example if you configured an idle timeout.

    By default, only few sessions keys are shared by SOAP (ipAddr, _utime, _session_id), you need to define which other keys you want to share in General parameters » Advanced parameters » SOAP » Exported attributes.

    You must start with + to keep default keys, else they will not be shared. For example:

    + uid cn mail

    To share only the listed attributes:

    _utime _session_id uid cn mail

    Apache

    Sessions SOAP end points access must be allowed in Apache portal configuration (for example, access by IP range):

    # SOAP functions for sessions management (disabled by default)
    <Location /index.pl/adminSessions>
        Order deny,allow
        Deny from all
        Allow from 192.168.2.0/24
    </Location>
     
    # SOAP functions for sessions access (disabled by default)
    <Location /index.pl/sessions>
        Order deny,allow
        Deny from all
        Allow from 192.168.2.0/24
    </Location>

    Real session backend

    Real session backend will be configured in lemonldap-ng.ini, in portal section (the portal hosts the SOAP service for sessions, and will do the link between SOAP requests and real sessions).

    For example, if real sessions are stored in files:

    [portal]
    globalStorage = Apache::Session::File
    globalStorageOptions = { 'Directory' => '/var/lib/lemonldap-ng/sessions/', 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/', }
    If your sessions explorer is on the same server that the portal, either use the adminSessions end point in Manager configuration, or override the globalStorage and globalStorageOptions parameters in section all (and not portal) of lemonldap-ng.ini.
    sqlconfbackend.html000066400000000000000000000310401325274564300341450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:sqlconfbackend

    Table of Contents

    • MySQL
      • Perl Driver
      • Database and table creation
        • RDBI
        • CDBI
      • Grant access
    • Connection settings
    • PostGreSQL
      • Perl Driver
      • Database and table creation
        • RDBI
        • CDBI
    • Connection settings

    SQL configuration backends

    There is 2 types of SQL configuration backends for LemonLDAP::NG:

    • CDBI: very simple storage
    • RDBI: triple store storage (recommended)
    You can use any database engine if it provides a Perl Driver. You will find here examples for MySQL and PostGreSQL, but other engines may also work.

    See how to change configuration backend.

    MySQL

    Perl Driver

    You need DBD::MySQL Perl module:

    • Debian:
    apt install libdbd-mysql-perl
    • Red Hat:
    yum install perl-DBD-MySQL

    Database and table creation

    Create database:

    CREATE DATABASE lemonldap-ng CHARACTER SET utf8;

    Use database to create table:

    USE lemonldap-ng

    RDBI

    CREATE TABLE lmConfig (
        cfgNum INT(11) NOT NULL,
        FIELD VARCHAR(255) NOT NULL DEFAULT '',
        VALUE longtext,
        PRIMARY KEY (cfgNum,FIELD)
        );

    CDBI

    CREATE TABLE lmConfig (
        cfgNum INT NOT NULL PRIMARY KEY,
        DATA longtext
    );

    Grant access

    You have to grant read/write access for the manager component. Other components needs just a read access. You can also use the same user for all.

    You can use different dbiUser strings:
    • one with read/write rights for servers hosting the manager
    • one with just read rights for other servers

    For example (suppose that our servers are in 10.0.0.0/24 network):

    GRANT SELECT,INSERT,UPDATE,DELETE,LOCK TABLES ON lemonldap-ng.lmConfig
      TO lemonldaprw@manager.host IDENTIFIED BY 'mypassword';
    GRANT SELECT ON lemonldap-ng.lmConfig
      TO lemonldapro@'10.0.0.%' IDENTIFIED BY 'myotherpassword';

    Connection settings

    Change configuration settings in /etc/lemonldap-ng/lemonldap-ng.ini file (section configuration):

    [configuration]
    type = RDBI
    dbiChain    = DBI:mysql:database=lemonldap-ng;host=1.2.3.4
    dbiUser     = lemonldaprw
    dbiPassword = mypassword
    ; optional
    dbiTable    = mytablename

    PostGreSQL

    Perl Driver

    You need DBD::Pg Perl module:

    • Debian:
    apt install libdbd-pg-perl
    • Red Hat:
    yum install perl-DBD-Pg

    Database and table creation

    Create database:

    CREATE DATABASE lemonldap-ng;

    Use database to create table:

    USE lemonldap-ng

    RDBI

    CREATE TABLE lmconfig (
        cfgnum INTEGER NOT NULL,
        FIELD text NOT NULL,
        VALUE text,
        PRIMARY KEY (cfgNum,FIELD)
        );

    CDBI

    CREATE TABLE lmConfig (
        cfgnum INTEGER NOT NULL PRIMARY KEY,
        DATA text
    );

    Connection settings

    Change configuration settings in /etc/lemonldap-ng/lemonldap-ng.ini file (section configuration):

    [configuration]
    type = RDBI
    dbiChain    = DBI:Pg:database=lemonldap-ng;host=1.2.3.4
    dbiUser     = lemonldaprw
    dbiPassword = mypassword
    ; optional
    dbiTable    = mytablename
    sqlsessionbackend.html000066400000000000000000000247351325274564300347200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:sqlsessionbackend

    Table of Contents

    • Setup
      • Prepare the database
        • MySQL
        • PostgreSQL
      • Manager
    • Security

    SQL session backend

    SQL session backend can be used with many SQL databases such as:

    • MySQL
    • PostgreSQL
    • Oracle
    • Informix
    • Sybase
    • ….

    Setup

    Prepare the database

    Your database must have a specific table to host sessions. Here are some examples for main databases servers.

    If your database doesn't accept UTF-8 characters in 'text' field, use 'blob' instead of 'text'.

    MySQL

    Create a database if necessary:

    mysqladmin create lemonldapng

    Create sessions table:

    CREATE TABLE sessions (
        id CHAR(32) NOT NULL PRIMARY KEY,
        a_session text
        );
    Change char(32) by char(64) if you use the now recommended SHA256 hash algorithm. See Sessions for more details
    You can change table name sessions to whatever you want, just adapt the parameter TableName in module options.

    PostgreSQL

    Create user and role:

    su - postgres
    createuser lemonldap-ng -P
    Entrez le mot de passe pour le nouveau rôle : <PASSWORD>
    Entrez-le de nouveau : <PASSWORD>
    Le nouveau rôle est-il un super-utilisateur ? (o/n) n
    Le nouveau rôle doit-il être autorisé à créer des bases de données ? (o/n) n
    Le nouveau rôle doit-il être autorisé à créer de nouveaux rôles ? (o/n) n

    Create database:

    createdb -O lemonldap-ng lemonldap-ng

    Create table:

    psql -h 127.0.0.1 -U lemonldap-ng -W lemonldap-ng
    Mot de passe pour l'utilisateur lemonldap-ng :
    [...]
    lemonldap-ng=> create table sessions ( id char(32) not null primary key, a_session text );
    lemonldap-ng=> q
    Change char(32) by char(64) if you use the now recommanded SHA256 hash algorithm. See Sessions for more details

    Manager

    Go in the Manager and set the session module (for example Apache::Session::Postgres for PostgreSQL) in General parameters » Sessions » Session storage » Apache::Session module and add the following parameters (case sensitive):

    Required parameters
    Name Comment Example
    DataSource The DBI string dbi:Pg:dbname=sessions;host=10.2.3.1
    UserName The database username lemonldapng
    Password The database password mysuperpassword
    Commit Required for PostgreSQL 1
    TableName Name of the table sessions

    You must read the man page corresponding to your database (Apache::Session::MySQL, …) to learn more about parameters. You must also install the database connector (DBD::Oracle, DBD::Pg,…)

    For MySQL, you need to set additional parameters:
    • LockDataSource
    • LockUserName
    • LockPassword

    If you choose to use MySQL, read how to increase MySQL performances.

    Security

    Restrict network access to the database.

    You can also use different user/password for your servers by overriding parameters globalStorage and globalStorageOptions in lemonldap-ng.ini file.

    ssocookie.html000066400000000000000000000156731325274564300332040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:ssocookie

    Single Sign On cookie, domain and portal URL

    SSO cookie

    The SSO cookie is build by the portal (as described in the login kinematic), or by the Handler for cross domain authentication (see CDA kinematic).

    To edit SSO cookie parameters, go in Manager, General Parameters > Cookies:

    • Cookie name: name of the cookie, can be changed to avoid conflicts with other LemonLDAP::NG installations
    • Domain: validity domain for the cookie (the cookie will not be sent on other domains)
    • Multiple domains: enable cross domain mechanism (without this, you cannot extend SSO to other domains)
    • Secured cookie: 4 options:
      • Non secured cookie: the cookie can be sent over HTTP and HTTPS connections
      • Secured cookie: the cookie can only be sent over HTTPS
      • Double cookie: two cookies are delivered, one for HTTP and HTTPS connections, the other for HTTPS only
      • Double cookie for single session: as same, two cookies are delivered, but only one session is written in session database
    • Javascript protection: set httpOnly flag, to avoid cookie been caught by javascript code
    • Cookie expiration time: by default, SSO cookie is a session cookie, which mean it will be destroyed when the browser is closed. You can change this behavior and set a cookie duration, for example:
      • +30s: 30 seconds from session creation
      • +10m: ten minutes from session creation
      • +1h: one hour from session creation
      • +3M: three months from session creation
      • +10y: ten years from session creation
      • Thursday, 25-Apr-1999 00:40:33 GMT: at the indicated time and date (but this is probably a bad idea)
    When you change cookie expiration time, it is written on the user hard disk unlike session cookie
    Changing the domain value will not update other configuration parameters, like virtual host names, portal URL, etc. You have to update them by yourself.

    Portal URL

    Portal URL is the address used to redirect users on the authentication portal by:

    • Handler: user is redirected if he has no SSO cookie (or in CDA mode)
    • Portal: the portal redirect on itself in many cases (credentials POST, SAML, etc.)
    The portal URL must be inside SSO domain. If secured cookie is enabled, the portal URL must be HTTPS.
    start.html000066400000000000000000001157161325274564300323420ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:start

    Table of Contents

    • Main changes
    • Installation
      • Before installation
      • Installation
      • After installation
    • Configuration
      • First steps
      • Portal
      • Authentication, users and password databases
      • Configuration database
      • Sessions database
      • Identity provider
    • Applications protection
      • Well known compatible applications
    • Advanced features
    • Mini howtos
    • Exploitation

    Documentation for LemonLDAP::NG 1.9

    Main changes

    Version 1.9 of LL::NG brings the following main changes:

    • OpenID Connect support
    • Nginx support
    • New manager interface
    You must read upgrade from 1.4 to 1.9 documentation page before installing it.

    Installation

    Before installation

    • Prerequisites and dependencies
    • Upgrade notes

    Installation

    • Installation from the tarball
    • Installation on Debian/Ubuntu with packages
    • Installation on RHEL/CentOS with packages
    • Installation on Suse Linux Enterprise Server with packages
    • Run in LemonLDAP::NG in Docker

    After installation

    • Deploy Apache configuration
    • Deploy Nginx configuration

    Configuration

    First steps

    • Configuration overview
    • Configure Single Sign On cookie and portal URL
    • Parameter redirections
    • Set exported variables
    • Manage virtual hosts
    • Configure sessions specificities

    Portal

    • Presentation
    • Portal customization
    • Portal menu
    • Captcha
    • Public pages

    Authentication, users and password databases

    Official Backends Authentication Users Password
    Active Directory ✔ ✔ ✔
    Apache (Kerberos, NTLM, OTP, ...) ✔
    BrowserID (Mozilla Persona) ✔
    CAS ✔
    Databases (DBI) ✔ ✔ ✔
    Demonstration ✔ ✔ ✔
    Facebook ✔ ✔
    Kerberos (available with version ≥ 1.9.14) ✔
    LDAP ✔ ✔ ✔
    LinkedIn ✔
    Null ✔ ✔ ✔
    OpenID Connect ✔ ✔
    Proxy LL::NG ✔ ✔
    Radius ✔
    SAML 2.0 / Shibboleth ✔ ✔
    Slave ✔ ✔
    SSL ✔
    Twitter ✔
    WebID ✔ ✔
    Yubikey ✔
    Combo Backends Authentication Users Password
    Choice by users ✔ ✔ ✔
    Multiple backends stack ✔ ✔
    Obsolete Backends Authentication Users Password
    Google ✔ ✔
    OpenID ✔ ✔
    Remote LL::NG ✔ ✔

    Configuration database

    LL::NG needs a storage system to store its own configuration (managed by the manager). Choose one of the following:

    Backend Shareable Comment
    File (JSON) Not shareable between servers except if used in conjunction with SOAP or with a shared file system (NFS,…). Selected by default during installation.
    SQL (RDBI/CDBI) ✔
    LDAP ✔
    MongoDB ✔
    SOAP ✔ Proxy backend to be used in conjunction with another configuration backend.
    Can be used to secure another backend for remote servers.
    You can not start with an empty configuration, so read how to change configuration backend to convert your existing configuration into another one.

    Sessions database

    Sessions are stored using Apache::Session modules family. All Apache::Session style modules are useable except for some features.

    Backend Shareable Session explorer Session restrictions Session expiration Comment
    File ✔ ✔ ✔ Not shareable between servers except if used in conjunction with SOAP session backend or with a shared file system (NFS,…). Selected by default during installation.
    SQL ✔ ✔ ✔ ✔ Unoptimized for session explorer and single session features.
    LDAP ✔ ✔ ✔ ✔
    Redis ✔ ✔ ✔ ✔ The faster. Must be secured by network access control.
    MongoDB ✔ ✔ ✔ ✔ Must be secured by network access control.
    Browseable (SQL, Redis or LDAP) ✔ ✔ ✔ ✔ Optimized for session explorer and single session features.
    SOAP ✔ ✔ ✔ ✔ Proxy backend to be used in conjunction with another session backend.
    Can be used to secure another backend for remote servers.

    Identity provider

    • All identity provider protocols can be used simultaneously
    • LemonLDAP::NG can be used as a proxy between those protocols

    • CAS 1.0 / 2.0 / 3.0
    • SAML 2.0 / Shibboleth
    • OpenID 2.0 (obsolete)
    • OpenID Connect
    • Get parameters provider

    Applications protection

    • Writing rules and headers
    • Variables that can be used in rules and headers
    • Integrate vendor applications
    • Integrate self-made applications
    • Form replay
    • Custom Handlers

    Well known compatible applications

    Here is a list of well known applications that are compatible with LL::NG. A full list is available on vendor applications page.

    ADFS

    Alfresco

    Bugzilla

    Dokuwiki

    Drupal

    FusionDirectory

    Gitlab

    GLPI

    Liferay

    Mediawiki

    NextCloud

    simpleSAMLphp

    Wordpress

    Zimbra

    Advanced features

    • Notifications system
    • Store password in session
    • Cross Domain Authentication (CDA)
    • Role Based Access Control (RBAC)
    • Use custom functions
    • Use extended functions
    • Reset password by mail (self service)
    • Create an account (self service)
    • Forward logout to applications
    • Secure Token Handler
    • LemonLDAP::NG kubernetes controller
    • Safe jail
    • Login history
    • AuthBasic Handler
    • Fast CGI support
    • See full parameters list

    Mini howtos

    • Modify Manager protection
    • Configuration and sessions in MySQL
    • Configuration and sessions in LDAP
    • Configuration and sessions access by SOAP
    • Integration in Active Directory (LDAP and Kerberos)
    • Create a protocol proxy (SAML to OpenID, CAS to SAML ,…)
    • Convert HTTP header into environment variable

    Exploitation

    • Performances
    • Security
    • SELinux
    • Handler status page
    • MRTG monitoring
    • Logs settings
    • Error messages
    • High Availability

    status.html000066400000000000000000000145371325274564300325270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:status

    Table of Contents

    • Presentation
    • Configuration
      • Apache
      • LemonLDAP::NG

    Handler Status

    Presentation

    When status feature is activated, Handlers and portal will collect statistics and save them in their local cache. This means that if several Handlers are deployed, each will manage its own statistics.

    This page can be browsed for example by MRTG using the MRTG monitoring script.

    The statistics are collected trough a daemon launched by the Handler. It can be seen in system processes, for example:

    perl -MLemonldap::NG::Handler::Status -I/etc/perl -I/usr/local/lib/perl/5.10.1 -I/usr/local/share/perl/5.10.1 -I/usr/lib/perl5 -I/usr/share/perl5 -I/usr/lib/perl/5.10 -I/usr/share/perl/5.10 -I/usr/local/lib/site_perl -I. -I/etc/apache2 -e &Lemonldap::NG::Handler::Status::run(Cache::FileCache,{?          'cache_depth' => 5,?          'cache_root' => '/tmp',?          'directory_umask' => '007',?          'default_expires_in' => 600,?          'namespace' => 'MyNamespace'?        }?);

    Statistics are displayed when calling the status path on an Handler (for example: http://test1.example.com/status).

    Example of status page:

    Configuration

    Apache

    You need to give access to status path in the Handler Apache configuration:

        # Uncomment this to activate status module
        <Location /status>
            Order deny,allow
            Allow from 127.0.0.0/8
            PerlHeaderParserHandler Lemonldap::NG::Handler->status
        </Location>

    Then restart Apache.

    You should change the Allow directive to match administration IP, or use another Apache protection mean.

    LemonLDAP::NG

    Edit lemonldap-ng.ini, and activate status in the handler section:

    [handler]
    # Set status to 1 if you want to have the report of activity (used for
    # example to inform MRTG)
    status = 1

    Then restart Apache.

    upgrade.html000066400000000000000000000343551325274564300326330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:upgrade

    Table of Contents

    • JSON serialization
    • Avoid using lmConfigEditor during upgrade
    • Migration of old configuration
    • Portal autocomplete configuration
    • Support for CentOS/RHEL 5 and CentOS/RHEL 6 dropped
    • Manager components protection
    • AJAX unauthenticated requests in handler
    • Persistent sessions
    • Multi backend
    • Specific Handler
    • SAML conditions checking
    • Unprotect rule in Apache configuration
    • Auto protected CGI

    Upgrade from 1.4 to 1.9

    JSON serialization

    From now, LemonLDAP::NG uses JSON serialization to store configuration and sessions instead of Storable::nfreeze Perl function. This permits one to have heterogenous servers connected to the same LL::NG organization (32/64 bits or different Perl versions). Old format still works but:

    • configuration backends: new format is applied at first configuration save,
    • sessions storages: new format is applied for each new session or when updating an existing session. You can force LemonLDAP::NG to keep the old serialization method by setting useStorable to 1 in sessions backend options if you have some custom hooks.
    If you have more than one server and don't want to stop the SSO service, start upgrading in the following order:
    • servers that have only handlers;
    • portal servers (all together if your load balancer doesn't keep state by user or client IP and if users use the menu);
    • manager server

    Avoid using lmConfigEditor during upgrade

    Some attributes may be removed during each upgrade. Since 1.9, saving is rejected if an attribute isn't declared in manager structure. So don't use lmConfigEditor during upgrade unless you know exactly which changes have been done.

    Migration of old configuration

    Old configuration format is compatible with current version. It will be converted to new format at first save. But you need to check all non-ASCII values that may have been registered with ISO instead of Unicode. You must convert them before saving the new configuration.

    Portal autocomplete configuration

    Modern browsers do not take into account the autocomplete attribute in password fields anymore. This means even if you don't want users to remember the password, the browser will still propose it.

    As it was not used anymore, this option is now removed. See https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues/824 for more details.

    Support for CentOS/RHEL 5 and CentOS/RHEL 6 dropped

    Due to a too old Perl version and some missing modules, LL::NG is no more available for CentOS/RHEL 5 and 6. You need CentOS/RHEL 7 or a Debian based box to run this version of LL::NG.

    Manager components protection

    You can no more set up a different protection parameter for sessions explorer and configuration management. The protection is used for all components, but can use access rules to manage authorizations between configuration, notifications and sessions:

    ^/(manager\.html|conf/) => $uid eq "dwho"
    default => $uid eq "dwho" or $uid eq "rtyler" 

    AJAX unauthenticated requests in handler

    To request for authentication, handlers sent a 302 HTTP code, then portal sent the HTML form even if request was an Ajax one. From now, after being redirected by the Handler, a 401 code will be sent by the portal with a WWW-Authenticate header containing “SSO <portal-URL>”. This is a little HTTP protocol hook created because browsers follow redirection transparently and we have to respond to JSON queries by JSON.

    If you want to keep old behavior, set noAjaxHook to 1 (in General Parameters → Advanced → Portal redirections → Keep redirections for Ajax).

    Persistent sessions

    Persistent sessions have a new attributes:

    • _session_uid: real user identifier
    • _utime: creation timestamp

    These attributes allow one to browse them in the sessions explorer. Old persistent sessions will automatically get these new attributes at user connexion.

    Multi backend

    The Multi backend configuration has changed. Now the stacks are defined in separate attributes:

    • multiAuthStack
    • multiUserDBStack

    So an old configuration like this:

    authentication = Multi LDAP;DBI
    userDB = Multi LDAP;DBI

    Must be replaced by:

    authentication = Multi
    userDB = Multi
    multiAuthStack = LDAP;DBI
    multiUserDBStack = LDAP;DBI

    Specific Handler

    Handler API has changed and specific Handlers have been rewritten. They still work but their configuration must be set in lemonldap-ng.ini file instead of Manager. More details:

    • Zimbra
    • Secure Token
    • Auth Basic

    Note that some specific Handlers have been removed, you will not be able to use them anymore:

    • Sympa AutoLogin
    • UpdateCookie
    • Internal Proxy

    SAML conditions checking

    Since 1.9.6

    The option to disable conditions checking in SAML response has been split into:

    • Time conditions checking
    • Audience conditions checking

    By default, conditions are checked. Set them both to Off if you need to deactivate conditions checking.

    Unprotect rule in Apache configuration

    In 1.4 version, you could unprotect some paths directly in Apache configuration with:

    <Location /test/>
        PerlHeaderParserHandler Lemonldap::NG::Handler->unprotect
    </Location>

    This is no more possible, the unprotect rule must be set in global configuration. Here is an example on how to do it with CLI :

    /usr/share/lemonldap-ng/bin/lemonldap-ng-cli addKey locationRules/test.example.com "(?#unprotect)^/test/" unprotect

    Auto protected CGI

    Lemonldap::NG::Handler::CGI usage has changed, these methods are no more available:

    • authenticate
    • authorize
    • testUri

    You now need to set the protection parameter, see documentation.

    variables.html000066400000000000000000000365771325274564300331640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:variables

    Table of Contents

    • Presentation
    • Modules
    • Connection
    • Authentication
    • Dates
    • SAML
    • Notifications
    • Login history
    • LDAP
    • OpenID
    • OpenID Connect
    • Other

    Variables

    Presentation

    Variables can be used in rules and headers. All rules are concerned:

    • Access rule in virtual host
    • SAML IDP preselection
    • Session opening
    • …

    Variables are stored in the user session. We can distinguish several kind of variables:

    • internal variables, managed by LemonLDAP::NG
    • exported variables collected from UserDB backend
    • macro and groups

    When you know the key of the variable, you just have to prefix it with the dollar sign to use it, for example to test if uid variable match coudot :

    $uid eq "coudot"
    You can inspect a user session with the sessions explorer (in Manager)

    Below are documented internal variables.

    Modules

    Register what module was used for authentication, user data, password, …

    Key Description
    _auth Authentication module
    _userDB User module
    _passwordDB Password module
    _issuerDB Issuer module (can be multivalued)
    _authChoice User choice done if authentication choice was used
    _authMulti Full name of authentication module (with #label) used in Multi
    _userDBMulti Full name of user module (with #label) used in Multi

    Connection

    Datas concerning the first connection to the portal

    Key Description
    ipAddr IP of the user (can be the X Forwarded For IP if trusted proxies are configured)
    _timezone Timezone of the user, set with javascript from standard login form (will be empty if other authentication methods are used)
    _url URL used before being redirected to the portal (empty if portal was used as entry point)

    Authentication

    Datas around the authentication process.

    Key Description
    _session_id Session identifier (carried in cookie)
    _user User found from login process
    _password Password found from login process (only if password store in session is configured)
    authenticationLevel Authentication level

    Dates

    Key Description
    _utime Timestamp of session creation
    startTime Date of session creation
    updateTime Date of session last modification
    _lastAuthnUTime Timestamp of last authentication time

    SAML

    Datas related to SAML protocol

    Key Description
    _idp Name of IDP used for authentication
    _idpConfKey Configuration key of IDP used for authentication
    _samlToken SAML token
    _lassoSessionDump Lasso session dump
    _lassoIdentityDump Lasso identity dump

    Notifications

    Key Description
    _notification_id Date of validation of the notification id

    Login history

    Key Description
    loginHistory HASH of login success and failures

    LDAP

    Only with UserDB LDAP.

    Key Description
    dn Distinguished name

    OpenID

    Key Description
    _openid_id Consent to share attribute id trough OpenID

    OpenID Connect

    Key Description
    OpenIDConnect_IDToken ID Token
    OpenIDConnect_OP Configuration key of OP used for authentication
    OpenIDConnect_access_token OAuth2 Access Token used to get UserInfo data
    _oidc_consent_scope_rp Scope for which consent was given for RP rp
    _oidc_consent_time_rp Time when consent was given for RP rp

    Other

    Key Description
    appsListOrder Order of categories in the menu
    _session_kind Type of session (SSO, Persistent, …)
    writingrulesand_headers.html000066400000000000000000000250451325274564300361140ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/doc/pages/documentation/current documentation:1.9:writingrulesand_headers

    Writing rules and headers

    Lemonldap::NG manage applications by their hostname (Apache's virtualHosts). Rules are used to protect applications, headers are HTTP headers added to the request to give datas to the application (for logs, profiles,…).

    Note that variables designed by $xx correspond to the name of the exported variables or macro names.

    Rules

    A rule associates a regular expression to a Perl boolean expression or a keyword.

    Examples:

    Goal Regular expression Rule
    Restrict /admin/ directory to user bart.simpson ^/admin/ $uid eq "bart.simpson"
    Restrict /js/ and /css/ directory to authenticated users ^/(css|js)/ accept
    Deny access to /config/ directory ^/config/ deny
    Do not restrict /public/ ^/public/ skip
    Makes authentication optional, but authenticated users are seen as such (that is, user data are sent to the app through HTTP headers) ^/forum/ unprotect
    Restrict access to the whole site to users that have the LDAP description field set to “LDAP administrator” (must be set in exported variables) default $description eq "LDAP administrator"

    The “default” access rule is used if no other access rule match the current URL.

    • Comments can be used to order your rules: rules are applied in the alphabetical order of comment (or regexp in there is no comment). See security chapter to learn more about writing good rules.
    • See performances to know how to use macros and groups in rules.

    Rules can also be used to intercept logout URL:

    Goal Regular expression Rule
    Logout user from Lemonldap::NG and redirect it to http://intranet/ ^/index.php\?logout logout_sso http://intranet/
    Logout user from current application and redirect it to the menu ^/index.php\?logout logout_app https://auth.example.com/
    Logout user from current application and from Lemonldap::NG and redirect it to http://intranet/ ^/index.php\?logout logout_app_sso http://intranet/
    logout_app and logout_app_sso rules are not available on Nginx, only on Apache.
    By default, user will be redirected on portal if no URL defined, or on the specified URL if any.
    Only current application is concerned by logout_app* targets. Be careful with some applications which doesn't verify Lemonldap::NG headers after having created their own cookies. If so, you can redirect users to a HTML page that explain that it is safe to close browser after disconnect.

    Headers

    Headers are associations between an header name and a perl expression that returns a string. Headers are used to give user datas to the application.

    Examples:

    Goal Header name Header value
    Give the uid (for accounting) Auth-User $uid
    Give a static value Some-Thing “static-value”
    Give display name Display-Name $givenName.“ ”.$surName
    Give a non ascii data Display-Name encode_base64($givenName." ".$surName, '')

    As described in performances chapter, you can use macros, local macros,…

    • Since many HTTP servers refuse non ascii headers, it is recommended to use encode_base64() function to transmit those headers
    • Don't forget to add an empty string as second argument of encode_base64 to avoid insert of “newline” characters in result
    • Header names must contain only letters and “-” character
    By default, SSO cookie is hidden, so protected applications cannot get SSO session key. But you can forward this key if it is really needed:
    Session-ID => $_session_id
    lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/000077500000000000000000000000001325274564300237775ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/apache2.conf000066400000000000000000000040661325274564300261570ustar00rootroot00000000000000PidFile conf/apache2.pid Timeout 300 KeepAlive On MaxKeepAliveRequests 100 KeepAliveTimeout 5 HostnameLookups Off LogLevel debug LoadModule cgi_module /usr/lib/apache2/modules/mod_cgi.so LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so LoadModule authz_host_module /usr/lib/apache2/modules/mod_authz_host.so LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so Options FollowSymLinks AllowOverride None = 2.3> Require all denied Order Deny,Allow Deny from all LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined ErrorLog conf/apache2.log CustomLog conf/apache2.log vhost_combined LoadModule alias_module /usr/lib/apache2/modules/mod_alias.so LoadModule dir_module /usr/lib/apache2/modules/mod_dir.so LoadModule env_module /usr/lib/apache2/modules/mod_env.so LoadModule mime_module /usr/lib/apache2/modules/mod_mime.so LoadModule fcgid_module /usr/lib/apache2/modules/mod_fcgid.so FcgidConnectTimeout 20 FcgidProcessTableFile conf/fcgid_shm FcgidIPCDir conf/ LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so LoadModule setenvif_module /usr/lib/apache2/modules/mod_setenvif.so LoadModule perl_module /usr/lib/apache2/modules/mod_perl.so PerlPassEnv LLNG_DEFAULTCONFFILE Include conf/env.conf AddHandler fcgid-script .fcgi LoadModule filter_module /usr/lib/apache2/modules/mod_filter.so TypesConfig /etc/mime.types AddLanguage en .en AddLanguage fr .fr LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so StartServers 2 MinSpareThreads 25 MaxSpareThreads 75 ThreadLimit 64 ThreadsPerChild 25 MaxRequestWorkers 150 MaxConnectionsPerChild 0 Include conf/manager-apache2.X.conf Include conf/portal-apache2.X.conf Include conf/handler-apache2.X.conf Include conf/test-apache2.X.conf lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/custom.pm000066400000000000000000000002041325274564300256430ustar00rootroot00000000000000package My; sub hello { return 'Hello'; } sub get_uri { return $_[0]; } sub get_additional_arg { return $_[1]; } 1; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/env.conf000066400000000000000000000007041325274564300254370ustar00rootroot00000000000000Listen 127.0.0.1:__port__ FcgidInitialEnv LLNG_DEFAULTCONFFILE __pwd__/e2e-tests/conf/lemonldap-ng.ini SetEnv LLNG_DEFAULTCONFFILE __pwd__/e2e-tests/conf/lemonldap-ng.ini use lib "__pwd__/lemonldap-ng-common/blib/lib"; use lib "__pwd__/lemonldap-ng-handler/blib/lib"; use lib "__pwd__/lemonldap-ng-portal/blib/lib"; use lib "__pwd__/lemonldap-ng-manager/blib/lib"; require "__pwd__/e2e-tests/custom.pm"; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/form.html000066400000000000000000000005421325274564300256310ustar00rootroot00000000000000
    lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/000077500000000000000000000000001325274564300254145ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/01-redirect.js000066400000000000000000000020631325274564300277720ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG', function() { describe('Redirection mechanism', function() { it('should redirect to portal', function() { browser.ignoreSynchronization = true; browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/'); expect(browser.getCurrentUrl()).toMatch(new RegExp('^http://auth.example.com(:' + process.env.TESTWEBSERVERPORT + ')?/\\?url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29tOjE5ODc2Lw==')); }); it('should accept authentication as dwho/dwho', function() { browser.driver.findElement(by.xpath("//input[@name='user']")).sendKeys('dwho'); browser.driver.findElement(by.xpath("//input[@name='password']")).sendKeys('dwho'); browser.driver.findElement(by.xpath("//button[@type='submit']")).click(); }); it('should redirect to test1.example.com', function() { expect(browser.getCurrentUrl()).toMatch(new RegExp('^http://test1.example.com(:' + process.env.TESTWEBSERVERPORT + ')?')); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/02-headers.js000066400000000000000000000016001325274564300276010ustar00rootroot00000000000000'use strict'; describe('Lemonldap::NG handler', function() { describe('Header insertion mechanism', function() { it('should display headers', function() { browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/'); expect(browser.driver.findElement(by.id('v-Auth-User')).getText()).toEqual('dwho'); expect(browser.driver.findElement(by.id('v-Ip-Addr')).getText()).toMatch(/^\d+\.\d+\.\d+\.\d+$/); }); it('should display custom functions results', function() { expect(browser.driver.findElement(by.id('v-Hello')).getText()).toEqual('Hello'); expect(browser.driver.findElement(by.id('v-Uri')).getText()).toEqual('/'); expect(browser.driver.findElement(by.id('v-Additional-Arg')).getText()).toEqual('header-added'); expect(browser.driver.findElement(by.id('v-Base64')).getText()).toEqual('YTpi'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/03-post.js000066400000000000000000000006531325274564300271630ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG', function() { describe('Form replay mechanism', function() { it('should redirect to index.pl', function() { browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/form.html'); expect(browser.driver.findElement(by.id('field_postuid')).getText()).toEqual('dwho'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/04-logout.js000066400000000000000000000006251325274564300275070ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG auth mechanism', function() { it('should allow logout', function() { browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/logout'); expect(browser.getCurrentUrl()).toMatch(new RegExp('^http://auth.example.com(:' + process.env.TESTWEBSERVERPORT + ')?')); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/10-logout_app.js000066400000000000000000000015661325274564300303510ustar00rootroot00000000000000'use strict'; describe('Lemonldap::NG', function() { describe('Logout_app', function() { it('should accept authentication as dwho/dwho', function() { browser.driver.get('http://auth.example.com:' + process.env.TESTWEBSERVERPORT + '/'); browser.driver.findElement(by.xpath("//input[@name='user']")).sendKeys('dwho'); browser.driver.findElement(by.xpath("//input[@name='password']")).sendKeys('dwho'); browser.driver.findElement(by.xpath("//button[@type='submit']")).click(); }); it('should allow logout_app', function() { browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/index.pl?logout_app'); }); it('should keep session', function() { expect(browser.getCurrentUrl()).toMatch(new RegExp('^http://test1.example.com(:' + process.env.TESTWEBSERVERPORT + ')?/index.pl\\?foo=1')); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/handler/11-logout_app_sso.js000066400000000000000000000013701325274564300312270ustar00rootroot00000000000000'use strict'; describe('Lemonldap::NG', function() { describe('Logout_app', function() { it('should allow logout_app_sso', function() { browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/index.pl?logout_all'); }); it('should redirect after logout', function() { expect(browser.getCurrentUrl()).toMatch(new RegExp('^https://lemonldap-ng\.org/welcome')); }); it('should redirect to portal', function() { browser.driver.get('http://test1.example.com:' + process.env.TESTWEBSERVERPORT + '/'); expect(browser.getCurrentUrl()).toMatch(new RegExp('^http://auth.example.com(:' + process.env.TESTWEBSERVERPORT + ')?/\\?url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29tOjE5ODc2Lw==')); }); }); }); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/lemonldap-ng.ini000066400000000000000000000013341325274564300270560ustar00rootroot00000000000000[all] logLevel = debug [configuration] type=File dirName=__pwd__/e2e-tests/conf localStorage=Cache::FileCache localStorageOptions={ \ 'namespace' => 'lemonldap-ng-config',\ 'default_expires_in' => 600, \ 'directory_umask' => '007', \ 'cache_root' => '__pwd__/e2e-tests/conf', \ 'cache_depth' => 0, \ } [portal] notification = 0 checkXSS = 0 portalSkin = pastel [handler] https = 0 status = 1 useRedirectOnError = 0 [manager] protection = manager logLevel = error staticPrefix = /static languages = fr, en templateDir = __pwd__/lemonldap-ng-manager/site/templates [apply] lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/lmConf-1.js000066400000000000000000000132161325274564300257140ustar00rootroot00000000000000{ "applicationList": { "0001-cat": { "catname": "Sample applications", "0002-app": { "options": { "description": "A simple application displaying authenticated user", "display": "auto", "logo": "demo.png", "name": "Application Test 1", "uri": "http://test1.example.com:__port__/" }, "type": "application" }, "0003-app": { "options": { "description": "The same simple application displaying authenticated user", "display": "auto", "logo": "thumbnail.png", "name": "Application Test 2", "uri": "http://test2.example.com:__port__/" }, "type": "application" }, "type": "category" }, "0004-cat": { "catname": "Administration", "0005-app": { "options": { "description": "Configure LemonLDAP::NG WebSSO", "display": "auto", "logo": "configure.png", "name": "WebSSO Manager", "uri": "http://manager.example.com:__port__/manager.html" }, "type": "application" }, "0006-app": { "options": { "description": "Explore WebSSO notifications", "display": "auto", "logo": "database.png", "name": "Notifications explorer", "uri": "http://manager.example.com:__port__/notifications.html" }, "type": "application" }, "0007-app": { "options": { "description": "Explore WebSSO sessions", "display": "auto", "logo": "database.png", "name": "Sessions explorer", "uri": "http://manager.example.com:__port__/sessions.html" }, "type": "application" }, "type": "category" }, "0008-cat": { "catname": "Documentation", "0009-app": { "options": { "description": "Documentation supplied with LemonLDAP::NG", "display": "on", "logo": "help.png", "name": "Local documentation", "uri": "http://manager.example.com:__port__/doc/" }, "type": "application" }, "0010-app": { "options": { "description": "Official LemonLDAP::NG Website", "display": "on", "logo": "network.png", "name": "Offical Website", "uri": "http://lemonldap-ng.org/" }, "type": "application" }, "type": "category" } }, "authentication": "Demo", "cfgAuthor": "The LemonLDAP::NG team", "cfgAuthorIP": "127.0.0.1", "cfgDate": 1428138808, "cfgLog": "Default configuration provided by LemonLDAP::NG team", "cfgNum": "1", "cookieName": "lemonldap", "customFunctions": "My::hello My::get_uri My::get_additional_arg", "demoExportedVars": { "cn": "cn", "mail": "mail", "uid": "uid" }, "domain": "example.com", "exportedHeaders": { "test1.example.com": { "Auth-User": "$uid", "Ip-Addr": "$ipAddr", "Hello": "hello()", "Uri": "get_uri()", "Additional-Arg": "get_additional_arg('header-added')", "Base64": "encode_base64('a:b','')" }, "test2.example.com": { "Auth-User": "$uid" } }, "exportedVars": { "UA": "HTTP_USER_AGENT" }, "globalStorage": "Apache::Session::File", "globalStorageOptions": { "Directory": "__pwd__/e2e-tests/conf/sessions", "LockDirectory": "__pwd__/e2e-tests/conf/sessions/lock", "generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256" }, "groups": {}, "key": "qwertyui", "localSessionStorageOptions": { "cache_depth": 3, "cache_root": "__pwd__/e2e-tests/conf", "default_expires_in": 600, "directory_umask": "007", "namespace": "lemonldap-ng-sessions" }, "locationRules": { "manager.example.com": { "(?#Configuration)^/(manager\\.html|conf/)": "$uid eq \"dwho\"", "(?#Notifications)^/notifications": "$uid eq \"dwho\" or $uid eq \"rtyler\"", "(?#Sessions)^/sessions": "$uid eq \"dwho\" or $uid eq \"rtyler\"", "default": "$uid eq \"dwho\"" }, "test1.example.com": { "^/logout": "logout_sso", "^/index.pl\\?logout_app$": "logout_app http://test1.example.com:__port__/index.pl?foo=1", "^/index.pl\\?logout_all$": "logout_app_sso http://lemonldap-ng.org/welcome/", "default": "accept" }, "test2.example.com": { "^/logout": "logout_sso", "default": "accept" } }, "loginHistoryEnabled": 1, "macros": { "_whatToTrace": "$_auth eq 'SAML' ? \"$_user\\@$_idpConfKey\" : \"$_user\"" }, "notification": 1, "notificationStorage": "File", "notificationStorageOptions": { "dirName": "__pwd__/e2e-tests/conf" }, "passwordDB": "Demo", "persistentStorage": "Apache::Session::File", "persistentStorageOptions": { "Directory": "__pwd__/e2e-tests/conf/persistents", "LockDirectory": "__pwd__/e2e-tests/conf/persistents/lock", "generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256" }, "portal": "http://auth.example.com:__port__/", "post": { "test2.example.com": {}, "manager.example.com": {}, "test1.example.com": { "/form.html": { "vars": [ ["postuid", "$_user"], ["postmail", "'x@x.org'"], ["poststatic", "'static content'"]], "jqueryUrl": "http://manager.example.com:19876/static/bwr/jquery/dist/jquery.js", "buttonSelector": "#bt", "formSelector": "#test", "target": "/index.pl" } } }, "registerDB": "Null", "reloadUrls": {}, "securedCookie": 0, "sessionDataToRemember": {}, "timeout": 72000, "userDB": "Demo", "whatToTrace": "_whatToTrace" }lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager-server.cgi000077500000000000000000000007211325274564300274040ustar00rootroot00000000000000#!/usr/bin/perl use warnings; BEGIN { $pwd = `pwd`; chomp $pwd; eval qq{ use lib "$pwd/../lemonldap-ng-common/blib/lib"; use lib "$pwd/../lemonldap-ng-handler/blib/lib"; use lib "$pwd/../lemonldap-ng-portal/blib/lib"; use lib "$pwd/../lemonldap-ng-manager/blib/lib"; }; die $@ if ($@); } use Lemonldap::NG::Manager; use Plack::Handler::CGI; Plack::Handler::CGI->new->run( Lemonldap::NG::Manager->run( {} ) ); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager-server.fcgi000077500000000000000000000012211325274564300275460ustar00rootroot00000000000000#!/usr/bin/perl use warnings; BEGIN { $pwd = `pwd`; chomp $pwd; eval qq{ use lib "$pwd/../lemonldap-ng-common/blib/lib"; use lib "$pwd/../lemonldap-ng-handler/blib/lib"; use lib "$pwd/../lemonldap-ng-portal/blib/lib"; use lib "$pwd/../lemonldap-ng-manager/blib/lib"; }; die $@ if ($@); } use Plack::Handler::FCGI; use Lemonldap::NG::Manager; # Roll your own my $server = Plack::Handler::FCGI->new(); #$server->run( # sub { # use Data::Dumper; # return [ "200", [ 'Content-Type' => 'text/plain' ], [ Dumper(\@_,\%ENV) ] ]; # } #); $server->run( Lemonldap::NG::Manager->run( {} ) ); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/000077500000000000000000000000001325274564300254115ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/00-auth.js000066400000000000000000000010771325274564300271320ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG', function() { describe('Auth mechanism', function() { it('should want to authenticate', function() { browser.driver.get('http://auth.example.com:' + process.env.TESTWEBSERVERPORT + '/'); browser.driver.findElement(by.xpath("//input[@name='user']")).sendKeys('dwho'); browser.driver.findElement(by.xpath("//input[@name='password']")).sendKeys('dwho'); browser.driver.findElement(by.xpath("//button[@type='submit']")).click(); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/01-tree.js000066400000000000000000000025721325274564300271320ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('Tree display', function() { it('should display 9 main nodes', function() { browser.get('/'); var mainNodes = element.all(by.repeater('node in data track by node.id')); expect(mainNodes.count()).toEqual(9); }); it('should find a rule', function() { browser.get('/#/confs/1'); var vhs = element(by.id('a-virtualHosts')); vhs.click(); var vh = element(by.id('a-virtualHosts/manager.example.com')); vh.click(); var r = element(by.id('a-virtualHosts/manager.example.com/locationRules')); r.click(); var def = element.all(by.id("t-virtualHosts/manager.example.com/locationRules/1")); expect(def.count()).toEqual(1); }); it('should find 19 auth/user modules but only Demo visible', function() { browser.get('/#/confs/1'); element(by.id('a-generalParameters')).click(); element(by.id('a-authParams')).click(); /* todo */ }); it('should have a hash form if a key is clicked', function() { element(by.id('a-demoParams')).click(); element(by.id('a-demoExportedVars')).click(); element(by.id('t-demoExportedVars/cn')).click(); var def = element.all(by.id('hashkeyinput')); expect(def.count()).toEqual(1); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/02-lang.js000066400000000000000000000015561325274564300271160ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('translation', function() { it('should translate in english and french', function() { var tests = { "en": "General Parameters", "fr": "Paramètres généraux" }; var els = element.all(by.css('[ng-click="getLanguage(lang)"]')); expect(els.count()).toEqual(4); els.each(function(el) { el.isDisplayed().then(function(isVisible) { if (isVisible) { el.getAttribute('src').then(function(lang) { lang = lang.replace(/^.*\/(\w+)\.png$/, '$1'); el.click(); var gp = element(by.id('t-generalParameters')); expect(gp.getText()).toEqual(tests[lang]); }); } }); }); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/05-form.js000066400000000000000000000073441325274564300271440ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('Form control', function() { it('should display text form', function() { browser.get('/#/confs/1'); element(by.id('a-generalParameters')).click(); element(by.id('a-portalParams')).click(); element(by.id('t-portal')).click(); expect(element.all(by.id('textinput')).count()).toEqual(1); }); it('should modify tree when input is modified', function() { element(by.id('a-virtualHosts')).click(); element(by.id('a-virtualHosts/test1.example.com')).click(); element(by.id('a-virtualHosts/test1.example.com/exportedHeaders')).click(); var hdr = element(by.id('t-virtualHosts/test1.example.com/exportedHeaders/1')); hdr.click(); var i = element.all(by.id('hashkeyinput')); expect(i.count()).toEqual(1); element(by.id('hashkeyinput')).clear().sendKeys('Hello'); expect(hdr.getText()).toEqual('Hello'); }); it('should be able to add keys in hash', function() { browser.get('/#/confs/1'); var els = element.all(by.css('[ng-click="getLanguage(lang)"]')); /* English version */ els.each(function(el) { el.isDisplayed().then(function(isVisible) { if (isVisible) { el.getAttribute('src').then(function(lang) { lang = lang.replace(/^.*\/(\w+)\.png$/, '$1'); if (lang == 'en') el.click(); }); } }); }); /* Variables */ var id = 1; element(by.id('a-variables')).click(); ['exportedVars', 'macros', 'groups'].forEach(function(type) { element(by.id('a-' + type)).click(); element(by.id('t-' + type)).click(); element(by.css('.glyphicon-plus-sign')).click(); expect(element(by.id('t-' + type + '/n' + id)).getText()).toEqual('new'); browser.sleep(3000); id++; }); /* Virtual hosts */ element(by.id('a-virtualHosts')).click(); element(by.id('a-virtualHosts/test1.example.com')).click(); element(by.id('a-virtualHosts/test1.example.com/locationRules')).click(); element(by.id('a-virtualHosts/test1.example.com/exportedHeaders')).click(); element(by.id('a-virtualHosts/test1.example.com/post')).click(); for (var i = 0; i++; i < 3) { /* Rules */ element(by.id('t-virtualHosts/test1.example.com/locationRules')).click(); element.all(by.css('[ng-click="menuClick(button)"]')).each(function(el) { el.getText().then(function(text) { if (text == 'New rule') { el.click(); } }); }); expect(element(by.id('t-virtualHosts/test1.example.com/locationRules/n' + id)).getText()).toEqual('New rule'); id++; /* Headers */ element(by.id('t-virtualHosts/test1.example.com/exportedHeaders')).click(); element.all(by.css('[ng-click="menuClick(button)"]')).each(function(el) { el.getText().then(function(text) { if (text == 'New entry') { el.click(); } }); }); expect(element(by.id('t-virtualHosts/test1.example.com/exportedHeaders/n' + id)).getText()).toEqual('new'); id++; /* Form replay */ element(by.id('t-virtualHosts/test1.example.com/post')).click(); element.all(by.css('[ng-click="menuClick(button)"]')).each(function(el) { el.getText().then(function(text) { if (text == 'New form replay') { el.click(); } }); }); expect(element(by.id('t-virtualHosts/test1.example.com/post/n' + id)).getText()).toMatch(/^https?:\/\//); id++; } }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/06-form.js000066400000000000000000000012571325274564300271420ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('Form control, part 2', function() { it('should display portal skin choice', function() { browser.get('/#/confs/1'); element(by.id('a-generalParameters')).click(); element(by.id('a-portalParams')).click(); element(by.id('a-portalCustomization')).click(); element(by.id('t-portalSkin')).click(); element(by.css('[ng-click="showModal(\'portalSkinChoice.html\')"]')).click(); var skinChoice = element.all(by.repeater('b in currentNode.select')); expect(skinChoice.count()).toEqual(4); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/07-authParams.js000066400000000000000000000035001325274564300302760ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('Form control, part 3 - authParams', function() { it('should display auth modules chosen', function() { browser.get('/#/confs/1'); element(by.id('a-generalParameters')).click(); element(by.id('a-authParams')).click(); element(by.id('t-authentication')).click(); expect(element(by.id('t-demoParams')).isDisplayed()).toBeTruthy(); element(by.xpath("//option[@value='Apache']")).click(); expect(element(by.id('t-apacheParams')).isDisplayed()).toBeTruthy(); }); it('should display auth modules chosen with authChoice', function() { element(by.xpath("//option[@value='Choice']")).click(); expect(element(by.id('t-choiceParams')).isDisplayed()).toBeTruthy(); element(by.id('a-choiceParams')).click(); element(by.id('t-authChoiceModules')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('a-authChoiceModules')).click(); element(by.id('t-authChoiceModules/n1')).click(); element(by.xpath("//option[@value='BrowserID']")).click(); expect(element(by.id('t-browseridParams')).isDisplayed()).toBeTruthy(); }); it('should display auth modules chosen with authMulti', function() { element(by.id('t-authentication')).click(); element(by.xpath("//option[@value='Multi']")).click(); expect(element(by.id('t-multiParams')).isDisplayed()).toBeTruthy(); element(by.id('a-multiParams')).click(); element(by.id('t-multiAuthStack')).click(); element(by.id('textinput')).sendKeys('LDAP 1==0;CAS 1==1'); expect(element(by.id('t-ldapParams')).isDisplayed()).toBeTruthy(); expect(element(by.id('t-casParams')).isDisplayed()).toBeTruthy(); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/07-utf8.js000066400000000000000000000022651325274564300270660ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('Apply mechanism', function() { it('should be able to send UTF-8 chars', function() { browser.get('/#/confs/latest'); element(by.id('a-generalParameters')).click(); element(by.id('a-advancedParams')).click(); element(by.id('a-security')).click(); element(by.id('t-key')).click(); element(by.id('pwdinput')).clear().sendKeys('éà©®'); }); it('should save new configuration', function() { element(by.id('save')).click(); element(by.id('longtextinput')).sendKeys('UTF-8 tests'); element(by.id('saveok')).click(); element(by.id('messageok')).click(); expect(element(by.id('cfgnum')).getText()).toEqual('2'); }); it('should restitute UTF chars', function() { element(by.id('a-generalParameters')).click(); element(by.id('a-advancedParams')).click(); element(by.id('a-security')).click(); element(by.id('t-key')).click(); element(by.id('showp')).click(); expect(element(by.id('pwdinput')).getAttribute('value')).toEqual('éà©®'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/08-apply.js000066400000000000000000000026001325274564300273170ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('Apply mechanism', function() { it('should be able to add reload urls', function() { browser.get('/#/confs/latest'); element(by.id('a-generalParameters')).click(); element(by.id('t-reloadUrls')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('a-reloadUrls')).click(); element(by.id('t-reloadUrls/n1')).click(); element(by.id('hashkeyinput')).clear().sendKeys('auth.example.com:19876'); element(by.id('hashvalueinput')).clear().sendKeys('http://auth.example.com:19876/static/nothing'); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('t-reloadUrls/n2')).click(); element(by.id('hashkeyinput')).clear().sendKeys('manager.example.com:19876'); element(by.id('hashvalueinput')).clear().sendKeys('http://auth.example.com:19876/static/js/manager.js'); }); it('should save new configuration', function() { element(by.id('save')).click(); element(by.id('longtextinput')).sendKeys('Reload URLs test'); element(by.id('saveok')).click(); expect(element.all(by.repeater('item in item.items')).count()).toEqual(2); element(by.id('messageok')).click(); expect(element(by.id('cfgnum')).getText()).toEqual('3'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/10-saml-config.js000066400000000000000000000035461325274564300303740ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('SAML configuration', function() { it('should enable SAML', function() { browser.get('/#/confs/latest'); element(by.id('a-generalParameters')).click(); element(by.id('a-issuerParams')).click(); element(by.id('a-issuerDBSAML')).click(); element(by.id('t-issuerDBSAMLActivation')).click(); element(by.id('bopeOn')).click(); }); it('should generate a signature key', function() { element(by.id('a-samlServiceMetaData')).click() element(by.id('a-samlServiceSecurity')).click() var el = element(by.id('t-samlServiceSecuritySig')); el.click() element(by.css('.glyphicon-plus-sign')).click(); element(by.id('passwordok')).click(); browser.sleep(500); }); it('should generate an encryption key', function() { var el = element(by.id('t-samlServiceSecurityEnc')); el.click() element(by.css('.glyphicon-plus-sign')).click(); element(by.id('passwordok')).click(); browser.sleep(500); }); it('should configure organization dysplay name', function() { element(by.id('a-samlOrganization')).click(); element(by.id('t-samlOrganizationDisplayName')).click(); element(by.id('textinput')).clear().sendKeys('Org1'); }); it('should configure organization name', function() { element(by.id('t-samlOrganizationName')).click(); element(by.id('textinput')).clear().sendKeys('Org1'); }); it('should save new configuration', function() { element(by.id('save')).click(); element(by.id('longtextinput')).sendKeys('Activate SAML'); element(by.id('saveok')).click(); element(by.id('messageok')).click(); expect(element(by.id('cfgnum')).getText()).toEqual('4'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/11-oidcop-config.js000066400000000000000000000061351325274564300307130ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('OIDC OP configuration', function() { it('should add an OIDC OP', function() { browser.get('/#/confs/latest'); element(by.id('t-oidcOPMetaDataNodes')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('promptok')).click(); browser.sleep(500); element(by.id('a-oidcOPMetaDataNodes/new__op-example')).click(); element(by.id('t-oidcOPMetaDataNodes/new__op-example/oidcOPMetaDataJSON')).click(); element(by.id('filetext')).sendKeys('{"a":"b"}'); element(by.id('t-oidcOPMetaDataNodes/new__op-example/oidcOPMetaDataJWKS')).click(); element(by.id('filetext')).sendKeys('{"c":"d"}'); element(by.id('t-oidcOPMetaDataNodes/new__op-example/oidcOPMetaDataExportedVars')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('a-oidcOPMetaDataNodes/new__op-example/oidcOPMetaDataExportedVars')).click(); element(by.id('t-oidcOPMetaDataNodes/new__op-example/oidcOPMetaDataExportedVars/n1')).click(); element(by.id('hashkeyinput')).clear().sendKeys('MyKey'); element(by.id('hashvalueinput')).clear().sendKeys('MyValue'); element(by.id('a-oidcOPMetaDataOptions')).click(); element(by.id('a-oidcOPMetaDataOptionsConfiguration')).click(); element(by.id('t-oidcOPMetaDataNodes/new__op-example/oidcOPMetaDataOptionsConfigurationURI')).click(); element(by.id('textinput')).clear().sendKeys('http://my-partner.com'); }); it('should save new configuration', function() { element(by.id('save')).click(); element(by.id('longtextinput')).sendKeys('Create OIDC OP'); element(by.id('saveok')).click(); element(by.id('messageok')).click(); expect(element(by.id('cfgnum')).getText()).toEqual('5'); }); it('should restore configured values', function() { element(by.id('a-oidcOPMetaDataNodes')).click(); element(by.id('a-oidcOPMetaDataNodes/op-example')).click(); element(by.id('t-oidcOPMetaDataNodes/op-example/oidcOPMetaDataJSON')).click(); expect(element(by.id('filetext')).getAttribute('value')).toEqual('{"a":"b"}'); element(by.id('t-oidcOPMetaDataNodes/op-example/oidcOPMetaDataJWKS')).click(); expect(element(by.id('filetext')).getAttribute('value')).toEqual('{"c":"d"}'); element(by.id('a-oidcOPMetaDataNodes/op-example/oidcOPMetaDataExportedVars')).click(); browser.sleep(500); element(by.id('t-oidcOPMetaDataNodes/op-example/oidcOPMetaDataExportedVars/1')).click(); expect(element(by.id('hashkeyinput')).getAttribute('value')).toEqual('MyKey'); expect(element(by.id('hashvalueinput')).getAttribute('value')).toEqual('MyValue'); element(by.id('a-oidcOPMetaDataOptions')).click(); element(by.id('a-oidcOPMetaDataOptionsConfiguration')).click(); element(by.id('t-oidcOPMetaDataNodes/op-example/oidcOPMetaDataOptionsConfigurationURI')).click(); expect(element(by.id('textinput')).getAttribute('value')).toEqual('http://my-partner.com'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/12-oidcrp-config.js000066400000000000000000000065241325274564300307210ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { describe('OIDC RP configuration', function() { it('should add an OIDC RP', function() { browser.get('/#/confs/latest'); element(by.id('t-oidcRPMetaDataNodes')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('promptok')).click(); browser.sleep(500); element(by.id('a-oidcRPMetaDataNodes/new__rp-example')).click(); element(by.id('t-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataExportedVars')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('a-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataExportedVars')).click(); element(by.id('t-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataExportedVars/n1')).click(); element(by.id('hashkeyinput')).clear().sendKeys('MyKey'); element(by.id('hashvalueinput')).clear().sendKeys('MyValue'); element(by.id('a-oidcRPMetaDataOptions')).click(); element(by.id('a-oidcRPMetaDataOptionsAuthentication')).click(); element(by.id('t-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataOptionsClientID')).click(); element(by.id('textinput')).clear().sendKeys('MyClientID'); element(by.id('t-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataOptionsExtraClaims')).click(); element(by.css('.glyphicon-plus-sign')).click(); element(by.id('a-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataOptionsExtraClaims')).click(); element(by.id('t-oidcRPMetaDataNodes/new__rp-example/oidcRPMetaDataOptionsExtraClaims/n2')).click(); element(by.id('hashkeyinput')).clear().sendKeys('MyClaim'); element(by.id('hashvalueinput')).clear().sendKeys('MyAttribute'); }); it('should save new configuration', function() { element(by.id('save')).click(); element(by.id('longtextinput')).sendKeys('Create OIDC RP'); element(by.id('saveok')).click(); element(by.id('messageok')).click(); expect(element(by.id('cfgnum')).getText()).toEqual('6'); }); it('should restore configured values', function() { element(by.id('a-oidcRPMetaDataNodes')).click(); element(by.id('a-oidcRPMetaDataNodes/rp-example')).click(); element(by.id('a-oidcRPMetaDataNodes/rp-example/oidcRPMetaDataExportedVars')).click(); browser.sleep(500); element(by.id('t-oidcRPMetaDataNodes/rp-example/oidcRPMetaDataExportedVars/1')).click(); expect(element(by.id('hashkeyinput')).getAttribute('value')).toEqual('MyKey'); expect(element(by.id('hashvalueinput')).getAttribute('value')).toEqual('MyValue'); element(by.id('a-oidcRPMetaDataOptions')).click(); element(by.id('a-oidcRPMetaDataOptionsAuthentication')).click(); element(by.id('t-oidcRPMetaDataNodes/rp-example/oidcRPMetaDataOptionsClientID')).click(); expect(element(by.id('textinput')).getAttribute('value')).toEqual('MyClientID'); element(by.id('a-oidcRPMetaDataNodes/rp-example/oidcRPMetaDataOptionsExtraClaims')).click(); browser.sleep(5000); element(by.id('t-oidcRPMetaDataNodes/rp-example/oidcRPMetaDataOptionsExtraClaims/1')).click(); expect(element(by.id('hashkeyinput')).getAttribute('value')).toEqual('MyClaim'); expect(element(by.id('hashvalueinput')).getAttribute('value')).toEqual('MyAttribute'); }); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/35-apply-old-conf.js000066400000000000000000000013401325274564300310160ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG Manager', function() { it('should be able to restore an old configuration', function() { browser.get('/#/confs/1'); element(by.id('save')).click(); element(by.id('longtextinput')).sendKeys('Restore conf 1'); element(by.id('saveok')).click(); browser.sleep(500); element(by.id('messageok')).click(); browser.sleep(500); element(by.id('forcesave')).click(); element(by.id('longtextinput')).sendKeys('Force to restore conf 1'); element(by.id('saveok')).click(); element(by.id('messageok')).click(); expect(element(by.id('cfglog')).getText()).toEqual('Force to restore conf 1'); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/40-sessions.js000066400000000000000000000034021325274564300300350ustar00rootroot00000000000000'use strict'; describe('Lemonldap::NG Manager', function() { it('should display other modules', function() { browser.get('/'); var links = element.all(by.repeater('l in links')); expect(links.count()).toEqual(3); element(by.xpath("//a[@href='sessions.html']")).click(); }); }); describe('Lemonldap::NG Session explorer', function() { var session, ip; it('Should display at least my session', function() { browser.get('/sessions.html'); var t = element.all(by.repeater('node in data track by node.id')); expect(t.count()).toBeGreaterThan(0); element(by.id("a-d")).click(); t = element.all(by.repeater('node in node.nodes track by node.id')); expect(t.count()).toBeGreaterThan(0); element(by.id("a-dwho")).click(); browser.manage().getCookie('lemonldap').then(function(cookie) { expect(cookie.value).toBeDefined(); expect(cookie.value).not.toEqual(''); session = cookie.value; element(by.id("s-" + session)).click(); var t = element.all(by.repeater('node in currentSession.nodes')); expect(t.count()).toBeGreaterThan(0); ip = element(by.id("v-ipAddr")); expect(ip.getText()).toMatch(/^\d+\.\d+\.\d+\.\d+$/); }); }); it('Should display my IP address', function() { element(by.id('a-ip')).click(); var t = element.all(by.repeater('node in data track by node.id')); expect(t.count()).toBeGreaterThan(0); element(by.id("a-127")).click(); element(by.id("a-127.0")).click(); element(by.id("a-127.0.0")).click(); element(by.id("a-127.0.0.1")).click(); element(by.id("a-dwho")).click(); element(by.id("s-" + session)).click(); var t = element.all(by.repeater('node in currentSession.nodes')); expect(t.count()).toBeGreaterThan(0); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/99-logout.js000066400000000000000000000004311325274564300275150ustar00rootroot00000000000000'use strict'; /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ describe('Lemonldap::NG auth mechanism', function() { it('should allow logout', function() { browser.driver.get('http://auth.example.com:' + process.env.TESTWEBSERVERPORT + '/?logout=1'); }); });lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/manager/README.md000066400000000000000000000021571325274564300266750ustar00rootroot00000000000000#End 2 End Testing (Protractor) To run the end-2-end tests against the application you use [Protractor](https://github.com/angular/protractor). ## Testing with Protractor As a one-time setup, download webdriver. ``` npm run update-webdriver ``` Start the Protractor test runner using the e2e configuration: ``` make e2e_test ``` ## Devel tips { locator_: { using: 'css selector', value: '[ng-click="getLanguage(lang)"]' }, parentElementFinder_: null, opt_actionResult_: { then: [Function: then], cancel: [Function: cancel], isPending: [Function: isPending] }, opt_index_: 1, click: [Function], sendKeys: [Function], getTagName: [Function], getCssValue: [Function], getAttribute: [Function], getText: [Function], getSize: [Function], getLocation: [Function], isEnabled: [Function], isSelected: [Function], submit: [Function], clear: [Function], isDisplayed: [Function], getOuterHtml: [Function], getInnerHtml: [Function], toWireValue: [Function] } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/nginx.conf000066400000000000000000000010261325274564300257700ustar00rootroot00000000000000worker_processes auto; load_module /usr/lib/nginx/modules/ndk_http_module.so; load_module /usr/lib/nginx/modules/ngx_http_lua_module.so; pid conf/nginx.pid; events { worker_connections 768; # multi_accept on; } http { sendfile on; client_body_temp_path conf/; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; error_log conf/nginx.log info; gzip off; include conf/*nginx.conf; access_log conf/nginx.log lm_combined; } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/protractor-conf.js000066400000000000000000000005141325274564300274570ustar00rootroot00000000000000exports.config = { allScriptsTimeout: 11000, specs: ['manager/*.js', 'handler/*.js'], capabilities: { 'browserName': 'chrome' }, chromeOnly: true, baseUrl: 'http://manager.example.com:' + process.env.TESTWEBSERVERPORT + '/', framework: 'jasmine', jasmineNodeOpts: { defaultTimeoutInterval: 30000 } };lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/e2e-tests/test-nginx.conf000066400000000000000000000026361325274564300267550ustar00rootroot00000000000000server { listen __port__; server_name test1.example.com test2.example.com; root __pwd__/e2e-tests/conf/site; # Internal authentication request location = /lmauth { internal; include /etc/nginx/fastcgi_params; fastcgi_pass unix:__pwd__/e2e-tests/conf/llng-fastcgi.sock; # Drop post datas fastcgi_pass_request_body off; fastcgi_param CONTENT_LENGTH ""; # Keep original hostname fastcgi_param HOST $http_host; # Keep original request (LLNG server will received /llauth) fastcgi_param X_ORIGINAL_URI $request_uri; } # Client requests location / { index index.pl; auth_request /lmauth; auth_request_set $lmremote_user $upstream_http_lm_remote_user; auth_request_set $lmlocation $upstream_http_location; error_page 401 $lmlocation; try_files $uri $uri/ =404; include conf/nginx-lua-headers.conf; } # Handle test CGI location ~ \.pl$ { include /etc/nginx/fastcgi_params; fastcgi_pass unix:__pwd__/e2e-tests/conf/llng-fastcgi.sock; fastcgi_param LLTYPE cgi; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_split_path_info ^(.*\.pl)(/.+)$; # Set REMOTE_USER (for FastCGI apps only) fastcgi_param REMOTE_USER $lmremote_user; } location = /status { include /etc/nginx/fastcgi_params; fastcgi_pass unix:__pwd__/e2e-tests/conf/llng-fastcgi.sock; fastcgi_param LLTYPE status; } } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/000077500000000000000000000000001325274564300251105ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/default/000077500000000000000000000000001325274564300265345ustar00rootroot00000000000000llng-fastcgi-server000066400000000000000000000005301325274564300322540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/default# Number of process (default: 7) #NPROC = 7 # Unix socket to listen to SOCKET=__FASTCGISOCKDIR__/llng-fastcgi.sock # Pid file PID=__FASTCGISOCKDIR__/llng-fastcgi-server.pid # User and GROUP USER=__USER__ GROUP=__GROUP__ # Custom functions file #CUSTOM_FUNCTIONS_FILE=/var/lib/lemonldap-ng/myfile.pm # Engine (default to FCGI) #ENGINE=FCGI lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/man/000077500000000000000000000000001325274564300256635ustar00rootroot00000000000000llng-fastcgi-server.1p000066400000000000000000000165621325274564300317360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/man.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .if !\nF .nr F 0 .if \nF>0 \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} .\} .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "llng-fastcgi-server 1" .TH llng-fastcgi-server 1 "2017-11-16" "perl v5.26.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" llng\-fastcgi\-server \- FastCGI server used to provide Lemonldap::NG services to Nginx .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 2 \& # Start server listening to /run/llng.sock with 10 process \& llng\-fastcgi\-server \-u nobody \-g nobody \-s /run/llng.sock \-n 10 .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" llng-fastcgi-server has been designed provides Lemonldap::NG services to Nginx. Portal, manager and handler will be compiled only is used. So this FastCGI server can be used on every Lemonldap::NG server even if it needs only some parts (isolated handlers, portal,...). .SH "PARAMETERS" .IX Header "PARAMETERS" Each parameter can be set by an option or a environment variable. .IP "\-\-pid \-p ($ENV{\s-1PID\s0}): pid file" 4 .IX Item "--pid -p ($ENV{PID}): pid file" .PD 0 .IP "\-\-user \-u ($ENV{\s-1USER\s0}): user" 4 .IX Item "--user -u ($ENV{USER}): user" .IP "\-\-group \-g ($ENV{\s-1GROUP\s0}): group" 4 .IX Item "--group -g ($ENV{GROUP}): group" .IP "\-\-proc \-n ($ENV{\s-1NPROC\s0}): Number of processus for \s-1FCGI\s0" 4 .IX Item "--proc -n ($ENV{NPROC}): Number of processus for FCGI" .IP "\-\-engine \-e ($ENV{\s-1ENGINE\s0}): Plack::Handler engine, default to \s-1FCGI\s0" 4 .IX Item "--engine -e ($ENV{ENGINE}): Plack::Handler engine, default to FCGI" .IP "\-\-socket \-s ($ENV{\s-1SOCKET\s0}): Unix socket" 4 .IX Item "--socket -s ($ENV{SOCKET}): Unix socket" .IP "\-\-customFunctionsFile \-f ($ENV{\s-1CUSTOM_FUNCTIONS_FILE\s0}): file to load for custom functions" 4 .IX Item "--customFunctionsFile -f ($ENV{CUSTOM_FUNCTIONS_FILE}): file to load for custom functions" .ie n .IP "\-\-plackOptions: other options to pass to Plack. This multi-valued parameter must have ""key=value"" values." 4 .el .IP "\-\-plackOptions: other options to pass to Plack. This multi-valued parameter must have ``key=value'' values." 4 .IX Item "--plackOptions: other options to pass to Plack. This multi-valued parameter must have key=value values." .PD .SH "SEE ALSO" .IX Header "SEE ALSO" .SH "AUTHORS" .IX Header "AUTHORS" .IP "Clement Oudot, " 4 .IX Item "Clement Oudot, " .PD 0 .IP "Xavier Guimard, " 4 .IX Item "Xavier Guimard, " .PD .SH "BUG REPORT" .IX Header "BUG REPORT" Use \s-1OW2\s0 system to report bug or ask for features: .SH "DOWNLOAD" .IX Header "DOWNLOAD" Lemonldap::NG is available at .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" .IP "Copyright (C) 2008\-2016 by Xavier Guimard, " 4 .IX Item "Copyright (C) 2008-2016 by Xavier Guimard, " .PD 0 .IP "Copyright (C) 2008\-2016 by Cle\*'ment Oudot, " 4 .IX Item "Copyright (C) 2008-2016 by Cle'ment Oudot, " .PD .PP This library is free software; you can redistribute it and/or modify it under the terms of the \s-1GNU\s0 General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. .PP This program is distributed in the hope that it will be useful, but \s-1WITHOUT ANY WARRANTY\s0; without even the implied warranty of \&\s-1MERCHANTABILITY\s0 or \s-1FITNESS FOR A PARTICULAR PURPOSE.\s0 See the \&\s-1GNU\s0 General Public License for more details. .PP You should have received a copy of the \s-1GNU\s0 General Public License along with this program. If not, see . lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/rc/000077500000000000000000000000001325274564300255145ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/rc/llng-fastcgi-server000077500000000000000000000062151325274564300313240ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: llng-fastcgi-server # Required-Start: $local_fs $remote_fs $network $syslog $named # Required-Stop: $local_fs $remote_fs $network $syslog $named # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: starts the Lemonldap::NG FastCGI server # Description: starts Lemonldap::NG FastCGI server using start-stop-daemon ### END INIT INFO PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=__SBINDIR__/llgn-fastcgi-server NAME=llng-fastcgi-server DESC=llng-fastcgi-server # Include llng-fastcgi-server defaults if available if [ -r __DEFAULTDIR__/llng-fastcgi-server ]; then . __DEFAULTDIR__/llng-fastcgi-server fi STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}" test -x $DAEMON || exit 0 . /lib/init/vars.sh . /lib/lsb/init-functions # Try to extract llng-fastcgi-server pidfile if [ -z "$PID" ]; then PID=__FASTCGISOCKDIR__/llng-fastcgi-server.pid fi if [ -z "$SOCKET" ]; then SOCKET=__FASTCGISOCKDIR__/llng-fastcgi.sock fi for f in "$PID" "$SOCKET"; do DIR=`dirname "$f"` if [ ! -d "$DIR" ]; then mkdir -p "$DIR" if [ -n "$USER" ]; then chown "$USER" "$DIR" fi if [ -n "$GROUP" ]; then chgrp "$GROUP" "$DIR" fi fi done DAEMON_OPTS="-p ${PID} -u ${USER} -g ${GROUP} -s ${SOCKET}" if [ -z "$CUSTOM_FUNCTIONS_FILE" ]; then DAEMON_OPTS="$DAEMON_OPTS -f ${CUSTOM_FUNCTIONS_FILE}" fi start_server() { # Start the daemon/service # # Returns: # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON -- \ $DAEMON_OPTS 2>/dev/null \ || return 2 } stop_server() { # Stops the daemon/service # # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PID --exec $DAEMON RETVAL="$?" sleep 1 return "$RETVAL" } reload_server() { # Function that sends a SIGHUP to the daemon/service start-stop-daemon --stop --signal HUP --quiet --pidfile $PID --exec $DAEMON return 0 } case "$1" in start) log_daemon_msg "Starting $DESC" "$NAME" start_server case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; stop) log_daemon_msg "Stopping $DESC" "$NAME" stop_server case "$?" in 0|1) log_end_msg 0 ;; 2) log_end_msg 1 ;; esac ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" stop_server case "$?" in 0|1) start_server case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; reload|force-reload) log_daemon_msg "Reloading $DESC configuration" "$NAME" reload_server log_end_msg $? ;; status) status_of_proc -p $PID "$DAEMON" "$NAME" && exit 0 || exit $? ;; *) echo "Usage: $NAME {start|stop|restart|reload|force-reload|status}" >&2 exit 3 ;; esac lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/sbin/000077500000000000000000000000001325274564300260435ustar00rootroot00000000000000llng-fastcgi-server000066400000000000000000000152201325274564300315650ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/sbin#!/usr/bin/perl use Plack::Runner; use strict; use warnings; use POSIX; use Getopt::Long; use Lemonldap::NG::Handler::SharedConf; our $VERSION = '1.9.9'; our ( $foreground, $engine, $nproc, $pidFile, $socket, $user, $listen, $group, $customFunctionsFile, %plackOptions ); my %_apps; $SIG{'PIPE'} = 'IGNORE'; $foreground = 0; $engine ||= $ENV{ENGINE} || 'FCGI'; $nproc ||= $ENV{NPROC} || 7; $pidFile ||= $ENV{PID} || '__FASTCGISOCKDIR__/llng-fastcgi.pid'; $socket ||= $ENV{SOCKET} || '__FASTCGISOCKDIR__/llng-fastcgi.sock'; $listen ||= $ENV{LISTEN} || undef; $user ||= $ENV{USER}; $group ||= $ENV{GROUP}; $customFunctionsFile ||= $ENV{CUSTOM_FUNCTIONS_FILE}; #Getopt::Long::Configure ("bundling_values"); GetOptions( 'foreground|F' => \$foreground, 'engine|e=s' => \$engine, 'proc|n=s' => \$nproc, 'pid|p=s' => \$pidFile, 'socket|s=s' => \$socket, 'listen|l=s' => \$listen, 'user|u=s' => \$user, 'group|g=s' => \$group, 'customFunctionsFile|f=s' => \$customFunctionsFile, 'plackOptions=s' => \%plackOptions, ); if ($group) { my $grp = getgrnam($group) or warn "Can't change uid to $group"; POSIX::setgid($grp); } if ($user) { my $uid = getpwnam($user) or warn "Can't change uid to $user"; POSIX::setuid($uid); } if ($customFunctionsFile) { eval { require $customFunctionsFile }; die $@ if ($@); } my %builder = ( handler => sub { require Lemonldap::NG::Handler::Nginx; return Lemonldap::NG::Handler::Nginx->run( {} ); }, authbasic => sub { require Lemonldap::NG::Handler::Nginx::AuthBasic; return Lemonldap::NG::Handler::Nginx::AuthBasic->run( {} ); }, reload => sub { require Lemonldap::NG::Handler::Nginx; return Lemonldap::NG::Handler::Nginx->reload(); }, status => sub { require Lemonldap::NG::Handler::Nginx; return Lemonldap::NG::Handler::Nginx->status(); }, manager => sub { require Lemonldap::NG::Manager; return Lemonldap::NG::Manager->run( {} ); }, cgi => sub { require CGI::Emulate::PSGI; require CGI::Compile; return sub { my $script = $_[0]->{SCRIPT_FILENAME}; return $_apps{$script}->(@_) if ( $_apps{$script} ); $_apps{$script} = CGI::Emulate::PSGI->handler( CGI::Compile->compile($script) ); return $_apps{$script}->(@_); }; }, ); unless ($>) { die "Refuse to run as root. Aborting"; } my $app = sub { my $type = $_[0]->{LLTYPE} || 'handler'; return $_apps{$type}->(@_) if ( defined $_apps{$type} ); if ( defined $builder{$type} ) { $_apps{$type} = $builder{$type}->(); return $_apps{$type}->(@_); } die "Unknown PSGI type $type"; }; # Hook for customFunctions initialization use Lemonldap::NG::Handler::API::PSGI::Server; $Lemonldap::NG::Handler::API::mode = 'PSGI::Server'; Lemonldap::NG::Handler::SharedConf->init(); # Load configuration and look if custom handlers have been defined { $Lemonldap::NG::Handler::API::mode = 'PSGI::Server'; my $conf = Lemonldap::NG::Handler::SharedConf->checkConf() or die "Unable to get configuration"; foreach my $lltype ( keys %{ $conf->{nginxCustomHandlers} || {} } ) { my $v = $conf->{nginxCustomHandlers}->{$lltype}; if ( $v =~ m#[/\\\.]# ) { eval { require $v; }; } else { eval "use $v"; } if ($@) { print STDERR "Unable to load $v, skipping: $@\n"; next; } $builder{$lltype} = sub { require $v; return $v->run( {} ); }; } } my $server = Plack::Runner->new(); $server->parse_options( '-s' => $engine, '-E' => 'deployment', '--pid' => $pidFile, '--nproc' => $nproc, '--socket' => $socket, ( $listen ? ('--listen', $listen) : ()), '--proc-title' => 'llng-fastcgi-server', ( $foreground ? () : '--daemonize' ), '--no-default-middleware', %plackOptions, ); $server->run($app); __END__ =head1 NAME =encoding utf8 llng-fastcgi-server - FastCGI server used to provide Lemonldap::NG services to Nginx =head1 SYNOPSIS # Start server listening to /run/llng.sock with 10 process llng-fastcgi-server -u nobody -g nobody -s /run/llng.sock -n 10 =head1 DESCRIPTION llng-fastcgi-server has been designed provides Lemonldap::NG services to Nginx. Portal, manager and handler will be compiled only is used. So this FastCGI server can be used on every Lemonldap::NG server even if it needs only some parts (isolated handlers, portal,...). =head1 PARAMETERS Each parameter can be set by an option or a environment variable. =over =item --pid -p ($ENV{PID}): pid file =item --user -u ($ENV{USER}): user =item --group -g ($ENV{GROUP}): group =item --proc -n ($ENV{NPROC}): Number of processus for FCGI =item --engine -e ($ENV{ENGINE}): Plack::Handler engine, default to FCGI =item --socket -s ($ENV{SOCKET}): Unix socket =item --listen -l ($ENV{LISTEN}): Listening address (HOST:PORT, :PORT, or PATH) =item --customFunctionsFile -f ($ENV{CUSTOM_FUNCTIONS_FILE}): file to load for custom functions =item --plackOptions: other options to pass to Plack. This multi-valued parameter must have "key=value" values. =back =head1 SEE ALSO L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2008-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/systemd/000077500000000000000000000000001325274564300266005ustar00rootroot00000000000000llng-fastcgi-server.conf000066400000000000000000000007361325274564300332540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/systemddescription "FastCGI server for Lemonldap::NG websso system" start on filesystem and static-network-up stop on runlevel [016] expect fork respawn pre-start script [ -x /usr/sbin/llng-fastcgi-server ] || { stop; exit 0; } end script exec mkdir __FASTCGISOCKDIR__; chown __USER__:__GROUP__ __FASTCGISOCKDIR__ && /usr/sbin/llng-fastcgi-server -u __USER__ -g __GROUP__ -s __FASTCGISOCKDIR__/llng-fastcgi.sock -p __FASTCGISOCKDIR__/llng-fastcgi-server.pid llng-fastcgi-server.service000066400000000000000000000006461325274564300337670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/systemd[Unit] Description=FastCGI server for Lemonldap::NG websso system After=network.target [Service] Type=forking EnvironmentFile=/etc/default/llng-fastcgi-server PIDFile=__FASTCGISOCKDIR__/llng-fastcgi-server.pid ExecStart=__SBINDIR__/llng-fastcgi-server ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile ${PID} KillMode=mixed [Install] Alias=llng-fastcgi-server.service WantedBy=multi-user.target llng-fastcgi-server.tmpfile000066400000000000000000000000551325274564300337610ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/fastcgi-server/systemdd __FASTCGISOCKDIR__ 0755 __USER__ __GROUP__ lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/000077500000000000000000000000001325274564300260275ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/Changes000066400000000000000000000012231325274564300273200ustar00rootroot00000000000000Revision history for Perl extension Lemonldap::NG::Common. See https://lemonldap-ng.org/documentation/latest/upgrade for newer revisions 0.94 Mon Jun 29 11:44:47 2009 - New SOAP wrapper - WSDL builder - New Apache2::Log wrapper - Syslog wrapper - setApacheUser() sub - LDAP storage backend for sessions and configuration - Crypt object in configuration - Safelib.pm : common subs useable in Lemonldap::NG rules 0.92 Sun Feb 8 08:04:25 2009 - Change CGI SOAP system 0.91 Sun Dec 26 10:06:42 2008 - Add SOAP::Lite dependency 0.9 Mon Nov 17 16:34:56 2008 - New package including the old Lemonldap::NG::manager::Conf and CGI common functions lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/MANIFEST000066400000000000000000000041231325274564300271600ustar00rootroot00000000000000Changes lemonldap-ng.ini lib/Lemonldap/NG/Common.pm lib/Lemonldap/NG/Common/Apache/Session.pm lib/Lemonldap/NG/Common/Apache/Session/Generate/SHA256.pm lib/Lemonldap/NG/Common/Apache/Session/Lock.pm lib/Lemonldap/NG/Common/Apache/Session/Serialize/JSON.pm lib/Lemonldap/NG/Common/Apache/Session/SOAP.pm lib/Lemonldap/NG/Common/Apache/Session/Store.pm lib/Lemonldap/NG/Common/Captcha.pm lib/Lemonldap/NG/Common/CGI.pm lib/Lemonldap/NG/Common/CGI/SOAPServer.pm lib/Lemonldap/NG/Common/CGI/SOAPService.pm lib/Lemonldap/NG/Common/Cli.pm lib/Lemonldap/NG/Common/Conf.pm lib/Lemonldap/NG/Common/Conf/_DBI.pm lib/Lemonldap/NG/Common/Conf/CDBI.pm lib/Lemonldap/NG/Common/Conf/Constants.pm lib/Lemonldap/NG/Common/Conf/DBI.pm lib/Lemonldap/NG/Common/Conf/DefaultValues.pm lib/Lemonldap/NG/Common/Conf/File.pm lib/Lemonldap/NG/Common/Conf/JSONFile.pm lib/Lemonldap/NG/Common/Conf/LDAP.pm lib/Lemonldap/NG/Common/Conf/MongoDB.pm lib/Lemonldap/NG/Common/Conf/RDBI.pm lib/Lemonldap/NG/Common/Conf/SAML/Metadata.pm lib/Lemonldap/NG/Common/Conf/Serializer.pm lib/Lemonldap/NG/Common/Conf/SOAP.pm lib/Lemonldap/NG/Common/Crypto.pm lib/Lemonldap/NG/Common/Notification.pm lib/Lemonldap/NG/Common/Notification/DBI.pm lib/Lemonldap/NG/Common/Notification/File.pm lib/Lemonldap/NG/Common/Notification/LDAP.pm lib/Lemonldap/NG/Common/PSGI.pm lib/Lemonldap/NG/Common/PSGI/Cli/Lib.pm lib/Lemonldap/NG/Common/PSGI/Constants.pm lib/Lemonldap/NG/Common/PSGI/Request.pm lib/Lemonldap/NG/Common/PSGI/Router.pm lib/Lemonldap/NG/Common/Regexp.pm lib/Lemonldap/NG/Common/Safe.pm lib/Lemonldap/NG/Common/Safelib.pm lib/Lemonldap/NG/Common/Session.pm Makefile.PL MANIFEST This list of files META.yml README scripts/convertConfig scripts/lemonldap-ng-cli scripts/lmMigrateConfFiles2ini scripts/rotateOidcKeys t/01-Common-Conf.t t/02-Common-Conf-File.t t/03-Common-Conf-CDBI.t t/03-Common-Conf-RDBI.t t/04-Common-Conf-SOAP.t t/05-Common-Conf-LDAP.t t/20-Common-CGI.t t/30-Common-Safelib.t t/35-Common-Crypto.t t/36-Common-Regexp.t t/40-Common-Session.t t/99-pod.t tools/apache-session-mysql.sql tools/lmConfig.CDBI.mysql tools/lmConfig.RDBI.mysql tools/sso.schema lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/META.yml000066400000000000000000000021551325274564300273030ustar00rootroot00000000000000--- abstract: 'Common files for Lemonldap::NG infrastructure' author: - 'Xavier Guimard , Clément Oudot ' build_requires: IO::String: '0' Net::LDAP: '0' Test::Pod: '1' XML::Simple: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Lemonldap-NG-Common no_index: directory: - t - inc recommends: Apache::Session::Browseable: '0' HTML::Template: '0' HTTP::Message: '0' JSON: '0' Net::LDAP: '0' XML::LibXML: '0' XML::Simple: '0' requires: Apache::Session: '0' CGI: '3.08' Cache::Cache: '0' Config::IniFiles: '0' Crypt::OpenSSL::Bignum: '0' Crypt::OpenSSL::RSA: '0' Crypt::OpenSSL::X509: '0' Crypt::Rijndael: '0' DBI: '0' Digest::SHA: '0' File::Basename: '0' JSON: '0' Mouse: '0' Net::CIDR::Lite: '0' SOAP::Lite: '0' Storable: '0' version: v1.9.16 x_serialization_backend: 'CPAN::Meta::YAML version 0.018' lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/Makefile.PL000066400000000000000000000047451325274564300300130ustar00rootroot00000000000000use 5.014; use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. sub MY::top_targets { my $self = shift; my $r = $self->MM::top_targets(@_); if ( my $cf = $ENV{LMNGCONFFILE} ) { $r =~ s/^(all.*)$/$1 my_target/m; $cf = quotemeta($cf); $r .= <<"EOT"; my_target: perl -i -pe 's/^(use constant DEFAULTCONFFILE\\s*=>).*\$\$/\$\$1 "$cf";/' blib/lib/Lemonldap/NG/Common/Conf/Constants.pm EOT } return $r; } WriteMakefile( NAME => 'Lemonldap::NG::Common', VERSION_FROM => 'lib/Lemonldap/NG/Common.pm', # finds $VERSION LICENSE => 'gpl', BUILD_REQUIRES => { 'IO::String' => 0, 'Net::LDAP' => 0, 'Test::Pod' => 1.00, 'XML::Simple' => 0, }, META_MERGE => { 'recommends' => { 'Apache::Session::Browseable' => 0, 'HTML::Template' => 0, 'HTTP::Message' => 0, 'JSON' => 0, 'Net::LDAP' => 0, 'XML::Simple' => 0, 'XML::LibXML' => 0, }, }, PREREQ_PM => { 'Apache::Session' => 0, 'Cache::Cache' => 0, 'CGI' => 3.08, 'Crypt::OpenSSL::Bignum' => 0, 'Crypt::OpenSSL::RSA' => 0, 'Crypt::OpenSSL::X509' => 0, 'Crypt::Rijndael' => 0, 'Config::IniFiles' => 0, 'DBI' => 0, 'Digest::SHA' => 0, 'File::Basename' => 0, 'JSON' => 0, 'Mouse' => 0, 'Net::CIDR::Lite' => 0, 'SOAP::Lite' => 0, 'Storable' => 0, }, # e.g., Module::Name => 1.1 #EXE_FILES => [ 'scripts/convertConfig', ], ( $] >= 5.005 ? ## Add these new keywords supported since 5.005 ( ABSTRACT_FROM => 'lib/Lemonldap/NG/Common.pm', # retrieve abstract from module AUTHOR => 'Xavier Guimard , Clément Oudot ' ) : () ), clean => { FILES => 't/lmConf*' }, MAN1PODS => { 'scripts/convertConfig' => 'blib/man1/convertConfig.1p', 'scripts/lemonldap-ng-cli' => 'blib/man1/lemonldap-ng-cli.1p', }, ); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/README000066400000000000000000000017721325274564300267160ustar00rootroot00000000000000LemonLDAP::NG ==================== LemonLDAP::NG is a modular Web-SSO based on Apache::Session modules. This is the common part of it. You can find documentation here: * for administrators: http://lemonldap-ng.org/ * for developers: see embedded perldoc LemonLDAP::NG is a 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 2, 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 L. Copyright: * 2005-2015 by Xavier Guimard and Clément Oudot * 2008-2011 by Thomas Chemineau * 2012-2015 by François-Xavier Deltombe and Sandro Cazzaniga lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lemonldap-ng.ini000066400000000000000000000243511325274564300311120ustar00rootroot00000000000000;============================================================================== ; LemonLDAP::NG local configuration parameters ; ; This file is dedicated to configuration parameters override ; You can set here configuration parameters that will be used only by ; local LemonLDAP::NG elements ; ; Section "all" is always read first before "portal", "handler" ; and "manager" ; ; Section "configuration" is used to load global configuration and set cache ; (replace old storage.conf file) ; ; Section "apply" is read by Manager to reload handlers ; (replace old apply.conf file) ; ; Other section are only read by the specific LemonLDAP::NG component ;============================================================================== [all] ; CUSTOM FUNCTION ; If you want to create customFunctions in rules, declare them here: ;customFunctions = function1 function2 ;customFunctions = Package::func1 Package::func2 ; CROSS-DOMAIN ; If you have some handlers that are not registered on the main domain, ; uncomment this ;cda = 1 ; SAFE JAIL ; Uncomment this to disable Safe jail. ; Warning: this can allow malicious code in custom functions or rules ;useSafeJail = 0 [configuration] ; GLOBAL CONFIGURATION ACCESS TYPE ; (File, SOAP, RDBI/CDBI, LDAP) ; Set here the parameters needed to access to LemonLDAP::NG configuration. ; You have to set "type" to one of the followings : ; ; * File: you have to set 'dirName' parameter. Example: ; ; type = File ; dirName = /var/lib/lemonldap-ng/conf ; ; * RDBI/CDBI : you have to set 'dbiChain' (required) and 'dbiUser' and 'dbiPassword' ; if needed. Example: ; ; type = RDBI ; ;type = CDBI ; dbiChain = DBI:mysql:database=lemonldap-ng;host=1.2.3.4 ; dbiUser = lemonldap ; dbiPassword = password ; ; * SOAP: SOAP configuration access is a sort of proxy: the portal is ; configured to use the real session storage type (DBI or File for ; example). ; You have to set 'proxy' parameter. Example: ; ; type = SOAP ; proxy = https://auth.example.com/index.pl/config ; proxyOptions = { timeout => 5 } ; User = lemonldap ; Password = mypassword ; ; * LDAP: you have to set ldapServer, ldapConfBase, ldapBindDN and ldapBindPassword. ; ; type = LDAP ; ldapServer = ldap://localhost ; ldapConfBase = ou=conf,ou=applications,dc=example,dc=com ; ldapBindDN = cn=manager,dc=example,dc=com ; ldapBindPassword = secret ; ldapObjectClass = applicationProcess ; ldapAttributeId = cn ; ldapAttributeContent = description type=File dirName=/var/lib/lemonldap-ng/conf ; LOCAL CACHE CONFIGURATION ; ; To increase performances, use a local cache for the configuration. You have ; to choose a Cache::Cache module and set its parameters. Example: ; ; localStorage = Cache::FileCache ; localStorageOptions={ \ ; 'namespace' => 'lemonldap-ng-config',\ ; 'default_expires_in' => 600, \ ; 'directory_umask' => '007', \ ; 'cache_root' => '/tmp', \ ; 'cache_depth' => 0, \ ; } localStorage=Cache::FileCache localStorageOptions={ \ 'namespace' => 'lemonldap-ng-config',\ 'default_expires_in' => 600, \ 'directory_umask' => '007', \ 'cache_root' => '/tmp', \ 'cache_depth' => 0, \ } [portal] ; PERFORMANCES ; By setting useLocalConf, Portal will use only local cached configuration ; To refresh it, you must have an handler on the same server or you have to ; restart your server. This increase performances ;useLocalConf = 1 ; PORTAL CUSTOMIZATION ; Name of the skin ;portalSkin = pastel ; Modules displayed ;portalDisplayLogout = 1 ;portalDisplayResetPassword = 1 ;portalDisplayChangePassword = 1 ;portalDisplayAppslist = 1 ;portalDisplayLoginHistory = 1 ; Require the old password when changing password ;portalRequireOldPassword = 1 ; Attribute displayed as connected user ;portalUserAttr = mail ; Old menu HTML code ; Enable it if you use old templates ;useOldMenuItems=1 ; Override error codes ;error_0 = You are well authenticated! ; Custom template parameters ; For example to use ;tpl_myparam = test ; LOG ; By default, all is logged in Apache file. To log user actions by ; syslog, just set syslog facility here: ;syslog = auth ; SOAP FUNCTIONS ; Remove comment to activate SOAP Functions getCookies(user,pwd) and ; error(language, code) ;Soap = 1 ; Note that getAttibutes() will be activated but on a different URI ; (http://auth.example.com/index.pl/sessions) ; You can also restrict attributes and macros exported by getAttributes ;exportedAttr = uid mail ; PASSWORD POLICY ; Remove comment to use LDAP Password Policy ;ldapPpolicyControl = 1 ; Remove comment to store password in session (use with caution) ;storePassword = 1 ; Remove comment to use LDAP modify password extension ; (beware of compatibility with LDAP Password Policy) ;ldapSetPassword = 1 ; RESET PASSWORD BY MAIL ; SMTP server (default to localhost), set to '' to use default mail service ;SMTPServer = localhost ; SMTP auth user ;SMTPAuthUser = toto ; SMTP auth password ;SMTPAuthPass = secret ; Mail From address ;mailFrom = noreply@example.com ; Reply To ;mailReplyTo = noreply@example.com ; Mail confirmation URL ;mailUrl = http://reset.example.com ; Mail subject for confirmation message ;mailConfirmSubject = [LemonLDAP::NG] Password reset confirmation ; Mail body for confiramtion (can use $url for confirmation URL, and other session ; infos, like $cn). Keep comment to use HTML templates ;mailConfirmBody = Hello $cn,\n\nClick here to receive your new password: $url ; Mail subject for new password message ;mailSubject = [LemonLDAP::NG] Your new password ; Mail body for new password (can use $password for generated password, and other session ; infos, like $cn). Keep comment to use HTML templates ;mailBody = Hello $cn,\n\nYour new password is $password ; LDAP filter to use ;mailLDAPFilter = '(&(mail=$mail)(objectClass=inetOrgPerson))' ; Random regexp for password generation ;randomPasswordRegexp = [A-Z]{3}[a-z]{5}.\d{2} ; LDAP GROUPS ; Set the base DN of your groups branch ;ldapGroupBase = ou=groups,dc=example,dc=com ; Objectclass used by groups ;ldapGroupObjectClass = groupOfUniqueNames ; Attribute used by groups to store member ;ldapGroupAttributeName = uniqueMember ; Attribute used by user to link to groups ;ldapGroupAttributeNameUser = dn ; Attribute used to identify a group. The group will be displayed as ; cn|mail|status, where cn, mail and status will be replaced by their ; values. ;ldapGroupAttributeNameSearch = cn mail ; NOTIFICATIONS SERVICE ; Use it to be able to notify messages during authentication ;notification = 1 ; Note that the SOAP function newNotification will be activated on ; http://auth.example.com/index.pl/notification ; If you want to hide this, just protect "/index.pl/notification" in ; your Apache configuration file ; XSS protection bypass ; By default, the portal refuse redirections that comes from sites not ; registered in the configuration (manager) except for those coming ; from trusted domains. By default, trustedDomains contains the domain ; declared in the manager. You can set trustedDomains to empty value so ; that, undeclared sites will be rejected. You can also set here a list ; of trusted domains or hosts separated by spaces. This is usefull if ; your website use LemonLDAP::NG without handler with SOAP functions. ;trustedDomains = my.trusted.host example2.com ; Check XSS ; Set to 0 to disable error on XSS attack detection ;checkXSS = 0 [handler] ; Handler cache configuration ; You can overwrite here local session cache settings in manager: ; localSessionStorage=Cache::FileCache ; localSessionStorageOptions={ \ ; 'namespace' => 'lemonldap-ng-sessions', \ ; 'default_expires_in' => 600, \ ; 'directory_umask' => '007', \ ; 'cache_root' => '/tmp', \ ; 'cache_depth' => 3, \ ; } ; Set https to 1 if your handler protect a https website (used only for ; redirections to the portal) ;https = 0 ; Set port if your your hanlder protect a website on a non standard port ; - 80 for http, 443 for https (used only for redirections to the portal) ;port = 8080 ; Set status to 1 if you want to have the report of activity (used for ; example to inform MRTG) status = 0 ; Set useRedirectOnForbidden to 1 if you want to use REDIRECT and not FORBIDDEN ; when a user is not allowed by Handler ;useRedirectOnForbidden = 1 ; Hide LemonLDAP::NG Handler in Apache Server Signature ;hideSignature = 1 useRedirectOnError = 1 ; Zimbra Handler parameters ;zimbraPreAuthKey = XXXX ;zimbraAccountKey = uid ;zimbraBy =id ;zimbraUrl = /service/preauth ;zimbraSsoUrl = ^/zimbrasso$ [manager] ; Manager protection: by default, the manager is protected by a demo account. ; You can protect it : ; * by Apache itself, ; * by the parameter 'protection' which can take one of the following ; values : ; * authenticate : all authenticated users can access ; * manager : manager is protected like other virtual hosts: you ; have to set rules in the corresponding virtual host ; * : you can set here directly the rule to apply ; * none : no protection protection = manager ; logLevel. Set here one of error, warn, notice, info or debug logLevel = warn ; staticPrefix: relative (or URL) location of static HTML components staticPrefix = __MANAGERSTATICDIR__ ; ; location of HTML templates directory templateDir = __MANAGERTEMPLATESDIR__ ; languages: available languages for manager interface languages = fr, en ; Manager modules enabled ; Set here the list of modules you want to see in manager interface ; The first will be used as default module displayed enabledModules = conf, sessions, notifications lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/000077500000000000000000000000001325274564300265755ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/000077500000000000000000000000001325274564300305105ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/000077500000000000000000000000001325274564300310145ustar00rootroot00000000000000Common.pm000066400000000000000000000032071325274564300325250ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NGpackage Lemonldap::NG::Common; our $VERSION = '1.9.16'; use strict; 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common - Common files for Lemonldap::NG infrastructure =head1 DESCRIPTION Lemonldap::NG is a modular Web-SSO based on Apache::Session modules. It simplifies the build of a protected area with a few changes in the application. This package contains common files. =head1 SEE ALSO L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2010-2016 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Common/000077500000000000000000000000001325274564300321655ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NGApache/000077500000000000000000000000001325274564300333465ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/CommonSession.pm000066400000000000000000000267741325274564300353470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache## @file # Add get_key_from_all_sessions() function to Apache::Session modules. # This file is used by Lemonldap::NG::Manager::Status and by the # purgeCentralCache script. # # Warning, this works only with SQL databases, simple or Berkeley files (not # for Apache::Session::Memcached for example) package Lemonldap::NG::Common::Apache::Session; use strict; use AutoLoader 'AUTOLOAD'; use Apache::Session; use base qw(Apache::Session); use Lemonldap::NG::Common::Apache::Session::Serialize::JSON; use Lemonldap::NG::Common::Apache::Session::Store; use Lemonldap::NG::Common::Apache::Session::Lock; our $VERSION = '1.9.5'; sub _load { my ( $backend, $func ) = @_; unless ( $backend->can('populate') ) { eval "require $backend"; die $@ if ($@); } return $func ? $backend->can($func) : 0; } sub populate { my $self = shift; my $backend = $self->{args}->{backend}; _load($backend); $backend .= "::populate"; { no strict 'refs'; $self = $self->$backend(@_); } if ( $backend =~ /^Apache::Session::(?:(?:Postgre|Redi)s|(?:Oracl|Sybas)e|(?:My|No)SQL|F(?:ile|lex)|Cassandra|LDAP)/ and !$self->{args}->{useStorable} ) { $self->{serialize} = \&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::serialize; $self->{unserialize} = \&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::unserialize; if ( $backend =~ /^Apache::Session::LDAP/ ) { $self->{unserialize} = \&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::unserializeBase64; } } if ( $self->{args}->{generateModule} ) { my $generate = $self->{args}->{generateModule}; eval "require $generate"; die $@ if ($@); $self->{generate} = \&{ $generate . "::generate" }; $self->{validate} = \&{ $generate . "::validate" }; } if ( $self->{args}->{setId} ) { $self->{generate} = \&setId; $self->{validate} = sub { 1 }; } # If cache is configured, use our specific object store module if ( $> and $self->{args}->{localStorage} ) { $self->{args}->{object_store} = $self->{object_store}; $self->{object_store} = Lemonldap::NG::Common::Apache::Session::Store->new($self); $self->{args}->{lock_manager} = $self->{lock_manager}; $self->{lock_manager} = Lemonldap::NG::Common::Apache::Session::Lock->new($self); } return $self; } __END__ sub setId { my $session = shift; $session->{data}->{_session_id} = $session->{args}->{setId}; } sub searchOn { my ( $class, $args, $selectField, $value, @fields ) = splice @_; return $args->{backend}->searchOn( $args, $selectField, $value, @fields ) if ( _load( $args->{backend}, 'searchOn' ) ); my %res = (); $class->get_key_from_all_sessions( $args, sub { my $entry = shift; my $id = shift; return undef unless ( $entry->{$selectField} eq $value ); if (@fields) { $res{$id}->{$_} = $entry->{$_} foreach (@fields); } else { $res{$id} = $entry; } undef; } ); return \%res; } sub searchOnExpr { my ( $class, $args, $selectField, $value, @fields ) = splice @_; return $args->{backend} ->searchOnExpr( $args, $selectField, $value, @fields ) if ( _load( $args->{backend}, 'searchOnExpr' ) ); $value = quotemeta($value); $value =~ s/\\\*/\.\*/g; $value = qr/^$value$/; my %res = (); $class->get_key_from_all_sessions( $args, sub { my $entry = shift; my $id = shift; return undef unless ( $entry->{$selectField} =~ $value ); if (@fields) { $res{$id}->{$_} = $entry->{$_} foreach (@fields); } else { $res{$id} = $entry; } undef; } ); return \%res; } sub searchLt { my ( $class, $args, $selectField, $value, @fields ) = splice @_; return $args->{backend}->searchLt( $args, $selectField, $value, @fields ) if ( _load( $args->{backend}, 'searchLt' ) ); my %res = (); $class->get_key_from_all_sessions( $args, sub { my $entry = shift; my $id = shift; return undef unless ( $entry->{$selectField} < $value ); if (@fields) { $res{$id}->{$_} = $entry->{$_} foreach (@fields); } else { $res{$id} = $entry; } undef; } ); return \%res; } sub get_key_from_all_sessions { my $class = shift; my ( $args, $data ) = @_; my $backend = $args->{backend}; if ( _load( $backend, 'get_key_from_all_sessions' ) ) { return $backend->get_key_from_all_sessions( $args, $data ); } if ( $args->{useStorable} ) { require Storable; $args->{unserialize} = \&Storable::thaw; } else { $args->{unserialize} = \&Lemonldap::NG::Common::Apache::Session::Serialize::JSON::_unserialize; } if ( $backend =~ /^Apache::Session::(MySQL|MySQL::NoLock|Postgres|Oracle|Sybase|Informix)$/ ) { return $class->_dbiGKFAS( $1, @_ ); } elsif ( $backend =~ /^Apache::Session::(?:NoSQL|Redis|Cassandra)$/ ) { return $class->_NoSQLGKFAS(@_); } elsif ( $backend =~ /^Apache::Session::(File|PHP|DBFile|LDAP)$/ ) { no strict 'refs'; my $tmp = "_${1}GKFAS"; return $class->$tmp(@_); } else { die "$backend can not provide session exploration"; } } sub decodeThaw64 { require MIME::Base64; my $s = shift; return Storable::thaw( MIME::Base64::decode_base64($s) ); } sub _dbiGKFAS { my ( $class, $type, $args, $data ) = @_; my $next; if ( $type !~ /(?:MySQL)/ ) { $next = \&decodeThaw64; if ( $args->{useStorable} ) { $args->{unserialize} = $next; } } my $dbh = DBI->connect( $args->{DataSource}, $args->{UserName}, $args->{Password} ) or die("$!$@"); my $sth = $dbh->prepare('SELECT id,a_session from sessions'); $sth->execute; my %res; while ( my @row = $sth->fetchrow_array ) { if ( ref($data) eq 'CODE' ) { my $tmp = &$data( $args->{unserialize}->( $row[1], $next ), $row[0] ); $res{ $row[0] } = $tmp if ( defined($tmp) ); } elsif ($data) { $data = [$data] unless ( ref($data) ); my $tmp = $args->{unserialize}->( $row[1], $next ); $res{ $row[0] }->{$_} = $tmp->{$_} foreach (@$data); } else { $res{ $row[0] } = $args->{unserialize}->( $row[1], $next ); } } return \%res; } sub _FileGKFAS { my ( $class, $args, $data ) = @_; $args->{Directory} ||= '/tmp'; unless ( opendir DIR, $args->{Directory} ) { die "Cannot open directory $args->{Directory}\n"; } my @t = grep { -f "$args->{Directory}/$_" and /^[A-Za-z0-9@\-]+$/ } readdir(DIR); closedir DIR; my %res; for my $f (@t) { open F, "$args->{Directory}/$f"; my $row = join '', ; if ( ref($data) eq 'CODE' ) { eval { $res{$f} = &$data( $args->{unserialize}->($row), $f ); }; if ($@) { $res{$f} = &$data( undef, $f ); } } elsif ($data) { $data = [$data] unless ( ref($data) ); my $tmp; eval { $tmp = $args->{unserialize}->($row); }; if ($@) { $res{$f}->{$_} = undef foreach (@$data); } else { $res{$f}->{$_} = $tmp->{$_} foreach (@$data); } } else { eval { $res{$f} = $args->{unserialize}->($row); }; } } return \%res; } sub _PHPGKFAS { require Apache::Session::Serialize::PHP; my ( $class, $args, $data ) = @_; my $directory = $args->{SavePath} || '/tmp'; unless ( opendir DIR, $args->{SavePath} ) { die "Cannot open directory $args->{SavePath}\n"; } my @t = grep { -f "$args->{SavePath}/$_" and /^sess_[A-Za-z0-9@\-]+$/ } readdir(DIR); closedir DIR; my %res; for my $f (@t) { open F, "$args->{SavePath}/$f"; my $row = join '', ; if ( ref($data) eq 'CODE' ) { $res{$f} = &$data( Apache::Session::Serialize::PHP::unserialize($row), $f ); } elsif ($data) { $data = [$data] unless ( ref($data) ); my $tmp = Apache::Session::Serialize::PHP::unserialize($row); $res{$f}->{$_} = $tmp->{$_} foreach (@$data); } else { $res{$f} = Apache::Session::Serialize::PHP::unserialize($row); } } return \%res; } sub _DBFileGKFAS { my ( $class, $args, $data ) = @_; if ( !tied %{ $class->{dbm} } ) { my $rv = tie %{ $class->{dbm} }, 'DB_File', $args->{FileName}; if ( !$rv ) { die "Could not open dbm file " . $args->{FileName} . ": $!"; } } my %res; foreach my $k ( keys %{ $class->{dbm} } ) { if ( ref($data) eq 'CODE' ) { $res{$k} = &$data( $args->{unserialize}->( $class->{dbm}->{$k} ), $k ); } elsif ($data) { $data = [$data] unless ( ref($data) ); my $tmp = $args->{unserialize}->( $class->{dbm}->{$k} ); $res{$k}->{$_} = $tmp->{$_} foreach (@$data); } else { $res{$k} = $args->{unserialize}->( $class->{dbm}->{$k} ); } } return \%res; } sub _LDAPGKFAS { my ( $class, $args, $data ) = @_; $args->{ldapObjectClass} ||= 'applicationProcess'; $args->{ldapAttributeId} ||= 'cn'; $args->{ldapAttributeContent} ||= 'description'; my $ldap = Apache::Session::Store::LDAP::ldap( { args => $args } ); my $msg = $ldap->search( base => $args->{ldapConfBase}, filter => '(objectClass=' . $args->{ldapObjectClass} . ')', attrs => [ $args->{ldapAttributeId}, $args->{ldapAttributeContent} ], ); $ldap->unbind(); $ldap->disconnect(); Apache::Session::Store::LDAP->logError($msg) if ( $msg->code ); my %res; foreach my $entry ( $msg->entries ) { my ( $k, $v ) = ( $entry->get_value( $args->{ldapAttributeId} ), $entry->get_value( $args->{ldapAttributeContent} ) ); eval { $v = $args->{unserialize}->( $v, \&decodeThaw64 ); }; next if ($@); if ( ref($data) eq 'CODE' ) { $res{$k} = &$data( $v, $k ); } elsif ($data) { $data = [$data] unless ( ref($data) ); my $tmp = $v; $res{$k}->{$_} = $tmp->{$_} foreach (@$data); } else { $res{$k} = $v; } } return \%res; } sub _NoSQLGKFAS { my ( $class, $args, $data ) = @_; require Redis; die "Only Redis is supported" unless ( $args->{Driver} eq 'Redis' ); my $redis = Redis->new(%$args); my @keys = $redis->keys('*'); my %res; foreach my $k (@keys) { my $v = eval { $args->{unserialize}->( $redis->get($k), \&decodeThaw64 ); }; next if ($@); if ( ref($data) eq 'CODE' ) { $res{$k} = &$data( $v, $k ); } elsif ($data) { $data = [$data] unless ( ref($data) ); $res{$k}->{$_} = $v->{$_} foreach (@$data); } else { $res{$k} = $v; } } return \%res; } 1; Session/000077500000000000000000000000001325274564300347715ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/ApacheGenerate/000077500000000000000000000000001325274564300365235ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/SessionSHA256.pm000066400000000000000000000023351325274564300377340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/Session/Generate############################################################################# # # Lemonldap::NG::Common::Apache::Session::Generate::SHA256 # Generates session identifier tokens using SHA-256 # Distribute under the Perl License # ############################################################################ package Lemonldap::NG::Common::Apache::Session::Generate::SHA256; use strict; use Digest::SHA qw(sha256 sha256_hex sha256_base64); our $VERSION = '1.9.1'; sub generate { my $session = shift; my $length = 64; if ( exists $session->{args}->{IDLength} ) { $length = $session->{args}->{IDLength}; } $session->{data}->{_session_id} = substr( Digest::SHA::sha256_hex( Digest::SHA::sha256_hex( time() . {} . rand() . $$ ) ), 0, $length ); } sub validate { #This routine checks to ensure that the session ID is in the form #we expect. This must be called before we start diddling around #in the database or the disk. my $session = shift; if ( $session->{data}->{_session_id} =~ /^([a-fA-F0-9]+)$/ ) { $session->{data}->{_session_id} = $1; } else { die "Invalid session ID: " . $session->{data}->{_session_id}; } } 1; Lock.pm000066400000000000000000000024331325274564300362210ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/Session package Lemonldap::NG::Common::Apache::Session::Lock; use strict; my $VERSION = '1.4.4'; sub new { my $class = shift; my $session = shift; my $self = {}; $self->{args} = $session->{args}; bless $self, $class; return $self; } sub module { my $self = shift; return $self->{args}->{lock_manager}; } sub cache { my $self = shift; return $self->{cache} if $self->{cache}; my $module = $self->{args}->{localStorage}; eval "use $module;"; $self->{cache} = $module->new( $self->{args}->{localStorageOptions} ); return $self->{cache}; } sub acquire_read_lock { my $self = shift; my $session = shift; # Get session from cache my $id = $session->{data}->{_session_id}; if ( $self->cache->get($id) ) { # got session from cache, no need to ask for locks } else { $self->module->acquire_read_lock($session); } } sub acquire_write_lock { my $self = shift; my $session = shift; $self->module->acquire_write_lock($session); } sub release_write_lock { my $self = shift; my $session = shift; $self->module->release_write_lock($session); } sub release_all_locks { my $self = shift; my $session = shift; $self->module->release_all_locks($session); } 1; SOAP.pm000066400000000000000000000273071325274564300361020ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/Session## @file # Client side of the SOAP proxy mechanism for Apache::Session modules ## @class # Client side of the SOAP proxy mechanism for Apache::Session modules package Lemonldap::NG::Common::Apache::Session::SOAP; use strict; use SOAP::Lite; our $VERSION = '1.9.1'; #parameter proxy Url of SOAP service #parameter proxyOptions SOAP::Lite options #parameter User Username #parameter Password Password #parameter localStorage Cache module #parameter localStorageOptions Cache module options # Variables shared with SOAP::Transport::HTTP::Client our ( $user, $password ) = ( '', '' ); BEGIN { sub SOAP::Transport::HTTP::Client::get_basic_credentials { return $Lemonldap::NG::Common::Apache::Session::SOAP::user => $Lemonldap::NG::Common::Apache::Session::SOAP::password; } } # PUBLIC INTERFACE ## @cmethod Lemonldap::NG::Common::Apache::Session::SOAP TIEHASH(string session_id, hashRef args) # Constructor for Perl TIE mechanism. See perltie(3) for more. # @return Lemonldap::NG::Common::Apache::Session::SOAP object sub TIEHASH { my $class = shift; my $session_id = shift; my $args = shift; my ( $proxy, $proxyOptions ); die "proxy argument is required" unless ( $args and $args->{proxy} ); my $self = { data => { _session_id => $session_id }, modified => 0, }; foreach (qw(proxy proxyOptions ns localStorage localStorageOptions)) { $self->{$_} = $args->{$_}; } ( $user, $password ) = ( $args->{User}, $args->{Password} ); bless $self, $class; if ( defined $session_id && $session_id ) { die "unexistant session $session_id" unless ( $self->get($session_id) ); } else { die "unable to create session" unless ( $self->newSession() ); } return $self; } sub FETCH { my $self = shift; my $key = shift; return $self->{data}->{$key}; } sub STORE { my $self = shift; my $key = shift; my $value = shift; $self->{data}->{$key} = $value; $self->{modified} = 1; return $value; } sub DELETE { my $self = shift; my $key = shift; $self->{modified} = 1; delete $self->{data}->{$key}; } sub CLEAR { my $self = shift; $self->{modified} = 1; $self->{data} = {}; } sub EXISTS { my $self = shift; my $key = shift; return exists $self->{data}->{$key}; } sub FIRSTKEY { my $self = shift; my $reset = keys %{ $self->{data} }; return each %{ $self->{data} }; } sub NEXTKEY { my $self = shift; return each %{ $self->{data} }; } sub DESTROY { my $self = shift; $self->save; } ## @method private SOAP::Lite _connect() # @return The SOAP::Lite object. Build it at the first call. sub _connect { my $self = shift; return $self->{service} if ( $self->{service} ); my @args = ( $self->{proxy} ); if ( $self->{proxyOptions} ) { push @args, %{ $self->{proxyOptions} }; } $self->{ns} ||= 'urn:Lemonldap/NG/Common/CGI/SOAPService'; return $self->{service} = SOAP::Lite->ns( $self->{ns} )->proxy(@args); } ## @method private $ _soapCall(string func, @args) # @param $func remote function to call # @param @args Functions parameters # @return Result sub _soapCall { my $self = shift; my $func = shift; my $r = $self->_connect->$func(@_); if ( $r->fault ) { print STDERR "SOAP Error: " . $r->fault->{faultstring}; return (); } return $r->result; } ## @method hashRef get(string id) # @param $id Apache::Session session ID. # @return User datas sub get { my $self = shift; my $id = shift; # Check cache if ( $self->{localStorage} && $self->cache->get("soap$id") ) { return $self->{data} = $self->cache->get("soap$id"); } # No cache, use SOAP and set cache my $r = $self->_soapCall( "getAttributes", $id ); return 0 unless ( $r or $r->{error} ); $self->{data} = $r->{attributes}; $self->cache->set( "soap$id", $self->{data} ) if $self->{localStorage}; return $self->{data}; } ## @method hashRef newSession() # Build a new Apache::Session session. # @return User datas (just the session ID) sub newSession { my $self = shift; $self->{data} = $self->_soapCall("newSession"); # Set cache if ( $self->{localStorage} ) { my $id = "soap" . $self->{data}->{_session_id}; if ( $self->cache->get($id) ) { $self->cache->remove($id); } $self->cache->set( $id, $self->{data} ); } return $self->{data}; } ## @method boolean save() # Save user datas if modified. sub save { my $self = shift; return unless ( $self->{modified} ); # Update session in cache if ( $self->{localStorage} ) { my $id = "soap" . $self->{data}->{_session_id}; if ( $self->cache->get($id) ) { $self->cache->remove($id); } $self->cache->set( $id, $self->{data} ); } # SOAP return $self->_soapCall( "setAttributes", $self->{data}->{_session_id}, $self->{data} ); } ## @method boolean delete() # Deletes the current session. sub delete { my $self = shift; # Remove session from cache if ( $self->{localStorage} ) { my $id = "soap" . $self->{data}->{_session_id}; if ( $self->cache->get($id) ) { $self->cache->remove($id); } } # SOAP return $self->_soapCall( "deleteSession", $self->{data}->{_session_id} ); } ## @method get_key_from_all_sessions() # Not documented. sub get_key_from_all_sessions() { my $class = shift; my $args = shift; my $data = shift; my $self = bless {}, $class; foreach (qw(proxy proxyOptions ns)) { $self->{$_} = $args->{$_}; } die('proxy is required') unless ( $self->{proxy} ); ( $user, $password ) = ( $args->{User}, $args->{Password} ); if ( ref($data) eq 'CODE' ) { my $r = $self->_soapCall( "get_key_from_all_sessions", $args ); my $res; if ($r) { foreach my $k ( keys %$r ) { my $tmp = &$data( $r->{$k}, $k ); $res->{$k} = $tmp if ( defined($tmp) ); } } } else { return $self->_soapCall( "get_key_from_all_sessions", $args, $data ); } } sub cache { my $self = shift; return $self->{cache} if $self->{cache}; my $module = $self->{localStorage}; eval "use $module;"; $self->{cache} = $module->new( $self->{localStorageOptions} ); return $self->{cache}; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::Apache::Session::SOAP - Perl extension written to access to Lemonldap::NG Web-SSO sessions via SOAP. =head1 SYNOPSIS =over =item * With Lemonldap::NG::Handler package My::Package; use Lemonldap::NG::Handler::SharedConf; our @ISA = qw(Lemonldap::NG::Handler::Simple); __PACKAGE__->init ({ globalStorage => 'Lemonldap::NG::Common::Apache::Session::SOAP', globalStorageOptions => { proxy => 'http://auth.example.com/index.pl/sessions', proxyOptions => { timeout => 5, }, # If soapserver is protected by HTTP Basic: User => 'http-user', Password => 'pass', # To have a local session cache localStorage => "Cache::FileCache", localStorageOptions => { 'namespace' => 'lemonldap-ng', 'default_expires_in' => 600, }, }, configStorage => { ... # See Lemonldap::NG::Handler =item * With Lemonldap::NG::Portal use Lemonldap::NG::Portal::SharedConf; my $portal = new Lemonldap::NG::Portal::SharedConf ( globalStorage => 'Lemonldap::NG::Common::Apache::Session::SOAP', globalStorageOptions => { proxy => 'http://auth.example.com/index.pl/sessions', proxyOptions => { timeout => 5, }, # If soapserver is protected by HTTP Basic: User => 'http-user', Password => 'pass', # To have a local session cache localStorage => "Cache::FileCache", localStorageOptions => { 'namespace' => 'lemonldap-ng', 'default_expires_in' => 600, }, }, configStorage => { ... # See Lemonldap::NG::Portal You can also set parameters corresponding to "Apache::Session module" in the manager. =back =head1 DESCRIPTION Lemonldap::NG::Common::Conf provides a simple interface to access to Lemonldap::NG Web-SSO configuration. It is used by L, L and L. Lemonldap::NG::Common::Apache::Session::SOAP used with L provides the ability to acces to Lemonldap::NG sessions via SOAP: the portal act as a proxy to access to the real Apache::Session module (see HTML documentation for more) =head2 SECURITY As Lemonldap::NG::Common::Conf::SOAP use SOAP::Lite, you have to see L to know arguments that can be passed to C. Lemonldap::NG provides a system for HTTP basic authentication. Examples : =over =item * HTTP Basic authentication SOAP::transport can use basic authentication by rewriting C<>SOAP::Transport::HTTP::Client::get_basic_credentials>: package My::Package; use base Lemonldap::NG::Handler::SharedConf; __PACKAGE__->init ( { globalStorage => 'Lemonldap::NG::Common::Apache::Session::SOAP', globalStorageOptions => { proxy => 'http://auth.example.com/index.pl/sessions', User => 'http-user', Password => 'pass', }, } ); =item * SSL Authentication SOAP::transport provides a simple way to use SSL certificate: you've just to set environment variables. package My::Package; use base Lemonldap::NG::Handler::SharedConf; # AUTHENTICATION $ENV{HTTPS_CERT_FILE} = 'client-cert.pem'; $ENV{HTTPS_KEY_FILE} = 'client-key.pem'; __PACKAGE__->init ( { globalStorage => 'Lemonldap::NG::Common::Apache::Session::SOAP', globalStorageOptions => { proxy => 'https://auth.example.com/index.pl/sessions', }, } ); =back =head1 SEE ALSO L, L, L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2013 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012 by François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Serialize/000077500000000000000000000000001325274564300367205ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/SessionJSON.pm000066400000000000000000000057021325274564300400330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/Session/Serializepackage Lemonldap::NG::Common::Apache::Session::Serialize::JSON; use strict; use JSON; our $VERSION = '1.9.12'; sub serialize { my $session = shift; $session->{serialized} = to_json( $session->{data}, { allow_nonref => 1 } ); } sub unserialize { my $session = shift; my $data = _unserialize( $session->{serialized} ); die "Session could not be unserialized" unless defined $data; $session->{data} = $data; } sub unserializeBase64 { my $session = shift; my $data = _unserialize( $session->{serialized}, \&decodeThaw64 ); die "Session could not be unserialized" unless defined $data; $session->{data} = $data; } sub decodeThaw64 { require MIME::Base64; my $s = shift; return Storable::thaw( MIME::Base64::decode_base64($s) ); } sub _unserialize { my ( $serialized, $next ) = @_; my $tmp; eval { $tmp = from_json( $serialized, { allow_nonref => 1 } ) }; if ($@) { require Storable; $next ||= \&Storable::thaw; return &$next($serialized); } return $tmp; } 1; =pod =head1 NAME =encoding utf8 Lemonldap::NG::Common::Apache::Session::Serialize::JSON - Use JSON to zip up data =head1 SYNOPSIS use Lemonldap::NG::Common::Apache::Session::Serialize::JSON; $zipped = Lemonldap::NG::Common::Apache::Session::Serialize::JSON::serialize($ref); $ref = Lemonldap::NG::Common::Apache::Session::Serialize::JSON::unserialize($zipped); =head1 DESCRIPTION This module fulfills the serialization interface of Apache::Session. It serializes the data in the session object by use of JSON C and C. The serialized data is UTF-8 text. =head1 SEE ALSO L, L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =back This library 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 2, 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 L. =cut Store.pm000066400000000000000000000047441325274564300364340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Apache/Session package Lemonldap::NG::Common::Apache::Session::Store; our $VERSION = '1.9.1'; sub new { my $class = shift; return bless {}, $class; } sub insert { my $self = shift; my $session = shift; $self->{args} = $session->{args}; # Store session in cache my $id = $session->{data}->{_session_id}; $self->cache->set( $id, $session->{serialized} ); # Store in session backend return $self->module->insert($session); } sub update { my $self = shift; my $session = shift; $self->{args} = $session->{args}; #TODO: remove cache on all LL::NG instances if updateCache == 1 unless ( $session->{args}->{updateCache} == -1 ) { # Update session in cache my $id = $session->{data}->{_session_id}; $self->cache->remove($id) if ( $self->cache->get($id) ); $self->cache->set( $id, $session->{serialized} ); } unless ( $session->{args}->{updateCache} == 2 ) { # Update session in backend return $self->module->update($session); } } sub materialize { my $self = shift; my $session = shift; $self->{args} = $session->{args}; # Get session from cache my $id = $session->{data}->{_session_id}; if ( $self->cache->get($id) ) { $session->{serialized} = $self->cache->get($id); return; } # Get session from backend $self->module->materialize($session); # Store session in cache $self->cache->set( $id, $session->{serialized} ); return; } sub remove { my $self = shift; my $session = shift; $self->{args} = $session->{args}; #TODO: remove cache on all LL::NG instances if updateCache == 1 unless ( $session->{args}->{updateCache} == -1 ) { # Remove session from cache my $id = $session->{data}->{_session_id}; $self->cache->remove($id) if ( $self->cache->get($id) ); } unless ( $session->{args}->{updateCache} == 2 ) { # Remove session from backend return $self->module->remove($session); } } sub close { my $self = shift; my $session = shift; $self->{args} = $session->{args}; return $self->module->close; } sub module { my $self = shift; return $self->{args}->{object_store}; } sub cache { my $self = shift; return $self->{cache} if $self->{cache}; my $module = $self->{args}->{localStorage}; eval "use $module;"; $self->{cache} = $module->new( $self->{args}->{localStorageOptions} ); return $self->{cache}; } 1; CGI.pm000066400000000000000000000367671325274564300331500ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common## @file # Base package for all Lemonldap::NG CGI ## @class # Base class for all Lemonldap::NG CGI package Lemonldap::NG::Common::CGI; use strict; use File::Basename; use MIME::Base64; use Time::Local; use CGI; use utf8; use Encode; use Net::CIDR::Lite; #parameter syslog Indicates syslog facility for logging user actions our $VERSION = '1.9.1'; our $_SUPER; our @ISA; BEGIN { if ( exists $ENV{MOD_PERL} ) { if ( $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 ) { eval 'use constant MP => 2;'; } else { eval 'use constant MP => 1;'; } } else { eval 'use constant MP => 0;'; } $_SUPER = 'CGI'; @ISA = ('CGI'); } sub import { my $pkg = shift; if ( $pkg eq __PACKAGE__ and @_ and $_[0] eq "fastcgi" ) { eval 'use CGI::Fast'; die($@) if ($@); unshift @ISA, 'CGI::Fast'; $_SUPER = 'CGI::Fast'; } } ## @cmethod Lemonldap::NG::Common::CGI new(@p) # Constructor: launch CGI::new() then secure parameters since CGI store them at # the root of the object. # @param p arguments for CGI::new() # @return new Lemonldap::NG::Common::CGI object sub new { my $class = shift; my $self = $_SUPER->new(@_) or return undef; $self->{_prm} = {}; my @tmp = $self->param(); foreach (@tmp) { $self->{_prm}->{$_} = $self->param($_); $self->delete($_); } $self->{lang} = extract_lang(); bless $self, $class; return $self; } ## @method scalar param(string s, scalar newValue) # Return the wanted parameter issued of GET or POST request. If $s is not set, # return the list of parameters names # @param $s name of the parameter # @param $newValue if set, the parameter will be set to his value # @return datas passed by GET or POST method sub param { my ( $self, $p, $v ) = @_; $self->{_prm}->{$p} = $v if ($v); unless ( defined $p ) { return keys %{ $self->{_prm} }; } return $self->{_prm}->{$p}; } ## @method scalar rparam(string s) # Return a reference to a parameter # @param $s name of the parameter # @return ref to parameter data sub rparam { my ( $self, $p ) = @_; return $self->{_prm}->{$p} ? \$self->{_prm}->{$p} : undef; } ## @method void lmLog(string mess, string level) # Log subroutine. Use Apache::Log in ModPerl::Registry context else simply # print on STDERR non debug messages. # @param $mess Text to log # @param $level Level (debug|info|notice|error) sub lmLog { my ( $self, $mess, $level ) = @_; my $call; if ( $level eq 'debug' ) { $mess = ( ref($self) ? ref($self) : $self ) . ": $mess"; } else { my @tmp = caller(); $call = "$tmp[1] $tmp[2]:"; } if ( $self->r and MP() ) { $self->abort( "Level is required", 'the parameter "level" is required when lmLog() is used' ) unless ($level); if ( MP() == 2 ) { require Apache2::Log; Apache2::ServerRec->log->debug($call) if ($call); Apache2::ServerRec->log->$level($mess); } else { Apache->server->log->debug($call) if ($call); Apache->server->log->$level($mess); } } else { $self->{hideLogLevels} = 'debug|info' unless defined( $self->{hideLogLevels} ); my $re = qr/^(?:$self->{hideLogLevels})$/o; print STDERR "$call\n" if ( $call and 'debug' !~ $re ); print STDERR "[$level] $mess\n" unless ( $level =~ $re ); } } ## @method void setApacheUser(string user) # Set user for Apache logs in ModPerl::Registry context. Does nothing else. # @param $user data to set as user in Apache logs sub setApacheUser { my ( $self, $user ) = @_; if ( $self->r and MP() ) { $self->lmLog( "Inform Apache about the user connected", 'debug' ); if ( MP() == 2 ) { require Apache2::Connection; $self->r->user($user); } else { $self->r->connection->user($user); } } $ENV{REMOTE_USER} = $user; } ##@method string getApacheHtdocsPath() # Return absolute path to the htdocs directory where the current script is # @return path string sub getApacheHtdocsPath { return dirname( $ENV{SCRIPT_FILENAME} || $0 ); } ## @method void soapTest(string soapFunctions, object obj) # Check if request is a SOAP request. If it is, launch # Lemonldap::NG::Common::CGI::SOAPServer and exit. Else simply return. # @param $soapFunctions list of authorized functions. # @param $obj optional object that will receive SOAP requests sub soapTest { my ( $self, $soapFunctions, $obj ) = @_; # If non form encoded datas are posted, we call SOAP Services if ( $ENV{HTTP_SOAPACTION} ) { require Lemonldap::NG::Common::CGI::SOAPServer; #link protected dispatcher require Lemonldap::NG::Common::CGI::SOAPService; #link protected soapService my @func = ( ref($soapFunctions) ? @$soapFunctions : split /\s+/, $soapFunctions ); my $dispatcher = Lemonldap::NG::Common::CGI::SOAPService->new( $obj || $self, @func ); Lemonldap::NG::Common::CGI::SOAPServer->dispatch_to($dispatcher) ->handle($self); $self->quit(); } } ## @method string header_public(string filename) # Implements the "304 Not Modified" HTTP mechanism. # If HTTP request contains an "If-Modified-Since" header and if # $filename was not modified since, prints the "304 Not Modified" response and # exit. Else, launch CGI::header() with "Cache-Control" and "Last-Modified" # headers. # @param $filename Optional name of the reference file. Default # $ENV{SCRIPT_FILENAME}. # @return Common Gateway Interface standard response header sub header_public { my $self = shift; my $filename = shift; $filename ||= $ENV{SCRIPT_FILENAME}; my @tmp = stat($filename); my $date = $tmp[9]; my $hd = gmtime($date); $hd =~ s/^(\w+)\s+(\w+)\s+(\d+)\s+([\d:]+)\s+(\d+)$/$1, $3 $2 $5 $4 GMT/; my $year = $5; my $cm = $2; # TODO: Remove TODO_ for stable releases if ( my $ref = $ENV{HTTP_IF_MODIFIED_SINCE} ) { my %month = ( jan => 0, feb => 1, mar => 2, apr => 3, may => 4, jun => 5, jul => 6, aug => 7, sep => 8, oct => 9, nov => 10, dec => 11 ); if ( $ref =~ /^\w+,\s+(\d+)\s+(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)/ ) { my $m = $month{ lc($2) }; $year-- if ( $m > $month{ lc($cm) } ); $ref = timegm( $6, $5, $4, $1, $m, $3 ); if ( $ref == $date ) { print $self->SUPER::header( -status => '304 Not Modified', @_ ); $self->quit(); } } } return $self->SUPER::header( '-Last-Modified' => $hd, '-Cache-Control' => 'public; must-revalidate; max-age=1800', @_ ); } ## @method void abort(string title, string text) # Display an error message and exit. # Used instead of die() in Lemonldap::NG CGIs. # @param title Title of the error message # @param text Optional text. Default: "See Apache's logs" sub abort { my $self = shift; my $cgi = CGI->new(); my ( $t1, $t2 ) = @_; # Default message $t2 ||= "See Apache's logs"; # Change \n into
    for HTML my $t2html = $t2; $t2html =~ s#\n#
    #g; print $cgi->header( -type => 'text/html; charset=utf-8', ); print $cgi->start_html( -title => $t1, -encoding => 'utf8', -style => { -code => ' body{ background:#000; color:#fff; padding:10px 50px; font-family:sans-serif; } a { text-decoration:none; color:#fff; } ' }, ); print "

    $t1

    $t2html

    "; print '
    LemonLDAP::NG
    '; print STDERR ( ref($self) || $self ) . " error: $t1, $t2\n"; print $cgi->end_html(); $self->quit(); } ##@method private void startSyslog() # Open syslog connection. sub startSyslog { my $self = shift; return if ( $self->{_syslog} ); eval { require Sys::Syslog; Sys::Syslog->import(':standard'); openlog( 'lemonldap-ng', 'ndelay,pid', $self->{syslog} ); }; $self->abort( "Unable to use syslog", $@ ) if ($@); $self->{_syslog} = 1; } ##@method void userLog(string mess, string level) # Log user actions on Apache logs or syslog. # @param $mess string to log # @param $level level of log message sub userLog { my ( $self, $mess, $level ) = @_; if ( $self->{syslog} ) { $self->startSyslog(); $level =~ s/^warn$/warning/; syslog( $level || 'notice', $mess ); } else { $self->lmLog( $mess, $level ); } } ##@method void userInfo(string mess) # Log non important user actions. Alias for userLog() with facility "info". # @param $mess string to log sub userInfo { my ( $self, $mess ) = @_; $mess = "Lemonldap::NG : $mess (" . $self->ipAddr . ")"; $self->userLog( $mess, 'info' ); } ##@method void userNotice(string mess) # Log user actions like access and logout. Alias for userLog() with facility # "notice". # @param $mess string to log sub userNotice { my ( $self, $mess ) = @_; $mess = "Lemonldap::NG : $mess (" . $self->ipAddr . ")"; $self->userLog( $mess, 'notice' ); } ##@method void userError(string mess) # Log user errors like "bad password". Alias for userLog() with facility # "warn". # @param $mess string to log sub userError { my ( $self, $mess ) = @_; $mess = "Lemonldap::NG : $mess (" . $self->ipAddr . ")"; $self->userLog( $mess, 'warn' ); } ## @method protected scalar _sub(string sub, array p) # Launch $self->{$sub} if defined, else launch $self->$sub. # @param $sub name of the sub to launch # @param @p parameters for the sub sub _sub { my ( $self, $sub, @p ) = @_; if ( $self->{$sub} ) { $self->lmLog( "processing to custom sub $sub", 'debug' ); return &{ $self->{$sub} }( $self, @p ); } else { $self->lmLog( "processing to sub $sub", 'debug' ); return $self->$sub(@p); } } ##@method string extract_lang #@return array of user's preferred languages (two letters) sub extract_lang { my $self = shift; my @langs = split /,\s*/, ( shift || $ENV{HTTP_ACCEPT_LANGUAGE} || "" ); my @res = (); foreach (@langs) { # Languages are supposed to be sorted by preference my $lang = ( split /;/ )[0]; # Take first part of lang code (part before -) $lang = ( split /-/, $lang )[0]; # Go to next if lang was already added next if grep( /\Q$lang\E/, @res ); # Store lang only if size is 2 characters push @res, $lang if ( length($lang) == 2 ); } return \@res; } ##@method void translate_template(string text_ref, string lang) # translate_template is used as an HTML::Template filter to tranlate strings in # the wanted language #@param text_ref reference to the string to translate #@param lang optionnal language wanted. Falls to browser language instead. #@return sub translate_template { my $self = shift; my $text_ref = shift; # Decode UTF-8 utf8::decode($$text_ref) unless ( $ENV{FCGI_ROLE} ); # Test if a translation is available for the selected language # If not available, return the first translated string # foreach ( @{ $self->{lang} } ) { if ( $$text_ref =~ m/$_=\"(.*?)\"/ ) { $$text_ref =~ s//$1/gx; return; } } $$text_ref =~ s//$1/gx; } ##@method void session_template(string text_ref) # session_template is used as an HTML::Template filter to replace session info # by their value #@param text_ref reference to the string to translate #@return sub session_template { my $self = shift; my $text_ref = shift; # Replace session information $$text_ref =~ s/\$(\w+)/decode("utf8",$self->{sessionInfo}->{$1})/ge; } ## @method private void quit() # Simply exit. sub quit { my $self = shift; if ( $_SUPER eq 'CGI::Fast' ) { next LMAUTH; } else { exit; } } ##@method string ipAddr() # Retrieve client IP address from remote address or X-FORWARDED-FOR header #@return client IP sub ipAddr { my $self = shift; unless ( $self->{ipAddr} ) { $self->{ipAddr} = $ENV{REMOTE_ADDR}; if ( my $xheader = $ENV{HTTP_X_FORWARDED_FOR} ) { if ( $self->{trustedProxies} =~ /\*/ or $self->{useXForwardedForIP} ) { $self->{ipAddr} = $1 if ( $xheader =~ /^([^,]*)/ ); } elsif ( $self->{trustedProxies} ) { my $localIP = Net::CIDR::Lite->new("127.0.0.0/8"); # TODO: add IPv6 local IP my $trustedIP = Net::CIDR::Lite->new( split /\s+/, $self->{trustedProxies} ); while ( ( $localIP->find( $self->{ipAddr} ) or $trustedIP->find( $self->{ipAddr} ) ) and $xheader =~ s/[,\s]*([^,\s]+)$// ) { # because it is of no use to store a local IP as client IP $self->{ipAddr} = $1 unless ( $localIP->find($1) ); } } } } return $self->{ipAddr}; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::CGI - Simple module to extend L to manage HTTP "If-Modified-Since / 304 Not Modified" system. =head1 SYNOPSIS use Lemonldap::NG::Common::CGI; my $cgi = Lemonldap::NG::Common::CGI->new(); $cgi->header_public($ENV{SCRIPT_FILENAME}); print "Static page"; ... =head1 DESCRIPTION Lemonldap::NG::Common::CGI just add header_public subroutine to CGI module to avoid printing HTML elements that can be cached. =head1 METHODS =head2 header_public header_public works like header (see L) but the first argument has to be a filename: the last modify date of this file is used for reference. =head2 EXPORT =head1 SEE ALSO L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012-2013 by François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Copyright (C) 2010-2016 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut CGI/000077500000000000000000000000001325274564300325675ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/CommonSOAPServer.pm000066400000000000000000000103201325274564300350520ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/CGI## @file # SOAP support for Lemonldap::NG::Common::CGI ## @class # Extend SOAP::Transport::HTTP::Server to be able to use posted datas catched # by CGI module. # All Lemonldap::NG cgi inherits from CGI so with this library, they can # understand both browser and SOAP requests. package Lemonldap::NG::Common::CGI::SOAPServer; use SOAP::Transport::HTTP; use base qw(SOAP::Transport::HTTP::Server); use bytes; our $VERSION = '1.9.1'; ## @method protected void DESTROY() # Call SOAP::Trace::objects(). sub DESTROY { SOAP::Trace::objects('()') } ## @cmethod Lemonldap::NG::Common::CGI::SOAPServer new(@param) # @param @param SOAP::Transport::HTTP::Server::new() parameters # @return Lemonldap::NG::Common::CGI::SOAPServer object sub new { my $self = shift; return $self if ref $self; my $class = ref($self) || $self; $self = $class->SUPER::new(@_); SOAP::Trace::objects('()'); return $self; } ## @method void handle(CGI cgi) # Build SOAP request using CGI->param('POSTDATA') and call # SOAP::Transport::HTTP::Server::handle() then return the result to the client. # @param $cgi CGI object sub handle { my $self = shift->new; my $cgi = shift; my $content = $cgi->param('POSTDATA'); my $length = bytes::length($content); if ( !$length ) { $self->response( HTTP::Response->new(411) ) # LENGTH REQUIRED } elsif ( defined $SOAP::Constants::MAX_CONTENT_SIZE && $length > $SOAP::Constants::MAX_CONTENT_SIZE ) { $self->response( HTTP::Response->new(413) ) # REQUEST ENTITY TOO LARGE } else { $self->request( HTTP::Request->new( 'POST' => $ENV{'SCRIPT_NAME'}, HTTP::Headers->new( map { ( /^HTTP_(.+)/i ? ( $1 =~ m/SOAPACTION/ ) ? ('SOAPAction') : ($1) : $_ ) => $ENV{$_} } keys %ENV ), $content, ) ); $self->SUPER::handle(); } print $cgi->header( -status => $self->response->code . " " . HTTP::Status::status_message( $self->response->code ), -type => $self->response->header('Content-Type'), -Content_Length => $self->response->header('Content-Length'), -SOAPServer => 'Lemonldap::NG CGI', ); binmode( STDOUT, ":bytes" ); print $self->response->content; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::CGI::SOAPServer - Extends L to be compatible with L. =head1 SYNOPSIS use CGI; use Lemonldap::NG::Common::CGI::SOAPServer; my $cgi = CGI->new(); Lemonldap::NG::Common::CGI::SOAPServer->dispatch_to('same as SOAP::Lite') ->handle($cgi) =head1 DESCRIPTION This extension just extend L handle() method to load datas from a L object instead of STDIN. =head1 SEE ALSO L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2010 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut SOAPService.pm000066400000000000000000000055611325274564300352170ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/CGI## @file # SOAP wrapper used to restrict exported functions ## @class # SOAP wrapper used to restrict exported functions package Lemonldap::NG::Common::CGI::SOAPService; require SOAP::Lite; our $VERSION = '1.9.1'; ## @cmethod Lemonldap::NG::Common::CGI::SOAPService new(object obj,string @func) # Constructor # @param $obj object which will be called for SOAP authorizated methods # @param @func authorizated methods # @return Lemonldap::NG::Common::CGI::SOAPService object sub new { my ( $class, $obj, @func ) = @_; s/.*::// foreach (@func); return bless { obj => $obj, func => \@func }, $class; } ## @method datas AUTOLOAD() # Call the wanted function with the object given to the constructor. # AUTOLOAD() is a magic method called by Perl interpreter fon non existent # functions. Here, we use it to call the wanted function (given by $AUTOLOAD) # if it is authorizated # @return datas provided by the exported function sub AUTOLOAD { my $self = shift; $AUTOLOAD =~ s/.*:://; if ( grep { $_ eq $AUTOLOAD } @{ $self->{func} } ) { my $tmp = $self->{obj}->$AUTOLOAD(@_); unless ( ref($tmp) and ref($tmp) eq 'SOAP::Data' ) { $tmp = SOAP::Data->name( result => $tmp ); } return $tmp; } elsif ( $AUTOLOAD ne 'DESTROY' ) { die "$AUTOLOAD is not an authorizated function"; } 1; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::CGI::SOAPService - Wrapper for all SOAP functions of Lemonldap::NG CGIs. =head1 SYNOPSIS See L =head1 DESCRIPTION Private class used by L to control SOAP functions access. =head1 SEE ALSO L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2009-2010 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Captcha.pm000066400000000000000000000051111325274564300340640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common##@file # Base package for LemonLDAP::NG Captcha ##@class # Captcha module that uses session backend package Lemonldap::NG::Common::Captcha; our $VERSION = '1.9.15'; use strict; use Lemonldap::NG::Common::Session; use Mouse; use Digest::MD5 qw(md5_hex); has 'storageModule' => ( is => 'ro', isa => 'Str', required => 1, ); has 'storageModuleOptions' => ( is => 'ro', isa => 'HashRef|Undef', ); has code => ( is => 'rw', isa => 'Str' ); has md5 => ( is => 'rw', isa => 'Str' ); has image => ( is => 'rw', isa => 'Str' ); has size => ( is => 'ro', isa => 'Int' ); sub BUILD { my $self = shift; unless ( $self->md5 ) { # Create captcha object require Authen::Captcha; my $captcha = Authen::Captcha->new(); # Generate code and md5 my $code = $captcha->generate_random_string( $self->size ); my $md5 = md5_hex($code); $self->code($code); $self->md5($md5); # Generate image data my $data = $captcha->create_image_file( $code, $md5 ); $self->image($$data); # Save captcha session $self->saveSession; } else { $self->getSession; } } sub saveSession { my $self = shift; # Create new session my $session = Lemonldap::NG::Common::Session->new( { storageModule => $self->storageModule, storageModuleOptions => $self->storageModuleOptions, id => $self->md5, force => 1, kind => "Captcha", } ); $session->update( { _utime => time, code => $self->code, image => $self->image } ); } sub getSession { my $self = shift; # Get session my $session = Lemonldap::NG::Common::Session->new( { storageModule => $self->storageModule, storageModuleOptions => $self->storageModuleOptions, id => $self->md5, } ); if ( $session && $session->data ) { $self->code( $session->data->{code} ) if $session->data->{code}; $self->image( $session->data->{image} ) if $session->data->{image}; } } sub removeSession { my $self = shift; # Get session my $session = Lemonldap::NG::Common::Session->new( { storageModule => $self->storageModule, storageModuleOptions => $self->storageModuleOptions, id => $self->md5, } ); if ($session) { return $session->remove; } return 0; } no Mouse; 1; Cli.pm000066400000000000000000000037301325274564300332350ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Commonpackage Lemonldap::NG::Common::Cli; use strict; use Mouse; use Data::Dumper; use Lemonldap::NG::Common::Conf; has confAccess => ( is => 'rw', builder => sub { my $res = Lemonldap::NG::Common::Conf->new( { ( ref $_[0] && $_[0]->{iniFile} ? ( confFile => $_[0]->{iniFile} ) : () ) } ); die $Lemonldap::NG::Common::Conf::msg unless ($res); return $res; }, ); has cfgNum => ( is => 'rw', isa => 'Int', ); sub info { my ($self) = @_; my $conf = $self->confAccess->getConf( { cfgNum => $self->cfgNum, raw => 1 } ) or die $Lemonldap::NG::Common::Conf::msg; print qq{ Num : $conf->{cfgNum} Author : $conf->{cfgAuthor} Author IP: $conf->{cfgAuthorIP} Date : } . localtime( $conf->{cfgDate} ) . qq{ Log : $conf->{cfgLog} }; } sub updateCache { my $self = shift; die "Must not be launched as root" unless ($>); my $conf = $self->confAccess->getConf( { noCache => 2 } ); print STDERR qq{Cache updated to configuration $conf->{cfgNum} for user $>\n}; } sub run { my $self = shift; # Options simply call corresponding accessor my $args = {}; while ( $_[0] =~ s/^--?// ) { my $k = shift; my $v = shift; if ( ref $self ) { eval { $self->$k($v) }; if ($@) { die "Unknown option -$k or bad value ($@)"; } } else { $args->{$k} = $v; } } unless ( ref $self ) { $self = $self->new($args); } unless (@_) { die 'nothing to do, aborting'; } $self->confAccess()->lastCfg() unless ( $self->cfgNum ); my $action = shift; unless ( $action =~ /^(?:info|update-cache)$/ ) { die "unknown action $action. Only info or update are accepted"; } $action =~ s/\-([a-z])/uc($1)/e; $self->$action(@_); } 1; Conf.pm000066400000000000000000000456541325274564300334260ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common##@file # Base package for Lemonldap::NG configuration system ##@class # Implements Lemonldap::NG shared configuration system. # In case of error or warning, the message is stored in the global variable # $Lemonldap::NG::Common::Conf::msg package Lemonldap::NG::Common::Conf; use strict; use utf8; no strict 'refs'; use Lemonldap::NG::Common::Conf::Constants; #inherits # TODO: don't import this big file, use a proxy use Lemonldap::NG::Common::Conf::DefaultValues; #inherits use Lemonldap::NG::Common::Crypto ; #link protected cipher Object "cypher" in configuration hash use Config::IniFiles; #inherits Lemonldap::NG::Common::Conf::File #inherits Lemonldap::NG::Common::Conf::DBI #inherits Lemonldap::NG::Common::Conf::SOAP #inherits Lemonldap::NG::Common::Conf::LDAP our $VERSION = '1.9.1'; our $msg = ''; our $iniObj; BEGIN { eval { require threads::shared; threads::shared::share($iniObj); }; } ## @cmethod Lemonldap::NG::Common::Conf new(hashRef arg) # Constructor. # Succeed if it has found a way to access to Lemonldap::NG configuration with # $arg (or default file). It can be : # - Nothing: default configuration file is tested, # - { confFile => "/path/to/storage.conf" }, # - { Type => "File", dirName => "/path/to/conf/dir/" }, # - { Type => "DBI", dbiChain => "DBI:mysql:database=lemonldap-ng;host=1.2.3.4", # dbiUser => "user", dbiPassword => "password" }, # - { Type => "SOAP", proxy => "https://auth.example.com/index.pl/config" }, # - { Type => "LDAP", ldapServer => "ldap://localhost", ldapConfBranch => "ou=conf,ou=applications,dc=example,dc=com", # ldapBindDN => "cn=manager,dc=example,dc=com", ldapBindPassword => "secret"}, # # $self->{type} contains the type of configuration access system and the # corresponding package is loaded. # @param $arg hash reference or hash table # @return New Lemonldap::NG::Common::Conf object sub new { my $class = shift; my $self = bless {}, $class; if ( ref( $_[0] ) ) { %$self = %{ $_[0] }; } else { if ( (@_) && $#_ % 2 == 1 ) { %$self = @_; } } unless ( $self->{mdone} ) { unless ( $self->{type} ) { # Use local conf to get configStorage and localStorage my $localconf = $self->getLocalConf( CONFSECTION, $self->{confFile}, 0 ); if ( defined $localconf ) { %$self = ( %$self, %$localconf ); } } unless ( $self->{type} ) { $msg .= "Error: configStorage: type is not defined.\n"; return 0; } unless ( $self->{type} =~ /^[\w:]+$/ ) { $msg .= "Error: configStorage: type is not well formed.\n"; } $self->{type} = "Lemonldap::NG::Common::Conf::$self->{type}" unless $self->{type} =~ /^Lemonldap::/; eval "require $self->{type}"; if ($@) { $msg .= "Error: Unknown package $self->{type}.\n"; return 0; } return 0 unless $self->prereq; $self->{mdone}++; $msg = "$self->{type} loaded.\n"; } if ( $self->{localStorage} and not defined( $self->{refLocalStorage} ) ) { eval "use $self->{localStorage};"; if ($@) { $msg .= "Unable to load $self->{localStorage}: $@.\n"; } # TODO: defer that until $> > 0 (to avoid creating local cache with # root privileges else { $self->{refLocalStorage} = $self->{localStorage}->new( $self->{localStorageOptions} ); } } return $self; } ## @method int saveConf(hashRef conf, hash args) # Serialize $conf and call store(). # @param $conf Lemonldap::NG configuration hashRef # @param %args Parameters # @return Number of the saved configuration, 0 in case of error. sub saveConf { my ( $self, $conf, %args ) = @_; my $last = $self->lastCfg; # If configuration was modified, return an error if ( not $args{force} ) { return CONFIG_WAS_CHANGED if ( $conf->{cfgNum} != $last ); return DATABASE_LOCKED if ( $self->isLocked() or not $self->lock() ); } $conf->{cfgNum} = $last + 1 unless ( $args{cfgNumFixed} ); delete $conf->{cipher}; # Try to store configuration my $tmp = $self->store($conf); unless ( $tmp > 0 ) { $msg .= "Configuration $conf->{cfgNum} not stored.\n"; $self->unlock(); return ( $tmp ? $tmp : UNKNOWN_ERROR ); } $msg .= "Configuration $conf->{cfgNum} stored.\n"; return ( $self->unlock() ? $tmp : UNKNOWN_ERROR ); } ## @method hashRef getConf(hashRef args) # Get configuration from remote configuration storage system or from local # cache if configuration has not been changed. If $args->{local} is set and if # a local configuration is available, remote configuration is not tested. # # Uses lastCfg to test and getDBConf() to get the remote configuration # @param $args Optional, contains {local=>1} or nothing # @return Lemonldap::NG configuration sub getConf { my ( $self, $args ) = @_; # Use only cache to get conf if $args->{local} is set if ( $> and $args->{local} and ref( $self->{refLocalStorage} ) and my $res = $self->{refLocalStorage}->get('conf') ) { $msg .= "Get configuration from cache without verification.\n"; return $res; } # Check cfgNum in conf backend # Get conf in backend only if a newer configuration is available else { $args->{cfgNum} ||= $self->lastCfg; unless ( $args->{cfgNum} ) { $msg .= "No configuration available in backend.\n"; } my $r; unless ( ref( $self->{refLocalStorage} ) ) { $msg .= "Get remote configuration (localStorage unavailable).\n"; $r = $self->getDBConf($args); } else { eval { $r = $self->{refLocalStorage}->get('conf') } if ( $> and not $args->{noCache} ); $msg = "Warn: $@" if ($@); if ( ref($r) and $r->{cfgNum} and $args->{cfgNum} and $r->{cfgNum} == $args->{cfgNum} ) { $msg .= "Configuration unchanged, get configuration from cache.\n"; $args->{noCache} = 1; } else { $r = $self->getDBConf($args); return undef unless ( $r->{cfgNum} ); # TODO: default values may not be set here unless ( $args->{raw} ) { # Adapt some values before storing in local cache # Get default values my $defaultValues = Lemonldap::NG::Common::Conf::DefaultValues ->defaultValues(); foreach my $k ( keys %$defaultValues ) { $r->{$k} //= $defaultValues->{$k}; } } # Convert old option useXForwardedForIP into trustedProxies if ( defined $r->{useXForwardedForIP} and $r->{useXForwardedForIP} == 1 ) { $r->{trustedProxies} = '*'; delete $r->{useXForwardedForIP}; } # Force Choice backend if ( $r->{authentication} eq "Choice" ) { $r->{userDB} = "Choice"; $r->{passwordDB} = "Choice"; } # Some parameters expect key name (example), not variable ($example) if ( defined $r->{whatToTrace} ) { $r->{whatToTrace} =~ s/^\$//; } # Store modified configuration in cache $self->setLocalConf($r) if ( $self->{refLocalStorage} and not( $args->{noCache} == 1 or $args->{raw} ) ); } } # Create cipher object unless ( $args->{raw} ) { eval { $r->{cipher} = Lemonldap::NG::Common::Crypto->new( $r->{key} ); }; if ($@) { $msg .= "Bad key: $@. \n"; } } # Return configuration hash return $r; } } ## @method hashRef getLocalConf(string section, string file, int loaddefault) # Get configuration from local file # # @param $section Optional section name (default DEFAULTSECTION) # @param $file Optional file name (default DEFAULTCONFFILE) # @param $loaddefault Optional load default section parameters (default 1) # @return Lemonldap::NG configuration sub getLocalConf { my ( $self, $section, $file, $loaddefault ) = @_; my $r = {}; $section ||= DEFAULTSECTION; $file ||= $self->{confFile} || $ENV{LLNG_DEFAULTCONFFILE} || DEFAULTCONFFILE; $loaddefault = 1 unless ( defined $loaddefault ); my $cfg; # First, search if this file has been parsed unless ( $cfg = $iniObj->{$file} ) { # If default configuration cannot be read # - Error if configuration section is requested # - Silent exit for other section requests unless ( -r $file ) { if ( $section eq CONFSECTION ) { $msg .= "Cannot read $file to get configuration access parameters.\n"; return $r; } return $r; } # Parse ini file $cfg = Config::IniFiles->new( -file => $file, -allowcontinue => 1 ); unless ( defined $cfg ) { $msg .= "Local config error: " . @Config::IniFiles::errors . "\n"; return $r; } # Check if default section exists unless ( $cfg->SectionExists(DEFAULTSECTION) ) { $msg .= "Default section (" . DEFAULTSECTION . ") is missing. \n"; return $r; } # Check if configuration section exists if ( $section eq CONFSECTION and !$cfg->SectionExists(CONFSECTION) ) { $msg .= "Configuration section (" . CONFSECTION . ") is missing.\n"; return $r; } } # First load all default section parameters if ($loaddefault) { foreach ( $cfg->Parameters(DEFAULTSECTION) ) { $r->{$_} = $cfg->val( DEFAULTSECTION, $_ ); if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) { eval "\$r->{$_} = $r->{$_}"; if ($@) { $msg .= "Warning: error in file $file: $@.\n"; return $r; } } } } # Stop if the requested section is the default section return $r if ( $section eq DEFAULTSECTION ); # Check if requested section exists return $r unless $cfg->SectionExists($section); # Load section parameters foreach ( $cfg->Parameters($section) ) { $r->{$_} = $cfg->val( $section, $_ ); if ( $r->{$_} =~ /^[{\[].*[}\]]$/ || $r->{$_} =~ /^sub\s*{.*}$/ ) { eval "\$r->{$_} = $r->{$_}"; if ($@) { $msg .= "Warning: error in file $file: $@.\n"; return $r; } } } return $r; } ## @method void setLocalConf(hashRef conf) # Store $conf in the local cache. # @param $conf Lemonldap::NG configuration hashRef sub setLocalConf { my ( $self, $conf ) = @_; return unless ($>); eval { $self->{refLocalStorage}->set( "conf", $conf ) }; $msg .= "Warn: $@\n" if ($@); } ## @method hashRef getDBConf(hashRef args) # Get configuration from remote storage system. # @param $args hashRef that must contains a key "cfgNum" (number of the wanted # configuration) and optionaly a key "fields" that points to an array of wanted # configuration keys # @return Lemonldap::NG configuration hashRef sub getDBConf { my ( $self, $args ) = @_; return undef unless $args->{cfgNum}; if ( $args->{cfgNum} < 0 ) { my @a = $self->available(); $args->{cfgNum} = ( @a + $args->{cfgNum} > 0 ) ? ( $a[ $#a + $args->{cfgNum} ] ) : $a[0]; } my $conf = $self->load( $args->{cfgNum} ); $msg .= "Get configuration $conf->{cfgNum}.\n" if ( defined $conf->{cfgNum} ); $self->setLocalConf($conf) if ( ref($conf) and $self->{refLocalStorage} and not( $args->{noCache} ) ); return $conf; } ## @method boolean prereq() # Call prereq() from the $self->{type} package. # @return True if succeed sub prereq { return &{ $_[0]->{type} . '::prereq' }(@_); } ## @method @ available() # Call available() from the $self->{type} package. # @return list of available configuration numbers sub available { return &{ $_[0]->{type} . '::available' }(@_); } ## @method int lastCfg() # Call lastCfg() from the $self->{type} package. # @return Number of the last configuration available sub lastCfg { my $result = &{ $_[0]->{type} . '::lastCfg' }(@_) || "0"; return $result; } ## @method boolean lock() # Call lock() from the $self->{type} package. # @return True if succeed sub lock { return &{ $_[0]->{type} . '::lock' }(@_); } ## @method boolean isLocked() # Call isLocked() from the $self->{type} package. # @return True if database is locked sub isLocked { return &{ $_[0]->{type} . '::isLocked' }(@_); } ## @method boolean unlock() # Call unlock() from the $self->{type} package. # @return True if succeed sub unlock { return &{ $_[0]->{type} . '::unlock' }(@_); } ## @method int store(hashRef conf) # Call store() from the $self->{type} package. # @param $conf Lemondlap configuration serialized # @return Number of new configuration stored if succeed, 0 else. sub store { return &{ $_[0]->{type} . '::store' }(@_); } ## @method load(int cfgNum, arrayRef fields) # Call load() from the $self->{type} package. # @return Lemonldap::NG Configuration hashRef if succeed, 0 else. sub load { return &{ $_[0]->{type} . '::load' }(@_); } ## @method boolean delete(int cfgNum) # Call delete() from the $self->{type} package. # @param $cfgNum Number of configuration to delete # @return True if succeed sub delete { my ( $self, $c ) = @_; my @a = $self->available(); if ( grep( /^$c$/, @a ) ) { return &{ $self->{type} . '::delete' }( $self, $c ); } else { return 0; } } sub logError { return &{ $_[0]->{type} . '::logError' }(@_); } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::Conf - Perl extension written to manage Lemonldap::NG Web-SSO configuration. =head1 SYNOPSIS use Lemonldap::NG::Common::Conf; my $confAccess = new Lemonldap::NG::Common::Conf( { type=>'File', dirName=>"/tmp/", # To use local cache, set : localStorage => "Cache::FileCache", localStorageOptions = { 'namespace' => 'lemonldap-ng-config', 'default_expires_in' => 600, 'directory_umask' => '007', 'cache_root' => '/tmp', 'cache_depth' => 5, }, }, ) or die "Unable to build Lemonldap::NG::Common::Conf, see Apache logs"; my $config = $confAccess->getConf(); =head1 DESCRIPTION Lemonldap::NG::Common::Conf provides a simple interface to access to Lemonldap::NG Web-SSO configuration. It is used by L, L and L. =head2 SUBROUTINES =over =item * B (constructor): it takes different arguments depending on the chosen type. Examples: =over =item * B: $confAccess = new Lemonldap::NG::Common::Conf( { type => 'File', dirName => '/var/lib/lemonldap-ng/', }); =item * B: $confAccess = new Lemonldap::NG::Common::Conf( { type => 'DBI', dbiChain => 'DBI:mysql:database=lemonldap-ng;host=1.2.3.4', dbiUser => 'lemonldap' dbiPassword => 'pass' dbiTable => 'lmConfig', }); =item * B: $confAccess = new Lemonldap::NG::Common::Conf( { type => 'SOAP', proxy => 'http://auth.example.com/index.pl/config', proxyOptions => { timeout => 5, }, }); SOAP configuration access is a sort of proxy: the portal is configured to use the real session storage type (DBI or File for example). See HTML documentation for more. =item * B: $confAccess = new Lemonldap::NG::Common::Conf( { type => 'LDAP', ldapServer => 'ldap://localhost', ldapConfBranch => 'ou=conf,ou=applications,dc=example,dc=com', ldapBindDN => 'cn=manager,dc=example,dc=com", ldapBindPassword => 'secret' }); =back WARNING: You have to use the same storage type on all Lemonldap::NG parts in the same server. =item * B: returns a hash reference to the configuration. it takes a hash reference as first argument containing 2 optional parameters: =over =item * C $number>: the number of the configuration wanted. If this argument is omitted, the last configuration is returned. =item * C [array of names]: the desired fields asked. By default, getConf returns all (C'; } $form .= ''; return $form; } ## @method boolean checkNotification(Lemonldap::NG::Portal portal) # Check if notifications have been displayed and accepted. # @param $portal Lemonldap::NG::Portal object that call # @return true if all checkboxes have been checked sub checkNotification { my ( $self, $portal ) = ( @_, 0, 2 ); my ( $refs, $checks ); # First, rebuild environment (cookies,...) foreach ( $portal->param() ) { if (/^cookie/) { my @tmp = split /(?:=|;\s+)/, $portal->param($_); my %tmp = @tmp; my $value = $portal->{cipher}->decrypt( $tmp[1] ); unless ( defined($value) ) { $self->lmLog( "Unable to decrypt cookie", 'warn' ); return 0; } push @{ $portal->{cookie} }, $portal->cookie( -name => $tmp[0], -value => $value, -domain => $tmp{domain}, -path => "/", -secure => ( grep( /^secure$/, @tmp ) ? 1 : 0 ), @_, ); if ( $tmp[0] eq $portal->{cookieName} ) { my $tmp = $portal->{existingSession}; $portal->{existingSession} = sub { 0 }; $portal->controlExistingSession($value); $portal->{existingSession} = $tmp; } } elsif (s/^reference//) { $refs->{$_} = $portal->param("reference$_"); } elsif ( s/^check// and /^(\d+x\d+)x(\d+)$/ ) { push @{ $checks->{$1} }, $2; } } $portal->controlExistingSession() unless ( $portal->{sessionInfo} ); unless ( $portal->{sessionInfo} ) { $self->lmLog( "Invalid session", 'warn' ); return 0; } my $result = 1; foreach my $ref ( keys %$refs ) { my $uid = $portal->{notificationField} || $portal->{whatToTrace} || 'uid'; $uid =~ s/\$//g; $uid = $portal->{sessionInfo}->{$uid}; # Get notifications by references # 1. For the user my $user = $self->_get( $uid, $refs->{$ref} ); # 2. For all users my $all = $self->_get( $portal->{notificationWildcard}, $refs->{$ref} ); # 3. Join results my $files = {}; if ( $user and $all ) { $files = { %$user, %$all }; } else { $files = $user ? $user : $all; } unless ($files) { $self->lmLog( "Can't find notification $refs->{$ref} for $uid", 'error' ); next; } # Browse found files foreach my $file ( keys %$files ) { my $xml; eval { $xml = $parser->parse_string( $files->{$file} ) }; if ($@) { $self->lmLog( "Bad XML notification for $uid", 'error' ); next; } # Browse notifications in file foreach my $notif ( $xml->documentElement->getElementsByTagName('notification') ) { my $reference = $notif->getAttribute('reference'); my @tmp = $notif->getElementsByTagName('check'); my $checkCount = @tmp; if ( $checkCount == 0 or ( $checks->{$ref} and $checkCount == @{ $checks->{$ref} } ) ) { # Notification is accepted $self->lmLog( "$uid has accepted notification $refs->{$ref}", 'notice' ); # 1. Register acceptation in persistent session my $time = time(); my $notifkey = "notification_" . $refs->{$ref}; $portal->updatePersistentSession( { $notifkey => $time }, ); $self->lmLog( "Notification " . $refs->{$ref} . " registered in persistent session", 'debug' ); # 2. Delete it if not a wildcard notification if ( exists $user->{$file} ) { if ( $self->_delete($file) ) { $self->lmLog( "Notification " . $refs->{$ref} . " deleted", 'debug' ); } else { $self->lmLog( "Unable to delete notification $refs->{$ref} for $uid", 'error' ); } } } else { $self->lmLog( "$uid has not accepted notification $refs->{$ref}", 'notice' ); $result = 0; } } } } return $result; } ## @method int newNotification(string xml) # Check XML datas and insert new notifications. # @param $xml XML string containing notification # @return number of notifications done sub newNotification { my ( $self, $xml ) = @_; eval { $xml = $parser->parse_string($xml); }; if ($@) { $self->lmLog( "Unable to read XML file : $@", 'error' ); return 0; } my @notifs; my ( $version, $encoding ) = ( $xml->version(), $xml->encoding() ); foreach my $notif ( $xml->documentElement->getElementsByTagName('notification') ) { my @datas = (); # Mandatory information foreach (qw(date uid reference)) { my $tmp; unless ( $tmp = $notif->getAttribute($_) ) { $self->lmLog( "Attribute $_ is missing", 'error' ); return 0; } push @datas, $tmp; } # Other information foreach (qw(condition)) { my $tmp; if ( $tmp = $notif->getAttribute($_) ) { push @datas, $tmp; } else { push @datas, ""; } } my $result = XML::LibXML::Document->new( $version, $encoding ); my $root = XML::LibXML::Element->new('root'); $root->appendChild($notif); $result->setDocumentElement($root); push @notifs, [ @datas, $result ]; } my $tmp = $self->{type}; my $count; foreach (@notifs) { $count++; my ( $r, $err ) = $self->_newNotif(@$_); die "$err" unless ($r); } return $count; } ## @method int deleteNotification(string $uid, string $myref) ## Delete notifications for the connected user ## @param $uid of the user ## @param $myref notification's reference ## @return number of deleted notifications sub deleteNotification { my ( $self, $uid, $myref ) = @_; my @data; # Check input parameters unless ( $uid and $myref ) { $self->lmLog( "SOAP service deleteNotification called without all parameters", 'error' ); return 0; } $self->lmLog( "SOAP service deleteNotification called for uid $uid and reference $myref", 'debug' ); # Get notifications my $user = $self->_get($uid); # Return 0 if no files were found return 0 unless ($user); # Counting my $count = 0; foreach my $ref ( keys %$user ) { my $xml = $parser->parse_string( $user->{$ref} ); # Browse notification in file foreach my $notif ( $xml->documentElement->getElementsByTagName('notification') ) { # Get notification's data if ( $notif->getAttribute('reference') eq $myref ) { push @data, $ref; } # Delete the notification (really) foreach (@data) { if ( $self->purge( $_, 1 ) ) { $self->lmLog( "Notification $_ was removed.", 'debug' ); $count++; } } } } return $count; } ## @method hashref getAll() # Return all messages not notified. Wrapper for storage module getAll() # @return hashref where keys are internal reference and values are hashref with # keys date, uid and ref. sub getAll { no strict 'refs'; return &{ $_[0]->{type} . '::getAll' }(@_); } ## @method hashref getDone() # Returns a list of notification that have been done. Wrapper for storage module # getDone(). # @return hashref where keys are internal reference and values are hashref with # keys notified, uid and ref. sub getDone { no strict 'refs'; return &{ $_[0]->{type} . '::getDone' }(@_); } ## @method boolean purge(string myref, boolean force) # Purge notification (really delete record). Wrapper for storage module purge() # @param $myref identifier returned by get or getAll # @param $force force purge for not deleted session # @return true if something was deleted sub purge { no strict 'refs'; return &{ $_[0]->{type} . '::purge' }(@_); } ## @method private hashref _get(string uid,string ref) # Returns notifications corresponding to the user $uid. Wrapper for storage # module get(). # If $ref is set, returns only notification corresponding to this reference. # @param $uid UID # @param $ref Notification reference # @return hashref where keys are internal reference and values are XML strings sub _get { no strict 'refs'; my $self = $_[0]; # Debug lines. Must be removed ? die ref($self) unless ( ref($self) eq 'Lemonldap::NG::Common::Notification' ); return &{ $_[0]->{type} . '::get' }(@_); } ## @method private boolean _delete(string myref) # Mark a notification as done. Wrapper for storage module delete() # @param $myref identifier returned by get() or getAll() sub _delete { no strict 'refs'; return &{ $_[0]->{type} . '::delete' }(@_); } ## @method private boolean _prereq() # Check if storage module parameters are set. Wrapper for storage module # prereq() # @return true if all is OK sub _prereq { no strict 'refs'; return &{ $_[0]->{type} . '::prereq' }(@_); } ## @method private boolean _newNotif(string date, string uid, string ref, string xml) # Insert a new notification. Wrapper for storage module newNotif() # @param date Date # @param uid UID # @param ref Reference of the notification # @param xml XML notification # @return true if succeed sub _newNotif { no strict 'refs'; return &{ $_[0]->{type} . '::newNotif' }(@_); } ## @method private string _getIdentifier(string uid, string ref, string date) # Get notification identifier # @param $uid uid # @param $ref ref # @param $date date # @return the notification identifier sub _getIdentifier { no strict 'refs'; return &{ $_[0]->{type} . '::getIdentifier' }(@_); } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::Notification - Provides notification messages system. =head1 SYNOPSIS use Lemonldap::NG::Portal; =head1 DESCRIPTION Lemonldap::NG::Common::Notification. =head1 SEE ALSO L, =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =item Sandro Cazzaniga, Ecazzaniga.sandro@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2009-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012 by Sandro Cazzaniga, Ecazzaniga.sandro@gmail.comE =item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Notification/000077500000000000000000000000001325274564300346135ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/CommonDBI.pm000066400000000000000000000155711325274564300355600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notification## @file # DBI storage methods for notifications ## @class # DBI storage methods for notifications package Lemonldap::NG::Common::Notification::DBI; use strict; use Time::Local; use DBI; use Encode; use utf8; our $VERSION = '1.9.1'; ## @method boolean prereq() # Check if DBI parameters are set. # @return true if all is OK sub prereq { my $self = shift; $self->{dbiTable} = $self->{table} if ( $self->{table} ); unless ( $self->{dbiChain} ) { $Lemonldap::NG::Common::Notification::msg = '"dbiChain" is required in DBI notification type'; return 0; } $self->lmLog( 'Warning: "dbiUser" parameter is not set', 'warn' ) unless ( $self->{dbiUser} ); 1; } ## @method hashref get(string uid,string ref) # Returns notifications corresponding to the user $uid. # If $ref is set, returns only notification corresponding to this reference. # @param $uid UID # @param $ref Notification reference # @return hashref where keys are internal reference and values are XML strings sub get { my ( $self, $uid, $ref ) = @_; return () unless ($uid); _execute( $self, "SELECT * FROM $self->{dbiTable} WHERE done IS NULL AND uid=?" . ( $ref ? " AND ref=?" : '' ) . "ORDER BY date", $uid, ( $ref ? $ref : () ) ) or return (); my $result; while ( my $h = $self->{sth}->fetchrow_hashref() ) { # Get XML message my $xml = $h->{xml}; # Decode it to get the correct uncoded string Encode::from_to( $xml, "utf8", "iso-8859-1", Encode::FB_CROAK ); # Store message in result my $identifier = &getIdentifier( $self, $h->{uid}, $h->{ref}, $h->{date} ); $result->{$identifier} = $xml; } $self->lmLog( $self->{sth}->err(), 'warn' ) if ( $self->{sth}->err() ); return $result; } ## @method hashref getAll() # Return all messages not notified. # @return hashref where keys are internal reference and values are hashref with # keys date, uid and ref. sub getAll { my $self = shift; _execute( $self, "SELECT * FROM $self->{dbiTable} WHERE done IS NULL ORDER BY date" ); my $result; while ( my $h = $self->{sth}->fetchrow_hashref() ) { $result->{"$h->{date}#$h->{uid}#$h->{ref}"} = { date => $h->{date}, uid => $h->{uid}, ref => $h->{ref}, condition => $h->{condition} }; } $self->lmLog( $self->{sth}->err(), 'warn' ) if ( $self->{sth}->err() ); return $result; } ## @method boolean delete(string myref) # Mark a notification as done. # @param $myref identifier returned by get() or getAll() sub delete { my ( $self, $myref ) = @_; my ( $d, $u, $r ); unless ( ( $d, $u, $r ) = ( $myref =~ /^([^#]+)#(.+?)#(.+)$/ ) ) { $Lemonldap::NG::Common::Notification::msg = "Bad reference $myref"; $self->lmLog( "Bad reference $myref", 'warn' ); return 0; } my @ts = localtime(); $ts[5] += 1900; $ts[4]++; return _execute( $self, "UPDATE $self->{dbiTable} " . "SET done='$ts[5]-$ts[4]-$ts[3] $ts[2]:$ts[1]' " . "WHERE done IS NULL AND uid=? AND ref=? AND date=?", $u, $r, $d ); } ## @method boolean purge(string myref, boolean force) # Purge notification (really delete record) # @param $myref identifier returned by get or getAll # @param $force force purge for not deleted session # @return true if something was deleted sub purge { my ( $self, $myref, $force ) = @_; my ( $d, $u, $r ); unless ( ( $d, $u, $r ) = ( $myref =~ /^([^#]+)#(.+?)#(.+)$/ ) ) { $Lemonldap::NG::Common::Notification::msg = "Bad reference $myref"; $self->lmLog( "Bad reference $myref", 'warn' ); return 0; } my $clause; $clause = "done IS NOT NULL AND" unless ($force); return _execute( $self, "DELETE FROM $self->{dbiTable} " . "WHERE $clause uid=? AND ref=? AND date=?", $u, $r, $d ); } ## @method boolean newNotif(string date, string uid, string ref, string condition, string xml) # Insert a new notification # @param date Date # @param uid UID # @param ref Reference of the notification # @param condition Condition for the notification # @param xml XML notification # @return true if succeed sub newNotif { my ( $self, $date, $uid, $ref, $condition, $xml ) = @_; $xml = $xml->serialize(); utf8::encode($xml); my $res = $condition =~ /.+/ ? _execute( $self, "INSERT INTO $self->{dbiTable} (date,uid,ref,cond,xml) " . "VALUES(?,?,?,?,?)", $date, $uid, $ref, $condition, $xml ) : _execute( $self, "INSERT INTO $self->{dbiTable} (date,uid,ref,xml) " . "VALUES(?,?,?,?)", $date, $uid, $ref, $xml ); return $res; } ## @method hashref getDone() # Returns a list of notification that have been done # @return hashref where keys are internal reference and values are hashref with # keys notified, uid and ref. sub getDone { my ($self) = @_; _execute( $self, "SELECT * FROM $self->{dbiTable} WHERE done IS NOT NULL ORDER BY done" ); my $result; while ( my $h = $self->{sth}->fetchrow_hashref() ) { my @t = split( /\D+/, $h->{date} ); my $done = timelocal( $t[5], $t[4], $t[3], $t[2], $t[1], $t[0] ); $result->{"$h->{date}#$h->{uid}#$h->{ref}"} = { notified => $done, uid => $h->{uid}, ref => $h->{ref}, }; } $self->lmLog( $self->{sth}->err(), 'warn' ) if ( $self->{sth}->err() ); return $result; } ## @method private object _execute(string query, array args) # Execute a query and catch errors # @return number of lines touched or 1 if select succeed sub _execute { my ( $self, $query, @args ) = @_; my $dbh = _dbh($self) or return 0; unless ( $self->{sth} = $dbh->prepare($query) ) { $self->lmLog( $dbh->errstr(), 'warn' ); return 0; } my $tmp; unless ( $tmp = $self->{sth}->execute(@args) ) { $self->lmLog( $self->{sth}->errstr(), 'warn' ); return 0; } return $tmp; } ## @method object private _dbh() # Return the DBI object (build it if needed). # @return database handle object sub _dbh { my $self = shift; $self->{dbiTable} ||= "notifications"; return $self->{dbh} if ( $self->{dbh} and $self->{dbh}->ping ); my $r = DBI->connect_cached( $self->{dbiChain}, $self->{dbiUser}, $self->{dbiPassword}, { RaiseError => 0 } ); $self->lmLog( $DBI::errstr, 'error' ) unless ($r); return $r; } ## @method string getIdentifier(string uid, string ref, string date) # Get notification identifier # @param $uid uid # @param $ref ref # @param $date date # @return the notification identifier sub getIdentifier { my ( $self, $uid, $ref, $date ) = @_; return $date . "#" . $uid . "#" . $ref; } 1; File.pm000066400000000000000000000130271325274564300360330ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notification## @file # File storage methods for notifications ## @class # File storage methods for notifications package Lemonldap::NG::Common::Notification::File; use strict; use MIME::Base64; our $VERSION = '1.9.1'; ## @method boolean prereq() # Check if parameters are set and if storage directory exists. # @return true if all is OK sub prereq { my $self = shift; unless ( $self->{dirName} ) { $Lemonldap::NG::Common::Notification::msg = '"dirName" is required in "File" notification type !'; return 0; } if ( $self->{table} ) { $self->{dirName} =~ s/\/conf\/?$//; $self->{dirName} .= "/$self->{table}"; } unless ( -d $self->{dirName} ) { $Lemonldap::NG::Common::Notification::msg = "Directory \"$self->{dirName}\" does not exist !"; return 0; } # Configure file name separator (_ by default) $self->{fileNameSeparator} ||= "_"; 1; } ## @method hashref get(string uid,string ref) # Returns notifications corresponding to the user $uid. # If $ref is set, returns only notification corresponding to this reference. # @param $uid UID # @param $ref Notification reference # @return hashref where keys are filenames and values are XML strings sub get { my ( $self, $uid, $ref ) = @_; return () unless ($uid); my $fns = $self->{fileNameSeparator}; my $identifier = &getIdentifier( $self, $uid, $ref ); opendir D, $self->{dirName}; my @notif = grep /^\d{8}${fns}${identifier}\S*\.xml$/, readdir(D); closedir D; my $files; foreach my $file (@notif) { unless ( open F, $self->{dirName} . "/$file" ) { $self->lmLog( "Unable to read notification $self->{dirName}/$file", 'error' ); next; } $files->{$file} = join( '', ); } return $files; } ## @method hashref getAll() # Return all messages not notified. # @return hashref where keys are internal reference and values are hashref with # keys date, uid and ref. sub getAll { my $self = shift; opendir D, $self->{dirName}; my @notif; my $fns = $self->{fileNameSeparator}; @notif = grep /^\S*\.xml$/, readdir(D); my %h = map { /^(\d{8})${fns}([^\s${fns}]+)${fns}([^\s${fns}]+)(?:${fns}([^\s${fns}]+))?\.xml$/ ? ( $_ => { date => $1, uid => $2, ref => decode_base64($3), condition => decode_base64( $4 // '' ) } ) : () } @notif; return \%h; } ## @method boolean delete(string myref) # Mark a notification as done. # @param $myref identifier returned by get() or getAll() sub delete { my ( $self, $myref ) = @_; my $new = ( $myref =~ /(.*?)(?:\.xml)$/ )[0] . '.done'; return rename( $self->{dirName} . "/$myref", $self->{dirName} . "/$new" ); } ## @method boolean purge(string myref) # Purge notification (really delete record) # @param $myref identifier returned by get() or getAll() # @return true if something was deleted sub purge { my ( $self, $myref ) = @_; return unlink( $self->{dirName} . "/$myref" ); } ## @method boolean newNotif(string date, string uid, string ref, string xml) # Insert a new notification # @param date Date # @param uid UID # @param ref Reference of the notification # @param xml XML notification # @return true if succeed sub newNotif { my ( $self, $date, $uid, $ref, $condition, $xml ) = @_; my $fns = $self->{fileNameSeparator}; $date =~ s/-//g; return ( 0, "Bad date" ) unless ( $date =~ /^\d{8}/ ); my $filename = $self->{dirName} . "/${date}${fns}${uid}${fns}" . encode_base64( $ref, '' ); $filename .= "${fns}" . encode_base64( $condition, '' ) if $condition; $filename .= ".xml"; return ( 0, 'This notification still exists' ) if ( -e $filename ); my $old = ( $filename =~ /(.*?)(?:\.xml)$/ )[0] . '.done'; return ( 0, 'This notification has been done' ) if ( -e $old ); open my $F, ">$filename" or return ( 0, "Unable to create $filename ($!)" ); binmode($F); $xml->toFH($F); return ( 0, "Unable to close $filename ($!)" ) unless ( close $F ); return 1; } ## @method hashref getDone() # Returns a list of notification that have been done # @return hashref where keys are internal reference and values are hashref with # keys notified, uid and ref. sub getDone { my ($self) = @_; opendir D, $self->{dirName}; my @notif; my $fns = $self->{fileNameSeparator}; @notif = grep /^\d{8}${fns}\S*\.done$/, readdir(D); my $res; foreach my $file (@notif) { my ( $u, $r ) = ( $file =~ /^\d+${fns}([^${fns}]+)${fns}([^${fns}]+)${fns}?([^${fns}]+)\.done$/ ); die unless ( -f "$self->{dirName}/$file" ); my $time = ( stat("$self->{dirName}/$file") )[10]; $res->{$file} = { 'uid' => $u, 'ref' => decode_base64($r), 'notified' => $time, }; } return $res; } ## @method string getIdentifier(string uid, string ref, string date) # Get notification identifier # @param $uid uid # @param $ref ref # @param $date date # @return the notification identifier sub getIdentifier { my ( $self, $uid, $ref, $date ) = @_; my $result; # Special fix to manage purge from notification explorer return $date if $date; my $fns = $self->{fileNameSeparator}; if ($date) { $result .= $date . $fns; } $result .= $uid; if ($ref) { my $tmp = encode_base64( $ref, '' ); $result .= $fns . $tmp; } return $result; } 1; LDAP.pm000066400000000000000000000275341325274564300357040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/Notification## @file # LDAP storage methods for notifications ## @class # LDAP storage methods for notifications package Lemonldap::NG::Common::Notification::LDAP; use strict; use Time::Local; use MIME::Base64; use Net::LDAP; use utf8; our $VERSION = '1.9.1'; ## @method boolean prereq() # Check if LDAP parameters are set. # @return true if all is OK sub prereq { my $self = shift; unless ( $self->{ldapServer} ) { $self->lmLog( '"ldapServer" is required in LDAP notification type', 'error' ); $Lemonldap::NG::Common::Notification::msg = '"ldapServer" is required in LDAP notification type'; return 0; } if ( $self->{table} ) { $self->{ldapConfBase} =~ s/^\w+=\w+(,.*)$/ou=$self->{table}$1/; } $self->lmLog( 'Warning: "ldapBindDN" parameter is not set', 'warn' ) unless ( $self->{ldapBindDN} ); 1; } ## @method hashref get(string uid,string ref) # Returns notifications corresponding to the user $uid. # If $ref is set, returns only notification corresponding to this reference. # @param $uid UID # @param $ref Notification reference # @return hashref where keys are internal reference and values are XML strings sub get { my ( $self, $uid, $ref ) = @_; return () unless ($uid); my $filter = '(&(objectClass=applicationProcess)(!(description={done}*))'; $filter .= '(description={uid}' . $uid . ')'; $filter .= '(description={ref}' . $ref . ')' if $ref; $filter .= ')'; my @entries = _search( $self, "$filter" ); my $result = {}; foreach my $entry (@entries) { my @notifValues = $entry->get_value('description'); my $f = {}; foreach (@notifValues) { my ( $k, $v ) = ( $_ =~ /\{(.*?)\}(.*)/smg ); $v = decodeLdapValue($v); $f->{$k} = $v; } my $xml = $f->{xml}; utf8::encode($xml); my $identifier = &getIdentifier( $self, $f->{uid}, $f->{ref}, $f->{date} ); $result->{$identifier} = "$xml"; $self->lmLog( "notification $identifier found", 'info' ); } return $result; } ## @method hashref getAll() # Return all messages not notified. # @return hashref where keys are internal reference and values are hashref with # keys date, uid and ref. sub getAll { my $self = shift; my @entries = _search( $self, '(&(objectClass=applicationProcess)(!(description={done}*)))' ); my $result = {}; foreach my $entry (@entries) { my @notifValues = $entry->get_value('description'); my $f = {}; foreach (@notifValues) { my ( $k, $v ) = ( $_ =~ /\{(.*?)\}(.*)/smg ); $v = decodeLdapValue($v); $f->{$k} = $v; } $result->{"$f->{date}#$f->{uid}#$f->{ref}"} = { date => $f->{date}, uid => $f->{uid}, ref => $f->{ref}, cond => $f->{condition}, }; } return $result; } ## @method boolean delete(string myref) # Mark a notification as done. # @param $myref identifier returned by get() or getAll() sub delete { my ( $self, $myref ) = @_; my ( $d, $u, $r ); unless ( ( $d, $u, $r ) = ( $myref =~ /^([^#]+)#(.+?)#(.+)$/ ) ) { $Lemonldap::NG::Common::Notification::msg = "Bad reference $myref"; $self->lmLog( "Bad reference $myref", 'warn' ); return 0; } my @ts = localtime(); $ts[5] += 1900; $ts[4]++; return _modify( $self, '(&(objectClass=applicationProcess)(description={uid}' . $u . ')(description={ref}' . $r . ')(description={date}' . $d . ')(!(description={done}*)))', "description", "{done}$ts[5]-$ts[4]-$ts[3] $ts[2]:$ts[1]" ); } ## @method boolean purge(string myref, boolean force) # Purge notification (really delete record) # @param $myref identifier returned by get or getAll # @param $force force purge for not deleted session # @return true if something was deleted sub purge { my ( $self, $myref, $force ) = @_; my ( $d, $u, $r ); unless ( ( $d, $u, $r ) = ( $myref =~ /^([^#]+)#(.+?)#(.+)$/ ) ) { $Lemonldap::NG::Common::Notification::msg = "Bad reference $myref"; $self->lmLog( "Bad reference $myref", 'warn' ); return 0; } my $clause; $clause = '(description={done}*)' unless ($force); return _delete( $self, '(&(objectClass=applicationProcess)(description={uid}' . $u . ')(description={ref}' . $r . ')(description={date}' . $d . ')' . $clause . ')' ); } ## @method boolean newNotif(string date, string uid, string ref, string condition, string xml) # Insert a new notification # @param date Date # @param uid UID # @param ref Reference of the notification # @param condition Condition for the notification # @param xml XML notification # @return true if succeed sub newNotif { my ( $self, $date, $uid, $ref, $condition, $xml ) = @_; my $fns = $self->{fileNameSeparator}; $fns ||= '_'; $date =~ s/-//g; return ( 0, "Bad date" ) unless ( $date =~ /^\d{8}/ ); my $cn = "${date}${fns}${uid}${fns}" . encode_base64( $ref, '' ); $cn .= "${fns}" . encode_base64( $condition, '' ) if $condition; $xml = $xml->serialize(); my $fields = $condition =~ /.+/ ? { "date" => "$date", "uid" => "$uid", "ref" => "$ref", "xml" => "$xml", "cond" => "$condition", } : { "date" => "$date", "uid" => "$uid", "ref" => "$ref", "xml" => "$xml", }; return _store( $self, $cn, $fields ); } ## @method hashref getDone() # Returns a list of notifications that have been done # @return hashref where keys are internal reference and values are hashref with # keys notified, uid and ref. sub getDone { my ($self) = @_; my @entries = _search( $self, '(&(objectClass=applicationProcess)(description={done}*))' ); my $result = {}; foreach my $entry (@entries) { my @notifValues = $entry->get_value('description'); my $f = {}; foreach (@notifValues) { my ( $k, $v ) = ( $_ =~ /\{(.*?)\}(.*)/smg ); $v = decodeLdapValue($v); $f->{$k} = $v; } my @t = split( /\D+/, $f->{done} ); my $done = timelocal( $t[5], $t[4], $t[3], $t[2], $t[1], $t[0] ); $result->{"$f->{date}#$f->{uid}#$f->{ref}"} = { notified => $done, uid => $f->{uid}, ref => $f->{ref}, }; } # $ldap->unbind() && delete $self->{ldap}; return $result; } ## @method object private _ldap() # Return the ldap object (build it if needed). # @param filter The LDAP filter to apply # @return list of entries returned by the LDAP search (set of Net::LDAP::Entry) sub _search { my ( $self, $filter ) = @_; my $ldap = _ldap($self) or return 0; my $search = $ldap->search( base => $self->{ldapConfBase}, filter => "$filter", scope => 'sub', attrs => ['description'], ); if ( $search->code ) { $self->lmLog( "search error: " . $search->error(), 'error' ); return (); } return $search->entries(); } ## @method object private _delete() # Deletes the all entries found by the given LDAP filter # @param filter The LDAP filter to apply # @return 1 if operation success, else 0 sub _delete { my ( $self, $filter ) = @_; my @entries = _search( $self, "$filter" ); my $mesg = {}; foreach my $entry (@entries) { $mesg = $self->{ldap}->delete( $entry->dn() ); $mesg->code && return 0; } # $ldap->unbind() && delete $self->{ldap}; return 1; } ## @method object private _modify() # add the given attribute value to all entries found by LDAP filter # @param filter The LDAP filter to apply # @param attr : name of the attribute to modify # @param value : new value to add # @return 1 if operation success, else 0 sub _modify { my ( $self, $filter, $attr, $value ) = @_; my @entries = _search( $self, "$filter" ); my $mesg = {}; foreach my $entry (@entries) { $mesg = $self->{ldap} ->modify( $entry->dn(), add => { "$attr" => "$value", }, ); $mesg->code && return 0; } # $ldap->unbind() && delete $self->{ldap}; return 1; } ## @method object private _store() # creates the notification defined by dn: cn=$cn,$ldapConfBase and $fields # stored in the description attribute # @param cn : cn value, used as a dn component # @param fields : set of values to store in description attribute # @return 1 if operation success, else 0 sub _store { my ( $self, $cn, $fields ) = @_; my $ldap = _ldap($self) or return 0; my $notifName = "$cn"; my $notifDN = "cn=$notifName," . $self->{ldapConfBase}; # Store values as {key}value my @notifValues; foreach my $k ( keys %$fields ) { my $v = encodeLdapValue( $fields->{$k} ); push @notifValues, "{$k}$v"; } my $add = $ldap->add( $notifDN, attrs => [ objectClass => [ 'top', 'applicationProcess' ], cn => $notifName, description => \@notifValues, ] ); if ( $add->code ) { $self->logError($add); return 0; } #$ldap->unbind() && delete $self->{ldap}; return 1; } ## @method object private encodeLdapValue() # encode ldap value in utf8 (try to encode to latin1, and if it fails, encode to utf8) # @param value value to encode # @return value encoded in utf8 sub encodeLdapValue { my $value = shift; eval { my $safevalue = $value; Encode::from_to( $safevalue, "utf8", "iso-8859-1", Encode::FB_CROAK ); }; if ($@) { Encode::from_to( $value, "iso-8859-1", "utf8", Encode::FB_CROAK ); } return $value; } ## @method object private decodeLdapValue() # decode ldap value from utf8 to latin1 # @param value value to decode # @return value decoded in latin1 sub decodeLdapValue { my $value = shift; Encode::from_to( $value, "utf8", "iso-8859-1", Encode::FB_CROAK ); return $value; } ## @method object private _ldap() # Return the ldap object (build it if needed). # @return ldap handle object sub _ldap { my $self = shift; return $self->{ldap} if ( $self->{ldap} ); # Parse servers configuration my $useTls = 0; my $tlsParam; my @servers = (); foreach my $server ( split /[\s,]+/, $self->{ldapServer} ) { if ( $server =~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) { $useTls = 1; $server = $1; $tlsParam = $2 || ""; } else { $useTls = 0; } push @servers, $server; } # Connect my $ldap = Net::LDAP->new( \@servers, onerror => undef, ( $self->{ldapPort} ? ( port => $self->{ldapPort} ) : () ), ); unless ($ldap) { $self->lmLog( 'connexion failed: ' . $@, 'error' ); return; } # Start TLS if needed if ($useTls) { my %h = split( /[&=]/, $tlsParam ); $h{cafile} = $self->{caFile} if ( $self->{caFile} ); $h{capath} = $self->{caPath} if ( $self->{caPath} ); my $start_tls = $ldap->start_tls(%h); if ( $start_tls->code ) { $self->lmLog( 'tls failed: ' . $start_tls->error, 'error' ); return; } } # Bind with credentials my $bind = $ldap->bind( $self->{ldapBindDN}, password => $self->{ldapBindPassword} ); if ( $bind->code ) { $self->lmLog( 'bind failed: ' . $bind->error, 'error' ); return; } $self->{ldap} = $ldap; return $ldap; } ## @method string getIdentifier(string uid, string ref, string date) # Get notification identifier # @param $uid uid # @param $ref ref # @param $date date # @return the notification identifier sub getIdentifier { my ( $self, $uid, $ref, $date ) = @_; return $date . "#" . $uid . "#" . $ref; } 1; PSGI.pm000066400000000000000000000303511325274564300332670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Commonpackage Lemonldap::NG::Common::PSGI; use 5.10.0; use Mouse; use JSON; use Lemonldap::NG::Common; use Lemonldap::NG::Common::PSGI::Constants; use Lemonldap::NG::Common::PSGI::Request; our $VERSION = '1.9.3'; our $_json = JSON->new->allow_nonref; has error => ( is => 'rw', default => '' ); has languages => ( is => 'rw', isa => 'Str', default => 'en' ); has logLevel => ( is => 'rw', isa => 'Str', default => 'info' ); has portal => ( is => 'rw', isa => 'Str' ); has staticPrefix => ( is => 'rw', isa => 'Str' ); has templateDir => ( is => 'rw', isa => 'Str' ); has links => ( is => 'rw', isa => 'ArrayRef' ); has menuLinks => ( is => 'rw', isa => 'ArrayRef' ); has syslog => ( is => 'rw', isa => 'Str', trigger => sub { if ( $_[0]->{syslog} ) { eval { require Sys::Syslog; Sys::Syslog->import(':standard'); openlog( 'lemonldap-ng', 'ndelay,pid', $_[0]->{syslog} ); }; $_[0] ->error("Unable to use syslog with facility $_[0]->{syslog}: $@") if ($@); } }, ); ## @method void lmLog(string mess, string level) # Log subroutine. Print on STDERR messages if it exceeds `logLevel` value # @param $mess Text to log # @param $level Level (debug|info|notice|warn|error) sub lmLog { my ( $self, $msg, $level ) = @_; my $levels = { error => 4, warn => 3, notice => 2, info => 1, debug => 0 }; my $l = $levels->{$level} || 1; return if ( ref($self) and $l < $levels->{ $self->{logLevel} } ); print STDERR "[$level] " . ( $l ? '' : (caller)[0] . ': ' ) . " $msg\n"; } ##@method void userLog(string mess, string level) # Log user actions on Apache logs or syslog. # @param $mess string to log # @param $level level of log message sub userLog { my ( $self, $mess, $level ) = @_; if ( $self->{syslog} ) { $level =~ s/^warn$/warning/; syslog( $level || 'notice', $mess ); } else { $self->lmLog( $mess, $level ); } } ##@method void userInfo(string mess) # Log non important user actions. Alias for userLog() with facility "info". # @param $mess string to log sub userInfo { my ( $self, $mess ) = @_; $self->userLog( $mess, 'info' ); } ##@method void userNotice(string mess) # Log user actions like access and logout. Alias for userLog() with facility # "notice". # @param $mess string to log sub userNotice { my ( $self, $mess ) = @_; $self->userLog( $mess, 'notice' ); } ##@method void userError(string mess) # Log user errors like "bad password". Alias for userLog() with facility # "warn". # @param $mess string to log sub userError { my ( $self, $mess ) = @_; $self->userLog( $mess, 'warn' ); } # Responses methods sub sendJSONresponse { my ( $self, $req, $j, %args ) = @_; $args{code} ||= 200; $args{headers} ||= []; my $type = 'application/json; charset=utf-8'; if ( ref $j ) { eval { $j = $_json->encode($j); }; return $self->sendError( $req, $@ ) if ($@); } return [ $args{code}, [ 'Content-Type' => $type, @{ $args{headers} } ], [$j] ]; } sub sendError { my ( $self, $req, $err, $code ) = @_; $err ||= $req->error; $code ||= 500; $self->lmLog( "Error $code: $err", $code > 499 ? 'error' : 'notice' ); return ( $req->accept =~ /json/ ? $self->sendJSONresponse( $req, { error => $err }, code => $code ) : [ $code, [ 'Content-Type' => 'text/plain' ], ["Error: $err"] ] ); } sub abort { my ( $self, $err ) = @_; $self->lmLog( $err, 'error' ); return sub { $self->sendError( Lemonldap::NG::Common::PSGI::Request->new( $_[0] ), $err, 500 ); }; } sub _mustBeDefined { my $name = ( caller(1) )[3]; $name =~ s/^.*:://; my $call = ( caller(1) )[0]; my $ref = ref( $_[0] ) || $call; die "$name() method must be implemented (probably in $ref)"; } sub init { my ( $self, $args ) = @_; unless ( ref $args ) { $self->error('init argument must be a hashref'); return 0; } foreach my $k ( keys %$args ) { $self->{$k} = $args->{$k}; } return 1; } sub handler { _mustBeDefined(@_) } sub sendHtml { my ( $self, $req, $template ) = @_; my $sp = $self->staticPrefix; $sp =~ s/\/*$/\//; my $sc = $req->scriptname; $sc = '.' unless ($sc); $sc =~ s#/*$#/#; if ( defined $req->params('js') ) { my $s = sprintf 'var staticPrefix="%s";' . 'var scriptname="%s";' . 'var availableLanguages="%s".split(/[,;] */);' . 'var portal="%s";', $sp, $sc, $self->languages, $self->portal; $s .= $self->javascript($req) if ( $self->can('javascript') ); return [ 200, [ 'Content-Type' => 'application/javascript', 'Content-Length' => length($s) ], [$s] ]; } my $htpl; $template = $self->templateDir . "/$template.tpl"; return $self->sendError( $req, "Unable to read $template", 500 ) unless ( -r $template and -f $template ); eval { $self->lmLog( "Starting HTML generation using $template", 'debug' ); require HTML::Template; $htpl = HTML::Template->new( filehandle => IO::File->new($template), path => $self->templateDir, die_on_bad_params => 1, die_on_missing_include => 1, cache => 0, ); # TODO: replace app # TODO: warn if STATICPREFIX does not end with '/' $htpl->param( STATIC_PREFIX => $sp, SCRIPTNAME => $sc, ( $self->can('tplParams') ? ( $self->tplParams ) : () ), ); }; if ($@) { return $self->sendError( $req, "Unable to load template: $@", 500 ); } $self->lmLog( 'For more performance, store the result of this as static file', 'debug' ); # Set headers my $hdrs = [ 'Content-Type' => 'text/html' ]; unless ( $self->logLevel eq 'debug' ) { push @$hdrs, ETag => "LMNG-manager-$VERSION", 'Cache-Control' => 'private, max-age=2592000'; } $self->lmLog( "Sending $template", 'debug' ); return [ 200, $hdrs, [ $htpl->output() ] ]; } ############### # Main method # ############### sub run { my ( $self, $args ) = @_; unless ( ref $self ) { $self = $self->new($args); return $self->abort( $self->error ) unless ( $self->init($args) ); } return $self->_run; } sub _run { my $self = shift; return sub { $self->handler( Lemonldap::NG::Common::PSGI::Request->new( $_[0] ) ); }; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::PSGI - Base library for PSGI modules of Lemonldap::NG. Use Lemonldap::NG::Common::PSGI::Router for REST API. =head1 SYNOPSIS package My::PSGI; use base Lemonldap::NG::Common::PSGI; sub init { my ($self,$args) = @_; # Will be called 1 time during startup # Store debug level $self->logLevel('info'); # Can use syslog for user actions $self->syslog('daemon'); # Return a boolean. If false, then error message has to be stored in # $self->error return 1; } sub handler { my ( $self, $req ) = @_; # Do something and return a PSGI response # NB: $req is a Lemonldap::NG::Common::PSGI::Request object return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Body lines' ] ]; } This package could then be called as a CGI, using FastCGI,... #!/usr/bin/env perl use My::PSGI; use Plack::Handler::FCGI; # or Plack::Handler::CGI Plack::Handler::FCGI->new->run( My::PSGI->run() ); =head1 DESCRIPTION This package provides base class for Lemonldap::NG web interfaces but could be used regardless. =head1 METHODS =head2 Running methods =head3 run ( $args ) Main method that will manage requests. It can be called using class or already created object: =over =item Class->run($args): launch new($args), init(), then manage requests (using private _run() method =item $object->run(): manage directly requests. Initialization must have be done earlier. =back =head2 Logging =head3 lmLog ( $msg, $level) Print on STDERR messages if level > $self->{logLevel}. Defined log levels are: debug, info, notice, warn, error. =head3 userLog ($msg, $level) If $self->syslog is configured, store message with it, else called simply lmLog(). $self->syslog must be empty or contain syslog facility =head3 userError() userNotice() userInfo() Alias for userLog(level). =head2 Content sending Note that $req, the first argument of these functions, is a L. See the corresponding documentation. =head3 sendHtml ( $req, $template ) This method build HTML response using HTML::Template and the template $template. $template file must be in $self->templateDir directory. HTML template will receive 5 variables: =over =item SCRIPT_NAME: the path to the (F)CGI =item STATIC_PREFIX: content of $self->staticPrefix =item AVAILABLE_LANGUAGES: content of $self->languages =item LINKS: JSON stringification of $self->links =item VERSION: Lemonldap::NG version =back The response is always send with a 200 code. =head3 sendJSONresponse ( $req, $json, %args ) Stringify $json object and send it to the client. $req is the Lemonldap::NG::Common::PSGI::Request object; %args can define the HTTP error code (200 by default) or headers to add. If client is not json compatible (`Accept` header), response is send in XML. Examples: $self->sendJSONresponse ( $req, { result => 0 }, code => 400 ); $self->sendJSONresponse ( $req, { result => 1 } ); $self->sendJSONresponse ( $req, { result => 1 }, headers => [ X => Z ] ); =head3 sendError ( $req, $msg, $code ) Call sendJSONresponse with `{ error => $msg }` and code (default to 500) and call lmLog() to duplicate error in logs =head3 abort ( $msg ) When an error is detected during startup (init() sub), you must not call sendError() but call abort(). Each request received later will receive the error (abort uses sendError() behind the scene). =head2 Accessors =head3 error String error. Used if init() fails or if sendError is called without argument. =head3 languages String containing list of languages (ie "fr, en'). Used by sendHtml(). =head3 logLevel See lmLog(). =head3 staticPrefix String indicating the path of static content (js, css,...). Used by sendHtml(). =head3 templateDir Directory containing template files. =head3 links Array of links to display by sendHtml(). Each element has the form: { target => 'http://target', title => 'string to display' } =head3 syslog Syslog facility. If empty, STDERR will be used for logging =head1 SEE ALSO L, L, L, L, L, L, L, L, =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut PSGI/000077500000000000000000000000001325274564300327275ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/CommonCli/000077500000000000000000000000001325274564300334365ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGILib.pm000066400000000000000000000142061325274564300345050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Clipackage Lemonldap::NG::Common::PSGI::Cli::Lib; use JSON; use Mouse; use Lemonldap::NG::Common::PSGI; our $VERSION = '1.9.13'; has iniFile => ( is => 'ro', isa => 'Str' ); has app => ( is => 'ro', isa => 'CodeRef' ); sub _get { my ( $self, $path, $query ) = @_; $query //= ''; return $self->app->( { 'HTTP_ACCEPT' => 'application/json, text/plain, */*', 'SCRIPT_NAME' => '', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 'SERVER_NAME' => '127.0.0.1', 'QUERY_STRING' => $query, 'HTTP_CACHE_CONTROL' => 'max-age=0', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', 'PATH_INFO' => $path, 'REQUEST_METHOD' => 'GET', 'REQUEST_URI' => $path . ( $query ? "?$query" : '' ), 'SERVER_PORT' => '8002', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => '127.0.0.1:8002' } ); } sub _post { my ( $self, $path, $query, $body, $type, $len ) = @_; die "$body must be a IO::Handle" unless ( ref($body) and $body->can('read') ); return $self->app->( { 'HTTP_ACCEPT' => 'application/json, text/plain, */*', 'SCRIPT_NAME' => '', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 'SERVER_NAME' => '127.0.0.1', 'QUERY_STRING' => $query, 'HTTP_CACHE_CONTROL' => 'max-age=0', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', 'PATH_INFO' => $path, 'REQUEST_METHOD' => 'POST', 'REQUEST_URI' => $path . ( $query ? "?$query" : '' ), 'SERVER_PORT' => '8002', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => '127.0.0.1:8002', 'psgix.input.buffered' => 1, 'psgi.input' => $body, 'CONTENT_LENGTH' => $len // scalar( ( stat $body )[7] ), 'CONTENT_TYPE' => $type, } ); } sub _put { my ( $self, $path, $query, $body, $type, $len ) = @_; die "$body must be a IO::Handle" unless ( ref($body) and $body->can('read') ); return $self->app->( { 'HTTP_ACCEPT' => 'application/json, text/plain, */*', 'SCRIPT_NAME' => '', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 'SERVER_NAME' => '127.0.0.1', 'QUERY_STRING' => $query, 'HTTP_CACHE_CONTROL' => 'max-age=0', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', 'PATH_INFO' => $path, 'REQUEST_METHOD' => 'PUT', 'REQUEST_URI' => $path . ( $query ? "?$query" : '' ), 'SERVER_PORT' => '8002', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => '127.0.0.1:8002', 'psgix.input.buffered' => 1, 'psgi.input' => $body, 'CONTENT_LENGTH' => $len // scalar( ( stat $body )[7] ), 'CONTENT_TYPE' => $type, } ); } sub _del { my ( $self, $path, $query ) = @_; return $self->app->( { 'HTTP_ACCEPT' => 'application/json, text/plain, */*', 'SCRIPT_NAME' => '', 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 'SERVER_NAME' => '127.0.0.1', 'QUERY_STRING' => $query, 'HTTP_CACHE_CONTROL' => 'max-age=0', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', 'PATH_INFO' => $path, 'REQUEST_METHOD' => 'DELETE', 'REQUEST_URI' => $path . ( $query ? "?$query" : '' ), 'SERVER_PORT' => '8002', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => '127.0.0.1:8002', } ); } sub jsonResponse { my ( $self, $path, $query ) = @_; my $res = $self->_get( $path, $query ) or die "Manager lib has refused my get, aborting"; unless ( $res->[0] == 200 ) { require Data::Dumper; print STDERR "Result dump :\n" . Data::Dumper::Dumper($res); die "Manager lib does not return a 200 code, aborting"; } my $href = from_json( $res->[2]->[0], { allow_nonref => 1 } ) or die 'Response is not JSON'; return $href; } sub jsonPostResponse { my ( $self, $path, $query, $body, $type, $len ) = @_; my $res = $self->_post( $path, $query, $body, $type, $len ) or die "Manager lib has refused my post, aborting"; unless ( $res->[0] == 200 ) { require Data::Dumper; print STDERR "Result dump :\n" . Data::Dumper::Dumper($res); die "Manager lib does not return a 200 code, aborting"; } my $href = from_json( $res->[2]->[0], { allow_nonref => 1 } ) or die 'Response is not JSON'; return $href; } sub jsonPutResponse { my ( $self, $path, $query, $body, $type, $len ) = @_; my $res = $self->_put( $path, $query, $body, $type, $len ) or die "Manager lib has refused my put, aborting"; unless ( $res->[0] == 200 ) { require Data::Dumper; print STDERR "Result dump :\n" . Data::Dumper::Dumper($res); die "Manager lib does not return a 200 code, aborting"; } my $href = from_json( $res->[2]->[0], { allow_nonref => 1 } ) or die 'Response is not JSON'; return $href; } 1; Constants.pm000066400000000000000000000007021325274564300352400ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGIpackage Lemonldap::NG::Common::PSGI::Constants; use strict; use Exporter 'import'; use base qw(Exporter); our $VERSION = '1.9.1'; # CONSTANTS use constant { DEBUG => 4, INFO => 3, WARN => 2, NOTICE => 1, ERROR => 0, }; our $no = qr/^(?:off|no|0)?$/i; our %EXPORT_TAGS = ( 'all' => [qw(DEBUG INFO WARN ERROR $no)] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = ( @{ $EXPORT_TAGS{'all'} } ); 1; Request.pm000066400000000000000000000204411325274564300347160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGIpackage Lemonldap::NG::Common::PSGI::Request; use strict; use Mouse; use JSON; use URI::Escape; our $VERSION = '1.9.13'; # http :// server / path ? query # fragment # m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|; has HTTP_ACCEPT => ( is => 'ro', reader => 'accept' ); has HTTP_ACCEPT_ENCODING => ( is => 'ro', reader => 'encodings' ); has HTTP_ACCEPT_LANGUAGE => ( is => 'ro', reader => 'languages' ); has HTTP_AUTHORIZATION => ( is => 'ro', reader => 'authorization' ); has HTTP_COOKIE => ( is => 'ro', reader => 'cookies' ); has HTTP_HOST => ( is => 'ro', reader => 'hostname' ); has REMOTE_ADDR => ( is => 'ro', isa => 'Str', reader => 'remote_ip' ); has REMOTE_PORT => ( is => 'ro', isa => 'Int', reader => 'port' ); has REQUEST_METHOD => ( is => 'ro', isa => 'Str', reader => 'method' ); has SCRIPT_NAME => ( is => 'ro', isa => 'Str', reader => 'scriptname' ); has SERVER_PORT => ( is => 'ro', isa => 'Int', reader => 'get_server_port' ); has X_ORIGINAL_URI => ( is => 'ro', isa => 'Str' ); has PATH_INFO => ( is => 'ro', reader => 'path', lazy => 1, default => '', trigger => sub { my $tmp = $_[0]->{SCRIPT_NAME}; $_[0]->{PATH_INFO} =~ s|//+|/|g; $_[0]->{PATH_INFO} =~ s|^$tmp|/|; }, ); has REQUEST_URI => ( is => 'ro', reader => 'uri', lazy => 1, default => '/', trigger => sub { my $uri = $_[0]->{X_ORIGINAL_URI} || $_[0]->{REQUEST_URI}; $_[0]->{unparsed_uri} = $uri; $_[0]->{REQUEST_URI} = uri_unescape($uri); $_[0]->{REQUEST_URI} =~ s|//+|/|g; }, ); has unparsed_uri => ( is => 'rw', isa => 'Str' ); has 'psgi.errors' => ( is => 'rw', reader => 'stderr' ); # Authentication has REMOTE_USER => ( is => 'ro', reader => 'user', trigger => sub { $_[0]->{userData} = { $Lemonldap::NG::Handler::Main::tsv->{whatTotrace} || _whatToTrace => $_[0]->{REMOTE_USER}, }; }, ); has userData => ( is => 'rw', isa => 'HashRef', default => sub { {} } ); # Query parameters has _params => ( is => 'rw', isa => 'HashRef', default => sub { {} } ); has QUERY_STRING => ( is => 'ro', reader => 'query', trigger => sub { my $self = shift; $self->{QUERY_STRING} = uri_unescape( $self->{QUERY_STRING} ); my @tmp = $self->{QUERY_STRING} ? split /&/, $self->{QUERY_STRING} : (); foreach my $s (@tmp) { if ( $s =~ /^(.+?)=(.+)$/ ) { $self->{_params}->{$1} = $2; } else { $self->{_params}->{$s} = 1; } } }, ); sub params { my ( $self, $key, $value ) = @_; return $self->_params unless ($key); $self->_params->{$key} = $value if ( defined $value ); return $self->_params->{$key}; } # POST management # # When CONTENT_LENGTH is set, store body in memory in `body` key has 'psgix.input.buffered' => ( is => 'ro', reader => '_psgixBuffered', ); has 'psgi.input' => ( is => 'ro', reader => '_psgiInput', ); has body => ( is => 'rw', isa => 'Str', default => '' ); has CONTENT_TYPE => ( is => 'ro', isa => 'Str', reader => 'contentType', ); has CONTENT_LENGTH => ( is => 'ro', reader => 'contentLength', lazy => 1, default => 0, trigger => sub { my $self = shift; if ( $self->method eq 'GET' ) { $self->{body} = undef; } elsif ( $self->method =~ /^(?:POST|PUT)$/ ) { $self->{body} = ''; if ( $self->_psgixBuffered ) { my $length = $self->{CONTENT_LENGTH}; while ( $length > 0 ) { my $buffer; $self->_psgiInput->read( $buffer, ( $length < 8192 ) ? $length : 8192 ); $length -= length($buffer); $self->{body} .= $buffer; } } else { $self->_psgiInput->read( $self->{body}, $self->{CONTENT_LENGTH}, 0 ); } utf8::upgrade( $self->{body} ); } } ); has error => ( is => 'rw', isa => 'Str', default => '' ); has respHeaders => ( is => 'rw', isa => 'HashRef', default => sub { {} } ); # JSON parser sub jsonBodyToObj { my $self = shift; unless ( $self->contentType =~ /application\/json/ ) { $self->error('Data is not JSON'); return undef; } unless ( $self->body ) { $self->error('No data'); return undef; } return $self->body if ( ref( $self->body ) ); my $j = eval { from_json( $self->body, { allow_nonref => 1 } ) }; if ($@) { $self->error("$@$!"); return undef; } return $self->{body} = $j; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::PSGI::Request - HTTP request object for Lemonldap::NG PSGIs =head1 SYNOPSIS package My::PSGI; use base Lemonldap::NG::Common::PSGI; # See Lemonldap::NG::Common::PSGI ... sub handler { my ( $self, $req ) = @_; # Do something and return a PSGI response # NB: $req is a Lemonldap::NG::Common::PSGI::Request object if ( $req->accept eq 'text/plain' ) { ... } return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Body lines' ] ]; } =head1 DESCRIPTION This package provides HTTP request objects used by Lemonldap::NG PSGIs. It contains common accessors to work with request =head1 METHODS =head2 Accessors =head3 accept 'Accept' header content. =head3 encodings 'Accept-Encoding' header content. =head3 languages 'Accept-Language header content. =head3 cookies 'Cookie' header content. =head3 hostname 'Host' header content. =head3 remote_ip Client IP address. =head3 port Client TCP port. =head3 method HTTP method asked by client (GET/POST/PUT/DELETE). =head3 scriptname SCRIPT_NAME environment variable provided by HTTP server. =head3 get_server_port Server port. =head3 path PATH_INFO content which has been subtracted `scriptname`. So it's the relative path_info for REST calls. =head3 uri REQUEST_URI environment variable. =head3 unparsed_uri Same as `uri` but without decoding. =head3 user REMOTE_USER environment variable. It contains username when a server authentication is done. =head3 userData Hash reference to be used by Lemonldap::NG::Handler::PSGI. If a server authentication is done, it contains: { _whatToTrace => `user()` } =head3 params GET parameters. =head3 body Content of POST requests =head3 error Set if an error occurs =head3 contentType Content type of posted datas. =head3 contentLength Length of posted datas. =head2 Private accessors =head3 _psgixBuffered PSGI psgix.input.buffered variable. =head3 _psgiInput PSGI psgix.input variable. =head2 Methods =head3 jsonBodyToObj() Get the content of a JSON POST request as Perl object. =head1 SEE ALSO L, L, L, L, L, L, L, L, =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Router.pm000066400000000000000000000230601325274564300345460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGIpackage Lemonldap::NG::Common::PSGI::Router; use Mouse; use Lemonldap::NG::Common::PSGI; use Lemonldap::NG::Common::PSGI::Constants; our $VERSION = '1.9.1'; extends 'Lemonldap::NG::Common::PSGI'; # Properties has 'routes' => ( is => 'rw', isa => 'HashRef', default => sub { { GET => {}, POST => {}, PUT => {}, DELETE => {} } } ); has 'defaultRoute' => ( is => 'rw', default => 'index.html' ); # Routes initialization sub addRoute { my ( $self, $word, $dest, $methods ) = (@_); $methods ||= [qw(GET POST PUT DELETE)]; foreach my $method (@$methods) { $self->genRoute( $self->routes->{$method}, $word, $dest ); } return $self; } sub genRoute { my ( $self, $routes, $word, $dest ) = @_; if ( ref $word eq 'ARRAY' ) { foreach my $w (@$word) { $self->genRoute( $routes, $w, $dest ); } } else { if ( $word =~ /^:(.*)$/ ) { $routes->{'#'} = $1; die "Target required for $word" unless ($dest); $word = ':'; } else { $dest ||= $word; } if ( my $t = ref $dest ) { if ( $t eq 'CODE' ) { $routes->{$word} = $dest; } elsif ( $t eq 'HASH' ) { $routes->{$word} ||= {}; foreach my $w ( keys %$dest ) { $self->genRoute( $routes->{$word}, $w, $dest->{$w} ); } } elsif ( $t eq 'ARRAY' ) { $routes->{$word} ||= {}; foreach my $w ( @{$dest} ) { $self->genRoute( $routes->{$word}, $w ); } } else { die "Type $t unauthorizated in routes"; } } elsif ( $dest =~ /^(.+)\.html$/ ) { my $tpl = $1 or die; $routes->{$word} = sub { $self->sendHtml( $_[1], $tpl ) }; } elsif ( $self->can($dest) ) { $routes->{$word} = sub { shift; $self->$dest(@_) }; } else { die "$dest() isn't a method"; } $self->lmLog( "route $word added", 'debug' ); } } sub handlerAbort { my ( $self, $path, $msg ) = @_; delete $self->routes->{$path}; $self->addRoute( $path => sub { my ( $self, $req ) = @_; return $self->sendError( $req, $msg, 500 ); } ); } # Methods that dispatch requests sub handler { my ( $self, $req ) = @_; #print STDERR Dumper($self->routes);use Data::Dumper; # Reinitialize configuration message $Lemonldap::NG::Common::Conf::msg = ''; # Launch reqInit() if exists if ( $self->can('reqInit') ) { $self->reqInit($req); } # Only words are taken in path my @path = grep { $_ =~ /^[\.\w]+/ } split /\//, $req->path(); $self->lmLog( "Start routing " . ( $path[0] // 'default route' ), 'debug' ); unless (@path) { push @path, $self->defaultRoute; # TODO: E-Tag, Expires,... # ## NB: this is not HTTP compliant: host and protocol are required ! #my $url = '/' . $self->defaultRoute; #return [ # 302, # [ 'Content-Type' => 'text/plain', 'Location' => $url ], # ['Document has moved here: $url'] #]; } return $self->followPath( $req, $self->routes->{ $req->method }, \@path ); } sub followPath { my ( $self, $req, $routes, $path ) = @_; if ( $path->[0] and defined $routes->{ $path->[0] } ) { my $w = shift @$path; if ( ref( $routes->{$w} ) eq 'CODE' ) { return $routes->{$w}->( $self, $req, @$path ); } return $self->followPath( $req, $routes->{$w}, $path ); } elsif ( $routes->{':'} ) { my $v = shift @$path; $req->params->{ $routes->{'#'} } = $v; if ( ref( $routes->{':'} ) eq 'CODE' ) { return $routes->{':'}->( $self, $req, @$path ); } return $self->followPath( $req, $routes->{':'}, $path ); } elsif ( my $sub = $routes->{'*'} ) { return $self->$sub( $req, @$path ); } else { $self->lmLog( 'Bad request received (' . $req->path . ')', 'error' ); return $self->sendError( $req, 'Bad request', 400 ); } } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::PSGI::Router - Base library for REST APIs of Lemonldap::NG. =head1 SYNOPSIS package My::PSGI; use base Lemonldap::NG::Common::PSGI::Router; sub init { my ($self,$args) = @_; # Will be called 1 time during startup # Declare REST routes (could be HTML templates or methods) $self->addRoute ( 'index.html', undef, ['GET'] ) ->addRoute ( books => { ':book' => 'booksMethod' }, ['GET', 'POST'] ) ->addRoute ( properties => { '*' => 'propertiesMethod' }, ['GET', 'POST', 'PUT', 'DELETE']); # Default route (ie: PATH_INFO == '/') $self->defaultRoute('index.html'); # See Lemonldap::NG::Common::PSGI for other options # Return a boolean. If false, then error message has to be stored in # $self->error return 1; } sub booksMethod { my ( $self, $req, @otherPathInfo ) = @_; my $book = $req->params('book'); my $method = $req->method; ... $self->sendJSONresponse(...); } sub propertiesMethod { my ( $self, $property, @otherPathInfo ) = @_; my $method = $req->method; ... $self->sendJSONresponse(...); } This package could then be called as a CGI, using FastCGI,... #!/usr/bin/env perl use My::PSGI; use Plack::Handler::FCGI; # or Plack::Handler::CGI Plack::Handler::FCGI->new->run( My::PSGI->run() ); =head1 DESCRIPTION This package provides base class for Lemonldap::NG REST API but could be used regardless. =head1 METHODS See L for logging methods, content sending,... =head2 Initialization methods =head3 addRoute ( $word, $dest, $methods ) Declare a REST route. Arguments: =over =item $word: the first word of /path/info. =item $dest: string, sub ref or hash ref (see "Route types" below) =item $methods: array ref containing the methods concerned by this route. =back =head4 Route types As seen in "SYNOPSIS", you can declare routes with variable component. $dest can be: =over =item a word: the name of the method to call =item undef: $word is used as $dest =item a ref to code: an anonymous subroutin to call =item a hash ref: it's a recursive call to `{ $word => $dest }` =item an array ref: in this case each element of the array will be considered as `{ $element => $element }`. So each element must be a word that makes a correspondence between a path_info word and a subroutine =back Some special $word: =over =item ':name': the word in path_info will be stored in GET parameters =item '*': the subroutine will be called with the word of path_info as second argument (after $req) =item 'something.html': if $word finishes with '.html', then sendHtml() will be called with 'something.tpl' as template name. In this case, $dest is not used. =back Examples: =over =item to manage http://.../books/127 with book() where 127 is the book number, use: $self->addRoute( books => { ':bookId' => 'book' }, ['GET'] ); booId parameter will be stored in $req->params('bookId'); =item to manage http://.../books/127/pages/5 with page(), use: $self->addRoute( books => { ':bookId' => { pages => { ':pageId' => 'page' } } }, ['GET'] ); =item to manage simultaneously the 2 previous examples $self->addRoute( books => { ':bookId' => { pages => { ':pageId' => 'page' } } }, ['GET'] ) ->addRoute( books => { ':bookId' => { '*' => 'book' } }, ['GET'] ); Note that book() will be called for any path_info containing /books/<$bookid>/<$other> except if $other == 'pages'. =item to manage /properties/p1, /properties/p2 with p1() and p2(), use: $self->addRoute( properties => [ 'p1', 'p2' ] ); =back =head3 defaultRoute($path) This method defined which path_info to use if path_info is '/' or empty. =head2 Accessors See L for inherited accessors (error, languages, logLevel, staticPrefix, templateDir, links, syslog). =head1 SEE ALSO L, L, L, L, L, L, L, L, =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Regexp.pm000066400000000000000000000027271325274564300337650ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common## @file # Common regexp issued from Regexp::Common package Lemonldap::NG::Common::Regexp; use AutoLoader 'AUTOLOAD'; 1; __END__ sub HOST { qr{^(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)|.{0})$}; } sub HOSTNAME { qr{^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?|.{0})$}; } sub HTTP_URI { qr{^(?:https?://(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?|.{0})$}; } sub reDomainsToHost { my $list = shift; return qr/^$/ unless ($list); $list =~ s/^\./\\\./g; $list = join( '|', split( /\s+/, $list ) ); return qr/^(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:$list)$/; } sub GOOGLEAXATTR { return qr/^(?:(?:la(?:nguag|stnam)|firstnam)e|country|email)$/; } sub OPENIDSREGATTR { return qr/^(?:(?:(?:full|nick)nam|languag|postcod|timezon)e|country|gender|email|dob)$/; } Safe.pm000066400000000000000000000065521325274564300334110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common## @file # LL::NG module for Safe jail ## @package # LL::NG module for Safe jail package Lemonldap::NG::Common::Safe; use strict; use base qw(Safe); use constant SAFEWRAP => ( Safe->can("wrap_code_ref") ? 1 : 0 ); use Scalar::Util 'weaken'; our $VERSION = '1.9.1'; our $self; # Safe cannot share a variable declared with my ## @constructor Lemonldap::NG::Common::Safe new(Lemonldap::NG::Portal::Simple portal) # Build a new Safe object # @param portal Lemonldap::NG::Portal::Simple object # @return Lemonldap::NG::Common::Safe object sub new { my ( $class, $portal ) = @_; my $self = {}; unless ( $portal->{useSafeJail} ) { # Fake jail $portal->lmLog( "Creating a fake Safe jail", 'debug' ); bless $self, $class; } else { # Safe jail $self = $class->SUPER::new(); $portal->lmLog( "Creating a real Safe jail", 'debug' ); } # Store portal object $self->{p} = $portal; weaken $self->{p}; return $self; } ## @method reval(string $e) # Evaluate an expression, inside or outside jail # @param e Expression to evaluate sub reval { local $self = shift; my ($e) = @_; my $result; # Replace $date $e =~ s/\$date/&POSIX::strftime("%Y%m%d%H%M%S",localtime())/e; # Replace variables by session content # Manage subroutine not the same way as plain perl expressions if ( $e =~ /^sub\s*{/ ) { $e =~ s/\$(?!ENV)(?!self)(\w+)/\$self->{sessionInfo}->{$1}/g; } else { $e =~ s/\$(?!ENV)(\w+)/\$self->{p}->{sessionInfo}->{$1}/g; } $self->{p}->lmLog( "Evaluate expression: $e", 'debug' ); if ( $self->{p}->{useSafeJail} ) { # Share $self to access sessionInfo HASH $self->SUPER::share('$self'); # Test SAFEWRAP and run reval $result = ( ( SAFEWRAP and ref($e) eq 'CODE' ) ? $self->SUPER::wrap_code_ref( $self->SUPER::reval($e) ) : $self->SUPER::reval($e) ); } else { # Use a standard eval $result = eval $e; } # Catch errors if ($@) { $self->{p} ->lmLog( "Error while evaluating the expression: $@", 'warn' ); return; } $self->{p}->lmLog( "Evaluation result: $result", 'debug' ); return $result; } ## @method share_from(string $pkg, arrayref $vars) # Share variables into Safe jail # @param pkg Package # @param vars Varibales sub share_from { local $self = shift; my ( $pkg, $vars ) = (@_); # If Safe jail, call parent if ( $self->{p}->{useSafeJail} ) { $self->SUPER::share_from( $pkg, $vars ); } # Else register varibales into current package # Code copied from Safe.pm else { no strict 'refs'; foreach my $arg (@$vars) { my ( $var, $type ); $type = $1 if ( $var = $arg ) =~ s/^(\W)//; for ( 1 .. 2 ) { # assign twice to avoid any 'used once' warnings *{$var} = ( !$type ) ? \&{ $pkg . "::$var" } : ( $type eq '&' ) ? \&{ $pkg . "::$var" } : ( $type eq '$' ) ? \${ $pkg . "::$var" } : ( $type eq '@' ) ? \@{ $pkg . "::$var" } : ( $type eq '%' ) ? \%{ $pkg . "::$var" } : ( $type eq '*' ) ? *{ $pkg . "::$var" } : undef; } } } } 1; Safelib.pm000066400000000000000000000155531325274564300341010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common##@file # Functions shared in Safe jail ##@class # Functions shared in Safe jail package Lemonldap::NG::Common::Safelib; use strict; use Encode; use MIME::Base64; #use AutoLoader qw(AUTOLOAD); our $VERSION = '1.9.1'; # Set here all the names of functions that must be available in Safe objects. # Not that only functions, not methods, can be written here our $functions = [ qw(&checkLogonHours &date &checkDate &basic &unicode2iso &iso2unicode &groupMatch) ]; ## @function boolean checkLogonHours(string logon_hours, string syntax, string time_correction, boolean default_access) # Function to check logon hours # @param $logon_hours string representing allowed logon hours (GMT) # @param $syntax optional hexadecimal (default) or octetstring # @param $time_correction optional hours to add or to subtract # @param $default_access optional what result to return for users without logons hours # @return 1 if access allowed, 0 else sub checkLogonHours { my ( $logon_hours, $syntax, $time_correction, $default_access ) = @_; # Active Directory - logonHours: $attr_src_syntax = octetstring # Samba - sambaLogonHours: ??? # LL::NG - ssoLogonHours: $attr_src_syntax = hexadecimal $syntax ||= "hexadecimal"; # Default access if no value $default_access ||= "0"; return $default_access unless $logon_hours; # Get the base2 value of logon_hours # Each byte represent an hour of the week # Begin with sunday at 0h00 my $base2_logon_hours; if ( $syntax eq "octetstring" ) { $base2_logon_hours = unpack( "B*", $logon_hours ); } if ( $syntax eq "hexadecimal" ) { # Remove white spaces $logon_hours =~ s/ //g; $base2_logon_hours = unpack( "B*", pack( "H*", $logon_hours ) ); } # Get the present day and hour my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = gmtime(time); # Get the hour position my $hourpos = $wday * 24 + $hour; # Use time_correction if ($time_correction) { my ( $sign, $time ) = ( $time_correction =~ /([+|-]?)(\d+)/ ); if ( $sign =~ /-/ ) { $hourpos -= $time; } else { $hourpos += $time; } } # Get the corresponding byte return substr( $base2_logon_hours, $hourpos, 1 ); } ## @function integer date # Get current local date # @param $gmt optional boolean To return GMT date (default is local date) # @return current date on format YYYYMMDDHHMMSS sub date { my $gmt = shift; my ( $sec, $min, $hour, $mday, $mon, $year ) = $gmt ? gmtime : localtime; $year += 1900; $mon += 1; $mon = "0" . $mon if ( $mon < 10 ); $mday = "0" . $mday if ( $mday < 10 ); $hour = "0" . $hour if ( $hour < 10 ); $min = "0" . $min if ( $min < 10 ); $sec = "0" . $sec if ( $sec < 10 ); return $year . $mon . $mday . $hour . $min . $sec; } ## @function boolean checkDate(string start, string end, boolean default_access) # Function to check a date # @param $start string Start date (GMT) # @param $end string End date (GMT) # @param $default_access optional what result to return for users without start or end start # @return 1 if access allowed, 0 else sub checkDate { my ( $start, $end, $default_access ) = @_; # Get date in string $start = substr( $start, 0, 14 ); $end = substr( $end, 0, 14 ); # Default access if no value $default_access ||= "0"; return $default_access unless ( $start or $end ); # If no start, set start to 0 $start ||= 0; # If no end, set end to the end of the world $end ||= 999999999999999; # Get the present day and hour my $date = &date; return 1 if ( ( $date >= $start ) and ( $date <= $end ) ); return 0; } ## @function string basic(string login, string password) # Return string that can be used for HTTP-BASIC authentication # @param login User login # @param password User password # @return Authorization header content sub basic { my ( $login, $password ) = @_; # UTF-8 strings should be ISO encoded $login = &unicode2iso($login); $password = &unicode2iso($password); return "Basic " . encode_base64( $login . ":" . $password, '' ); } ## @function string unicode2iso(string string) # Convert UTF-8 in ISO-8859-1 # @param string UTF-8 string # @return ISO string sub unicode2iso { my ($string) = @_; return encode( "iso-8859-1", decode( "utf-8", $string ) ); } ## @function string iso2unicode(string string) # Convert ISO-8859-1 in UTF-8 # @param string ISO string # @return UTF-8 string sub iso2unicode { my ($string) = @_; return encode( "utf-8", decode( "iso-8859-1", $string ) ); } ## @function int groupMatch(hashref groups, string attribute, string value) # Check in hGroups structure if a group attribute contains a value # @param groups The $hGroups variable # @param attribute Name of the attribute # @param value Value to check # @return int Number of values that match sub groupMatch { my ( $groups, $attribute, $value ) = @_; my $match = 0; foreach my $group ( keys %$groups ) { if ( ref( $groups->{$group}->{$attribute} ) eq "ARRAY" ) { foreach ( @{ $groups->{$group}->{$attribute} } ) { $match++ if ( $_ =~ /$value/ ); } } else { $match++ if ( $groups->{$group}->{$attribute} =~ /$value/ ); } } return $match; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Common::Safelib - Contains functions that are automatically imported in Lemonldap::NG Safe objects to be used in expressions like rules, macros,... =head1 SYNOPSIS Private module not documented. =head1 DESCRIPTION Private module not documented. =head1 SEE ALSO L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2009-2010 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2009-2016 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Session.pm000066400000000000000000000101351325274564300341460ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/lib/Lemonldap/NG/Common##@file # Base package for LemonLDAP::NG session object ##@class # Specify a session object, how to create/update/remove session package Lemonldap::NG::Common::Session; our $VERSION = '1.9.1'; use Mouse; use Lemonldap::NG::Common::Apache::Session; has 'id' => ( is => 'rw', isa => 'Str|Undef', ); has 'force' => ( is => 'rw', isa => 'Bool', default => 0, ); has 'kind' => ( is => 'rw', isa => 'Str|Undef', ); has 'data' => ( is => 'rw', isa => 'HashRef', default => sub { {} }, ); has 'options' => ( is => 'rw', isa => 'HashRef', ); has 'storageModule' => ( is => 'ro', isa => 'Str', required => 1, ); has 'storageModuleOptions' => ( is => 'ro', isa => 'HashRef|Undef', ); has 'cacheModule' => ( is => 'rw', isa => 'Str|Undef', ); has 'cacheModuleOptions' => ( is => 'rw', isa => 'HashRef|Undef', ); has 'error' => ( is => 'rw', isa => 'Str|Undef', ); sub BUILD { my $self = shift; # Load Apache::Session module unless ( $self->storageModule->can('populate') ) { eval "require " . $self->storageModule; return undef if $@; } # Register options for common Apache::Session module my $moduleOptions = $self->storageModuleOptions || {}; my %options = ( %$moduleOptions, backend => $self->storageModule, localStorage => $self->cacheModule, localStorageOptions => $self->cacheModuleOptions ); $self->options( \%options ); my $data = $self->_tie_session; # Is it a session creation request? my $creation = 1 if ( !$self->id or ( $self->id and !$data and $self->force ) ); # If session id was submitted but session is not found # And we want to force id # Then use setId to create session if ( $self->id and $creation ) { $options{setId} = $self->id; $self->options( \%options ); $self->id(undef); $self->error(undef); $data = $self->_tie_session; } # If session is created # Then set session kind in session if ( $creation and $self->kind ) { $data->{_session_kind} = $self->kind; } # Load session data into object if ($data) { $self->_save_data($data); $self->kind( $data->{_session_kind} ); $self->id( $data->{_session_id} ); untie(%$data); } } sub _tie_session { my $self = shift; my $options = shift || {}; my %h; eval { # SOAP session module must be directly tied if ( $self->storageModule =~ /Lemonldap::NG::Common::Apache::Session::SOAP/ ) { tie %h, $self->storageModule, $self->id, { %{ $self->options }, %$options }; } else { tie %h, 'Lemonldap::NG::Common::Apache::Session', $self->id, { %{ $self->options }, %$options }; } }; if ( $@ or not tied(%h) ) { my $msg = "Session cannot be tied"; $msg .= ": $@" if $@; $self->error($msg); return undef; } return \%h; } sub _save_data { my ( $self, $data ) = @_; my %saved_data = %$data; $self->data( \%saved_data ); } sub update { my $self = shift; my $infos = shift; my $tieOptions = shift; unless ( ref $infos eq "HASH" ) { $self->error("You need to provide a HASHREF"); return 0; } my $data = $self->_tie_session($tieOptions); if ($data) { foreach ( keys %$infos ) { if ( defined $infos->{$_} ) { $data->{$_} = $infos->{$_}; } else { delete $data->{$_}; } } $self->_save_data($data); untie(%$data); return 1; } $self->error("No data found in session"); return 0; } sub remove { my $self = shift; my $tieOptions = shift; my $data = $self->_tie_session($tieOptions); eval { tied(%$data)->delete(); }; if ($@) { $self->error("Unable to delete session: $@"); return 0; } return 1; } no Mouse; 1; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/scripts/000077500000000000000000000000001325274564300275165ustar00rootroot00000000000000convertConfig000077500000000000000000000072431325274564300322010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/scripts#!/usr/bin/perl use strict; use Getopt::Long; use Lemonldap::NG::Common::Conf; my %opts; my $result = GetOptions( \%opts, 'help|h', 'current|c=s', 'new|n=s', 'latest|l', 'force|f' ); if ( $opts{help} or not( $opts{current} and $opts{new} ) ) { print STDERR " ## Lemonldap::NG configuration converter ## Usage: $0 --current=/current/lemonldap-ng.ini --new=/new/lemonldap-ng.ini other parameters: --latest -l convert only last configuration --force -f continue even if an error occurs "; exit 1; } foreach ( $opts{current}, $opts{new} ) { unless ( -e $_ ) { print STDERR "$_ does not exist\n"; exit 2; } unless ( -r $_ ) { print STDERR "$_ is not readable\n"; exit 3; } } my $old = Lemonldap::NG::Common::Conf->new( { confFile => $opts{current}, } ); unless ($old) { print STDERR "Failed to get current conf : $Lemonldap::NG::Common::Conf::msg\n"; exit 4; } my %newargs = ( force => 1, noCache => 1, cfgNumFixed => 1, ); my $new = Lemonldap::NG::Common::Conf->new( { confFile => $opts{new}, } ); unless ($new) { print STDERR "Failed to create new conf object : $Lemonldap::NG::Common::Conf::msg\n"; exit 5; } my @available; if ( $opts{latest} ) { @available = $old->lastCfg(); } else { @available = $old->available(); } foreach (@available) { my $conf = $old->getConf( { cfgNum => $_, noCache => 1 } ); unless ($conf) { print STDERR "\nFailed to get conf $_ : $Lemonldap::NG::Common::Conf::msg\n"; next if ( $opts{force} ); exit 6; } if ( my $r = $new->saveConf( $conf, %newargs ) ) { print "Conf $conf->{cfgNum} stored\n"; next; } print STDERR "Unable to store configuration $conf->{cfgNum}: $Lemonldap::NG::Common::Conf::msg"; next if ( $opts{force} ); exit 7; } __END__ =head1 NAME =encoding utf8 convertConfig - tool used to change Lemonldap::NG configuration database. =head1 SYNOPSIS convertConfig --current=/current/lemonldap-ng.ini --new=/new/lemonldap-ng.ini # Convert only latest (loose history) convertConfig --latest --current=... --new=... # Continue even if an error occurs convertConfig --force -f --current=... --new=... =head1 DESCRIPTION convertConfig is a command line toot that can be used to do initialize a new Lemonldap::NG configuration database keeping current configuration and history. =head1 SEE ALSO L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2008-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut lemonldap-ng-cli000077500000000000000000000067331325274564300325200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/scripts#!/usr/bin/perl use warnings; use strict; use POSIX; my $action; eval { POSIX::setgid( scalar( getgrnam('__APACHEGROUP__') ) ); POSIX::setuid( scalar( getpwnam('__APACHEUSER__') ) ); }; for ( my $i = 0 ; $i < @ARGV ; $i++ ) { if ( $ARGV[$i] =~ /^-/ ) { $i++; next; } $action = $ARGV[$i]; last; } $action ||= "help"; if ( $action =~ /^(?:[gs]et|(?:add|del)Key)$/ ) { eval { require Lemonldap::NG::Manager::Cli; }; die 'Manager libraries not available, aborting' if ($@); Lemonldap::NG::Manager::Cli->run(@ARGV); } elsif ( $action =~ /^(?:info|update-cache)$/ ) { eval { require Lemonldap::NG::Common::Cli; }; die "Lemonldap::NG common libraries not available, aborting ($@)" if ($@); Lemonldap::NG::Common::Cli->run(@ARGV); } else { help(); } sub help { print STDERR qq{Usage: $0 action Available actions: - help : print this - info : get currentconfiguration info - update-cache : force configuration cache to be updated - get : get values of parameters - set : set parameter(s) value(s) - addKey : add or set a subkey in a parameter - delKey : delete subkey of a parameter See Lemonldap::NG::Common::Cli(3) or Lemonldap::NG::Manager::CLi(3) for more }; } __END__ =head1 NAME =encoding utf8 lemonldap-ng-cli - Command-line manager for Lemonldap::NG web-SSO system. =head1 SYNOPSIS # Get information about current configuration $ lemonldap-ng-cli info # Update local configuration cache $ lemonldap-ng-cli update-cache # Get some configuration parameter values $ lemonldap-ng-cli get portal domain cookieName # Set some values $ lemonldap-ng-cli set portal http://auth.e.com/ domain e.com # add or set a key $ lemonldap-ng-cli addKey macro fullname '$givenName." ".$lastName' =head1 DESCRIPTION lemonldap-ng-cli is a command line client that can be used to do some actions on Lemonldap::NG configuration. Commands are detailed in L and L =head1 SEE ALSO L, L L =head1 AUTHORS =over =item David Coutateur, Edavid.jose.delassus@gmail.comE =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut lmMigrateConfFiles2ini000077500000000000000000000157521325274564300336730ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/scripts#!/usr/bin/perl use strict; use Getopt::Long; use Config::IniFiles; use XML::LibXML; use Lemonldap::NG::Common::Conf::Constants; # Get command line options my %opts; my $result = GetOptions( \%opts, 'storage|s=s', 'apply|a=s', 'dir|d=s', 'ini|i=s', 'preserve|p', 'menuxml|m=s', 'verbose|v', 'help|h', 'die', ); # Help if ( $opts{help} ) { print "$0 script imports old config file into the new lemonldap-ng.ini file\n"; print "Options:\n"; print "\t--dir,-d: path to main configuration directory (default: /etc/lemonldap-ng)\n"; print "\t--storage,-s: path to storage.conf (if not stored in conf dir)\n"; print "\t--apply,-a: path to apply.conf (if not stored in conf dir)\n"; print "\t--menuxml,-m: path to apps-list.xml (if not stored in conf dir)\n"; print "\t--ini,-i: path to lemonldap-ng.ini (if not stored in conf dir)\n"; print "\t--preserve,-p: do not erase old files after import\n"; print "\t--help,-h: show this message\n"; print "\t--verbose,-v: let me tell you my life\n"; exit 0; } # Set default values $opts{dir} ||= '/etc/lemonldap-ng'; my $old = { storage => $opts{storage} || $opts{dir} . "/storage.conf", apply => $opts{apply} || $opts{dir} . "/apply.conf", menuxml => $opts{menuxml} || $opts{dir} . "/apps-list.xml", menudtd => $opts{dir} . "/apps-list.dtd", }; my $new = $opts{ini} || $opts{dir} . "/lemonldap-ng.ini"; my $datas; if ( $opts{verbose} ) { print "Using values:\n"; print "\tMain configuration dir: " . $opts{dir} . "\n"; print "\tFile storage: " . $old->{storage} . "\n"; print "\tFile apply: " . $old->{apply} . "\n"; print "\tFile menu: " . $old->{menuxml} . "\n"; print "\tNew ini file: " . $new . "\n"; print "\tPreserve: " . ( $opts{preserve} ? "yes" : "no" ) . "\n\n"; } # Convert storage.conf if ( -r $old->{storage} ) { print "Parsing " . $old->{storage} . "\n" if $opts{verbose}; open F, $old->{storage}; while () { next if (/^\s*(?:#.*)?$/); my ( $k, $v ) = (/^(\w+)\s*=\s*(.*)$/) or quit( 3, "bad line in " . $old->{storage} . ":$_" ); $datas->{ +CONFSECTION }->{$k} = $v; print "\t$k: $v\n" if $opts{verbose}; } close F; print "\n" if $opts{verbose}; } elsif ( $opts{die} ) { quit( 2, $old->{storage} . " is not readable" ); } else { print STDERR $old->{storage} . " is not readable\n"; } # Convert apply.conf if ( -r $old->{apply} ) { print "Parsing " . $old->{apply} . "\n" if $opts{verbose}; open F, $old->{apply}; while () { next if (/^\s*(?:#.*)?$/); my ( $k, $v ) = (/^([\w\.\-]+)\s+(.*)$/) or quit( 3, "bad line in " . $old->{apply} . ":$_" ); $datas->{ +APPLYSECTION }->{$k} = $v; print "\t$k: $v\n" if $opts{verbose}; } close F; print "\n" if $opts{verbose}; } elsif ( $opts{die} ) { quit( 2, $old->{apply} . " is not readable" ); } else { print STDERR $old->{apply} . " is not readable\n"; } # Convert apps-list.xml if ( -r $old->{menuxml} ) { print "Parsing " . $old->{menuxml} . "\n" if $opts{verbose}; # Open XML file my $parser = XML::LibXML->new(); my $xml; eval { $xml = $parser->parse_file( $old->{menuxml} ); }; quit( 6, "Bad XML file: $@" ) if ($@); # Get root element my $root = $xml->documentElement; my $value = "{ "; $value .= _parseCategory($root); $value .= " }"; $datas->{ +PORTALSECTION }->{applicationList} = $value; print "\tapplicationList: $value\n\n" if $opts{verbose}; } elsif ( $opts{die} ) { quit( 2, $old->{menuxml} . " is not readable" ); } else { print STDERR $old->{menuxml} . " is not readable\n"; } # Open ini configuration file my $conf; if ( -e $new ) { -w $new or quit( 4, "$new is not writeable" ); $conf = Config::IniFiles->new( -file => $new, -allowcontinue => 1 ) or quit( 4, "Unable to open $new:\n\t" . join( "\n\t", @Config::IniFiles::errors ) ); } else { $conf = Config::IniFiles->new(); } # Write sections my @sections = $conf->Sections(); foreach ( ( CONFSECTION, APPLYSECTION, PORTALSECTION ) ) { print "Write data for section $_\n" if $opts{verbose}; next unless ( ref $datas->{$_} ); $conf->AddSection($_) unless ( $conf->SectionExists($_) ); while ( my ( $k, $v ) = each %{ $datas->{$_} } ) { # Old Config::IniFiles modules does not have 'exists' subroutine if ( $conf->can('exists') ) { if ( $conf->exists( $_, $k ) ) { $conf->setval( $_, $k, $v ); } else { $conf->newval( $_, $k, $v ); } } else { # Try setval, else newval unless ( $conf->setval( $_, $k, $v ) ) { $conf->newval( $_, $k, $v ); } } } } if ( -e $new ) { $conf->RewriteConfig(); } else { $conf->WriteConfig($new) or quit( 5, "Unable to create $new:\n\t" . join( "\n\t", @Config::IniFiles::errors ) ); } # Remove old files unless ( $opts{preserve} ) { print "Remove old files\n" if $opts{verbose}; unlink $old->{storage}, $old->{apply}, $old->{menuxml}, $old->{menudtd}; } # Local subroutines sub quit { print STDERR "$_[1]\n"; exit $_[0]; } sub _parseCategory { my $category = shift; my $value; my $catname = $category->getAttribute('name') || "Menu"; # Escape quote $catname =~ s/'/\\'/; $value .= "'$catname' => { type => 'category', "; # Applications my @appnodes = $category->findnodes("application"); foreach (@appnodes) { $value .= _parseApplication($_); } # Sub categories my @catnodes = $category->findnodes("category"); foreach (@catnodes) { $value .= _parseCategory($_); } $value .= " },"; return $value; } sub _parseApplication { my $application = $_; my $value; # Get application items my $appid = $application->getAttribute('id'); my $appname = $application->getChildrenByTagName('name')->string_value(); my $appuri = $application->getChildrenByTagName('uri')->string_value(); my $appdesc = $application->getChildrenByTagName('description')->string_value(); my $applogo = $application->getChildrenByTagName('logo')->string_value(); my $appdisplay = $application->getChildrenByTagName('display')->string_value(); # Escape quote $appid =~ s/'/\\'/; $appname =~ s/'/\\'/; $appdesc =~ s/'/\\'/; # Print application items $value .= "'$appid' => { type => 'application', options => { "; $value .= "name => '$appname', " if $appname; $value .= "uri => '$appuri', " if $appuri; $value .= "description => '$appdesc', " if $appdesc; $value .= "logo => '$applogo', " if $applogo; $value .= "display => '$appdisplay', " if $appdisplay; $value .= " },"; # Sub applications my @appnodes = $application->findnodes("application"); foreach (@appnodes) { $value .= _parseApplication($_); } $value .= " },"; return $value; } rotateOidcKeys000077500000000000000000000036411325274564300323220ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/scripts#!/usr/bin/perl #============================================================================= # Rotation of OpenID Connect keys # # This module is written to be used by cron to rotate keys. # # This is part of LemonLDAP::NG product, released under GPL #============================================================================= use strict; use Convert::PEM; use Crypt::OpenSSL::RSA; use Lemonldap::NG::Common::Conf; use String::Random qw(random_string); my $debug = 0; #============================================================================= # Load configuration #============================================================================= my $lmconf = Lemonldap::NG::Common::Conf->new() or die $Lemonldap::NG::Common::Conf::msg; my $conf = $lmconf->getConf(); print "Configuration loaded\n" if $debug; #============================================================================= # Generate new key #============================================================================= my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); my $key_id = random_string("ssssssssss"); my $keys = { 'private' => $rsa->get_private_key_string(), 'public' => $rsa->get_public_key_x509_string(), 'id' => $key_id, }; print "Private key generated:\n" . $keys->{private} . "\n" if $debug; print "Public key generated:\n" . $keys->{public} . "\n" if $debug; print "Key ID generated: " . $keys->{id} . "\n" if $debug; #============================================================================= # Save configuration #============================================================================= $conf->{cfgAuthor} = 'Key rotation script'; $conf->{oidcServicePrivateKeySig} = $keys->{private}; $conf->{oidcServicePublicKeySig} = $keys->{public}; $conf->{oidcServiceKeyIdSig} = $keys->{id}; $lmconf->saveConf($conf) or die $Lemonldap::NG::Common::Conf::msg; print "Configuration saved\n" if $debug; exit 0; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t/000077500000000000000000000000001325274564300262725ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t/01-Common-Conf.t000066400000000000000000000017521325274564300310150ustar00rootroot00000000000000# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 3; BEGIN { use_ok('Lemonldap::NG::Common::Conf') } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $h; my $inifile = "lemonldap-ng.ini"; my $confsection = "configuration"; ok( ( Lemonldap::NG::Common::Conf->new( type => 'bad' ) == 0 and $Lemonldap::NG::Common::Conf::msg =~ /Error: Unknown package Lemonldap::NG::Common::Conf::bad\.$/ ), 'Bad module' ); $h = bless {}, 'Lemonldap::NG::Common::Conf'; ok( ( %$h = ( %$h, %{ $h->getLocalConf( $confsection, $inifile, 0 ) } ) and exists $h->{localStorage} ), "Read $inifile" ); 02-Common-Conf-File.t000066400000000000000000000031711325274564300316110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use strict; use Test::More; BEGIN { use_ok('Lemonldap::NG::Common::Conf') } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $h; ok( $h = new Lemonldap::NG::Common::Conf( { type => 'File', dirName => "t/", } ), 'type => file', ); my $count = 2; my @test = ( # simple ascii { cfgNum => 1, test => 'ascii' }, # utf-8 { cfgNum => 1, test => 'Русский' }, # compatible utf8/latin-1 char but with different codes { cfgNum => 1, test => 'éà' } ); for ( my $i = 0 ; $i < @test ; $i++ ) { ok( $h->store( $test[$i] ) == 1, "Test $i is stored" ) or print STDERR "$Lemonldap::NG::Common::Conf::msg $!"; $count++; if ( -x '/usr/bin/file' ) { eval { open F, 'file t/lmConf-1.js |'; $_ = join( '', ); close F; ok( /(ascii|utf-?8)/si, "File is $1 encoded" ); $count++; }; } my $cfg; ok( $cfg = $h->load(1), "Test $i can be read" ) or print STDERR $Lemonldap::NG::Common::Conf::msg; ok( $cfg->{test} eq $test[$i]->{test}, "Test $i is restored" ) or print STDERR "Expect $cfg->{test} eq $test[$i]->{test}\n"; $count += 2; } #unlink 't/lmConf-1.js'; done_testing($count); 03-Common-Conf-CDBI.t000066400000000000000000000037321325274564300314370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use strict; use Test::More tests => 14; BEGIN { use_ok('Lemonldap::NG::Common::Conf'); } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $h; @ARGV = ("help=groups"); unlink 't/lmConf.sql'; SKIP: { eval { require DBI; }; skip( "DBI not installed", 13 ) if ($@); my $skipSQLite = 0; ok( $h = new Lemonldap::NG::Common::Conf( { type => 'CDBI', dbiChain => "DBI:SQLite:dbname=t/lmConf.sql", dbiUser => '', dbiPassword => '', } ), 'CDBI object' ); ok( $h->can('_dbh'), 'Driver is build' ); eval { require DBD::SQLite }; skip( "DBD::SQLite not installed", 11 ) if ($@); ok( $h->_dbh->{sqlite_unicode} = 1, 'Set unicode' ); ok( $h->_dbh->do( ' CREATE TABLE lmConfig ( cfgNum int not null primary key, data text)' ), 'Test database created' ); my @test = ( # simple ascii { cfgNum => 1, test => 'ascii' }, # utf-8 { cfgNum => 2, test => 'Русский' }, # compatible utf8/latin-1 char but with different codes { cfgNum => 3, test => 'éà' } ); for ( my $i = 0 ; $i < @test ; $i++ ) { ok( $h->store( $test[$i] ) == $i + 1, "Test $i is stored" ) or print STDERR "$Lemonldap::NG::Common::Conf::msg $!"; my $cfg; ok( $cfg = $h->load( $i + 1 ), "Test $i can be read" ) or print STDERR $Lemonldap::NG::Common::Conf::msg; ok( $cfg->{test} eq $test[$i]->{test}, "Test $i is restored" ); } unlink 't/lmConf.sql'; } 03-Common-Conf-RDBI.t000066400000000000000000000040361325274564300314540ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use strict; use Test::More tests => 14; BEGIN { use_ok('Lemonldap::NG::Common::Conf'); } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $h; @ARGV = ("help=groups"); unlink 't/lmConf.sql'; SKIP: { eval { require DBI; }; skip( "DBI not installed", 13 ) if ($@); my $skipSQLite = 0; ok( $h = new Lemonldap::NG::Common::Conf( { type => 'RDBI', dbiChain => "DBI:SQLite:dbname=t/lmConf.sql", dbiUser => '', dbiPassword => '', } ), 'RDBI object' ); ok( $h->can('_dbh'), 'Driver is build' ); eval { require DBD::SQLite }; skip( "DBD::SQLite not installed", 11 ) if ($@); ok( $h->_dbh->{sqlite_unicode} = 1, 'Set unicode' ); ok( $h->_dbh->do( "CREATE TABLE lmConfig ( cfgNum int not null, field varchar(255) NOT NULL DEFAULT '', value longblob, PRIMARY KEY (cfgNum,field))" ), 'Test database created' ); my @test = ( # simple ascii { cfgNum => 1, test => 'ascii' }, # utf-8 { cfgNum => 2, test => 'Русский' }, # compatible utf8/latin-1 char but with different codes { cfgNum => 3, test => 'éà' } ); for ( my $i = 0 ; $i < @test ; $i++ ) { ok( $h->store( $test[$i] ) == $i + 1, "Test $i is stored" ) or print STDERR "$Lemonldap::NG::Common::Conf::msg $!"; my $cfg; ok( $cfg = $h->load( $i + 1 ), "Test $i can be read" ) or print STDERR "$Lemonldap::NG::Common::Conf::msg $!$@"; ok( $cfg->{test} eq $test[$i]->{test}, "Test $i is restored" ); } unlink 't/lmConf.sql'; } 04-Common-Conf-SOAP.t000066400000000000000000000017251325274564300315010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 4; # SOAP::Lite is not required, so Lemonldap::NG::Common::Conf::SOAP may # not run. SKIP: { eval { require SOAP::Lite }; skip "SOAP::Lite is not installed, so SOAP configuration access will not work", 4 if ($@); use_ok('Lemonldap::NG::Common::Conf'); my $h; ok( $h = new Lemonldap::NG::Common::Conf( { type => 'SOAP', proxy => 'http://localhost', } ) ); ok( $h->can('_connect') ); ok( $h->can('_soapCall') ); } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. 05-Common-Conf-LDAP.t000066400000000000000000000021521325274564300314530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 3; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. SKIP: { eval { require Net::LDAP; }; skip "Net::LDAP is not installed, skipping tests", 3 if ($@); use_ok('Lemonldap::NG::Common::Conf'); my $h; ok( $h = new Lemonldap::NG::Common::Conf( { type => 'LDAP', ldapServer => 'ldap://localhost', ldapConfBase => 'ou=conf,ou=websso,dc=example,dc=com', ldapBindDN => 'cn=admin,dc=example,dc=com', ldapBindPassword => 'secret', } ), "New object" ) or print STDERR "Error reported: " . $Lemonldap::NG::Common::Conf::msg; ok( ref($h) and $h->can('ldap') ); } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t/20-Common-CGI.t000066400000000000000000000104511325274564300305270ustar00rootroot00000000000000# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Manager.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; package My::Portal; use strict; use Test::More tests => 25; use_ok('Lemonldap::NG::Common::CGI'); #our @ISA = qw('Lemonldap::NG::Common::CGI'); use base 'Lemonldap::NG::Common::CGI'; sub mySubtest { return 'OK1'; } sub abort { shift; $, = ''; print STDERR @_; die 'abort has been called'; } sub quit { 2; } our $param; sub param { return $param; } sub soapfunc { return 'SoapOK'; } our $buf; our $lastpos = 0; sub diff { my $str = $buf; $str =~ s/^.{$lastpos}//s if ($lastpos); $str =~ s/\r//gs; $lastpos = length $buf; return $str; } SKIP: { eval "use IO::String;"; skip "IO::String not installed", 9 if ($@); tie *STDOUT, 'IO::String', $buf; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $cgi; $ENV{SCRIPT_NAME} = '/test.pl'; $ENV{SCRIPT_FILENAME} = 't/20-Common-CGI.t'; $ENV{REQUEST_METHOD} = 'GET'; $ENV{REQUEST_URI} = '/'; $ENV{QUERY_STRING} = ''; #$cgi = CGI->new; ok( ( $cgi = Lemonldap::NG::Common::CGI->new() ), 'New CGI' ); bless $cgi, 'My::Portal'; # Test header_public ok( $buf = $cgi->header_public('t/20-Common-CGI.t'), 'header_public' ); ok( $buf =~ /Cache-control: public; must-revalidate; max-age=\d+\r?\n/s, 'Cache-Control' ); ok( $buf =~ /Last-modified: /s, 'Last-Modified' ); # Test _sub mechanism ok( $cgi->_sub('mySubtest') eq 'OK1', '_sub mechanism 1' ); $cgi->{mySubtest} = sub { return 'OK2' }; ok( $cgi->_sub('mySubtest') eq 'OK2', '_sub mechanism 2' ); # Test extract_lang my $lang; ok( $lang = $cgi->extract_lang(), 'extract_lang 0 with void "Accept-language"' ); ok( scalar(@$lang) == 0, 'extract_lang 1 with void "Accept-language"' ); my $cgi2; $ENV{SCRIPT_NAME} = '/test.pl'; $ENV{SCRIPT_FILENAME} = 't/20-Common-CGI.t'; $ENV{REQUEST_METHOD} = 'GET'; $ENV{REQUEST_URI} = '/'; $ENV{QUERY_STRING} = ''; $ENV{HTTP_ACCEPT_LANGUAGE} = 'fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'; ok( ( $cgi2 = Lemonldap::NG::Common::CGI->new() ), 'New CGI' ); ok( $lang = $cgi2->extract_lang(), 'extract_lang' ); ok( $lang->[0] eq 'fr', 'extract_lang' ); ok( $lang->[1] eq 'en', 'extract_lang' ); ok( scalar(@$lang) == 2, 'extract_lang' ); # Extract lang Android (See #LEMONLDAP-530) my $cgi3; $ENV{HTTP_ACCEPT_LANGUAGE} = 'fr-FR, en-US'; ok( ( $cgi3 = Lemonldap::NG::Common::CGI->new() ), 'New CGI' ); ok( $lang = $cgi3->extract_lang(), 'extract_lang Android' ); ok( $lang->[0] eq 'fr', 'extract_lang Android' ); ok( $lang->[1] eq 'en', 'extract_lang Android' ); ok( scalar(@$lang) == 2, 'extract_lang Android' ); # Extract lang with * value my $cgi4; $ENV{HTTP_ACCEPT_LANGUAGE} = "fr,en,*"; ok( ( $cgi4 = Lemonldap::NG::Common::CGI->new() ), 'New CGI' ); ok( $lang = $cgi4->extract_lang(), 'extract_lang with * value' ); ok( scalar(@$lang) == 2, 'extract_lang with * value' ); # SOAP eval { require SOAP::Lite }; skip "SOAP::Lite is not installed, so CGI SOAP functions will not work", 3 if ($@); $ENV{HTTP_SOAPACTION} = 'http://localhost/Lemonldap/NG/Common/CGI/SOAPService#soapfunc'; $param = 'fr'; ok( $cgi->soapTest('soapfunc') == 2, 'SOAP call exit fine' ); my $tmp = diff(); ok( $tmp =~ /^Status: 200/s, 'HTTP response 200' ); ok( $tmp =~ /SoapOK<\/result>/s, 'result of SOAP call' ); } 30-Common-Safelib.t000066400000000000000000000007621325274564300314200ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Common.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 1; BEGIN { use_ok('Lemonldap::NG::Common::Safelib') } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. 35-Common-Crypto.t000066400000000000000000000026321325274564300313360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Common.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 21; use Digest::MD5 qw(md5 md5_hex md5_base64); use strict; BEGIN { use_ok('Lemonldap::NG::Common::Crypto'); } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $c; ok( $c = Lemonldap::NG::Common::Crypto->new( 'lemonldap-ng-key', Crypt::Rijndael::MODE_CBC() ), 'New object' ); foreach my $i ( 1 .. 17 ) { my $s = ''; $s = join( '', map { chr( int( rand(94) ) + 33 ) } ( 1 .. $i ) ); ok( $c->decrypt( $c->encrypt($s) ) eq $s, "Test of base64 encrypting with $i characters string" ); } my $data = md5_hex(rand); my $secondKey = md5(rand); ok( $c->decryptHex( $c->encryptHex( $data, $secondKey ), $secondKey ) eq $data, "Test of hexadecimal encrypting" ); # Test a long value, and replace carriage return by %0A my $long = "f5a1f72e7ab2f7712855a068af0066f36bfcf2c87e6feb9cf4200da1868e1dfe"; my $cryptedlong = "Da6sYxp9NCXv8+8TirqHmPWwTQHyEGmkCBGCLCX/81dPSMwIQVQNV7X9KG3RrKZfyRmzJR6DZYdU%0Ab75+VH3+CA=="; ok( $c->decrypt($cryptedlong) eq $long, "Test of long value encrypting" ); 36-Common-Regexp.t000066400000000000000000000025151325274564300313110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-common/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Common.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 20; use strict; BEGIN { use_ok('Lemonldap::NG::Common::Regexp'); } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. *HOST = *Lemonldap::NG::Common::Regexp::HOST; *HOSTNAME = *Lemonldap::NG::Common::Regexp::HOSTNAME; *HTTP_URI = *Lemonldap::NG::Common::Regexp::HTTP_URI; *reDomainsToHost = *Lemonldap::NG::Common::Regexp::reDomainsToHost; ok( 'test.ex.com' =~ HOST() ); ok( 'test.ex.com' =~ HOSTNAME() ); ok( 'test..ex.com' !~ HOST() ); ok( 'test..ex.com' !~ HOSTNAME() ); ok( '10.1.1.1' =~ HOST() ); ok( '10.1.1.1' !~ HOSTNAME() ); ok( 'test.ex.com' !~ HTTP_URI() ); ok( 'https://test.ex.com' =~ HTTP_URI() ); ok( 'https://test.ex.com/' =~ HTTP_URI() ); ok( 'https://test.ex.com/a' =~ HTTP_URI() ); ok( 'https://test.ex.com/?\n" if ($jqueryUrl); return $jqueryUrl . "\n"; } 1; Main/000077500000000000000000000000001325274564300333235ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/HandlerJail.pm000066400000000000000000000107461325274564300345500ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Mainpackage Lemonldap::NG::Handler::Main::Jail; use strict; use Safe; use Lemonldap::NG::Common::Safelib; #link protected safe Safe object use constant SAFEWRAP => ( Safe->can("wrap_code_ref") ? 1 : 0 ); use Mouse; use Lemonldap::NG::Handler::Main::Logger; has customFunctions => ( is => 'rw', isa => 'Maybe[Str]' ); has useSafeJail => ( is => 'rw', isa => 'Maybe[Int]' ); has jail => ( is => 'rw' ); has error => ( is => 'rw' ); our $VERSION = '1.9.16'; use Lemonldap::NG::Handler::Main '$datas', '$tsv'; use Lemonldap::NG::Handler::API ':functions'; ## @imethod protected build_jail() # Build and return the security jail used to compile rules and headers. # @return Safe object sub build_jail { my $self = shift; return $self->jail if ( $self->jail && $self->jail->useSafeJail == $self->useSafeJail && $self->jail->customFunctions eq $self->customFunctions ); $self->useSafeJail(1) unless defined $self->useSafeJail; my @t = $self->customFunctions ? split( /\s+/, $self->customFunctions ) : (); foreach (@t) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Custom function : $_", 'debug' ); my $sub = $_; unless (/::/) { $sub = "$self\::$_"; } else { s/^.*:://; } next if ( $self->can($_) ); eval "sub $_ { my \$uri = Lemonldap::NG::Handler::API::${Lemonldap::NG::Handler::API::mode}::uri_with_args(); return $sub(\$uri,\@_) }"; Lemonldap::NG::Handler::Main::Logger->lmLog( $@, 'error' ) if ($@); $_ = "&$_"; } if ( $self->useSafeJail ) { $self->jail( Safe->new ); $self->jail->share_from( 'main', ['%ENV'] ); } else { $self->jail($self); } # Share objects with Safe jail $self->jail->share_from( 'Lemonldap::NG::Common::Safelib', $Lemonldap::NG::Common::Safelib::functions ); $self->jail->share_from( 'Lemonldap::NG::Handler::Main', [ '$tsv', '$datas' ] ); $self->jail->share_from( 'Lemonldap::NG::Handler::API', [ qw( &hostname &remote_ip &uri &uri_with_args &unparsed_uri &args &method &header_in ) ] ); $self->jail->share_from( __PACKAGE__, [ @t, '&encrypt' ] ); $self->jail->share_from( 'MIME::Base64', ['&encode_base64'] ); # Initialize cryptographic functions to be able to use them in jail. eval { encrypt('a') }; return $self->jail; } # Import crypto methods for jail sub encrypt { return $tsv->{cipher}->encrypt(@_); } ## @method reval # Fake reval method if useSafeJail is off sub reval { my ( $self, $e ) = @_; my $res = eval $e; if ($@) { $self->error($@); return undef; } return $res; } ## @method wrap_code_ref # Fake wrap_code_ref method if useSafeJail is off sub wrap_code_ref { my ( $self, $e ) = @_; return $e; } ## @method share # Fake share method if useSafeJail is off sub share { my ( $self, @vars ) = @_; $self->share_from( scalar(caller), \@vars ); } ## @method share_from # Fake share_from method if useSafeJail is off sub share_from { my ( $self, $pkg, $vars ) = @_; no strict 'refs'; foreach my $arg (@$vars) { my ( $var, $type ); $type = $1 if ( $var = $arg ) =~ s/^(\W)//; for ( 1 .. 2 ) { # assign twice to avoid any 'used once' warnings *{$var} = ( !$type ) ? \&{ $pkg . "::$var" } : ( $type eq '&' ) ? \&{ $pkg . "::$var" } : ( $type eq '$' ) ? \${ $pkg . "::$var" } : ( $type eq '@' ) ? \@{ $pkg . "::$var" } : ( $type eq '%' ) ? \%{ $pkg . "::$var" } : ( $type eq '*' ) ? *{ $pkg . "::$var" } : undef; } } } ## @imethod protected jail_reval() # Build and return restricted eval command with SAFEWRAP, if activated # @return evaluation of $reval or $reval2 sub jail_reval { my ( $self, $reval ) = @_; # if nothing is returned by reval, add the return statement to # the "no safe wrap" reval my $nosw_reval = $reval; if ( $reval !~ /^sub\{return\(.*\}$/ ) { $nosw_reval =~ s/^sub\{(.*)\}$/sub{return($1)}/; } my $res; eval { $res = ( SAFEWRAP ? $self->jail->wrap_code_ref( $self->jail->reval($reval) ) : $self->jail->reval($nosw_reval) ); }; if ($@) { $self->error($@); return undef; } return $res; } 1; Logger.pm000066400000000000000000000030261325274564300351010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Mainpackage Lemonldap::NG::Handler::Main::Logger; use Lemonldap::NG::Handler::API qw( :httpCodes ); my $logLevel; # To control Lemonldap::NG logs: allows to overwrite # log level defined in server config, or to set it # if it can't be configured elsewhere (e.g. on CGIs) my $logLevels = { # To compare log levels emerg => 7, alert => 6, crit => 5, error => 4, warn => 3, notice => 2, info => 1, debug => 0, }; BEGIN { Lemonldap::NG::Handler::API->thread_share($logLevel); Lemonldap::NG::Handler::API->thread_share($logLevels); } # @method void logLevelInit # Set log level for Lemonldap::NG logs sub logLevelInit { my ( $class, $level ) = @_; $logLevel = $level || $Lemonldap::NG::Handler::API::logLevel || "debug"; $logLevel = $logLevels->{$logLevel} || 0; } ## @rmethod void lmLog(string msg, string level) # Wrapper for Apache log system # @param $msg message to log # @param $level string (emerg|alert|crit|error|warn|notice|info|debug) sub lmLog { my ( $class, $msg, $level ) = @_; return if ( $logLevels->{$level} < $logLevel ); my ( $module, $file, $line ) = caller(); if ( $level eq 'debug' ) { $file =~ s#.+/##; Lemonldap::NG::Handler::API->lmLog( "$file($line): $msg", "debug" ); } else { Lemonldap::NG::Handler::API->lmLog( "$file($line):", "debug" ) if ( $logLevel == 0 ); Lemonldap::NG::Handler::API->lmLog( "Lemonldap::NG::Handler: $msg", $level ); } } 1; Menu.pm000066400000000000000000000032001325274564300336740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler##@file # Menu ##@class # Menu # # Display a menu on protected applications package Lemonldap::NG::Handler::Menu; use strict; use Lemonldap::NG::Handler::SharedConf qw(:all); use Lemonldap::NG::Handler::API qw(:httpCodes); use base qw(Lemonldap::NG::Handler::SharedConf); use Apache2::Filter (); use constant BUFF_LEN => 8192; sub handler { my $r = pop; __PACKAGE__->run($r); } ## @rmethod Apache2::Const run(Apache2::Filter f) # Overload main run method # @param f Apache2 Filter # @return Apache2::Const::OK sub run { my $class = shift; my $f = $_[0]; unless ( $f->ctx ) { $f->r->headers_out->unset('Content-Length'); $f->ctx(1); } # CSS parameters my $background = "#ccc"; my $border = "#aaa"; my $width = "30%"; my $marginleft = "35%"; my $marginright = "35%"; my $menudiv = qq(
    ☖ Home ☒ Logout
    ); while ( $f->read( my $buffer, BUFF_LEN ) ) { $buffer =~ s/<\/body>/$menudiv<\/body>/g; $f->print($buffer); } return OK; } 1; Nginx.pm000066400000000000000000000153451325274564300340700ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler# PSGI authentication package written for Nginx. It replace # Lemonldap::NG::Handler::PSGI::Server to manage Nginx behaviour package Lemonldap::NG::Handler::Nginx; use strict; use Mouse; use Lemonldap::NG::Handler::SharedConf qw(:tsv); extends 'Lemonldap::NG::Handler::PSGI'; our $VERSION = '1.9.6'; ## @method void _run() # Return a subroutine that call _authAndTrace() and tranform redirection # response code from 302 to 401 (not authenticated) ones. This is required # because Nginx "auth_request" parameter does not accept it. The Nginx # configuration file should transform them back to 302 using: # # auth_request_set $lmlocation $upstream_http_location; # error_page 401 $lmlocation; # #@return subroutine that will be called to manage FastCGI queries sub _run { my $self = shift; return sub { my $req = $_[0]; $self->lmLog( 'New request', 'debug' ); my $res = $self->_authAndTrace( Lemonldap::NG::Common::PSGI::Request->new($req) ); # Transform 302 responses in 401 since Nginx refuse it if ( $res->[0] == 302 or $res->[0] == 303 ) { $res->[0] = 401; } return $res; }; } ## @method PSGI-Response handler() # Transform headers returned by handler main process: # each "Name: value" is transformed to: # - Headername: Name # - Headervalue: value # where is an integer starting from 1 # It can be used in Nginx virtualhost configuration: # # auth_request_set $headername1 $upstream_http_headername1; # auth_request_set $headervalue1 $upstream_http_headervalue1; # #proxy_set_header $headername1 $headervalue1; # # OR # #fastcgi_param $headername1 $headervalue1; # # LLNG::Handler::API::PSGI add also a header called Lm-Remote-User set to # whatToTrace value that can be used in Nginx virtualhost configuration to # insert user id in logs # # auth_request_set $llremoteuser $upstream_http_lm_remote_user # #@param $req Lemonldap::NG::Common::PSGI::Request sub handler { my ( $self, $req ) = @_; my $hdrs = $req->{respHeaders}; $req->{respHeaders} = {}; my @convertedHdrs = ( 'Content-Length' => 0, Cookie => ( $req->cookies // '' ) ); my $i = 0; foreach my $k ( keys %$hdrs ) { if ( $k =~ /^(?:Lm-Remote-User|Cookie)$/ ) { push @convertedHdrs, $k, $hdrs->{$k}; } else { $i++; push @convertedHdrs, "Headername$i", $k, "Headervalue$i", $hdrs->{$k}, $k, $hdrs->{$k}; } } return [ 200, \@convertedHdrs, [] ]; } 1; __END__ =pod =encoding utf8 =head1 NAME Lemonldap::NG::Handler::Nginx - Lemonldap::NG FastCGI handler for Nginx. =head1 SYNOPSIS FastCGI server: use Lemonldap::NG::Handler::Nginx; Lemonldap::NG::Handler::Nginx->run( {} ); Launch it with plackup: plackup -s FCGI --listen /tmp/llng.sock --no-default-middleware Configure Nginx: http { log_format lm_combined '$remote_addr - $lmremote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; server { server_name test1.example.com; access_log /log/file lm_combined # Internal authentication request location = /lmauth { internal; include /etc/nginx/fastcgi_params; fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock; # Drop post datas fastcgi_pass_request_body off; fastcgi_param CONTENT_LENGTH ""; # Keep original hostname fastcgi_param HOST $http_host; # Keep original request (LLNG server will received /llauth) fastcgi_param X_ORIGINAL_URI $request_uri; } # Client requests location / { # Activate access control auth_request /lmauth; # Set logs auth_request_set $lmremote_user $upstream_http_lm_remote_user; auth_request_set $lmlocation $upstream_http_location; error_page 401 $lmlocation; try_files $uri $uri/ =404; # Add as many 3-lines block as max number of headers returned by # configuration auth_request_set $headername1 $upstream_http_headername1; auth_request_set $headervalue1 $upstream_http_headervalue1; #proxy_set_header $headername1 $headervalue1; # OR #fastcgi_param $fheadername1 $headervalue1; auth_request_set $headername2 $upstream_http_headername2; auth_request_set $headervalue2 $upstream_http_headervalue2; #proxy_set_header $headername2 $headervalue2; # OR #fastcgi_param $fheadername2 $headervalue2; auth_request_set $headername3 $upstream_http_headername3; auth_request_set $headervalue3 $upstream_http_headervalue3; #proxy_set_header $headername3 $headervalue3; # OR #fastcgi_param $fheadername3 $headervalue3; } } =head1 DESCRIPTION Lemonldap::NG is a modular Web-SSO based on Apache::Session modules. It simplifies the build of a protected area with a few changes in the application. It manages both authentication and authorization and provides headers for accounting. So you can have a full AAA protection for your web space as described below. Lemonldap::NG::Handler::Nginx provides a FastCGI server that can be used by Nginx as authentication server. =head1 SEE ALSO L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012-2015 by François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Copyright (C) 2006-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Nginx/000077500000000000000000000000001325274564300335225ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/HandlerAuthBasic.pm000066400000000000000000000030021325274564300357160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Nginxpackage Lemonldap::NG::Handler::Nginx::AuthBasic; use strict; use Mouse; extends 'Lemonldap::NG::Handler::PSGI::AuthBasic'; our $VERSION = '1.9.6'; ## @method PSGI-Response handler() # Transform headers returned by handler main process: # each "Name: value" is transformed to: # - Headername: Name # - Headervalue: value # where is an integer starting from 1 # It can be used in Nginx virtualhost configuration: # # auth_request_set $headername1 $upstream_http_headername1; # auth_request_set $headervalue1 $upstream_http_headervalue1; # #proxy_set_header $headername1 $headervalue1; # # OR # #fastcgi_param $headername1 $headervalue1; # # LLNG::Handler::API::PSGI add also a header called Lm-Remote-User set to # whatToTrace value that can be used in Nginx virtualhost configuration to # insert user id in logs # # auth_request_set $llremoteuser $upstream_http_lm_remote_user # #@param $req Lemonldap::NG::Common::PSGI::Request sub handler { my ( $self, $req ) = @_; my $hdrs = $req->{respHeaders}; $req->{respHeaders} = {}; my @convertedHdrs = ( 'Content-Length' => 0, Cookie => ( $req->cookies // '' ) ); my $i = 0; foreach my $k ( keys %$hdrs ) { if ( $k =~ /^(?:Lm-Remote-User|Cookie)$/ ) { push @convertedHdrs, $k, $hdrs->{$k}; } else { $i++; push @convertedHdrs, "Headername$i", $k, "Headervalue$i", $hdrs->{$k}, $k, $hdrs->{$k}; } } return [ 200, \@convertedHdrs, [] ]; } 1; PSGI.pm000066400000000000000000000067141325274564300335470ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handlerpackage Lemonldap::NG::Handler::PSGI; use 5.10.0; use Mouse; extends 'Lemonldap::NG::Handler::PSGI::Base', 'Lemonldap::NG::Common::PSGI'; our $VERSION = '1.9.1'; sub init { my $tmp = $_[0]->Lemonldap::NG::Common::PSGI::init( $_[1] ) and $_[0]->Lemonldap::NG::Handler::PSGI::Base::init( $_[1] ); return $tmp; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::PSGI - Base library for protected PSGI applications. =head1 SYNOPSIS package My::PSGI; use base Lemonldap::NG::Handler; sub init { my ($self,$args) = @_; $self->protection('manager'); # See Lemonldap::NG::Common::PSGI for more ... # Return a boolean. If false, then error message has to be stored in # $self->error return 1; } sub handler { my ( $self, $req ) = @_; # Will be called only if authorisated my $userId = $self->userId; ... $self->sendJSONresponse(...); } This package could then be called as a CGI, using FastCGI,... #!/usr/bin/env perl use My::PSGI; use Plack::Handler::FCGI; # or Plack::Handler::CGI Plack::Handler::FCGI->new->run( My::PSGI->run() ); =head1 DESCRIPTION This package provides base class for Lemonldap::NG protected REST API. =head1 METHODS See L for logging methods, content sending,... =head2 Accessors See L for inherited accessors. =head3 protection Level of protection. It can be one of: =over =item 'none': no protection =item 'authenticate': all authenticated users are granted =item 'manager': access is granted following Lemonldap::NG rules =back =head2 Running methods =head3 user Returns user session datas. If empty (no protection), returns: { _whatToTrace => 'anonymous' } But if page is protected by server (Auth-Basic,...), it will return: { _whatToTrace => $REMOTE_USER } =head3 UserId Returns user()->{'_whatToTrace'}. =head3 group Returns a list of groups to which user belongs. =head1 SEE ALSO L, L, L, L, L, L, L, L, =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut PSGI/000077500000000000000000000000001325274564300332015ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/HandlerAuthBasic.pm000066400000000000000000000005251325274564300354040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGIpackage Lemonldap::NG::Handler::PSGI::AuthBasic; use strict; use Mouse; use Lemonldap::NG::Handler::Specific::AuthBasic; extends 'Lemonldap::NG::Handler::PSGI::Server'; sub init { my $self = shift; $self->subhandler('Lemonldap::NG::Handler::Specific::AuthBasic'); return $self->SUPER::init(@_); } our $VERSION = '1.9.6'; 1; Base.pm000066400000000000000000000133121325274564300344110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGIpackage Lemonldap::NG::Handler::PSGI::Base; use 5.10.0; use Mouse; use Lemonldap::NG::Handler::SharedConf qw(:tsv :variables :jailSharedVars); our $VERSION = '1.9.6'; has protection => ( is => 'rw', isa => 'Str' ); has rule => ( is => 'rw', isa => 'Str' ); has subhandler => ( is => 'rw', default => 'Lemonldap::NG::Handler::SharedConf' ); ## @method boolean init($args) # Initalize main handler sub init { my ( $self, $args ) = @_; eval { $self->subhandler->init($self) }; if ( $@ and not $self->{protection} eq 'none' ) { $self->error($@); return 0; } unless ( $self->subhandler->checkConf($self) or $self->{protection} eq 'none' ) { $self->error( "Unable to protect this app ($Lemonldap::NG::Common::Conf::msg)"); return 0; } eval { $self->portal( $tsv->{portal}->() ) }; my $rule = $self->{protection} || $localConfig->{protection} || ''; $self->rule( $rule eq 'authenticate' ? 1 : $rule eq 'manager' ? '' : $rule ); return 1; } ## @methodi void _run() # Check if protecton is activated then return a code ref that will launch # _authAndTrace() if protection in on or handler() else #@return code-ref sub _run { my $self = shift; # Override _run() only if protection != 'none' if ( $self->rule ne 'none' ) { $self->lmLog( 'PSGI app is protected', 'debug' ); # Handle requests # Developers, be careful: Only this part is executed at each request return sub { return $self->_authAndTrace( Lemonldap::NG::Common::PSGI::Request->new( $_[0] ) ); }; } else { $self->lmLog( 'PSGI app is not protected', 'debug' ); # Check if main handler initialization has been done unless (%$tsv) { $self->lmLog( 'Checking conf', 'debug' ); eval { $self->subhandler->checkConf() }; $self->lmLog( $@, 'error' ) if ($@); } # Handle unprotected requests return sub { my $req = Lemonldap::NG::Common::PSGI::Request->new( $_[0] ); my $res = $self->handler($req); push @{ $res->[1] }, %{ $req->{respHeaders} }; return $res; }; } } sub status { my ( $class, $args ) = @_; $args //= {}; my $self = $class->new($args); # Check if main handler initialization has been done unless (%$tsv) { return $self->abort( $self->error ) unless ( $self->init($args) ); } return sub { my $req = Lemonldap::NG::Common::PSGI::Request->new( $_[0] ); Lemonldap::NG::Handler::API->newRequest($req); $self->subhandler->status(); return [ 200, [ %{ $req->{respHeaders} } ], [ $req->{respBody} ] ]; }; } sub reload { my ( $class, $args ) = @_; $args //= {}; my $self = $class->new($args); # Check if main handler initialization has been done unless (%$tsv) { return $self->abort( $self->error ) unless ( $self->init($args) ); } return sub { my $req = Lemonldap::NG::Common::PSGI::Request->new( $_[0] ); Lemonldap::NG::Handler::API->newRequest($req); $self->subhandler->reload(); return [ 200, [ %{ $req->{respHeaders} } ], [ $req->{respBody} ] ]; }; } ## @method private PSGI-Response _authAndTrace($req) # Launch Lemonldap::NG::Handler::SharedConf::run() and then handler() if # response is 200. sub _authAndTrace { my ( $self, $req ) = @_; Lemonldap::NG::Handler::API->newRequest($req); my $res = $self->subhandler->run( $self->{rule} ); $self->portal( $tsv->{portal}->() ); $req->userData($datas) if ($datas); if ( $res < 300 ) { $self->lmLog( 'User authenticated, calling handler()', 'debug' ); $res = $self->handler($req); push @{ $res->[1] }, %{ $req->{respHeaders} }; return $res; } elsif ( $res < 400 ) { return [ $res, [ %{ $req->{respHeaders} } ], [] ]; } else { my %h = $req->{respHeaders} ? %{ $req->{respHeaders} } : (); my $s = $tsv->{portal}->() . "?lmError=$res"; $s = 'Redirection' . qq{} . '

    Please wait

    ' . qq{

    An error occurs, you're going to be redirected to $s.

    } . ''; $h{'Content-Type'} = 'text/html'; $h{'Content-Length'} = length $s; return [ $res, [%h], [$s] ]; } } ## @method hashRef user() # @return hash of user datas sub user { my ( $self, $req ) = @_; return $req->userData || { $Lemonldap::NG::Handler::Main::tsv->{whatToTrace} || _whatToTrace => 'anonymous' }; } ## @method string userId() # @return user identifier to log sub userId { my ( $self, $req ) = @_; return $req->userData->{ $Lemonldap::NG::Handler::Main::tsv->{whatToTrace} || '_whatToTrace' } || 'anonymous'; } ## @method boolean group(string group) # @param $group name of the Lemonldap::NG group to test # @return boolean : true if user is in this group sub group { my ( $self, $req, $group ) = @_; return () unless ( $req->userData->{groups} ); return ( $req->userData->{groups} =~ /\b$group\b/ ); } ## @method PSGI::Response sendError($req,$err,$code) # Add user di to $err before calling Lemonldap::NG::Common::PSGI::sendError() # @param $req Lemonldap::NG::Common::PSGI::Request # @param $err String to push # @code int HTTP error code (default to 500) sub sendError { my ( $self, $req, $err, $code ) = @_; $err ||= $req->error; $err = '[' . $self->userId($req) . "] $err"; return $self->Lemonldap::NG::Common::PSGI::sendError( $req, $err, $code ); } 1; Router.pm000066400000000000000000000075761325274564300350360ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGIpackage Lemonldap::NG::Handler::PSGI::Router; use 5.10.0; use Mouse; extends 'Lemonldap::NG::Handler::PSGI::Base', 'Lemonldap::NG::Common::PSGI::Router'; our $VERSION = '1.9.1'; sub init { my $tmp = $_[0]->Lemonldap::NG::Common::PSGI::Router::init( $_[1] ) and $_[0]->Lemonldap::NG::Handler::PSGI::Base::init( $_[1] ); return $tmp; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::PSGI::Router - Base library for protected REST APIs of Lemonldap::NG. =head1 SYNOPSIS package My::PSGI; use base Lemonldap::NG::Handler::PSGI::Router; sub init { my ($self,$args) = @_; $self->protection('manager'); # See Lemonldap::NG::Common::PSGI::Router for more # Declare REST routes (could be HTML templates or methods) $self->addRoute ( 'index.html', undef, ['GET'] ) ->addRoute ( books => { ':book' => 'booksMethod' }, ['GET', 'POST'] ); # Default route (ie: PATH_INFO == '/') $self->defaultRoute('index.html'); # Return a boolean. If false, then error message has to be stored in # $self->error return 1; } sub booksMethod { my ( $self, $req, @otherPathInfo ) = @_; # Will be called only if authorisated my $userId = $self->userId; my $book = $req->params('book'); my $method = $req->method; ... $self->sendJSONresponse(...); } This package could then be called as a CGI, using FastCGI,... #!/usr/bin/env perl use My::PSGI; use Plack::Handler::FCGI; # or Plack::Handler::CGI Plack::Handler::FCGI->new->run( My::PSGI->run() ); =head1 DESCRIPTION This package provides base class for Lemonldap::NG protected REST API. =head1 METHODS See L for logging methods, content sending,... =head2 Accessors See L for inherited accessors. =head3 protection Level of protection. It can be one of: =over =item 'none': no protection =item 'authenticate': all authenticated users are granted =item 'manager': access is granted following Lemonldap::NG rules =back =head2 Running methods =head3 user Returns user session datas. If empty (no protection), returns: { _whatToTrace => 'anonymous' } But if page is protected by server (Auth-Basic,...), it will return: { _whatToTrace => $REMOTE_USER } =head3 UserId Returns user()->{'_whatToTrace'}. =head3 group Returns a list of groups to which user belongs. =head1 SEE ALSO L, L, L, L, L, L, L, L, =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Server.pm000066400000000000000000000016341325274564300350110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/PSGIpackage Lemonldap::NG::Handler::PSGI::Server; use strict; use Mouse; use Lemonldap::NG::Handler::SharedConf qw(:tsv); extends 'Lemonldap::NG::Handler::PSGI'; ## @method void _run() # Return subroutine that add headers stored in $req->{respHeaders} in # response returned by handler() # sub _run { my ($self) = @_; return sub { my $req = Lemonldap::NG::Common::PSGI::Request->new( $_[0] ); my $res = $self->_authAndTrace($req); push @{ $res->[1] }, %{ $req->{respHeaders} }, Cookie => ( $req->{Cookie} // '' ); return $res; }; } ## @method PSGI-Response handler($req) # If PSGI is used as an authentication FastCGI only, this method will be # called for authenticated users and returns only 200. Headers are set by # Lemonldap::NG::Handler::PSGI. # @param $req Lemonldap::NG::Common::PSGI::Request sub handler { return [ 200, [ 'Content-Length', 0 ], [] ]; } 1; Reload.pm000066400000000000000000000363451325274564300342160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler# Methods run at configuration reload package Lemonldap::NG::Handler::Reload; #use Lemonldap::NG::Handler::Main qw(:all); use Lemonldap::NG::Common::Safelib; #link protected safe Safe object use constant UNPROTECT => 1; use constant SKIP => 2; use Lemonldap::NG::Handler::Main::Jail; use Lemonldap::NG::Handler::Main::Logger; use Lemonldap::NG::Handler::API qw(:httpCodes); use Lemonldap::NG::Common::Crypto; our $VERSION = '1.9.9'; ## @imethod void configReload(hashRef conf, hashRef tsv) # Given a Lemonldap::NG configuration $conf, computes values used to # handle requests and store them in a thread shared object called $tsv # # methods called by configReload, and thread shared values computed, are: # - jailInit(): # - jail # - defaultValuesInit(): # (scalars for global options) # - cda # - cookieExpiration # warning: absent from default Conf # - cookieName # - securedCookie, # - httpOnly # - whatToTrace # - customFunctions # - timeoutActivity # - timeoutActivityInterval # - useRedirectOnError # - useRedirectOnForbidden # - useSafeJail # (objects) # - cipher # Lemonldap::NG::Common::Crypto object # (hashrefs for vhost options) # - https # - port # - maintenance # - portalInit(): # - portal (functions that returns portal URL) # - locationRulesInit(): # - locationCount # - defaultCondition # - defaultProtection # - locationCondition # - locationProtection # - locationRegexp # - locationConditionText # - sessionStorageInit(): # - sessionStorageModule # - sessionStorageOptions # - sessionCacheModule # - sessionCacheOptions # - headersInit(): # - headerList # - forgeHeaders # - postUrlInit(): # - inputPostData # - outputPostData # - aliasInit(): # - vhostAlias # # The *Init() methods can be run in any order, # but jailInit must be run first because $tsv->{jail} # is used by locationRulesInit, headersInit and postUrlInit. # @param $conf reference to the configuration hash # @param $tsv reference to the thread-shared parameters conf sub configReload { my ( $class, $conf, $tsv ) = @_; Lemonldap::NG::Handler::Main::Logger->lmLog( "Loading configuration $conf->{cfgNum} for process $$", "info" ); foreach my $sub ( qw( defaultValuesInit jailInit portalInit locationRulesInit sessionStorageInit headersInit postUrlInit aliasInit ) ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Process $$ calls $sub", "debug" ); $class->$sub( $conf, $tsv ); } return 1; } ## @imethod protected void jailInit(hashRef args) # Set default values for non-customized variables # @param $args reference to the configuration hash sub jailInit { my ( $class, $conf, $tsv ) = @_; $tsv->{jail} = Lemonldap::NG::Handler::Main::Jail->new( 'jail' => $tsv->{jail}, 'useSafeJail' => $conf->{useSafeJail}, 'customFunctions' => $conf->{customFunctions} ); $tsv->{jail}->build_jail(); } ## @imethod protected void defaultValuesInit(hashRef args) # Set default values for non-customized variables # @param $args reference to the configuration hash sub defaultValuesInit { my ( $class, $conf, $tsv ) = @_; $tsv->{$_} = $conf->{$_} foreach ( qw( cda cookieExpiration cookieName customFunctions httpOnly securedCookie timeout timeoutActivity timeoutActivityInterval useRedirectOnError useRedirectOnForbidden useSafeJail whatToTrace ) ); $tsv->{cipher} = Lemonldap::NG::Common::Crypto->new( $conf->{key} ); foreach my $opt (qw(https port maintenance)) { if ( defined $conf->{$opt} ) { # Record default value in key '_' $tsv->{$opt} = { _ => $conf->{$opt} }; } # Override with vhost options if ( $conf->{vhostOptions} ) { my $name = 'vhost' . ucfirst($opt); foreach my $vhost ( keys %{ $conf->{vhostOptions} } ) { my $val = $conf->{vhostOptions}->{$vhost}->{$name}; Lemonldap::NG::Handler::Main::Logger->lmLog( "Options $opt for vhost $vhost: $val", 'debug' ); $tsv->{$opt}->{$vhost} = $val if ( $val >= 0 ); # Keep default value if $val is negative } } } return 1; } ## @imethod protected void portalInit(hashRef args) # Verify that portal variable exists. Die unless # @param $args reference to the configuration hash sub portalInit { my ( $class, $conf, $tsv ) = @_; unless ( $conf->{portal} ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "portal parameter required", 'error' ); return 0; } if ( $conf->{portal} =~ /[\$\(&\|"']/ ) { ( $tsv->{portal} ) = $class->conditionSub( $conf->{portal}, $tsv ); } else { $tsv->{portal} = sub { return $conf->{portal} }; } return 1; } ## @imethod void locationRulesInit(hashRef args) # Compile rules. # Rules are stored in $args->{locationRules}->{<virtualhost>} that contains # regexp=>test expressions where : # - regexp is used to test URIs # - test contains an expression used to grant the user # # This function creates 2 hashRef containing : # - one list of the compiled regular expressions for each virtual host # - one list of the compiled functions (compiled with conditionSub()) for each # virtual host # @param $args reference to the configuration hash sub locationRulesInit { my ( $class, $conf, $tsv ) = @_; foreach my $vhost ( keys %{ $conf->{locationRules} } ) { my $rules = $conf->{locationRules}->{$vhost}; foreach my $url ( sort keys %{$rules} ) { my ( $cond, $prot ) = $class->conditionSub( $rules->{$url}, $tsv ); unless ($cond) { $tsv->{maintenance}->{$vhost} = 1; Lemonldap::NG::Handler::Main::Logger->lmLog( "Unable to build rule '$rules->{$url}': " . $tsv->{jail}->error, 'error' ); next; } if ( $url eq 'default' ) { $tsv->{defaultCondition}->{$vhost} = $cond; $tsv->{defaultProtection}->{$vhost} = $prot; } else { push @{ $tsv->{locationCondition}->{$vhost} }, $cond; push @{ $tsv->{locationProtection}->{$vhost} }, $prot; push @{ $tsv->{locationRegexp}->{$vhost} }, qr/$url/; push @{ $tsv->{locationConditionText}->{$vhost} }, $cond =~ /^\(\?#(.*?)\)/ ? $1 : $cond =~ /^(.*?)##(.+)$/ ? $2 : $url; $tsv->{locationCount}->{$vhost}++; } } # Default policy set to 'accept' unless ( $tsv->{defaultCondition}->{$vhost} ) { $tsv->{defaultCondition}->{$vhost} = sub { 1 }; $tsv->{defaultProtection}->{$vhost} = 0; } } return 1; } ## @imethod protected void sessionStorageInit(hashRef args) # Initialize the Apache::Session::* module choosed to share user's variables # and the Cache::Cache module choosed to cache sessions # @param $args reference to the configuration hash sub sessionStorageInit { my ( $class, $conf, $tsv ) = @_; unless ( $tsv->{sessionStorageModule} = $conf->{globalStorage} ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "globalStorage required", 'error' ); return 0; } eval "use $tsv->{sessionStorageModule}"; die($@) if ($@); $tsv->{sessionStorageOptions} = $conf->{globalStorageOptions}; if ( $conf->{localSessionStorage} ) { $tsv->{sessionCacheModule} = $conf->{localSessionStorage}; $tsv->{sessionCacheOptions} = $conf->{localSessionStorageOptions}; $tsv->{sessionCacheOptions}->{default_expires_in} ||= 600; if ( $conf->{status} ) { my $params = ""; if ( $tsv->{sessionCacheModule} ) { require Data::Dumper; $params = " $tsv->{sessionCacheModule}," . Data::Dumper->new( [ $tsv->{sessionCacheOptions} ] ) ->Terse(1)->Indent(0)->Dump; # To send params on one line } print { $tsv->{statusPipe} } "RELOADCACHE$params"; } } return 1; } ## @imethod void headersInit(hashRef args) # Create the subroutines used to insert headers into the HTTP request. # @param $args reference to the configuration hash sub headersInit { my ( $class, $conf, $tsv ) = @_; # Creation of the subroutine which will generate headers foreach my $vhost ( keys %{ $conf->{exportedHeaders} } ) { my %headers = %{ $conf->{exportedHeaders}->{$vhost} }; $tsv->{headerList}->{$vhost} = [ keys %headers ]; my $sub; foreach ( keys %headers ) { my $val = $class->substitute( $headers{$_} ); $sub .= "('$_' => $val),"; } unless ( $tsv->{forgeHeaders}->{$vhost} = $tsv->{jail}->jail_reval("sub{return($sub)}") ) { $tsv->{maintenance}->{$vhost} = 1; Lemonldap::NG::Handler::Main::Logger->lmLog( "$self: Unable to forge headers: " . $tsv->{jail}->error, 'error' ); } } return 1; } ## @imethod protected void postUrlInit() # Prepare methods to post form attributes sub postUrlInit { my ( $class, $conf, $tsv ) = @_; return unless ( $conf->{post} ); # Browse all vhost foreach my $vhost ( keys %{ $conf->{post} } ) { # Browse all POST URI foreach my $url ( keys %{ $conf->{post}->{$vhost} } ) { my $d = $conf->{post}->{$vhost}->{$url}; Lemonldap::NG::Handler::Main::Logger->lmLog( "Compiling POST data for $url", 'debug' ); # Where to POST $d->{target} ||= $url; my $sub; $d->{vars} ||= []; foreach my $input ( @{ delete $d->{vars} } ) { $sub .= "'$input->[0]' => " . $class->substitute( $input->[1] ) . ","; } unless ( $tsv->{inputPostData}->{$vhost}->{ delete $d->{target} } = $tsv->{outputPostData}->{$vhost}->{$url} = $tsv->{jail}->jail_reval("sub{$sub}") ) { $tsv->{maintenance}->{$vhost} = 1; Lemonldap::NG::Handler::Main::Logger->lmLog( "$self: Unable to build post datas: " . $tsv->{jail}->error, 'error' ); } $tsv->{postFormParams}->{$vhost}->{$url} = $d; } } return 1; } ## @imethod protected codeRef conditionSub(string cond) # Returns a compiled function used to grant users (used by # locationRulesInit(). The second value returned is a non null # constant if URL is not protected (by "unprotect" or "skip"), 0 else. # @param $cond The boolean expression to use # @param $mainClass optional # @return array (ref(sub), int) sub conditionSub { my ( $class, $cond, $tsv ) = @_; my ( $OK, $NOK ) = ( sub { 1 }, sub { 0 } ); # Simple cases : accept and deny return ( $OK, 0 ) if ( $cond =~ /^accept$/i ); return ( $NOK, 0 ) if ( $cond =~ /^deny$/i ); # Cases unprotect and skip : 2nd value is 1 or 2 return ( $OK, UNPROTECT ) if ( $cond =~ /^unprotect$/i ); return ( $OK, SKIP ) if ( $cond =~ /^skip$/i ); # Case logout if ( $cond =~ /^logout(?:_sso)?(?:\s+(.*))?$/i ) { my $url = $1; return ( $url ? ( sub { $Lemonldap::NG::Handler::Main::datas->{_logout} = $url; return 0; }, 0 ) : ( sub { $Lemonldap::NG::Handler::Main::datas->{_logout} = &{ $tsv->{portal} }(); return 0; }, 0 ) ); } # Since filter exists only with Apache>=2, logout_app and logout_app_sso # targets are available only for it. # This error can also appear with Manager configured as CGI script if ( $cond =~ /^logout_app/i and MP() < 2 ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Rules logout_app and logout_app_sso require Apache>=2", 'warn' ); return ( sub { 1 }, 0 ); } # logout_app if ( $cond =~ /^logout_app(?:\s+(.*))?$/i ) { my $u = $1 || &{ $tsv->{portal} }(); eval 'use Apache2::Filter' unless ( $INC{"Apache2/Filter.pm"} ); return ( sub { $Lemonldap::NG::Handler::API::ApacheMP2::request ->add_output_filter( sub { return Lemonldap::NG::Handler::Main->redirectFilter( $u, @_ ); } ); 1; }, 0 ); } elsif ( $cond =~ /^logout_app_sso(?:\s+(.*))?$/i ) { my $u = $1 || &{ $tsv->{portal} }(); eval 'use Apache2::Filter' unless ( $INC{"Apache2/Filter.pm"} ); return ( sub { Lemonldap::NG::Handler::Main->localUnlog; $Lemonldap::NG::Handler::API::ApacheMP2::request ->add_output_filter( sub { my $r = $_[0]->r; return Lemonldap::NG::Handler::Main->redirectFilter( &{ $tsv->{portal} }() . "?url=" . Lemonldap::NG::Handler::Main->encodeUrl($u) . "&logout=1", @_ ); } ); 1; }, 0 ); } # Replace some strings in condition $cond = $class->substitute($cond); my $sub; unless ( $sub = $tsv->{jail}->jail_reval("sub{return($cond)}") ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "$self: Unable to build condition ($cond): " . $tsv->{jail}->error, 'error' ); } # Return sub and protected flag return ( $sub, 0 ); } ## @method arrayref aliasInit # @param options vhostOptions configuration item # @return arrayref of vhost and aliases sub aliasInit { my ( $class, $conf, $tsv ) = @_; foreach my $vhost ( keys %{ $conf->{vhostOptions} || {} } ) { if ( my $aliases = $conf->{vhostOptions}->{$vhost}->{vhostAliases} ) { foreach ( split /\s+/, $aliases ) { $tsv->{vhostAlias}->{$_} = $vhost; Lemonldap::NG::Handler::Main::Logger->lmLog( "Registering $_ as alias of $vhost", 'debug' ); } } } return 1; } # TODO: support wildcards in aliases sub substitute { my ( $class, $expr ) = @_; # substitute special vars, just for retro-compatibility $expr =~ s/\$date\b/&date/sg; $expr =~ s/\$vhost\b/&hostname/sg; $expr =~ s/\$ip\b/&remote_ip/sg; # substitute vars with session datas, excepts special vars $_ and $\d+ $expr =~ s/\$(?!ENV)(_*[a-zA-Z]\w*)/\$datas->{$1}/sg; return $expr; } 1; SharedConf.pm000066400000000000000000000262311325274564300350150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler## @file # Main handler. ## @class # Main handler. # All methods in handler are class methods: in ModPerl environment, handlers # are always launched without object created. # # The main method is run() who is called by Apache for each requests (using # handler() wrapper). # # The initialization process is splitted in two parts : # - init() is launched as Apache startup # - globalInit() is launched at each first request received by an Apache child # and each time a new configuration is detected package Lemonldap::NG::Handler::SharedConf; #use strict; use Lemonldap::NG::Handler::Main qw(:all); use Lemonldap::NG::Handler::Main::Logger; use Lemonldap::NG::Handler::API qw(:httpCodes); use Lemonldap::NG::Handler::Reload; use Lemonldap::NG::Common::Conf; #link protected lmConf use Lemonldap::NG::Common::Conf::Constants; #inherits use base qw(Lemonldap::NG::Handler::Main); our $VERSION = '1.9.1'; our $lmConf; # Lemonldap::NG::Common::Conf object to get config our $localConfig; # Local configuration parameters, i.e. defined # in lemonldap-ng.ini or in startup parameters our $cfgNum = 0; # Number of the loaded remote configuration our $lastCheck = 0; # Date of last configuration check (unix time) our $checkTime = 600; # Time between 2 configuration check (in seconds); # default value is 600, can be reset in local config BEGIN { Lemonldap::NG::Handler::API->thread_share($cfgNum); Lemonldap::NG::Handler::API->thread_share($lastCheck); Lemonldap::NG::Handler::API->thread_share($checkTime); Lemonldap::NG::Handler::API->thread_share($lmConf); Lemonldap::NG::Handler::API->thread_share($localConfig); *EXPORT_TAGS = *Lemonldap::NG::Handler::Main::EXPORT_TAGS; *EXPORT_OK = *Lemonldap::NG::Handler::Main::EXPORT_OK; push( @{ $EXPORT_TAGS{$_} }, qw($cfgNum $lastCheck $checkTime $lmConf $localConfig) ) foreach (qw(variables localStorage)); push @EXPORT_OK, qw($cfgNum $lastCheck $checkTime $lmConf $localConfig); } # INIT PROCESS ## @imethod void init(hashRef args) # Read parameters and build the Lemonldap::NG::Common::Conf object. # @param $args hash containing parameters sub init($$) { my ( $class, $args ) = @_; # According to doc, localStorage can be declared in $args root, # but it must be in $args->{configStorage} foreach (qw(localStorage localStorageOptions)) { $args->{configStorage}->{$_} ||= $args->{$_}; } $lmConf = Lemonldap::NG::Common::Conf->new( $args->{configStorage} ); die( "$class : unable to build configuration: " . "$Lemonldap::NG::Common::Conf::msg" ) unless ($lmConf); # Merge local configuration parameters so that params defined in # startup parameters have precedence over lemonldap-ng.ini params $localConfig = { %{ $lmConf->getLocalConf(HANDLERSECTION) }, %{$args} }; $checkTime = $localConfig->{checkTime} || $checkTime; # Few actions that must be done at server startup: # * set log level for Lemonldap::NG logs Lemonldap::NG::Handler::Main::Logger->logLevelInit( $localConfig->{logLevel} ); # * set server signature $class->serverSignatureInit unless ( $localConfig->{hideSignature} ); # * launch status process $class->statusInit($tsv) if ( $localConfig->{status} ); 1; } # @method void serverSignatureInit # adapt server signature sub serverSignatureInit { my $class = shift; Lemonldap::NG::Handler::API->setServerSignature( "Lemonldap::NG/" . $Lemonldap::NG::Handler::VERSION ) if ($Lemonldap::NG::Handler::VERSION); } ## @ifn protected void statusInit() # Launch the status process sub statusInit { my ( $class, $tsv ) = @_; return if ( $tsv->{statusPipe} and $tsv->{statusOut} ); require IO::Pipe; $statusPipe = IO::Pipe->new; $statusOut = IO::Pipe->new; if ( my $pid = fork() ) { # TODO: log new process pid $statusPipe->writer(); $statusOut->reader(); $statusPipe->autoflush(1); ( $tsv->{statusPipe}, $tsv->{statusOut} ) = ( $statusPipe, $statusOut ); } else { $statusPipe->reader(); $statusOut->writer(); my $fdin = $statusPipe->fileno; my $fdout = $statusOut->fileno; open STDIN, "<&$fdin"; open STDOUT, ">&$fdout"; my $perl_exec = ( $^X =~ /perl/ ) ? $^X : 'perl'; exec $perl_exec, '-MLemonldap::NG::Handler::Status', map( {"-I$_"} @INC ), '-e &Lemonldap::NG::Handler::Status::run()'; } } # MAIN ## @rmethod int run # Check configuration and launch Lemonldap::NG::Handler::Main::run(). # Each $checkTime, the Apache child verify if its configuration is the same # as the configuration stored in the local storage. # @param $rule optional Perl expression to grant access # @return Apache constant sub run { my $class = shift; if ( time() - $lastCheck > $checkTime ) { die("$class: No configuration found") unless ( $class->checkConf ); } if ( my $rule = shift ) { my ( $cond, $prot ) = Lemonldap::NG::Handler::Reload->conditionSub( $rule, $tsv ); return $class->SUPER::run( $cond, $prot ); } return $class->SUPER::run(); } # CONFIGURATION UPDATE ## @rmethod protected int checkConf(boolean force) # Check if configuration is up to date, and reload it if needed. # If the optional boolean $force is set to true, # * cached configuration is ignored # * and checkConf returns false if it fails to load remote config # @param $force boolean # @return true if config is up to date or if reload config succeeded sub checkConf { my ( $class, $force ) = @_; my $conf = $lmConf->getConf( { local => !$force } ); unless ( ref($conf) ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "$class: Unable to load configuration: $Lemonldap::NG::Common::Conf::msg", 'error' ); return $force ? 0 : $cfgNum ? 1 : 0; } if ( !$cfgNum or $cfgNum != $conf->{cfgNum} ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Get configuration $conf->{cfgNum} ($Lemonldap::NG::Common::Conf::msg)", 'debug' ); $lastCheck = time(); unless ( $cfgNum = $conf->{cfgNum} ) { Lemonldap::NG::Handler::Main::Logger->lmLog( 'No configuration available', 'error' ); return 0; } $conf->{$_} = $localConfig->{$_} foreach ( keys %$localConfig ); Lemonldap::NG::Handler::Reload->configReload( $conf, $tsv ); } Lemonldap::NG::Handler::Main::Logger->lmLog( "$class: configuration is up to date", 'debug' ); return $conf; } # RELOAD SYSTEM *refresh = *reload; ## @rmethod int reload # Launch checkConf() with $local=0, so remote configuration is tested. # Then build a simple HTTP response that just returns "200 OK" or # "500 Server Error". # @return Apache constant (OK or SERVER_ERROR) sub reload { my $class = shift; Lemonldap::NG::Handler::Main::Logger->lmLog( "Request for configuration reload", 'notice' ); return $class->checkConf(1) ? DONE : SERVER_ERROR; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::SharedConf - Perl extension to use dynamic configuration provide by Lemonldap::NG::Manager. =head1 SYNOPSIS package My::Package; use Lemonldap::NG::Handler::SharedConf; @ISA = qw(Lemonldap::NG::Handler::SharedConf); __PACKAGE__->init ( { localStorage => "Cache::FileCache", localStorageOptions => { 'namespace' => 'lemonldap-ng', 'default_expires_in' => 600, }, configStorage => { type => "DBI" dbiChain => "DBI:mysql:database=$database;host=$hostname;port=$port", dbiUser => "lemonldap", dbiPassword => "password", }, } ); Call your package in /apache-dir/conf/httpd.conf : PerlRequire MyFile # TOTAL PROTECTION PerlHeaderParserHandler My::Package # OR SELECTED AREA PerlHeaderParserHandler My::Package The configuration is loaded only at Apache start. Create an URI to force configuration reload, so you don't need to restart Apache at each change : # /apache-dir/conf/httpd.conf Order deny,allow Deny from all Allow from my.manager.com PerlHeaderParserHandler My::Package->refresh =head1 DESCRIPTION This library inherit from L to build a complete SSO Handler System: a central database contains the policy of your domain. People that want to access to a protected applications are redirected to the portal that run L. After reading configuration from the database and authenticating the user, it stores a key word for each application the user is granted to access to. Then the user is redirected to the application he wanted to access and the Apache handler build with L has just to verify that the keyword corresponding to the protected area is stored in the database. =head2 OVERLOADED SUBROUTINES =head3 init Like L::init() but read only localStorage related options. You may change default time between two configuration checks with the C parameter (default 600s). =head1 OPERATION Each new Apache child checks if there's a configuration stored in the local store. If not, it calls getConf to get one and store it in the local store by calling setconf. Every 600 seconds, each Apache child checks if the local stored configuration has changed and reload it if it has. When refresh subroutine is called (by http for example: see synopsis), getConf is called to get the new configuration and setconf is called to store it in the local store. =head1 SEE ALSO L, L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2005-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012 by François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Copyright (C) 2006-2016 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Specific/000077500000000000000000000000001325274564300341645ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/HandlerAuthBasic.pm000066400000000000000000000165621325274564300363770ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Specific##@file # Auth-basic authentication with Lemonldap::NG rights management ##@class # Auth-basic authentication with Lemonldap::NG rights management # This specific handler is intended to be called directly by Apache package Lemonldap::NG::Handler::Specific::AuthBasic; use Lemonldap::NG::Handler::SharedConf qw(:all); use Lemonldap::NG::Handler::API qw(:httpCodes); use Digest::MD5; use MIME::Base64; use HTTP::Headers; use SOAP::Lite; # link protected portalRequest use Lemonldap::NG::Common::Session; use base qw(Lemonldap::NG::Handler::SharedConf); our $VERSION = '1.9.1'; sub handler { my ( $class, $request ) = ( __PACKAGE__, shift ); Lemonldap::NG::Handler::API->newRequest($request); $class->run($request); } ## @rmethod Apache2::Const run(Apache2::RequestRec r) # Overload main run method # @param r Current request # @return Apache2::Const value (OK, FORBIDDEN, REDIRECT or SERVER_ERROR) sub run { my $class = shift; my $r = $_[0]; return $class->SUPER::run(); } ## @rmethod protected fetchId # Get user session id from Authorization header # Unlike usual processing, session id is computed from user creds, # so that it remains secret but handler can easily get it. # It is still changed from time to time - once a day - to prevent from # using indefinitely a session id disclosed accidentally or maliciously. # @return session id sub fetchId { my $class = shift; if ( my $creds = Lemonldap::NG::Handler::API->header_in('Authorization') ) { $creds =~ s/^Basic\s+//; my @date = localtime; my $day = $date[5] * 366 + $date[7]; return Digest::MD5::md5_hex( $creds . $day ); } else { return 0; } } ## @rmethod protected boolean retrieveSession(id) # Tries to retrieve the session whose index is id, # and if needed, ask portal to create it through a SOAP request # @return true if the session was found, false else sub retrieveSession { my ( $class, $id ) = @_; # First check if session already exists return 1 if ( $class->SUPER::retrieveSession($id) ); # Then ask portal to create it if ( $class->createSession($id) ) { return $class->SUPER::retrieveSession($id); } else { return 0; } } ## @rmethod protected boolean retrieveSession(id) # Ask portal to create it through a SOAP request # @return true if the session is created, else false sub createSession { my ( $class, $id ) = @_; # Add client IP as X-Forwarded-For IP in SOAP request my $xheader = Lemonldap::NG::Handler::API->header_in('X-Forwarded-For'); $xheader .= ", " if ($xheader); $xheader .= Lemonldap::NG::Handler::API->remote_ip; my $soapHeaders = HTTP::Headers->new( "X-Forwarded-For" => $xheader ); my $soapClient = SOAP::Lite->proxy( $tsv->{portal}->(), default_headers => $soapHeaders ) ->uri('urn:Lemonldap::NG::Common::CGI::SOAPService'); my $creds = Lemonldap::NG::Handler::API->header_in('Authorization'); $creds =~ s/^Basic\s+//; my ( $user, $pwd ) = ( decode_base64($creds) =~ /^(.*?):(.*)$/ ); Lemonldap::NG::Handler::Main::Logger->lmLog( "AuthBasic authentication for user: $user", 'debug' ); my $soapRequest = $soapClient->getCookies( $user, $pwd, $id ); # Catch SOAP errors if ( $soapRequest->fault ) { $class->abort( "SOAP request to the portal failed: " . $soapRequest->fault->{faultstring} ); } else { my $res = $soapRequest->result(); # If authentication failed, display error if ( $res->{errorCode} ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Authentication failed for $user: " . $soapClient->error( $res->{errorCode}, 'en' )->result(), 'notice' ); return 0; } else { return 1; } } } ## @rmethod protected void hideCookie() # Hide user credentials to the protected application sub hideCookie { my $class = shift; Lemonldap::NG::Handler::Main::Logger->lmLog( "removing Authorization header", 'debug' ); Lemonldap::NG::Handler::API->unset_header_in('Authorization'); } ## @rmethod protected int goToPortal(string url, string arg) # If user is asked to authenticate, return AUTH_REQUIRED, # else redirect him to the portal to display some message defined by $arg # @param $url Url requested # @param $arg optionnal GET parameters # @return Apache2::Const::REDIRECT or Apache2::Const::AUTH_REQUIRED sub goToPortal { my ( $class, $url, $arg ) = @_; if ($arg) { return $class->SUPER::goToPortal( $url, $arg ); } else { Lemonldap::NG::Handler::API->set_header_out( 'WWW-Authenticate' => 'Basic realm="LemonLDAP::NG"' ); return AUTH_REQUIRED; } } __PACKAGE__->init(); 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::AuthBasic - Perl extension to be able to authenticate users by basic web system but to use Lemonldap::NG to control authorizations. =head1 SYNOPSIS Create your own package: package My::Package; use Lemonldap::NG::Handler::AuthBasic; # IMPORTANT ORDER our @ISA = qw (Lemonldap::NG::Handler::AuthBasic); __PACKAGE__->init ( { # Local storage used for sessions and configuration localStorage => "Cache::DBFile", localStorageOptions => {...}, # How to get my configuration configStorage => { type => "DBI", dbiChain => "DBI:mysql:database=lemondb;host=$hostname", dbiUser => "lemonldap", dbiPassword => "password", } # Uncomment this to activate status module # status => 1, } ); Call your package in /conf/httpd.conf PerlRequire MyFile PerlHeaderParserHandler My::Package =head1 DESCRIPTION This library provides a way to use Lemonldap::NG to manage authorizations without using Lemonldap::NG for authentications. This can be used in conjunction with a normal Lemonldap::NG installation but to manage non-browser clients. =head1 SEE ALSO L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2010 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012-2013 by François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut SecureToken.pm000066400000000000000000000226231325274564300367560ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Specific##@file # Secure Token ##@class # Secure Token # # Create a secure token used to resolve user identity by a protected application # This specific handler is intended to be called directly by Apache package Lemonldap::NG::Handler::Specific::SecureToken; use strict; use Lemonldap::NG::Handler::SharedConf qw(:all); use Lemonldap::NG::Handler::API qw(:httpCodes); use base qw(Lemonldap::NG::Handler::SharedConf); use Cache::Memcached; use Apache::Session::Generate::MD5; use Lemonldap::NG::Handler::Main::Logger; our $VERSION = '1.9.1'; # Shared variables our $secureTokenMemcachedConnection; BEGIN { eval { require threads::shared; threads::share($secureTokenMemcachedConnection); }; } sub handler { my ( $class, $request ) = ( __PACKAGE__, shift ); Lemonldap::NG::Handler::API->newRequest($request); $class->run($request); } ## @rmethod Apache2::Const run(Apache2::RequestRec r) # Overload main run method # @param r Current request # @return Apache2::Const value (OK, FORBIDDEN, REDIRECT or SERVER_ERROR) sub run { my $class = shift; my $r = $_[0]; my $ret = $class->SUPER::run(); # Continue only if user is authorized return $ret unless ( $ret == OK ); # Get current URI my $uri = Lemonldap::NG::Handler::API->uri_with_args($r); # Catch Secure Token parameters my $localConfig = $Lemonldap::NG::Handler::SharedConf::localConfig; my $secureTokenMemcachedServers = $localConfig->{secureTokenMemcachedServers} || ['127.0.0.1:11211']; my $secureTokenExpiration = $localConfig->{secureTokenExpiration} || 60; my $secureTokenAttribute = $localConfig->{secureTokenAttribute} || 'uid'; my $secureTokenUrls = $localConfig->{'secureTokenUrls'} || ['.*']; my $secureTokenHeader = $localConfig->{secureTokenHeader} || 'Auth-Token'; my $secureTokenAllowOnError = 1 unless defined $localConfig->{'secureTokenAllowOnError'}; # Force some parameters to be array references foreach (qw/secureTokenMemcachedServers secureTokenUrls/) { no strict 'refs'; unless ( ref ${$_} eq "ARRAY" ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Transform $_ value into an array reference", 'debug' ); my @array = split( /\s+/, ${$_} ); ${$_} = \@array; } } # Display found values in debug mode Lemonldap::NG::Handler::Main::Logger->lmLog( "secureTokenMemcachedServers: @$secureTokenMemcachedServers", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "secureTokenExpiration: $secureTokenExpiration", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "secureTokenAttribute: $secureTokenAttribute", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "secureTokenUrls: @$secureTokenUrls", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "secureTokenHeader: $secureTokenHeader", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "secureTokenAllowOnError: $secureTokenAllowOnError", 'debug' ); # Return if we are not on a secure token URL my $checkurl = 0; foreach (@$secureTokenUrls) { if ( $uri =~ m#$_# ) { $checkurl = 1; Lemonldap::NG::Handler::Main::Logger->lmLog( "URL $uri detected as an Secure Token URL (rule $_)", 'debug' ); last; } } return OK unless ($checkurl); # Test Memcached connection unless ( $class->_isAlive() ) { $secureTokenMemcachedConnection = $class->_createMemcachedConnection($secureTokenMemcachedServers); } # Exit if no connection return $class->_returnError( $r, $secureTokenAllowOnError ) unless $class->_isAlive(); # Value to store my $value = $datas->{$secureTokenAttribute}; # Set token my $key = $class->_setToken( $value, $secureTokenExpiration ); return $class->_returnError( $r, $secureTokenAllowOnError ) unless $key; # Header location Lemonldap::NG::Handler::API->set_header_in( $secureTokenHeader => $key ); # Remove token eval 'use Apache2::Filter' unless ( $INC{"Apache2/Filter.pm"} ); $r->add_output_filter( sub { my $f = shift; while ( $f->read( my $buffer, 1024 ) ) { $f->print($buffer); } if ( $f->seen_eos ) { $class->_deleteToken($key); } return OK; } ); # Return OK return OK; } ## @method private Cache::Memcached _createMemcachedConnection(ArrayRef secureTokenMemcachedServers) # Create Memcached connexion # @param $secureTokenMemcachedServers Memcached servers # @return Cache::Memcached object sub _createMemcachedConnection { my ( $class, $secureTokenMemcachedServers ) = @_; # Open memcached connexion my $memd = new Cache::Memcached { 'servers' => $secureTokenMemcachedServers, 'debug' => 0, }; Lemonldap::NG::Handler::Main::Logger->lmLog( "Memcached connection created", 'debug' ); return $memd; } ## @method private string _setToken(string value, int secureTokenExpiration) # Set token value # @param value Value # @param secureTokenExpiration expiration # @return Token key sub _setToken { my ( $class, $value, $secureTokenExpiration ) = @_; my $key = Apache::Session::Generate::MD5::generate(); my $res = $secureTokenMemcachedConnection->set( $key, $value, $secureTokenExpiration ); unless ($res) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Unable to store secure token $key", 'error' ); return; } Lemonldap::NG::Handler::Main::Logger->lmLog( "Set $value in token $key", 'info' ); return $key; } ## @method private boolean _deleteToken(string key) # Delete token # @param key Key # @return result sub _deleteToken { my ( $class, $key ) = @_; my $res = $secureTokenMemcachedConnection->delete($key); unless ($res) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Unable to delete secure token $key", 'error' ); } else { Lemonldap::NG::Handler::Main::Logger->lmLog( "Token $key deleted", 'info' ); } return $res; } ## @method private boolean _isAlive() # Run a STATS command to see if Memcached connection is alive # @param connection Cache::Memcached object # @return result sub _isAlive { my ($class) = @_; return 0 unless defined $secureTokenMemcachedConnection; my $stats = $secureTokenMemcachedConnection->stats(); if ( $stats and defined $stats->{'total'} ) { my $total_c = $stats->{'total'}->{'connection_structures'}; my $total_i = $stats->{'total'}->{'total_items'}; Lemonldap::NG::Handler::Main::Logger->lmLog( "Memcached connection is alive ($total_c connections / $total_i items)", 'debug' ); return 1; } Lemonldap::NG::Handler::Main::Logger->lmLog( "Memcached connection is not alive", 'error' ); return 0; } ## @method private int _returnError(boolean secureTokenAllowOnError) # Give hand back to Apache # @param secureTokenAllowOnError # @return Apache2::Const value sub _returnError { my ( $class, $r, $secureTokenAllowOnError ) = @_; if ($secureTokenAllowOnError) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Allow request without secure token", 'debug' ); return OK; } # Redirect or Forbidden? if ( $tsv->{useRedirectOnError} ) { Lemonldap::NG::Handler::Main::Logger->lmLog( "Use redirect for error", 'debug' ); return $class->goToPortal( '/', 'lmError=500' ); } else { Lemonldap::NG::Handler::Main::Logger->lmLog( "Return error", 'debug' ); return SERVER_ERROR; } } __PACKAGE__->init( {} ); 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::SecureToken - Perl extension to generate a secure token =head1 SYNOPSIS package My::SecureToken; use Lemonldap::NG::Handler::SecureToken; @ISA = qw(Lemonldap::NG::Handler::SecureToken); __PACKAGE__->init ( { # See Lemonldap::NG::Handler for more } ); 1; =head1 DESCRIPTION Edit your vhost configuration like this: ServerName secure.example.com # Load Secure Token Handler PerlRequire __HANDLERDIR__/MyHandlerSecureToken.pm PerlHeaderParserHandler My::SecureToken =head2 EXPORT See L =head1 SEE ALSO L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2011, 2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. ZimbraPreAuth.pm000066400000000000000000000132131325274564300372370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Specific##@file # Zimbra preauthentication ##@class # Zimbra preauthentication # # It will build Zimbra preauth URL # This specific handler is intended to be called directly by Apache package Lemonldap::NG::Handler::Specific::ZimbraPreAuth; use strict; use Lemonldap::NG::Handler::SharedConf qw(:all); use Lemonldap::NG::Handler::API qw(:httpCodes); use base qw(Lemonldap::NG::Handler::SharedConf); use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex); use Lemonldap::NG::Handler::Main::Logger; our $VERSION = '1.9.1'; sub handler { my ( $class, $request ) = ( __PACKAGE__, shift ); Lemonldap::NG::Handler::API->newRequest($request); $class->run($request); } ## @rmethod Apache2::Const run(Apache2::RequestRec r) # Overload main run method # @param r Current request # @return Apache2::Const value (OK, FORBIDDEN, REDIRECT or SERVER_ERROR) sub run { my $class = shift; my $r = $_[0]; my $ret = $class->SUPER::run(); # Continue only if user is authorized return $ret unless ( $ret == OK ); # Get current URI my $uri = Lemonldap::NG::Handler::API->uri_with_args($r); # Get Zimbra parameters my $localConfig = $Lemonldap::NG::Handler::SharedConf::localConfig; my $zimbraPreAuthKey = $localConfig->{zimbraPreAuthKey}; my $zimbraAccountKey = $localConfig->{zimbraAccountKey} || 'uid'; my $zimbraBy = $localConfig->{zimbraBy} || 'id'; my $zimbraUrl = $localConfig->{zimbraUrl} || '/service/preauth'; my $zimbraSsoUrl = $localConfig->{zimbraSsoUrl} || '^/zimbrasso$'; my $timeout = $localConfig->{'timeout'} || '0'; # Display found values in debug mode Lemonldap::NG::Handler::Main::Logger->lmLog( "zimbraPreAuthKey: $zimbraPreAuthKey", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "zimbraAccountKey: $zimbraAccountKey", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "zimbraBy: $zimbraBy", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "zimbraUrl: $zimbraUrl", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "zimbraSsoUrl: $zimbraSsoUrl", 'debug' ); Lemonldap::NG::Handler::Main::Logger->lmLog( "timeout: $timeout", 'debug' ); # Return if we are not on a Zimbra SSO URI return OK unless ( $uri =~ $zimbraSsoUrl ); # Check mandatory parameters unless ($zimbraPreAuthKey) { Lemonldap::NG::Handler::Main::Logger->lmLog( "No Zimbra preauth key configured", 'error' ); return SERVER_ERROR; } # Build URL my $zimbra_url = $class->_buildZimbraPreAuthUrl( $zimbraPreAuthKey, $zimbraUrl, $datas->{$zimbraAccountKey}, $zimbraBy, $timeout ); # Header location Lemonldap::NG::Handler::API->set_header_out( 'Location' => $zimbra_url ); # Return REDIRECT return REDIRECT; } ## @method private string _buildZimbraPreAuthUrl(string key, string url, string account, string by, int timeout) # Build Zimbra PreAuth URL # @param key PreAuthKey # @param url URL # @param account User account # @param by Account type # @param timeout Timout # @return Zimbra PreAuth URL sub _buildZimbraPreAuthUrl { my ( $class, $key, $url, $account, $by, $timeout ) = @_; # Expiration time is calculated with _utime and timeout my $expires = $timeout ? ( $datas->{_utime} + $timeout ) * 1000 : $timeout; # Timestamp my $timestamp = time() * 1000; # Compute preauth value my $computed_value = hmac_sha1_hex( "$account|$by|$expires|$timestamp", $key ); Lemonldap::NG::Handler::Main::Logger->lmLog( "Compute value $account|$by|$expires|$timestamp into $computed_value", 'debug' ); # Build PreAuth URL my $zimbra_url = "$url?account=$account&by=$by×tamp=$timestamp&expires=$expires&preauth=$computed_value"; Lemonldap::NG::Handler::Main::Logger->lmLog( "Build Zimbra URL: $zimbra_url", 'debug' ); return $zimbra_url; } __PACKAGE__->init( {} ); 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::ZimbraPreAuth - Perl extension to generate Zimbra preauth URL for users authenticated by Lemonldap::NG =head1 DESCRIPTION Edit you Zimbra vhost configuration like this: PerlModule Lemonldap::NG::Handler::Specific::ZimbraPreAuth ServerName zimbra.example.com # Load Zimbra Handler PerlHeaderParserHandler Lemonldap::NG::Handler::Specific::ZimbraPreAuth =head2 EXPORT See L =head1 SEE ALSO L L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2010 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2010, 2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Status.pm000066400000000000000000000367151325274564300342740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/lib/Lemonldap/NG/Handler## @file # Status process mechanism package Lemonldap::NG::Handler::Status; use strict; use POSIX qw(setuid setgid); use Data::Dumper; our $VERSION = '1.9.1'; our $status = {}; our $activity = []; our $start = int( time / 60 ); use constant MN_COUNT => 5; our $page_title = 'Lemonldap::NG statistics'; ## @fn private hashRef portalTab() # @return Constant hash used to convert error codes into string. sub portalTab { return { -5 => 'PORTAL_IMG_NOK', -4 => 'PORTAL_IMG_OK', -3 => 'PORTAL_INFO', -2 => 'PORTAL_REDIRECT', -1 => 'PORTAL_DONE', 0 => 'PORTAL_OK', 1 => 'PORTAL_SESSIONEXPIRED', 2 => 'PORTAL_FORMEMPTY', 3 => 'PORTAL_WRONGMANAGERACCOUNT', 4 => 'PORTAL_USERNOTFOUND', 5 => 'PORTAL_BADCREDENTIALS', 6 => 'PORTAL_LDAPCONNECTFAILED', 7 => 'PORTAL_LDAPERROR', 8 => 'PORTAL_APACHESESSIONERROR', 9 => 'PORTAL_FIRSTACCESS', 10 => 'PORTAL_BADCERTIFICATE', 21 => 'PORTAL_PP_ACCOUNT_LOCKED', 22 => 'PORTAL_PP_PASSWORD_EXPIRED', 23 => 'PORTAL_CERTIFICATEREQUIRED', 24 => 'PORTAL_ERROR', 25 => 'PORTAL_PP_CHANGE_AFTER_RESET', 26 => 'PORTAL_PP_PASSWORD_MOD_NOT_ALLOWED', 27 => 'PORTAL_PP_MUST_SUPPLY_OLD_PASSWORD', 28 => 'PORTAL_PP_INSUFFICIENT_PASSWORD_QUALITY', 29 => 'PORTAL_PP_PASSWORD_TOO_SHORT', 30 => 'PORTAL_PP_PASSWORD_TOO_YOUNG', 31 => 'PORTAL_PP_PASSWORD_IN_HISTORY', 32 => 'PORTAL_PP_GRACE', 33 => 'PORTAL_PP_EXP_WARNING', 34 => 'PORTAL_PASSWORD_MISMATCH', 35 => 'PORTAL_PASSWORD_OK', 36 => 'PORTAL_NOTIFICATION', 37 => 'PORTAL_BADURL', 38 => 'PORTAL_NOSCHEME', 39 => 'PORTAL_BADOLDPASSWORD', 40 => 'PORTAL_MALFORMEDUSER', 41 => 'PORTAL_SESSIONNOTGRANTED', 42 => 'PORTAL_CONFIRM', 43 => 'PORTAL_MAILFORMEMPTY', 44 => 'PORTAL_BADMAILTOKEN', 45 => 'PORTAL_MAILERROR', 46 => 'PORTAL_MAILOK', 47 => 'PORTAL_LOGOUT_OK', 48 => 'PORTAL_SAML_ERROR', 49 => 'PORTAL_SAML_LOAD_SERVICE_ERROR', 50 => 'PORTAL_SAML_LOAD_IDP_ERROR', 51 => 'PORTAL_SAML_SSO_ERROR', 52 => 'PORTAL_SAML_UNKNOWN_ENTITY', 53 => 'PORTAL_SAML_DESTINATION_ERROR', 54 => 'PORTAL_SAML_CONDITIONS_ERROR', 55 => 'PORTAL_SAML_IDPSSOINITIATED_NOTALLOWED', 56 => 'PORTAL_SAML_SLO_ERROR', 57 => 'PORTAL_SAML_SIGNATURE_ERROR', 58 => 'PORTAL_SAML_ART_ERROR', 59 => 'PORTAL_SAML_SESSION_ERROR', 60 => 'PORTAL_SAML_LOAD_SP_ERROR', 61 => 'PORTAL_SAML_ATTR_ERROR', 62 => 'PORTAL_OPENID_EMPTY', 63 => 'PORTAL_OPENID_BADID', 64 => 'PORTAL_MISSINGREQATTR', 65 => 'PORTAL_BADPARTNER', 66 => 'PORTAL_MAILCONFIRMATION_ALREADY_SENT', 67 => 'PORTAL_PASSWORDFORMEMPTY', 68 => 'PORTAL_CAS_SERVICE_NOT_ALLOWED', 69 => 'PORTAL_MAILFIRSTACCESS', 70 => 'PORTAL_MAILNOTFOUND', 71 => 'PORTAL_PASSWORDFIRSTACCESS', 72 => 'PORTAL_MAILCONFIRMOK', 73 => 'PORTAL_RADIUSCONNECTFAILED', 74 => 'PORTAL_MUST_SUPPLY_OLD_PASSWORD', 75 => 'PORTAL_FORBIDDENIP', 76 => 'PORTAL_CAPTCHAERROR', 77 => 'PORTAL_CAPTCHAEMPTY', 78 => 'PORTAL_REGISTERFIRSTACCESS', 79 => 'PORTAL_REGISTERFORMEMPTY', 80 => 'PORTAL_REGISTERALREADYEXISTS', }; } eval { setgid( ( getgrnam( $ENV{APACHE_RUN_GROUP} ) )[2] ); setuid( ( getpwnam( $ENV{APACHE_RUN_USER} ) )[2] ); }; ## @rfn void run() # Main. # Reads requests from STDIN to : # - update counts # - display results sub run { $| = 1; my ( $lastMn, $mn, $count, $cache ); while () { $mn = int( time / 60 ) - $start + 1; # Cleaning activity array if ( $mn > $lastMn ) { for ( my $i = 0 ; $i < $mn - $lastMn ; $i++ ) { unshift @$activity, {}; delete $activity->[ MN_COUNT + 1 ]; } } $lastMn = $mn; # Activity collect if ( /^(\S+)\s+=>\s+(\S+)\s+(OK|REJECT|REDIRECT|LOGOUT|UNPROTECT|\-?\d+)$/ ) { my ( $user, $uri, $code ) = ( $1, $2, $3 ); # Portal error translation $code = portalTab->{$code} || $code if ( $code =~ /^\-?\d+$/ ); # Per user activity $status->{user}->{$user}->{$code}++; # Per uri activity $uri =~ s/^(.*?)\?.*$/$1/; $status->{uri}->{$uri}->{$code}++; $count->{uri}->{$uri}++; # Per vhost activity my ($vhost) = ( $uri =~ /^([^\/]+)/ ); $status->{vhost}->{$vhost}->{$code}++; $count->{vhost}->{$vhost}++; # Last 5 minutes activity $activity->[0]->{$code}++; } elsif (/^RELOADCACHE(?:\s+(\S+?),(\S+))?$/) { if ( my ( $cacheModule, $cacheOptions ) = ( $1, $2 ) ) { eval "use $cacheModule;" . "\$cache = new $cacheModule(\$cacheOptions);"; print STDERR "$@\n" if ($@); # TODO: use lmLog instead } else { $cache = undef; } } # Status requests # $args contains parameters passed to url status page (a=1 for example # if request is http://test.example.com/status?a=1). To be used # later... elsif (/^STATUS(?:\s+(\S+))?$/) { my $tmp = $1; my $args = {}; %$args = split( /[=&]/, $tmp ) if ($tmp); &head; my ( $c, $m, $u ); foreach my $user ( keys %{ $status->{user} } ) { my $v = $status->{user}->{$user}; $u++ unless ( $user =~ /^\d+\.\d+\.\d+\.\d+$/ ); # Total requests foreach ( keys %$v ) { $c->{$_} += $v->{$_}; } } for ( my $i = 1 ; $i < @$activity ; $i++ ) { $m->{$_} += $activity->[$i]->{$_} foreach ( keys %{ $activity->[$i] } ); } foreach ( keys %$m ) { $m->{$_} = sprintf( "%.2f", $m->{$_} / MN_COUNT ); $m->{$_} = int( $m->{$_} ) if ( $m->{$_} > 99 ); } # Raw values (Dump) if ( $args->{'dump'} ) { print "
    \n";
                    print Dumper( $status, $activity, $count );
                    print "
    \n"; &end; } else { # Total requests print "

    Total

    \n
    \n";
                    print sprintf( "%-30s : \%6d (%.02f / mn)\n",
                        $_, $c->{$_}, $c->{$_} / $mn )
                      foreach ( sort keys %$c );
                    print "\n
    \n"; # Average print "

    Average for last " . MN_COUNT . " minutes

    \n
    \n";
                    print sprintf( "%-30s : %6s / mn\n", $_, $m->{$_} )
                      foreach ( sort keys %$m );
                    print "\n
    \n"; # Users connected print "

    \nTotal users : $u\n

    \n"; # Local cache if ($cache) { my @t = $cache->get_keys( $_[1]->{namespace} ); print "

    \nLocal Cache : " . @t . " objects\n

    \n"; } # Uptime print "

    \nServer up for : " . &timeUp($mn) . "\n

    \n"; # Top uri if ( $args->{top} ) { print "
    \n"; $args->{categories} ||= 'REJECT,PORTAL_FIRSTACCESS,LOGOUT,OK'; # Vhost activity print "

    Virtual Host activity

    \n
    \n";
                        foreach (
                            sort { $count->{vhost}->{$b} <=> $count->{vhost}->{$a} }
                            keys %{ $count->{vhost} }
                          )
                        {
                            print
                              sprintf( "%-40s : %6d\n", $_, $count->{vhost}->{$_} );
                        }
                        print "\n
    \n"; # General print "

    Top used URI

    \n
    \n";
                        my $i = 0;
                        foreach (
                            sort { $count->{uri}->{$b} <=> $count->{uri}->{$a} }
                            keys %{ $count->{uri} }
                          )
                        {
                            last if ( $i == $args->{top} );
                            last unless ( $count->{uri}->{$_} );
                            $i++;
                            print
                              sprintf( "%-80s : %6d\n", $_, $count->{uri}->{$_} );
                        }
                        print "\n
    \n"; # Top by category print "\n"; foreach my $cat ( split /,/, $args->{categories} ) { print ""; } print "
    CodeTop
    $cat\n
    \n"; topByCat( $cat, $args->{top} ); print "
    \n
    \n"; } &end; } } } } ## @rfn private string timeUp(int d) # Return the time since the status process was launched (last Apache reload). # @param $d Number of minutes since start # @return Date in format "day hour minute" sub timeUp { my $d = shift; my $mn = $d % 60; $d = ( $d - $mn ) / 60; my $h = $d % 24; $d = ( $d - $h ) / 24; return "${d}d ${h}h ${mn}mn"; } ## @rfn private void topByCat(string cat,int max) # Display the "top 10" for a category (OK, REDIRECT,...). # @param $cat Category to display # @param $max Number of lines to display sub topByCat { my ( $cat, $max ) = @_; my $i = 0; print "
    \n";
        foreach (
            sort { $status->{uri}->{$b}->{$cat} <=> $status->{uri}->{$a}->{$cat} }
            keys %{ $status->{uri} }
          )
        {
            last if ( $i == $max );
            last unless ( $status->{uri}->{$_}->{$cat} );
            $i++;
            print sprintf( "%-80s : %6d\n", $_, $status->{uri}->{$_}->{$cat} );
        }
        print "
    \n"; } ## @rfn private void head() # Display head of HTML status responses. sub head { print <<"EOF"; $page_title
       

    $page_title

       
    EOF } ## @rfn private void end() # Display end of HTML status responses. sub end { print <<"EOF";
    END EOF } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Handler::Status - Perl extension to add a mod_status like system for L =head1 SYNOPSIS =head2 Create your Apache module Create your own package (example using a central configuration database): package My::Package; use Lemonldap::NG::Handler::SharedConf; @ISA = qw(Lemonldap::NG::Handler::SharedConf); __PACKAGE__->init ( { # Activate status feature status => 1, # Local storage used for sessions and configuration localStorage => "Cache::DBFile", localStorageOptions => {...}, # How to get my configuration configStorage => { type => "DBI", dbiChain => "DBI:mysql:database=lemondb;host=$hostname", dbiUser => "lemonldap", dbiPassword => "password", } # ... See Lemonldap::NG::Handler } ); =head2 Configure Apache Call your package in /apache-dir/conf/httpd.conf: # Load your package PerlRequire /My/File # Normal Protection PerlHeaderParserHandler My::Package # Status page Order deny,allow Allow from 10.1.1.0/24 Deny from all PerlHeaderParserHandler My::Package->status =head1 DESCRIPTION Lemonldap::NG::Handler::Status adds a mod_status like feature to display Lemonldap::NG::Handler activity on a protected server. It can so be used by L or directly browsed by your browser. =head1 SEE ALSO L, L, L, L =head1 AUTHOR =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Sandro Cazzaniga, Ecazzaniga.sandro@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2012 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2012 by Sandro Cazzaniga, Ecazzaniga.sandro@gmail.comE =item Copyright (C) 2012 by François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Copyright (C) 2010-2012 by Clement Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t/000077500000000000000000000000001325274564300264175ustar00rootroot0000000000000001-Lemonldap-NG-Handler-Main.t000066400000000000000000000040571325274564300334230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; package LemonldapNGHandlerMain; use strict; use warnings; use Test::More tests => 10; BEGIN { use_ok( 'Lemonldap::NG::Handler::Main', qw(:all) ) } BEGIN { use_ok( 'Lemonldap::NG::Handler::Reload', qw(:all) ) } # get a standard basic configuration in $args hashref use Cwd 'abs_path'; use File::Basename; use lib dirname( abs_path $0 ); ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $h; $h = bless {}, 'Lemonldap::NG::Handler::Main'; $ENV{SERVER_NAME} = "test1.example.com"; open STDERR, '>/dev/null'; my $conf = { 'portal' => 'http://auth.example.com/', 'globalStorage' => 'Apache::Session::File', 'post' => {}, 'locationRules' => { 'test1.example.com' => { # Basic rules 'default' => 'accept', '^/no' => 'deny', 'test' => '$groups =~ /\badmin\b/', # Bad ordered rules '^/a/a' => 'deny', '^/a' => 'accept', # Good ordered rules '(?#1 first)^/b/a' => 'deny', '(?#2 second)^/b' => 'accept', }, }, }; # includes # - defaultValuesInit # - portalInit # - locationRulesInit # - globalStorageInit # - headerListInit # - forgeHeadersInit # - postUrlInit ok( Lemonldap::NG::Handler::Reload->configReload( $conf, $Lemonldap::NG::Handler::Main::tsv ) ); ok( &{ $tsv->{portal} }() eq 'http://auth.example.com/', 'portal' ); ok( $h->grant('/s'), 'basic rule "accept"' ); ok( !$h->grant('/no'), 'basic rule "deny"' ); ok( $h->grant('/a/a'), 'bad ordered rule 1/2' ); ok( $h->grant('/a'), 'bad ordered rule 2/2' ); ok( !$h->grant('/b/a'), 'good ordered rule 1/2' ); ok( $h->grant('/b'), 'good ordered rule 2/2' ); 02-Lemonldap-NG-Handler-Main-Portal.t000066400000000000000000000027651325274564300346670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t## Before `make install' is performed this script should be runnable with ## `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler.t' # ########################## # ## change 'tests => 1' to 'tests => last_test_to_print'; no warnings; use Test::More; #qw(no_plan) my $numTests = 1; plan tests => $numTests; # get a standard basic configuration in $args hashref use Cwd 'abs_path'; use File::Basename; use lib dirname( abs_path $0 ); open STDERR, '>/dev/null'; ########################## # ## Insert your test code below, the Test::More module is use()ed here so read ## its man page ( perldoc Test::More ) for help writing this test script. use_ok( 'Lemonldap::NG::Handler::Main', ':all' ); #if ( $numTests == 2 ) { # my $h; # $h = bless {}, 'Lemonldap::NG::Handler::Main'; # # # Portal value with $vhost # # $vhost -> test.example.com # # # Create a fake Apache2::RequestRec # my $mock = Test::MockObject->new(); # $mock->fake_module( # 'Apache2::RequestRec' => new => # sub { return bless {}, 'Apache2::RequestRec' }, # hostname => sub { 'test.example.com' }, # ); # our $apacheRequest = Apache2::RequestRec->new(); # # my $portal = '"http://".$vhost."/portal"'; # # my $args = { # 'portal' => "$portal", # 'globalStorage' => 'Apache::Session::File', # 'post' => {}, # }; # $h->globalInit($args); # # ok( ( $h->portal() eq 'http://test.example.com/portal' ), # 'Portal value with $vhost' ); #} 05-Lemonldap-NG-Handler-Reload.t000066400000000000000000000041321325274564300337430ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-Vhost.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; package My::Package; use Test::More tests => 6; BEGIN { use_ok('Lemonldap::NG::Handler::Main'); use_ok('Lemonldap::NG::Handler::Reload'); } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. my $globalinit; open STDERR, '>/dev/null'; my $tsv = {}; ok( Lemonldap::NG::Handler::Reload->jailInit( { https => 0, port => 0, maintenance => 0, vhostOptions => { www1 => { vhostHttps => 1, vhostPort => 443, vhostMaintenance => 1, vhostAliases => 'www2 www3', } }, }, $tsv ), 'defaultValuesInit' ); ok( Lemonldap::NG::Handler::Reload->defaultValuesInit( { https => 0, port => 0, maintenance => 0, vhostOptions => { www1 => { vhostHttps => 1, vhostPort => 443, vhostMaintenance => 1, vhostAliases => 'www2 www3', } }, }, $tsv ), 'defaultValuesInit' ); ok( Lemonldap::NG::Handler::Reload->locationRulesInit( { 'locationRules' => { 'www1' => { 'default' => 'accept', '^/no' => 'deny', 'test' => '$groups =~ /\badmin\b/', } } }, $tsv ), 'locationRulesInit' ); ok( Lemonldap::NG::Handler::Reload->headersInit( { exportedHeaders => { www1 => { Auth => '$uid', } } }, $tsv ), 'forgeHeadersInit' ); 10-Lemonldap-NG-Handler-SharedConf.t000066400000000000000000000047161325274564300345550ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-SharedConf.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More; use Cwd 'abs_path'; use File::Basename; use File::Temp; my $numTests = 4; unless ( eval { require Test::MockObject } ) { $numTests = 1; warn "Warning: Test::MockObject is needed to run deeper tests\n"; } plan tests => $numTests; my $ini = File::Temp->new(); my $dir = dirname( abs_path($0) ); my $tmp = File::Temp::tempdir(); print $ini "[all] [configuration] type=File dirName=$dir localStorage=Cache::FileCache localStorageOptions={ \\ 'namespace' => 'lemonldap-ng-config',\\ 'default_expires_in' => 600, \\ 'directory_umask' => '007', \\ 'cache_root' => '$tmp', \\ 'cache_depth' => 0, \\ } "; $ini->flush(); use Env qw(LLNG_DEFAULTCONFFILE); $LLNG_DEFAULTCONFFILE = $ini->filename; #open STDERR, '>/dev/null'; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. $Lemonldap::NG::Handler::API::logLevel = 'error'; use_ok('Lemonldap::NG::Handler'); # we don't want to use all Apache::* stuff $ENV{MOD_PERL} = undef; $ENV{MOD_PERL_API_VERSION} = 2; # Create a fake Apache2::RequestRec my $mock = Test::MockObject->new(); my $ret; $mock->fake_module( 'Lemonldap::NG::Handler::API', newRequest => sub { 1 }, header_in => sub { "" }, hostname => sub { 'test1.example.com' }, is_initial_req => sub { '1' }, remote_ip => sub { '127.0.0.1' }, args => sub { undef }, unparsed_uri => sub { '/' }, uri => sub { '/' }, uri_with_args => sub { '/' }, get_server_port => sub { '80' }, set_header_out => sub { $ret = join( ':', $_[1], $_[2], ); }, ); our $apacheRequest; my $h = bless {}, 'Lemonldap::NG::Handler'; ok( $h->init(), 'Initialize handler' ); ok( $h->run($apacheRequest), 'run Handler with basic configuration and no cookie' ); ok( "$ret" eq 'Location:http://auth.example.com/?url=aHR0cDovL3Rlc3QxLmV4YW1wbGUuY29tLw==', 'testing redirection URL from previous run' ) or print STDERR "Got: $ret\n"; $LLNG_DEFAULTCONFFILE = undef; 12-Lemonldap-NG-Handler-Jail.t000066400000000000000000000030651325274564300334160ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-SharedConf.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 6; BEGIN { use_ok('Lemonldap::NG::Handler::Main::Jail') } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. ok( my $jail = Lemonldap::NG::Handler::Main::Jail->new( 'jail' => undef, 'useSafeJail' => 1, 'customFunctions' => undef ), 'new jail object' ); $jail->build_jail(); my $sub1 = "sub { return( basic('login','password') ) }"; my $basic = $jail->jail_reval($sub1); ok( ( !defined($basic) or defined($basic) ), 'basic extended function can be undef with recent Safe Jail' ); my $sub2 = "sub { return ( encode_base64('test') ) }"; my $encode_base64 = $jail->jail_reval($sub2); ok( ( !defined($encode_base64) or defined($encode_base64) ), 'encode_base64 function can be undef with recent Safe Jail' ); my $sub3 = "sub { return(checkDate('20000000000000','21000000000000')) }"; my $checkDate = $jail->jail_reval($sub3); ok( ( !defined($checkDate) or defined($checkDate) ), 'checkDate extended function can be undef with recent Safe Jail' ); # basic and encode_base64 are not supported by safe jail, but checkDate is ok( &$checkDate == "1", 'checkDate extended function working with Safe Jail' ); 13-Lemonldap-NG-Handler-Fake-Safe.t000066400000000000000000000026521325274564300342630ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-SharedConf.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 6; BEGIN { use_ok('Lemonldap::NG::Handler::Main::Jail') } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. ok( my $jail = Lemonldap::NG::Handler::Main::Jail->new( 'jail' => undef, 'useSafeJail' => 0, 'customFunctions' => undef ), 'new fake jail object' ); $jail->build_jail(); my $sub1 = "sub { return( basic('login','password') ) }"; my $basic; ok( $basic = $jail->jail_reval($sub1), 'Compilation succeed' ) or print STDERR $jail->error . "\n"; like( &$basic, '/^Basic bG9naW46cGFzc3dvcmQ=$/', 'basic extended function working without Safe Jail' ); my $sub2 = "sub { return ( encode_base64('test') ) }"; my $encode_base64 = $jail->jail_reval($sub2); like( &$encode_base64, '/^dGVzdA==$/', 'encode_base64 extended function working without Safe Jail' ); my $sub3 = "sub { return(checkDate('20000000000000','21000000000000')) }"; my $checkDate = $jail->jail_reval($sub3); ok( &$checkDate == "1", 'checkDate extended function working without Safe Jail' ); 30-Lemonldap-NG-Handler-CGI.t000066400000000000000000000025411325274564300331370ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-CGI.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 1; use Cwd 'abs_path'; use File::Basename; use File::Temp; my $ini = File::Temp->new(); my $dir = dirname( abs_path($0) ); print $ini "[all] [configuration] type=File dirName=$dir "; $ini->flush(); use Env qw(LLNG_DEFAULTCONFFILE); $LLNG_DEFAULTCONFFILE = $ini->filename; use_ok('Lemonldap::NG::Handler::CGI'); $LLNG_DEFAULTCONFFILE = undef; # sub Lemonldap::NG::Handler::CGI::lmLog { } ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. __END__ my $p; # CGI Environment $ENV{SCRIPT_NAME} = '/test.pl'; $ENV{SCRIPT_FILENAME} = '/tmp/test.pl'; $ENV{REQUEST_METHOD} = 'GET'; $ENV{REQUEST_URI} = '/'; $ENV{QUERY_STRING} = ''; ok( $p = Lemonldap::NG::Handler::CGI->new( { configStorage => { confFile => 'undefined.xx', }, https => 0, portal => 'http://auth.example.com/', globalStorage => 'Apache::Session::File', } ), 'Portal object' ); 50-Lemonldap-NG-Handler-SecureToken.t000066400000000000000000000020151325274564300347620ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-Proxy.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 1; use Cwd 'abs_path'; use File::Basename; use File::Temp; my $ini = File::Temp->new(); my $dir = dirname( abs_path($0) ); print $ini "[all] [configuration] type=File dirName=$dir "; $ini->flush(); use Env qw(LLNG_DEFAULTCONFFILE); $LLNG_DEFAULTCONFFILE = $ini->filename; open STDERR, '>/dev/null'; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. SKIP: { eval { require Cache::Memcached }; skip "Cache::Memcached is not installed, so Lemonldap::NG::Handler::Specific::SecureToken will not be useable", 1 if ($@); use_ok('Lemonldap::NG::Handler::Specific::SecureToken'); } $LLNG_DEFAULTCONFFILE = undef; 51-Lemonldap-NG-Handler-Zimbra.t000066400000000000000000000020101325274564300337530ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-Proxy.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 1; use Cwd 'abs_path'; use File::Basename; use File::Temp; my $ini = File::Temp->new(); my $dir = dirname( abs_path($0) ); print $ini "[all] [configuration] type=File dirName=$dir "; $ini->flush(); use Env qw(LLNG_DEFAULTCONFFILE); $LLNG_DEFAULTCONFFILE = $ini->filename; open STDERR, '>/dev/null'; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. SKIP: { eval { require Digest::HMAC_SHA1 }; skip "Digest::HMAC_SHA1 is not installed, so Lemonldap::NG::Handler::ZimbraPreAuth will not be useable", 1 if ($@); use_ok('Lemonldap::NG::Handler::Specific::ZimbraPreAuth'); } $LLNG_DEFAULTCONFFILE = undef; 52-Lemonldap-NG-Handler-AuthBasic.t000066400000000000000000000015121325274564300344010ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl Lemonldap-NG-Handler-Proxy.t' ######################### # change 'tests => 1' to 'tests => last_test_to_print'; use Test::More tests => 1; use Cwd 'abs_path'; use File::Basename; use File::Temp; my $ini = File::Temp->new(); my $dir = dirname( abs_path($0) ); print $ini "[all] [configuration] type=File dirName=$dir "; $ini->flush(); use Env qw(LLNG_DEFAULTCONFFILE); $LLNG_DEFAULTCONFFILE = $ini->filename; open STDERR, '>/dev/null'; ######################### # Insert your test code below, the Test::More module is use()ed here so read # its man page ( perldoc Test::More ) for help writing this test script. use_ok('Lemonldap::NG::Handler::Specific::AuthBasic'); $LLNG_DEFAULTCONFFILE = undef; 60-Lemonldap-NG-Handler-PSGI.t000066400000000000000000000034641325274564300333070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/tuse Test::More; use JSON; use Data::Dumper; use MIME::Base64; require 't/test-psgi-lib.pm'; init('Lemonldap::NG::Handler::PSGI'); my $res; # Unauthentified query ok( $res = $client->_get('/'), 'Unauthentified query' ); ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' ); ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); my %h = @{ $res->[1] }; ok( $h{Location} eq 'http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ), 'Redirection points to portal' ) or explain( \%h, 'Location => http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ) ); count(4); # Authentified queries # -------------------- # Authorizated query ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res, 200 ); count(2); # Denied query ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ), 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); count(2); # Bad cookie ok( $res = $client->_get( '/deny', undef, 'manager.example.com', 'lemonldap=e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545' ), 'Bad cookie' ); ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); count(2); done_testing( count() ); clean(); sub Lemonldap::NG::Handler::PSGI::handler { my ( $self, $req ) = @_; ok( $req->{HTTP_AUTH_USER} eq 'dwho', 'Header is given to app' ) or explain( $req->{HTTP_REMOTE_USER}, 'dwho' ); count(1); return [ 200, [ 'Content-Type', 'text/plain' ], ['Hello'] ]; } 61-Lemonldap-NG-Handler-PSGI-Server.t000066400000000000000000000033131325274564300345450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/tuse Test::More; use JSON; use Data::Dumper; use MIME::Base64; require 't/test-psgi-lib.pm'; init('Lemonldap::NG::Handler::PSGI::Server'); my $res; # Unauthentified query ok( $res = $client->_get('/'), 'Unauthentified query' ); ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' ); ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); my %h = @{ $res->[1] }; ok( $h{Location} eq 'http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ), 'Redirection points to portal' ) or explain( \%h, 'Location => http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ) ); count(4); # Authentified queries # -------------------- # Authorizated query ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); count(2); # Check headers %h = @{ $res->[1] }; ok( $h{'Auth-User'} eq 'dwho', 'Header Auth-User is set to "dwho"' ) or explain( $h, 'Auth-User => "dwho"' ); count(1); # Denied query ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ), 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); count(2); # Bad cookie ok( $res = $client->_get( '/deny', undef, 'manager.example.com', 'lemonldap=e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545' ), 'Bad cookie' ); ok( $res->[0] == 302, 'Code is 302' ) or explain( $res->[0], 302 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); count(2); done_testing( count() ); clean(); 62-Lemonldap-NG-Handler-Nginx.t000066400000000000000000000035061325274564300336270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/tuse Test::More; use JSON; use Data::Dumper; use MIME::Base64; require 't/test-psgi-lib.pm'; init('Lemonldap::NG::Handler::Nginx'); my $res; # Unauthentified query ok( $res = $client->_get('/'), 'Unauthentified query' ); ok( ref($res) eq 'ARRAY', 'Response is an array' ) or explain( $res, 'array' ); ok( $res->[0] == 401, 'Code is 401' ) or explain( $res->[0], 401 ); my %h = @{ $res->[1] }; ok( $h{Location} eq 'http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ), 'Redirection points to portal' ) or explain( \%h, 'Location => http://auth.example.com/?url=' . encode_base64( 'http://test1.example.com/', '' ) ); count(4); # Authentified queries # -------------------- # Authorizated query ok( $res = $client->_get( '/', undef, undef, "lemonldap=$sessionId" ), 'Authentified query' ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); count(2); # Check headers %h = @{ $res->[1] }; ok( $h{'Headername1'} eq 'Auth-User', 'Headername1 is set to "Auth-User"' ) or explain( \%h, 'Headername1 => "Auth-User"' ); ok( $h{'Headervalue1'} eq 'dwho', 'Headervalue1 is set to "dwho"' ) or explain( \%h, 'Headervalue1 => "dwho"' ); count(2); # Denied query ok( $res = $client->_get( '/deny', undef, undef, "lemonldap=$sessionId" ), 'Denied query' ); ok( $res->[0] == 403, 'Code is 403' ) or explain( $res->[0], 403 ); count(2); # Bad cookie ok( $res = $client->_get( '/deny', undef, 'manager.example.com', 'lemonldap=e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545' ), 'Bad cookie' ); ok( $res->[0] == 401, 'Code is 401' ) or explain( $res->[0], 401 ); unlink( 't/sessions/lock/Apache-Session-e5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock' ); count(2); done_testing( count() ); clean(); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t/99-pod.t000066400000000000000000000002011325274564300276160ustar00rootroot00000000000000use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t/lmConf-1.js000066400000000000000000000027671325274564300303450ustar00rootroot00000000000000{ "authentication": "Demo", "cfgAuthor": "The LemonLDAP::NG team", "cfgAuthorIP": "127.0.0.1", "cfgDate": 1428138808, "cfgLog": "Handler test conf", "cfgNum": "1", "cookieName": "lemonldap", "demoExportedVars": { "cn": "cn", "mail": "mail", "uid": "uid" }, "domain": "example.com", "exportedHeaders": { "test1.example.com": { "Auth-User": "$uid" }, "test2.example.com": { "Auth-User": "$uid" } }, "exportedVars": { "UA": "HTTP_USER_AGENT" }, "globalStorage": "Apache::Session::File", "globalStorageOptions": { "Directory": "t/sessions", "LockDirectory": "t/sessions/lock", "generateModule": "Lemonldap::NG::Common::Apache::Session::Generate::SHA256" }, "groups": {}, "key": "qwertyui", "locationRules": { "manager.example.com": { "(?#Configuration)^/(manager\\.html|conf/)": "$uid eq \"dwho\"", "(?#Notifications)^/notifications": "$uid eq \"dwho\" or $uid eq \"rtyler\"", "(?#Sessions)^/sessions": "$uid eq \"dwho\" or $uid eq \"rtyler\"", "default": "$uid eq \"dwho\"" }, "test1.example.com": { "^/logout": "logout_sso", "^/deny": "deny", "default": "accept" }, "test2.example.com": { "^/logout": "logout_sso", "default": "accept" } }, "macros": { "_whatToTrace": "$_auth eq 'SAML' ? \"$_user\\@$_idpConfKey\" : \"$_user\"" }, "portal": "http://auth.example.com/", "reloadUrls": {}, "userDB": "Demo", "whatToTrace": "_whatToTrace" } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t/sessions/000077500000000000000000000000001325274564300302655ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t/sessions/lock/000077500000000000000000000000001325274564300312155ustar00rootroot00000000000000Apache-Session-f5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock000066400000000000000000000000001325274564300461760ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t/sessions/locktest-psgi-lib.pm000066400000000000000000000063321325274564300313650ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-handler/t# Base library for tests use strict; use Data::Dumper; use 5.10.0; use POSIX 'strftime'; use_ok('Lemonldap::NG::Common::PSGI::Cli::Lib'); our $client; our $count = 1; $Data::Dumper::Deparse = 1; my $module; our $sessionId = 'f5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545'; our $file = "t/sessions/$sessionId"; sub init { $module = shift; use_ok($module); ok( $client = Lemonldap::NG::Handler::PSGI::Cli::Lib->new(), 'Client object' ); ok( $client->app, 'App object' ) or explain( $client, '->app...' ); count(3); open F, ">$file" or die $!; my $now = time; my $ts = strftime "%Y%m%d%H%M%S", localtime; print F '{"updateTime":"' . $ts . '","_timezone":"1","_session_kind":"SSO","_passwordDB":"Demo","startTime":"' . $ts . '","ipAddr":"127.0.0.1","UA":"Mozilla/5.0 (X11; VAX4000; rv:43.0) Gecko/20100101 Firefox/143.0 Iceweasel/143.0.1","_user":"dwho","_userDB":"Demo","_lastAuthnUTime":' . $now . ',"uid":"dwho","_issuerDB":"Null","_session_id":"f5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545","authenticationLevel":1,"_whatToTrace":"dwho","_auth":"Demo","_utime":' . $now . ',"loginHistory":{"successLogin":[{"ipAddr":"127.0.0.1","_utime":' . $now . '}]},"cn":"Doctor Who","mail":"dwho@badwolf.org"}'; close F; } sub client { return $client; } sub count { my $c = shift; $count += $c if ($c); return $count; } sub explain { my ( $get, $ref ) = @_; $get = Dumper($get) if ( ref $get ); print STDERR "Expect $ref, get $get\n"; } sub clean { unlink $file; } package Lemonldap::NG::Handler::PSGI::Cli::Lib; use Mouse; extends 'Lemonldap::NG::Common::PSGI::Cli::Lib'; has app => ( is => 'ro', isa => 'CodeRef', builder => sub { return $module->run( { configStorage => { type => 'File', dirName => 't' }, localSessionStorage => '', logLevel => 'warn', cookieName => 'lemonldap', securedCookie => 0, https => 0, } ); } ); sub _get { my ( $self, $path, $query, $host, $cookie ) = @_; $query //= ''; $host ||= 'test1.example.com'; return $self->app->( { 'HTTP_ACCEPT' => 'text/html', 'SCRIPT_NAME' => 'lmAuth', 'SERVER_NAME' => '127.0.0.1', 'QUERY_STRING' => $query, 'HTTP_CACHE_CONTROL' => 'max-age=0', 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', 'PATH_INFO' => $path, 'REQUEST_METHOD' => 'GET', 'REQUEST_URI' => '/lmauth', 'X_ORIGINAL_URI' => $path . ( $query ? "?$query" : '' ), 'SERVER_PORT' => '80', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_USER_AGENT' => 'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox', 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_HOST' => $host, ( $cookie ? ( HTTP_COOKIE => $cookie ) : ( HTTP_COOKIE => '' ) ) } ); } 1; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/000077500000000000000000000000001325274564300261515ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/.bowerrc000066400000000000000000000000751325274564300276170ustar00rootroot00000000000000{ "directory": "site/static/bwr", "interactive": false } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/Changes000066400000000000000000000071021325274564300274440ustar00rootroot00000000000000Revision history for Perl extension Lemonldap::NG::Manager. See https://lemonldap-ng.org/documentation/latest/upgrade for newer revisions 0.9 Mon jun 29 11:58:54 2009 - Doxygen documentation - Sessions.pm now use $whatToTrace parameter instead of $uid - SOAP server is now obsolete (replaced by portal) 0.87 Tue dec 25 9:01:21 2008 - New module Sessions.pm and example - Configuration modules migrates to Lemonldap::NG::Common 0.86 Mon aug 25 22:02:23 2008 - UTF8 in _i18n.pm - change in default values 0.85 Tue may 6 7:02:34 2008 - CSS update 0.84 Mon apr 7 14:55:45 2008 - Javascript update (Closes: #308775 / forge.objectweb.org) 0.83 Fri feb 8 17:45:34 2008 - bugs in SOAP server - sessions timeout is now included in the manager 0.82 Sat jul 21 15:21:32 2007 - TLS support in LDAP - Help for new logout system 0.8 Sat jun 23 21:54:27 2007 - New feature: syntax errors are now displayed in the manager interface 0.72 Tue jun 20 22:00:15 2007 - Javascript rewrite 0.71 Mon jun 19 22:22:33 2007 - Bug in javascript : a 'z' is added in regexp 0.7 Tue jun 12 22:20:54 2007 - Changing storage format due to a bug in Storable module 0.66 Tue May 15 19:53:40 2007 - Little bug correction: '-' is authorized in domain names 0.65 Sun May 6 16:15:49 2007 - SOAP: HTTP basic authentication and little bug correction in 'sessions' mode - More tests in conf 0.64 Sun Apr 29 16:18:31 2007 - File permissions fix to 0640 in File.pm - Multiple configuration in the same server is now possible - LDAP help in english - LDAPS documentation - Javascript control (an XML id can not start with a number) - whatToTrace parameter in configuration interface - Fix tree bug when an hash ref is not defined - More tests - Next and previous conf 0.61 Thu Mar 29 21:45:44 2007 - configuration is checked before saving 0.6 Sat Mar 17 22:13:08 2007 - New feature : * restricted version of Manager. Only choosen virtual hosts are displayed 0.512 Tue Mar 13 7:57:30 2007 - New feature in Manager : "Delete VHost" button (Closes: #306761 / forge.objectweb.org) 0.511 Sun Mar 11 8:24:32 2007 - Bug correction: lock does not work with File.pm (Closes: #306760 / forge.objectweb.org) 0.51 Fri Mar 9 7:16:42 2007 - Bug corrections issued from test in real life. - More help in english 0.45 Sat Mar 3 9:26:08 2007 - New error system when uploading conf - Verification if configuration has changed before saving - New feature: "apply configuration" 0.44 Sat Feb 24 16:32:34 2007 - Adding SOAP support to access to configuration 0.43 Sun Jan 28 19:10:24 2007 - Little correction on patch 0.41->0.42 0.42 Wed Jan 17 20:35:43 2007 - Correction issued from the first test in real life: * Close #306573 / forge.objectweb.org * Close #306574 / forge.objectweb.org 0.41 Sun Jan 14 14:04:05 2007 - I18n in javascripts (close #306564 / forge.objectweb.org) - Increase number of help chapter (General Parameters) 0.4 Sat Jan 13 20:23:18 2007 - New configuration parameter named 'macros'. It can be used to declare new attributes (exported vars) calculated with Perl expressions on variables. TODO: documentation (but french help is done) 0.3 Thu Jan 4 9:22:34 2007 - Help system skeleton and help in french 0.2 Sun Dec 31 16:40:04 2006 - Localization (fr and en) 0.1 - Bug corrections 0.03 Sun Dec 16 12:29:12 2006 - Autoload does not work when inherits from CGI. Temporarly disabled 0.02 Mon Dec 4 7:57:25 2006 - little doc 0.01 Sun Dec 3 13:23:58 2006 - original version; created by h2xs 1.23 with options -Xn Lemonldap::NG::Manager lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/KINEMATIC.md000066400000000000000000000025101325274564300277750ustar00rootroot00000000000000# Lemonldap::NG::Manager kinematic ## Initialization PSGI file | +-> Common::PSGI::run() (Manager inheritance) | +-> Common::PSGI::new() unless(defined $self) | +-> Manager::init() | | | +-> Handler::PSGI::Router::init() | | | | | +-> Common::PSGI::init() | | | | | +-> Handler::PSGI::Base::init() | | | +-> Manager::::addRoutes() | (module can be one of `Conf`, `Sessions`, `Notifications`) | | | +-> Common::PSGI::Router::addRoute() | +-> Handler::PSGI::Base::_run() | +-> if protected: Handler::PSGI::Base::_authAndTrace() _Common::PSGI::run()_ returns a subroutine ## HTTP responses PSGI system launch the previous sub returned by Handler::PSGI::Base::\_run() sub | +-> if protection is set: | Lemonldap::NG::Handler::SharedConf::run() | +-> Common::PSGI::Router::handler ( Lemonldap::NG::Common::PSGI::Request->new() ) | +-> Common::PSGI::Router::followPath() | +-> Launch the corresponding Manager:: subroutine declared with addRoutes() lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/MANIFEST000066400000000000000000000135321325274564300273060ustar00rootroot00000000000000.bowerrc bower.json Changes eg/manager-server.cgi eg/manager-server.fcgi eg/manager-server.psgi KINEMATIC.md lib/Lemonldap/NG/Manager.pm lib/Lemonldap/NG/Manager/Attributes.pm lib/Lemonldap/NG/Manager/Build.pm lib/Lemonldap/NG/Manager/Build/Attributes.pm lib/Lemonldap/NG/Manager/Build/CTrees.pm lib/Lemonldap/NG/Manager/Build/Tree.pm lib/Lemonldap/NG/Manager/Cli.pm lib/Lemonldap/NG/Manager/Cli/Lib.pm lib/Lemonldap/NG/Manager/Conf.pm lib/Lemonldap/NG/Manager/Conf/Parser.pm lib/Lemonldap/NG/Manager/Conf/Tests.pm lib/Lemonldap/NG/Manager/Conf/Zero.pm lib/Lemonldap/NG/Manager/Constants.pm lib/Lemonldap/NG/Manager/Lib.pm lib/Lemonldap/NG/Manager/Notifications.pm lib/Lemonldap/NG/Manager/Sessions.pm Makefile.PL MANIFEST This list of files META.yml README REST-API.md scripts/lmConfigEditor scripts/testConfBackend.pl site/static/bwr/angular-animate/angular-animate.js site/static/bwr/angular-animate/angular-animate.min.js site/static/bwr/angular-animate/angular-animate.min.js.map site/static/bwr/angular-bootstrap/ui-bootstrap-tpls.js site/static/bwr/angular-bootstrap/ui-bootstrap-tpls.min.js site/static/bwr/angular-cookies/angular-cookies.js site/static/bwr/angular-cookies/angular-cookies.min.js site/static/bwr/angular-cookies/angular-cookies.min.js.map site/static/bwr/angular-ui-tree/dist/angular-ui-tree.js site/static/bwr/angular-ui-tree/dist/angular-ui-tree.min.css site/static/bwr/angular-ui-tree/dist/angular-ui-tree.min.js site/static/bwr/angular/angular-csp.css site/static/bwr/angular/angular-csp.min.css site/static/bwr/angular/angular.js site/static/bwr/angular/angular.min.js site/static/bwr/angular/angular.min.js.map site/static/bwr/bootstrap/dist/css/bootstrap-theme.css site/static/bwr/bootstrap/dist/css/bootstrap-theme.css.map site/static/bwr/bootstrap/dist/css/bootstrap-theme.min.css site/static/bwr/bootstrap/dist/css/bootstrap.css site/static/bwr/bootstrap/dist/css/bootstrap.min.css site/static/bwr/bootstrap/dist/css/bootstrap.min.css.map site/static/bwr/bootstrap/dist/fonts/glyphicons-halflings-regular.eot site/static/bwr/bootstrap/dist/fonts/glyphicons-halflings-regular.svg site/static/bwr/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf site/static/bwr/bootstrap/dist/fonts/glyphicons-halflings-regular.woff site/static/bwr/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 site/static/bwr/es5-shim/es5-shim.js site/static/bwr/es5-shim/es5-shim.map site/static/bwr/es5-shim/es5-shim.min.js site/static/bwr/file-saver.js/FileSaver.js site/static/bwr/file-saver.js/FileSaver.min.js site/static/bwr/jquery/dist/jquery.js site/static/bwr/jquery/dist/jquery.min.js site/static/bwr/jquery/dist/jquery.min.map site/static/css/manager.css site/static/css/manager.min.css site/static/forms/authChoice.html site/static/forms/authChoiceContainer.html site/static/forms/authParams.html site/static/forms/authParamsText.html site/static/forms/authParamsTextContainer.html site/static/forms/blackWhiteList.html site/static/forms/bool.html site/static/forms/boolOrExpr.html site/static/forms/catAndAppList.html site/static/forms/doubleHash.html site/static/forms/file.html site/static/forms/grant.html site/static/forms/grantContainer.html site/static/forms/home.html site/static/forms/int.html site/static/forms/keyText.html site/static/forms/keyTextContainer.html site/static/forms/longtext.html site/static/forms/menuApp.html site/static/forms/menuCat.html site/static/forms/mini.html site/static/forms/oidcOPMetaDataNode.html site/static/forms/oidcOPMetaDataNodeContainer.html site/static/forms/oidcRPMetaDataNode.html site/static/forms/oidcRPMetaDataNodeContainer.html site/static/forms/password.html site/static/forms/portalskin.html site/static/forms/portalskinbackground.html site/static/forms/post.html site/static/forms/postContainer.html site/static/forms/README.md site/static/forms/restore.html site/static/forms/RSAKey.html site/static/forms/RSAKeyNoPassword.html site/static/forms/rule.html site/static/forms/ruleContainer.html site/static/forms/samlAssertion.html site/static/forms/samlAttribute.html site/static/forms/samlAttributeContainer.html site/static/forms/samlIDPMetaDataNode.html site/static/forms/samlIDPMetaDataNodeContainer.html site/static/forms/samlService.html site/static/forms/samlSPMetaDataNode.html site/static/forms/samlSPMetaDataNodeContainer.html site/static/forms/select.html site/static/forms/simpleInputContainer.html site/static/forms/text.html site/static/forms/trool.html site/static/forms/virtualHost.html site/static/forms/virtualHostContainer.html site/static/forms/white.html site/static/js/conftree.js site/static/js/conftree.min.js site/static/js/filterFunctions.js site/static/js/filterFunctions.min.js site/static/js/llApp.js site/static/js/llApp.min.js site/static/js/manager.js site/static/js/manager.min.js site/static/js/notifications.js site/static/js/notifications.min.js site/static/js/sessions.js site/static/js/sessions.min.js site/static/languages/en.json site/static/languages/fr.json site/static/logos/bootstrap.png site/static/logos/custom.png site/static/logos/dark.png site/static/logos/en.png site/static/logos/favicon.ico site/static/logos/fr.png site/static/logos/impact.png site/static/logos/llng-icon-32.png site/static/logos/llng-logo-32.png site/static/logos/pastel.png site/static/struct.json site/templates/footer.tpl site/templates/header.tpl site/templates/manager.tpl site/templates/menubar.tpl site/templates/notifications.tpl site/templates/scripts.tpl site/templates/sessions.tpl site/templates/tree.tpl t/02-HTML-template.t t/03-HTML-forms.t t/05-rest-api.t t/06-rest-api.t t/07-utf8.t t/10-save-unchanged-conf.t t/12-save-changed-conf.t t/14-bad-changes-in-conf.t t/20-test-coverage.t t/40-sessions.t t/50-notifications.t t/80-attributes.t t/90-translations.t t/99-pod.t t/conf/lmConf-1.js t/jsonfiles/01-base-tree.json t/jsonfiles/02-base-tree-all-nodes-opened.json t/jsonfiles/12-modified.json t/jsonfiles/14-bad.json t/lemonldap-ng.ini t/test-lib.pm lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/META.yml000066400000000000000000000016461325274564300274310ustar00rootroot00000000000000--- abstract: 'Perl extension for managing Lemonldap::NG Web-SSO system.' author: - 'Xavier Guimard , Clément Oudot , Thomas Chemineau ' build_requires: IO::String: '0' Regexp::Common: '0' Test::Pod: '1' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Lemonldap-NG-Manager no_index: directory: - t - inc requires: Convert::PEM: '0' Crypt::OpenSSL::RSA: '0' HTML::Template: '0' JSON: '0' LWP: '0' Lemonldap::NG::Common: v1.9.16 Lemonldap::NG::Handler: v1.9.16 URI: '0' XML::LibXML: '0' XML::LibXSLT: '0' version: v1.9.16 x_LWP::Protocol::https: 0 x_serialization_backend: 'CPAN::Meta::YAML version 0.018' lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/Makefile.PL000066400000000000000000000026371325274564300301330ustar00rootroot00000000000000use 5.014; use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( NAME => 'Lemonldap::NG::Manager', VERSION_FROM => 'lib/Lemonldap/NG/Manager.pm', # finds $VERSION LICENSE => 'gpl', BUILD_REQUIRES => { 'IO::String' => 0, 'Regexp::Common' => 0, 'Test::Pod' => 1.00, }, META_MERGE => { 'LWP::Protocol::https' => 0, }, PREREQ_PM => { 'Convert::PEM' => 0, 'Crypt::OpenSSL::RSA' => 0, 'HTML::Template' => 0, 'JSON' => 0, 'Lemonldap::NG::Common' => '1.9.16', 'Lemonldap::NG::Handler' => '1.9.16', 'LWP' => 0, 'URI' => 0, 'XML::LibXSLT' => 0, 'XML::LibXML' => 0, }, # e.g., Module::Name => 1.1 ( $] >= 5.005 ? ## Add these new keywords supported since 5.005 ( ABSTRACT_FROM => 'lib/Lemonldap/NG/Manager.pm', # retrieve abstract from module AUTHOR => 'Xavier Guimard ' . ', Clément Oudot ' . ', Thomas Chemineau ' ) : () ), clean => { FILES => 't/conf/lmConf-2.js', }, ); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/README000066400000000000000000000017731325274564300270410ustar00rootroot00000000000000LemonLDAP::NG ==================== LemonLDAP::NG is a modular Web-SSO based on Apache::Session modules. This is the manager part of it. You can find documentation here: * for administrators: http://lemonldap-ng.org/ * for developers: see embedded perldoc LemonLDAP::NG is a 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 2, 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 L. Copyright: * 2005-2015 by Xavier Guimard and Clément Oudot * 2008-2011 by Thomas Chemineau * 2012-2015 by François-Xavier Deltombe and Sandro Cazzaniga lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/REST-API.md000066400000000000000000000045571325274564300276720ustar00rootroot00000000000000# Lemonldap::NG::Manager REST API ## Configurations * List of available configuration: `/confs` * Last configuration number: `/confs/latest/cfgNum` * Configuration metadatas: `/confs/` * Key value: `/confs//` * Full configuration (for saving): `/confs/?full` Examples: * `/confs/latest/portal` * `/confs/184/portal` * `/confs/184/virtualHosts/test1.example.com/locationRules` ### Available verbs: * `GET`: see above * `POST /confs`: push a new configuration (or a saved one) `POST /confs?force=yes`: push a new configuration even if another has been posted before * _`DELETE /confs/`: not allowed_, administrator has to push an older with `?force=yes` **And perhaps:** * `PUT /confs/prepared/`: modify a value in the future configuration * `DELETE /confs/prepared//`: delete a hash entry (virtual host for example) * `GET /confs/prepared/`: get value from prepared configuration if exists, get current value otherwise ## Sessions Note that global can be replaced by persistent to list persistent sessions. * Sessions list: `/sessions/global` * Session: `/sessions/global/` * **TODO**: Session key: `/sessions/global//` * Delete session: `DELETE /sessions/global/` * Filters: * All connected users which username start by a letter: `/sessions/global?_whatToTrace=*&groupBy=_whatToTrace` * User's sessions: `/sessions/global?_whatToTrace=foo.bar` * IP's sessions: `/sessions/global?ip=1.2.3.4` * Double sessions by IP: `/sessions/global?doubleIP` * Group by: * First letter of Connected users: `/sessions/global?groupBy=substr(_whatToTrace,1)` * Order: * Sessions sorted by user: `/sessions/global?orderBy=_whatToTrace` Note that sessions are grouped automaticaly. ## Notifications * Notifications list: `/notifications/actives` * Notification: `/notifications/actives/` * Notified elements list: `/notifications/done` * Notified element: `/notifications/done/` * New session: `POST /notifications` * Filters: * All notifications for users which name starts by a letter: `/notifications?_whatToTrace=*&groupBy=_whatToTrace` * User's notifications: `/notifications/(actives|done)?_whatToTrace=foo.bar` * Mark as notified: `PUT /notifications/actives/ done=1` * Delete notofication: `DELETE /notifications/done/` lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/bower.json000066400000000000000000000007221325274564300301630ustar00rootroot00000000000000{ "name": "angular-lemonldap-ng-manager", "description": "Lemonldap::NG manager with AngularJS", "version": "1.9.0", "homepage": "https://lemonldap-ng.org", "license": "GPL2", "private": true, "dependencies": { "angular": "1.5.x", "angular-animate": "1.5.x", "angular-bootstrap": "x", "angular-cookies": "1.5.x", "angular-ui-tree": "x", "bootstrap": "x", "es5-shim": "x", "file-saver.js": "x", "jquery": "2.x" } } lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/eg/000077500000000000000000000000001325274564300265445ustar00rootroot00000000000000manager-server.cgi000077500000000000000000000002141325274564300320670ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/eg#!/usr/bin/perl use Lemonldap::NG::Manager; use Plack::Handler::CGI; Plack::Handler::CGI->new->run( Lemonldap::NG::Manager->run( {} ) ); manager-server.fcgi000077500000000000000000000002651325274564300322430ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/eg#!/usr/bin/perl use Plack::Handler::FCGI; use Lemonldap::NG::Manager; # Roll your own my $server = Plack::Handler::FCGI->new(); $server->run( Lemonldap::NG::Manager->run( {} ) ); manager-server.psgi000066400000000000000000000001261325274564300322660ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/eg#!/usr/bin/env plackup use Lemonldap::NG::Manager; Lemonldap::NG::Manager->run({}); lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/000077500000000000000000000000001325274564300267175ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/000077500000000000000000000000001325274564300306325ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/000077500000000000000000000000001325274564300311365ustar00rootroot00000000000000Manager.pm000066400000000000000000000213461325274564300327750ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG# Manager main component # # This package contains these parts: # - Properties and private methods # - Initialization method (launched by Lemonldap::NG::Common::PSGI) # that declares routes # - Upload methods (launched by Lemonldap::NG::Common::PSGI::Router) # # It inherits from Conf.pm to responds to display methods and from # Sessions.pm to manage sessions package Lemonldap::NG::Manager; use 5.10.0; use utf8; use Mouse; our $VERSION = '1.9.16'; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; extends 'Lemonldap::NG::Manager::Lib', 'Lemonldap::NG::Handler::PSGI::Router'; has csp => ( is => 'rw' ); ## @method boolean init($args) # Launch initialization method # # @param $args hashref to merge with object # @return 0 in case of error, 1 else sub init { my ( $self, $args ) = @_; $args ||= {}; my $conf = $self->confAcc; if ( my $localconf = $conf->getLocalConf(MANAGERSECTION) ) { $self->{$_} = $args->{$_} // $localconf->{$_} foreach ( keys %$localconf ); } # Manager needs to keep new Ajax behaviour $args->{noAjaxHook} = 0; return 0 unless ( $self->Lemonldap::NG::Handler::PSGI::Router::init($args) ); # TODO: manage errors unless ( -r $self->{templateDir} ) { $self->error("Unable to read $self->{templateDir}"); return 0; } $self->{enabledModules} ||= "conf, sessions, notifications"; my @links; my @enabledModules = map { push @links, $_; "Lemonldap::NG::Manager::" . ucfirst($_) } split( /[,\s]+/, $self->{enabledModules} ); extends 'Lemonldap::NG::Handler::PSGI::Router', @enabledModules; my @working; for ( my $i = 0 ; $i < @enabledModules ; $i++ ) { my $mod = $enabledModules[$i]; no strict 'refs'; if ( &{"${mod}::addRoutes"}($self) ) { $self->lmLog( "Module $mod enabled", 'debug' ); push @working, $mod; } else { $links[$i] = undef; $self->lmLog( "Module $mod can not be enabled: " . $self->error, 'error' ); } } return 0 unless (@working); $self->addRoute( links => 'links', ['GET'] ); my $portal = $Lemonldap::NG::Handler::Main::tsv->{portal}->(); $portal =~ s#https?://([^/]*).*#$1#; $self->csp( "default-src 'self';frame-ancestors 'none';form-action 'self';img-src 'self' $portal;style-src 'self' $portal;" ); $self->defaultRoute( $working[0]->defaultRoute ); my $linksIcons = { 'conf' => 'cog', 'sessions' => 'duplicate', 'notifications' => 'bell' }; $self->links( [] ); for ( my $i = 0 ; $i < @links ; $i++ ) { next unless ( defined $links[$i] ); push @{ $self->links }, { target => $enabledModules[$i]->defaultRoute, title => $links[$i], icon => $linksIcons->{ $links[$i] } }; } $self->menuLinks( [] ); push @{ $self->menuLinks }, { target => &{ $Lemonldap::NG::Handler::SharedConf::tsv->{portal} }, title => 'backtoportal', icon => 'home' } if ( defined $Lemonldap::NG::Handler::SharedConf::tsv->{portal} ); push @{ $self->menuLinks }, { target => &{ $Lemonldap::NG::Handler::SharedConf::tsv->{portal} } . '?logout=1', title => 'logout', icon => 'log-out' } if ( defined $Lemonldap::NG::Handler::SharedConf::tsv->{portal} ); 1; } sub tplParams { return ( VERSION => $VERSION, ); } sub javascript { my ($self) = @_; return 'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";' . ( $self->links ? 'var links=' . JSON::to_json( $self->links ) . ';' : '' ) . ( $self->menuLinks ? 'var menulinks=' . JSON::to_json( $self->menuLinks ) . ';' : '' ); } sub sendHtml { my ( $self, $req, $template, %args ) = @_; my $res = $self->SUPER::sendHtml( $req, $template, %args ); push @{ $res->[1] }, 'Content-Security-Policy' => $self->csp, 'X-Content-Type-Options' => 'nosniff', 'X-Frame-Options' => 'DENY', 'X-XSS-Protection' => '1; mode=block'; return $res; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager - Perl extension for managing Lemonldap::NG Web-SSO system. =head1 SYNOPSIS #!/usr/bin/env plackup -I pl/lib use Lemonldap::NG::Manager; # This must be the last instruction ! See PSGI for more Lemonldap::NG::Manager->run($opts); =head1 DESCRIPTION Lemonldap::NG::Manager provides a web interface to manage Lemonldap::NG Web-SSO system. The Perl part of Lemonldap::NG::Manager is the REST server. Web interface is written in Javascript, using AngularJS framework and can be found in `site` directory. The REST API is described in REST-API.md file given in source tree. Lemonldap::NG Manager uses L to be compatible with CGI, FastCGI,... It inherits of L =head1 ORGANIZATION Lemonldap::NG Manager contains 4 parts: =over =item Configuration management: see L; =item Session explorer: see L; =item Notification explorer: see L; =item Some files uses to generate static files: see below. =back =head2 Generation of static files The `scripts/jsongenerator.pl` file uses Lemonldap::NG::Manager::Build::Attributes, Lemonldap::NG::Manager::Build::Tree and Lemonldap::NG::Manager::Build::CTrees to generate =over =item `site/static/struct.json`: the main file that contains the tree view; =item `site/static/js/conftree.js`: generates sub tree for virtualhosts and SAML and OpenID-Connect partners; =item `Lemonldap::NG::Manager::Constants`: constants used by all Perl manager components; =item `Lemonldap::NG::Common::Conf::DefaultValues`: constants used to read configuration. =back =head1 PARAMETERS You can use a hash ref to override any LemonLDAP::NG parameter. Currently, you can specify where your lemonldap-ng.ini file is: Lemonldap::NG::Manager->run( { confFile => '/path/to/lemonldap-ng.ini' } ); =head2 lemonldap-ng.ini parameters You can override any configuration parameter in lemonldap-ng.ini, but some are required and can't be set to global configuration (as any Lemonldap::NG module, you can also fix them in $opts hash ref passed as argument to run() or new()). [manager] ;protection: choose one of none, authenticate, manager as explain in ; Lemonldap::NG::Handler::PSGI::Router doc. protection = manager ;enabledModules: Modules to display. Default to `conf, sessions, notifications` enabledModules = conf, sessions, notifications ;logLevel: choose one of error, warn, notice, info, debug ; See Lemonldap::NG::Common::PSGI doc for more logLevel = notice ;staticPrefix: set here the URI path to static content ; See Lemonldap::NG::Common::PSGI doc for more staticPrefix = static/ ;languages: Available interface languages languages = en, fr ;templateDir: the path to the directory containing HTML templates ; See Lemonldap::NG::Common::PSGI doc for more templateDir = /usr/share/lemonldap-ng/manager/ =head1 SEE ALSO L, L, L, L, L, L, L L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Manager/000077500000000000000000000000001325274564300324315ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NGAttributes.pm000066400000000000000000002732031325274564300351240ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager# This file is generated by Lemonldap::NG::Manager::Build. Don't modify it by hand package Lemonldap::NG::Manager::Attributes; our $VERSION = '1.9.11'; sub types { return { 'authParamsText' => { 'test' => sub { 1; } }, 'blackWhiteList' => { 'test' => sub { 1; } }, 'bool' => { 'msgFail' => '__notABoolean__', 'test' => qr/^[01]$/ }, 'boolOrExpr' => { 'msgFail' => '__notAValidPerlExpression__', 'test' => sub { my ( $val, $conf ) = @_; my $s = ''; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval "$s $val"; return $@ ? ( 1, "__badExpression__: $@" ) : 1; } }, 'catAndAppList' => { 'test' => sub { 1; } }, 'file' => { 'test' => sub { 1; } }, 'hostname' => { 'form' => 'text', 'msgFail' => '__badHostname__', 'test' => qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))?$/ }, 'int' => { 'msgFail' => '__notAnInteger__', 'test' => qr/^\-?\d+$/ }, 'keyText' => { 'keyTest' => qr/^[a-zA-Z0-9_]+$/, 'msgFail' => '__badValue__', 'test' => qr/^.*$/ }, 'keyTextContainer' => { 'keyMsgFail' => '__badKeyName__', 'keyTest' => qr/^\w[\w\.\-]*$/, 'msgFail' => '__emptyValueNotAllowed__', 'test' => qr/./ }, 'lmAttrOrMacro' => { 'form' => 'text', 'test' => sub { my ( $val, $conf ) = @_; return 1 if defined $conf->{'macros'}{$val} or $val eq '_timezone'; foreach $_ ( keys %$conf ) { return 1 if $_ =~ /exportedvars$/i and defined $conf->{$_}{$val}; } return 1, "__unknownAttrOrMacro__: $val"; } }, 'longtext' => { 'test' => sub { 1; } }, 'menuApp' => { 'test' => sub { 1; } }, 'menuCat' => { 'test' => sub { 1; } }, 'oidcmetadatajson' => { 'test' => sub { 1; } }, 'oidcmetadatajwks' => { 'test' => sub { 1; } }, 'oidcOPMetaDataNode' => { 'test' => sub { 1; } }, 'oidcRPMetaDataNode' => { 'test' => sub { 1; } }, 'password' => { 'msgFail' => '__malformedValue__', 'test' => sub { 1; } }, 'pcre' => { 'form' => 'text', 'test' => sub { eval { do { qr/$_[0]/; } }; return $@ ? ( 0, "__badRegexp__: $@" ) : 1; } }, 'PerlModule' => { 'form' => 'text', 'msgFail' => '__badPerlPackageName__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9]*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/ }, 'portalskin' => { 'test' => sub { 1; } }, 'portalskinbackground' => { 'test' => sub { 1; } }, 'post' => { 'test' => sub { 1; } }, 'RSAPrivateKey' => { 'test' => sub { return $_[0] =~ m[^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$]s ? 1 : ( 1, '__badPemEncoding__' ); } }, 'RSAPublicKey' => { 'test' => sub { return $_[0] =~ m[^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$]s ? 1 : ( 1, '__badPemEncoding__' ); } }, 'RSAPublicKeyOrCertificate' => { 'test' => sub { return $_[0] =~ m[^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$]s ? 1 : ( 1, '__badPemEncoding__' ); } }, 'rule' => { 'test' => sub { 1; } }, 'samlAssertion' => { 'test' => sub { 1; } }, 'samlAttribute' => { 'test' => sub { 1; } }, 'samlIDPMetaDataNode' => { 'test' => sub { 1; } }, 'samlService' => { 'test' => sub { 1; } }, 'samlSPMetaDataNode' => { 'test' => sub { 1; } }, 'select' => { 'test' => sub { my $test = grep( { $_ eq $_[0]; } map( { $_->{'k'}; } @{ $_[2]{'select'}; } ) ); return $test ? 1 : ( 0, "Invalid value '$_[0]' for this select" ); } }, 'subContainer' => { 'keyTest' => qr/\w/, 'test' => sub { 1; } }, 'text' => { 'msgFail' => '__malformedValue__', 'test' => sub { 1; } }, 'trool' => { 'msgFail' => '__authorizedValues__: -1, 0, 1', 'test' => qr/^(?:-1|0|1)$/ }, 'url' => { 'form' => 'text', 'msgFail' => '__badUrl__', 'test' => qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)/ } }; } sub attributes { return { 'activeTimer' => { 'default' => 1, 'type' => 'bool' }, 'ADPwdExpireWarning' => { 'default' => 0, 'type' => 'int' }, 'ADPwdMaxAge' => { 'default' => 0, 'type' => 'int' }, 'apacheAuthnLevel' => { 'default' => 4, 'type' => 'int' }, 'applicationList' => { 'default' => { 'default' => { 'catname' => 'Default category', 'type' => 'category' } }, 'keyTest' => qr/\w/, 'type' => 'catAndAppList' }, 'authChoiceModules' => { 'keyMsgFail' => '__badChoiceKey__', 'keyTest' => qr/^(\d*)?[a-zA-Z0-9_]+$/, 'select' => [ [ { 'k' => 'Apache', 'v' => 'Apache' }, { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'BrowserID', 'v' => 'BrowserID (Mozilla Persona)' }, { 'k' => 'CAS', 'v' => 'Central Authentication Service (CAS)' }, { 'k' => 'DBI', 'v' => 'Database (DBI)' }, { 'k' => 'Demo', 'v' => 'Demo' }, { 'k' => 'Facebook', 'v' => 'Facebook' }, { 'k' => 'Google', 'v' => 'Google' }, { 'k' => 'Kerberos', 'v' => 'Kerberos' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'LinkedIn', 'v' => 'LinkedIn' }, { 'k' => 'Null', 'v' => 'None' }, { 'k' => 'OpenID', 'v' => 'OpenID' }, { 'k' => 'OpenIDConnect', 'v' => 'OpenID Connect' }, { 'k' => 'Proxy', 'v' => 'Proxy' }, { 'k' => 'Radius', 'v' => 'Radius' }, { 'k' => 'Remote', 'v' => 'Remote' }, { 'k' => 'SAML', 'v' => 'SAML v2' }, { 'k' => 'Slave', 'v' => 'Slave' }, { 'k' => 'SSL', 'v' => 'SSL' }, { 'k' => 'Twitter', 'v' => 'Twitter' }, { 'k' => 'WebID', 'v' => 'WebID' }, { 'k' => 'Yubikey', 'v' => 'Yubikey' } ], [ { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'DBI', 'v' => 'Database (DBI)' }, { 'k' => 'Demo', 'v' => 'Demo' }, { 'k' => 'Facebook', 'v' => 'Facebook' }, { 'k' => 'Google', 'v' => 'Google' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'Null', 'v' => 'None' }, { 'k' => 'OpenID', 'v' => 'OpenID' }, { 'k' => 'OpenIDConnect', 'v' => 'OpenID Connect' }, { 'k' => 'Proxy', 'v' => 'Proxy' }, { 'k' => 'Remote', 'v' => 'Remote' }, { 'k' => 'SAML', 'v' => 'SAML v2' }, { 'k' => 'Slave', 'v' => 'Slave' }, { 'k' => 'WebID', 'v' => 'WebID' } ], [ { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'DBI', 'v' => 'Database (DBI)' }, { 'k' => 'Demo', 'v' => 'Demo' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'Null', 'v' => 'None' } ] ], 'test' => sub { 1; }, 'type' => 'authChoiceContainer' }, 'authChoiceParam' => { 'default' => 'lmAuth', 'type' => 'text' }, 'authentication' => { 'default' => 'Demo', 'select' => [ { 'k' => 'Apache', 'v' => 'Apache' }, { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'BrowserID', 'v' => 'BrowserID (Mozilla Persona)' }, { 'k' => 'Choice', 'v' => 'authChoice' }, { 'k' => 'CAS', 'v' => 'Central Authentication Service (CAS)' }, { 'k' => 'DBI', 'v' => 'Database (DBI)' }, { 'k' => 'Demo', 'v' => 'Demonstration' }, { 'k' => 'Facebook', 'v' => 'Facebook' }, { 'k' => 'Google', 'v' => 'Google' }, { 'k' => 'Kerberos', 'v' => 'Kerberos' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'LinkedIn', 'v' => 'LinkedIn' }, { 'k' => 'Multi', 'v' => 'Multiple' }, { 'k' => 'Null', 'v' => 'None' }, { 'k' => 'OpenID', 'v' => 'OpenID' }, { 'k' => 'OpenIDConnect', 'v' => 'OpenID Connect' }, { 'k' => 'Proxy', 'v' => 'Proxy' }, { 'k' => 'Radius', 'v' => 'Radius' }, { 'k' => 'Remote', 'v' => 'Remote' }, { 'k' => 'SAML', 'v' => 'SAML v2' }, { 'k' => 'Slave', 'v' => 'Slave' }, { 'k' => 'SSL', 'v' => 'SSL' }, { 'k' => 'Twitter', 'v' => 'Twitter' }, { 'k' => 'WebID', 'v' => 'WebID' }, { 'k' => 'Yubikey', 'v' => 'Yubikey' } ], 'type' => 'select' }, 'AuthLDAPFilter' => { 'type' => 'text' }, 'browserIdAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'browserIdAutoLogin' => { 'type' => 'bool' }, 'browserIdBackgroundColor' => { 'type' => 'text' }, 'browserIdSiteLogo' => { 'type' => 'text' }, 'browserIdSiteName' => { 'type' => 'text' }, 'browserIdVerificationURL' => { 'type' => 'text' }, 'captcha_login_enabled' => { 'default' => 0, 'type' => 'bool' }, 'captcha_mail_enabled' => { 'default' => 0, 'type' => 'bool' }, 'captcha_register_enabled' => { 'default' => 1, 'type' => 'bool' }, 'captcha_size' => { 'default' => 6, 'type' => 'int' }, 'captchaStorage' => { 'default' => 'Apache::Session::File', 'type' => 'PerlModule' }, 'captchaStorageOptions' => { 'default' => { 'Directory' => '/var/lib/lemonldap-ng/captcha/' }, 'type' => 'keyTextContainer' }, 'CAS_authnLevel' => { 'default' => 1, 'type' => 'int' }, 'CAS_CAFile' => { 'type' => 'text' }, 'CAS_gateway' => { 'type' => 'bool' }, 'CAS_pgtFile' => { 'default' => '/tmp/pgt.txt', 'type' => 'text' }, 'CAS_proxiedServices' => { 'keyMsgFail' => '__badCasProxyId__', 'keyTest' => qr/^\w+$/, 'type' => 'keyTextContainer' }, 'CAS_renew' => { 'type' => 'bool' }, 'CAS_url' => { 'msgFail' => '__badUrl__', 'test' => qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)/, 'type' => 'text' }, 'casAccessControlPolicy' => { 'default' => 'none', 'select' => [ { 'k' => 'none', 'v' => 'None' }, { 'k' => 'error', 'v' => 'Display error on portal' }, { 'k' => 'faketicket', 'v' => 'Send a fake service ticket' } ], 'type' => 'select' }, 'casAttr' => { 'type' => 'text' }, 'casAttributes' => { 'type' => 'keyTextContainer' }, 'casStorage' => { 'type' => 'PerlModule' }, 'casStorageOptions' => { 'type' => 'keyTextContainer' }, 'cda' => { 'default' => 0, 'type' => 'bool' }, 'cfgAuthor' => { 'type' => 'text' }, 'cfgAuthorIP' => { 'type' => 'text' }, 'cfgDate' => { 'type' => 'int' }, 'cfgLog' => { 'type' => 'longtext' }, 'cfgNum' => { 'default' => 0, 'type' => 'int' }, 'checkXSS' => { 'default' => 1, 'type' => 'bool' }, 'confirmFormMethod' => { 'default' => 'post', 'select' => [ { 'k' => 'get', 'v' => 'GET' }, { 'k' => 'post', 'v' => 'POST' } ], 'type' => 'select' }, 'cookieExpiration' => { 'type' => 'text' }, 'cookieName' => { 'default' => 'lemonldap', 'msgFail' => '__badCookieName__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/, 'type' => 'text' }, 'customFunctions' => { 'msgFail' => '__badCustomFuncName__', 'test' => qr/^(?:\w+(?:::\w+)*(?:\s+\w+(?:::\w+)*)*)?$/, 'type' => 'text' }, 'dbiAuthChain' => { 'type' => 'text' }, 'dbiAuthLoginCol' => { 'type' => 'text' }, 'dbiAuthnLevel' => { 'default' => 2, 'type' => 'int' }, 'dbiAuthPassword' => { 'type' => 'password' }, 'dbiAuthPasswordCol' => { 'type' => 'text' }, 'dbiAuthPasswordHash' => { 'type' => 'text' }, 'dbiAuthTable' => { 'type' => 'text' }, 'dbiAuthUser' => { 'type' => 'text' }, 'dbiDynamicHashEnabled' => { 'type' => 'bool' }, 'dbiDynamicHashNewPasswordScheme' => { 'type' => 'text' }, 'dbiDynamicHashValidSaltedSchemes' => { 'type' => 'text' }, 'dbiDynamicHashValidSchemes' => { 'type' => 'text' }, 'dbiExportedVars' => { 'default' => {}, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'dbiPasswordMailCol' => { 'type' => 'text' }, 'dbiUserChain' => { 'type' => 'text' }, 'dbiUserPassword' => { 'type' => 'password' }, 'dbiUserTable' => { 'type' => 'text' }, 'dbiUserUser' => { 'type' => 'text' }, 'demoExportedVars' => { 'default' => { 'cn' => 'cn', 'mail' => 'mail', 'uid' => 'uid' }, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'domain' => { 'default' => 'example.com', 'msgFail' => '__badDomainName__', 'test' => qr/^(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?))?/, 'type' => 'text' }, 'exportedAttr' => { 'type' => 'text' }, 'exportedHeaders' => { 'keyMsgFail' => '__badHostname__', 'keyTest' => qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)$/, 'test' => { 'keyMsgFail' => '__badHeaderName__', 'keyTest' => qr/^(?=[^\-])[\w\-]+(?<=[^-])$/, 'test' => sub { my ( $val, $conf ) = @_; my $s = $val; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval $s; return $@ ? ( 1, "__badExpression__: $@" ) : 1; } }, 'type' => 'keyTextContainer' }, 'exportedVars' => { 'default' => { 'UA' => 'HTTP_USER_AGENT' }, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[_a-zA-Z][a-zA-Z0-9_]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[_a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'facebookAppId' => { 'type' => 'text' }, 'facebookAppSecret' => { 'type' => 'text' }, 'facebookAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'facebookExportedVars' => { 'default' => {}, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'failedLoginNumber' => { 'default' => 5, 'type' => 'int' }, 'globalStorage' => { 'default' => 'Apache::Session::File', 'type' => 'PerlModule' }, 'globalStorageOptions' => { 'default' => { 'Directory' => '/var/lib/lemonldap-ng/sessions/', 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/' }, 'type' => 'keyTextContainer' }, 'googleAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'googleExportedVars' => { 'default' => {}, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'grantSessionRules' => { 'keyTest' => sub { my ( $val, $conf ) = @_; my $s = ''; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval "$s $val"; return $@ ? ( 1, "__badExpression__: $@" ) : 1; }, 'test' => sub { 1; }, 'type' => 'grantContainer' }, 'groups' => { 'default' => {}, 'test' => sub { my ( $val, $conf ) = @_; my $s = ''; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval "$s $val"; return $@ ? ( 1, "__badExpression__: $@" ) : 1; }, 'type' => 'keyTextContainer' }, 'hiddenAttributes' => { 'default' => '_password', 'type' => 'text' }, 'hideOldPassword' => { 'default' => 0, 'type' => 'bool' }, 'httpOnly' => { 'default' => 1, 'type' => 'bool' }, 'https' => { 'default' => 0, 'type' => 'bool' }, 'infoFormMethod' => { 'default' => 'get', 'select' => [ { 'k' => 'get', 'v' => 'GET' }, { 'k' => 'post', 'v' => 'POST' } ], 'type' => 'select' }, 'issuerDBCASActivation' => { 'default' => 0, 'type' => 'bool' }, 'issuerDBCASPath' => { 'default' => '^/cas/', 'type' => 'pcre' }, 'issuerDBCASRule' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'issuerDBGetActivation' => { 'default' => 0, 'type' => 'bool' }, 'issuerDBGetParameters' => { 'default' => {}, 'keyMsgFail' => '__badHostname__', 'keyTest' => qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)$/, 'test' => { 'keyMsgFail' => '__badKeyName__', 'keyTest' => qr/^(?=[^\-])[\w\-]+(?<=[^-])$/, 'test' => sub { my ( $val, $conf ) = @_; return 1 if defined $conf->{'macros'}{$val} or $val eq '_timezone'; foreach $_ ( keys %$conf ) { return 1 if $_ =~ /exportedvars$/i and defined $conf->{$_}{$val}; } return 1, "__unknownAttrOrMacro__: $val"; } }, 'type' => 'doubleHash' }, 'issuerDBGetPath' => { 'default' => '^/get/', 'type' => 'text' }, 'issuerDBGetRule' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'issuerDBOpenIDActivation' => { 'default' => 0, 'type' => 'bool' }, 'issuerDBOpenIDConnectActivation' => { 'default' => 0, 'type' => 'bool' }, 'issuerDBOpenIDConnectPath' => { 'default' => '^/oauth2/', 'type' => 'text' }, 'issuerDBOpenIDConnectRule' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'issuerDBOpenIDPath' => { 'default' => '^/openidserver/', 'type' => 'pcre' }, 'issuerDBOpenIDRule' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'issuerDBSAMLActivation' => { 'default' => 0, 'type' => 'bool' }, 'issuerDBSAMLPath' => { 'default' => '^/saml/', 'type' => 'pcre' }, 'issuerDBSAMLRule' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'jsRedirect' => { 'default' => 0, 'type' => 'boolOrExpr' }, 'key' => { 'type' => 'password' }, 'krbAuthnLevel' => { 'default' => 3, 'type' => 'int' }, 'krbByJs' => { 'default' => 0, 'type' => 'bool' }, 'krbKeytab' => { 'type' => 'text' }, 'krbRemoveDomain' => { 'default' => 1, 'type' => 'bool' }, 'krbUseModKrb' => { 'default' => 0, 'type' => 'bool' }, 'ldapAllowResetExpiredPassword' => { 'default' => 0, 'type' => 'bool' }, 'ldapAuthnLevel' => { 'default' => 2, 'type' => 'int' }, 'ldapBase' => { 'default' => 'dc=example,dc=com', 'msgFail' => '__badValue__', 'test' => qr/^(?:\w+=.*|)$/, 'type' => 'text' }, 'ldapChangePasswordAsUser' => { 'default' => 0, 'type' => 'bool' }, 'ldapExportedVars' => { 'default' => { 'cn' => 'cn', 'mail' => 'mail', 'uid' => 'uid' }, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'LDAPFilter' => { 'type' => 'text' }, 'ldapGroupAttributeName' => { 'default' => 'member', 'type' => 'text' }, 'ldapGroupAttributeNameGroup' => { 'default' => 'dn', 'type' => 'text' }, 'ldapGroupAttributeNameSearch' => { 'default' => 'cn', 'type' => 'text' }, 'ldapGroupAttributeNameUser' => { 'default' => 'dn', 'type' => 'text' }, 'ldapGroupBase' => { 'type' => 'text' }, 'ldapGroupObjectClass' => { 'default' => 'groupOfNames', 'type' => 'text' }, 'ldapGroupRecursive' => { 'default' => 0, 'type' => 'bool' }, 'ldapPasswordResetAttribute' => { 'default' => 'pwdReset', 'type' => 'text' }, 'ldapPasswordResetAttributeValue' => { 'default' => 'TRUE', 'type' => 'text' }, 'ldapPort' => { 'default' => 389, 'type' => 'int' }, 'ldapPpolicyControl' => { 'default' => 0, 'type' => 'bool' }, 'ldapPwdEnc' => { 'default' => 'utf-8', 'msgFail' => '__badEncoding__', 'test' => qr/^[a-zA-Z0-9_][a-zA-Z0-9_\-]*[a-zA-Z0-9_]$/, 'type' => 'text' }, 'ldapRaw' => { 'type' => 'text' }, 'ldapSearchDeref' => { 'default' => 'find', 'select' => [ { 'k' => 'never', 'v' => 'never' }, { 'k' => 'search', 'v' => 'search' }, { 'k' => 'find', 'v' => 'find' }, { 'k' => 'always', 'v' => 'always' } ], 'type' => 'select' }, 'ldapServer' => { 'default' => 'ldap://localhost', 'test' => sub { my $l = shift(); my @s = split( /[\s,]+/, $l, 0 ); foreach my $s (@s) { return 0, qq[__badLdapUri__: "$s"] unless $s =~ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?::\d{1,5})?/?.*)$]o; } return 1; }, 'type' => 'text' }, 'ldapSetPassword' => { 'default' => 0, 'type' => 'bool' }, 'ldapTimeout' => { 'default' => 120, 'type' => 'int' }, 'ldapUsePasswordResetAttribute' => { 'default' => 1, 'type' => 'bool' }, 'ldapVersion' => { 'default' => 3, 'type' => 'int' }, 'linkedInAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'linkedInClientID' => { 'type' => 'text' }, 'linkedInClientSecret' => { 'type' => 'password' }, 'linkedInFields' => { 'default' => 'id,first-name,last-name,email-address', 'type' => 'text' }, 'linkedInScope' => { 'default' => 'r_basicprofile r_emailaddress', 'type' => 'text' }, 'linkedInUserField' => { 'default' => 'emailAddress', 'type' => 'text' }, 'localSessionStorage' => { 'default' => 'Cache::FileCache', 'type' => 'PerlModule' }, 'localSessionStorageOptions' => { 'default' => { 'cache_depth' => 3, 'cache_root' => '/tmp', 'default_expires_in' => 600, 'directory_umask' => '007', 'namespace' => 'lemonldap-ng-sessions' }, 'type' => 'keyTextContainer' }, 'locationRules' => { 'default' => { 'default' => 'deny' }, 'keyMsgFail' => '__badHostname__', 'keyTest' => qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)$/, 'test' => { 'keyMsgFail' => '__badRegexp__', 'keyTest' => sub { eval { do { qr/$_[0]/; } }; return $@ ? 0 : 1; }, 'msgFail' => '__badExpression__', 'test' => sub { my ( $val, $conf ) = @_; my $s = $val; if ( $s =~ s/^logout(?:_(?:sso|app(?:_sso)?))?\s*// ) { return $s =~ m[^(?:https?://.*)?$] ? 1 : ( 0, '__badUrl__' ); } $s =~ s/\b(accept|deny|unprotect|skip)\b/1/g; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval $s; return $@ ? ( 1, "__badExpression__: $@" ) : 1; } }, 'type' => 'ruleContainer' }, 'loginHistoryEnabled' => { 'default' => 1, 'type' => 'bool' }, 'logoutServices' => { 'default' => {}, 'type' => 'keyTextContainer' }, 'lwpSslOpts' => { 'type' => 'keyTextContainer' }, 'macros' => { 'default' => {}, 'keyMsgFail' => '__badMacroName__', 'keyTest' => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, 'test' => sub { my ( $val, $conf ) = @_; my $s = ''; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval "$s $val"; return $@ ? ( 1, "__badExpression__: $@" ) : 1; }, 'type' => 'keyTextContainer' }, 'mailBody' => { 'type' => 'longtext' }, 'mailCharset' => { 'default' => 'utf-8', 'type' => 'text' }, 'mailConfirmBody' => { 'type' => 'longtext' }, 'mailConfirmSubject' => { 'default' => '[LemonLDAP::NG] Password reset confirmation', 'type' => 'text' }, 'mailFrom' => { 'default' => 'noreply@example.com', 'type' => 'text' }, 'mailLDAPFilter' => { 'type' => 'text' }, 'mailOnPasswordChange' => { 'default' => 0, 'type' => 'bool' }, 'mailReplyTo' => { 'type' => 'text' }, 'mailSessionKey' => { 'default' => 'mail', 'type' => 'text' }, 'mailSubject' => { 'default' => '[LemonLDAP::NG] Your new password', 'type' => 'text' }, 'mailTimeout' => { 'default' => 0, 'type' => 'int' }, 'mailUrl' => { 'default' => 'http://auth.example.com/mail.pl', 'type' => 'url' }, 'maintenance' => { 'default' => 0, 'type' => 'bool' }, 'managerDn' => { 'default' => '', 'msgFail' => '__badValue__', 'test' => qr/^(?:\w+=.*)?$/, 'type' => 'text' }, 'managerPassword' => { 'default' => '', 'msgFail' => '__badValue__', 'test' => qr/^\S*$/, 'type' => 'password' }, 'multiAuthStack' => { 'type' => 'authParamsText' }, 'multiUserDBStack' => { 'type' => 'authParamsText' }, 'multiValuesSeparator' => { 'default' => '; ', 'type' => 'authParamsText' }, 'nginxCustomHandlers' => { 'keyTest' => qr/^\w+$/, 'msgFail' => '__badPerlPackageName__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9]*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/, 'type' => 'keyTextContainer' }, 'noAjaxHook' => { 'default' => 0, 'type' => 'bool' }, 'notification' => { 'default' => 0, 'type' => 'bool' }, 'notificationStorage' => { 'default' => 'File', 'type' => 'PerlModule' }, 'notificationStorageOptions' => { 'default' => { 'dirName' => '/var/lib/lemonldap-ng/notifications' }, 'type' => 'keyTextContainer' }, 'notificationWildcard' => { 'default' => 'allusers', 'type' => 'text' }, 'notificationXSLTfile' => { 'type' => 'text' }, 'notifyDeleted' => { 'default' => 1, 'type' => 'bool' }, 'notifyOther' => { 'default' => 0, 'type' => 'bool' }, 'nullAuthnLevel' => { 'default' => 2, 'type' => 'int' }, 'oidcAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'oidcOPMetaDataExportedVars' => { 'default' => { 'cn' => 'name', 'mail' => 'email', 'sn' => 'family_name', 'uid' => 'sub' }, 'type' => 'keyTextContainer' }, 'oidcOPMetaDataJSON' => { 'type' => 'file' }, 'oidcOPMetaDataJWKS' => { 'type' => 'file' }, 'oidcOPMetaDataNodes' => { 'type' => 'oidcOPMetaDataNodeContainer' }, 'oidcOPMetaDataOptions' => { 'type' => 'subContainer' }, 'oidcOPMetaDataOptionsAcrValues' => { 'type' => 'text' }, 'oidcOPMetaDataOptionsCheckJWTSignature' => { 'default' => 1, 'type' => 'bool' }, 'oidcOPMetaDataOptionsClientID' => { 'type' => 'text' }, 'oidcOPMetaDataOptionsClientSecret' => { 'type' => 'password' }, 'oidcOPMetaDataOptionsConfigurationURI' => { 'type' => 'url' }, 'oidcOPMetaDataOptionsDisplay' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'page', 'v' => 'page' }, { 'k' => 'popup', 'v' => 'popup' }, { 'k' => 'touch', 'v' => 'touch' }, { 'k' => 'wap', 'v' => 'wap' } ], 'type' => 'select' }, 'oidcOPMetaDataOptionsDisplayName' => { 'type' => 'text' }, 'oidcOPMetaDataOptionsIcon' => { 'type' => 'text' }, 'oidcOPMetaDataOptionsIDTokenMaxAge' => { 'default' => 30, 'type' => 'int' }, 'oidcOPMetaDataOptionsJWKSTimeout' => { 'default' => 0, 'type' => 'int' }, 'oidcOPMetaDataOptionsMaxAge' => { 'default' => 0, 'type' => 'int' }, 'oidcOPMetaDataOptionsPrompt' => { 'type' => 'text' }, 'oidcOPMetaDataOptionsScope' => { 'default' => 'openid profile', 'type' => 'text' }, 'oidcOPMetaDataOptionsStoreIDToken' => { 'default' => 0, 'type' => 'bool' }, 'oidcOPMetaDataOptionsTokenEndpointAuthMethod' => { 'default' => 'client_secret_post', 'select' => [ { 'k' => 'client_secret_post', 'v' => 'client_secret_post' }, { 'k' => 'client_secret_basic', 'v' => 'client_secret_basic' } ], 'type' => 'select' }, 'oidcOPMetaDataOptionsUiLocales' => { 'type' => 'text' }, 'oidcOPMetaDataOptionsUseNonce' => { 'default' => 1, 'type' => 'bool' }, 'oidcRPCallbackGetParam' => { 'default' => 'openidconnectcallback', 'type' => 'text' }, 'oidcRPMetaDataExportedVars' => { 'default' => { 'email' => 'mail', 'family_name' => 'sn', 'name' => 'cn' }, 'type' => 'keyTextContainer' }, 'oidcRPMetaDataNodes' => { 'type' => 'oidcRPMetaDataNodeContainer' }, 'oidcRPMetaDataOptions' => { 'type' => 'subContainer' }, 'oidcRPMetaDataOptionsAccessTokenExpiration' => { 'default' => 3600, 'type' => 'int' }, 'oidcRPMetaDataOptionsBypassConsent' => { 'default' => 0, 'type' => 'bool' }, 'oidcRPMetaDataOptionsClientID' => { 'type' => 'text' }, 'oidcRPMetaDataOptionsClientSecret' => { 'type' => 'password' }, 'oidcRPMetaDataOptionsDisplayName' => { 'type' => 'text' }, 'oidcRPMetaDataOptionsExtraClaims' => { 'default' => {}, 'type' => 'keyTextContainer' }, 'oidcRPMetaDataOptionsIcon' => { 'type' => 'text' }, 'oidcRPMetaDataOptionsIDTokenExpiration' => { 'default' => 3600, 'type' => 'int' }, 'oidcRPMetaDataOptionsIDTokenSignAlg' => { 'default' => 'HS512', 'select' => [ { 'k' => 'none', 'v' => 'None' }, { 'k' => 'HS256', 'v' => 'HS256' }, { 'k' => 'HS384', 'v' => 'HS384' }, { 'k' => 'HS512', 'v' => 'HS512' }, { 'k' => 'RS256', 'v' => 'RS256' }, { 'k' => 'RS384', 'v' => 'RS384' }, { 'k' => 'RS512', 'v' => 'RS512' } ], 'type' => 'select' }, 'oidcRPMetaDataOptionsPostLogoutRedirectUris' => { 'type' => 'text' }, 'oidcRPMetaDataOptionsRedirectUris' => { 'type' => 'text' }, 'oidcRPMetaDataOptionsUserIDAttr' => { 'type' => 'text' }, 'oidcRPStateTimeout' => { 'default' => 600, 'type' => 'int' }, 'oidcServiceAllowAuthorizationCodeFlow' => { 'default' => 1, 'type' => 'bool' }, 'oidcServiceAllowDynamicRegistration' => { 'default' => 0, 'type' => 'bool' }, 'oidcServiceAllowHybridFlow' => { 'default' => 0, 'type' => 'bool' }, 'oidcServiceAllowImplicitFlow' => { 'default' => 0, 'type' => 'bool' }, 'oidcServiceKeyIdSig' => { 'type' => 'text' }, 'oidcServiceMetaDataAuthnContext' => { 'default' => { 'loa-1' => 1, 'loa-2' => 2, 'loa-3' => 3, 'loa-4' => 4, 'loa-5' => 5 }, 'keyTest' => qr/\w/, 'type' => 'keyTextContainer' }, 'oidcServiceMetaDataAuthorizeURI' => { 'default' => 'authorize', 'type' => 'text' }, 'oidcServiceMetaDataCheckSessionURI' => { 'default' => 'checksession', 'type' => 'text' }, 'oidcServiceMetaDataEndSessionURI' => { 'default' => 'logout', 'type' => 'text' }, 'oidcServiceMetaDataIssuer' => { 'default' => 'http://auth.example.com', 'type' => 'text' }, 'oidcServiceMetaDataJWKSURI' => { 'default' => 'jwks', 'type' => 'text' }, 'oidcServiceMetaDataRegistrationURI' => { 'default' => 'register', 'type' => 'text' }, 'oidcServiceMetaDataTokenURI' => { 'default' => 'token', 'type' => 'text' }, 'oidcServiceMetaDataUserInfoURI' => { 'default' => 'userinfo', 'type' => 'text' }, 'oidcServicePrivateKeySig' => { 'type' => 'RSAPrivateKey' }, 'oidcServicePublicKeySig' => { 'type' => 'RSAPublicKey' }, 'oidcStorage' => { 'type' => 'PerlModule' }, 'oidcStorageOptions' => { 'type' => 'keyTextContainer' }, 'openIdAttr' => { 'type' => 'text' }, 'openIdAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'openIdExportedVars' => { 'default' => {}, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'openIdIDPList' => { 'default' => '0;', 'type' => 'blackWhiteList' }, 'openIdIssuerSecret' => { 'type' => 'text' }, 'openIdSecret' => { 'type' => 'text' }, 'openIdSPList' => { 'default' => '0;', 'type' => 'blackWhiteList' }, 'openIdSreg_country' => { 'type' => 'lmAttrOrMacro' }, 'openIdSreg_dob' => { 'type' => 'lmAttrOrMacro' }, 'openIdSreg_email' => { 'default' => 'mail', 'type' => 'lmAttrOrMacro' }, 'openIdSreg_fullname' => { 'default' => 'cn', 'type' => 'lmAttrOrMacro' }, 'openIdSreg_gender' => { 'type' => 'lmAttrOrMacro' }, 'openIdSreg_language' => { 'type' => 'lmAttrOrMacro' }, 'openIdSreg_nickname' => { 'default' => 'uid', 'type' => 'lmAttrOrMacro' }, 'openIdSreg_postcode' => { 'type' => 'lmAttrOrMacro' }, 'openIdSreg_timezone' => { 'default' => '_timezone', 'type' => 'lmAttrOrMacro' }, 'passwordDB' => { 'default' => 'Demo', 'select' => [ { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'Choice', 'v' => 'authChoice' }, { 'k' => 'DBI', 'v' => 'Database (DBI)' }, { 'k' => 'Demo', 'v' => 'Demonstration' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'Null', 'v' => 'None' } ], 'type' => 'select' }, 'persistentStorage' => { 'type' => 'PerlModule' }, 'persistentStorageOptions' => { 'type' => 'keyTextContainer' }, 'port' => { 'type' => 'int' }, 'portal' => { 'default' => 'http://auth.example.com/', 'type' => 'url' }, 'portalAntiFrame' => { 'default' => 1, 'type' => 'bool' }, 'portalCheckLogins' => { 'default' => 1, 'type' => 'bool' }, 'portalDisplayAppslist' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'portalDisplayChangePassword' => { 'default' => '$_auth =~ /^(LDAP|DBI|Demo)$/', 'type' => 'boolOrExpr' }, 'portalDisplayLoginHistory' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'portalDisplayLogout' => { 'default' => 1, 'type' => 'boolOrExpr' }, 'portalDisplayRegister' => { 'default' => 1, 'type' => 'bool' }, 'portalDisplayResetPassword' => { 'default' => 1, 'type' => 'bool' }, 'portalErrorOnExpiredSession' => { 'default' => 1, 'type' => 'bool' }, 'portalErrorOnMailNotFound' => { 'default' => 0, 'type' => 'bool' }, 'portalForceAuthn' => { 'default' => 0, 'type' => 'bool' }, 'portalForceAuthnInterval' => { 'default' => 5, 'type' => 'int' }, 'portalOpenLinkInNewWindow' => { 'default' => 0, 'type' => 'bool' }, 'portalPingInterval' => { 'default' => 60000, 'type' => 'int' }, 'portalRequireOldPassword' => { 'default' => 1, 'type' => 'bool' }, 'portalSkin' => { 'default' => 'bootstrap', 'select' => [ { 'k' => 'bootstrap', 'v' => 'Bootstrap' }, { 'k' => 'pastel', 'v' => 'Pastel' }, { 'k' => 'impact', 'v' => 'Impact' }, { 'k' => 'dark', 'v' => 'Dark' } ], 'type' => 'portalskin' }, 'portalSkinBackground' => { 'select' => [ { 'k' => '', 'v' => 'None' }, { 'k' => '1280px-Anse_Source_d\'Argent_2-La_Digue.jpg', 'v' => 'Anse' }, { 'k' => '1280px-Autumn-clear-water-waterfall-landscape_-_Virginia_-_ForestWander.jpg', 'v' => 'Waterfall' }, { 'k' => '1280px-BrockenSnowedTrees.jpg', 'v' => 'Snowed Trees' }, { 'k' => '1280px-Cedar_Breaks_National_Monument_partially.jpg', 'v' => 'National Monument' }, { 'k' => '1280px-Parry_Peak_from_Winter_Park.jpg', 'v' => 'Winter' }, { 'k' => 'Aletschgletscher_mit_Pinus_cembra1.jpg', 'v' => 'Pinus' } ], 'type' => 'portalskinbackground' }, 'portalSkinRules' => { 'keyMsgFail' => '__badSkinRule__', 'keyTest' => sub { my ( $val, $conf ) = @_; my $s = ''; my (@cf) = ( 'encode_base64', 'checkLogonHours', 'date', 'checkDate', 'basic', 'unicode2iso', 'iso2unicode', 'groupMatch', 'encrypt' ); push @cf, defined $conf->{'customFunctions'} ? map( { my $f = $_; $f =~ s/\w+:://g; $f, $_; } split( /\s+/, $conf->{'customFunctions'}, 0 ) ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } BEGIN { ${^WARNING_BITS} = "\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05"; } eval "$s $val"; return $@ ? ( 1, "__badExpression__: $@" ) : 1; }, 'msgFail' => '__badValue__', 'test' => qr/^\w+$/, 'type' => 'keyTextContainer' }, 'portalUserAttr' => { 'default' => '_user', 'type' => 'text' }, 'post' => { 'keyMsgFail' => '__badHostname__', 'keyTest' => qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)$/, 'test' => sub { 1; }, 'type' => 'postContainer' }, 'protection' => { 'default' => 'none', 'msgFail' => '__authorizedValues__: none authenticate manager', 'test' => qr/^(?:none|authenticate|manager|)$/, 'type' => 'text' }, 'radiusAuthnLevel' => { 'default' => 3, 'type' => 'int' }, 'radiusSecret' => { 'type' => 'text' }, 'radiusServer' => { 'type' => 'text' }, 'randomPasswordRegexp' => { 'default' => '[A-Z]{3}[a-z]{5}.\\d{2}', 'type' => 'pcre' }, 'redirectFormMethod' => { 'default' => 'get', 'select' => [ { 'k' => 'get', 'v' => 'GET' }, { 'k' => 'post', 'v' => 'POST' } ], 'type' => 'select' }, 'registerConfirmSubject' => { 'default' => '[LemonLDAP::NG] Account register confirmation', 'type' => 'text' }, 'registerDB' => { 'default' => 'Demo', 'select' => [ { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'Demo', 'v' => 'Demonstration' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'Null', 'v' => 'None' } ], 'type' => 'select' }, 'registerDoneSubject' => { 'default' => '[LemonLDAP::NG] Your new account', 'type' => 'text' }, 'registerTimeout' => { 'default' => 0, 'type' => 'int' }, 'registerUrl' => { 'default' => 'http://auth.example.com/register.pl', 'type' => 'text' }, 'reloadUrls' => { 'keyTest' => qr/^(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+))(?::\d+)?$/, 'msgFail' => '__badUrl__', 'test' => qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)/, 'type' => 'keyTextContainer' }, 'remoteCookieName' => { 'type' => 'text' }, 'remoteGlobalStorage' => { 'default' => 'Lemonldap::NG::Common::Apache::Session::SOAP', 'type' => 'PerlModule' }, 'remoteGlobalStorageOptions' => { 'default' => { 'ns' => 'http://auth.example.com/Lemonldap/NG/Common/CGI/SOAPService', 'proxy' => 'http://auth.example.com/index.pl/sessions' }, 'type' => 'keyTextContainer' }, 'remotePortal' => { 'type' => 'text' }, 'samlAttributeAuthorityDescriptorAttributeServiceSOAP' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/AA/SOAP;', 'type' => 'samlService' }, 'samlAuthnContextMapKerberos' => { 'default' => 4, 'type' => 'int' }, 'samlAuthnContextMapPassword' => { 'default' => 2, 'type' => 'int' }, 'samlAuthnContextMapPasswordProtectedTransport' => { 'default' => 3, 'type' => 'int' }, 'samlAuthnContextMapTLSClient' => { 'default' => 5, 'type' => 'int' }, 'samlCommonDomainCookieActivation' => { 'default' => 0, 'type' => 'bool' }, 'samlCommonDomainCookieDomain' => { 'msgFail' => '__badDomainName__', 'test' => qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)$/, 'type' => 'text' }, 'samlCommonDomainCookieReader' => { 'msgFail' => '__badUrl__', 'test' => qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)/, 'type' => 'text' }, 'samlCommonDomainCookieWriter' => { 'msgFail' => '__badUrl__', 'test' => qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::(?:(?:[0-9]*)))?(?:\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:\/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?](?:(?:(?:[;\/?:@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)/, 'type' => 'text' }, 'samlEntityID' => { 'default' => '#PORTAL#/saml/metadata', 'type' => 'text' }, 'samlIDPMetaDataExportedAttributes' => { 'default' => {}, 'keyMsgFail' => '__badMetadataName__', 'keyTest' => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, 'msgFail' => '__badValue__', 'test' => qr/\w/, 'type' => 'samlAttributeContainer' }, 'samlIDPMetaDataNodes' => { 'type' => 'samlIDPMetaDataNodeContainer' }, 'samlIDPMetaDataOptions' => { 'keyMsgFail' => '__badMetadataName__', 'keyTest' => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, 'type' => 'keyTextContainer' }, 'samlIDPMetaDataOptionsAdaptSessionUtime' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsAllowLoginFromIDP' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsAllowProxiedAuthn' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsCheckAudience' => { 'default' => 1, 'type' => 'bool' }, 'samlIDPMetaDataOptionsCheckSLOMessageSignature' => { 'default' => 1, 'type' => 'bool' }, 'samlIDPMetaDataOptionsCheckSSOMessageSignature' => { 'default' => 1, 'type' => 'bool' }, 'samlIDPMetaDataOptionsCheckTime' => { 'default' => 1, 'type' => 'bool' }, 'samlIDPMetaDataOptionsEncryptionMode' => { 'default' => 'none', 'select' => [ { 'k' => 'none', 'v' => 'None' }, { 'k' => 'nameid', 'v' => 'Name ID' }, { 'k' => 'assertion', 'v' => 'Assertion' } ], 'type' => 'select' }, 'samlIDPMetaDataOptionsForceAuthn' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsForceUTF8' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsIsPassive' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsNameIDFormat' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'unspecified', 'v' => 'Unspecified' }, { 'k' => 'email', 'v' => 'Email' }, { 'k' => 'x509', 'v' => 'X509 certificate' }, { 'k' => 'windows', 'v' => 'Windows' }, { 'k' => 'kerberos', 'v' => 'Kerberos' }, { 'k' => 'entity', 'v' => 'Entity' }, { 'k' => 'persistent', 'v' => 'Persistent' }, { 'k' => 'transient', 'v' => 'Transient' }, { 'k' => 'encrypted', 'v' => 'Encrypted' } ], 'type' => 'select' }, 'samlIDPMetaDataOptionsRelayStateURL' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataOptionsRequestedAuthnContext' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'kerberos', 'v' => 'Kerberos' }, { 'k' => 'password-protected-transport', 'v' => 'Password protected transport' }, { 'k' => 'password', 'v' => 'Password' }, { 'k' => 'tls-client', 'v' => 'TLS client certificate' } ], 'type' => 'select' }, 'samlIDPMetaDataOptionsResolutionRule' => { 'default' => '', 'type' => 'longtext' }, 'samlIDPMetaDataOptionsSignSLOMessage' => { 'default' => -1, 'type' => 'trool' }, 'samlIDPMetaDataOptionsSignSSOMessage' => { 'default' => -1, 'type' => 'trool' }, 'samlIDPMetaDataOptionsSLOBinding' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'http-post', 'v' => 'POST' }, { 'k' => 'http-redirect', 'v' => 'Redirect' }, { 'k' => 'http-soap', 'v' => 'SOAP' }, { 'k' => 'artifact-get', 'v' => 'Artifact GET' }, { 'k' => 'artifact-post', 'v' => 'Artifact POST' } ], 'type' => 'select' }, 'samlIDPMetaDataOptionsSSOBinding' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'http-post', 'v' => 'POST' }, { 'k' => 'http-redirect', 'v' => 'Redirect' }, { 'k' => 'http-soap', 'v' => 'SOAP' }, { 'k' => 'artifact-get', 'v' => 'Artifact GET' }, { 'k' => 'artifact-post', 'v' => 'Artifact POST' } ], 'type' => 'select' }, 'samlIDPMetaDataOptionsStoreSAMLToken' => { 'default' => 0, 'type' => 'bool' }, 'samlIDPMetaDataXML' => { 'type' => 'file' }, 'samlIdPResolveCookie' => { 'default' => 'lemonldapidp', 'type' => 'text' }, 'samlIDPSSODescriptorArtifactResolutionServiceArtifact' => { 'default' => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact', 'type' => 'samlAssertion' }, 'samlIDPSSODescriptorSingleLogoutServiceHTTPPost' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;#PORTAL#/saml/singleLogout;#PORTAL#/saml/singleLogoutReturn', 'type' => 'samlService' }, 'samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;#PORTAL#/saml/singleLogout;#PORTAL#/saml/singleLogoutReturn', 'type' => 'samlService' }, 'samlIDPSSODescriptorSingleLogoutServiceSOAP' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/singleLogoutSOAP;', 'type' => 'samlService' }, 'samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;#PORTAL#/saml/singleSignOnArtifact;', 'type' => 'samlService' }, 'samlIDPSSODescriptorSingleSignOnServiceHTTPPost' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;#PORTAL#/saml/singleSignOn;', 'type' => 'samlService' }, 'samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;#PORTAL#/saml/singleSignOn;', 'type' => 'samlService' }, 'samlIDPSSODescriptorSingleSignOnServiceSOAP' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/singleSignOnSOAP;', 'type' => 'samlService' }, 'samlIDPSSODescriptorWantAuthnRequestsSigned' => { 'default' => 1, 'type' => 'bool' }, 'samlMetadataForceUTF8' => { 'default' => 1, 'type' => 'bool' }, 'samlNameIDFormatMapEmail' => { 'default' => 'mail', 'type' => 'text' }, 'samlNameIDFormatMapKerberos' => { 'default' => 'uid', 'type' => 'text' }, 'samlNameIDFormatMapWindows' => { 'default' => 'uid', 'type' => 'text' }, 'samlNameIDFormatMapX509' => { 'default' => 'mail', 'type' => 'text' }, 'samlOrganizationDisplayName' => { 'default' => 'Example', 'type' => 'text' }, 'samlOrganizationName' => { 'default' => 'Example', 'type' => 'text' }, 'samlOrganizationURL' => { 'default' => 'http://www.example.com', 'type' => 'text' }, 'samlRelayStateTimeout' => { 'default' => 600, 'type' => 'int' }, 'samlServicePrivateKeyEnc' => { 'default' => '', 'type' => 'RSAPrivateKey' }, 'samlServicePrivateKeyEncPwd' => { 'type' => 'password' }, 'samlServicePrivateKeySig' => { 'default' => '', 'type' => 'RSAPrivateKey' }, 'samlServicePrivateKeySigPwd' => { 'default' => '', 'type' => 'password' }, 'samlServicePublicKeyEnc' => { 'default' => '', 'type' => 'RSAPublicKeyOrCertificate' }, 'samlServicePublicKeySig' => { 'default' => '', 'type' => 'RSAPublicKeyOrCertificate' }, 'samlServiceUseCertificateInResponse' => { 'default' => 0, 'type' => 'bool' }, 'samlSPMetaDataExportedAttributes' => { 'default' => {}, 'keyMsgFail' => '__badMetadataName__', 'keyTest' => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, 'msgFail' => '__badValue__', 'test' => qr/\w/, 'type' => 'samlAttributeContainer' }, 'samlSPMetaDataNodes' => { 'type' => 'samlSPMetaDataNodeContainer' }, 'samlSPMetaDataOptions' => { 'keyMsgFail' => '__badMetadataName__', 'keyTest' => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, 'type' => 'keyTextContainer' }, 'samlSPMetaDataOptionsCheckSLOMessageSignature' => { 'default' => 1, 'type' => 'bool' }, 'samlSPMetaDataOptionsCheckSSOMessageSignature' => { 'default' => 1, 'type' => 'bool' }, 'samlSPMetaDataOptionsEnableIDPInitiatedURL' => { 'default' => 0, 'type' => 'bool' }, 'samlSPMetaDataOptionsEncryptionMode' => { 'default' => 'none', 'select' => [ { 'k' => 'none', 'v' => 'None' }, { 'k' => 'nameid', 'v' => 'Name ID' }, { 'k' => 'assertion', 'v' => 'Assertion' } ], 'type' => 'select' }, 'samlSPMetaDataOptionsForceUTF8' => { 'default' => 1, 'type' => 'bool' }, 'samlSPMetaDataOptionsNameIDFormat' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'unspecified', 'v' => 'Unspecified' }, { 'k' => 'email', 'v' => 'Email' }, { 'k' => 'x509', 'v' => 'X509 certificate' }, { 'k' => 'windows', 'v' => 'Windows' }, { 'k' => 'kerberos', 'v' => 'Kerberos' }, { 'k' => 'entity', 'v' => 'Entity' }, { 'k' => 'persistent', 'v' => 'Persistent' }, { 'k' => 'transient', 'v' => 'Transient' }, { 'k' => 'encrypted', 'v' => 'Encrypted' } ], 'type' => 'select' }, 'samlSPMetaDataOptionsNameIDSessionKey' => { 'type' => 'text' }, 'samlSPMetaDataOptionsNotOnOrAfterTimeout' => { 'default' => 72000, 'type' => 'int' }, 'samlSPMetaDataOptionsOneTimeUse' => { 'default' => 0, 'type' => 'bool' }, 'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => { 'default' => 72000, 'type' => 'int' }, 'samlSPMetaDataOptionsSignSLOMessage' => { 'default' => -1, 'type' => 'trool' }, 'samlSPMetaDataOptionsSignSSOMessage' => { 'default' => -1, 'type' => 'trool' }, 'samlSPMetaDataXML' => { 'type' => 'file' }, 'samlSPSSODescriptorArtifactResolutionServiceArtifact' => { 'default' => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/artifact', 'type' => 'samlAssertion' }, 'samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact' => { 'default' => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;#PORTAL#/saml/proxySingleSignOnArtifact', 'type' => 'samlAssertion' }, 'samlSPSSODescriptorAssertionConsumerServiceHTTPPost' => { 'default' => '0;1;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;#PORTAL#/saml/proxySingleSignOnPost', 'type' => 'samlAssertion' }, 'samlSPSSODescriptorAuthnRequestsSigned' => { 'default' => 1, 'type' => 'bool' }, 'samlSPSSODescriptorSingleLogoutServiceHTTPPost' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;#PORTAL#/saml/proxySingleLogout;#PORTAL#/saml/proxySingleLogoutReturn', 'type' => 'samlService' }, 'samlSPSSODescriptorSingleLogoutServiceHTTPRedirect' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;#PORTAL#/saml/proxySingleLogout;#PORTAL#/saml/proxySingleLogoutReturn', 'type' => 'samlService' }, 'samlSPSSODescriptorSingleLogoutServiceSOAP' => { 'default' => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;#PORTAL#/saml/proxySingleLogoutSOAP;', 'type' => 'samlService' }, 'samlSPSSODescriptorWantAssertionsSigned' => { 'default' => 1, 'type' => 'bool' }, 'samlStorage' => { 'type' => 'PerlModule' }, 'samlStorageOptions' => { 'type' => 'keyTextContainer' }, 'samlUseQueryStringSpecific' => { 'default' => 0, 'type' => 'bool' }, 'securedCookie' => { 'default' => 0, 'select' => [ { 'k' => '0', 'v' => 'unsecuredCookie' }, { 'k' => '1', 'v' => 'securedCookie' }, { 'k' => '2', 'v' => 'doubleCookie' }, { 'k' => '3', 'v' => 'doubleCookieForSingleSession' } ], 'type' => 'select' }, 'secureTokenAllowOnError' => { 'default' => 1, 'type' => 'bool' }, 'secureTokenAttribute' => { 'default' => 'uid', 'type' => 'text' }, 'secureTokenExpiration' => { 'default' => 60, 'type' => 'int' }, 'secureTokenHeader' => { 'default' => 'Auth-Token', 'type' => 'text' }, 'secureTokenMemcachedServers' => { 'default' => '127.0.0.1:11211', 'type' => 'text' }, 'secureTokenUrls' => { 'default' => '.*', 'type' => 'pcre' }, 'sessionDataToRemember' => { 'keyMsgFail' => '__invalidSessionData__', 'keyTest' => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, 'type' => 'keyTextContainer' }, 'singleIP' => { 'default' => 0, 'type' => 'bool' }, 'singleSession' => { 'default' => 0, 'type' => 'bool' }, 'singleSessionUserByIP' => { 'default' => 0, 'type' => 'bool' }, 'singleUserByIP' => { 'default' => 0, 'type' => 'bool' }, 'slaveAuthnLevel' => { 'default' => 2, 'type' => 'int' }, 'slaveExportedVars' => { 'default' => {}, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'slaveHeaderContent' => { 'type' => 'text' }, 'slaveHeaderName' => { 'type' => 'text' }, 'slaveMasterIP' => { 'msgFail' => '__badIPv4Address__', 'test' => qr/^((?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)\s*)*$/, 'type' => 'text' }, 'slaveUserHeader' => { 'type' => 'text' }, 'SMTPAuthPass' => { 'type' => 'password' }, 'SMTPAuthUser' => { 'type' => 'text' }, 'SMTPServer' => { 'default' => '', 'test' => qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+))(?::\d+)?)?$/, 'type' => 'text' }, 'Soap' => { 'default' => 0, 'type' => 'bool' }, 'soapAuthService' => { 'type' => 'text' }, 'soapSessionService' => { 'type' => 'text' }, 'SSLAuthnLevel' => { 'default' => 5, 'type' => 'int' }, 'SSLVar' => { 'type' => 'text' }, 'staticPrefix' => { 'type' => 'text' }, 'storePassword' => { 'default' => 0, 'type' => 'bool' }, 'successLoginNumber' => { 'default' => 5, 'type' => 'int' }, 'sympaMailKey' => { 'type' => 'text' }, 'sympaSecret' => { 'type' => 'text' }, 'syslog' => { 'default' => '', 'msgFail' => '__authorizedValues__: auth, authpriv, daemon, local0-7, user', 'test' => qr/^(?:auth|authpriv|daemon|local\d|user)?$/, 'type' => 'text' }, 'timeout' => { 'default' => 72000, 'test' => sub { $_[0] > 0; }, 'type' => 'int' }, 'timeoutActivity' => { 'default' => 0, 'test' => sub { $_[0] >= 0; }, 'type' => 'int' }, 'timeoutActivityInterval' => { 'default' => 60, 'test' => sub { $_[0] >= 0; }, 'type' => 'int' }, 'trustedDomains' => { 'type' => 'text' }, 'trustedProxies' => { 'default' => '', 'type' => 'text' }, 'twitterAppName' => { 'type' => 'text' }, 'twitterAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'twitterKey' => { 'type' => 'text' }, 'twitterSecret' => { 'type' => 'text' }, 'userControl' => { 'default' => '^[\\w\\.\\-@]+$', 'type' => 'pcre' }, 'userDB' => { 'default' => 'Demo', 'select' => [ { 'k' => 'AD', 'v' => 'Active Directory' }, { 'k' => 'DBI', 'v' => 'Database (DBI)' }, { 'k' => 'Choice', 'v' => 'authChoice' }, { 'k' => 'Demo', 'v' => 'Demonstration' }, { 'k' => 'Facebook', 'v' => 'Facebook' }, { 'k' => 'Google', 'v' => 'Google' }, { 'k' => 'LDAP', 'v' => 'LDAP' }, { 'k' => 'Multi', 'v' => 'Multiple' }, { 'k' => 'Null', 'v' => 'None' }, { 'k' => 'OpenID', 'v' => 'OpenID' }, { 'k' => 'OpenIDConnect', 'v' => 'OpenID Connect' }, { 'k' => 'Proxy', 'v' => 'Proxy' }, { 'k' => 'Remote', 'v' => 'Remote' }, { 'k' => 'SAML', 'v' => 'SAML v2' }, { 'k' => 'Slave', 'v' => 'Slave' }, { 'k' => 'WebID', 'v' => 'WebID' } ], 'type' => 'select' }, 'useRedirectOnError' => { 'default' => 1, 'type' => 'bool' }, 'useRedirectOnForbidden' => { 'default' => 0, 'type' => 'bool' }, 'userPivot' => { 'type' => 'text' }, 'useSafeJail' => { 'default' => 1, 'type' => 'bool' }, 'vhostAliases' => { 'type' => 'text' }, 'vhostHttps' => { 'default' => -1, 'type' => 'trool' }, 'vhostMaintenance' => { 'default' => 0, 'type' => 'bool' }, 'vhostOptions' => { 'type' => 'subContainer' }, 'vhostPort' => { 'default' => -1, 'type' => 'int' }, 'virtualHosts' => { 'type' => 'virtualHostContainer' }, 'webIDAuthnLevel' => { 'default' => 1, 'type' => 'int' }, 'webIDExportedVars' => { 'default' => {}, 'keyMsgFail' => '__badVariableName__', 'keyTest' => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, 'msgFail' => '__badValue__', 'test' => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, 'type' => 'keyTextContainer' }, 'webIDWhitelist' => { 'type' => 'text' }, 'whatToTrace' => { 'default' => 'uid', 'type' => 'lmAttrOrMacro' }, 'yubikeyAuthnLevel' => { 'default' => 3, 'type' => 'int' }, 'yubikeyClientID' => { 'type' => 'text' }, 'yubikeyPublicIDSize' => { 'default' => 12, 'type' => 'int' }, 'yubikeySecretKey' => { 'type' => 'text' }, 'zimbraAccountKey' => { 'type' => 'text' }, 'zimbraBy' => { 'default' => '', 'select' => [ { 'k' => '', 'v' => '' }, { 'k' => 'name', 'v' => 'User name' }, { 'k' => 'id', 'v' => 'User id' }, { 'k' => 'foreignPrincipal', 'v' => 'Foreign principal' } ], 'type' => 'select' }, 'zimbraPreAuthKey' => { 'type' => 'text' }, 'zimbraSsoUrl' => { 'type' => 'text' }, 'zimbraUrl' => { 'type' => 'text' } }; } Build.pm000066400000000000000000000511431325274564300340320ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Managerpackage Lemonldap::NG::Manager::Build; use strict; use utf8; use Mouse; use Lemonldap::NG::Manager::Build::Attributes; use Lemonldap::NG::Manager::Build::Tree; use Lemonldap::NG::Manager::Build::CTrees; use Lemonldap::NG::Manager::Conf::Zero; use Data::Dumper; use Regexp::Assemble; use JSON; use Getopt::Std; use IO::String; our $VERSION = '1.9.7'; has structFile => ( isa => 'Str', is => 'ro', required => 1 ); has confTreeFile => ( isa => 'Str', is => 'ro', required => 1 ); has managerConstantsFile => ( isa => 'Str', is => 'ro', required => 1 ); has managerAttributesFile => ( isa => 'Str', is => 'ro', required => 1 ); has defaultValuesFile => ( isa => 'Str', is => 'ro', required => 1 ); has confConstantsFile => ( isa => 'Str', is => 'ro', required => 1 ); has firstLmConfFile => ( isa => 'Str', is => 'ro', required => 1 ); my @managerAttrKeys = qw(keyTest keyMsgFail select type test msgFail default); my $format = 'Creating %-69s: '; my $reIgnoreKeys = qr/^$/; my $module = __PACKAGE__; my @angularScopeVars; my @cnodesKeys; my %cnodesRe; my @ignoreKeys; my $ignoreKeys; my $mainTree; my @sessionTypes; my @simpleHashKeys; my @doubleHashKeys; my $defaultValues; my $attributes = Lemonldap::NG::Manager::Build::Attributes::attributes(); my $jsonEnc = JSON->new()->allow_nonref; $jsonEnc->canonical(1); $Data::Dumper::Sortkeys = sub { my ($hash) = @_; return [ ( defined $hash->{id} ? ('id') : () ), ( defined $hash->{title} ? ( 'title', ) : () ), ( grep { /^(?:id|title)$/ ? 0 : 1 } sort { return 1 if ( $a =~ /node/ and $b !~ /node/ ); return -1 if ( $b =~ /node/ ); lc($a) cmp lc($b); } keys %$hash ) ]; }; $Data::Dumper::Deparse = 1; $Data::Dumper::Deepcopy = 1; sub run { my $self = shift; $self = $module->new(@_) unless ref $self; # 1. confTree.js printf STDERR $format, $self->confTreeFile; $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); my $script = 'function templates(tpl,key) { var ind; var scalarTemplate = function(r) { return { "id": tpl+"s/"+(ind++), "title": r, "get": tpl+"s/"+key+"/"+r }; }; switch(tpl){ '; # To build confTree.js, each special node is scanned from # Lemonldap::NG::Manager::Build::CTrees foreach my $node ( sort keys %$mainTree ) { @cnodesKeys = (); my $jsonTree = []; $self->scanTree( $mainTree->{$node}, $jsonTree, '__KEY__', '' ); $jsonEnc->pretty(1); my $tmp = $jsonEnc->encode($jsonTree); $tmp =~ s!"__KEY__!tpl+"s/"+key+"/"+"!mg; $tmp =~ s/"(true|false)"/$1/sg; $tmp =~ s/:\s*"(\d+)"\s*(["\}])/:$1$2/sg; $script .= " case '$node': return $tmp; "; # Second step, Manager/Constants.pm file will contain datas issued from # this scan my $ra = Regexp::Assemble->new; # Build $oidcOPMetaDataNodeKeys, $samlSPMetaDataNodeKeys,... foreach my $r (@cnodesKeys) { $ra->add($r); } $cnodesRe{$node} = $ra->as_string; push @ignoreKeys, $node; } $script .= " default:\n return [];\n }\n}"; open F, ">", $self->confTreeFile or die $!; print F $script; close F; print STDERR "done\n"; my $ra = Regexp::Assemble->new; foreach my $re (@ignoreKeys) { $ra->add($re); } $ignoreKeys = $ra->as_string; $reIgnoreKeys = $ra->re; # Reinitialize $defaultValues $defaultValues = {}; # 2. struct.json printf STDERR $format, $self->structFile; $mainTree = Lemonldap::NG::Manager::Build::Tree::tree(); my $jsonTree = []; $self->scanTree( $mainTree, $jsonTree, '', '' ); $script = "\n\nfunction setScopeVars(scope) {\n"; foreach my $v (@angularScopeVars) { $script .= " scope.$v->[0] = scope$v->[1];\n scope.getKey(scope.$v->[0]);\n"; } $script .= "}"; open F, ">>", $self->confTreeFile || die $!; print F $script; close F; open F, ">", $self->structFile || die $!; $jsonEnc->pretty(0); my $tmp = $jsonEnc->encode($jsonTree); $tmp =~ s/"(true|false)"/$1/sg; $tmp =~ s/:\s*"(\d+)"\s*(["\}])/:$1$2/sg; print F $tmp; close F; print STDERR "done\n"; $tmp = undef; printf STDERR $format, $self->managerConstantsFile; my $sessionTypes = join( "', '", @sessionTypes ); open F, ">", $self->managerConstantsFile or die($!); my $exportedVars = '$' . join( 'Keys $', 'simpleHash', 'specialNode', 'doubleHash', sort keys %cnodesRe ) . 'Keys $specialNodeHash @sessionTypes'; print F < [qw($exportedVars)] ); our \@EXPORT_OK = ( \@{ \$EXPORT_TAGS{'all'} } ); our \@EXPORT = ( \@{ \$EXPORT_TAGS{'all'} } ); our \$specialNodeHash = { virtualHosts => [qw(exportedHeaders locationRules post vhostOptions)], samlIDPMetaDataNodes => [qw(samlIDPMetaDataXML samlIDPMetaDataExportedAttributes samlIDPMetaDataOptions)], samlSPMetaDataNodes => [qw(samlSPMetaDataXML samlSPMetaDataExportedAttributes samlSPMetaDataOptions)], oidcOPMetaDataNodes => [qw(oidcOPMetaDataJSON oidcOPMetaDataJWKS oidcOPMetaDataOptions oidcOPMetaDataExportedVars)], oidcRPMetaDataNodes => [qw(oidcRPMetaDataOptions oidcRPMetaDataExportedVars oidcRPMetaDataOptionsExtraClaims)], }; our \@sessionTypes = ( '$sessionTypes' ); EOF # Reinitialize $attributes $attributes = Lemonldap::NG::Manager::Build::Attributes::attributes(); $ra = Regexp::Assemble->new; foreach (@doubleHashKeys) { $ra->add($_); } print F "our \$doubleHashKeys = '" . $ra->as_string . "';\n"; $ra = Regexp::Assemble->new; foreach (@simpleHashKeys) { $ra->add($_); } print F "our \$simpleHashKeys = '" . $ra->as_string . "';\n" . "our \$specialNodeKeys = '${ignoreKeys}s';\n"; foreach ( sort keys %cnodesRe ) { print F "our \$${_}Keys = '$cnodesRe{$_}';\n"; } print F "\n1;\n"; close F; print STDERR "done\n"; printf STDERR $format, $self->defaultValuesFile; $defaultValues->{locationRules} = $attributes->{locationRules}->{default}; my $defaultAttr = mydump( $defaultValues, 'defaultValues' ); $defaultAttr = "# This file is generated by $module. Don't modify it by hand package Lemonldap::NG::Common::Conf::DefaultValues; our \$VERSION = '$Lemonldap::NG::Manager::Build::Attributes::VERSION'; $defaultAttr} 1; "; my $dst; eval { require Perl::Tidy; Perl::Tidy::perltidy( source => IO::String->new($defaultAttr), destination => \$dst ); }; $dst = $defaultAttr if ($@); open( F, ">", $self->defaultValuesFile ) or die($!); print F $dst; close F; print STDERR "done\n"; printf STDERR $format, $self->confConstantsFile; $ra = Regexp::Assemble->new; foreach ( @simpleHashKeys, @doubleHashKeys, sort keys %cnodesRe ) { $ra->add($_); } foreach ( qw(exportedHeaders locationRules post vhostOptions samlIDPMetaDataXML samlIDPMetaDataExportedAttributes samlIDPMetaDataOptions samlSPMetaDataXML samlSPMetaDataExportedAttributes samlSPMetaDataOptions oidcOPMetaDataJSON oidcOPMetaDataJWKS oidcOPMetaDataOptions oidcOPMetaDataExportedVars oidcRPMetaDataOptions oidcRPMetaDataExportedVars oidcRPMetaDataOptionsExtraClaims) ) { $ra->add($_); } my $confConstants = "our \$hashParameters = qr/^" . $ra->as_string . "\$/;\n"; open( F, ">", $self->confConstantsFile ) or die($!); print F < -1; use constant UNKNOWN_ERROR => -2; use constant DATABASE_LOCKED => -3; use constant UPLOAD_DENIED => -4; use constant SYNTAX_ERROR => -5; use constant DEPRECATED => -6; use constant DEFAULTCONFFILE => "/usr/local/lemonldap-ng/etc/lemonldap-ng.ini"; use constant DEFAULTSECTION => "all"; use constant CONFSECTION => "configuration"; use constant PORTALSECTION => "portal"; use constant HANDLERSECTION => "handler"; use constant MANAGERSECTION => "manager"; use constant SESSIONSEXPLORERSECTION => "sessionsExplorer"; use constant APPLYSECTION => "apply"; $confConstants our %EXPORT_TAGS = ( 'all' => [ qw( CONFIG_WAS_CHANGED UNKNOWN_ERROR DATABASE_LOCKED UPLOAD_DENIED SYNTAX_ERROR DEPRECATED DEFAULTCONFFILE DEFAULTSECTION CONFSECTION PORTALSECTION HANDLERSECTION MANAGERSECTION SESSIONSEXPLORERSECTION APPLYSECTION \$hashParameters ) ] ); our \@EXPORT_OK = ( \@{ \$EXPORT_TAGS{'all'} } ); our \@EXPORT = ( \@{ \$EXPORT_TAGS{'all'} } ); 1; EOF close F; print STDERR "done\n"; printf STDERR $format, $self->managerAttributesFile; my $managerAttr = { map { my @r; foreach my $f (@managerAttrKeys) { push @r, $f, $attributes->{$_}->{$f} if ( defined $attributes->{$_}->{$f} ); } ( $_ => {@r} ); } keys(%$attributes) }; $managerAttr = mydump( $managerAttr, 'attributes' ); my $managerTypes = mydump( Lemonldap::NG::Manager::Build::Attributes::types(), 'types' ); $managerAttr = "# This file is generated by $module. Don't modify it by hand package Lemonldap::NG::Manager::Attributes; our \$VERSION = '$Lemonldap::NG::Manager::Build::Attributes::VERSION'; $managerTypes} $managerAttr} "; eval { Perl::Tidy::perltidy( source => IO::String->new($managerAttr), destination => \$dst ); }; $dst = $managerAttr if ($@); open( F, ">", $self->managerAttributesFile ) or die($!); print F $dst; close F; print STDERR "done\n"; $self->buildZeroConf(); } sub buildZeroConf { my $self = shift; $jsonEnc->pretty(1); printf STDERR $format, $self->firstLmConfFile; open( F, '>', $self->firstLmConfFile ) or die($!); my $tmp = Lemonldap::NG::Manager::Conf::Zero::zeroConf( '__DNSDOMAIN__', '__SESSIONDIR__', '__PSESSIONDIR__', '__NOTIFICATIONDIR__' ); $tmp->{cfgNum} = 1; print F $jsonEnc->encode($tmp); close F; print STDERR "done\n"; } sub mydump { my ( $obj, $subname ) = @_; my $t = Dumper($obj); $t =~ s/^\s*(?:use strict;|package .*?;|)\n//gm; $t =~ s/^\$VAR1\s*=/sub $subname {\n return/; return $t; } sub scanTree { my ( $self, $tree, $json, $prefix, $path ) = @_; unless ( ref($tree) eq 'ARRAY' ) { die 'Not an array'; } $prefix //= ''; my $ord = -1; my $nodeName = $path ? '_nodes' : 'data'; foreach my $leaf (@$tree) { $ord++; my $jleaf = {}; # Grouped leaf if ( ref($leaf) and $leaf->{group} ) { die "'form' is required when using 'group'" unless ( $leaf->{form} ); push @$json, { id => "$prefix$leaf->{title}", title => $leaf->{title}, type => $leaf->{form}, get => $leaf->{group} }; } # Subnode elsif ( ref($leaf) ) { $jleaf->{title} = $jleaf->{id} = $leaf->{title}; $jleaf->{type} = $leaf->{form} if ( $leaf->{form} ); foreach my $n (qw(nodes nodes_cond)) { if ( $leaf->{$n} ) { $jleaf->{"_$n"} = []; $self->scanTree( $leaf->{$n}, $jleaf->{"_$n"}, $prefix, "$path.$nodeName\[$ord\]" ); if ( $n eq 'nodes_cond' ) { foreach my $sn ( @{ $jleaf->{"_$n"} } ) { $sn->{show} = 'false'; } } } } $jleaf->{help} = $leaf->{help} if ( $leaf->{help} ); $jleaf->{_nodes_filter} = $leaf->{nodes_filter} if ( $leaf->{nodes_filter} ); push @$json, $jleaf; } # Leaf else { # Get data type and build tree # # Types : PerlModule bool boolOrExpr catAndAppList file hostname int # keyTextContainer lmAttrOrMacro longtext openidServerList pcre # rulesContainer samlAssertion samlAttributeContainer samlService # select text trool url virtualHostContainer word # password if ( $leaf =~ s/^\*// ) { push @angularScopeVars, [ $leaf, "$path._nodes[$ord]" ]; } push @sessionTypes, $1 if ( $leaf =~ /^(.*)(?{$leaf} or die("Missing attribute $leaf"); $jleaf = { id => "$prefix$leaf", title => $leaf }; unless ( $attr->{type} ) { print STDERR "Fatal: no type: $leaf\n"; exit; } # TODO: change this $attr->{type} =~ s/^(?:url|word|pcre|lmAttrOrMacro|hostname|PerlModule)$/text/; $jleaf->{type} = $attr->{type} if ( $attr->{type} ne 'text' ); foreach my $w (qw(default help select get template)) { $jleaf->{$w} = $attr->{$w} if ( defined $attr->{$w} ); } if ( defined $jleaf->{default} ) { $defaultValues->{$leaf} = $jleaf->{default}; if ( ref( $jleaf->{default} ) ) { $jleaf->{default} = []; my $type = $attr->{type}; $type =~ s/Container//; foreach my $k ( sort keys( %{ $attr->{default} } ) ) { push @{ $jleaf->{default} }, { id => "$prefix$leaf/$k", title => $k, type => $type, data => $attr->{default}->{$k}, ( $type eq 'rule' ? ( re => $k ) : () ), }; } } } if ($prefix) { push @cnodesKeys, $leaf; } if ( $attr->{type} =~ /^(?:catAndAppList|\w+Container)$/ ) { $jleaf->{cnodes} = $prefix . $leaf; unless ( $prefix or $leaf =~ $reIgnoreKeys ) { push @simpleHashKeys, $leaf; } } elsif ( $attr->{type} eq 'doubleHash' and $leaf !~ $reIgnoreKeys ) { push @doubleHashKeys, $leaf; } else { if ( $prefix and !$jleaf->{get} ) { $jleaf->{get} = $prefix . $jleaf->{title}; } } push @$json, $jleaf; } } } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager::Build - Static files generator of Lemonldap::NG Web-SSO system. =head1 SYNOPSIS use Lemonldap::NG::Manager::Build; Lemonldap::NG::Manager::Build->run( structFile => "site/static/struct.json", confTreeFile => "site/static/js/conftree.js", managerConstantsFile => "lib/Lemonldap/NG/Manager/Constants.pm", managerAttributesFile => 'lib/Lemonldap/NG/Manager/Attributes.pm', defaultValuesFile => "lib/Lemonldap/NG/Common/Conf/DefaultValues.pm", firstLmConfFile => "_example/conf/lmConf-1.js", ); =head1 DESCRIPTION Lemonldap::NG::Manager::Build is used only to build javascript files and Lemonldap::NG constants Perl files. It has to be launched after each change. =head2 DEVELOPER CORNER To add a new parameter, you have to: =over =item declare it in Manager/Build/Attributes.pm; =item declare its position in the tree in Manager/Build/Tree.pm (or Manager/Build/CTrees.pm for complex nodes); =item refresh files using this (or launch any build makefile target at the root of the Lemonldap::NG project sources). =back See below for details. =head3 Attribute declaration Set your new attribute as a key of attributes() function that points to a hash ref containing: =over =item type (required): the type of the content. It must be declared in sub types() in the same file (except if attribute embeds its own tests) and must correspond to a form stored in the static/forms/ directory; =item help (optional): the relative HTML path to the help page (relative to /doc/pages/documentation//); =item default (recommended): a default value to set if not defined; =item select (optional): required only if type is `select`. In this case, it must contains an array of { k => , v => } hashref =item documentation (recommended): some words for other developpers =item test (optional): if test is not defined for this type or if test must be more restrictive, set her a regular expression or a subroutine. Arguments passed to subroutine are (keyValue, newConf, currentKey), it returns 2 arguments: a boolean result and a message (if non empty will be displayed as warning or error depending of result); =item msgFail (optional): for regexp based tests, message to display in case of error. Words to translate have to be written as so: __toTranslate__; =item keyTest (optional): for keys/values attributes, test to be applied on key; =item keyMsgFail (optional): for regexp based key tests, same as msgFail for keys test; =back If you decide to declare a new type, you have to declare the following properties: =over =item test, msgFail, keyTest, keyMsgFail as shown above, =item form: the form to use if it doesn't have the same name. =back =head3 Tree positioning The tree is now very simple: it contains nodes and leaf. Leaf are designed only by their attribute name. All description must be done in the file described above. Nodes are array member designed as this: { title => 'titleToTranslate', help => 'helpUrl', form => 'relativeUrl', nodes => [ ... nodes or leaf ... ] } Explanations: =over =item title (required): it must contain an entry of static/languages/lang.json =item help (recommended): as above, the relative HTML path to the help page (relative to /doc/pages/documentation//); =item form (optional): the name of a static/forms/.html file =item nodes: array of sub nodes and leaf attached to this node =item group: must never be used in conjunction with nodes. Array of leafs only to be displayed in the same form =item nodes_cond: array of sub nodes that will be displayed with a filter. Not yet documented here, see the source code of site/static/js/filterFunctions.js. =item nodes_filter: filter entry in site/static/js/filterFunctions.js for the same feature. =back =head1 SEE ALSO L, L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Build/000077500000000000000000000000001325274564300334705ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/ManagerAttributes.pm000066400000000000000000002656561325274564300362000ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build# This file contains the description of all configuration parameters # It may be included only by batch files, never in portal or handler chain # for performances reasons # DON'T FORGET TO RUN jsongenerator.pl AFTER EACH CHANGE package Lemonldap::NG::Manager::Build::Attributes; our $VERSION = '1.9.11'; use strict; use Regexp::Common qw/URI/; my $perlExpr = sub { my ( $val, $conf ) = @_; my $s = ''; my @cf = qw( encode_base64 checkLogonHours date checkDate basic unicode2iso iso2unicode groupMatch encrypt ); push @cf, defined $conf->{customFunctions} ? map { my $f = $_; $f =~ s/\w+:://g; ( $f, $_ ) } split( /\s+/, $conf->{customFunctions} ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } no warnings( 'redefine', 'uninitialized' ); eval "$s $val"; return $@ ? ( 1, "__badExpression__: $@" ) : (1); }; my $url = $RE{URI}{HTTP}{ -scheme => "https?" }; $url =~ s/(?<=[^\\])\$/\\\$/g; $url = qr/$url/; sub types { return { # Simple text types text => { test => sub { 1 }, msgFail => '__malformedValue__', }, password => { test => sub { 1 }, msgFail => '__malformedValue__', }, longtext => { test => sub { 1 } }, url => { form => 'text', test => $url, msgFail => '__badUrl__', }, PerlModule => { form => 'text', test => qr/^[a-zA-Z][a-zA-Z0-9]*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/, msgFail => '__badPerlPackageName__', }, hostname => { form => 'text', test => qr/^(?:$Regexp::Common::URI::RFC2396::host)?$/, msgFail => '__badHostname__', }, pcre => { form => 'text', test => sub { eval { qr/$_[0]/ }; return $@ ? ( 0, "__badRegexp__: $@" ) : (1); }, }, lmAttrOrMacro => { form => 'text', test => sub { my ( $val, $conf ) = @_; return 1 if ( defined $conf->{macros}->{$val} or $val eq '_timezone' ); foreach ( keys %$conf ) { return 1 if ( $_ =~ /exportedvars$/i and defined $conf->{$_}->{$val} ); } return ( 1, "__unknownAttrOrMacro__: $val" ); }, }, # Other types int => { test => qr/^\-?\d+$/, msgFail => '__notAnInteger__', }, bool => { test => qr/^[01]$/, msgFail => '__notABoolean__', }, trool => { test => qr/^(?:-1|0|1)$/, msgFail => '__authorizedValues__: -1, 0, 1', }, boolOrExpr => { test => $perlExpr, msgFail => '__notAValidPerlExpression__', }, keyTextContainer => { test => qr/./, msgFail => '__emptyValueNotAllowed__', keyTest => qr/^\w[\w\.\-]*$/, keyMsgFail => '__badKeyName__', }, subContainer => { keyTest => qr/\w/, test => sub { 1 }, }, select => { test => sub { my $test = grep ( { $_ eq $_[0] } map ( { $_->{k} } @{ $_[2]->{select} } ) ); return $test ? 1 : ( 0, "Invalid value '$_[0]' for this select" ); }, }, # Files type (long text) file => { test => sub { 1 } }, RSAPublicKey => { test => sub { return ( $_[0] =~ /^(?:(?:\-+\s*BEGIN\s+PUBLIC\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+PUBLIC\s+KEY\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); }, }, 'RSAPublicKeyOrCertificate' => { 'test' => sub { return ( $_[0] =~ /^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); }, }, RSAPrivateKey => { test => sub { return ( $_[0] =~ /^(?:(?:\-+\s*BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY\s*\-+\r?\n)?[a-zA-Z0-9\/\+\r\n]+={0,2}(?:\r?\n\-+\s*END\s+(?:RSA\s+)PRIVATE\s+KEY\s*\-+)?[\r\n]*)?$/s ? (1) : ( 1, '__badPemEncoding__' ) ); }, }, authParamsText => { test => sub { 1 } }, blackWhiteList => { test => sub { 1 } }, catAndAppList => { test => sub { 1 } }, keyText => { keyTest => qr/^[a-zA-Z0-9_]+$/, test => qr/^.*$/, msgFail => '__badValue__', }, menuApp => { test => sub { 1 } }, menuCat => { test => sub { 1 } }, oidcOPMetaDataNode => { test => sub { 1 } }, oidcRPMetaDataNode => { test => sub { 1 } }, oidcmetadatajson => { test => sub { 1 } }, oidcmetadatajwks => { test => sub { 1 } }, portalskin => { test => sub { 1 } }, portalskinbackground => { test => sub { 1 } }, post => { test => sub { 1 } }, rule => { test => sub { 1 } }, samlAssertion => { test => sub { 1 } }, samlAttribute => { test => sub { 1 } }, samlIDPMetaDataNode => { test => sub { 1 } }, samlSPMetaDataNode => { test => sub { 1 } }, samlService => { test => sub { 1 } }, }; } sub attributes { return { # Other cfgNum => { type => 'int', default => 0, documentation => 'Enable Cross Domain Authentication', }, cfgAuthor => { type => 'text', documentation => 'Name of the author of the current configuration', }, cfgAuthorIP => { type => 'text', documentation => 'Uploader IP address of the current configuration', }, cfgDate => { type => 'int', documentation => 'Timestamp of the current configuration', }, cfgLog => { type => 'longtext', documentation => 'Configuration update log', }, confirmFormMethod => { type => "select", select => [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], default => 'post', documentation => 'HTTP method for confirm page form', }, customFunctions => { type => 'text', test => qr/^(?:\w+(?:::\w+)*(?:\s+\w+(?:::\w+)*)*)?$/, msgFail => "__badCustomFuncName__", documentation => 'List of custom functions' }, https => { default => 0, type => 'bool', documentation => 'Use HTTPS for redirection from portal', }, infoFormMethod => { type => "select", select => [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], default => 'get', documentation => 'HTTP method for info page form', }, port => { type => 'int', }, jsRedirect => { type => 'boolOrExpr', default => 0, documentation => 'Use javascript for redirections', }, logoutServices => { type => 'keyTextContainer', help => 'logoutforward.html', default => {}, documentation => 'Send logout trough GET request to these services', }, maintenance => { default => 0, type => 'bool', documentation => 'Maintenance mode for all virtual hosts', }, nginxCustomHandlers => { type => 'keyTextContainer', keyTest => qr/^\w+$/, test => qr/^[a-zA-Z][a-zA-Z0-9]*(?:::[a-zA-Z][a-zA-Z0-9]*)*$/, msgFail => '__badPerlPackageName__', }, noAjaxHook => { default => 0, type => 'bool', documentation => 'Avoid replacing 302 by 401 for Ajax responses', }, portal => { type => 'url', default => 'http://auth.example.com/', documentation => 'Portal URL', }, portalUserAttr => { type => 'text', default => '_user', documentation => 'Session parameter to display connected user in portal', }, redirectFormMethod => { type => "select", select => [ { k => 'get', v => 'GET' }, { k => 'post', v => 'POST' }, ], default => 'get', documentation => 'HTTP method for redirect page form', }, reloadUrls => { type => 'keyTextContainer', help => 'configlocation.html#configuration_reload', keyTest => qr/^$Regexp::Common::URI::RFC2396::host(?::\d+)?$/, test => $url, msgFail => '__badUrl__' }, staticPrefix => { type => 'text', documentation => 'Prefix of static files for HTML templates', }, syslog => { type => 'text', test => qr/^(?:auth|authpriv|daemon|local\d|user)?$/, msgFail => '__authorizedValues__: auth, authpriv, daemon, local0-7, user', default => '', documentation => 'Syslog facility', }, # Manager protection => { type => 'text', test => qr/^(?:none|authenticate|manager|)$/, msgFail => '__authorizedValues__: none authenticate manager', default => 'none', documentation => 'Manager protection method', }, # Menu activeTimer => { type => 'bool', default => 1, documentation => 'Enable timers on portal pages', }, applicationList => { type => 'catAndAppList', keyTest => qr/\w/, help => 'portalmenu.html#categories_and_applications', default => { default => { catname => 'Default category', type => "category" } }, documentation => 'Applications list', }, portalErrorOnExpiredSession => { type => 'bool', default => 1, documentation => 'Show error if session is expired', }, portalErrorOnMailNotFound => { type => 'bool', default => 0, documentation => 'Show error if mail is not found in password reset process', }, portalOpenLinkInNewWindow => { type => 'bool', default => 0, documentation => 'Open applications in new windows', }, portalPingInterval => { type => 'int', default => 60000, documentation => 'Interval in ms between portal Ajax pings ', }, portalSkin => { type => 'portalskin', default => 'bootstrap', documentation => 'Name of portal skin', select => [ { k => 'bootstrap', v => 'Bootstrap' }, { k => 'pastel', v => 'Pastel' }, { k => 'impact', v => 'Impact' }, { k => 'dark', v => 'Dark' }, ], }, portalSkinBackground => { type => 'portalskinbackground', documentation => 'Background image of portal skin', select => [ { k => "", v => 'None' }, { k => "1280px-Anse_Source_d'Argent_2-La_Digue.jpg", v => 'Anse' }, { k => "1280px-Autumn-clear-water-waterfall-landscape_-_Virginia_-_ForestWander.jpg", v => 'Waterfall' }, { k => "1280px-BrockenSnowedTrees.jpg", v => 'Snowed Trees' }, { k => "1280px-Cedar_Breaks_National_Monument_partially.jpg", v => 'National Monument' }, { k => "1280px-Parry_Peak_from_Winter_Park.jpg", v => 'Winter' }, { k => "Aletschgletscher_mit_Pinus_cembra1.jpg", v => 'Pinus' }, ], }, portalSkinRules => { type => 'keyTextContainer', help => 'portalcustom.html', keyTest => $perlExpr, keyMsgFail => '__badSkinRule__', test => qr/^\w+$/, msgFail => '__badValue__', }, # Security cda => { default => 0, type => 'bool', documentation => 'Enable Cross Domain Authentication', }, checkXSS => { default => 1, type => 'bool', documentation => 'Check XSS', }, grantSessionRules => { type => 'grantContainer', keyTest => $perlExpr, test => sub { 1 }, }, hiddenAttributes => { type => 'text', default => '_password', documentation => 'Name of attributes to hide in logs', }, key => { type => 'password', documentation => 'Secret key', }, portalAntiFrame => { default => 1, type => 'bool', documentation => 'Avoid portal to be displayed inside frames', }, portalCheckLogins => { default => 1, type => 'bool', documentation => 'Display login history checkbox in portal', }, portalForceAuthn => { default => 0, type => 'bool', documentation => 'Force to authenticate when displaying portal', }, portalForceAuthnInterval => { type => 'int', default => 5, documentation => 'Minimum number of seconds since last authentifcation to force reauthentication', }, randomPasswordRegexp => { type => 'pcre', default => '[A-Z]{3}[a-z]{5}.\d{2}', documentation => 'Regular expression to create a random password', }, trustedDomains => { type => 'text', }, storePassword => { default => 0, type => 'bool', documentation => 'Store password in session', }, timeout => { type => 'int', test => sub { $_[0] > 0 }, default => 72000, documentation => 'Session timeout on server side', }, timeoutActivity => { type => 'int', test => sub { $_[0] >= 0 }, default => 0, documentation => 'Session activity timeout on server side', }, timeoutActivityInterval => { type => 'int', test => sub { $_[0] >= 0 }, default => 60, documentation => 'Update session timeout interval on server side', }, trustedProxies => { type => 'text', default => '', documentation => 'Trusted proxies', }, userControl => { type => 'pcre', default => '^[\w\.\-@]+$', documentation => 'Regular expression to validate login', }, useRedirectOnError => { type => 'bool', default => 1, documentation => 'Use 302 redirect code for error (500)', }, useRedirectOnForbidden => { default => 0, type => 'bool', documentation => 'Use 302 redirect code for forbidden (403)', }, useSafeJail => { default => 1, type => 'bool', documentation => 'Activate Safe jail', }, whatToTrace => { type => 'lmAttrOrMacro', default => 'uid', documentation => 'Session parameter used to fill REMOTE_USER', }, lwpSslOpts => { type => 'keyTextContainer', documentation => 'Options given to LWP::UserAgent', }, # History failedLoginNumber => { default => 5, type => 'int', documentation => 'Number of failures stored in login history', }, loginHistoryEnabled => { default => 0, type => 'bool', default => 1, documentation => 'Enable login history', }, portalDisplayLoginHistory => { type => 'boolOrExpr', default => 1, documentation => 'Display login history tab in portal', }, successLoginNumber => { default => 5, type => 'int', documentation => 'Number of success stored in login history', }, # Other displays portalDisplayAppslist => { type => 'boolOrExpr', default => 1, documentation => 'Display applications tab in portal', }, portalDisplayChangePassword => { type => 'boolOrExpr', default => '$_auth =~ /^(LDAP|DBI|Demo)$/', documentation => 'Display password tab in portal', }, portalDisplayLogout => { default => 1, type => 'boolOrExpr', documentation => 'Display logout tab in portal', }, portalDisplayRegister => { default => 1, type => 'bool', documentation => 'Display register button in portal', }, portalDisplayResetPassword => { default => 1, type => 'bool', documentation => 'Display reset password button in portal', }, # Cookies cookieExpiration => { type => 'text', }, cookieName => { type => 'text', test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/, msgFail => '__badCookieName__', default => 'lemonldap', documentation => 'Name of the main cookie', }, domain => { type => 'text', test => qr/^(?:$Regexp::Common::URI::RFC2396::hostname)?/, msgFail => '__badDomainName__', default => 'example.com', documentation => 'DNS domain', }, httpOnly => { default => 1, type => 'bool', documentation => 'Enable httpOnly flag in cookie', }, securedCookie => { type => 'select', select => [ { k => '0', v => 'unsecuredCookie' }, { k => '1', v => 'securedCookie' }, { k => '2', v => 'doubleCookie' }, { k => '3', v => 'doubleCookieForSingleSession' }, ], default => 0, documentation => 'Cookie securisation method', }, # Notification notificationWildcard => { type => 'text', default => 'allusers', documentation => 'Notification string to match all users', }, notificationXSLTfile => { type => 'text', }, notification => { default => 0, type => 'bool', documentation => 'Notification activation', }, notificationStorage => { type => 'PerlModule', default => 'File', documentation => 'Notification backend', }, notificationStorageOptions => { type => 'keyTextContainer', default => { dirName => '/var/lib/lemonldap-ng/notifications', }, documentation => 'Notification backend options', }, # Captcha captcha_login_enabled => { default => 0, type => 'bool', documentation => 'Captcha on login page', }, captcha_mail_enabled => { default => 0, type => 'bool', documentation => 'Captcha on password reset page', }, captcha_register_enabled => { default => 1, type => 'bool', documentation => 'Captcha on account creation page', }, captcha_size => { type => 'int', default => 6, documentation => 'Captcha size', }, #captcha_data #captcha_output captchaStorage => { type => 'PerlModule', default => 'Apache::Session::File', documentation => 'Captcha backend module', }, captchaStorageOptions => { type => 'keyTextContainer', default => { 'Directory' => '/var/lib/lemonldap-ng/captcha/', }, documentation => 'Captcha backend module options', }, # Variables exportedVars => { type => 'keyTextContainer', help => 'exportedvars.html', keyTest => qr/^!?[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badVariableName__', test => qr/^[_a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => { 'UA' => 'HTTP_USER_AGENT' }, documentation => 'Main exported variables', }, groups => { type => 'keyTextContainer', help => 'exportedvars.html#extend_variables_using_macros_and_groups', test => $perlExpr, default => {}, documentation => 'Groups', }, macros => { type => 'keyTextContainer', help => 'exportedvars.html#extend_variables_using_macros_and_groups', keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__badMacroName__', test => $perlExpr, default => {}, documentation => 'Macros', }, # Storage globalStorage => { type => 'PerlModule', default => 'Apache::Session::File', documentation => 'Session backend module', }, globalStorageOptions => { type => 'keyTextContainer', default => { 'Directory' => '/var/lib/lemonldap-ng/sessions/', 'LockDirectory' => '/var/lib/lemonldap-ng/sessions/lock/', 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', }, documentation => 'Session backend module options', }, localSessionStorage => { type => 'PerlModule', default => 'Cache::FileCache', , documentation => 'Sessions cache module', }, localSessionStorageOptions => { type => 'keyTextContainer', default => { 'namespace' => 'lemonldap-ng-sessions', 'default_expires_in' => 600, 'directory_umask' => '007', 'cache_root' => '/tmp', 'cache_depth' => 3, }, documentation => 'Sessions cache module options', }, # Persistent storage persistentStorage => { type => 'PerlModule', }, persistentStorageOptions => { type => 'keyTextContainer', }, sessionDataToRemember => { type => 'keyTextContainer', keyTest => qr/^[_a-zA-Z][a-zA-Z0-9_]*$/, keyMsgFail => '__invalidSessionData__', }, # SAML issuer issuerDBSAMLActivation => { default => 0, type => 'bool', documentation => 'SAML IDP activation', }, issuerDBSAMLPath => { type => 'pcre', default => '^/saml/', documentation => 'SAML IDP request path', }, issuerDBSAMLRule => { type => 'boolOrExpr', default => 1, documentation => 'SAML IDP rule', }, # OpenID-Connect issuer issuerDBOpenIDConnectActivation => { type => 'bool', default => 0, documentation => 'OpenID Connect server activation', }, issuerDBOpenIDConnectPath => { type => 'text', default => '^/oauth2/', documentation => 'OpenID Connect server request path', }, issuerDBOpenIDConnectRule => { type => 'boolOrExpr', default => 1, documentation => 'OpenID Connect server rule', }, # GET issuer issuerDBGetActivation => { type => 'bool', default => 0, documentation => 'Get issuer activation', }, issuerDBGetPath => { type => 'text', default => '^/get/', documentation => 'Get issuer request path', }, issuerDBGetRule => { type => 'boolOrExpr', default => 1, documentation => 'Get issuer rule', }, issuerDBGetParameters => { type => 'doubleHash', default => {}, keyTest => qr/^$Regexp::Common::URI::RFC2396::hostname$/, keyMsgFail => '__badHostname__', test => { keyTest => qr/^(?=[^\-])[\w\-]+(?<=[^-])$/, keyMsgFail => '__badKeyName__', test => sub { my ( $val, $conf ) = @_; return 1 if ( defined $conf->{macros}->{$val} or $val eq '_timezone' ); foreach ( keys %$conf ) { return 1 if ( $_ =~ /exportedvars$/i and defined $conf->{$_}->{$val} ); } return ( 1, "__unknownAttrOrMacro__: $val" ); }, }, documentation => 'List of virtualHosts with their get parameters', }, # Password mailOnPasswordChange => { default => 0, type => 'bool', documentation => 'Send a mail when password is changed', }, portalRequireOldPassword => { default => 1, type => 'bool', documentation => 'Old password is required to change the password', }, hideOldPassword => { default => 0, type => 'bool', documentation => 'Hide old password in portal', }, # Mails mailBody => { type => 'longtext', }, mailCharset => { type => 'text', default => 'utf-8', documentation => 'Mail charset', }, mailConfirmBody => { type => 'longtext', }, mailConfirmSubject => { type => 'text', default => '[LemonLDAP::NG] Password reset confirmation', documentation => 'Mail subject for reset confirmation', }, mailFrom => { type => 'text', default => 'noreply@example.com', documentation => 'Sender email', }, mailReplyTo => { type => 'text', }, mailSessionKey => { type => 'text', default => 'mail', documentation => 'Session parameter where mail is stored', }, mailSubject => { type => 'text', default => '[LemonLDAP::NG] Your new password', documentation => 'Mail subject for new password email', }, mailTimeout => { type => 'int', default => 0, documentation => 'Mail session timeout', }, mailUrl => { type => 'url', default => 'http://auth.example.com/mail.pl', documentation => 'URL of password reset page', }, SMTPServer => { type => 'text', default => '', test => qr/^(?:$Regexp::Common::URI::RFC2396::host(?::\d+)?)?$/, documentation => 'SMTP Server', }, SMTPAuthUser => { type => 'text', }, SMTPAuthPass => { type => 'password', }, # Registration registerConfirmSubject => { type => 'text', default => '[LemonLDAP::NG] Account register confirmation', documentation => 'Mail subject for register confirmation', }, registerDB => { type => 'select', select => [ { k => 'AD', v => 'Active Directory' }, { k => 'Demo', v => 'Demonstration' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Null', v => 'None' }, ], default => 'Demo', documentation => 'Register module', }, registerDoneSubject => { type => 'text', default => '[LemonLDAP::NG] Your new account', documentation => 'Mail subject when register is done', }, registerTimeout => { default => 0, type => 'int', documentation => 'Register session timeout', }, registerUrl => { type => 'text', default => 'http://auth.example.com/register.pl', documentation => 'URL of register page', }, # Single session notifyDeleted => { default => 1, type => 'bool', documentation => 'Show deleted sessions in portal', }, notifyOther => { default => 0, type => 'bool', documentation => 'Show other sessions in portal', }, singleSession => { default => 0, type => 'bool', documentation => 'Allow only one session per user', }, singleIP => { default => 0, type => 'bool', documentation => 'Allow only one session per IP', }, singleUserByIP => { default => 0, type => 'bool', }, singleSessionUserByIP => { default => 0, type => 'bool', documentation => 'Allow only one session per user on an IP', }, # SOAP server Soap => { default => 0, type => 'bool', documentation => 'Enable SOAP services', }, exportedAttr => { type => 'text', }, ## Virtualhosts # Fake attribute: used by manager REST API to agglomerate all other # nodes virtualHosts => { type => 'virtualHostContainer', help => 'configvhost.html', template => 'virtualHost', }, locationRules => { type => 'ruleContainer', help => 'writingrulesand_headers.html#rules', test => { keyTest => sub { eval { qr/$_[0]/ }; return $@ ? 0 : 1; }, keyMsgFail => '__badRegexp__', test => sub { my ( $val, $conf ) = @_; my $s = $val; if ( $s =~ s/^logout(?:_(?:sso|app(?:_sso)?))?\s*// ) { return $s =~ m{^(?:https?://.*)?$} ? (1) : ( 0, '__badUrl__' ); } $s =~ s/\b(accept|deny|unprotect|skip)\b/1/g; my @cf = qw(encode_base64 checkLogonHours date checkDate basic unicode2iso iso2unicode groupMatch encrypt); push @cf, defined $conf->{customFunctions} ? map { my $f = $_; $f =~ s/\w+:://g; ( $f, $_ ) } split( /\s+/, $conf->{customFunctions} ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } no warnings( 'redefine', 'uninitialized' ); eval $s; return $@ ? ( 1, "__badExpression__: $@" ) : (1); }, msgFail => '__badExpression__', }, keyTest => qr/^$Regexp::Common::URI::RFC2396::hostname$/, keyMsgFail => '__badHostname__', default => { default => 'deny', }, documentation => 'Virtualhost rules', }, exportedHeaders => { type => 'keyTextContainer', help => 'writingrulesand_headers.html#headers', keyTest => qr/^$Regexp::Common::URI::RFC2396::hostname$/, keyMsgFail => '__badHostname__', test => { keyTest => qr/^(?=[^\-])[\w\-]+(?<=[^-])$/, keyMsgFail => '__badHeaderName__', test => sub { my ( $val, $conf ) = @_; my $s = $val; my @cf = qw(encode_base64 checkLogonHours date checkDate basic unicode2iso iso2unicode groupMatch encrypt); push @cf, defined $conf->{customFunctions} ? map { my $f = $_; $f =~ s/\w+:://g; ( $f, $_ ) } split( /\s+/, $conf->{customFunctions} ) : (); foreach my $f (@cf) { $s = "sub $f {1} $s"; } no warnings( 'redefine', 'uninitialized' ); eval $s; return $@ ? ( 1, "__badExpression__: $@" ) : (1); } }, documentation => 'Virtualhost headers', }, post => { type => 'postContainer', help => 'formreplay.html', test => sub { 1 }, keyTest => qr/^$Regexp::Common::URI::RFC2396::hostname$/, keyMsgFail => '__badHostname__', documentation => 'Virtualhost urls/Datas to post', }, vhostOptions => { type => 'subContainer', }, vhostPort => { type => 'int', default => -1, }, vhostHttps => { type => 'trool', default => -1, }, vhostMaintenance => { type => 'bool', default => 0, }, vhostAliases => { type => 'text', }, # CAS IDP casAttr => { type => 'text', }, casAttributes => { type => 'keyTextContainer', }, casAccessControlPolicy => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'error', v => 'Display error on portal' }, { k => 'faketicket', v => 'Send a fake service ticket' }, ], default => 'none', documentation => 'CAS access control policy', }, casStorage => { type => 'PerlModule', }, casStorageOptions => { type => 'keyTextContainer', }, issuerDBCASActivation => { default => 0, type => 'bool', documentation => 'CAS server activation', }, issuerDBCASPath => { type => 'pcre', default => '^/cas/', documentation => 'CAS server request path', }, issuerDBCASRule => { type => 'boolOrExpr', default => 1, documentation => 'CAS server rule', }, # OpenID Issuer issuerDBOpenIDActivation => { default => 0, type => 'bool', documentation => 'OpenID server activation', }, issuerDBOpenIDPath => { type => 'pcre', default => '^/openidserver/', documentation => 'OpenID server request path', }, issuerDBOpenIDRule => { type => 'boolOrExpr', default => 1, documentation => 'OpenID server rule', }, openIdIssuerSecret => { type => 'text', }, openIdAttr => { type => 'text', }, openIdSreg_fullname => { type => 'lmAttrOrMacro', default => 'cn', documentation => 'OpenID SREG fullname session parameter', }, openIdSreg_nickname => { type => 'lmAttrOrMacro', default => 'uid', documentation => 'OpenID SREG nickname session parameter', }, openIdSreg_language => { type => 'lmAttrOrMacro', }, openIdSreg_postcode => { type => 'lmAttrOrMacro', }, openIdSreg_timezone => { type => 'lmAttrOrMacro', default => '_timezone', documentation => 'OpenID SREG timezone session parameter', }, openIdSreg_country => { type => 'lmAttrOrMacro', }, openIdSreg_gender => { type => 'lmAttrOrMacro', }, openIdSreg_email => { type => 'lmAttrOrMacro', default => 'mail', documentation => 'OpenID SREG email session parameter', }, openIdSreg_dob => { type => 'lmAttrOrMacro', }, openIdSPList => { type => 'blackWhiteList', default => '0;' }, # Zimbra zimbraPreAuthKey => { type => 'text', }, zimbraAccountKey => { type => 'text', }, zimbraBy => { type => 'select', select => [ { k => '', v => '' }, { k => 'name', v => 'User name' }, { k => 'id', v => 'User id' }, { k => 'foreignPrincipal', v => 'Foreign principal' }, ], default => '', }, zimbraUrl => { type => 'text', }, zimbraSsoUrl => { type => 'text', }, # Sympa sympaSecret => { type => 'text', }, sympaMailKey => { type => 'text', }, # Secure Token secureTokenMemcachedServers => { type => 'text', default => '127.0.0.1:11211', documentation => 'Secure Token Handler memcached servers', }, secureTokenExpiration => { type => 'int', default => 60, documentation => 'Secure Token Handler token expiration', }, secureTokenAttribute => { type => 'text', default => 'uid', documentation => 'Secure Token Handler attribute to store', }, secureTokenUrls => { type => 'pcre', default => '.*', documentation => 'Secure Token Handler regular expression to match protected URL', }, secureTokenHeader => { type => 'text', default => 'Auth-Token', documentation => 'Secure Token Handler header name', }, secureTokenAllowOnError => { default => 1, type => 'bool', documentation => 'Secure Token Handler allow request on error', }, ######### ## SAML # ######### samlEntityID => { type => 'text', default => '#PORTAL#/saml/metadata', documentation => 'SAML service entityID', }, samlOrganizationDisplayName => { type => 'text', default => 'Example', documentation => 'SAML service organization display name', }, samlOrganizationName => { type => 'text', default => 'Example', documentation => 'SAML service organization name', }, samlOrganizationURL => { type => 'text', default => 'http://www.example.com', documentation => 'SAML service organization URL', }, samlNameIDFormatMapEmail => { type => 'text', default => 'mail', documentation => 'SAML session parameter for NameID email', }, samlNameIDFormatMapX509 => { type => 'text', default => 'mail', documentation => 'SAML session parameter for NameID x509', }, samlNameIDFormatMapWindows => { type => 'text', default => 'uid', documentation => 'SAML session parameter for NameID windows', }, samlNameIDFormatMapKerberos => { type => 'text', default => 'uid', documentation => 'SAML session parameter for NameID kerberos', }, samlAttributeAuthorityDescriptorAttributeServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/AA/SOAP;', documentation => 'SAML Attribute Authority SOAP', }, samlServicePrivateKeySig => { type => 'RSAPrivateKey', default => '', documentation => 'SAML signature private key', }, samlServicePrivateKeySigPwd => { type => 'password', default => '', documentation => 'SAML signature private key password', }, samlServicePublicKeySig => { type => 'RSAPublicKeyOrCertificate', default => '', documentation => 'SAML signature public key', }, samlServicePrivateKeyEnc => { type => 'RSAPrivateKey', default => '', documentation => 'SAML encryption private key', }, samlServicePrivateKeyEncPwd => { type => 'password', }, samlServicePublicKeyEnc => { type => 'RSAPublicKeyOrCertificate', default => '', documentation => 'SAML encryption public key', }, samlServiceUseCertificateInResponse => { type => 'bool', default => 0, documentation => 'Use certificate instead of public key in SAML responses', }, samlIdPResolveCookie => { type => 'text', default => 'lemonldapidp', documentation => 'SAML IDP resolution cookie', }, samlMetadataForceUTF8 => { default => 1, type => 'bool', documentation => 'SAML force metadata UTF8 conversion', }, samlStorage => { type => 'PerlModule', }, samlStorageOptions => { type => 'keyTextContainer', }, samlAuthnContextMapPassword => { type => 'int', default => 2, documentation => 'SAML authn context password level', }, samlAuthnContextMapPasswordProtectedTransport => { type => 'int', default => 3, documentation => 'SAML authn context password protected transport level', }, samlAuthnContextMapTLSClient => { type => 'int', default => 5, documentation => 'SAML authn context TLS client level', }, samlAuthnContextMapKerberos => { type => 'int', default => 4, documentation => 'SAML authn context kerberos level', }, samlCommonDomainCookieActivation => { default => 0, type => 'bool', documentation => 'SAML CDC activation', }, samlCommonDomainCookieDomain => { type => 'text', test => qr/^$Regexp::Common::URI::RFC2396::hostname$/, msgFail => '__badDomainName__', }, samlCommonDomainCookieReader => { type => 'text', test => $url, msgFail => '__badUrl__', }, samlCommonDomainCookieWriter => { type => 'text', test => $url, msgFail => '__badUrl__', }, samlRelayStateTimeout => { type => 'int', default => 600, documentation => 'SAML timeout of relay state', }, samlUseQueryStringSpecific => { default => 0, type => 'bool', documentation => 'SAML use specific method for query_string', }, samlIDPSSODescriptorWantAuthnRequestsSigned => { type => 'bool', default => 1, documentation => 'SAML IDP want authn request signed', }, samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' . '#PORTAL#/saml/singleSignOn;', documentation => 'SAML IDP SSO HTTP Redirect', }, samlIDPSSODescriptorSingleSignOnServiceHTTPPost => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/singleSignOn;', documentation => 'SAML IDP SSO HTTP POST', }, samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;' . '#PORTAL#/saml/singleSignOnArtifact;', documentation => 'SAML IDP SSO HTTP Artifact', }, samlIDPSSODescriptorSingleSignOnServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/singleSignOnSOAP;', documentation => 'SAML IDP SSO SOAP', }, samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' . '#PORTAL#/saml/singleLogout;' . '#PORTAL#/saml/singleLogoutReturn', documentation => 'SAML IDP SLO HTTP Redirect', }, samlIDPSSODescriptorSingleLogoutServiceHTTPPost => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/singleLogout;' . '#PORTAL#/saml/singleLogoutReturn', documentation => 'SAML IDP SLO HTTP POST', }, samlIDPSSODescriptorSingleLogoutServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/singleLogoutSOAP;', documentation => 'SAML IDP SLO SOAP', }, samlIDPSSODescriptorArtifactResolutionServiceArtifact => { type => 'samlAssertion', default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/artifact', documentation => 'SAML IDP artifact resolution service', }, # Fake attribute: used by manager REST API to agglomerate all nodes # related to a SAML IDP partner samlIDPMetaDataNodes => { type => 'samlIDPMetaDataNodeContainer', template => 'samlIDPMetaDataNode', }, # Fake attribute: used by manager REST API to agglomerate all nodes # related to a SAML SP partner samlSPMetaDataNodes => { type => 'samlSPMetaDataNodeContainer', help => 'authsaml.html', template => 'samlSPMetaDataNode', }, # TODO: split that # IDP Keys samlIDPMetaDataExportedAttributes => { type => 'samlAttributeContainer', help => 'authsaml.html#exported_attributes', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', test => qr/\w/, msgFail => '__badValue__', default => {}, }, samlIDPMetaDataXML => { type => 'file', }, samlIDPMetaDataOptions => { type => 'keyTextContainer', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', }, samlIDPMetaDataOptionsNameIDFormat => { type => 'select', select => [ { k => '', v => '' }, { k => 'unspecified', v => 'Unspecified' }, { k => 'email', v => 'Email' }, { k => 'x509', v => 'X509 certificate' }, { k => 'windows', v => 'Windows' }, { k => 'kerberos', v => 'Kerberos' }, { k => 'entity', v => 'Entity' }, { k => 'persistent', v => 'Persistent' }, { k => 'transient', v => 'Transient' }, { k => 'encrypted', v => 'Encrypted' }, ], default => '', }, samlIDPMetaDataOptionsForceAuthn => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsIsPassive => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsAllowProxiedAuthn => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsAllowLoginFromIDP => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsRequestedAuthnContext => { type => 'select', select => [ { k => '', v => '' }, { k => 'kerberos', v => 'Kerberos' }, { k => 'password-protected-transport', v => 'Password protected transport' }, { k => 'password', v => 'Password' }, { k => 'tls-client', v => 'TLS client certificate' }, ], default => '', }, samlIDPMetaDataOptionsAdaptSessionUtime => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsForceUTF8 => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsSignSSOMessage => { type => 'trool', default => -1, }, samlIDPMetaDataOptionsCheckSSOMessageSignature => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsSignSLOMessage => { type => 'trool', default => -1, }, samlIDPMetaDataOptionsCheckSLOMessageSignature => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsSSOBinding => { type => 'select', select => [ { k => '', v => '' }, { k => 'http-post', v => 'POST' }, { k => 'http-redirect', v => 'Redirect' }, { k => 'http-soap', v => 'SOAP' }, { k => 'artifact-get', v => 'Artifact GET' }, { k => 'artifact-post', v => 'Artifact POST' }, ], default => '', }, samlIDPMetaDataOptionsSLOBinding => { type => 'select', select => [ { k => '', v => '' }, { k => 'http-post', v => 'POST' }, { k => 'http-redirect', v => 'Redirect' }, { k => 'http-soap', v => 'SOAP' }, { k => 'artifact-get', v => 'Artifact GET' }, { k => 'artifact-post', v => 'Artifact POST' }, ], default => '', }, samlIDPMetaDataOptionsEncryptionMode => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'nameid', v => 'Name ID' }, { k => 'assertion', v => 'Assertion' }, ], default => 'none', }, samlIDPMetaDataOptionsCheckTime => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsCheckAudience => { type => 'bool', default => 1, }, samlIDPMetaDataOptionsResolutionRule => { type => 'longtext', default => '', }, samlIDPMetaDataOptionsStoreSAMLToken => { type => 'bool', default => 0, }, samlIDPMetaDataOptionsRelayStateURL => { type => 'bool', default => 0, }, # SP keys samlSPMetaDataExportedAttributes => { type => 'samlAttributeContainer', help => 'idpsaml.html#exported_attributes', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', test => qr/\w/, msgFail => '__badValue__', default => {}, }, samlSPMetaDataXML => { type => 'file', }, samlSPMetaDataOptions => { type => 'keyTextContainer', keyTest => qr/^[a-zA-Z](?:[a-zA-Z0-9_\-\.]*\w)?$/, keyMsgFail => '__badMetadataName__', }, samlSPSSODescriptorAuthnRequestsSigned => { default => 1, type => 'bool', documentation => 'SAML SP AuthnRequestsSigned', }, samlSPSSODescriptorWantAssertionsSigned => { default => 1, type => 'bool', documentation => 'SAML SP WantAssertionsSigned', }, samlSPSSODescriptorSingleLogoutServiceHTTPRedirect => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect;' . '#PORTAL#/saml/proxySingleLogout;' . '#PORTAL#/saml/proxySingleLogoutReturn', documentation => 'SAML SP SLO HTTP Redirect', }, samlSPSSODescriptorSingleLogoutServiceHTTPPost => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/proxySingleLogout;' . '#PORTAL#/saml/proxySingleLogoutReturn', documentation => 'SAML SP SLO HTTP POST', }, samlSPSSODescriptorSingleLogoutServiceSOAP => { type => 'samlService', default => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/proxySingleLogoutSOAP;', documentation => 'SAML SP SLO SOAP', }, samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact => { type => 'samlAssertion', default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact;' . '#PORTAL#/saml/proxySingleSignOnArtifact', documentation => 'SAML SP ACS HTTP artifact', }, samlSPSSODescriptorAssertionConsumerServiceHTTPPost => { type => 'samlAssertion', default => '0;1;urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST;' . '#PORTAL#/saml/proxySingleSignOnPost', documentation => 'SAML SP ACS HTTP POST', }, samlSPSSODescriptorArtifactResolutionServiceArtifact => { type => 'samlAssertion', default => '1;0;urn:oasis:names:tc:SAML:2.0:bindings:SOAP;' . '#PORTAL#/saml/artifact', documentation => 'SAML SP artifact resolution service ', }, samlSPMetaDataOptionsNameIDFormat => { type => 'select', select => [ { k => '', v => '' }, { k => 'unspecified', v => 'Unspecified' }, { k => 'email', v => 'Email' }, { k => 'x509', v => 'X509 certificate' }, { k => 'windows', v => 'Windows' }, { k => 'kerberos', v => 'Kerberos' }, { k => 'entity', v => 'Entity' }, { k => 'persistent', v => 'Persistent' }, { k => 'transient', v => 'Transient' }, { k => 'encrypted', v => 'Encrypted' }, ], default => '', }, samlSPMetaDataOptionsNameIDSessionKey => { type => 'text', }, samlSPMetaDataOptionsOneTimeUse => { type => 'bool', default => 0, }, samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => { type => 'int', default => 72000, }, samlSPMetaDataOptionsNotOnOrAfterTimeout => { type => 'int', default => 72000, }, samlSPMetaDataOptionsSignSSOMessage => { type => 'trool', default => -1, }, samlSPMetaDataOptionsCheckSSOMessageSignature => { type => 'bool', default => 1, }, samlSPMetaDataOptionsSignSLOMessage => { type => 'trool', default => -1, }, samlSPMetaDataOptionsCheckSLOMessageSignature => { type => 'bool', default => 1, }, samlSPMetaDataOptionsEncryptionMode => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'nameid', v => 'Name ID' }, { k => 'assertion', v => 'Assertion' }, ], default => 'none', }, samlSPMetaDataOptionsEnableIDPInitiatedURL => { type => 'bool', default => 0, }, samlSPMetaDataOptionsForceUTF8 => { type => 'bool', default => 1, }, # AUTH, USERDB and PASSWORD MODULES authentication => { type => 'select', select => [ { k => 'Apache', v => 'Apache' }, { k => 'AD', v => 'Active Directory' }, { k => 'BrowserID', v => 'BrowserID (Mozilla Persona)' }, { k => 'Choice', v => 'authChoice' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demonstration' }, { k => 'Facebook', v => 'Facebook' }, { k => 'Google', v => 'Google' }, { k => 'Kerberos', v => 'Kerberos' }, { k => 'LDAP', v => 'LDAP' }, { k => 'LinkedIn', v => 'LinkedIn' }, { k => 'Multi', v => 'Multiple' }, { k => 'Null', v => 'None' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Radius', v => 'Radius' }, { k => 'Remote', v => 'Remote' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Slave', v => 'Slave' }, { k => 'SSL', v => 'SSL' }, { k => 'Twitter', v => 'Twitter' }, { k => 'WebID', v => 'WebID' }, { k => 'Yubikey', v => 'Yubikey' }, ], default => 'Demo', documentation => 'Authentication module', }, userDB => { type => 'select', select => [ { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Choice', v => 'authChoice' }, { k => 'Demo', v => 'Demonstration' }, { k => 'Facebook', v => 'Facebook' }, { k => 'Google', v => 'Google' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Multi', v => 'Multiple' }, { k => 'Null', v => 'None' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Remote', v => 'Remote' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Slave', v => 'Slave' }, { k => 'WebID', v => 'WebID' }, ], default => 'Demo', documentation => 'User module', }, passwordDB => { type => 'select', select => [ { k => 'AD', v => 'Active Directory' }, { k => 'Choice', v => 'authChoice' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demonstration' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Null', v => 'None' }, ], default => 'Demo', documentation => 'Password module', }, # DEMO demoExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => { cn => 'cn', mail => 'mail', uid => 'uid', }, documentation => 'Demo exported variables', }, # AD ADPwdExpireWarning => { type => 'int', default => 0, documentation => 'AD password expire warning', }, ADPwdMaxAge => { type => 'int', default => 0, documentation => 'AD password max age', }, # LDAP managerDn => { type => 'text', test => qr/^(?:\w+=.*)?$/, msgFail => '__badValue__', default => '', documentation => 'LDAP manager DN', }, managerPassword => { type => 'password', test => qr/^\S*$/, msgFail => '__badValue__', default => '', documentation => 'LDAP manager Password', }, ldapAuthnLevel => { type => 'int', default => 2, documentation => 'LDAP authentication level', }, ldapBase => { type => 'text', test => qr/^(?:\w+=.*|)$/, msgFail => '__badValue__', default => 'dc=example,dc=com', documentation => 'LDAP search base', }, ldapExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => { cn => 'cn', mail => 'mail', uid => 'uid', }, documentation => 'LDAP exported variables', }, ldapPort => { type => 'int', default => 389, documentation => 'LDAP port', }, ldapServer => { type => 'text', test => sub { my $l = shift; my (@s) = split( /[\s,]+/, $l ); foreach my $s (@s) { $s =~ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?::\d{1,5})?/?.*)$}o or return ( 0, "__badLdapUri__: \"$s\"" ); } return 1; }, default => 'ldap://localhost', documentation => 'LDAP server (host or URI)', }, ldapPwdEnc => { type => 'text', test => qr/^[a-zA-Z0-9_][a-zA-Z0-9_\-]*[a-zA-Z0-9_]$/, msgFail => '__badEncoding__', default => 'utf-8', documentation => 'LDAP password encoding', }, ldapUsePasswordResetAttribute => { default => 0, type => 'bool', default => 1, documentation => 'LDAP store reset flag in an attribute', }, ldapPasswordResetAttribute => { type => 'text', default => 'pwdReset', documentation => 'LDAP password reset attribute', }, ldapPasswordResetAttributeValue => { type => 'text', default => 'TRUE', documentation => 'LDAP password reset value', }, ldapPpolicyControl => { default => 0, type => 'bool', }, ldapSetPassword => { default => 0, type => 'bool', }, ldapChangePasswordAsUser => { default => 0, type => 'bool', }, ldapSearchDeref => { type => 'select', select => [ { k => 'never', v => 'never' }, { k => 'search', v => 'search' }, { k => 'find', v => 'find' }, { k => 'always', v => 'always' } ], default => 'find', documentation => '"deref" param of Net::LDAP::search()', }, mailLDAPFilter => { type => 'text', }, LDAPFilter => { type => 'text', }, AuthLDAPFilter => { type => 'text', }, ldapGroupRecursive => { default => 0, type => 'bool', documentation => 'LDAP recursive search in groups', }, ldapGroupObjectClass => { type => 'text', default => 'groupOfNames', documentation => 'LDAP object class of groups', }, ldapGroupBase => { type => 'text', }, ldapGroupAttributeName => { type => 'text', default => 'member', documentation => 'LDAP attribute name for member in groups', }, ldapGroupAttributeNameUser => { type => 'text', default => 'dn', documentation => 'LDAP attribute name in user entry referenced as member in groups', }, ldapGroupAttributeNameSearch => { type => 'text', default => 'cn', documentation => 'LDAP attributes to search in groups', }, ldapGroupAttributeNameGroup => { type => 'text', default => 'dn', documentation => 'LDAP attribute name in group entry referenced as member in groups', }, ldapTimeout => { type => 'int', default => 120, documentation => 'LDAP connection timeout', }, ldapVersion => { type => 'int', default => 3, documentation => 'LDAP protocol version', }, ldapRaw => { type => 'text', }, ldapAllowResetExpiredPassword => { default => 0, type => 'bool', documentation => 'Allow a user to reset his expired password', }, # SSL SSLAuthnLevel => { type => 'int', default => 5, documentation => 'SSL authentication level', }, SSLVar => { type => 'text', }, # CAS CAS_authnLevel => { type => 'int', default => 1, documentation => 'CAS authentication level', }, CAS_url => { type => 'text', test => $url, msgFail => '__badUrl__', }, CAS_CAFile => { type => 'text', }, CAS_renew => { type => 'bool', }, CAS_gateway => { type => 'bool', }, CAS_pgtFile => { type => 'text', default => '/tmp/pgt.txt', documentation => 'CAS PGT file', }, CAS_proxiedServices => { type => 'keyTextContainer', keyTest => qr/^\w+$/, keyMsgFail => '__badCasProxyId__', }, # Radius radiusAuthnLevel => { type => 'int', default => 3, documentation => 'Radius authentication level', }, radiusSecret => { type => 'text', }, radiusServer => { type => 'text', }, # Remote remotePortal => { type => 'text', }, remoteGlobalStorage => { type => 'PerlModule', default => 'Lemonldap::NG::Common::Apache::Session::SOAP', documentation => 'Remote session backend', }, remoteGlobalStorageOptions => { type => 'keyTextContainer', default => { proxy => 'http://auth.example.com/index.pl/sessions', ns => 'http://auth.example.com/Lemonldap/NG/Common/CGI/SOAPService', }, documentation => 'Demo exported variables', }, # Proxy soapAuthService => { type => 'text', }, remoteCookieName => { type => 'text', }, soapSessionService => { type => 'text', }, # OpenID openIdAuthnLevel => { type => 'int', default => 1, documentation => 'OpenID authentication level', }, openIdSecret => { type => 'text', }, openIdExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'OpenID exported variables', }, 'openIdIDPList' => { 'type' => 'blackWhiteList', default => '0;' }, # Google googleAuthnLevel => { type => 'int', default => 1, documentation => 'Google authentication level', }, googleExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'Google exported variables', }, # Facebook facebookAuthnLevel => { type => 'int', default => 1, documentation => 'Facebook authentication level', }, facebookAppId => { type => 'text', }, facebookAppSecret => { type => 'text', }, facebookExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'Facebook exported variables', }, # Twitter twitterAuthnLevel => { type => 'int', default => 1, documentation => 'Twitter authentication level', }, twitterKey => { type => 'text', }, twitterSecret => { type => 'text', }, twitterAppName => { type => 'text', }, # LinkedIn linkedInAuthnLevel => { type => 'int', default => 1, documentation => 'LinkedIn authentication level', }, linkedInClientID => { type => 'text', }, linkedInClientSecret => { type => 'password', }, linkedInFields => { type => 'text', default => 'id,first-name,last-name,email-address' }, linkedInUserField => { type => 'text', default => 'emailAddress' }, linkedInScope => { type => 'text', default => 'r_basicprofile r_emailaddress' }, # WebID webIDAuthnLevel => { type => 'int', default => 1, documentation => 'WebID authentication level', }, webIDWhitelist => { type => 'text', }, webIDExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'WebID exported variables', }, # DBI dbiAuthnLevel => { type => 'int', default => 2, documentation => 'DBI authentication level', }, dbiAuthChain => { type => 'text', }, dbiAuthUser => { type => 'text', }, dbiAuthPassword => { type => 'password', }, dbiUserChain => { type => 'text', }, dbiUserUser => { type => 'text', }, dbiUserPassword => { type => 'password', }, dbiAuthTable => { type => 'text', }, dbiUserTable => { type => 'text', }, dbiAuthLoginCol => { type => 'text', }, dbiAuthPasswordCol => { type => 'text', }, dbiPasswordMailCol => { type => 'text', }, userPivot => { type => 'text', }, dbiAuthPasswordHash => { type => 'text', help => 'authdbi.html#password', }, dbiDynamicHashEnabled => { type => 'bool', help => 'authdbi.html#password', }, dbiDynamicHashValidSchemes => { type => 'text', help => 'authdbi.html#password', }, dbiDynamicHashValidSaltedSchemes => { type => 'text', help => 'authdbi.html#password', }, dbiDynamicHashNewPasswordScheme => { type => 'text', help => 'authdbi.html#password', }, dbiExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'DBI exported variables', }, # Apache apacheAuthnLevel => { type => 'int', default => 4, documentation => 'Apache authentication level', }, # Null nullAuthnLevel => { type => 'int', default => 2, documentation => 'Null authentication level', }, # Kerberos krbKeytab => { type => 'text', documentation => 'Kerberos keytab', }, krbByJs => { type => 'bool', default => 0, documentation => 'Launch Kerberos authentication by Ajax', }, krbAuthnLevel => { type => 'int', default => 3, documentation => 'Null authentication level', }, krbUseModKrb => { type => 'bool', default => 0, documentation => 'Rely on Web Server Kerberos module', }, krbRemoveDomain => { type => 'bool', default => 1, documentation => 'Remove domain in Kerberos username', }, # Slave slaveAuthnLevel => { type => 'int', default => 2, documentation => 'Slave authentication level', }, slaveUserHeader => { type => 'text', }, slaveExportedVars => { type => 'keyTextContainer', keyTest => qr/^!?[a-zA-Z][a-zA-Z0-9_-]*$/, keyMsgFail => '__badVariableName__', test => qr/^[a-zA-Z][a-zA-Z0-9_:\-]*$/, msgFail => '__badValue__', default => {}, documentation => 'Slave exported variables', }, slaveMasterIP => { type => 'text', test => qr/^($Regexp::Common::URI::RFC2396::IPv4address\s*)*$/, msgFail => '__badIPv4Address__', }, slaveHeaderName => { type => 'text', }, slaveHeaderContent => { type => 'text', }, # Choice authChoiceParam => { type => 'text', default => 'lmAuth', documentation => 'Applications list', }, authChoiceModules => { type => 'authChoiceContainer', keyTest => qr/^(\d*)?[a-zA-Z0-9_]+$/, keyMsgFail => '__badChoiceKey__', test => sub { 1 }, select => [ [ { k => 'Apache', v => 'Apache' }, { k => 'AD', v => 'Active Directory' }, { k => 'BrowserID', v => 'BrowserID (Mozilla Persona)' }, { k => 'CAS', v => 'Central Authentication Service (CAS)' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demo' }, { k => 'Facebook', v => 'Facebook' }, { k => 'Google', v => 'Google' }, { k => 'Kerberos', v => 'Kerberos' }, { k => 'LDAP', v => 'LDAP' }, { k => 'LinkedIn', v => 'LinkedIn' }, { k => 'Null', v => 'None' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Radius', v => 'Radius' }, { k => 'Remote', v => 'Remote' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Slave', v => 'Slave' }, { k => 'SSL', v => 'SSL' }, { k => 'Twitter', v => 'Twitter' }, { k => 'WebID', v => 'WebID' }, { k => 'Yubikey', v => 'Yubikey' } ], [ { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demo' }, { k => 'Facebook', v => 'Facebook' }, { k => 'Google', v => 'Google' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Null', v => 'None' }, { k => 'OpenID', v => 'OpenID' }, { k => 'OpenIDConnect', v => 'OpenID Connect' }, { k => 'Proxy', v => 'Proxy' }, { k => 'Remote', v => 'Remote' }, { k => 'SAML', v => 'SAML v2' }, { k => 'Slave', v => 'Slave' }, { k => 'WebID', v => 'WebID' } ], [ { k => 'AD', v => 'Active Directory' }, { k => 'DBI', v => 'Database (DBI)' }, { k => 'Demo', v => 'Demo' }, { k => 'LDAP', v => 'LDAP' }, { k => 'Null', v => 'None' } ] ], }, # Multi multiAuthStack => { type => 'authParamsText', }, multiUserDBStack => { type => 'authParamsText', }, multiValuesSeparator => { type => 'authParamsText', default => '; ', documentation => 'Separator for multiple values', }, # Yubikey yubikeyAuthnLevel => { type => 'int', default => 3, documentation => 'Yubikey authentication level', }, yubikeyClientID => { type => 'text', }, yubikeySecretKey => { type => 'text', }, yubikeyPublicIDSize => { type => 'int', default => 12, documentation => 'Yubikey public ID size', }, # BrowserID browserIdAuthnLevel => { type => 'int', default => 1, documentation => 'Browser ID authentication level', }, browserIdAutoLogin => { type => 'bool', }, browserIdVerificationURL => { type => 'text', }, browserIdSiteName => { type => 'text', }, browserIdSiteLogo => { type => 'text', }, browserIdBackgroundColor => { type => 'text', }, # OpenID Connect auth params oidcAuthnLevel => { type => 'int', default => 1, documentation => 'OpenID Connect authentication level', }, oidcRPCallbackGetParam => { type => 'text', default => 'openidconnectcallback', documentation => 'OpenID Connect Callback GET URLparameter', }, oidcRPStateTimeout => { type => 'int', default => 600, documentation => 'OpenID Connect Timeout of state sessions', }, # OpenID Connect service oidcServiceMetaDataIssuer => { type => 'text', default => 'http://auth.example.com', documentation => 'OpenID Connect issuer', }, oidcServiceMetaDataAuthorizeURI => { type => 'text', default => 'authorize', documentation => 'OpenID Connect authorizaton endpoint', }, oidcServiceMetaDataTokenURI => { type => 'text', default => 'token', documentation => 'OpenID Connect token endpoint', }, oidcServiceMetaDataUserInfoURI => { type => 'text', default => 'userinfo', documentation => 'OpenID Connect user info endpoint', }, oidcServiceMetaDataJWKSURI => { type => 'text', default => 'jwks', documentation => 'OpenID Connect JWKS endpoint', }, oidcServiceMetaDataRegistrationURI => { type => 'text', default => 'register', documentation => 'OpenID Connect registration endpoint', }, oidcServiceMetaDataEndSessionURI => { type => 'text', default => 'logout', documentation => 'OpenID Connect end session endpoint', }, oidcServiceMetaDataCheckSessionURI => { type => 'text', default => 'checksession', documentation => 'OpenID Connect check session iframe', }, oidcServiceMetaDataAuthnContext => { type => 'keyTextContainer', keyTest => qr/\w/, default => { 'loa-1' => 1, 'loa-2' => 2, 'loa-3' => 3, 'loa-4' => 4, 'loa-5' => 5, }, documentation => 'OpenID Connect Authentication Context Class Ref', }, oidcServicePrivateKeySig => { type => 'RSAPrivateKey', }, oidcServicePublicKeySig => { type => 'RSAPublicKey', }, oidcServiceKeyIdSig => { type => 'text', documentation => 'OpenID Connect Signature Key ID', }, oidcServiceAllowDynamicRegistration => { type => 'bool', default => 0, documentation => 'OpenID Connect allow dynamic client registration', }, oidcServiceAllowAuthorizationCodeFlow => { type => 'bool', default => 1, documentation => 'OpenID Connect allow authorization code flow', }, oidcServiceAllowImplicitFlow => { type => 'bool', default => 0, documentation => 'OpenID Connect allow implicit flow', }, oidcServiceAllowHybridFlow => { type => 'bool', default => 0, documentation => 'OpenID Connect allow hybrid flow', }, oidcStorage => { type => 'PerlModule', }, oidcStorageOptions => { type => 'keyTextContainer', }, # OpenID Connect metadata nodes oidcOPMetaDataNodes => { type => 'oidcOPMetaDataNodeContainer', help => 'authopenidconnect.html#declare_the_openid_connect_provider_in_llng', }, oidcRPMetaDataNodes => { type => 'oidcRPMetaDataNodeContainer', help => 'idpopenidconnect.html#configuration_of_relying_party_in_llng', }, oidcOPMetaDataOptions => { type => 'subContainer', }, oidcRPMetaDataOptions => { type => 'subContainer', }, # OpenID Connect providers oidcOPMetaDataJSON => { type => 'file', }, oidcOPMetaDataJWKS => { type => 'file', }, oidcOPMetaDataExportedVars => { type => 'keyTextContainer', default => { 'cn' => 'name', 'sn' => 'family_name', 'mail' => 'email', 'uid' => 'sub' } }, oidcOPMetaDataOptionsConfigurationURI => { type => 'url', }, oidcOPMetaDataOptionsJWKSTimeout => { type => 'int', default => 0 }, oidcOPMetaDataOptionsClientID => { type => 'text', }, oidcOPMetaDataOptionsClientSecret => { type => 'password', }, oidcOPMetaDataOptionsScope => { type => 'text', default => 'openid profile' }, oidcOPMetaDataOptionsDisplay => { type => 'select', select => [ { k => '', v => '' }, { k => 'page', v => 'page' }, { k => 'popup', v => 'popup' }, { k => 'touch', v => 'touch' }, { k => 'wap', v => 'wap' }, ], default => "", }, oidcOPMetaDataOptionsPrompt => { type => 'text' }, oidcOPMetaDataOptionsMaxAge => { type => 'int', default => 0 }, oidcOPMetaDataOptionsUiLocales => { type => 'text', }, oidcOPMetaDataOptionsAcrValues => { type => 'text', }, oidcOPMetaDataOptionsTokenEndpointAuthMethod => { type => 'select', select => [ { k => 'client_secret_post', v => 'client_secret_post' }, { k => 'client_secret_basic', v => 'client_secret_basic' }, ], default => 'client_secret_post', }, oidcOPMetaDataOptionsCheckJWTSignature => { type => 'bool', default => 1 }, oidcOPMetaDataOptionsIDTokenMaxAge => { type => 'int', default => 30 }, oidcOPMetaDataOptionsUseNonce => { type => 'bool', default => 1 }, oidcOPMetaDataOptionsDisplayName => { type => 'text', }, oidcOPMetaDataOptionsIcon => { type => 'text', }, oidcOPMetaDataOptionsStoreIDToken => { type => 'bool', default => 0 }, # OpenID Connect relying parties oidcRPMetaDataExportedVars => { type => 'keyTextContainer', default => { 'name' => 'cn', 'family_name' => 'sn', 'email' => 'mail' } }, oidcRPMetaDataOptionsClientID => { type => 'text', }, oidcRPMetaDataOptionsClientSecret => { type => 'password', }, oidcRPMetaDataOptionsDisplayName => { type => 'text', }, oidcRPMetaDataOptionsIcon => { type => 'text', }, oidcRPMetaDataOptionsUserIDAttr => { type => 'text', }, oidcRPMetaDataOptionsIDTokenSignAlg => { type => 'select', select => [ { k => 'none', v => 'None' }, { k => 'HS256', v => 'HS256' }, { k => 'HS384', v => 'HS384' }, { k => 'HS512', v => 'HS512' }, { k => 'RS256', v => 'RS256' }, { k => 'RS384', v => 'RS384' }, { k => 'RS512', v => 'RS512' }, ], default => 'HS512', }, oidcRPMetaDataOptionsIDTokenExpiration => { type => 'int', default => 3600 }, oidcRPMetaDataOptionsAccessTokenExpiration => { type => 'int', default => 3600 }, oidcRPMetaDataOptionsRedirectUris => { type => 'text', }, oidcRPMetaDataOptionsExtraClaims => { type => 'keyTextContainer', default => {} }, oidcRPMetaDataOptionsBypassConsent => { type => 'bool', default => 0 }, oidcRPMetaDataOptionsPostLogoutRedirectUris => { type => 'text', }, }; } 1; CTrees.pm000066400000000000000000000205121325274564300352130ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build# This file contains the description of special subtrees of the manager # interface. # You can only use the following keys: # * title: the name of the node # * nodes: the subnodes of the node # * group: grouped subnodes (see RSAKey form for example) # * form: only for nodes, the form to display when selected # # Conf parameters are just strings in the `nodes` array # # All other ideas have to be set in Manager/Build/Attributes.pm ! # DON'T FORGET TO RUN jsongenerator.pl AFTER EACH CHANGE package Lemonldap::NG::Manager::Build::CTrees; sub cTrees { return { virtualHost => [ 'locationRules', 'exportedHeaders', 'post', { title => 'vhostOptions', help => 'configvhost.html#options', nodes => [ 'vhostPort', 'vhostHttps', 'vhostMaintenance', 'vhostAliases' ], }, ], samlIDPMetaDataNode => [ 'samlIDPMetaDataXML', 'samlIDPMetaDataExportedAttributes', { title => 'samlIDPMetaDataOptions', help => 'authsaml.html#options', form => 'simpleInputContainer', nodes => [ 'samlIDPMetaDataOptionsResolutionRule', 'samlIDPMetaDataOptionsNameIDFormat', 'samlIDPMetaDataOptionsForceAuthn', 'samlIDPMetaDataOptionsIsPassive', 'samlIDPMetaDataOptionsAllowProxiedAuthn', 'samlIDPMetaDataOptionsAllowLoginFromIDP', 'samlIDPMetaDataOptionsRequestedAuthnContext', 'samlIDPMetaDataOptionsRelayStateURL', ], }, { title => "samlIDPMetaDataOptionsSession", form => 'simpleInputContainer', nodes => [ "samlIDPMetaDataOptionsAdaptSessionUtime", "samlIDPMetaDataOptionsForceUTF8", "samlIDPMetaDataOptionsStoreSAMLToken" ] }, { title => "samlIDPMetaDataOptionsSignature", form => 'simpleInputContainer', nodes => [ "samlIDPMetaDataOptionsSignSSOMessage", "samlIDPMetaDataOptionsCheckSSOMessageSignature", "samlIDPMetaDataOptionsSignSLOMessage", "samlIDPMetaDataOptionsCheckSLOMessageSignature" ] }, { title => "samlIDPMetaDataOptionsBinding", form => 'simpleInputContainer', nodes => [ "samlIDPMetaDataOptionsSSOBinding", "samlIDPMetaDataOptionsSLOBinding" ] }, { title => "samlIDPMetaDataOptionsSecurity", form => 'simpleInputContainer', nodes => [ "samlIDPMetaDataOptionsEncryptionMode", "samlIDPMetaDataOptionsCheckTime", "samlIDPMetaDataOptionsCheckAudience" ] } ], samlSPMetaDataNode => [ "samlSPMetaDataXML", "samlSPMetaDataExportedAttributes", { title => "samlSPMetaDataOptions", help => 'idpsaml.html#options', nodes => [ { title => "samlSPMetaDataOptionsAuthnResponse", form => 'simpleInputContainer', nodes => [ "samlSPMetaDataOptionsNameIDFormat", "samlSPMetaDataOptionsNameIDSessionKey", "samlSPMetaDataOptionsOneTimeUse", "samlSPMetaDataOptionsSessionNotOnOrAfterTimeout", "samlSPMetaDataOptionsNotOnOrAfterTimeout", "samlSPMetaDataOptionsForceUTF8" ] }, { title => "samlSPMetaDataOptionsSignature", form => 'simpleInputContainer', nodes => [ "samlSPMetaDataOptionsSignSSOMessage", "samlSPMetaDataOptionsCheckSSOMessageSignature", "samlSPMetaDataOptionsSignSLOMessage", "samlSPMetaDataOptionsCheckSLOMessageSignature" ] }, { title => "samlSPMetaDataOptionsSecurity", form => 'simpleInputContainer', nodes => [ "samlSPMetaDataOptionsEncryptionMode", "samlSPMetaDataOptionsEnableIDPInitiatedURL" ] } ] } ], oidcOPMetaDataNode => [ 'oidcOPMetaDataJSON', 'oidcOPMetaDataJWKS', 'oidcOPMetaDataExportedVars', { title => 'oidcOPMetaDataOptions', nodes => [ { title => 'oidcOPMetaDataOptionsConfiguration', form => 'simpleInputContainer', nodes => [ 'oidcOPMetaDataOptionsConfigurationURI', 'oidcOPMetaDataOptionsJWKSTimeout', 'oidcOPMetaDataOptionsClientID', 'oidcOPMetaDataOptionsClientSecret', 'oidcOPMetaDataOptionsStoreIDToken' ] }, { title => 'oidcOPMetaDataOptionsProtocol', form => 'simpleInputContainer', nodes => [ 'oidcOPMetaDataOptionsScope', 'oidcOPMetaDataOptionsDisplay', 'oidcOPMetaDataOptionsPrompt', 'oidcOPMetaDataOptionsMaxAge', 'oidcOPMetaDataOptionsUiLocales', 'oidcOPMetaDataOptionsAcrValues', 'oidcOPMetaDataOptionsTokenEndpointAuthMethod', 'oidcOPMetaDataOptionsCheckJWTSignature', 'oidcOPMetaDataOptionsIDTokenMaxAge', 'oidcOPMetaDataOptionsUseNonce' ] }, { title => 'oidcOPMetaDataOptionsDisplayParams', form => 'simpleInputContainer', nodes => [ 'oidcOPMetaDataOptionsDisplayName', 'oidcOPMetaDataOptionsIcon' ] }, ] }, ], oidcRPMetaDataNode => [ 'oidcRPMetaDataExportedVars', { title => 'oidcRPMetaDataOptions', nodes => [ { title => 'oidcRPMetaDataOptionsAuthentication', form => 'simpleInputContainer', nodes => [ 'oidcRPMetaDataOptionsClientID', 'oidcRPMetaDataOptionsClientSecret' ] }, { title => 'oidcRPMetaDataOptionsDisplay', form => 'simpleInputContainer', nodes => [ 'oidcRPMetaDataOptionsDisplayName', 'oidcRPMetaDataOptionsIcon' ], }, 'oidcRPMetaDataOptionsUserIDAttr', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'oidcRPMetaDataOptionsIDTokenExpiration', 'oidcRPMetaDataOptionsAccessTokenExpiration', 'oidcRPMetaDataOptionsRedirectUris', 'oidcRPMetaDataOptionsPostLogoutRedirectUris', 'oidcRPMetaDataOptionsBypassConsent', ] }, 'oidcRPMetaDataOptionsExtraClaims', ], }; } 1; Tree.pm000066400000000000000000001233441325274564300347340ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build# This file describes the manager tree. # You can only use the following keys: # * title: the name of the node # * nodes: the subnodes of the node # * group: grouped subnodes (see RSAKey form for example) # * form: only for nodes, the form to display when selected # # Conf parameters are just strings in the `nodes` array # # All other ideas have to be set in Manager/Build/Attributes.pm ! # DON'T FORGET TO RUN jsongenerator.pl AFTER EACH CHANGE package Lemonldap::NG::Manager::Build::Tree; our $VERSION = '1.9.11'; # TODO: Missing: # * activeTimer # * confirmFormMethod # * redirectFormMethod sub tree { return [ { title => 'generalParameters', nodes => [ { title => 'portalParams', help => 'portal.html', nodes => [ '*portal', { title => 'portalMenu', help => 'portalmenu.html', nodes => [ { title => 'portalModules', form => 'simpleInputContainer', nodes => [ 'portalDisplayLogout', 'portalDisplayChangePassword', 'portalDisplayAppslist', 'portalDisplayLoginHistory' ] }, 'applicationList' ] }, { title => 'portalCustomization', help => 'portalcustom.html', nodes => [ 'portalSkin', 'portalSkinBackground', 'portalSkinRules', { title => 'portalButtons', form => 'simpleInputContainer', nodes => [ 'portalCheckLogins', 'portalDisplayResetPassword', 'portalDisplayRegister' ] }, { title => 'passwordManagement', form => 'simpleInputContainer', nodes => [ 'portalRequireOldPassword', 'hideOldPassword', 'mailOnPasswordChange' ] }, { title => 'portalOther', form => 'simpleInputContainer', nodes => [ 'portalUserAttr', 'portalOpenLinkInNewWindow', 'portalAntiFrame', 'portalPingInterval', 'portalErrorOnExpiredSession', 'portalErrorOnMailNotFound' ] } ] }, { title => 'portalCaptcha', help => 'captcha.html', nodes => [ 'captcha_login_enabled', 'captcha_mail_enabled', 'captcha_register_enabled', 'captcha_size', 'captchaStorage', 'captchaStorageOptions' ] } ] }, { title => 'authParams', help => 'start.html#authentication_users_and_password_databases', form => 'authParams', nodes => [ 'authentication', 'userDB', 'passwordDB' ], nodes_cond => [ { title => 'adParams', help => 'authad.html', nodes => [ 'ADPwdMaxAge', 'ADPwdExpireWarning' ] }, { title => 'choiceParams', help => 'authchoice.html', nodes => [ 'authChoiceParam', 'authChoiceModules' ] }, { title => 'apacheParams', help => 'authapache.html', form => 'simpleInputContainer', nodes => ['apacheAuthnLevel'] }, { title => 'browseridParams', help => 'authbrowserid.html', form => 'simpleInputContainer', nodes => [ 'browserIdAuthnLevel', 'browserIdAutoLogin', 'browserIdVerificationURL', 'browserIdSiteName', 'browserIdSiteLogo', 'browserIdBackgroundColor' ] }, { title => 'casParams', help => 'authcas.html', nodes => [ 'CAS_authnLevel', 'CAS_url', 'CAS_CAFile', 'CAS_renew', 'CAS_gateway', 'CAS_pgtFile', 'CAS_proxiedServices' ] }, { title => 'dbiParams', help => 'authdbi.html', nodes => [ 'dbiAuthnLevel', 'dbiExportedVars', { title => 'dbiConnection', help => 'authdbi.html#connection', nodes => [ { title => 'dbiConnectionAuth', form => 'simpleInputContainer', nodes => [ 'dbiAuthChain', 'dbiAuthUser', 'dbiAuthPassword' ] }, { title => 'dbiConnectionUser', form => 'simpleInputContainer', nodes => [ 'dbiUserChain', 'dbiUserUser', 'dbiUserPassword' ] } ] }, { title => 'dbiSchema', help => 'authdbi.html#schema', form => 'simpleInputContainer', nodes => [ 'dbiAuthTable', 'dbiUserTable', 'dbiAuthLoginCol', 'dbiAuthPasswordCol', 'dbiPasswordMailCol', 'userPivot' ] }, { title => 'dbiPassword', help => 'authdbi.html#password', nodes => [ 'dbiAuthPasswordHash', { title => 'dbiDynamicHash', help => 'authdbi.html#password', form => 'simpleInputContainer', nodes => [ 'dbiDynamicHashEnabled', 'dbiDynamicHashValidSchemes', 'dbiDynamicHashValidSaltedSchemes', 'dbiDynamicHashNewPasswordScheme' ] } ] } ] }, { title => 'demoParams', help => 'authdemo.html', nodes => ['demoExportedVars'] }, { title => 'facebookParams', help => 'authfacebook.html', nodes => [ 'facebookAuthnLevel', 'facebookExportedVars', 'facebookAppId', 'facebookAppSecret' ] }, { title => 'googleParams', help => 'authgoogle.html', nodes => [ 'googleAuthnLevel', 'googleExportedVars' ] }, { title => 'kerberosParams', help => 'authkerberos.html', nodes => [ 'krbKeytab', 'krbByJs', 'krbAuthnLevel', 'krbUseModKrb', 'krbRemoveDomain' ] }, { title => 'ldapParams', help => 'authldap.html', nodes => [ 'ldapAuthnLevel', 'ldapExportedVars', { title => 'ldapConnection', help => 'authldap.html#connection', form => 'simpleInputContainer', nodes => [ 'ldapServer', 'ldapPort', 'ldapBase', 'managerDn', 'managerPassword', 'ldapTimeout', 'ldapVersion', 'ldapRaw' ] }, { title => 'ldapFilters', help => 'authldap.html#filters', form => 'simpleInputContainer', nodes => [ 'LDAPFilter', 'AuthLDAPFilter', 'mailLDAPFilter', 'ldapSearchDeref', ] }, { title => 'ldapGroups', help => 'authldap.html#groups', form => 'simpleInputContainer', nodes => [ 'ldapGroupBase', 'ldapGroupObjectClass', 'ldapGroupAttributeName', 'ldapGroupAttributeNameUser', 'ldapGroupAttributeNameSearch', 'ldapGroupRecursive', 'ldapGroupAttributeNameGroup' ] }, { title => 'ldapPassword', help => 'authldap.html#password', form => 'simpleInputContainer', nodes => [ 'ldapPpolicyControl', 'ldapSetPassword', 'ldapChangePasswordAsUser', 'ldapPwdEnc', 'ldapUsePasswordResetAttribute', 'ldapPasswordResetAttribute', 'ldapPasswordResetAttributeValue', 'ldapAllowResetExpiredPassword' ] }, ] }, { title => 'linkedinParams', help => 'authlinkedin.html', nodes => [ 'linkedInAuthnLevel', 'linkedInClientID', 'linkedInClientSecret', 'linkedInFields', 'linkedInUserField', 'linkedInScope' ] }, { title => 'multiParams', help => 'authmulti.html', form => 'authParamsTextContainer', nodes => [ 'multiAuthStack', 'multiUserDBStack' ] }, { title => 'nullParams', help => 'authnull.html', form => 'simpleInputContainer', nodes => ['nullAuthnLevel'] }, { title => 'openidParams', help => 'authopenid.html', nodes => [ 'openIdAuthnLevel', 'openIdExportedVars', 'openIdSecret', 'openIdIDPList' ] }, { title => 'oidcParams', help => 'authopenidconnect.html', nodes => [ 'oidcAuthnLevel', 'oidcRPCallbackGetParam', 'oidcRPStateTimeout' ] }, { title => 'proxyParams', help => 'authproxy.html', form => 'simpleInputContainer', nodes => [ 'soapAuthService', 'remoteCookieName', 'soapSessionService' ] }, { title => 'radiusParams', help => 'authradius.html', form => 'simpleInputContainer', nodes => [ 'radiusAuthnLevel', 'radiusSecret', 'radiusServer' ] }, { title => 'remoteParams', help => 'authremote.html', nodes => [ 'remotePortal', 'remoteCookieName', 'remoteGlobalStorage', 'remoteGlobalStorageOptions' ] }, { title => 'slaveParams', help => 'authslave.html', nodes => [ 'slaveAuthnLevel', 'slaveExportedVars', 'slaveUserHeader', 'slaveMasterIP', 'slaveHeaderName', 'slaveHeaderContent' ] }, { title => 'sslParams', help => 'authssl.html', form => 'simpleInputContainer', nodes => [ 'SSLAuthnLevel', 'SSLVar' ] }, { title => 'twitterParams', help => 'authtwitter.html', form => 'simpleInputContainer', nodes => [ 'twitterAuthnLevel', 'twitterKey', 'twitterSecret', 'twitterAppName' ] }, { title => 'webidParams', help => 'authwebid.html', nodes => [ 'webIDAuthnLevel', 'webIDExportedVars', 'webIDWhitelist' ] }, { title => 'yubikeyParams', help => 'authyubikey.html', form => 'simpleInputContainer', nodes => [ 'yubikeyAuthnLevel', 'yubikeyClientID', 'yubikeySecretKey', 'yubikeyPublicIDSize' ] }, ], 'nodes_filter' => 'authParams' }, { title => 'issuerParams', help => 'start.html#identity_provider', nodes => [ { title => 'issuerDBSAML', help => 'idpsaml.html', form => 'simpleInputContainer', nodes => [ 'issuerDBSAMLActivation', 'issuerDBSAMLPath', 'issuerDBSAMLRule' ] }, { title => 'issuerDBCAS', help => 'idpcas.html', nodes => [ 'issuerDBCASActivation', 'issuerDBCASPath', 'issuerDBCASRule', { title => 'issuerDBCASOptions', nodes => [ 'casAttr', 'casAttributes', 'casAccessControlPolicy', 'casStorage', 'casStorageOptions' ] } ] }, { title => 'issuerDBOpenID', help => 'idpopenid.html', nodes => [ 'issuerDBOpenIDActivation', 'issuerDBOpenIDPath', 'issuerDBOpenIDRule', { title => 'issuerDBOpenIDOptions', nodes => [ 'openIdIssuerSecret', 'openIdAttr', 'openIdSPList', { title => 'openIdSreg', form => 'simpleInputContainer', nodes => [ 'openIdSreg_fullname', 'openIdSreg_nickname', 'openIdSreg_language', 'openIdSreg_postcode', 'openIdSreg_timezone', 'openIdSreg_country', 'openIdSreg_gender', 'openIdSreg_email', 'openIdSreg_dob' ] } ] } ] }, { title => 'issuerDBOpenIDConnect', help => 'idpopenidconnect.html', nodes => [ 'issuerDBOpenIDConnectActivation', 'issuerDBOpenIDConnectPath', 'issuerDBOpenIDConnectRule', ] }, { title => 'issuerDBGet', nodes => [ 'issuerDBGetActivation', 'issuerDBGetPath', 'issuerDBGetRule', 'issuerDBGetParameters' ] }, ] }, { title => 'logParams', help => 'logs.html', form => 'simpleInputContainer', nodes => [ 'syslog', 'trustedProxies', 'whatToTrace', 'hiddenAttributes' ] }, { title => 'cookieParams', help => 'ssocookie.html', form => 'simpleInputContainer', nodes => [ 'cookieName', '*domain', 'cda', 'securedCookie', 'httpOnly', 'cookieExpiration' ] }, { title => 'sessionParams', help => 'sessions.html', nodes => [ 'storePassword', 'timeout', 'timeoutActivity', 'timeoutActivityInterval', 'grantSessionRules', { title => 'sessionStorage', help => 'start.html#sessions_database', nodes => [ 'globalStorage', 'globalStorageOptions', 'localSessionStorage', 'localSessionStorageOptions' ] }, { title => 'multipleSessions', form => 'simpleInputContainer', nodes => [ 'singleSession', 'singleIP', 'singleUserByIP', 'singleSessionUserByIP', 'notifyDeleted', 'notifyOther' ] }, { title => 'persistentSessions', nodes => [ 'persistentStorage', 'persistentStorageOptions' ] } ] }, 'reloadUrls', { title => 'advancedParams', help => 'start.html#advanced_features', nodes => [ 'customFunctions', { title => 'soap', form => 'simpleInputContainer', nodes => [ 'Soap', 'exportedAttr' ] }, { title => 'loginHistory', help => 'loginhistory.html', nodes => [ 'loginHistoryEnabled', 'successLoginNumber', 'failedLoginNumber', 'sessionDataToRemember' ] }, { title => 'notifications', help => 'notifications.html', nodes => [ 'notification', 'notificationStorage', 'notificationStorageOptions', 'notificationWildcard', 'notificationXSLTfile' ] }, { title => 'passwordManagement', help => 'resetpassword.html', nodes => [ { title => 'SMTP', form => 'simpleInputContainer', nodes => [ 'SMTPServer', 'SMTPAuthUser', 'SMTPAuthPass' ] }, { title => 'mailHeaders', form => 'simpleInputContainer', nodes => [ 'mailFrom', 'mailReplyTo', 'mailCharset' ] }, { title => 'mailContent', form => 'simpleInputContainer', nodes => [ 'mailSubject', 'mailBody', 'mailConfirmSubject', 'mailConfirmBody' ] }, { title => 'mailOther', form => 'simpleInputContainer', nodes => [ 'mailUrl', 'randomPasswordRegexp', 'mailTimeout', 'mailSessionKey' ] } ] }, { title => 'register', help => 'register.html', form => 'simpleInputContainer', nodes => [ 'registerDB', 'registerUrl', 'registerTimeout', 'registerConfirmSubject', 'registerDoneSubject' ] }, { title => 'security', help => 'security.html#configure_security_settings', form => 'simpleInputContainer', nodes => [ 'userControl', 'portalForceAuthn', 'portalForceAuthnInterval', 'key', 'trustedDomains', 'useSafeJail', 'checkXSS', 'lwpSslOpts' ] }, { title => 'redirection', help => 'redirections.html', form => 'simpleInputContainer', nodes => [ 'https', 'port', 'useRedirectOnForbidden', 'useRedirectOnError', 'maintenance' ] }, { title => 'portalRedirection', help => 'redirections.html#portal_redirections', form => 'simpleInputContainer', nodes => [ 'jsRedirect', 'noAjaxHook' ] }, { title => 'specialHandlers', nodes => [ { title => 'zimbraHandler', help => 'applications/zimbra.html', form => 'simpleInputContainer', nodes => [ 'zimbraPreAuthKey', 'zimbraAccountKey', 'zimbraBy', 'zimbraUrl', 'zimbraSsoUrl' ] }, { title => 'sympaHandler', help => 'applications/sympa.html', form => 'simpleInputContainer', nodes => [ 'sympaSecret', 'sympaMailKey' ] }, { title => 'secureTokenHandler', help => 'securetoken.html', form => 'simpleInputContainer', nodes => [ 'secureTokenMemcachedServers', 'secureTokenExpiration', 'secureTokenAttribute', 'secureTokenUrls', 'secureTokenHeader', 'secureTokenAllowOnError' ] } ] }, 'nginxCustomHandlers', 'logoutServices', 'multiValuesSeparator', { title => 'forms', nodes => [ 'infoFormMethod', 'confirmFormMethod', 'redirectFormMethod', 'activeTimer', ] } ] } ] }, { title => 'variables', nodes => [ 'exportedVars', 'macros', 'groups' ] }, 'virtualHosts', { title => 'samlServiceMetaData', help => 'samlservice.html', nodes => [ 'samlEntityID', { title => 'samlServiceSecurity', help => 'samlservice.html#security_parameters', nodes => [ { title => 'samlServiceSecuritySig', form => 'RSAKey', group => [ 'samlServicePrivateKeySig', 'samlServicePrivateKeySigPwd', 'samlServicePublicKeySig' ] }, { title => 'samlServiceSecurityEnc', form => 'RSAKey', group => [ 'samlServicePrivateKeyEnc', 'samlServicePrivateKeyEncPwd', 'samlServicePublicKeyEnc' ] }, 'samlServiceUseCertificateInResponse' ] }, { title => 'samlNameIDFormatMap', help => 'samlservice.html#nameid_formats', form => 'simpleInputContainer', nodes => [ 'samlNameIDFormatMapEmail', 'samlNameIDFormatMapX509', 'samlNameIDFormatMapWindows', 'samlNameIDFormatMapKerberos' ] }, { title => 'samlAuthnContextMap', help => 'samlservice.html#authentication_contexts', form => 'simpleInputContainer', nodes => [ 'samlAuthnContextMapPassword', 'samlAuthnContextMapPasswordProtectedTransport', 'samlAuthnContextMapTLSClient', 'samlAuthnContextMapKerberos' ] }, { title => 'samlOrganization', help => 'samlservice.html#organization', form => 'simpleInputContainer', nodes => [ 'samlOrganizationDisplayName', 'samlOrganizationName', 'samlOrganizationURL' ] }, { title => 'samlSPSSODescriptor', help => 'samlservice.html#service_provider', nodes => [ 'samlSPSSODescriptorAuthnRequestsSigned', 'samlSPSSODescriptorWantAssertionsSigned', { title => 'samlSPSSODescriptorSingleLogoutService', nodes => [ 'samlSPSSODescriptorSingleLogoutServiceHTTPRedirect', 'samlSPSSODescriptorSingleLogoutServiceHTTPPost', 'samlSPSSODescriptorSingleLogoutServiceSOAP' ] }, { title => 'samlSPSSODescriptorAssertionConsumerService', nodes => [ 'samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact', 'samlSPSSODescriptorAssertionConsumerServiceHTTPPost' ] }, { title => 'samlSPSSODescriptorArtifactResolutionService', nodes => [ 'samlSPSSODescriptorArtifactResolutionServiceArtifact' ] } ] }, { title => 'samlIDPSSODescriptor', help => 'samlservice.html#identity_provider', nodes => [ 'samlIDPSSODescriptorWantAuthnRequestsSigned', { title => 'samlIDPSSODescriptorSingleSignOnService', nodes => [ 'samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect', 'samlIDPSSODescriptorSingleSignOnServiceHTTPPost', 'samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact', 'samlIDPSSODescriptorSingleSignOnServiceSOAP' ] }, { title => 'samlIDPSSODescriptorSingleLogoutService', nodes => [ 'samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect', 'samlIDPSSODescriptorSingleLogoutServiceHTTPPost', 'samlIDPSSODescriptorSingleLogoutServiceSOAP' ] }, { title => 'samlIDPSSODescriptorArtifactResolutionService', nodes => [ 'samlIDPSSODescriptorArtifactResolutionServiceArtifact' ] } ] }, { title => 'samlAttributeAuthorityDescriptor', help => 'samlservice.html#attribute_authority', nodes => [ { title => 'samlAttributeAuthorityDescriptorAttributeService', nodes => [ 'samlAttributeAuthorityDescriptorAttributeServiceSOAP' ] } ] }, { title => 'samlAdvanced', help => 'samlservice.html#advanced', nodes => [ 'samlIdPResolveCookie', 'samlMetadataForceUTF8', 'samlStorage', 'samlStorageOptions', 'samlRelayStateTimeout', 'samlUseQueryStringSpecific', { title => 'samlCommonDomainCookie', form => 'simpleInputContainer', nodes => [ 'samlCommonDomainCookieActivation', 'samlCommonDomainCookieDomain', 'samlCommonDomainCookieReader', 'samlCommonDomainCookieWriter' ] } ] } ] }, 'samlIDPMetaDataNodes', 'samlSPMetaDataNodes', { title => 'oidcServiceMetaData', help => 'openidconnectservice.html#service_configuration', nodes => [ 'oidcServiceMetaDataIssuer', { title => 'oidcServiceMetaDataEndPoints', form => 'simpleInputContainer', nodes => [ 'oidcServiceMetaDataAuthorizeURI', 'oidcServiceMetaDataTokenURI', 'oidcServiceMetaDataUserInfoURI', 'oidcServiceMetaDataJWKSURI', 'oidcServiceMetaDataRegistrationURI', 'oidcServiceMetaDataEndSessionURI', 'oidcServiceMetaDataCheckSessionURI', ] }, 'oidcServiceMetaDataAuthnContext', { title => 'oidcServiceMetaDataSecurity', nodes => [ { title => 'oidcServiceMetaDataKeys', form => 'RSAKeyNoPassword', group => [ 'oidcServicePrivateKeySig', 'oidcServicePublicKeySig', ], }, 'oidcServiceKeyIdSig', 'oidcServiceAllowDynamicRegistration', 'oidcServiceAllowAuthorizationCodeFlow', 'oidcServiceAllowImplicitFlow', 'oidcServiceAllowHybridFlow', ], }, { title => "oidcServiceMetaDataSessions", nodes => [ 'oidcStorage', 'oidcStorageOptions', ], }, ] }, 'oidcOPMetaDataNodes', 'oidcRPMetaDataNodes', ]; } 1; Cli.pm000066400000000000000000000243511325274564300335030ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Managerpackage Lemonldap::NG::Manager::Cli; use strict; use Mouse; use Data::Dumper; use Lemonldap::NG::Manager::Constants; extends('Lemonldap::NG::Manager::Cli::Lib'); has cfgNum => ( is => 'rw', isa => 'Int', trigger => sub { $_[0]->{req} = Lemonldap::NG::Manager::Cli::Request->new( cfgNum => $_[0]->{cfgNum} ); } ); has sep => ( is => 'rw', isa => 'Str', default => '/' ); has req => ( is => 'ro' ); has format => ( is => 'rw', isa => 'Str', default => "%-25s | %-25s | %-25s" ); has yes => ( is => 'rw', isa => 'Bool', default => 0 ); has force => ( is => 'rw', isa => 'Bool', default => 0 ); sub get { my ( $self, @keys ) = @_; die 'get requires at least one key' unless (@keys); L: foreach my $key (@keys) { my $value = $self->_getKey($key); if ( ref $value eq 'HASH' ) { print "$key has the following keys:\n"; print " $_\n" foreach ( sort keys %$value ); } else { $value //= ''; print "$key = $value\n"; } } } sub set { my ( $self, %pairs ) = @_; my $format = $self->format . "\n"; die 'set requires at least one key and one value' unless (%pairs); my @list; foreach my $key ( keys %pairs ) { my $oldValue = $self->_getKey($key); if ( ref $oldValue ) { die "$key seems to be a hash, modification refused"; } push @list, [ $key, $oldValue, $pairs{$key} ]; } unless ( $self->yes ) { print "Proposed changes:\n"; printf $format, 'Key', 'Old value', 'New value'; foreach (@list) { printf $format, @$_; } print "Confirm (N/y)? "; my $c = ; unless ( $c =~ /^y(?:es)?$/ ) { die "Aborting"; } } require Clone; my $new = Clone::clone( $self->mgr->currentConf ); foreach my $key ( keys %pairs ) { $self->_setKey( $new, $key, $pairs{$key} ); } return $self->_save($new); } sub addKey { my $self = shift; unless ( @_ % 3 == 0 ) { die 'usage: "addKey (?:rootKey newKey newValue)+'; } my $sep = $self->sep; my @list; while (@_) { my $root = shift; my $newKey = shift; my $value = shift; unless ( $root =~ /$simpleHashKeys$/o or $root =~ /$sep/o ) { die "$root is not a simple hash. Aborting"; } push @list, [ $root, $newKey, $value ]; } require Clone; my $new = Clone::clone( $self->mgr->currentConf ); foreach my $el (@list) { my @path = split $sep, $el->[0]; if ( $#path == 0 ) { $new->{ $path[0] }->{ $el->[1] } = $el->[2]; } elsif ( $#path == 1 ) { $new->{ $path[0] }->{ $path[1] }->{ $el->[1] } = $el->[2]; } else { die $el->[0] . " has too many levels. Aborting"; } } return $self->_save($new); } sub delKey { my $self = shift; unless ( @_ % 2 == 0 ) { die 'usage: "delKey (?:rootKey key)+'; } my $sep = $self->sep; my @list; while (@_) { my $root = shift; my $key = shift; unless ( $root =~ /$simpleHashKeys$/o or $root =~ /$sep/o ) { die "$root is not a simple hash. Aborting"; } push @list, [ $root, $key ]; } require Clone; my $new = Clone::clone( $self->mgr->currentConf ); foreach my $el (@list) { my @path = split $sep, $el->[0]; if ( $#path == 0 ) { delete $new->{ $path[0] }->{ $el->[1] }; } elsif ( $#path == 1 ) { delete $new->{ $path[0] }->{ $path[1] }->{ $el->[1] }; } else { die $el->[0] . " has too many levels. Aborting"; } } return $self->_save($new); } sub lastCfg { my ($self) = @_; return $self->jsonResponse('/confs/latest')->{cfgNum}; } sub _getKey { my ( $self, $key ) = @_; my $sep = $self->sep; my ( $base, @path ) = split $sep, $key; unless ( $base =~ /^\w+$/ ) { warn "Malformed key $base"; return (); } my $value = $self->mgr->getConfKey( $self->req, $base, noCache => 1 ); if ( $self->req->error ) { die $self->req->error; } if ( ref $value eq 'HASH' ) { while ( my $next = shift @path ) { unless ( exists $value->{$next} ) { warn "Unknown subkey $next for $key"; next L; } $value = $value->{$next}; } } elsif (@path) { warn "No subkeys for $base"; return (); } return $value; } sub _setKey { my ( $self, $conf, $key, $value ) = @_; my $sep = $self->sep; my (@path) = split $sep, $key; my $last = pop @path; while ( my $next = shift @path ) { $conf = $conf->{$next}; } $conf->{$last} = $value; } sub _save { my ( $self, $new ) = @_; require Lemonldap::NG::Manager::Conf::Parser; my $parser = Lemonldap::NG::Manager::Conf::Parser->new( { newConf => $new, refConf => $self->mgr->currentConf, req => $self->req } ); unless ( $parser->testNewConf() ) { printf STDERR "Modifications rejected: %s:\n", $parser->{message}; } my $saveParams = { force => $self->force }; if ( $self->force and $self->cfgNum ) { $saveParams->{cfgNum} = $self->cfgNum; $saveParams->{cfgNumFixed} = 1; } my $s = $self->mgr->confAcc->saveConf( $new, %$saveParams ); if ( $s > 0 ) { print STDERR "Saved under number $s\n"; $parser->{status} = [ $self->mgr->applyConf($new) ]; } else { printf STDERR "Modifications rejected: %s:\n", $parser->{message}; print STDERR Dumper($parser); } foreach (qw(errors warnings status)) { if ( $parser->{$_} and @{ $parser->{$_} } ) { my $s = Dumper( $parser->{$_} ); $s =~ s/\$VAR1\s*=\s*//; printf STDERR "%-8s: %s", ucfirst($_), $s; } } } sub run { my $self = shift; print STDERR "VERY EXPERIMENTAL FEATURE, prefer web interface\n"; # Options simply call corresponding accessor my $args = {}; while ( $_[0] =~ s/^--?// ) { my $k = shift; my $v = shift; if ( ref $self ) { eval { $self->$k($v) }; if ($@) { die "Unknown option -$k or bad value ($@)"; } } else { $args->{$k} = $v; } } unless ( ref $self ) { $self = $self->new($args); } unless (@_) { die 'nothing to do, aborting'; } $self->cfgNum( $self->lastCfg ) unless ( $self->cfgNum ); my $action = shift; unless ( $action =~ /^(?:get|set|addKey|delKey)$/ ) { die "unknown action $action. Only get, set, addKey or delKey are accepted"; } $self->$action(@_); } package Lemonldap::NG::Manager::Cli::Request; use Mouse; has cfgNum => ( is => 'rw' ); has error => ( is => 'rw' ); sub params { my ( $self, $key ) = @_; return $self->{$key}; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager::Cli - EXPERIMENTAL command line manager for Lemonldap::NG web SSO system. =head1 SYNOPSIS #!/usr/bin/env perl use warnings; use strict; use Lemonldap::NG::Manager::Cli; # Optional: you can specify here some parameters my $cli = Lemonldap::NG::Manager::Cli->new(iniFile=>'t/lemonldap-ng.ini'); $cli->run(@ARGV); or use llng-manager-cli provides with this package. llng-manager-cli =head1 DESCRIPTION Lemonldap::NG::Manager provides a web interface to manage Lemonldap::NG Web-SSO system. Lemonldap::NG Manager::Cli provides an EXPERIMENTAL command line client to read or modify configuration. =head1 METHODS =head2 ACCESSORS All accessors can be set using the command line: just set a '-' before their names. Example llng-manager-cli -sep ',' get macros,_whatToTrace =head3 iniFile() The lemonldap-ng.ini file to use is not default value. =head3 sep() The key separator, default to '/'. For example to read the value of macro _whatToTrace using ',', use: llng-manager-cli -sep ',' get macros,_whatToTrace =head3 cfgNum() The configuration number. If not set, it will use the latest configuration. =head3 yes() If set to 1, no confirmation is asked to save new values: llng-manager -yes 1 set portal http://somewhere/ =head3 force() Set it to 1 to save a configuration earlier than latest =head3 format() Confirmation array line format. Default to "%-25s | %-25s | %-25s" =head2 run() The main method: it reads option, command and launch the corresponding subroutine. =head3 Commands =head4 get Using get, you can read several keys. Example: llng-manager-cli get portal cookieName domain =head1 SEE ALSO For other features of llng-cli, see L Other links: L, L =head1 AUTHORS Original idea from David Delassus in 2012. =over =item Clement Oudot, Eclem.oudot@gmail.comE =item David Delassus, Elinkdd@cpan.orgE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Cli/000077500000000000000000000000001325274564300331405ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/ManagerLib.pm000066400000000000000000000010641325274564300342050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Clipackage Lemonldap::NG::Manager::Cli::Lib; use Mouse; use Lemonldap::NG::Manager; extends 'Lemonldap::NG::Common::PSGI::Cli::Lib'; has mgr => ( is => 'ro', isa => 'Lemonldap::NG::Manager' ); has app => ( is => 'ro', isa => 'CodeRef', builder => sub { my $args = { protection => 'none' }; $args->{configStorage} = { confFile => $_[0]->{iniFile} } if ( $_[0]->{iniFile} ); $_[0]->{mgr} = Lemonldap::NG::Manager->new($args); $_[0]->{mgr}->init($args); return $_[0]->{mgr}->run(); } ); 1; Conf.pm000066400000000000000000001074421325274564300336640ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager# This module implements all the methods that responds to '/confs/*' requests # It contains 4 sections: # - initialization methods # - private methods (to access required conf) # - display methods # - upload method package Lemonldap::NG::Manager::Conf; use 5.10.0; use utf8; use Mouse; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; use Lemonldap::NG::Manager::Constants; use Crypt::OpenSSL::RSA; use Convert::PEM; use URI::URL; use feature 'state'; extends 'Lemonldap::NG::Manager::Lib'; our $VERSION = '1.9.7'; ############################# # I. INITIALIZATION METHODS # ############################# use constant defaultRoute => 'manager.html'; sub addRoutes { my $self = shift; # HTML template $self->addRoute( 'manager.html', undef, ['GET'] ) # READ # Special keys ->addRoute( confs => { ':cfgNum' => [ qw(virtualHosts samlIDPMetaDataNodes samlSPMetaDataNodes applicationList oidcOPMetaDataNodes oidcRPMetaDataNodes authChoiceModules grantSessionRules) ] }, ['GET'] ) # Other keys ->addRoute( confs => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] ) # New key and conf save ->addRoute( confs => { newRSAKey => 'newRSAKey', raw => 'newRawConf', '*' => 'newConf' }, ['POST'] ) # Url loader ->addRoute( 'prx', undef, ['POST'] ); } ####################### # II. PRIVATE METHODS # ####################### ## @method scalar getConfKey($req, $key) # Return key value # # Return the value of $key key in current configuration. If cfgNum is set to # `latest`, get before last configuration number. # # Errors: set an error in $req->error and return undef if: # * query does not have a cfgNum parameter (set by Common/PSGI/Router.pm) # * cfgNum is not a number # #@param $req Lemonldap::NG::Common::PSGI::Request #@param $key Key name #@return keyvalue (string, int or hashref) sub getConfKey { my ( $self, $req, $key, @args ) = @_; state $confAcc ||= $self->confAcc; $self->lmLog( "Search for $key in conf", 'debug' ); # Verify that cfgNum has been asked unless ( defined $req->params('cfgNum') ) { $req->error("Missing configuration number"); return undef; } $self->lmLog( "Cfgnum set to " . $req->params('cfgNum'), 'debug' ); # when 'latest' => replace by last cfgNum if ( $req->params('cfgNum') eq 'latest' ) { my $tmp = $self->confAcc->lastCfg; $req->params( 'cfgNum', $tmp ); if ($Lemonldap::NG::Common::Conf::msg) { $req->error($Lemonldap::NG::Common::Conf::msg); return undef; } } elsif ( $req->params('cfgNum') !~ /^\d+$/ ) { $req->error("cfgNum must be a number"); return undef; } unless ( defined $self->getConfByNum( $req->params('cfgNum'), @args ) ) { $req->error( "Configuration " . $req->params('cfgNum') . " is not available (" . $Lemonldap::NG::Common::Conf::msg . ')' ); return undef; } # TODO: insert default values # Set an error if key is not defined return $self->currentConf->{$key}; } sub getConfByNum { my ( $self, $cfgNum, @args ) = @_; unless ( %{ $self->currentConf } and $cfgNum == $self->currentConf->{cfgNum} ) { my $tmp; if ( $cfgNum == 0 ) { require Lemonldap::NG::Manager::Conf::Zero; $tmp = Lemonldap::NG::Manager::Conf::Zero::zeroConf(); } else { $tmp = $self->confAcc->getConf( { cfgNum => $cfgNum, raw => 1, noCache => 1, @args } ); return undef unless ( $tmp and ref($tmp) and %$tmp ); } $self->currentConf($tmp); } return $cfgNum; } ######################## # III. Display methods # ######################## # Values are send depending of the /path/info/. For example, # /confs/1/portal to get portal value. # This section contains several methods: # - complex nodes: # * complexNodesRoot() call for root queries (no subkeys) to display the list # * virtualHosts() # * _samlMetaDataNodes() is called by saml(IDP|RP)MetaDataNode # * _oidcMetaDataNodes() is called by oidc(OP|RP)MetaDataNodes # - other special nodes: # * authChoiceModules() # * grantSessionRules() # * openIdIDPList() (old OpenID) # * applicationList() # - root: # root query (/confs/latest for example) is redirected to metadatas() # - other requests: # they are managed by getKey() # - newRSAKey() returns a new RSA key pair if /confs/newRSAKey is called in a # POST request # - prx() load a request and return the content (for SAML/OIDC metadatas) # 31 - Complex subnodes # ---------------- ## @method PSGI-JSON-response complexNodesRoot($req, $query, $tpl) # Respond to root requests for virtual hosts and SAMLmetadatas # #@param $req Lemonldap::NG::Common::PSGI::Request #@param $query Configuration root key #@param $tpl Javascript template to use (see JS/JSON generator script) #@return PSGI JSON response sub complexNodesRoot { my ( $self, $req, $query, $tpl ) = @_; $self->lmLog( "Query for $query template keys", 'debug' ); my $tmp = $self->getConfKey( $req, $query ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); my @res; if ( ref($tmp) ) { foreach my $f ( sort keys %$tmp ) { push @res, { id => "${tpl}s/$f", title => $f, type => $tpl, template => $tpl }; } } return $self->sendJSONresponse( $req, \@res ); } # 311 - Virtual hosts # ------------- ## @method PSGI-JSON-response virtualHosts($req, @path) # Respond to virtualhosts sub requests # #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `virtualhosts` #@return PSGI JSON response sub virtualHosts { my ( $self, $req, @path ) = @_; return $self->complexNodesRoot( $req, 'locationRules', 'virtualHost' ) unless (@path); my $vh = shift @path; my $query; unless ( $query = shift @path ) { return $self->sendError( $req, 'Bad request: virtualHost query must ask for a key', 400 ); } # Send setDefault for new vhosts return $self->sendError( $req, 'setDefault', 200 ) if ( $vh =~ /^new__/ ); # Reject unknown vhosts return $self->sendError( $req, "Unknown virtualhost ($vh)", 400 ) unless ( defined $self->getConfKey( $req, 'locationRules' )->{$vh} ); if ( $query =~ /^(?:(?:exportedHeader|locationRule)s|post)$/ ) { my ( $id, $resp ) = ( 1, [] ); my $vhk = eval { $self->getConfKey( $req, $query )->{$vh} } // {}; return $self->sendError( $req, undef, 400 ) if ( $req->error ); $self->lmLog( "Query for $vh/$query keys", 'debug' ); # Keys are ordered except 'default' which must be at the end foreach my $r ( sort { $query eq 'locationRules' ? ( $a eq 'default' ? 1 : ( $b eq 'default' ? -1 : $a cmp $b ) ) : $a cmp $b } keys %$vhk ) { my $res = { id => "virtualHosts/$vh/$query/" . $id++, title => $r, data => $vhk->{$r}, type => 'keyText', }; # If rule contains a comment, split it if ( $query eq 'locationRules' ) { $res->{comment} = ''; if ( $r =~ s/\(\?#(.*?)\)// ) { $res->{title} = $res->{comment} = $1; } $res->{re} = $r; $res->{type} = 'rule'; } elsif ( $query eq 'post' ) { $res->{data} = $vhk->{$r}; $res->{type} = 'post'; } push @$resp, $res; } return $self->sendJSONresponse( $req, $resp ); } elsif ( $query =~ /^vhost(?:(?:Aliase|Http)s|Maintenance|Port)$/ ) { $self->lmLog( "Query for $vh/$query key", 'debug' ); # TODO: verify how this is done actually my $k1 = $self->getConfKey( $req, 'vhostOptions' ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); # Default values are set by JS my $res = eval { $k1->{$vh}->{$query} } // undef; return $self->sendJSONresponse( $req, { value => $res } ); } else { return $self->sendError( $req, "Unknown vhost subkey ($query)", 400 ); } } # 312 - SAML # ---- ## @method PSGI-JSON-response _samlMetaDataNode($type, $req, @path) # Respond to SAML metadata subnodes # #@param $type `SP` or `IDP` #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `saml{IDP|SP}MetaDataNode` #@return PSGI JSON response sub _samlMetaDataNodes { my ( $self, $type, $req, @path ) = @_; return $self->complexNodesRoot( $req, "saml${type}MetaDataXML", "saml${type}MetaDataNode" ) unless (@path); my $partner = shift @path; my $query = shift @path; unless ($query) { return $self->sendError( $req, "Bad request: saml${type}MetaDataNode query must ask for a key", 400 ); } # setDefault response for new partners return $self->sendError( $req, 'setDefault', 200 ) if ( $partner =~ /^new__/ ); # Reject unknown partners return $self->sendError( $req, "Unknown SAML partner ($partner)", 400 ) unless ( defined eval { $self->getConfKey( $req, "saml${type}MetaDataXML" )->{$partner}; } ); my ( $id, $resp ) = ( 1, [] ); # Return all exported attributes if asked if ( $query =~ /^saml${type}MetaDataExportedAttributes$/ ) { my $pk = eval { $self->getConfKey( $req, $query )->{$partner} } // {}; return $self->sendError( $req, undef, 400 ) if ( $req->error ); foreach my $h ( sort keys %$pk ) { push @$resp, { id => "saml${type}MetaDataNodes/$partner/$query/" . $id++, title => $h, data => [ split /;/, $pk->{$h} ], type => 'samlAttribute', }; } return $self->sendJSONresponse( $req, $resp ); } # Simple root keys elsif ( $query =~ /^saml${type}MetaDataXML$/ ) { my $value = eval { $self->getConfKey( $req, $query )->{$partner}->{$query}; } // undef; return $self->sendError( $req, undef, 400 ) if ( $req->error ); return $self->sendJSONresponse( $req, { value => $value } ); } # These regexps are generated by jsongenerator.pl and stored in # Lemonldap::NG::Manager::Constants elsif ( $query =~ { IDP => qr/^$samlIDPMetaDataNodeKeys$/o, SP => qr/^$samlSPMetaDataNodeKeys$/o }->{$type} ) { my $value = eval { $self->getConfKey( $req, "saml${type}MetaDataOptions" )->{$partner} ->{$query}; } // undef; # Note that types "samlService" and "samlAssertion" will be splitted by # manager.js in an array return $self->sendJSONresponse( $req, { value => $value } ); } else { return $self->sendError( $req, "Bad key for saml${type}MetaDataNode ($query)", 400 ); } } ## @method PSGI-JSON-response samlIDPMetaDataNode($req, @path) # Launch _samlMetaDataNode('IDP', @_) # #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `samlIDPMetaDataNode` #@return PSGI JSON response sub samlIDPMetaDataNodes { my ( $self, $req, @path ) = @_; return $self->_samlMetaDataNodes( 'IDP', $req, @path ); } ## @method PSGI-JSON-response samlSPMetaDataNode($req, @path) # Launch _samlMetaDataNode('SP', @_) # #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `samlSPMetaDataNode` #@return PSGI JSON response sub samlSPMetaDataNodes { my ( $self, $req, @path ) = @_; return $self->_samlMetaDataNodes( 'SP', $req, @path ); } # 313 - OpenID-Connect # -------------- ## @method PSGI-JSON-response _oidcMetaDataNodes($type, $req, @path) # Respond to OpenID-Connect metadata subnodes # #@param $type `OP` or `RP` #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `oidc{OP|RP}MetaDataNode` #@return PSGI JSON response sub _oidcMetaDataNodes { my ( $self, $type, $req, @path ) = @_; my $refKey = ( $type eq 'RP' ? 'oidcRPMetaDataOptions' : 'oidcOPMetaDataJSON' ); return $self->complexNodesRoot( $req, $refKey, "oidc${type}MetaDataNode" ) unless (@path); my $partner = shift @path; my $query = shift @path; unless ($query) { return $self->sendError( $req, "Bad request: oidc${type}MetaDataNode query must ask for a key", 400 ); } # setDefault response for new partners return $self->sendError( $req, 'setDefault', 200 ) if ( $partner =~ /^new__/ ); # Reject unknown partners return $self->sendError( $req, "Unknown OpenID-Connect partner ($partner)", 400 ) unless ( defined eval { $self->getConfKey( $req, $refKey )->{$partner}; } ); my ( $id, $resp ) = ( 1, [] ); # Return all exported attributes if asked if ( $query =~ /^(?:oidc${type}MetaDataExportedVars|oidcRPMetaDataOptionsExtraClaims)$/ ) { my $pk = eval { $self->getConfKey( $req, $query )->{$partner} } // {}; return $self->sendError( $req, undef, 400 ) if ( $req->error ); foreach my $h ( sort keys %$pk ) { push @$resp, { id => "oidc${type}MetaDataNodes/$partner/$query/" . $id++, title => $h, data => $pk->{$h}, type => 'keyText', }; } return $self->sendJSONresponse( $req, $resp ); } # Long text types (OP only) elsif ( $query =~ /^oidcOPMetaData(?:JSON|JWKS)$/ ) { my $value = eval { $self->getConfKey( $req, $query )->{$partner}; } // undef; return $self->sendError( $req, undef, 400 ) if ( $req->error ); return $self->sendJSONresponse( $req, { value => $value } ); } # Options elsif ( $query =~ { OP => qr/^$oidcOPMetaDataNodeKeys$/o, RP => qr/^$oidcRPMetaDataNodeKeys$/o }->{$type} ) { my $value = eval { $self->getConfKey( $req, "oidc${type}MetaDataOptions" )->{$partner} ->{$query}; } // undef; return $self->sendJSONresponse( $req, { value => $value } ); } else { return $self->sendError( $req, "Bad key for oidc${type}MetaDataNode ($query)", 400 ); } } ## @method PSGI-JSON-response oidcOPMetaDataNodes($req, @path) # Launch _oidcMetaDataNodes('SP', @_) # #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `oidcOPMetaDataNode` #@return PSGI JSON response sub oidcOPMetaDataNodes { my ( $self, $req, @path ) = @_; return $self->_oidcMetaDataNodes( 'OP', $req, @path ); } ## @method PSGI-JSON-response oidcRPMetaDataNodes($req, @path) # Launch _oidcMetaDataNodes('SP', @_) # #@param $req Lemonldap::NG::Common::PSGI::Request #@param @path words in path after `oidcRPMetaDataNode` #@return PSGI JSON response sub oidcRPMetaDataNodes { my ( $self, $req, @path ) = @_; return $self->_oidcMetaDataNodes( 'RP', $req, @path ); } # 32 - Other special nodes # ------------------- # 321 - Choice authentication ## @method PSGI-JSON-response authChoiceModules($req,$key) # Returns authChoiceModules keys splitted in arrays # #@param $req Lemonldap::NG::Common::PSGI::Request #@param key optional subkey #@return PSGI JSON response sub authChoiceModules { my ( $self, $req, $key ) = @_; my $value = $self->getConfKey( $req, 'authChoiceModules' ); unless ($key) { my @res; foreach my $k ( sort keys %$value ) { push @res, { id => "authChoiceModules/$k", title => "$k", data => [ split /;/, $value->{$k} ], type => 'authChoice' }; } return $self->sendJSONresponse( $req, \@res ); } else { my $r = $value->{$key} ? [ split( /[;\|]/, $value->{$key} ) ] : []; return $self->sendJSONresponse( $req, { value => $r } ); } } # 322 - Rules to grant sessions ## @method PSGI-JSON-response grantSessionRules($req) # Split grantSessionRules key=>value into 3 elements # #@param $req Lemonldap::NG::Common::PSGI::Request #@return PSGI JSON response sub grantSessionRules { my ( $self, $req, $key ) = @_; return $self->sendError( 'Subkeys forbidden for grantSessionRules', 400 ) if ($key); my $value = $self->getConfKey( $req, 'grantSessionRules' ); my @res; sub _sort { my $A = ( $a =~ /^.*?##(.*)$/ )[0]; my $B = ( $b =~ /^.*?##(.*)$/ )[0]; return !$A ? 1 : !$B ? -1 : $A cmp $B; } my $id = 0; foreach my $k ( sort _sort keys %$value ) { my $r = $k; my $c = ( $r =~ s/^(.*)?##(.*)$/$1/ ? $2 : '' ); $id++; push @res, { id => "grantSessionRules/$id", title => $c || $r, re => $r, comment => $c, data => $value->{$k}, type => 'grant' }; } return $self->sendJSONresponse( $req, \@res ); } # 323 - (old)OpenID IDP black/white list ##method PSGI-JSON-response openIdIDPList($req) # Split openIdIDPList parameter into 2 elements sub openIdIDPList { my ( $self, $req, $key ) = @_; return $self->sendError( 'Subkeys forbidden for openIdIDPList', 400 ) if ($key); my $value = $self->getConfKey( $req, 'openIdIDPList' ); $value //= '0;'; my ( $type, $v ) = split /;/, $value; $v //= ''; return $self->sendJSONresponse( $req, { value => [ $type, $v ] } ); } # 324 - Application for menu # -------------------- ## @method PSGI-JSON-response applicationList($req, @other) # Return the full menu tree # #@param $req Lemonldap::NG::Common::PSGI::Request #@param @other words in path after `applicationList` #@return PSGI JSON response sub applicationList { my ( $self, $req, @other ) = @_; return $self->sendError( $req, 'There is no subkey for applicationList', 400 ) if (@other); my $apps = $self->getConfKey( $req, 'applicationList' ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); $apps = {} unless ( ref($apps) eq 'HASH' ); my $json = $self->_scanCatsAndApps( $apps, 'applicationList' ); return $self->sendJSONresponse( $req, $json ); } ## @method arrayRef _scanCatsAndApps($apps) # Recursive method used to build categories & applications menu # #@param $apps HashRef pointing to a subnode of catAndApps conf tree #@return arrayRef sub _scanCatsAndApps { my ( $self, $apps, $baseId ) = @_; my @res; foreach my $cat ( grep { not /^(?:catname|type)$/ } sort keys %$apps ) { my $item = { id => "$baseId/$cat" }; if ( $apps->{$cat}->{type} eq 'category' ) { $item->{title} = $apps->{$cat}->{catname}; $item->{type} = 'menuCat'; $item->{nodes} = $self->_scanCatsAndApps( $apps->{$cat}, "$baseId/$cat" ); } else { $item->{title} = $apps->{$cat}->{options}->{name}; $item->{type} = $apps->{$cat}->{type} = 'menuApp'; foreach my $o ( grep { not /^name$/ } keys %{ $apps->{$cat}->{options} } ) { $item->{data}->{$o} = $apps->{$cat}->{options}->{$o}; } } push @res, $item; } return \@res; } # 33 - Root queries # ----------- ## @method PSGI-JSON-response metadatas($req) # Respond to `/conf/:cfgNum` requests by sending configuration metadatas # # NB: if `full=1` is set in the query, configuration is returned directly in # JSON # #@param $req Lemonldap::NG::Common::PSGI::Request #@return PSGI JSON response sub metadatas { my ( $self, $req ) = @_; if ( $req->params('full') and $req->params('full') !~ $no ) { my $c = $self->getConfKey( $req, 'cfgNum' ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); $self->userNotice( 'User ' . $self->userId($req) . ' ask for full configuration ' . $c ); return $self->sendJSONresponse( $req, $self->currentConf, forceJSON => 1, headers => [ 'Content-Disposition' => "Attachment; filename=lmConf-$c.json" ], ); } else { my $res = {}; $res->{cfgNum} = $self->getConfKey( $req, 'cfgNum' ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); return $self->sendError( $req, "Configuration without cfgNum", 500 ) unless ( defined $res->{cfgNum} ); foreach my $key (qw(cfgAuthor cfgDate cfgAuthorIP cfgLog)) { $res->{$key} = $self->getConfKey( $req, $key ); } # Find next and previous conf my @a = $self->confAcc->available; my $id = -1; my ($ind) = map { $id++; $_ == $res->{cfgNum} ? ($id) : () } @a; if ($ind) { $res->{prev} = $a[ $ind - 1 ]; } if ( $ind and $ind < $#a ) { $res->{next} = $a[ $ind + 1 ]; } $self->userNotice( 'User ' . $self->userId($req) . ' ask for configuration metadatas (' . $res->{cfgNum} . ')' ); return $self->sendJSONresponse( $req, $res ); } } # 34 - Other values # ------------ ## @method PSGI-JSON-response getKey($req, $key, $subkey) # Return the value of a root key of current configuration # #@param $req Lemonldap::NG::Common::PSGI::Request #@param $key Name of key requested #@param $subkey Subkey for hash values #@return PSGI JSON response sub getKey { my ( $self, $req, $key, $subkey ) = @_; unless ($key) { return $self->metadatas($req); } $self->userInfo( 'User ' . $self->userId($req) . " asks for key $key" ); my $value = $self->getConfKey( $req, $key ); return $self->sendError( $req, undef, 400 ) if ( $req->error ); # When "hash" if ( $key =~ qr/^$simpleHashKeys$/o ) { return $self->sendError( $req, 'setDefault', 200 ) unless defined($value); # If a hash key is asked return its value if ($subkey) { return $self->sendJSONresponse( $req, { value => $value->{$subkey} // undef, } ); } # else return the list of keys my @res; foreach my $k ( sort keys %$value ) { push @res, { id => "$key/$k", title => "$k", data => $value->{$k}, type => 'keyText' }; } return $self->sendJSONresponse( $req, \@res ); } elsif ( $key =~ qr/^$doubleHashKeys$/o ) { my @res; $value ||= {}; foreach my $host ( sort keys %$value ) { my @tmp; foreach my $k ( sort keys %{ $value->{$host} } ) { push @tmp, { k => $k, v => $value->{$host}->{$k} }; } push @res, { k => $host, h => \@tmp }; } return $self->sendJSONresponse( $req, { value => \@res } ); } # When scalar return $self->sendError( $req, "Key $key is not a hash", 400 ) if ($subkey); return $self->sendError( $req, 'setDefault', 200 ) unless defined($value); return $self->sendJSONresponse( $req, { value => $value } ); # TODO authParam key } # 35 - New RSA key pair on demand # -------------------------- ##@method public PSGI-JSON-response newRSAKey($req) # Return a hashref containing private and public keys # The posted datas must contain a JSON object containing # {"password":"newpassword"} # #@param $req Lemonldap::NG::Common::PSGI::Request object #@return PSGI JSON response sub newRSAKey { my ( $self, $req, @others ) = @_; return $self->sendError( $req, 'There is no subkey for "newRSAKey"', 400 ) if (@others); my $query = $req->jsonBodyToObj; my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); my $keys = { 'private' => $rsa->get_private_key_string(), 'public' => $rsa->get_public_key_x509_string(), }; if ( $query->{password} ) { my $pem = Convert::PEM->new( Name => 'RSA PRIVATE KEY', ASN => q( RSAPrivateKey SEQUENCE { version INTEGER, n INTEGER, e INTEGER, d INTEGER, p INTEGER, q INTEGER, dp INTEGER, dq INTEGER, iqmp INTEGER } ) ); $keys->{private} = $pem->encode( Content => $pem->decode( Content => $keys->{private} ), Password => $query->{password}, ); } return $self->sendJSONresponse( $req, $keys ); } # 36 - URL File loader # --------------- ##@method public PSGI-JSON-response prx() # Load file using posted URL and return its content # #@return PSGI JSON response sub prx { my ( $self, $req, @others ) = @_; return $self->sendError( $req, 'There is no subkey for "prx"', 400 ) if (@others); my $query = $req->jsonBodyToObj; return $self->sendError( $req, 'Missing parameter', 400 ) unless ( $query->{url} ); return $self->sendError( $req, 'Bad parameter', 400 ) unless ( $query->{url} =~ m#^(?:f|ht)tps?://\w# ); require LWP::UserAgent; my $ua = new LWP::UserAgent(); $ua->timeout(10); my $response = $ua->get( $query->{url} ); unless ( $response->code == 200 ) { return $self->sendError( $req, $response->code . " (" . $response->message . ")", 400 ); } unless ( $response->header('Content-Type') =~ m#^(?:application/json|(?:application|text)/.*xml).*$# ) { return $self->sendError( $req, 'Content refused for security reason (neither XML or JSON)', 400 ); } return $self->sendJSONresponse( $req, { content => $response->content } ); } ###################### # IV. Upload methods # ###################### # In this section, 3 methods: # - newConf() # - newRawConf(): restore a saved conf # - applyConf(): called by the 2 previous to prevent other servers that a new # configuration is available ## @method PSGI-JSON-response newConf($req) # Call Lemonldap::NG::Manager::Conf::Parser to parse new configuration and store # it # #@param $req Lemonldap::NG::Common::PSGI::Request #@return PSGI JSON response sub newConf { my ( $self, $req, @other ) = @_; return $self->sendError( $req, 'There is no subkey for "newConf"', 400 ) if (@other); # Body must be json my $new = $req->jsonBodyToObj; unless ( defined($new) ) { return $self->sendError( $req, undef, 400 ); } # Verify that cfgNum has been asked unless ( defined $req->params('cfgNum') ) { return $self->sendError( $req, "Missing configuration number", 400 ); } # Set current conf to cfgNum unless ( defined $self->getConfByNum( $req->params('cfgNum') ) ) { return $self->sendError( $req, "Configuration " . $req->params('cfgNum') . " not available " . $Lemonldap::NG::Common::Conf::msg, 400 ); } # Parse new conf require Lemonldap::NG::Manager::Conf::Parser; my $parser = Lemonldap::NG::Manager::Conf::Parser->new( { tree => $new, refConf => $self->currentConf, req => $req } ); # If ref conf isn't last conf, consider conf changed my $cfgNum = $self->confAcc->lastCfg; unless ( defined $cfgNum ) { $req->error($Lemonldap::NG::Common::Conf::msg); } return $self->sendError( $req, undef, 400 ) if ( $req->error ); if ( $cfgNum ne $req->params('cfgNum') ) { $parser->confChanged(1); } my $res = { result => $parser->check }; # "message" fields: note that words enclosed by "__" (__word__) will be # translated $res->{message} = $parser->{message}; foreach my $t (qw(errors warnings changes)) { $res->{details}->{ '__' . $t . '__' } = $parser->$t if ( @{ $parser->$t } ); } if ( $res->{result} ) { if ( $self->{demoMode} ) { $res->{message} = '__demoModeOn__'; } else { my %args; $args{force} = 1 if ( $req->params('force') ); my $s = $self->confAcc->saveConf( $parser->newConf, %args ); if ( $s > 0 ) { $self->userNotice( 'User ' . $self->userId($req) . " has stored conf $s" ); $res->{result} = 1; $res->{cfgNum} = $s; if ( my $status = $self->applyConf( $parser->newConf ) ) { push @{ $res->{details}->{__applyResult__} }, { message => "$_: $status->{$_}" } foreach ( keys %$status ); } } else { $self->userNotice( 'Saving attempt rejected, asking for confirmation to ' . $self->userId($req) ); $res->{result} = 0; if ( $s == CONFIG_WAS_CHANGED ) { $res->{needConfirm} = 1; $res->{message} .= '__needConfirmation__'; } else { $res->{message} = $Lemonldap::NG::Common::Conf::msg; } } } } return $self->sendJSONresponse( $req, $res ); } ## @method PSGI-JSON-response newRawConf($req) # Store directly raw configuration # #@param $req Lemonldap::NG::Common::PSGI::Request #@return PSGI JSON response sub newRawConf { my ( $self, $req, @other ) = @_; return $self->sendError( $req, 'There is no subkey for "newConf"', 400 ) if (@other); # Body must be json my $new = $req->jsonBodyToObj; unless ( defined($new) ) { return $self->sendError( $req, undef, 400 ); } my $res = {}; if ( $self->{demoMode} ) { $res->{message} = '__demoModeOn__'; } else { # When uploading a new conf, always force it since cfgNum has a few # chances to be equal to last config cfgNum my $s = $self->confAcc->saveConf( $new, force => 1 ); if ( $s > 0 ) { $self->userNotice( 'User ' . $self->userId($req) . " has stored (raw) conf $s" ); $res->{result} = 1; $res->{cfgNum} = $s; } else { $self->userNotice( 'Raw saving attempt rejected, asking for confirmation to ' . $self->userId($req) ); $res->{result} = 0; $res->{needConfirm} = 1 if ( $s == CONFIG_WAS_CHANGED ); $res->{message} .= '__needConfirmation__'; } } return $self->sendJSONresponse( $req, $res ); } ## @method private applyConf() # Try to prevent other servers declared in `reloadUrls` that a new # configuration is available. # #@return reload status as boolean sub applyConf { my ( $self, $newConf ) = @_; my $status; # Get apply section values my %reloadUrls = %{ $self->confAcc->getLocalConf( APPLYSECTION, undef, 0 ) }; if ( !%reloadUrls && $newConf->{reloadUrls} ) { %reloadUrls = %{ $newConf->{reloadUrls} }; } return {} unless (%reloadUrls); # Create user agent require LWP::UserAgent; my $ua = new LWP::UserAgent( requests_redirectable => [] ); $ua->timeout(3); # Parse apply values while ( my ( $host, $request ) = each %reloadUrls ) { my $r = HTTP::Request->new( 'GET', "http://$host$request" ); if ( $request =~ /^https?:\/\/[^\/]+.*$/ ) { my $url = URI::URL->new($request); my $targetUrl = $url->scheme . "://" . $host; $targetUrl .= ":" . $url->port if defined( $url->port ); $targetUrl .= $url->full_path; $r = HTTP::Request->new( 'GET', $targetUrl, HTTP::Headers->new( Host => $url->host ) ); if ( defined $url->userinfo && $url->userinfo =~ /^([^:]+):(.*)$/ ) { $r->authorization_basic( $1, $2 ); } } my $response = $ua->request($r); if ( $response->code != 200 ) { $status->{$host} = "Error " . $response->code . " (" . $response->message . ")"; $self->userError( "Apply configuration for $host: error " . $response->code . " (" . $response->message . ")" ); } else { $status->{$host} = "OK"; $self->userNotice("Apply configuration for $host: ok"); } } return $status; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager::Conf - Configuration management part of L. =head1 SYNOPSIS See L. =head1 DESCRIPTION Lemonldap::NG::Manager provides a web interface to manage Lemonldap::NG Web-SSO system. The Perl part of Lemonldap::NG::Manager is the REST server. Web interface is written in Javascript, using AngularJS framework and can be found in `site` directory. The REST API is described in REST-API.md file given in source tree. Lemonldap::NG Manager::Conf provides the configuration management part. =head1 ORGANIZATION Lemonldap::NG::Manager configuration is managed by 2 files: =over =item This file to display configuration metadatas and keys content, and to save new configuration, =item L used to check proposed configuration. =back =head1 OPERATION The first Ajax request given by the manager web interface is generaly `/confs/latest`, Lemonldap::NG::Manager::Conf returns the configuration metadatas (author, data, log,...). Then for each key read by the user, web interface launch an Ajax request to get the value. At the end, when modifications are saved, a POST request is done to `/confs`. Then Lemonldap::NG::Manager::Conf calls L to verify new configuration. If good, it tries to store it. Then it calls applyConf() that tries to call other servers to explain them that configuration has changed. Then it returns all errors, warnings in a JSON object that is displayed by web interface. =head1 SEE ALSO L, L, L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Conf/000077500000000000000000000000001325274564300333165ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/ManagerParser.pm000066400000000000000000001201501325274564300351070ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Confpackage Lemonldap::NG::Manager::Conf::Parser; # This module is called either to parse a new configuration in JSON format (as # posted by the web interface) and test a new configuration object. # # The new object must be build with the following properties: # - refConf: the actual configuration # - req : the Lemonldap::NG::Common::PSGI::Request # - tree : the new configuration in JSON format # or # - newConf: the configuration to test # # The main method is check() which calls: # - scanTree() if configuration is not parsed (JSON string) # - testNewConf() # # It returns a boolean. Errors, warnings and changes are stored as array # containing `{ message => 'Explanation' }. A main message is stored in # `message` property. use strict; use utf8; use Mouse; use Lemonldap::NG::Manager::Constants; use Lemonldap::NG::Manager::Attributes; our $VERSION = '1.9.4'; # High debugging for developpers, set this to 1 use constant HIGHDEBUG => 0; # Messages storage has errors => ( is => 'rw', isa => 'ArrayRef', default => sub { return [] } ); has warnings => ( is => 'rw', isa => 'ArrayRef', default => sub { return [] }, trigger => sub { hdebug( 'warnings contains', $_[0]->{warnings} ); } ); has changes => ( is => 'rw', isa => 'ArrayRef', default => sub { return [] } ); has message => ( is => 'rw', isa => 'Str', default => '', trigger => sub { hdebug( "Message becomes " . $_[0]->{message} ); } ); # Booleans has needConfirm => ( is => 'rw', isa => 'ArrayRef', default => sub { return [] } ); has confChanged => ( is => 'rw', isa => 'Bool', default => 0, trigger => sub { hdebug( "condChanged: " . $_[0]->{confChanged} ); } ); # Properties required during build has refConf => ( is => 'ro', isa => 'HashRef', required => 1 ); has req => ( is => 'ro', required => 1 ); has newConf => ( is => 'rw', isa => 'HashRef' ); has tree => ( is => 'rw', isa => 'ArrayRef' ); # High debug method sub hdebug { if (HIGHDEBUG) { foreach my $d (@_) { if ( ref $d ) { require Data::Dumper; print STDERR Data::Dumper::Dumper($d); } else { print STDERR "$d\n" } } } undef; } ##@method boolean check() # Main method #@return result sub check { my $self = shift; hdebug("# check()"); my $res; unless ( $self->newConf ) { $res = $self->scanTree; return 0 unless ($res); } unless ( $self->testNewConf ) { hdebug(" testNewConf() failed"); return 0; } hdebug(" tests succeed"); unless ( $self->confChanged ) { hdebug(" no changes detected"); $self->message('__confNotChanged__'); return 0; } return 1; } ##@method boolean scanTree() # Methods to build new conf from JSON string #@result true if succeed sub scanTree { my $self = shift; hdebug("# scanTree()"); $self->newConf( {} ); $self->_scanNodes( $self->tree ) or return 0; # Set cfgNum to ref cfgNum (will be changed when saving), set other # metadatas and set a value to the key if empty $self->newConf->{cfgNum} = $self->req->params('cfgNum'); $self->newConf->{cfgAuthor} = $self->req->userData->{ $Lemonldap::NG::Handler::Main::tsv->{whatToTrace} || '_whatToTrace' } // "anonymous"; $self->newConf->{cfgAuthorIP} = $self->req->remote_ip; $self->newConf->{cfgDate} = time; $self->newConf->{key} ||= join( '', map { chr( int( rand(94) ) + 33 ) } ( 1 .. 16 ) ); return 1; } use feature 'state'; ##@method private boolean _scanNodes() # Recursive JSON parser #@result true if succeed sub _scanNodes { my ( $self, $tree, ) = @_; hdebug("# _scanNodes()"); state( $knownCat, %newNames ); unless ( ref($tree) eq 'ARRAY' ) { print STDERR 'Fatal: node is not an array'; push @{ $self->errors }, { message => 'Fatal: node is not an array' }; return 0; } unless (@$tree) { hdebug(' empty tree !?'); } foreach my $leaf (@$tree) { my $name = $leaf->{title}; hdebug("Looking to $name"); # subnode my $subNodes = $leaf->{nodes} // $leaf->{_nodes}; my $subNodesCond = $leaf->{nodes_cond} // $leaf->{_nodes_cond}; ################################## # VirtualHosts and SAML partners # ################################## # Root nodes if ( $leaf->{id} =~ /^($specialNodeKeys)$/io ) { hdebug("Root special node detected $leaf->{id}"); # If node has not been opened if ( $leaf->{cnodes} ) { hdebug(" not opened"); foreach my $k ( @{ $specialNodeHash->{ $leaf->{id} } } ) { hdebug(" copying $k"); $self->newConf->{$k} = $self->refConf->{$k}; } next; } $self->_scanNodes($subNodes); # Check deleted keys my $field = $specialNodeHash->{ $leaf->{id} }->[0]; my @old = keys %{ $self->refConf->{$field} }; foreach my $k ( keys %{ $self->newConf->{$field} } ) { @old = grep { $_ ne $k } @old; } if (@old) { hdebug( "Keys detected as removed:", \@old ); $self->confChanged(1); foreach my $deletedHost (@old) { push @{ $self->changes }, { key => $leaf->{id}, old => $deletedHost }; } } next; } # 1st sublevel elsif ( $leaf->{id} =~ /^($specialNodeKeys)\/([^\/]+)$/io ) { hdebug("Special node chield detected $leaf->{id}"); my ( $base, $host ) = ( $1, $2 ); # Check hostname/partner name changes (id points to the old name) $newNames{$host} = $leaf->{title}; if ( $newNames{$host} ne $host and $host !~ /^new__/ ) { hdebug(" $host becomes $newNames{$host}"); $self->confChanged(1); push @{ $self->changes }, { key => $base, old => $host, new => $newNames{$host} }; } $self->_scanNodes($subNodes); next; } # Other sub levels elsif ( $leaf->{id} =~ /^($specialNodeKeys)\/([^\/]+)\/([^\/]+)(?:\/(.*))?$/io ) { my ( $base, $key, $oldName, $target, $h ) = ( $1, $newNames{$2}, $2, $3, $4 ); hdebug( "Special node chield subnode detected $leaf->{id}", " base $base, key $key, target $target, h " . ( $h ? $h : 'undef' ) ); # VirtualHosts if ( $base eq 'virtualHosts' ) { hdebug(" virtualhost"); if ( $target =~ /^(?:locationRules|exportedHeaders|post)$/ ) { if ( $leaf->{cnodes} ) { hdebug(' unopened subnode'); $self->newConf->{$target}->{$key} = $self->refConf->{$target}->{$oldName} // {}; } elsif ($h) { hdebug(' 4 levels'); if ( $target eq 'locationRules' ) { hdebug(' locationRules'); my $k = $leaf->{comment} ? "(?#$leaf->{comment})$leaf->{re}" : $leaf->{re}; $self->set( $target, $key, $k, $leaf->{data} ); } else { hdebug(' other than locationrules'); $self->set( $target, $key, $leaf->{title}, $leaf->{data} ); } } # Unless $h is set, scan subnodes and check changes else { hdebug(' 3 levels only (missing $h)'); if ( ref $subNodes ) { hdebug(' has subnodes'); $self->_scanNodes($subNodes) or return 0; } if ( exists $self->refConf->{$target}->{$key} and %{ $self->refConf->{$target}->{$key} } ) { hdebug(' old conf subnode has values'); my $c = $self->newConf->{$target}; foreach my $k ( keys %{ $self->refConf->{$target}->{$key} } ) { unless ( defined $c->{$key}->{$k} ) { hdebug(' missing value in old conf'); $self->confChanged(1); push @{ $self->changes }, { key => "$target, $key", old => $k, }; } } } elsif ( exists $self->newConf->{$target}->{$key} and %{ $self->newConf->{$target}->{$key} } ) { hdebug(" '$key' has values"); $self->confChanged(1); push @{ $self->changes }, { key => "$target", new => $key }; } } } elsif ( $target =~ /^$virtualHostKeys$/o ) { $self->set( 'vhostOptions', [ $oldName, $key ], $target, $leaf->{data} ); } else { push @{ $self->errors }, { message => "Unknown vhost key $target" }; return 0; } next; } # SAML elsif ( $base =~ /^saml(?:S|ID)PMetaDataNodes$/ ) { hdebug('SAML'); if ( defined $leaf->{data} and ref( $leaf->{data} ) eq 'ARRAY' ) { hdebug(" SAML data is an array, serializing"); $leaf->{data} = join ';', @{ $leaf->{data} }; } if ( $target =~ /^saml(?:S|ID)PMetaDataExportedAttributes$/ ) { if ( $leaf->{cnodes} ) { hdebug(" $target: unopened node"); $self->newConf->{$target}->{$key} = $self->refConf->{$target}->{$oldName} // {}; } elsif ($h) { hdebug(" $target: opened node"); $self->confChanged(1); $self->set( $target, $key, $leaf->{title}, $leaf->{data} ); } else { hdebug(" $target: looking for subnodes"); $self->_scanNodes($subNodes); } } elsif ( $target =~ /^saml(?:S|ID)PMetaDataXML$/ ) { hdebug(" $target"); $self->set( $target, [ $oldName, $key ], $target, $leaf->{data} ); } elsif ( $target =~ /^saml(?:ID|S)PMetaDataOptions/ ) { my $optKey = $&; hdebug(" $base sub key: $target"); if ( $target =~ /^(?:$samlIDPMetaDataNodeKeys|$samlSPMetaDataNodeKeys)/o ) { $self->set( $optKey, [ $oldName, $key ], $target, $leaf->{data} ); } else { push @{ $self->errors }, { message => "Unknown SAML metadata option $target" }; return 0; } } else { push @{ $self->errors }, { message => "Unknown SAML key $target" }; return 0; } next; } # OIDC elsif ( $base =~ /^oidc(?:O|R)PMetaDataNodes$/ ) { hdebug('OIDC'); if ( $target =~ /^oidc(?:O|R)PMetaDataOptions$/ ) { hdebug(" $target: looking for subnodes"); $self->_scanNodes($subNodes); $self->set( $target, $key, $leaf->{title}, $leaf->{data} ); } elsif ( $target =~ /^oidcOPMetaData(?:JSON|JWKS)$/ ) { hdebug(" $target"); $self->set( $target, $key, $leaf->{data} ); } elsif ( $target =~ /^oidc(?:O|R)PMetaDataExportedVars$/ ) { hdebug(" $target"); if ( $leaf->{cnodes} ) { hdebug(' unopened'); $self->newConf->{$target}->{$key} = $self->refConf->{$target}->{$oldName} // {}; } elsif ($h) { hdebug(' opened'); $self->set( $target, $key, $leaf->{title}, $leaf->{data} ); } else { hdebug(" $target: looking for subnodes"); $self->_scanNodes($subNodes); } } elsif ( $target =~ /^oidc(?:O|R)PMetaDataOptions/ ) { my $optKey = $&; hdebug " $base sub key: $target"; if ( $target eq 'oidcRPMetaDataOptionsExtraClaims' ) { if ( $leaf->{cnodes} ) { hdebug(' unopened'); $self->newConf->{$target}->{$key} = $self->refConf->{$target}->{$oldName} // {}; } elsif ($h) { hdebug(' opened'); $self->set( $target, $key, $leaf->{title}, $leaf->{data} ); } else { hdebug(" $target: looking for subnodes"); $self->_scanNodes($subNodes); } } elsif ( $target =~ /^(?:$oidcOPMetaDataNodeKeys|$oidcRPMetaDataNodeKeys)/o ) { $self->set( $optKey, [ $oldName, $key ], $target, $leaf->{data} ); } else { push @{ $self->errors }, { message => "Unknown OIDC metadata option $target" }; return 0; } } else { push @{ $self->errors }, { message => "Unknown OIDC key $target" }; return 0; } next; } else { push @{ $self->errors }, { message => "Fatal: unknown special sub node $base" }; return 0; } } #################### # Application list # #################### # Application list root node elsif ( $leaf->{title} eq 'applicationList' ) { hdebug( $leaf->{title} ); if ( $leaf->{cnodes} ) { hdebug(' unopened'); $self->newConf->{applicationList} = $self->refConf->{applicationList} // {}; } else { $self->_scanNodes($subNodes) or return 0; # Check for deleted my @listCatRef = map { $self->refConf->{applicationList}->{$_}->{catname} } keys %{ $self->refConf->{applicationList} }; my @listCatNew = map { $self->newConf->{applicationList}->{$_}->{catname} } keys( %{ ref $self->newConf->{applicationList} ? $self->newConf->{applicationList} : {} } ); foreach my $cat (@listCatNew) { @listCatRef = grep { $_ ne $cat } @listCatRef; } if (@listCatRef) { $self->confChanged(1); foreach my $cat (@listCatRef) { push @{ $self->changes }, { key => $leaf->{id}, old => $cat }; } } } next; } # Application list sub nodes elsif ( $leaf->{id} =~ /^applicationList\/(.+)$/ ) { hdebug('Application list subnode'); use feature 'state'; my @cats = split /\//, $1; my $app = pop @cats; $self->newConf->{applicationList} //= {}; # $cn is a pointer to the parent my $cn = $self->newConf->{applicationList}; my $cmp = $self->refConf->{applicationList}; my @path; # Makes $cn point to the parent foreach my $cat (@cats) { hdebug(" looking to cat $cat"); unless ( defined $knownCat->{$cat} ) { push @{ $self->{errors} }, { message => "Fatal: sub cat/app before parent ($leaf->{id})" }; return 0; } $cn = $cn->{ $knownCat->{$cat} }; push @path, $cn->{catname}; $cmp->{$cat} //= {}; $cmp = $cmp->{$cat}; } # Create new category # # Note that this works because nodes are ordered so "cat/cat2/app" # is looked after "cat" and "cat/cat2" if ( $leaf->{type} eq 'menuCat' ) { hdebug(' menu cat'); $knownCat->{__id}++; my $s = $knownCat->{$app} = sprintf '%04d-cat', $knownCat->{__id}; $cn->{$s} = { catname => $leaf->{title}, type => 'category' }; unless ($cmp->{$app} and $cmp->{$app}->{catname} eq $cn->{$s}->{catname} ) { $self->confChanged(1); push @{ $self->changes }, { key => join( ', ', 'applicationList', @path, $leaf->{title} ), new => $cn->{$s}->{catname}, old => ( $cn->{$s} ? $cn->{$s}->{catname} : undef ) }; } if ( ref $subNodes ) { $self->_scanNodes($subNodes) or return 0; } my @listCatRef = keys %{ $cmp->{$app} }; my @listCatNew = keys %{ $cn->{$s} }; # Check for deleted unless ( @listCatRef == @listCatNew ) { $self->confChanged(1); push @{ $self->changes }, { key => join( ', ', 'applicationList', @path ), new => 'Changes in cat(s)/app(s)', }; } } # Create new apps else { hdebug(' new app'); $knownCat->{__id}++; my $name = sprintf( '%04d-app', $knownCat->{__id} ); $cn->{$name} = { type => 'application', options => $leaf->{data} }; $cn->{$name}->{options}->{name} = $leaf->{title}; unless ( $cmp->{$app} ) { $self->confChanged(1); push @{ $self->changes }, { key => join( ', ', 'applicationList', @path ), new => $leaf->{title}, }; } else { foreach my $k ( keys %{ $cn->{$name}->{options} } ) { unless ( $cmp->{$app}->{options}->{$k} eq $cn->{$name}->{options}->{$k} ) { $self->confChanged(1); push @{ $self->changes }, { key => join( ', ', 'applicationList', @path, $leaf->{title}, $k ), new => $cn->{$name}->{options}->{$k}, old => $cmp->{$app}->{options}->{$k} }; } } } } next; } elsif ( $leaf->{id} eq 'grantSessionRules' ) { hdebug('grantSessionRules'); if ( $leaf->{cnodes} ) { hdebug(' unopened'); $self->newConf->{$name} = $self->refConf->{$name} // {}; } else { hdebug(' opened'); $subNodes //= []; my $count = 0; my $ref = $self->refConf->{grantSessionRules}; my $new = $self->newConf->{grantSessionRules}; my @old = ref $ref ? keys %$ref : (); $self->newConf->{grantSessionRules} = {}; foreach my $n (@$subNodes) { hdebug(" looking at $n subnode"); my $k = $n->{re} . ( $n->{comment} ? "##$n->{comment}" : '' ); $self->newConf->{grantSessionRules}->{$k} = $n->{data}; $count++; unless ( defined $ref->{$k} ) { $self->confChanged(1); push @{ $self->changes }, { keys => 'grantSessionRules', new => $k }; } elsif ( $ref->{$k} ne $n->{data} ) { $self->confChanged(1); push @{ $self->changes }, { key => "grantSessionRules, $k", old => $self->refConf->{grantSessionRules}->{$k}, new => $n->{data} }; } @old = grep { $_ ne $k } @old; } if (@old) { $self->confChanged(1); push @{ $self->changes }, { key => 'grantSessionRules', old => $_, } foreach (@old); } } next; } # openIdIDPList: data is splitted by Conf.pm into a boolean and a # string elsif ( $name eq 'openIdIDPList' ) { hdebug('openIdIDPList'); if ( $leaf->{data} ) { unless ( ref $leaf->{data} eq 'ARRAY' ) { push @{ $self->{errors} }, { message => 'Malformed openIdIDPList ' . $leaf->{data} }; return 0; } $self->set( $name, join( ';', @{ $leaf->{data} } ) ); } else { $self->set( $name, undef ); } next; } #################### # Other hash nodes # #################### elsif ( $leaf->{title} =~ /^$simpleHashKeys$/o and not $leaf->{title} eq 'applicationList' ) { hdebug( $leaf->{title} ); # If a `cnodes` key is found, keep old key unchanges if ( $leaf->{cnodes} ) { hdebug(' unopened'); $self->newConf->{$name} = $self->refConf->{$name} // {}; } else { hdebug(' opened'); $subNodes //= []; my $count = 0; my @old = ( ref( $self->refConf->{$name} ) ? ( keys %{ $self->refConf->{$name} } ) : () ); $self->newConf->{$name} = {}; foreach my $n (@$subNodes) { hdebug(" looking at $n subnode"); if ( ref $n->{data} and ref $n->{data} eq 'ARRAY' ) { $n->{data} = join ';', @{ $n->{data} }; } $self->newConf->{$name}->{ $n->{title} } = $n->{data}; $count++; unless ( defined $self->refConf->{$name}->{ $n->{title} } ) { $self->confChanged(1); push @{ $self->changes }, { key => $name, new => $n->{title}, }; } elsif ( $self->refConf->{$name}->{ $n->{title} } ne $n->{data} ) { $self->confChanged(1); push @{ $self->changes }, { key => "$name, $n->{title}", old => $self->refConf->{$name}->{ $n->{title} }, new => $n->{data} }; } @old = grep { $_ ne $n->{title} } @old; } if (@old) { $self->confChanged(1); push @{ $self->changes }, { key => $name, old => $_, } foreach (@old); } } next; } # Double hash nodes elsif ( $leaf->{title} =~ /^$doubleHashKeys$/ ) { hdebug( $leaf->{title} ); my @oldHosts = ( ref( $self->refConf->{$name} ) ? ( keys %{ $self->refConf->{$name} } ) : () ); $self->newConf->{$name} = {}; unless ( defined $leaf->{data} ) { hdebug(' unopened'); $self->newConf->{$name} = $self->refConf->{$name} || {}; next; } foreach my $getHost ( @{ $leaf->{data} } ) { my $change = 0; my @oldKeys; my $host = $getHost->{k}; hdebug(" looking at host: $host"); $self->newConf->{$name}->{$host} = {}; unless ( defined $self->refConf->{$name}->{$host} ) { $self->confChanged(1); $change++; push @{ $self->changes }, { key => $name, new => $host }; hdebug(" $host is new"); } else { @oldHosts = grep { $_ ne $host } @oldHosts; @oldKeys = keys %{ $self->refConf->{$name}->{$host} }; } foreach my $prm ( @{ $getHost->{h} } ) { $self->newConf->{$name}->{$host}->{ $prm->{k} } = $prm->{v}; if ( !$change and ( not defined( $self->refConf->{$name}->{$host}->{ $prm->{k} } ) or $self->newConf->{$name}->{$host}->{ $prm->{k} } ne $self->refConf->{$name}->{$host}->{ $prm->{k} } ) ) { $self->confChanged(1); hdebug(" key $prm->{k} has been changed"); push @{ $self->changes }, { key => "$name/$host", new => $prm->{k} }; } elsif ( !$change ) { @oldKeys = grep { $_ ne $prm->{k} } @oldKeys; } } if (@oldKeys) { $self->confChanged(1); hdebug( " old keys: " . join( ' ', @oldKeys ) ); push @{ $self->changes }, { key => "$name/$host", old => $_ } foreach (@oldKeys); } } if (@oldHosts) { $self->confChanged(1); hdebug( " old hosts " . join( ' ', @oldHosts ) ); push @{ $self->changes }, { key => "$name", old => $_ } foreach (@oldHosts); } next; } ############### # Other nodes # ############### # Check if subnodes my $n = 0; if ( ref $subNodesCond ) { hdebug(' conditional subnodes detected'); # Bad idea,subnode unopened are not read #$subNodesCond = [ grep { $_->{show} } @$subNodesCond ]; $self->_scanNodes($subNodesCond) or return 0; $n++; } if ( ref $subNodes ) { hdebug(' subnodes detected'); $self->_scanNodes($subNodes) or return 0; $n++; } if ($n) { next; } if ( defined $leaf->{data} and ref( $leaf->{data} ) eq 'ARRAY' ) { if ( ref( $leaf->{data}->[0] ) eq 'HASH' ) { hdebug(" array found"); $self->_scanNodes( $leaf->{data} ) or return 0; } else { $self->set( $name, join( ';', @{ $leaf->{data} } ) ); } } # Grouped nodes not opened elsif ( $leaf->{get} and ref $leaf->{get} eq 'ARRAY' ) { hdebug(" unopened grouped node"); foreach my $subkey ( @{ $leaf->{get} } ) { $self->set( $subkey, undef ); } } # Normal leaf else { $self->set( $name, $leaf->{data} ); } } return 1; } ##@method private void set($target, @path, $data) # Store a value in the $target key (following subkeys if @path is set) sub set { my $self = shift; my $data = pop; my @confs = ( $self->refConf, $self->newConf ); my @path; while ( @_ > 1 ) { my $tmp = shift; push @path, $tmp; foreach my $i ( 0, 1 ) { my $v = ref($tmp) ? $tmp->[$i] : $tmp; $confs[$i]->{$v} //= {}; $confs[$i] = $confs[$i]->{$v}; } } my $target = shift; hdebug( "# set() called:", { data => $data, path => \@path, target => $target } ); die @path unless ($target); # Check new value if ( defined $data ) { hdebug(" data defined"); # TODO: remove if $data == default value $confs[1]->{$target} = $data; eval { unless ( $target eq 'cfgLog' or ( defined $confs[0]->{$target} and $confs[0]->{$target} eq $data ) or ( !defined $confs[0]->{$target} and defined $self->defaultValue($target) and $data eq $self->defaultValue($target) ) ) { $self->confChanged(1); push @{ $self->changes }, { key => join( ', ', @path, $target ), old => $confs[0]->{$target} // $self->defaultValue($target), new => $confs[1]->{$target} }; } }; } # Set old value if exists else { hdebug(" data undefined"); if ( exists $confs[0]->{$target} ) { hdebug(" old value exists"); $confs[1]->{$target} = $confs[0]->{$target}; } else { hdebug(" no old value, skipping"); } } } sub defaultValue { my ( $self, $target ) = @_; hdebug("# defaultValue($target)"); die unless ($target); my $res = eval { &Lemonldap::NG::Manager::Attributes::attributes()->{$target} ->{'default'}; }; return $res; } ##@method boolean testNewConf() # Launch _unitTest() and _globaTest() # #@return true if tests succeed sub testNewConf { my $self = shift; hdebug('# testNewConf()'); return $self->_unitTest( $self->newConf(), '' ) && $self->_globalTest(); } ##@method private boolean _unitTest() # Launch unit tests declared in Lemonldap::NG::Manager::Build::Attributes file # #@return true if tests succeed sub _unitTest { my ( $self, $conf ) = @_; hdebug('# _unitTest()'); my $types = &Lemonldap::NG::Manager::Attributes::types(); my $attrs = &Lemonldap::NG::Manager::Attributes::attributes(); my $res = 1; foreach my $key ( keys %$conf ) { hdebug("Testing $key"); my $attr = $attrs->{$key}; my $type = $types->{ $attr->{type} }; unless ( $type or $attr->{test} ) { print STDERR "Unknown attribute $key, deleting it\n"; delete $conf->{$key}; next; } if ( $attr->{type} and $attr->{type} eq 'subContainer' ) { # TODO Recursive for SAML/OIDC nodes } else { # Check if key exists unless ($attr) { push @{ $self->errors }, { message => "__unknownKey__: $key" }; $res = 0; next; } # Hash parameters if ( $key =~ /^$simpleHashKeys$/o ) { $conf->{$key} //= {}; unless ( ref $conf->{$key} eq 'HASH' ) { push @{ $self->errors }, { message => "$key is not a hash ref" }; $res = 0; next; } } elsif ( $attr->{type} =~ /Container$/ ) { #TODO } if ( $key =~ /^(?:$simpleHashKeys|$doubleHashKeys)$/o or $attr->{type} =~ /Container$/ ) { my $keyMsg = $attr->{keyMsgFail} // $type->{keyMsgFail}; my $msg = $attr->{msgFail} // $type->{msgFail}; $res = 0 unless ( $self->_execTest( { keyTest => $attr->{keyTest} // $type->{keyTest}, keyMsgFail => $attr->{keyMsgFail} // $type->{keyMsgFail}, test => $attr->{test} // $type->{test}, msgFail => $attr->{msgFail} // $type->{msgFail}, }, $conf->{$key}, $key, $attr, undef, $conf ) ); } elsif ( defined $attr->{keyTest} ) { #TODO } else { my $msg = $attr->{msgFail} // $type->{msgFail}; $res = 0 unless ( $self->_execTest( $attr->{test} // $type->{test}, $conf->{$key}, $key, $attr, $msg, $conf ) ); } } } return $res; } ##@method private boolean _execTest($test, $value) # Execute the given test with value #@param test that can be a code-ref, or a regexp #@return result of test sub _execTest { my ( $self, $test, $value, $key, $attr, $msg, $conf ) = @_; my $ref; die "Malformed test for $key: only regexp ref or sub are accepted (type \"$ref\")" unless ( $ref = ref($test) and $ref =~ /^(CODE|Regexp|HASH)$/ ); if ( $ref eq 'CODE' ) { my ( $r, $m ) = ( $test->( $value, $conf, $attr ) ); if ($m) { push @{ $self->{ ( $r ? 'warnings' : 'errors' ) } }, { message => "$key: $m" }; } elsif ( !$r ) { push @{ $self->{errors} }, { message => "$key: $msg" }; } return $r; } elsif ( $ref eq 'Regexp' ) { my $r = $value =~ $test; push @{ $self->errors }, { message => "$key: $msg" } unless ($r); return $r; } # Recursive test (for locationRules,...) else { my $res = 1; return $res unless ( ref($value) eq 'HASH' ); foreach my $k ( keys %$value ) { $res = 0 unless ( $self->_execTest( $test->{keyTest}, $k, "$key/$k", $attr, $test->{keyMsgFail}, $conf ) and $self->_execTest( $test->{test}, $value->{$k}, "$key/$k", $attr, $test->{msgFail}, $conf ) ); } return $res; } } ##@method private boolean _globalTest() # Launch all tests declared in Lemonldap::NG::Manager::Conf::Tests::tests() # #@return true if tests succeed sub _globalTest { my $self = shift; require Lemonldap::NG::Manager::Conf::Tests; hdebug('# _globalTest()'); my $result = 1; my $tests = &Lemonldap::NG::Manager::Conf::Tests::tests( $self->newConf ); foreach my $name ( keys %$tests ) { my $sub = $tests->{$name}; my ( $res, $msg ); eval { ( $res, $msg ) = $sub->(); if ( $res == -1 ) { push @{ $self->needConfirm }, { message => $msg }; } elsif ($res) { if ($msg) { push @{ $self->warnings }, { message => $msg }; } } else { $result = 0; push @{ $self->errors }, { message => $msg }; } }; if ($@) { push @{ $self->warnings }, "Test $name failed: $@"; print STDERR "Test $name failed: $@\n"; } } return $result; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager::Conf::Parser - Perl extension for parsing new uploaded configurations. =head1 SYNOPSIS require Lemonldap::NG::Manager::Conf::Parser; my $parser = Lemonldap::NG::Manager::Conf::Parser->new( { tree => $new, refConf => $self->currentConf } ); my $res = { result => $parser->check }; $res->{message} = $parser->{message}; foreach my $t (qw(errors warnings changes)) { push @{ $res->{details} }, { message => $t, items => $parser->$t } if ( @{$parser->$t} ); } =head1 DESCRIPTION Lemonldap::NG::Manager::Conf::Parser checks new configuration This package is used by Manager to examine uploaded configuration. It is currently called using check() which return a boolean. check() looks if a newConf is available. If not, it builds it from uploaded JSON (using scanTree() subroutine) Messages are stored in errors(), warnings() and changes() as arrays. This interface uses L to be compatible with CGI, FastCGI,... =head1 SEE ALSO L, L =head1 AUTHORS =over =item Xavier Guimard, Ex.guimard@free.frE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Tests.pm000066400000000000000000000262521325274564300347650ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Confpackage Lemonldap::NG::Manager::Conf::Tests; use utf8; use Lemonldap::NG::Common::Regexp; use Lemonldap::NG::Handler::SharedConf; our $VERSION = '1.9.10'; ## @method hashref tests(hashref conf) # Return a hash ref where keys are the names of the tests and values # subroutines to execute. # # Subroutines can return one of the followings : # - (1) : everything is OK # - (1,message) : OK with a warning # - (0,message) : NOK # - (-1,message) : OK, but must be confirmed (ignored if confirm parameter is # set # # Those subroutines can also modify configuration. # # @param $conf Configuration to test # @return hash ref where keys are the names of the tests and values sub tests { my $conf = shift; return { # 1. CHECKS # Check if portal is in domain portalIsInDomain => sub { return ( 1, ( index( $conf->{portal}, $conf->{domain} ) > 0 ? '' : "Portal seems not to be in the domain $conf->{domain}" ) ); }, # Check if virtual hosts are in the domain vhostInDomainOrCDA => sub { return 1 if ( $conf->{cda} ); my @pb; foreach my $vh ( keys %{ $conf->{locationRules} } ) { push @pb, $vh unless ( index( $vh, $conf->{domain} ) >= 0 ); } return ( 1, ( @pb ? 'Virtual hosts ' . join( ', ', @pb ) . " are not in $conf->{domain} and cross-domain-authentication is not set" : undef ) ); }, # Check if virtual host do not contain a port vhostWithPort => sub { my @pb; foreach my $vh ( keys %{ $conf->{locationRules} } ) { push @pb, $vh if ( $vh =~ /:/ ); } if (@pb) { return ( 0, 'Virtual hosts ' . join( ', ', @pb ) . " contain a port, this is not allowed" ); } else { return 1; } }, # Force vhost to be lowercase vhostUpperCase => sub { my @pb; foreach my $vh ( keys %{ $conf->{locationRules} } ) { push @pb, $vh if ( $vh ne lc $vh ); } if (@pb) { return ( 0, 'Virtual hosts ' . join( ', ', @pb ) . " must be in lower case" ); } else { return 1; } }, # Check if "userDB" and "authentication" are consistent authAndUserDBConsistency => sub { foreach my $type (qw(Facebook Google OpenID OpenIDConnect SAML WebID)) { return ( 0, "\"$type\" can not be used as user database without using \"$type\" for authentication" ) if ( $conf->{userDB} =~ /$type/ and $conf->{authentication} !~ /$type/ ); } return 1; }, # Check that OpenID macros exists checkAttrAndMacros => sub { my @tmp; foreach my $k ( keys %$conf ) { if ( $k =~ /^(?:openIdSreg_(?:(?:(?:full|nick)nam|languag|postcod|timezon)e|country|gender|email|dob)|whatToTrace)$/ ) { my $v = $conf->{$k}; $v =~ s/^$//; next if ( $v =~ /^_/ ); push @tmp, $k unless ( defined( $conf->{exportedVars}->{$v} or defined( $conf->{macros}->{$v} ) ) ); } } return ( 1, ( @tmp ? 'Values of parameter(s) "' . join( ', ', @tmp ) . '" are not defined in exported attributes or macros' : '' ) ); }, # Test that variables are exported if Google is used as UserDB checkUserDBGoogleAXParams => sub { my @tmp; if ( $conf->{userDB} =~ /^Google$/ ) { foreach my $k ( keys %{ $conf->{exportedVars} } ) { my $v = $conf->{exportedVars}->{$k}; if ( $v !~ Lemonldap::NG::Common::Regexp::GOOGLEAXATTR() ) { push @tmp, $v; } } } return ( 1, ( @tmp ? 'Values of parameter(s) "' . join( ', ', @tmp ) . '" are not exported by Google' : '' ) ); }, # Test that variables are exported if OpenID is used as UserDB checkUserDBOpenIDParams => sub { my @tmp; if ( $conf->{userDB} =~ /^OpenID$/ ) { foreach my $k ( keys %{ $conf->{exportedVars} } ) { my $v = $conf->{exportedVars}->{$k}; if ( $v !~ Lemonldap::NG::Common::Regexp::OPENIDSREGATTR() ) { push @tmp, $v; } } } return ( 1, ( @tmp ? 'Values of parameter(s) "' . join( ', ', @tmp ) . '" are not exported by OpenID SREG' : '' ) ); }, # Try to use Apache::Session module testApacheSession => sub { my ( $id, %h ); my $gc = $Lemonldap::NG::Handler::SharedConf::tsv->{sessionStorageModule}; return 1 if ( ( $gc and $gc eq $conf->{globalStorage} ) or $conf->{globalStorage} eq 'Lemonldap::NG::Common::Apache::Session::SOAP' ); eval "use $conf->{globalStorage}"; return ( -1, "Unknown package $conf->{globalStorage}" ) if ($@); eval { tie %h, 'Lemonldap::NG::Common::Apache::Session', undef, { %{ $conf->{globalStorageOptions} }, backend => $conf->{globalStorage} }; }; return ( -1, "Unable to create a session ($@)" ) if ( $@ or not tied(%h) ); eval { $h{a} = 1; $id = $h{_session_id} or return ( -1, 'No _session_id' ); untie(%h); tie %h, 'Lemonldap::NG::Common::Apache::Session', $id, { %{ $conf->{globalStorageOptions} }, backend => $conf->{globalStorage} }; }; return ( -1, "Unable to insert datas ($@)" ) if ($@); return ( -1, "Unable to recover data stored" ) unless ( $h{a} == 1 ); eval { tied(%h)->delete; }; return ( -1, "Unable to delete session ($@)" ) if ($@); return ( -1, 'All sessions may be lost and you must restart all your Apache servers' ) if ( $conf->{globalStorage} ne $gc ); return 1; }, # Warn if cookie name has changed cookieNameChanged => sub { my $cn = $Lemonldap::NG::Handler::SharedConf::tsv->{cookieName}; return ( 1, ( $cn and $cn ne $conf->{cookieName} ? 'Cookie name has changed, you must restart all your Apache servers' : () ) ); }, # Warn if manager seems to be unprotected managerProtection => sub { return ( 1, ( $conf->{cfgAuthor} eq 'anonymous' ? 'Your manager seems to be unprotected' : '' ) ); }, # Test SMTP connection and authentication smtpConnectionAuthentication => sub { # Skip test if no SMTP configuration return 1 unless ( $conf->{SMTPServer} ); # Use SMTP eval "use Net::SMTP"; return ( 1, "Net::SMTP module is required to use SMTP server" ) if ($@); # Create SMTP object my $smtp = Net::SMTP->new( $conf->{SMTPServer} ); return ( 1, "SMTP connection to " . $conf->{SMTPServer} . " failed" ) unless ($smtp); # Skip other tests if no authentication return 1 unless ( $conf->{SMTPAuthUser} and $conf->{SMTPAuthPass} ); # Try authentication return ( 1, "SMTP authentication failed" ) unless $smtp->auth( $conf->{SMTPAuthUser}, $conf->{SMTPAuthPass} ); # Return return 1; }, # SAML entity ID must be unique samlIDPEntityIdUniqueness => sub { return 1 unless ( $conf->{samlIDPMetaDataXML} and %{ $conf->{samlIDPMetaDataXML} } ); my @msg; my $res = 1; my %entityIds; foreach my $idpId ( keys %{ $conf->{samlIDPMetaDataXML} } ) { unless ( $conf->{samlIDPMetaDataXML}->{$idpId}->{samlIDPMetaDataXML} =~ /entityID=(['"])(.+?)\1/si ) { push @msg, "$idpId SAML metadata has no EntityID"; $res = 0; next; } my $eid = $2; if ( defined $entityIds{$eid} ) { push @msg, "$idpId and $entityIds{$eid} have the same SAML EntityID"; $res = 0; next; } $entityIds{$eid} = $idpId; } return ( $res, join( ', ', @msg ) ); }, samlSPEntityIdUniqueness => sub { return 1 unless ( $conf->{samlSPMetaDataXML} and %{ $conf->{samlSPMetaDataXML} } ); my @msg; my $res = 1; my %entityIds; foreach my $spId ( keys %{ $conf->{samlSPMetaDataXML} } ) { unless ( $conf->{samlSPMetaDataXML}->{$spId}->{samlSPMetaDataXML} =~ /entityID=(['"])(.+?)\1/si ) { push @msg, "$spId SAML metadata has no EntityID"; $res = 0; next; } my $eid = $2; if ( defined $entityIds{$eid} ) { push @msg, "$spId and $entityIds{$eid} have the same SAML EntityID"; $res = 0; next; } $entityIds{$eid} = $spId; } return ( $res, join( ', ', @msg ) ); }, }; } 1; Zero.pm000066400000000000000000000165251325274564300346040ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Confpackage Lemonldap::NG::Manager::Conf::Zero; sub zeroConf { my ( $domain, $sessionDir, $persistentSessionDir, $notificationDir ) = @_; $domain ||= 'example.com'; $sessionDir ||= '/var/lib/lemonldap-ng/sessions'; $persistentSessionDir ||= '/var/lib/lemonldap-ng/psessions'; $notificationDir ||= '/var/lib/lemonldap-ng/notifications'; return { 'timeout' => 72000, 'loginHistoryEnabled' => 1, 'userDB' => 'Demo', 'applicationList' => { '2administration' => { 'manager' => { 'options' => { 'logo' => 'configure.png', 'name' => 'WebSSO Manager', 'display' => 'auto', 'uri' => "http://manager.$domain/manager.html", 'description' => 'Configure LemonLDAP::NG WebSSO' }, 'type' => 'application' }, 'sessions' => { 'type' => 'application', 'options' => { 'display' => 'auto', 'uri' => "http://manager.$domain/sessions.html", 'description' => 'Explore WebSSO sessions', 'logo' => 'database.png', 'name' => 'Sessions explorer' } }, 'catname' => 'Administration', 'notifications' => { 'type' => 'application', 'options' => { 'description' => 'Explore WebSSO notifications', 'uri' => "http://manager.$domain/notifications.html", 'display' => 'auto', 'logo' => 'database.png', 'name' => 'Notifications explorer' } }, 'type' => 'category' }, '3documentation' => { 'type' => 'category', 'localdoc' => { 'options' => { 'description' => 'Documentation supplied with LemonLDAP::NG', 'display' => 'on', 'uri' => "http://manager.$domain/doc/", 'name' => 'Local documentation', 'logo' => 'help.png' }, 'type' => 'application' }, 'officialwebsite' => { 'options' => { 'description' => 'Official LemonLDAP::NG Website', 'uri' => 'http://lemonldap-ng.org/', 'display' => 'on', 'logo' => 'network.png', 'name' => 'Offical Website' }, 'type' => 'application' }, 'catname' => 'Documentation' }, '1sample' => { 'test1' => { 'options' => { 'description' => 'A simple application displaying authenticated user', 'uri' => "http://test1.$domain/", 'display' => 'auto', 'logo' => 'demo.png', 'name' => 'Application Test 1' }, 'type' => 'application' }, 'catname' => 'Sample applications', 'type' => 'category', 'test2' => { 'type' => 'application', 'options' => { 'description' => 'The same simple application displaying authenticated user', 'uri' => "http://test2.$domain/", 'display' => 'auto', 'name' => 'Application Test 2', 'logo' => 'thumbnail.png' } } } }, 'cfgNum' => 0, 'globalStorageOptions' => { 'Directory' => $sessionDir, 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', 'LockDirectory' => "$sessionDir/lock" }, 'macros' => { '_whatToTrace' => '$_auth eq \'SAML\' ? "$_user\\@$_idpConfKey" : "$_user"' }, 'notificationStorageOptions' => { 'dirName' => $notificationDir }, 'authentication' => 'Demo', 'demoExportedVars' => { 'mail' => 'mail', 'cn' => 'cn', 'uid' => 'uid' }, 'domain' => $domain, 'globalStorage' => 'Apache::Session::File', 'passwordDB' => 'Demo', 'persistentStorage' => 'Apache::Session::File', 'persistentStorageOptions' => { 'Directory' => $persistentSessionDir, 'LockDirectory' => "$persistentSessionDir/lock" }, 'reloadUrls' => { "reload.$domain" => "http://reload.$domain/reload" }, 'sessionDataToRemember' => {}, 'notification' => 1, 'groups' => {}, 'exportedHeaders' => { "test1.$domain" => { 'Auth-User' => '$uid' }, "test2.$domain" => { 'Auth-User' => '$uid' } }, 'registerDB' => 'Demo', 'registerUrl' => "http://auth.$domain/register.pl", 'portal' => "http://auth.$domain/", 'notificationStorage' => 'File', 'locationRules' => { "test1.$domain" => { 'default' => 'accept', '^/logout' => 'logout_sso' }, "test2.$domain" => { 'default' => 'accept', '^/logout' => 'logout_sso' }, "manager.$domain" => { 'default' => '$uid eq "dwho"', '(?#Configuration)^/(manager\.html|conf/)' => '$uid eq "dwho"', '(?#Sessions)/sessions' => '$uid eq "dwho" or $uid eq "rtyler"', '(?#Notifications)/notifications' => '$uid eq "dwho" or $uid eq "rtyler"', } }, 'whatToTrace' => '_whatToTrace', 'securedCookie' => 0, 'cookieName' => 'lemonldap', 'cfgAuthor' => 'The LemonLDAP::NG team', 'exportedVars' => { 'UA' => 'HTTP_USER_AGENT' }, 'portalSkin' => 'bootstrap', 'portalSkinBackground' => '1280px-Cedar_Breaks_National_Monument_partially.jpg', 'mailUrl' => "http://auth.$domain/mail.pl", 'localSessionStorage' => 'Cache::FileCache', 'localSessionStorageOptions' => { 'namespace' => 'lemonldap-ng-sessions', 'default_expires_in' => 600, 'directory_umask' => '007', 'cache_root' => '/tmp', 'cache_depth' => 3, }, }; } 1; Constants.pm000066400000000000000000000062221325274564300347450ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Manager# This file is generated by Lemonldap::NG::Manager::Build. Don't modify it by hand package Lemonldap::NG::Manager::Constants; use strict; use Exporter 'import'; use base qw(Exporter); our $VERSION = '1.9.11'; our %EXPORT_TAGS = ( 'all' => [qw($simpleHashKeys $specialNodeKeys $doubleHashKeys $oidcOPMetaDataNodeKeys $oidcRPMetaDataNodeKeys $samlIDPMetaDataNodeKeys $samlSPMetaDataNodeKeys $virtualHostKeys $specialNodeHash @sessionTypes)] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = ( @{ $EXPORT_TAGS{'all'} } ); our $specialNodeHash = { virtualHosts => [qw(exportedHeaders locationRules post vhostOptions)], samlIDPMetaDataNodes => [qw(samlIDPMetaDataXML samlIDPMetaDataExportedAttributes samlIDPMetaDataOptions)], samlSPMetaDataNodes => [qw(samlSPMetaDataXML samlSPMetaDataExportedAttributes samlSPMetaDataOptions)], oidcOPMetaDataNodes => [qw(oidcOPMetaDataJSON oidcOPMetaDataJWKS oidcOPMetaDataOptions oidcOPMetaDataExportedVars)], oidcRPMetaDataNodes => [qw(oidcRPMetaDataOptions oidcRPMetaDataExportedVars oidcRPMetaDataOptionsExtraClaims)], }; our @sessionTypes = ( 'captcha', 'remoteGlobal', 'cas', 'global', 'localSession', 'persistent', 'saml', 'oidc' ); our $doubleHashKeys = 'issuerDBGetParameters'; our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wpSslOpt)|g(?:r(?:antSessionRule|oup)|lobalStorageOption|oogleExportedVar)|ca(?:s(?:StorageOption|Attribute)|ptchaStorageOption)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|n(?:otificationStorageOption|ginxCustomHandler)|p(?:ersistentStorageOption|ortalSkinRule)|re(?:moteGlobalStorageOption|loadUrl)|CAS_proxiedService|macro)s|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember)|a(?:uthChoiceModules|pplicationList))'; our $specialNodeKeys = '(?:(?:saml(?:ID|S)|oidc[OR])PMetaDataNode|virtualHost)s'; our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|S(?:toreIDToken|cope)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))'; our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:(?:(?:PostLogout)?RedirectUri|ExtraClaim)s|I(?:DToken(?:Expiration|SignAlg)|con)|AccessTokenExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|UserIDAttr)|ExportedVars)'; our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|S(?:ignS[LS]OMessage|toreSAMLToken|[LS]OBinding)|Force(?:Authn|UTF8)|NameIDFormat)|ExportedAttributes|XML)'; our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|S(?:essionNotOnOrAfterTimeout|ignS[LS]OMessage)|(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|En(?:ableIDPInitiatedURL|cryptionMode)|ForceUTF8)|ExportedAttributes|XML)'; our $virtualHostKeys = '(?:vhost(?:(?:Aliase|Http)s|Maintenance|Port)|(?:exportedHeader|locationRule)s|post)'; 1; Lib.pm000066400000000000000000000021161325274564300334750ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Managerpackage Lemonldap::NG::Manager::Lib; use 5.10.0; use utf8; use Mouse; use Lemonldap::NG::Common::Conf; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; has '_confAcc' => ( is => 'rw', isa => 'Lemonldap::NG::Common::Conf' ); has 'configStorage' => ( is => 'rw', isa => 'HashRef', default => sub { {} } ); has 'currentConf' => ( is => 'rw', required => 1, default => sub { {} } ); has 'protection' => ( is => 'rw', isa => 'Str', default => 'manager' ); our $VERSION = '1.9.1'; ## @method Lemonldap::NG::Common::Conf confAcc() # Configuration access object # # Return _confAcc property if exists or create it. # #@return Lemonldap::NG::Common::Conf object sub confAcc { my $self = shift; return $self->_confAcc if ( $self->_confAcc ); # TODO: pass args and remove this my $d = `pwd`; chomp $d; my $tmp; unless ( $tmp = Lemonldap::NG::Common::Conf->new( $self->configStorage ) ) { die "Unable to build Lemonldap::NG::Common::Conf " . $Lemonldap::NG::Common::Conf::msg; } return $self->_confAcc($tmp); } 1; Notifications.pm000066400000000000000000000302731325274564300356050ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Managerpackage Lemonldap::NG::Manager::Notifications; use 5.10.0; use utf8; use Mouse; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::PSGI::Constants; use Lemonldap::NG::Manager::Constants; use Lemonldap::NG::Common::Notification; use feature 'state'; extends 'Lemonldap::NG::Manager::Lib'; our $VERSION = '1.9.1'; has _notifAccess => ( is => 'rw' ); ############################# # I. INITIALIZATION METHODS # ############################# use constant defaultRoute => 'notifications.html'; sub addRoutes { my $self = shift; unless ( $self->notifAccess ) { $self->addRoute( 'notifications.html', 'notEnabled', ['GET'] ); $self->addRoute( notifications => 'notEnabled', ['GET'] ); return ( $self->error ? 0 : 1 ); } $self->{multiValuesSeparator} ||= '; '; # HTML template $self->addRoute( 'notifications.html', undef, ['GET'] ) # READ ->addRoute( 'notifications' => { actives => 'activeNotifications', done => 'doneNotifications' }, ['GET'] ) # Create new notification ->addRoute( 'notifications' => { actives => 'newNotification' }, ['POST'] ) # Update a notification (mark as done) ->addRoute( notifications => { actives => { ':notificationId' => 'updateNotification' } }, ['PUT'] ) # Delete a notification ->addRoute( notifications => { done => { ':notificationId' => 'deleteDoneNotification' } }, ['DELETE'] ); } sub notifAccess { my $self = shift; return $self->_notifAccess if ( $self->_notifAccess ); # 1. Get notificationStorage or build it using globalStorage my $conf = $self->_confAcc->getConf(); unless ($conf) { $self->error($Lemonldap::NG::Common::Conf::msg); return 0; } my $args; # TODO: refresh system $self->{$_} //= $conf->{$_} foreach ( qw/portal notification notificationStorage notificationStorageOptions/); unless ( $self->{notification} ) { return 0; } # TODO: old parameters (with table) unless ( $self->{notificationStorage} ) { $self->handlerAbort( notifications => 'notificationStorage is not defined in configuration' ); return 0; } $args->{type} = $self->{notificationStorage}; foreach ( keys %{ $self->{notificationStorageOptions} } ) { $args->{$_} = $self->{notificationStorageOptions}->{$_}; } # Get the type $args->{type} =~ s/.*:://; $args->{type} =~ s/(CBDI|RDBI)/DBI/; # CDBI/RDBI are DBI # If type not File or DBI, abort unless ( $args->{type} =~ /^(File|DBI|LDAP)$/ ) { $self->handlerAbort( notifications => "Only File, DBI or LDAP supported for Notifications" ); return 0; } # Force table name $args->{p} = $self; unless ( $self->_notifAccess( Lemonldap::NG::Common::Notification->new($args) ) ) { $self->handlerAbort( notifications => $Lemonldap::NG::Common::Notification::msg ); return 0; } return $self->_notifAccess(); } ####################### # II. DISPLAY METHODS # ####################### sub notEnabled { my ( $self, $req ) = @_; return $self->sendError( $req, 'Notifications are not enabled in your configuration', 400 ); } sub activeNotifications { my ( $self, $req, $notif ) = @_; return $self->notifications( $req, $notif, 'actives' ); } sub doneNotifications { my ( $self, $req, $notif ) = @_; return $self->notifications( $req, $notif, 'done' ); } sub notifications { my ( $self, $req, $notif, $type ) = @_; my $sub = { actives => 'getAll', done => 'getDone' }->{$type} or die "Unknown type $type"; # Case 1: a notification is required return $self->notification( $req, $notif, $type ) if ($notif); # Case 2: list my $params = $req->params(); my ( $notifs, $res ); $notifs = $self->notifAccess->$sub(); # Restrict to wanted values if ( my %filters = map { /^(?:(?:group|order)By)$/ ? () : ( $_ => $params->{$_} ); } keys %$params ) { while ( my ( $field, $value ) = each %filters ) { $value =~ s/\*/\.\*/g; $value = qr/^$value$/; foreach my $k ( keys %$notifs ) { delete $notifs->{$k} unless ( $notifs->{$k}->{$field} =~ $value ); } } } if ( my $groupBy = $req->params('groupBy') ) { my ( $length, $start, $r ) = ( 0, 0 ); if ( $groupBy =~ /^substr\((\w+)(?:,(\d+)(?:,(\d+))?)?\)$/ ) { ( $groupBy, $length, $start ) = ( $1, $2, $3 ); $start ||= 0; $length = 1 if ( $length < 1 ); } foreach my $k ( keys %$notifs ) { my $s = $length ? substr( $notifs->{$k}->{$groupBy}, $start, $length ) : $notifs->{$k}->{$groupBy}; $r->{$s}++; } my $count = 0; $res = [ map { $count += $r->{$_}; { value => $_, count => $r->{$_} } } sort keys %$r ]; return $self->sendJSONresponse( $req, { result => 1, count => $count, values => $res, } ); } else { my @r = map { my $r = { notification => $_ }; foreach my $k (qw(uid date condition)) { $r->{$k} = $notifs->{$_}->{$k}; } $r->{reference} = $notifs->{$_}->{ref}; $r; } keys %$notifs; if ( my $orderBy = $req->params('orderBy') ) { my @fields = split /,/, $orderBy; while ( my $f = pop @fields ) { @r = sort { $a->{$f} cmp $b->{$f} } @r; } } return $self->sendJSONresponse( $req, { result => 1, count => scalar(@r), values => \@r } ); } } sub notification { my ( $self, $req, $id, $type ) = @_; if ( $type eq 'actives' ) { my ( $uid, $ref ) = ( $id =~ /([^_]+?)_(.+)/ ); my $n = $self->notifAccess->_get( $uid, $ref ); unless ($n) { $self->lmLog( "Notification $ref not found for user $uid", 'notice' ); return $self->sendJSONresponse( $req, { result => 0, error => "Notification $ref not found for user $uid" } ); } return $self->sendJSONresponse( $req, { result => 1, count => 1, notifications => [ values %$n ] } ); } else { return $self->sendJSONresponse( $req, { result => 1, count => 1, done => $id } ); } } sub newNotification { my ( $self, $req, @other ) = @_; return $self->sendError( $req, 'There is no subkey for "newNotification"', 200 ) if (@other); my $json = $req->jsonBodyToObj; unless ( defined($json) ) { return $self->sendError( $req, undef, 200 ); } foreach my $r (qw(uid date reference xml)) { return $self->sendError( $req, "Missing $r", 200 ) unless ( $json->{$r} ); } unless ( $json->{date} =~ /^\d{4}-\d{2}-\d{2}$/ ) { return $self->sendError( $req, "Malformed date", 200 ); } utf8::decode( $json->{xml} ); my $newNotif = qq# {$_} ) { $t =~ s/"/'/g; qq#$_="$t"# } else { () } } (qw(uid date reference condition)) ) . ">$json->{xml}"; unless ( eval { $self->notifAccess->newNotification($newNotif) } ) { $self->lmLog( "Notification not created: $@$Lemonldap::NG::Common::Notification::msg", 'error' ); return $self->sendError( $req, "Notification not created: $@$Lemonldap::NG::Common::Notification::msg", 200 ); } else { return $self->sendJSONresponse( $req, { result => 1 } ); } } sub updateNotification { my ( $self, $req ) = @_; my $json = $req->jsonBodyToObj; unless ( defined($json) ) { return $self->sendError( $req, undef, 200 ); } # For now, only "mark as done" is proposed unless ( $json->{done} ) { return $self->sendError( $req, 'Only "done=1" is accepted for now', 200 ); } my $id = $req->params('notificationId') or die; my ( $uid, $ref ) = ( $id =~ /([^_]+?)_(.+)/ ); my ( $n, $res ); unless ( $n = $self->notifAccess->_get( $uid, $ref ) ) { $self->lmLog( "Notification $ref not found for user $uid", 'notice' ); return $self->sendError( $req, "Notification $ref not found for user $uid" ); } # Delete notifications my $status = 1; foreach ( keys %$n ) { $status = 0 unless ( $self->notifAccess->_delete($_) ); } unless ($status) { $self->lmLog( "Notification $ref for user $uid not deleted", 'error' ); return $self->sendError( $req, "Notification $ref for user $uid not deleted" ); } else { $self->lmLog( "Notification $ref deleted for user $uid", 'info' ); return $self->sendJSONresponse( $req, { result => 1 } ); } } sub deleteDoneNotification { my ( $self, $req ) = @_; my $res; # Purge notification my $id = $req->params('notificationId') or die; my ( $uid, $ref, $date ) = ( $id =~ /([^_]+?)_([^_]+?)_(.+)/ ); my $identifier = $self->notifAccess->_getIdentifier( $uid, $ref, $date ); unless ( $self->notifAccess->purge($identifier) ) { $self->lmLog( "Notification $identifier not purged ($Lemonldap::NG::Common::Notification::msg)", 'warn' ); return $self->sendError( $req, "Notification $identifier not purged ($Lemonldap::NG::Common::Notification::msg)", 400 ); } $self->lmLog( "Notification $identifier purged", 'info' ); return $self->sendJSONresponse( $req, { result => 1 } ); } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager::Notifications - Notifications explorer component of L. =head1 SYNOPSIS See L. =head1 DESCRIPTION Lemonldap::NG::Manager provides a web interface to manage Lemonldap::NG Web-SSO system. The Perl part of Lemonldap::NG::Manager is the REST server. Web interface is written in Javascript, using AngularJS framework and can be found in `site` directory. The REST API is described in REST-API.md file given in source tree. Lemonldap::NG Manager::Notifications provides the notifications explorer part. =head1 ORGANIZATION Lemonldap::NG Manager::Notifications is the only one module used to explore notifications. The javascript part is in `site/static/js/notifications.js` file. =head1 SEE ALSO L, L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut Sessions.pm000066400000000000000000000343771325274564300346130ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/lib/Lemonldap/NG/Managerpackage Lemonldap::NG::Manager::Sessions; use 5.10.0; use utf8; use strict; use Mouse; use Lemonldap::NG::Common::Session; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Common::Session; use Lemonldap::NG::Common::PSGI::Constants; use Lemonldap::NG::Manager::Constants; use Lemonldap::NG::Handler::Main qw(:tsv); use feature 'state'; extends 'Lemonldap::NG::Manager::Lib'; has conf => ( is => 'rw', isa => 'HashRef', default => sub { {} } ); our $VERSION = '1.9.9'; ############################# # I. INITIALIZATION METHODS # ############################# use constant defaultRoute => 'sessions.html'; sub addRoutes { my $self = shift; # HTML template $self->addRoute( 'sessions.html', undef, ['GET'] ) # READ ->addRoute( sessions => { ':sessionType' => 'sessions' }, ['GET'] ) # DELETE ->addRoute( sessions => { ':sessionType' => { ':sessionId' => 'delSession' } }, ['DELETE'] ); #TODO: transfer this in Manager.pm ? if ( my $localConf = $self->confAcc->getLocalConf(SESSIONSEXPLORERSECTION) ) { $self->{$_} = $localConf->{$_} foreach ( keys %$localConf ); } my $conf = $self->confAcc->getConf(); # # Return unless configuration is available return 0 unless ($conf); foreach my $type (@sessionTypes) { if ( my $tmp = $self->{ $type . 'Storage' } || $conf->{ $type . 'Storage' } ) { $self->{conf}->{$type}->{module} = $tmp; $self->{conf}->{$type}->{options} = $self->{ $type . 'StorageOptions' } || $conf->{ $type . 'StorageOptions' } || {}; $self->{conf}->{$type}->{kind} = ( $type eq 'global' ? 'SSO' : ucfirst($type) ); } } $self->{ipField} ||= 'ipAddr'; $self->{multiValuesSeparator} ||= '; '; $self->{hiddenAttributes} //= "_password"; } ####################### # II. DISPLAY METHODS # ####################### sub sessions { my ( $self, $req, $session, $skey ) = @_; # Case 1: only one session is required if ($session) { return $self->session( $req, $session, $skey ); } my $mod = $self->getMod($req) or return $self->sendError( $req, undef, 400 ); my $params = $req->params(); my $type = delete $params->{sessionType}; $type = $type eq 'global' ? 'SSO' : ucfirst($type); my $res; # Case 2: list of sessions # 2.1 Get fields to require my @fields = ( '_httpSessionType', $self->{ipField}, $tsv->{whatToTrace} ); if ( my $groupBy = $params->{groupBy} ) { $groupBy =~ s/^substr\((\w+)(?:,\d+(?:,\d+)?)?\)$/$1/ or $groupBy =~ s/^net4\((\w+),\d\)$/$1/; $groupBy =~ s/^_whatToTrace$/$tsv->{whatToTrace}/o or push @fields, $groupBy; } elsif ( my $order = $params->{orderBy} ) { $order =~ s/\bnet4\((\w+)\)$/$1/; $order =~ s/\b_whatToTrace\b/$tsv->{whatToTrace}/o or push @fields, split( /, /, $order ); } else { push @fields, '_utime'; } # 2.2 Restrict query if possible: search for filters (any query arg that is # not a keyword) my $moduleOptions = $mod->{options}; $moduleOptions->{backend} = $mod->{module}; my %filters = map { my $s = $_; $s =~ s/\b_whatToTrace\b/$tsv->{whatToTrace}/o; /^(?:(?:group|order)By|doubleIp)$/ ? () : ( $s => $params->{$_} ); } keys %$params; $filters{_session_kind} = $type; push @fields, keys(%filters); { my %seen; @fields = grep { !$seen{$_}++ } @fields; } # Check if a '*' is required my $function = 'searchOn'; $function = 'searchOnExpr' if ( grep /\*/, values %filters ); # For now, only one argument can be passed to # Lemonldap::NG::Common::Apache::Session so just the first filter is # used my ($firstFilter) = sort { $a eq '_session_kind' ? 1 : $b eq '_session_kind' ? -1 : $a cmp $b } keys %filters; $res = Lemonldap::NG::Common::Apache::Session->$function( $moduleOptions, $firstFilter, $filters{$firstFilter}, @fields ); return $self->sendJSONresponse( $req, { result => 1, count => 0, total => 0, values => [] } ) unless ( $res and %$res ); delete $filters{$firstFilter}; foreach my $k ( keys %filters ) { $filters{$k} =~ s/\./\\./g; $filters{$k} =~ s/\*/\.\*/g; foreach my $session ( keys %$res ) { if ( $res->{$session}->{$k} ) { delete $res->{$session} unless ( $res->{$session}->{$k} =~ /^$filters{$k}$/ ); } } } my $total = ( keys %$res ); # 2.4 Special case doubleIp (users connected from more than 1 IP) if ( $params->{doubleIp} ) { my %r; # 2.4.1 Store user IP addresses in %r foreach my $id ( keys %$res ) { my $entry = $res->{$id}; next if ( $entry->{_httpSessionType} ); $r{ $entry->{ $tsv->{whatToTrace} } } ->{ $entry->{ $self->{ipField} } }++; } # 2.4.2 Store sessions owned by users that has more than one IP address in $r my $r; $total = 0; foreach my $k ( keys %$res ) { my @tmp = keys %{ $r{ $res->{$k}->{ $tsv->{whatToTrace} } } }; if ( @tmp > 1 ) { $total += 1; $res->{$k}->{_sessionId} = $k; push @{ $r->{ $res->{$k}->{ $tsv->{whatToTrace} } } }, $res->{$k}; } } # 2.4.3 Store these session in an array. Array elements are : # { # uid => whatToTraceFieldValue, # sessions => [ # { session => , date => <_utime> }, # { session => , date => <_utime> }, # ] # } $res = []; foreach my $uid ( sort keys %$r ) { push @$res, { value => $uid, count => scalar( @{ $r->{$uid} } ), sessions => [ map { { session => $_->{_sessionId}, date => $_->{_utime} } } @{ $r->{$uid} } ] }; } } # 2.4 Order and group by # $res will become an array ref here (except for doubleIp, already done below). # If "groupBy" is asked, elements will be like: # { uid => 'foo.bar', count => 3 } elsif ( my $group = $req->params('groupBy') ) { my $r; $group =~ s/\b_whatToTrace\b/$tsv->{whatToTrace}/o; # Substrings if ( $group =~ /^substr\((\w+)(?:,(\d+)(?:,(\d+))?)?\)$/ ) { my ( $field, $length, $start ) = ( $1, $2, $3 ); $start ||= 0; $length = 1 if ( $length < 1 ); foreach my $k ( keys %$res ) { $r->{ substr $res->{$k}->{$field}, $start, $length }++ if ( $res->{$k}->{$field} ); } $group = $field; } # Subnets elsif ( $group =~ /^net4\((\w+),(\d)\)$/ ) { my $field = $1; my $nb = $2 - 1; foreach my $k ( keys %$res ) { if ( $res->{$k}->{$field} =~ /^((((\d+)\.\d+)\.\d+)\.\d+)$/ ) { my @d = ( $4, $3, $2, $1 ); $r->{ $d[$nb] }++; } } $group = $field; } # Simple field groupBy query elsif ( $group =~ /^\w+$/ ) { eval { foreach my $k ( keys %$res ) { $r->{ $res->{$k}->{$group} }++; } }; return $self->sendError( $req, "Use of an unexistent attribute $group to group sessions", 400 ) if ($@); } else { return $self->sendError( $req, 'Syntax error in groupBy', 400 ); } # Build result $res = [ sort { $a->{value} cmp $b->{value} } map { { value => $_, count => $r->{$_} } } keys %$r ]; } # Else if "orderBy" is asked, $res elements will be like: # { uid => 'foo.bar', session => } elsif ( my $f = $req->params('orderBy') ) { my @fields = split /,/, $f; my @r = map { my $tmp = { session => $_ }; foreach my $f (@fields) { my $s = $f; $s =~ s/^net4\((\w+)\)$/$1/; $tmp->{$s} = $res->{$_}->{$s}; } $tmp } keys %$res; while ( my $f = pop @fields ) { $f =~ s/^_whatToTrace$/$tsv->{whatToTrace}/o; if ( $f =~ s/^net4\((\w+)\)$/$1/ ) { @r = sort { my @a = split /\./, $a->{$f}; my @b = split /\./, $b->{$f}; my $cmp = 0; F: for ( my $i = 0 ; $i < 4 ; $i++ ) { if ( $a[$i] != $b[$i] ) { $cmp = $a[$i] <=> $b[$i]; last F; } } $cmp; } @r; } else { @r = sort { $a->{$f} cmp $b->{$f} } @r; } } $res = [@r]; } # Else, $res elements will be like: # { session => , date => } else { $res = [ sort { $a->{date} <=> $b->{date} } map { { session => $_, date => $res->{$_}->{_utime} } } keys %$res ]; } return $self->sendJSONresponse( $req, { result => 1, count => scalar(@$res), total => $total, values => $res } ); } sub delSession { my ( $self, $req ) = @_; return $self->sendJSONresponse( $req, { result => 1 } ) if ( $self->{demoMode} ); my $mod = $self->getMod($req) or return $self->sendError( $req, undef, 400 ); my $id = $req->params('sessionId') or return $self->sendError( $req, 'sessionId is missing', 400 ); my $session = $self->getApacheSession( $mod, $id ); $session->remove; if ( $session->error ) { return $self->sendError( $req, $session->error, 200 ); } return $self->sendJSONresponse( $req, { result => 1 } ); } sub session { my ( $self, $req, $id, $skey ) = @_; my ( %h, $res ); my $mod = $self->getMod($req) or return $self->sendError( $req, undef, 400 ); # Try to read session my $apacheSession = $self->getApacheSession( $mod, $id ) or return $self->sendError( $req, undef, 400 ); my %session = %{ $apacheSession->data }; foreach my $k ( keys %session ) { $session{$k} = '**********' if ( $self->{hiddenAttributes} =~ /\b$k\b/ ); $session{$k} = [ split /$self->{multiValuesSeparator}/o, $session{$k} ] if ( $session{$k} =~ /$self->{multiValuesSeparator}/o ); } if ($skey) { return $self->sendJSONresponse( $req, $session{$skey} ); } else { return $self->sendJSONresponse( $req, \%session ); } # TODO: check for utf-8 problems } sub getApacheSession { my ( $self, $mod, $id ) = @_; my $apacheSession = Lemonldap::NG::Common::Session->new( { storageModule => $mod->{module}, storageModuleOptions => $mod->{options}, cacheModule => $tsv->{sessionCacheModule}, cacheModuleOptions => $tsv->{sessionCacheOptions}, id => $id, kind => $mod->{kind}, } ); if ( $apacheSession->error ) { $self->error( $apacheSession->error ); return undef; } return $apacheSession; } sub getMod { my ( $self, $req ) = @_; my ( $s, $m ); unless ( $s = $req->params('sessionType') ) { $self->error('Session type is required'); return (); } unless ( $m = $self->conf->{$s} ) { $self->error('Unknown (or unconfigured) session type'); return (); } return $m; } 1; __END__ =head1 NAME =encoding utf8 Lemonldap::NG::Manager::Sessions - Sessions explorer component of L. =head1 SYNOPSIS See L. =head1 DESCRIPTION Lemonldap::NG::Manager provides a web interface to manage Lemonldap::NG Web-SSO system. The Perl part of Lemonldap::NG::Manager is the REST server. Web interface is written in Javascript, using AngularJS framework and can be found in `site` directory. The REST API is described in REST-API.md file given in source tree. Lemonldap::NG Manager::Sessions provides the sessions explorer part. =head1 ORGANIZATION Lemonldap::NG Manager::Sessions is the only one module used to explore sessions. The javascript part is in `site/static/js/sessions.js` file. =head1 SEE ALSO L, L =head1 AUTHORS =over =item Clement Oudot, Eclem.oudot@gmail.comE =item François-Xavier Deltombe, Efxdeltombe@gmail.com.E =item Xavier Guimard, Ex.guimard@free.frE =item Thomas Chemineau, Ethomas.chemineau@gmail.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2015-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2015-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library 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 2, 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 L. =cut lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/scripts/000077500000000000000000000000001325274564300276405ustar00rootroot00000000000000lmConfigEditor000066400000000000000000000057261325274564300324230ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/scripts#!/usr/bin/perl use Lemonldap::NG::Common::Conf; use Lemonldap::NG::Common::Conf::Constants; use Lemonldap::NG::Manager::Conf::Parser; use Data::Dumper; use English qw(-no_match_vars); use File::Temp; use POSIX qw(setuid setgid); use strict; eval { setgid( ( getgrnam('__APACHEGROUP__') )[2] ); setuid( ( getpwnam('__APACHEUSER__') )[2] ); print STDERR "Running as uid $EUID and gid $EGID\n"; }; my $conf = Lemonldap::NG::Common::Conf->new(); unless ($conf) { print STDERR $Lemonldap::NG::Common::Conf::msg; exit 1; } my $refConf = $conf->getConf( { raw => 1, noCache => 1 } ); delete $refConf->{reVHosts}; delete $refConf->{cipher}; delete $refConf->{cfgAuthor}; delete $refConf->{cfgAuthorIP}; delete $refConf->{cfgDate}; $refConf->{cfgLog} = ''; # Sort keys $Data::Dumper::Sortkeys = 1; my $tmp = Dumper($refConf); my $refFile = File::Temp->new( UNLINK => 1 ); my $editFile = File::Temp->new( UNLINK => 1 ); print $refFile $tmp; print $editFile $tmp; close $refFile; close $editFile; my $editor = $ENV{EDITOR} || 'editor'; system "$editor $editFile"; if (`diff $refFile $editFile`) { my $VAR1; my $buf; # Check if the new configuration hash is valid open F1, $editFile->filename(); while () { $buf .= $_; } eval $buf; die $EVAL_ERROR if $EVAL_ERROR; # Update author and date $VAR1->{cfgAuthor} = $ENV{SUDO_USER} || $ENV{LOGNAME} || "lmConfigEditor"; $VAR1->{cfgAuthorIP} = $ENV{SSH_CONNECTION} || "localhost"; $VAR1->{cfgDate} = time(); $VAR1->{cfgLog} ||= 'Edited by lmConfigEditor'; # Test new configuration my $parser = Lemonldap::NG::Manager::Conf::Parser->new( { refConf => $refConf, newConf => $VAR1, req => 1, } ); unless($parser->testNewConf) { print STDERR "Configuration seems to have some errors:\n "; print STDERR Dumper({errors => $parser->errors,warnings => $parser->warnings}); print STDERR "Are you sure you want to write it ? (yes/no) "; my $resp = ; die "Aborted" unless $resp =~ /^yes$/i; } undef $parser; # Store new configuration my $res = $conf->saveConf($VAR1); if ( $res > 0 ) { print STDERR "Configuration $res saved\n"; } else { print STDERR "Configuration was not saved:\n "; if ( $res == CONFIG_WAS_CHANGED ) { print STDERR "Configuration has changed\n"; } elsif ( $res == DATABASE_LOCKED ) { print STDERR "Configuration database is or can nor be locked\n"; } elsif ( $res == UPLOAD_DENIED ) { print STDERR "You're not authorized to save this configuration\n"; } elsif ( $res == SYNTAX_ERROR ) { print STDERR "Syntax error in your configuration\n"; } elsif ( $res == UNKNOWN_ERROR ) { print STDERR "Unknown error\n"; } } } else { print STDERR "Configuration not changed\n"; } testConfBackend.pl000077500000000000000000000044641325274564300331660ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/scripts#!/usr/bin/perl use strict; use Getopt::Long; use Lemonldap::NG::Common::Conf; use Test::More tests => 12; my %opts; my $result = GetOptions( \%opts, 'help|h', 'module|m=s', 'options|o=s' ); if ( $opts{help} or not( $opts{module} ) ) { print STDERR qq/ ## Lemonldap::NG configuration module tester ## Usage: $0 --module= --options='{ some => "parameter" }' must be the last word of Perl package: file must be named Lemonldap::NG::Common::Conf::New /; exit 1; } my $module = $opts{module}; my $args = eval $opts{options} // {}; my $currentConf; # 1 ok( $currentConf = Lemonldap::NG::Common::Conf->new( { confFile => 'test/lemonldap-ng.ini', noCache => 1, } ), 'Load test conf module' ); $Lemonldap::NG::Common::Conf::msg = ''; # 2 my $new; ok( $new = Lemonldap::NG::Common::Conf->new( { type => $module, %$args, force => 1, noCache => 1, cfgNumFixed => 1, } ), 'Load new module' ); # 3 ok( ref($new), "New conf object ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; # 4 my $cfgNum; ok( $cfgNum = $currentConf->lastCfg(), 'Test configuration available' ); # 5 my $conf; ok( $conf = $currentConf->getConf( { cfgNum => $cfgNum } ), "Get test conf ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; $conf->{cfgNum}++; my $r; # 6 eval { $new->delete( $conf->{cfgNum} ) }; ok( $r = $new->saveConf($conf), "Store conf in new module ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; # 7 ok( $r == $conf->{cfgNum}, 'Return cfgNum' ); # 8 ok( [ $new->available() ], "Some conf are available ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; # 9 ok( $r == $new->lastCfg(), "New conf is available ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; # 10 my $nc; ok( $nc = $new->getConf( { cfgNum => $r } ), "Get new conf ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; # 11 ok( $nc->{cfgNum} == $r, 'Good cfgNum in new conf' ); # 12 ok( $new->delete($r), "Delete conf ($Lemonldap::NG::Common::Conf::msg)" ); $Lemonldap::NG::Common::Conf::msg = ''; lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/000077500000000000000000000000001325274564300271155ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/000077500000000000000000000000001325274564300304045ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/000077500000000000000000000000001325274564300311765ustar00rootroot00000000000000angular-animate/000077500000000000000000000000001325274564300341645ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwrangular-animate.js000066400000000000000000004464551325274564300376110ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-animate/** * @license AngularJS v1.5.11 * (c) 2010-2017 Google, Inc. http://angularjs.org * License: MIT */ (function(window, angular) {'use strict'; var ELEMENT_NODE = 1; var COMMENT_NODE = 8; var ADD_CLASS_SUFFIX = '-add'; var REMOVE_CLASS_SUFFIX = '-remove'; var EVENT_CLASS_PREFIX = 'ng-'; var ACTIVE_CLASS_SUFFIX = '-active'; var PREPARE_CLASS_SUFFIX = '-prepare'; var NG_ANIMATE_CLASSNAME = 'ng-animate'; var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; // Detect proper transitionend/animationend event names. var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; // If unprefixed events are not supported but webkit-prefixed are, use the latter. // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. // Register both events in case `window.onanimationend` is not supported because of that, // do the same for `transitionend` as Safari is likely to exhibit similar behavior. // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit // therefore there is no reason to test anymore for other vendor prefixes: // http://caniuse.com/#search=transition if ((window.ontransitionend === undefined) && (window.onwebkittransitionend !== undefined)) { CSS_PREFIX = '-webkit-'; TRANSITION_PROP = 'WebkitTransition'; TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; } else { TRANSITION_PROP = 'transition'; TRANSITIONEND_EVENT = 'transitionend'; } if ((window.onanimationend === undefined) && (window.onwebkitanimationend !== undefined)) { CSS_PREFIX = '-webkit-'; ANIMATION_PROP = 'WebkitAnimation'; ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; } else { ANIMATION_PROP = 'animation'; ANIMATIONEND_EVENT = 'animationend'; } var DURATION_KEY = 'Duration'; var PROPERTY_KEY = 'Property'; var DELAY_KEY = 'Delay'; var TIMING_KEY = 'TimingFunction'; var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; var ANIMATION_PLAYSTATE_KEY = 'PlayState'; var SAFE_FAST_FORWARD_DURATION_VALUE = 9999; var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY; var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY; var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY; var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY; var ngMinErr = angular.$$minErr('ng'); function assertArg(arg, name, reason) { if (!arg) { throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); } return arg; } function mergeClasses(a,b) { if (!a && !b) return ''; if (!a) return b; if (!b) return a; if (isArray(a)) a = a.join(' '); if (isArray(b)) b = b.join(' '); return a + ' ' + b; } function packageStyles(options) { var styles = {}; if (options && (options.to || options.from)) { styles.to = options.to; styles.from = options.from; } return styles; } function pendClasses(classes, fix, isPrefix) { var className = ''; classes = isArray(classes) ? classes : classes && isString(classes) && classes.length ? classes.split(/\s+/) : []; forEach(classes, function(klass, i) { if (klass && klass.length > 0) { className += (i > 0) ? ' ' : ''; className += isPrefix ? fix + klass : klass + fix; } }); return className; } function removeFromArray(arr, val) { var index = arr.indexOf(val); if (val >= 0) { arr.splice(index, 1); } } function stripCommentsFromElement(element) { if (element instanceof jqLite) { switch (element.length) { case 0: return element; case 1: // there is no point of stripping anything if the element // is the only element within the jqLite wrapper. // (it's important that we retain the element instance.) if (element[0].nodeType === ELEMENT_NODE) { return element; } break; default: return jqLite(extractElementNode(element)); } } if (element.nodeType === ELEMENT_NODE) { return jqLite(element); } } function extractElementNode(element) { if (!element[0]) return element; for (var i = 0; i < element.length; i++) { var elm = element[i]; if (elm.nodeType === ELEMENT_NODE) { return elm; } } } function $$addClass($$jqLite, element, className) { forEach(element, function(elm) { $$jqLite.addClass(elm, className); }); } function $$removeClass($$jqLite, element, className) { forEach(element, function(elm) { $$jqLite.removeClass(elm, className); }); } function applyAnimationClassesFactory($$jqLite) { return function(element, options) { if (options.addClass) { $$addClass($$jqLite, element, options.addClass); options.addClass = null; } if (options.removeClass) { $$removeClass($$jqLite, element, options.removeClass); options.removeClass = null; } }; } function prepareAnimationOptions(options) { options = options || {}; if (!options.$$prepared) { var domOperation = options.domOperation || noop; options.domOperation = function() { options.$$domOperationFired = true; domOperation(); domOperation = noop; }; options.$$prepared = true; } return options; } function applyAnimationStyles(element, options) { applyAnimationFromStyles(element, options); applyAnimationToStyles(element, options); } function applyAnimationFromStyles(element, options) { if (options.from) { element.css(options.from); options.from = null; } } function applyAnimationToStyles(element, options) { if (options.to) { element.css(options.to); options.to = null; } } function mergeAnimationDetails(element, oldAnimation, newAnimation) { var target = oldAnimation.options || {}; var newOptions = newAnimation.options || {}; var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); if (newOptions.preparationClasses) { target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses); delete newOptions.preparationClasses; } // noop is basically when there is no callback; otherwise something has been set var realDomOperation = target.domOperation !== noop ? target.domOperation : null; extend(target, newOptions); // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this. if (realDomOperation) { target.domOperation = realDomOperation; } if (classes.addClass) { target.addClass = classes.addClass; } else { target.addClass = null; } if (classes.removeClass) { target.removeClass = classes.removeClass; } else { target.removeClass = null; } oldAnimation.addClass = target.addClass; oldAnimation.removeClass = target.removeClass; return target; } function resolveElementClasses(existing, toAdd, toRemove) { var ADD_CLASS = 1; var REMOVE_CLASS = -1; var flags = {}; existing = splitClassesToLookup(existing); toAdd = splitClassesToLookup(toAdd); forEach(toAdd, function(value, key) { flags[key] = ADD_CLASS; }); toRemove = splitClassesToLookup(toRemove); forEach(toRemove, function(value, key) { flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS; }); var classes = { addClass: '', removeClass: '' }; forEach(flags, function(val, klass) { var prop, allow; if (val === ADD_CLASS) { prop = 'addClass'; allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX]; } else if (val === REMOVE_CLASS) { prop = 'removeClass'; allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX]; } if (allow) { if (classes[prop].length) { classes[prop] += ' '; } classes[prop] += klass; } }); function splitClassesToLookup(classes) { if (isString(classes)) { classes = classes.split(' '); } var obj = {}; forEach(classes, function(klass) { // sometimes the split leaves empty string values // incase extra spaces were applied to the options if (klass.length) { obj[klass] = true; } }); return obj; } return classes; } function getDomNode(element) { return (element instanceof jqLite) ? element[0] : element; } function applyGeneratedPreparationClasses(element, event, options) { var classes = ''; if (event) { classes = pendClasses(event, EVENT_CLASS_PREFIX, true); } if (options.addClass) { classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX)); } if (options.removeClass) { classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX)); } if (classes.length) { options.preparationClasses = classes; element.addClass(classes); } } function clearGeneratedClasses(element, options) { if (options.preparationClasses) { element.removeClass(options.preparationClasses); options.preparationClasses = null; } if (options.activeClasses) { element.removeClass(options.activeClasses); options.activeClasses = null; } } function blockTransitions(node, duration) { // we use a negative delay value since it performs blocking // yet it doesn't kill any existing transitions running on the // same element which makes this safe for class-based animations var value = duration ? '-' + duration + 's' : ''; applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]); return [TRANSITION_DELAY_PROP, value]; } function blockKeyframeAnimations(node, applyBlock) { var value = applyBlock ? 'paused' : ''; var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; applyInlineStyle(node, [key, value]); return [key, value]; } function applyInlineStyle(node, styleTuple) { var prop = styleTuple[0]; var value = styleTuple[1]; node.style[prop] = value; } function concatWithSpace(a,b) { if (!a) return b; if (!b) return a; return a + ' ' + b; } var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { var queue, cancelFn; function scheduler(tasks) { // we make a copy since RAFScheduler mutates the state // of the passed in array variable and this would be difficult // to track down on the outside code queue = queue.concat(tasks); nextTick(); } queue = scheduler.queue = []; /* waitUntilQuiet does two things: * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through * 2. It will delay the next wave of tasks from running until the quiet `fn` has run. * * The motivation here is that animation code can request more time from the scheduler * before the next wave runs. This allows for certain DOM properties such as classes to * be resolved in time for the next animation to run. */ scheduler.waitUntilQuiet = function(fn) { if (cancelFn) cancelFn(); cancelFn = $$rAF(function() { cancelFn = null; fn(); nextTick(); }); }; return scheduler; function nextTick() { if (!queue.length) return; var items = queue.shift(); for (var i = 0; i < items.length; i++) { items[i](); } if (!cancelFn) { $$rAF(function() { if (!cancelFn) nextTick(); }); } } }]; /** * @ngdoc directive * @name ngAnimateChildren * @restrict AE * @element ANY * * @description * * ngAnimateChildren allows you to specify that children of this element should animate even if any * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` * (structural) animation, child elements that also have an active structural animation are not animated. * * Note that even if `ngAnimateChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). * * * @param {string} ngAnimateChildren If the value is empty, `true` or `on`, * then child animations are allowed. If the value is `false`, child animations are not allowed. * * @example *

    List of items:
    Item {{item}}
    .container.ng-enter, .container.ng-leave { transition: all ease 1.5s; } .container.ng-enter, .container.ng-leave-active { opacity: 0; } .container.ng-leave, .container.ng-enter-active { opacity: 1; } .item { background: firebrick; color: #FFF; margin-bottom: 10px; } .item.ng-enter, .item.ng-leave { transition: transform 1.5s ease; } .item.ng-enter { transform: translateX(50px); } .item.ng-enter-active { transform: translateX(0); } angular.module('ngAnimateChildren', ['ngAnimate']) .controller('MainController', function MainController() { this.animateChildren = false; this.enterElement = false; });
    */ var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { return { link: function(scope, element, attrs) { var val = attrs.ngAnimateChildren; if (isString(val) && val.length === 0) { //empty attribute element.data(NG_ANIMATE_CHILDREN_DATA, true); } else { // Interpolate and set the value, so that it is available to // animations that run right after compilation setData($interpolate(val)(scope)); attrs.$observe('ngAnimateChildren', setData); } function setData(value) { value = value === 'on' || value === 'true'; element.data(NG_ANIMATE_CHILDREN_DATA, value); } } }; }]; /* exported $AnimateCssProvider */ var ANIMATE_TIMER_KEY = '$$animateCss'; /** * @ngdoc service * @name $animateCss * @kind object * * @description * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or * directives to create more complex animations that can be purely driven using CSS code. * * Note that only browsers that support CSS transitions and/or keyframe animations are capable of * rendering animations triggered via `$animateCss` (bad news for IE9 and lower). * * ## Usage * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, * any automatic control over cancelling animations and/or preventing animations from being run on * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger * the CSS animation. * * The example below shows how we can create a folding animation on an element using `ng-if`: * * ```html * *
    * This element will go BOOM *
    * * ``` * * Now we create the **JavaScript animation** that will trigger the CSS transition: * * ```js * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { * return { * enter: function(element, doneFn) { * var height = element[0].offsetHeight; * return $animateCss(element, { * from: { height:'0px' }, * to: { height:height + 'px' }, * duration: 1 // one second * }); * } * } * }]); * ``` * * ## More Advanced Uses * * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code. * * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation, * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order * to provide a working animation that will run in CSS. * * The example below showcases a more advanced version of the `.fold-animation` from the example above: * * ```js * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { * return { * enter: function(element, doneFn) { * var height = element[0].offsetHeight; * return $animateCss(element, { * addClass: 'red large-text pulse-twice', * easing: 'ease-out', * from: { height:'0px' }, * to: { height:height + 'px' }, * duration: 1 // one second * }); * } * } * }]); * ``` * * Since we're adding/removing CSS classes then the CSS transition will also pick those up: * * ```css * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code, * the CSS classes below will be transitioned despite them being defined as regular CSS classes */ * .red { background:red; } * .large-text { font-size:20px; } * * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */ * .pulse-twice { * animation: 0.5s pulse linear 2; * -webkit-animation: 0.5s pulse linear 2; * } * * @keyframes pulse { * from { transform: scale(0.5); } * to { transform: scale(1.5); } * } * * @-webkit-keyframes pulse { * from { -webkit-transform: scale(0.5); } * to { -webkit-transform: scale(1.5); } * } * ``` * * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen. * * ## How the Options are handled * * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline * styles using the `from` and `to` properties. * * ```js * var animator = $animateCss(element, { * from: { background:'red' }, * to: { background:'blue' } * }); * animator.start(); * ``` * * ```css * .rotating-animation { * animation:0.5s rotate linear; * -webkit-animation:0.5s rotate linear; * } * * @keyframes rotate { * from { transform: rotate(0deg); } * to { transform: rotate(360deg); } * } * * @-webkit-keyframes rotate { * from { -webkit-transform: rotate(0deg); } * to { -webkit-transform: rotate(360deg); } * } * ``` * * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied * and spread across the transition and keyframe animation. * * ## What is returned * * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties: * * ```js * var animator = $animateCss(element, { ... }); * ``` * * Now what do the contents of our `animator` variable look like: * * ```js * { * // starts the animation * start: Function, * * // ends (aborts) the animation * end: Function * } * ``` * * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends. * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties * and that changing them will not reconfigure the parameters of the animation. * * ### runner.done() vs runner.then() * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**. * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()` * unless you really need a digest to kick off afterwards. * * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works. * * @param {DOMElement} element the element that will be animated * @param {object} options the animation-related options that will be applied during the animation * * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.) * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted. * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both). * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`). * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`). * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation. * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition. * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation. * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation. * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0` * is provided then the animation will be skipped entirely. * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same * CSS delay value. * * `stagger` - A numeric time value representing the delay between successively animated elements * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.}) * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`) * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.) * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once * the animation is closed. This is useful for when the styles are used purely for the sake of * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation). * By default this value is set to `false`. * * @return {object} an object with start and end methods and details about the animation. * * * `start` - The method to start the animation. This will return a `Promise` when called. * * `end` - This method will cancel the animation and remove all applied CSS classes and styles. */ var ONE_SECOND = 1000; var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; var CLOSING_TIME_BUFFER = 1.5; var DETECT_CSS_PROPERTIES = { transitionDuration: TRANSITION_DURATION_PROP, transitionDelay: TRANSITION_DELAY_PROP, transitionProperty: TRANSITION_PROP + PROPERTY_KEY, animationDuration: ANIMATION_DURATION_PROP, animationDelay: ANIMATION_DELAY_PROP, animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY }; var DETECT_STAGGER_CSS_PROPERTIES = { transitionDuration: TRANSITION_DURATION_PROP, transitionDelay: TRANSITION_DELAY_PROP, animationDuration: ANIMATION_DURATION_PROP, animationDelay: ANIMATION_DELAY_PROP }; function getCssKeyframeDurationStyle(duration) { return [ANIMATION_DURATION_PROP, duration + 's']; } function getCssDelayStyle(delay, isKeyframeAnimation) { var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; return [prop, delay + 's']; } function computeCssStyles($window, element, properties) { var styles = Object.create(null); var detectedStyles = $window.getComputedStyle(element) || {}; forEach(properties, function(formalStyleName, actualStyleName) { var val = detectedStyles[formalStyleName]; if (val) { var c = val.charAt(0); // only numerical-based values have a negative sign or digit as the first value if (c === '-' || c === '+' || c >= 0) { val = parseMaxTime(val); } // by setting this to null in the event that the delay is not set or is set directly as 0 // then we can still allow for negative values to be used later on and not mistake this // value for being greater than any other negative value. if (val === 0) { val = null; } styles[actualStyleName] = val; } }); return styles; } function parseMaxTime(str) { var maxValue = 0; var values = str.split(/\s*,\s*/); forEach(values, function(value) { // it's always safe to consider only second values and omit `ms` values since // getComputedStyle will always handle the conversion for us if (value.charAt(value.length - 1) === 's') { value = value.substring(0, value.length - 1); } value = parseFloat(value) || 0; maxValue = maxValue ? Math.max(value, maxValue) : value; }); return maxValue; } function truthyTimingValue(val) { return val === 0 || val != null; } function getCssTransitionDurationStyle(duration, applyOnlyDuration) { var style = TRANSITION_PROP; var value = duration + 's'; if (applyOnlyDuration) { style += DURATION_KEY; } else { value += ' linear all'; } return [style, value]; } function createLocalCacheLookup() { var cache = Object.create(null); return { flush: function() { cache = Object.create(null); }, count: function(key) { var entry = cache[key]; return entry ? entry.total : 0; }, get: function(key) { var entry = cache[key]; return entry && entry.value; }, put: function(key, value) { if (!cache[key]) { cache[key] = { total: 1, value: value }; } else { cache[key].total++; } } }; } // we do not reassign an already present style value since // if we detect the style property value again we may be // detecting styles that were added via the `from` styles. // We make use of `isDefined` here since an empty string // or null value (which is what getPropertyValue will return // for a non-existing style) will still be marked as a valid // value for the style (a falsy value implies that the style // is to be removed at the end of the animation). If we had a simple // "OR" statement then it would not be enough to catch that. function registerRestorableStyles(backup, node, properties) { forEach(properties, function(prop) { backup[prop] = isDefined(backup[prop]) ? backup[prop] : node.style.getPropertyValue(prop); }); } var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) { var gcsLookup = createLocalCacheLookup(); var gcsStaggerLookup = createLocalCacheLookup(); this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', function($window, $$jqLite, $$AnimateRunner, $timeout, $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); var parentCounter = 0; function gcsHashFn(node, extraClasses) { var KEY = '$$ngAnimateParentKey'; var parentNode = node.parentNode; var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; } function computeCachedCssStyles(node, className, cacheKey, properties) { var timings = gcsLookup.get(cacheKey); if (!timings) { timings = computeCssStyles($window, node, properties); if (timings.animationIterationCount === 'infinite') { timings.animationIterationCount = 1; } } // we keep putting this in multiple times even though the value and the cacheKey are the same // because we're keeping an internal tally of how many duplicate animations are detected. gcsLookup.put(cacheKey, timings); return timings; } function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { var stagger; // if we have one or more existing matches of matching elements // containing the same parent + CSS styles (which is how cacheKey works) // then staggering is possible if (gcsLookup.count(cacheKey) > 0) { stagger = gcsStaggerLookup.get(cacheKey); if (!stagger) { var staggerClassName = pendClasses(className, '-stagger'); $$jqLite.addClass(node, staggerClassName); stagger = computeCssStyles($window, node, properties); // force the conversion of a null value to zero incase not set stagger.animationDuration = Math.max(stagger.animationDuration, 0); stagger.transitionDuration = Math.max(stagger.transitionDuration, 0); $$jqLite.removeClass(node, staggerClassName); gcsStaggerLookup.put(cacheKey, stagger); } } return stagger || {}; } var rafWaitQueue = []; function waitUntilQuiet(callback) { rafWaitQueue.push(callback); $$rAFScheduler.waitUntilQuiet(function() { gcsLookup.flush(); gcsStaggerLookup.flush(); // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. // PLEASE EXAMINE THE `$$forceReflow` service to understand why. var pageWidth = $$forceReflow(); // we use a for loop to ensure that if the queue is changed // during this looping then it will consider new requests for (var i = 0; i < rafWaitQueue.length; i++) { rafWaitQueue[i](pageWidth); } rafWaitQueue.length = 0; }); } function computeTimings(node, className, cacheKey) { var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); var aD = timings.animationDelay; var tD = timings.transitionDelay; timings.maxDelay = aD && tD ? Math.max(aD, tD) : (aD || tD); timings.maxDuration = Math.max( timings.animationDuration * timings.animationIterationCount, timings.transitionDuration); return timings; } return function init(element, initialOptions) { // all of the animation functions should create // a copy of the options data, however, if a // parent service has already created a copy then // we should stick to using that var options = initialOptions || {}; if (!options.$$prepared) { options = prepareAnimationOptions(copy(options)); } var restoreStyles = {}; var node = getDomNode(element); if (!node || !node.parentNode || !$$animateQueue.enabled()) { return closeAndReturnNoopAnimator(); } var temporaryStyles = []; var classes = element.attr('class'); var styles = packageStyles(options); var animationClosed; var animationPaused; var animationCompleted; var runner; var runnerHost; var maxDelay; var maxDelayTime; var maxDuration; var maxDurationTime; var startTime; var events = []; if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) { return closeAndReturnNoopAnimator(); } var method = options.event && isArray(options.event) ? options.event.join(' ') : options.event; var isStructural = method && options.structural; var structuralClassName = ''; var addRemoveClassName = ''; if (isStructural) { structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true); } else if (method) { structuralClassName = method; } if (options.addClass) { addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX); } if (options.removeClass) { if (addRemoveClassName.length) { addRemoveClassName += ' '; } addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX); } // there may be a situation where a structural animation is combined together // with CSS classes that need to resolve before the animation is computed. // However this means that there is no explicit CSS code to block the animation // from happening (by setting 0s none in the class name). If this is the case // we need to apply the classes before the first rAF so we know to continue if // there actually is a detected transition or keyframe animation if (options.applyClassesEarly && addRemoveClassName.length) { applyAnimationClasses(element, options); } var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); var fullClassName = classes + ' ' + preparationClasses; var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); var hasToStyles = styles.to && Object.keys(styles.to).length > 0; var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; // there is no way we can trigger an animation if no styles and // no classes are being applied which would then trigger a transition, // unless there a is raw keyframe value that is applied to the element. if (!containsKeyframeAnimation && !hasToStyles && !preparationClasses) { return closeAndReturnNoopAnimator(); } var cacheKey, stagger; if (options.stagger > 0) { var staggerVal = parseFloat(options.stagger); stagger = { transitionDelay: staggerVal, animationDelay: staggerVal, transitionDuration: 0, animationDuration: 0 }; } else { cacheKey = gcsHashFn(node, fullClassName); stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); } if (!options.$$skipPreparationClasses) { $$jqLite.addClass(element, preparationClasses); } var applyOnlyDuration; if (options.transitionStyle) { var transitionStyle = [TRANSITION_PROP, options.transitionStyle]; applyInlineStyle(node, transitionStyle); temporaryStyles.push(transitionStyle); } if (options.duration >= 0) { applyOnlyDuration = node.style[TRANSITION_PROP].length > 0; var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration); // we set the duration so that it will be picked up by getComputedStyle later applyInlineStyle(node, durationStyle); temporaryStyles.push(durationStyle); } if (options.keyframeStyle) { var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle]; applyInlineStyle(node, keyframeStyle); temporaryStyles.push(keyframeStyle); } var itemIndex = stagger ? options.staggerIndex >= 0 ? options.staggerIndex : gcsLookup.count(cacheKey) : 0; var isFirst = itemIndex === 0; // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY // without causing any combination of transitions to kick in. By adding a negative delay value // it forces the setup class' transition to end immediately. We later then remove the negative // transition delay to allow for the transition to naturally do it's thing. The beauty here is // that if there is no transition defined then nothing will happen and this will also allow // other transitions to be stacked on top of each other without any chopping them out. if (isFirst && !options.skipBlocking) { blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); } var timings = computeTimings(node, fullClassName, cacheKey); var relativeDelay = timings.maxDelay; maxDelay = Math.max(relativeDelay, 0); maxDuration = timings.maxDuration; var flags = {}; flags.hasTransitions = timings.transitionDuration > 0; flags.hasAnimations = timings.animationDuration > 0; flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty === 'all'; flags.applyTransitionDuration = hasToStyles && ( (flags.hasTransitions && !flags.hasTransitionAll) || (flags.hasAnimations && !flags.hasTransitions)); flags.applyAnimationDuration = options.duration && flags.hasAnimations; flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions); flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations; flags.recalculateTimingStyles = addRemoveClassName.length > 0; if (flags.applyTransitionDuration || flags.applyAnimationDuration) { maxDuration = options.duration ? parseFloat(options.duration) : maxDuration; if (flags.applyTransitionDuration) { flags.hasTransitions = true; timings.transitionDuration = maxDuration; applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0; temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration)); } if (flags.applyAnimationDuration) { flags.hasAnimations = true; timings.animationDuration = maxDuration; temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)); } } if (maxDuration === 0 && !flags.recalculateTimingStyles) { return closeAndReturnNoopAnimator(); } if (options.delay != null) { var delayStyle; if (typeof options.delay !== 'boolean') { delayStyle = parseFloat(options.delay); // number in options.delay means we have to recalculate the delay for the closing timeout maxDelay = Math.max(delayStyle, 0); } if (flags.applyTransitionDelay) { temporaryStyles.push(getCssDelayStyle(delayStyle)); } if (flags.applyAnimationDelay) { temporaryStyles.push(getCssDelayStyle(delayStyle, true)); } } // we need to recalculate the delay value since we used a pre-emptive negative // delay value and the delay value is required for the final event checking. This // property will ensure that this will happen after the RAF phase has passed. if (options.duration == null && timings.transitionDuration > 0) { flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst; } maxDelayTime = maxDelay * ONE_SECOND; maxDurationTime = maxDuration * ONE_SECOND; if (!options.skipBlocking) { flags.blockTransition = timings.transitionDuration > 0; flags.blockKeyframeAnimation = timings.animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0; } if (options.from) { if (options.cleanupStyles) { registerRestorableStyles(restoreStyles, node, Object.keys(options.from)); } applyAnimationFromStyles(element, options); } if (flags.blockTransition || flags.blockKeyframeAnimation) { applyBlocking(maxDuration); } else if (!options.skipBlocking) { blockTransitions(node, false); } // TODO(matsko): for 1.5 change this code to have an animator object for better debugging return { $$willAnimate: true, end: endFn, start: function() { if (animationClosed) return; runnerHost = { end: endFn, cancel: cancelFn, resume: null, //this will be set during the start() phase pause: null }; runner = new $$AnimateRunner(runnerHost); waitUntilQuiet(start); // we don't have access to pause/resume the animation // since it hasn't run yet. AnimateRunner will therefore // set noop functions for resume and pause and they will // later be overridden once the animation is triggered return runner; } }; function endFn() { close(); } function cancelFn() { close(true); } function close(rejected) { // if the promise has been called already then we shouldn't close // the animation again if (animationClosed || (animationCompleted && animationPaused)) return; animationClosed = true; animationPaused = false; if (!options.$$skipPreparationClasses) { $$jqLite.removeClass(element, preparationClasses); } $$jqLite.removeClass(element, activeClasses); blockKeyframeAnimations(node, false); blockTransitions(node, false); forEach(temporaryStyles, function(entry) { // There is only one way to remove inline style properties entirely from elements. // By using `removeProperty` this works, but we need to convert camel-cased CSS // styles down to hyphenated values. node.style[entry[0]] = ''; }); applyAnimationClasses(element, options); applyAnimationStyles(element, options); if (Object.keys(restoreStyles).length) { forEach(restoreStyles, function(value, prop) { if (value) { node.style.setProperty(prop, value); } else { node.style.removeProperty(prop); } }); } // the reason why we have this option is to allow a synchronous closing callback // that is fired as SOON as the animation ends (when the CSS is removed) or if // the animation never takes off at all. A good example is a leave animation since // the element must be removed just after the animation is over or else the element // will appear on screen for one animation frame causing an overbearing flicker. if (options.onDone) { options.onDone(); } if (events && events.length) { // Remove the transitionend / animationend listener(s) element.off(events.join(' '), onAnimationProgress); } //Cancel the fallback closing timeout and remove the timer data var animationTimerData = element.data(ANIMATE_TIMER_KEY); if (animationTimerData) { $timeout.cancel(animationTimerData[0].timer); element.removeData(ANIMATE_TIMER_KEY); } // if the preparation function fails then the promise is not setup if (runner) { runner.complete(!rejected); } } function applyBlocking(duration) { if (flags.blockTransition) { blockTransitions(node, duration); } if (flags.blockKeyframeAnimation) { blockKeyframeAnimations(node, !!duration); } } function closeAndReturnNoopAnimator() { runner = new $$AnimateRunner({ end: endFn, cancel: cancelFn }); // should flush the cache animation waitUntilQuiet(noop); close(); return { $$willAnimate: false, start: function() { return runner; }, end: endFn }; } function onAnimationProgress(event) { event.stopPropagation(); var ev = event.originalEvent || event; // we now always use `Date.now()` due to the recent changes with // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) var timeStamp = ev.$manualTimeStamp || Date.now(); /* Firefox (or possibly just Gecko) likes to not round values up * when a ms measurement is used for the animation */ var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); /* $manualTimeStamp is a mocked timeStamp value which is set * within browserTrigger(). This is only here so that tests can * mock animations properly. Real events fallback to event.timeStamp, * or, if they don't, then a timeStamp is automatically created for them. * We're checking to see if the timeStamp surpasses the expected delay, * but we're using elapsedTime instead of the timeStamp on the 2nd * pre-condition since animationPauseds sometimes close off early */ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { // we set this flag to ensure that if the transition is paused then, when resumed, // the animation will automatically close itself since transitions cannot be paused. animationCompleted = true; close(); } } function start() { if (animationClosed) return; if (!node.parentNode) { close(); return; } // even though we only pause keyframe animations here the pause flag // will still happen when transitions are used. Only the transition will // not be paused since that is not possible. If the animation ends when // paused then it will not complete until unpaused or cancelled. var playPause = function(playAnimation) { if (!animationCompleted) { animationPaused = !playAnimation; if (timings.animationDuration) { var value = blockKeyframeAnimations(node, animationPaused); if (animationPaused) { temporaryStyles.push(value); } else { removeFromArray(temporaryStyles, value); } } } else if (animationPaused && playAnimation) { animationPaused = false; close(); } }; // checking the stagger duration prevents an accidentally cascade of the CSS delay style // being inherited from the parent. If the transition duration is zero then we can safely // rely that the delay value is an intentional stagger delay style. var maxStagger = itemIndex > 0 && ((timings.transitionDuration && stagger.transitionDuration === 0) || (timings.animationDuration && stagger.animationDuration === 0)) && Math.max(stagger.animationDelay, stagger.transitionDelay); if (maxStagger) { $timeout(triggerAnimationStart, Math.floor(maxStagger * itemIndex * ONE_SECOND), false); } else { triggerAnimationStart(); } // this will decorate the existing promise runner with pause/resume methods runnerHost.resume = function() { playPause(true); }; runnerHost.pause = function() { playPause(false); }; function triggerAnimationStart() { // just incase a stagger animation kicks in when the animation // itself was cancelled entirely if (animationClosed) return; applyBlocking(false); forEach(temporaryStyles, function(entry) { var key = entry[0]; var value = entry[1]; node.style[key] = value; }); applyAnimationClasses(element, options); $$jqLite.addClass(element, activeClasses); if (flags.recalculateTimingStyles) { fullClassName = node.className + ' ' + preparationClasses; cacheKey = gcsHashFn(node, fullClassName); timings = computeTimings(node, fullClassName, cacheKey); relativeDelay = timings.maxDelay; maxDelay = Math.max(relativeDelay, 0); maxDuration = timings.maxDuration; if (maxDuration === 0) { close(); return; } flags.hasTransitions = timings.transitionDuration > 0; flags.hasAnimations = timings.animationDuration > 0; } if (flags.applyAnimationDelay) { relativeDelay = typeof options.delay !== 'boolean' && truthyTimingValue(options.delay) ? parseFloat(options.delay) : relativeDelay; maxDelay = Math.max(relativeDelay, 0); timings.animationDelay = relativeDelay; delayStyle = getCssDelayStyle(relativeDelay, true); temporaryStyles.push(delayStyle); node.style[delayStyle[0]] = delayStyle[1]; } maxDelayTime = maxDelay * ONE_SECOND; maxDurationTime = maxDuration * ONE_SECOND; if (options.easing) { var easeProp, easeVal = options.easing; if (flags.hasTransitions) { easeProp = TRANSITION_PROP + TIMING_KEY; temporaryStyles.push([easeProp, easeVal]); node.style[easeProp] = easeVal; } if (flags.hasAnimations) { easeProp = ANIMATION_PROP + TIMING_KEY; temporaryStyles.push([easeProp, easeVal]); node.style[easeProp] = easeVal; } } if (timings.transitionDuration) { events.push(TRANSITIONEND_EVENT); } if (timings.animationDuration) { events.push(ANIMATIONEND_EVENT); } startTime = Date.now(); var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime; var endTime = startTime + timerTime; var animationsData = element.data(ANIMATE_TIMER_KEY) || []; var setupFallbackTimer = true; if (animationsData.length) { var currentTimerData = animationsData[0]; setupFallbackTimer = endTime > currentTimerData.expectedEndTime; if (setupFallbackTimer) { $timeout.cancel(currentTimerData.timer); } else { animationsData.push(close); } } if (setupFallbackTimer) { var timer = $timeout(onAnimationExpired, timerTime, false); animationsData[0] = { timer: timer, expectedEndTime: endTime }; animationsData.push(close); element.data(ANIMATE_TIMER_KEY, animationsData); } if (events.length) { element.on(events.join(' '), onAnimationProgress); } if (options.to) { if (options.cleanupStyles) { registerRestorableStyles(restoreStyles, node, Object.keys(options.to)); } applyAnimationToStyles(element, options); } } function onAnimationExpired() { var animationsData = element.data(ANIMATE_TIMER_KEY); // this will be false in the event that the element was // removed from the DOM (via a leave animation or something // similar) if (animationsData) { for (var i = 1; i < animationsData.length; i++) { animationsData[i](); } element.removeData(ANIMATE_TIMER_KEY); } } } }; }]; }]; var $$AnimateCssDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { $$animationProvider.drivers.push('$$animateCssDriver'); var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor'; var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out'; var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in'; function isDocumentFragment(node) { return node.parentNode && node.parentNode.nodeType === 11; } this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document', function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) { // only browsers that support these properties can render animations if (!$sniffer.animations && !$sniffer.transitions) return noop; var bodyNode = $document[0].body; var rootNode = getDomNode($rootElement); var rootBodyElement = jqLite( // this is to avoid using something that exists outside of the body // we also special case the doc fragment case because our unit test code // appends the $rootElement to the body after the app has been bootstrapped isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode ); return function initDriverFn(animationDetails) { return animationDetails.from && animationDetails.to ? prepareFromToAnchorAnimation(animationDetails.from, animationDetails.to, animationDetails.classes, animationDetails.anchors) : prepareRegularAnimation(animationDetails); }; function filterCssClasses(classes) { //remove all the `ng-` stuff return classes.replace(/\bng-\S+\b/g, ''); } function getUniqueValues(a, b) { if (isString(a)) a = a.split(' '); if (isString(b)) b = b.split(' '); return a.filter(function(val) { return b.indexOf(val) === -1; }).join(' '); } function prepareAnchoredAnimation(classes, outAnchor, inAnchor) { var clone = jqLite(getDomNode(outAnchor).cloneNode(true)); var startingClasses = filterCssClasses(getClassVal(clone)); outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME); rootBodyElement.append(clone); var animatorIn, animatorOut = prepareOutAnimation(); // the user may not end up using the `out` animation and // only making use of the `in` animation or vice-versa. // In either case we should allow this and not assume the // animation is over unless both animations are not used. if (!animatorOut) { animatorIn = prepareInAnimation(); if (!animatorIn) { return end(); } } var startingAnimator = animatorOut || animatorIn; return { start: function() { var runner; var currentAnimation = startingAnimator.start(); currentAnimation.done(function() { currentAnimation = null; if (!animatorIn) { animatorIn = prepareInAnimation(); if (animatorIn) { currentAnimation = animatorIn.start(); currentAnimation.done(function() { currentAnimation = null; end(); runner.complete(); }); return currentAnimation; } } // in the event that there is no `in` animation end(); runner.complete(); }); runner = new $$AnimateRunner({ end: endFn, cancel: endFn }); return runner; function endFn() { if (currentAnimation) { currentAnimation.end(); } } } }; function calculateAnchorStyles(anchor) { var styles = {}; var coords = getDomNode(anchor).getBoundingClientRect(); // we iterate directly since safari messes up and doesn't return // all the keys for the coords object when iterated forEach(['width','height','top','left'], function(key) { var value = coords[key]; switch (key) { case 'top': value += bodyNode.scrollTop; break; case 'left': value += bodyNode.scrollLeft; break; } styles[key] = Math.floor(value) + 'px'; }); return styles; } function prepareOutAnimation() { var animator = $animateCss(clone, { addClass: NG_OUT_ANCHOR_CLASS_NAME, delay: true, from: calculateAnchorStyles(outAnchor) }); // read the comment within `prepareRegularAnimation` to understand // why this check is necessary return animator.$$willAnimate ? animator : null; } function getClassVal(element) { return element.attr('class') || ''; } function prepareInAnimation() { var endingClasses = filterCssClasses(getClassVal(inAnchor)); var toAdd = getUniqueValues(endingClasses, startingClasses); var toRemove = getUniqueValues(startingClasses, endingClasses); var animator = $animateCss(clone, { to: calculateAnchorStyles(inAnchor), addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd, removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove, delay: true }); // read the comment within `prepareRegularAnimation` to understand // why this check is necessary return animator.$$willAnimate ? animator : null; } function end() { clone.remove(); outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); } } function prepareFromToAnchorAnimation(from, to, classes, anchors) { var fromAnimation = prepareRegularAnimation(from, noop); var toAnimation = prepareRegularAnimation(to, noop); var anchorAnimations = []; forEach(anchors, function(anchor) { var outElement = anchor['out']; var inElement = anchor['in']; var animator = prepareAnchoredAnimation(classes, outElement, inElement); if (animator) { anchorAnimations.push(animator); } }); // no point in doing anything when there are no elements to animate if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return; return { start: function() { var animationRunners = []; if (fromAnimation) { animationRunners.push(fromAnimation.start()); } if (toAnimation) { animationRunners.push(toAnimation.start()); } forEach(anchorAnimations, function(animation) { animationRunners.push(animation.start()); }); var runner = new $$AnimateRunner({ end: endFn, cancel: endFn // CSS-driven animations cannot be cancelled, only ended }); $$AnimateRunner.all(animationRunners, function(status) { runner.complete(status); }); return runner; function endFn() { forEach(animationRunners, function(runner) { runner.end(); }); } } }; } function prepareRegularAnimation(animationDetails) { var element = animationDetails.element; var options = animationDetails.options || {}; if (animationDetails.structural) { options.event = animationDetails.event; options.structural = true; options.applyClassesEarly = true; // we special case the leave animation since we want to ensure that // the element is removed as soon as the animation is over. Otherwise // a flicker might appear or the element may not be removed at all if (animationDetails.event === 'leave') { options.onDone = options.domOperation; } } // We assign the preparationClasses as the actual animation event since // the internals of $animateCss will just suffix the event token values // with `-active` to trigger the animation. if (options.preparationClasses) { options.event = concatWithSpace(options.event, options.preparationClasses); } var animator = $animateCss(element, options); // the driver lookup code inside of $$animation attempts to spawn a // driver one by one until a driver returns a.$$willAnimate animator object. // $animateCss will always return an object, however, it will pass in // a flag as a hint as to whether an animation was detected or not return animator.$$willAnimate ? animator : null; } }]; }]; // TODO(matsko): use caching here to speed things up for detection // TODO(matsko): add documentation // by the time... var $$AnimateJsProvider = ['$animateProvider', /** @this */ function($animateProvider) { this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', function($injector, $$AnimateRunner, $$jqLite) { var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); // $animateJs(element, 'enter'); return function(element, event, classes, options) { var animationClosed = false; // the `classes` argument is optional and if it is not used // then the classes will be resolved from the element's className // property as well as options.addClass/options.removeClass. if (arguments.length === 3 && isObject(classes)) { options = classes; classes = null; } options = prepareAnimationOptions(options); if (!classes) { classes = element.attr('class') || ''; if (options.addClass) { classes += ' ' + options.addClass; } if (options.removeClass) { classes += ' ' + options.removeClass; } } var classesToAdd = options.addClass; var classesToRemove = options.removeClass; // the lookupAnimations function returns a series of animation objects that are // matched up with one or more of the CSS classes. These animation objects are // defined via the module.animation factory function. If nothing is detected then // we don't return anything which then makes $animation query the next driver. var animations = lookupAnimations(classes); var before, after; if (animations.length) { var afterFn, beforeFn; if (event === 'leave') { beforeFn = 'leave'; afterFn = 'afterLeave'; // TODO(matsko): get rid of this } else { beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1); afterFn = event; } if (event !== 'enter' && event !== 'move') { before = packageAnimations(element, event, options, animations, beforeFn); } after = packageAnimations(element, event, options, animations, afterFn); } // no matching animations if (!before && !after) return; function applyOptions() { options.domOperation(); applyAnimationClasses(element, options); } function close() { animationClosed = true; applyOptions(); applyAnimationStyles(element, options); } var runner; return { $$willAnimate: true, end: function() { if (runner) { runner.end(); } else { close(); runner = new $$AnimateRunner(); runner.complete(true); } return runner; }, start: function() { if (runner) { return runner; } runner = new $$AnimateRunner(); var closeActiveAnimations; var chain = []; if (before) { chain.push(function(fn) { closeActiveAnimations = before(fn); }); } if (chain.length) { chain.push(function(fn) { applyOptions(); fn(true); }); } else { applyOptions(); } if (after) { chain.push(function(fn) { closeActiveAnimations = after(fn); }); } runner.setHost({ end: function() { endAnimations(); }, cancel: function() { endAnimations(true); } }); $$AnimateRunner.chain(chain, onComplete); return runner; function onComplete(success) { close(success); runner.complete(success); } function endAnimations(cancelled) { if (!animationClosed) { (closeActiveAnimations || noop)(cancelled); onComplete(cancelled); } } } }; function executeAnimationFn(fn, element, event, options, onDone) { var args; switch (event) { case 'animate': args = [element, options.from, options.to, onDone]; break; case 'setClass': args = [element, classesToAdd, classesToRemove, onDone]; break; case 'addClass': args = [element, classesToAdd, onDone]; break; case 'removeClass': args = [element, classesToRemove, onDone]; break; default: args = [element, onDone]; break; } args.push(options); var value = fn.apply(fn, args); if (value) { if (isFunction(value.start)) { value = value.start(); } if (value instanceof $$AnimateRunner) { value.done(onDone); } else if (isFunction(value)) { // optional onEnd / onCancel callback return value; } } return noop; } function groupEventedAnimations(element, event, options, animations, fnName) { var operations = []; forEach(animations, function(ani) { var animation = ani[fnName]; if (!animation) return; // note that all of these animations will run in parallel operations.push(function() { var runner; var endProgressCb; var resolved = false; var onAnimationComplete = function(rejected) { if (!resolved) { resolved = true; (endProgressCb || noop)(rejected); runner.complete(!rejected); } }; runner = new $$AnimateRunner({ end: function() { onAnimationComplete(); }, cancel: function() { onAnimationComplete(true); } }); endProgressCb = executeAnimationFn(animation, element, event, options, function(result) { var cancelled = result === false; onAnimationComplete(cancelled); }); return runner; }); }); return operations; } function packageAnimations(element, event, options, animations, fnName) { var operations = groupEventedAnimations(element, event, options, animations, fnName); if (operations.length === 0) { var a, b; if (fnName === 'beforeSetClass') { a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); } else if (fnName === 'setClass') { a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass'); b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass'); } if (a) { operations = operations.concat(a); } if (b) { operations = operations.concat(b); } } if (operations.length === 0) return; // TODO(matsko): add documentation return function startAnimation(callback) { var runners = []; if (operations.length) { forEach(operations, function(animateFn) { runners.push(animateFn()); }); } if (runners.length) { $$AnimateRunner.all(runners, callback); } else { callback(); } return function endFn(reject) { forEach(runners, function(runner) { if (reject) { runner.cancel(); } else { runner.end(); } }); }; }; } }; function lookupAnimations(classes) { classes = isArray(classes) ? classes : classes.split(' '); var matches = [], flagMap = {}; for (var i = 0; i < classes.length; i++) { var klass = classes[i], animationFactory = $animateProvider.$$registeredAnimations[klass]; if (animationFactory && !flagMap[klass]) { matches.push($injector.get(animationFactory)); flagMap[klass] = true; } } return matches; } }]; }]; var $$AnimateJsDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { $$animationProvider.drivers.push('$$animateJsDriver'); this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { return function initDriverFn(animationDetails) { if (animationDetails.from && animationDetails.to) { var fromAnimation = prepareAnimation(animationDetails.from); var toAnimation = prepareAnimation(animationDetails.to); if (!fromAnimation && !toAnimation) return; return { start: function() { var animationRunners = []; if (fromAnimation) { animationRunners.push(fromAnimation.start()); } if (toAnimation) { animationRunners.push(toAnimation.start()); } $$AnimateRunner.all(animationRunners, done); var runner = new $$AnimateRunner({ end: endFnFactory(), cancel: endFnFactory() }); return runner; function endFnFactory() { return function() { forEach(animationRunners, function(runner) { // at this point we cannot cancel animations for groups just yet. 1.5+ runner.end(); }); }; } function done(status) { runner.complete(status); } } }; } else { return prepareAnimation(animationDetails); } }; function prepareAnimation(animationDetails) { // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations var element = animationDetails.element; var event = animationDetails.event; var options = animationDetails.options; var classes = animationDetails.classes; return $$animateJs(element, event, classes, options); } }]; }]; var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animateProvider) { var PRE_DIGEST_STATE = 1; var RUNNING_STATE = 2; var ONE_SPACE = ' '; var rules = this.rules = { skip: [], cancel: [], join: [] }; function makeTruthyCssClassMap(classString) { if (!classString) { return null; } var keys = classString.split(ONE_SPACE); var map = Object.create(null); forEach(keys, function(key) { map[key] = true; }); return map; } function hasMatchingClasses(newClassString, currentClassString) { if (newClassString && currentClassString) { var currentClassMap = makeTruthyCssClassMap(currentClassString); return newClassString.split(ONE_SPACE).some(function(className) { return currentClassMap[className]; }); } } function isAllowed(ruleType, element, currentAnimation, previousAnimation) { return rules[ruleType].some(function(fn) { return fn(element, currentAnimation, previousAnimation); }); } function hasAnimationClasses(animation, and) { var a = (animation.addClass || '').length > 0; var b = (animation.removeClass || '').length > 0; return and ? a && b : a || b; } rules.join.push(function(element, newAnimation, currentAnimation) { // if the new animation is class-based then we can just tack that on return !newAnimation.structural && hasAnimationClasses(newAnimation); }); rules.skip.push(function(element, newAnimation, currentAnimation) { // there is no need to animate anything if no classes are being added and // there is no structural animation that will be triggered return !newAnimation.structural && !hasAnimationClasses(newAnimation); }); rules.skip.push(function(element, newAnimation, currentAnimation) { // why should we trigger a new structural animation if the element will // be removed from the DOM anyway? return currentAnimation.event === 'leave' && newAnimation.structural; }); rules.skip.push(function(element, newAnimation, currentAnimation) { // if there is an ongoing current animation then don't even bother running the class-based animation return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural; }); rules.cancel.push(function(element, newAnimation, currentAnimation) { // there can never be two structural animations running at the same time return currentAnimation.structural && newAnimation.structural; }); rules.cancel.push(function(element, newAnimation, currentAnimation) { // if the previous animation is already running, but the new animation will // be triggered, but the new animation is structural return currentAnimation.state === RUNNING_STATE && newAnimation.structural; }); rules.cancel.push(function(element, newAnimation, currentAnimation) { // cancel the animation if classes added / removed in both animation cancel each other out, // but only if the current animation isn't structural if (currentAnimation.structural) return false; var nA = newAnimation.addClass; var nR = newAnimation.removeClass; var cA = currentAnimation.addClass; var cR = currentAnimation.removeClass; // early detection to save the global CPU shortage :) if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { return false; } return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); }); this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', function($$rAF, $rootScope, $rootElement, $document, $$HashMap, $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) { var activeAnimationsLookup = new $$HashMap(); var disabledElementsLookup = new $$HashMap(); var animationsEnabled = null; function postDigestTaskFactory() { var postDigestCalled = false; return function(fn) { // we only issue a call to postDigest before // it has first passed. This prevents any callbacks // from not firing once the animation has completed // since it will be out of the digest cycle. if (postDigestCalled) { fn(); } else { $rootScope.$$postDigest(function() { postDigestCalled = true; fn(); }); } }; } // Wait until all directive and route-related templates are downloaded and // compiled. The $templateRequest.totalPendingRequests variable keeps track of // all of the remote templates being currently downloaded. If there are no // templates currently downloading then the watcher will still fire anyway. var deregisterWatch = $rootScope.$watch( function() { return $templateRequest.totalPendingRequests === 0; }, function(isEmpty) { if (!isEmpty) return; deregisterWatch(); // Now that all templates have been downloaded, $animate will wait until // the post digest queue is empty before enabling animations. By having two // calls to $postDigest calls we can ensure that the flag is enabled at the // very end of the post digest queue. Since all of the animations in $animate // use $postDigest, it's important that the code below executes at the end. // This basically means that the page is fully downloaded and compiled before // any animations are triggered. $rootScope.$$postDigest(function() { $rootScope.$$postDigest(function() { // we check for null directly in the event that the application already called // .enabled() with whatever arguments that it provided it with if (animationsEnabled === null) { animationsEnabled = true; } }); }); } ); var callbackRegistry = Object.create(null); // remember that the classNameFilter is set during the provider/config // stage therefore we can optimize here and setup a helper function var classNameFilter = $animateProvider.classNameFilter(); var isAnimatableClassName = !classNameFilter ? function() { return true; } : function(className) { return classNameFilter.test(className); }; var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); function normalizeAnimationDetails(element, animation) { return mergeAnimationDetails(element, animation, {}); } // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. var contains = window.Node.prototype.contains || /** @this */ function(arg) { // eslint-disable-next-line no-bitwise return this === arg || !!(this.compareDocumentPosition(arg) & 16); }; function findCallbacks(parent, element, event) { var targetNode = getDomNode(element); var targetParentNode = getDomNode(parent); var matches = []; var entries = callbackRegistry[event]; if (entries) { forEach(entries, function(entry) { if (contains.call(entry.node, targetNode)) { matches.push(entry.callback); } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) { matches.push(entry.callback); } }); } return matches; } function filterFromRegistry(list, matchContainer, matchCallback) { var containerNode = extractElementNode(matchContainer); return list.filter(function(entry) { var isMatch = entry.node === containerNode && (!matchCallback || entry.callback === matchCallback); return !isMatch; }); } function cleanupEventListeners(phase, element) { if (phase === 'close' && !element[0].parentNode) { // If the element is not attached to a parentNode, it has been removed by // the domOperation, and we can safely remove the event callbacks $animate.off(element); } } var $animate = { on: function(event, container, callback) { var node = extractElementNode(container); callbackRegistry[event] = callbackRegistry[event] || []; callbackRegistry[event].push({ node: node, callback: callback }); // Remove the callback when the element is removed from the DOM jqLite(container).on('$destroy', function() { var animationDetails = activeAnimationsLookup.get(node); if (!animationDetails) { // If there's an animation ongoing, the callback calling code will remove // the event listeners. If we'd remove here, the callbacks would be removed // before the animation ends $animate.off(event, container, callback); } }); }, off: function(event, container, callback) { if (arguments.length === 1 && !isString(arguments[0])) { container = arguments[0]; for (var eventType in callbackRegistry) { callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container); } return; } var entries = callbackRegistry[event]; if (!entries) return; callbackRegistry[event] = arguments.length === 1 ? null : filterFromRegistry(entries, container, callback); }, pin: function(element, parentElement) { assertArg(isElement(element), 'element', 'not an element'); assertArg(isElement(parentElement), 'parentElement', 'not an element'); element.data(NG_ANIMATE_PIN_DATA, parentElement); }, push: function(element, event, options, domOperation) { options = options || {}; options.domOperation = domOperation; return queueAnimation(element, event, options); }, // this method has four signatures: // () - global getter // (bool) - global setter // (element) - element getter // (element, bool) - element setter enabled: function(element, bool) { var argCount = arguments.length; if (argCount === 0) { // () - Global getter bool = !!animationsEnabled; } else { var hasElement = isElement(element); if (!hasElement) { // (bool) - Global setter bool = animationsEnabled = !!element; } else { var node = getDomNode(element); if (argCount === 1) { // (element) - Element getter bool = !disabledElementsLookup.get(node); } else { // (element, bool) - Element setter disabledElementsLookup.put(node, !bool); } } } return bool; } }; return $animate; function queueAnimation(element, event, initialOptions) { // we always make a copy of the options since // there should never be any side effects on // the input data when running `$animateCss`. var options = copy(initialOptions); var node, parent; element = stripCommentsFromElement(element); if (element) { node = getDomNode(element); parent = element.parent(); } options = prepareAnimationOptions(options); // we create a fake runner with a working promise. // These methods will become available after the digest has passed var runner = new $$AnimateRunner(); // this is used to trigger callbacks in postDigest mode var runInNextPostDigestOrNow = postDigestTaskFactory(); if (isArray(options.addClass)) { options.addClass = options.addClass.join(' '); } if (options.addClass && !isString(options.addClass)) { options.addClass = null; } if (isArray(options.removeClass)) { options.removeClass = options.removeClass.join(' '); } if (options.removeClass && !isString(options.removeClass)) { options.removeClass = null; } if (options.from && !isObject(options.from)) { options.from = null; } if (options.to && !isObject(options.to)) { options.to = null; } // there are situations where a directive issues an animation for // a jqLite wrapper that contains only comment nodes... If this // happens then there is no way we can perform an animation if (!node) { close(); return runner; } var className = [node.className, options.addClass, options.removeClass].join(' '); if (!isAnimatableClassName(className)) { close(); return runner; } var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; var documentHidden = $document[0].hidden; // this is a hard disable of all animations for the application or on // the element itself, therefore there is no need to continue further // past this point if not enabled // Animations are also disabled if the document is currently hidden (page is not visible // to the user), because browsers slow down or do not flush calls to requestAnimationFrame var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node); var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; var hasExistingAnimation = !!existingAnimation.state; // there is no point in traversing the same collection of parent ancestors if a followup // animation will be run on the same element that already did all that checking work if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) { skipAnimations = !areAnimationsAllowed(element, parent, event); } if (skipAnimations) { // Callbacks should fire even if the document is hidden (regression fix for issue #14120) if (documentHidden) notifyProgress(runner, event, 'start'); close(); if (documentHidden) notifyProgress(runner, event, 'close'); return runner; } if (isStructural) { closeChildAnimations(element); } var newAnimation = { structural: isStructural, element: element, event: event, addClass: options.addClass, removeClass: options.removeClass, close: close, options: options, runner: runner }; if (hasExistingAnimation) { var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); if (skipAnimationFlag) { if (existingAnimation.state === RUNNING_STATE) { close(); return runner; } else { mergeAnimationDetails(element, existingAnimation, newAnimation); return existingAnimation.runner; } } var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); if (cancelAnimationFlag) { if (existingAnimation.state === RUNNING_STATE) { // this will end the animation right away and it is safe // to do so since the animation is already running and the // runner callback code will run in async existingAnimation.runner.end(); } else if (existingAnimation.structural) { // this means that the animation is queued into a digest, but // hasn't started yet. Therefore it is safe to run the close // method which will call the runner methods in async. existingAnimation.close(); } else { // this will merge the new animation options into existing animation options mergeAnimationDetails(element, existingAnimation, newAnimation); return existingAnimation.runner; } } else { // a joined animation means that this animation will take over the existing one // so an example would involve a leave animation taking over an enter. Then when // the postDigest kicks in the enter will be ignored. var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); if (joinAnimationFlag) { if (existingAnimation.state === RUNNING_STATE) { normalizeAnimationDetails(element, newAnimation); } else { applyGeneratedPreparationClasses(element, isStructural ? event : null, options); event = newAnimation.event = existingAnimation.event; options = mergeAnimationDetails(element, existingAnimation, newAnimation); //we return the same runner since only the option values of this animation will //be fed into the `existingAnimation`. return existingAnimation.runner; } } } } else { // normalization in this case means that it removes redundant CSS classes that // already exist (addClass) or do not exist (removeClass) on the element normalizeAnimationDetails(element, newAnimation); } // when the options are merged and cleaned up we may end up not having to do // an animation at all, therefore we should check this before issuing a post // digest callback. Structural animations will always run no matter what. var isValidAnimation = newAnimation.structural; if (!isValidAnimation) { // animate (from/to) can be quickly checked first, otherwise we check if any classes are present isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) || hasAnimationClasses(newAnimation); } if (!isValidAnimation) { close(); clearElementAnimationState(element); return runner; } // the counter keeps track of cancelled animations var counter = (existingAnimation.counter || 0) + 1; newAnimation.counter = counter; markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); $rootScope.$$postDigest(function() { var animationDetails = activeAnimationsLookup.get(node); var animationCancelled = !animationDetails; animationDetails = animationDetails || {}; // if addClass/removeClass is called before something like enter then the // registered parent element may not be present. The code below will ensure // that a final value for parent element is obtained var parentElement = element.parent() || []; // animate/structural/class-based animations all have requirements. Otherwise there // is no point in performing an animation. The parent node must also be set. var isValidAnimation = parentElement.length > 0 && (animationDetails.event === 'animate' || animationDetails.structural || hasAnimationClasses(animationDetails)); // this means that the previous animation was cancelled // even if the follow-up animation is the same event if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) { // if another animation did not take over then we need // to make sure that the domOperation and options are // handled accordingly if (animationCancelled) { applyAnimationClasses(element, options); applyAnimationStyles(element, options); } // if the event changed from something like enter to leave then we do // it, otherwise if it's the same then the end result will be the same too if (animationCancelled || (isStructural && animationDetails.event !== event)) { options.domOperation(); runner.end(); } // in the event that the element animation was not cancelled or a follow-up animation // isn't allowed to animate from here then we need to clear the state of the element // so that any future animations won't read the expired animation data. if (!isValidAnimation) { clearElementAnimationState(element); } return; } // this combined multiple class to addClass / removeClass into a setClass event // so long as a structural event did not take over the animation event = !animationDetails.structural && hasAnimationClasses(animationDetails, true) ? 'setClass' : animationDetails.event; markElementAnimationState(element, RUNNING_STATE); var realRunner = $$animation(element, event, animationDetails.options); // this will update the runner's flow-control events based on // the `realRunner` object. runner.setHost(realRunner); notifyProgress(runner, event, 'start', {}); realRunner.done(function(status) { close(!status); var animationDetails = activeAnimationsLookup.get(node); if (animationDetails && animationDetails.counter === counter) { clearElementAnimationState(getDomNode(element)); } notifyProgress(runner, event, 'close', {}); }); }); return runner; function notifyProgress(runner, event, phase, data) { runInNextPostDigestOrNow(function() { var callbacks = findCallbacks(parent, element, event); if (callbacks.length) { // do not optimize this call here to RAF because // we don't know how heavy the callback code here will // be and if this code is buffered then this can // lead to a performance regression. $$rAF(function() { forEach(callbacks, function(callback) { callback(element, phase, data); }); cleanupEventListeners(phase, element); }); } else { cleanupEventListeners(phase, element); } }); runner.progress(event, phase, data); } function close(reject) { clearGeneratedClasses(element, options); applyAnimationClasses(element, options); applyAnimationStyles(element, options); options.domOperation(); runner.complete(!reject); } } function closeChildAnimations(element) { var node = getDomNode(element); var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']'); forEach(children, function(child) { var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10); var animationDetails = activeAnimationsLookup.get(child); if (animationDetails) { switch (state) { case RUNNING_STATE: animationDetails.runner.end(); /* falls through */ case PRE_DIGEST_STATE: activeAnimationsLookup.remove(child); break; } } }); } function clearElementAnimationState(element) { var node = getDomNode(element); node.removeAttribute(NG_ANIMATE_ATTR_NAME); activeAnimationsLookup.remove(node); } function isMatchingElement(nodeOrElmA, nodeOrElmB) { return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB); } /** * This fn returns false if any of the following is true: * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed * b) a parent element has an ongoing structural animation, and animateChildren is false * c) the element is not a child of the body * d) the element is not a child of the $rootElement */ function areAnimationsAllowed(element, parentElement, event) { var bodyElement = jqLite($document[0].body); var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; var rootElementDetected = isMatchingElement(element, $rootElement); var parentAnimationDetected = false; var animateChildren; var elementDisabled = disabledElementsLookup.get(getDomNode(element)); var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA); if (parentHost) { parentElement = parentHost; } parentElement = getDomNode(parentElement); while (parentElement) { if (!rootElementDetected) { // angular doesn't want to attempt to animate elements outside of the application // therefore we need to ensure that the rootElement is an ancestor of the current element rootElementDetected = isMatchingElement(parentElement, $rootElement); } if (parentElement.nodeType !== ELEMENT_NODE) { // no point in inspecting the #document element break; } var details = activeAnimationsLookup.get(parentElement) || {}; // either an enter, leave or move animation will commence // therefore we can't allow any animations to take place // but if a parent animation is class-based then that's ok if (!parentAnimationDetected) { var parentElementDisabled = disabledElementsLookup.get(parentElement); if (parentElementDisabled === true && elementDisabled !== false) { // disable animations if the user hasn't explicitly enabled animations on the // current element elementDisabled = true; // element is disabled via parent element, no need to check anything else break; } else if (parentElementDisabled === false) { elementDisabled = false; } parentAnimationDetected = details.structural; } if (isUndefined(animateChildren) || animateChildren === true) { var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA); if (isDefined(value)) { animateChildren = value; } } // there is no need to continue traversing at this point if (parentAnimationDetected && animateChildren === false) break; if (!bodyElementDetected) { // we also need to ensure that the element is or will be a part of the body element // otherwise it is pointless to even issue an animation to be rendered bodyElementDetected = isMatchingElement(parentElement, bodyElement); } if (bodyElementDetected && rootElementDetected) { // If both body and root have been found, any other checks are pointless, // as no animation data should live outside the application break; } if (!rootElementDetected) { // If no rootElement is detected, check if the parentElement is pinned to another element parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA); if (parentHost) { // The pin target element becomes the next parent element parentElement = getDomNode(parentHost); continue; } } parentElement = parentElement.parentNode; } var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; return allowAnimation && rootElementDetected && bodyElementDetected; } function markElementAnimationState(element, state, details) { details = details || {}; details.state = state; var node = getDomNode(element); node.setAttribute(NG_ANIMATE_ATTR_NAME, state); var oldValue = activeAnimationsLookup.get(node); var newValue = oldValue ? extend(oldValue, details) : details; activeAnimationsLookup.put(node, newValue); } }]; }]; /* exported $$AnimationProvider */ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animateProvider) { var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; var drivers = this.drivers = []; var RUNNER_STORAGE_KEY = '$$animationRunner'; function setRunner(element, runner) { element.data(RUNNER_STORAGE_KEY, runner); } function removeRunner(element) { element.removeData(RUNNER_STORAGE_KEY); } function getRunner(element) { return element.data(RUNNER_STORAGE_KEY); } this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { var animationQueue = []; var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); function sortAnimations(animations) { var tree = { children: [] }; var i, lookup = new $$HashMap(); // this is done first beforehand so that the hashmap // is filled with a list of the elements that will be animated for (i = 0; i < animations.length; i++) { var animation = animations[i]; lookup.put(animation.domNode, animations[i] = { domNode: animation.domNode, fn: animation.fn, children: [] }); } for (i = 0; i < animations.length; i++) { processNode(animations[i]); } return flatten(tree); function processNode(entry) { if (entry.processed) return entry; entry.processed = true; var elementNode = entry.domNode; var parentNode = elementNode.parentNode; lookup.put(elementNode, entry); var parentEntry; while (parentNode) { parentEntry = lookup.get(parentNode); if (parentEntry) { if (!parentEntry.processed) { parentEntry = processNode(parentEntry); } break; } parentNode = parentNode.parentNode; } (parentEntry || tree).children.push(entry); return entry; } function flatten(tree) { var result = []; var queue = []; var i; for (i = 0; i < tree.children.length; i++) { queue.push(tree.children[i]); } var remainingLevelEntries = queue.length; var nextLevelEntries = 0; var row = []; for (i = 0; i < queue.length; i++) { var entry = queue[i]; if (remainingLevelEntries <= 0) { remainingLevelEntries = nextLevelEntries; nextLevelEntries = 0; result.push(row); row = []; } row.push(entry.fn); entry.children.forEach(function(childEntry) { nextLevelEntries++; queue.push(childEntry); }); remainingLevelEntries--; } if (row.length) { result.push(row); } return result; } } // TODO(matsko): document the signature in a better way return function(element, event, options) { options = prepareAnimationOptions(options); var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; // there is no animation at the current moment, however // these runner methods will get later updated with the // methods leading into the driver's end/cancel methods // for now they just stop the animation from starting var runner = new $$AnimateRunner({ end: function() { close(); }, cancel: function() { close(true); } }); if (!drivers.length) { close(); return runner; } setRunner(element, runner); var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); var tempClasses = options.tempClasses; if (tempClasses) { classes += ' ' + tempClasses; options.tempClasses = null; } var prepareClassName; if (isStructural) { prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; $$jqLite.addClass(element, prepareClassName); } animationQueue.push({ // this data is used by the postDigest code and passed into // the driver step function element: element, classes: classes, event: event, structural: isStructural, options: options, beforeStart: beforeStart, close: close }); element.on('$destroy', handleDestroyedElement); // we only want there to be one function called within the post digest // block. This way we can group animations for all the animations that // were apart of the same postDigest flush call. if (animationQueue.length > 1) return runner; $rootScope.$$postDigest(function() { var animations = []; forEach(animationQueue, function(entry) { // the element was destroyed early on which removed the runner // form its storage. This means we can't animate this element // at all and it already has been closed due to destruction. if (getRunner(entry.element)) { animations.push(entry); } else { entry.close(); } }); // now any future animations will be in another postDigest animationQueue.length = 0; var groupedAnimations = groupAnimations(animations); var toBeSortedAnimations = []; forEach(groupedAnimations, function(animationEntry) { toBeSortedAnimations.push({ domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), fn: function triggerAnimationStart() { // it's important that we apply the `ng-animate` CSS class and the // temporary classes before we do any driver invoking since these // CSS classes may be required for proper CSS detection. animationEntry.beforeStart(); var startAnimationFn, closeFn = animationEntry.close; // in the event that the element was removed before the digest runs or // during the RAF sequencing then we should not trigger the animation. var targetElement = animationEntry.anchors ? (animationEntry.from.element || animationEntry.to.element) : animationEntry.element; if (getRunner(targetElement)) { var operation = invokeFirstDriver(animationEntry); if (operation) { startAnimationFn = operation.start; } } if (!startAnimationFn) { closeFn(); } else { var animationRunner = startAnimationFn(); animationRunner.done(function(status) { closeFn(!status); }); updateAnimationRunners(animationEntry, animationRunner); } } }); }); // we need to sort each of the animations in order of parent to child // relationships. This ensures that the child classes are applied at the // right time. $$rAFScheduler(sortAnimations(toBeSortedAnimations)); }); return runner; // TODO(matsko): change to reference nodes function getAnchorNodes(node) { var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']'; var items = node.hasAttribute(NG_ANIMATE_REF_ATTR) ? [node] : node.querySelectorAll(SELECTOR); var anchors = []; forEach(items, function(node) { var attr = node.getAttribute(NG_ANIMATE_REF_ATTR); if (attr && attr.length) { anchors.push(node); } }); return anchors; } function groupAnimations(animations) { var preparedAnimations = []; var refLookup = {}; forEach(animations, function(animation, index) { var element = animation.element; var node = getDomNode(element); var event = animation.event; var enterOrMove = ['enter', 'move'].indexOf(event) >= 0; var anchorNodes = animation.structural ? getAnchorNodes(node) : []; if (anchorNodes.length) { var direction = enterOrMove ? 'to' : 'from'; forEach(anchorNodes, function(anchor) { var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR); refLookup[key] = refLookup[key] || {}; refLookup[key][direction] = { animationID: index, element: jqLite(anchor) }; }); } else { preparedAnimations.push(animation); } }); var usedIndicesLookup = {}; var anchorGroups = {}; forEach(refLookup, function(operations, key) { var from = operations.from; var to = operations.to; if (!from || !to) { // only one of these is set therefore we can't have an // anchor animation since all three pieces are required var index = from ? from.animationID : to.animationID; var indexKey = index.toString(); if (!usedIndicesLookup[indexKey]) { usedIndicesLookup[indexKey] = true; preparedAnimations.push(animations[index]); } return; } var fromAnimation = animations[from.animationID]; var toAnimation = animations[to.animationID]; var lookupKey = from.animationID.toString(); if (!anchorGroups[lookupKey]) { var group = anchorGroups[lookupKey] = { structural: true, beforeStart: function() { fromAnimation.beforeStart(); toAnimation.beforeStart(); }, close: function() { fromAnimation.close(); toAnimation.close(); }, classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes), from: fromAnimation, to: toAnimation, anchors: [] // TODO(matsko): change to reference nodes }; // the anchor animations require that the from and to elements both have at least // one shared CSS class which effectively marries the two elements together to use // the same animation driver and to properly sequence the anchor animation. if (group.classes.length) { preparedAnimations.push(group); } else { preparedAnimations.push(fromAnimation); preparedAnimations.push(toAnimation); } } anchorGroups[lookupKey].anchors.push({ 'out': from.element, 'in': to.element }); }); return preparedAnimations; } function cssClassesIntersection(a,b) { a = a.split(' '); b = b.split(' '); var matches = []; for (var i = 0; i < a.length; i++) { var aa = a[i]; if (aa.substring(0,3) === 'ng-') continue; for (var j = 0; j < b.length; j++) { if (aa === b[j]) { matches.push(aa); break; } } } return matches.join(' '); } function invokeFirstDriver(animationDetails) { // we loop in reverse order since the more general drivers (like CSS and JS) // may attempt more elements, but custom drivers are more particular for (var i = drivers.length - 1; i >= 0; i--) { var driverName = drivers[i]; var factory = $injector.get(driverName); var driver = factory(animationDetails); if (driver) { return driver; } } } function beforeStart() { element.addClass(NG_ANIMATE_CLASSNAME); if (tempClasses) { $$jqLite.addClass(element, tempClasses); } if (prepareClassName) { $$jqLite.removeClass(element, prepareClassName); prepareClassName = null; } } function updateAnimationRunners(animation, newRunner) { if (animation.from && animation.to) { update(animation.from.element); update(animation.to.element); } else { update(animation.element); } function update(element) { var runner = getRunner(element); if (runner) runner.setHost(newRunner); } } function handleDestroyedElement() { var runner = getRunner(element); if (runner && (event !== 'leave' || !options.$$domOperationFired)) { runner.end(); } } function close(rejected) { element.off('$destroy', handleDestroyedElement); removeRunner(element); applyAnimationClasses(element, options); applyAnimationStyles(element, options); options.domOperation(); if (tempClasses) { $$jqLite.removeClass(element, tempClasses); } element.removeClass(NG_ANIMATE_CLASSNAME); runner.complete(!rejected); } }; }]; }]; /** * @ngdoc directive * @name ngAnimateSwap * @restrict A * @scope * * @description * * ngAnimateSwap is a animation-oriented directive that allows for the container to * be removed and entered in whenever the associated expression changes. A * common usecase for this directive is a rotating banner or slider component which * contains one image being present at a time. When the active image changes * then the old image will perform a `leave` animation and the new element * will be inserted via an `enter` animation. * * @animations * | Animation | Occurs | * |----------------------------------|--------------------------------------| * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM | * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM | * * @example * * *
    *
    * {{ number }} *
    *
    *
    * * angular.module('ngAnimateSwapExample', ['ngAnimate']) * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) { * $scope.number = 0; * $interval(function() { * $scope.number++; * }, 1000); * * var colors = ['red','blue','green','yellow','orange']; * $scope.colorClass = function(number) { * return colors[number % colors.length]; * }; * }]); * * * .container { * height:250px; * width:250px; * position:relative; * overflow:hidden; * border:2px solid black; * } * .container .cell { * font-size:150px; * text-align:center; * line-height:250px; * position:absolute; * top:0; * left:0; * right:0; * border-bottom:2px solid black; * } * .swap-animation.ng-enter, .swap-animation.ng-leave { * transition:0.5s linear all; * } * .swap-animation.ng-enter { * top:-250px; * } * .swap-animation.ng-enter-active { * top:0px; * } * .swap-animation.ng-leave { * top:0px; * } * .swap-animation.ng-leave-active { * top:250px; * } * .red { background:red; } * .green { background:green; } * .blue { background:blue; } * .yellow { background:yellow; } * .orange { background:orange; } * *
    */ var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) { return { restrict: 'A', transclude: 'element', terminal: true, priority: 600, // we use 600 here to ensure that the directive is caught before others link: function(scope, $element, attrs, ctrl, $transclude) { var previousElement, previousScope; scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) { if (previousElement) { $animate.leave(previousElement); } if (previousScope) { previousScope.$destroy(); previousScope = null; } if (value || value === 0) { previousScope = scope.$new(); $transclude(previousScope, function(element) { previousElement = element; $animate.enter(element, null, $element); }); } }); } }; }]; /** * @ngdoc module * @name ngAnimate * @description * * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. * *
    * * # Usage * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within * the HTML element that the animation will be triggered on. * * ## Directive Support * The following directives are "animation aware": * * | Directive | Supported Animations | * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | * | {@link module:ngMessages#animations ngMessage} | enter and leave | * * (More information can be found by visiting each the documentation associated with each directive.) * * ## CSS-based Animations * * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML * and CSS code we can create an animation that will be picked up by Angular when an underlying directive performs an operation. * * The example below shows how an `enter` animation can be made possible on an element using `ng-if`: * * ```html *
    * Fade me in out *
    * * * ``` * * Notice the CSS class **fade**? We can now create the CSS transition code that references this class: * * ```css * /* The starting CSS styles for the enter animation */ * .fade.ng-enter { * transition:0.5s linear all; * opacity:0; * } * * /* The finishing CSS styles for the enter animation */ * .fade.ng-enter.ng-enter-active { * opacity:1; * } * ``` * * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards. * * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions: * * ```css * /* now the element will fade out before it is removed from the DOM */ * .fade.ng-leave { * transition:0.5s linear all; * opacity:1; * } * .fade.ng-leave.ng-leave-active { * opacity:0; * } * ``` * * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class: * * ```css * /* there is no need to define anything inside of the destination * CSS class since the keyframe will take charge of the animation */ * .fade.ng-leave { * animation: my_fade_animation 0.5s linear; * -webkit-animation: my_fade_animation 0.5s linear; * } * * @keyframes my_fade_animation { * from { opacity:1; } * to { opacity:0; } * } * * @-webkit-keyframes my_fade_animation { * from { opacity:1; } * to { opacity:0; } * } * ``` * * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element. * * ### CSS Class-based Animations * * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added * and removed. * * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class: * * ```html *
    * Show and hide me *
    * * * * ``` * * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest. * * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation * with CSS styles. * * ```html *
    * Highlight this box *
    * * * * ``` * * We can also make use of CSS keyframes by placing them within the CSS classes. * * * ### CSS Staggering Animations * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for * the animation. The style property expected within the stagger class can either be a **transition-delay** or an * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). * * ```css * .my-animation.ng-enter { * /* standard transition code */ * transition: 1s linear all; * opacity:0; * } * .my-animation.ng-enter-stagger { * /* this will have a 100ms delay between each successive leave animation */ * transition-delay: 0.1s; * * /* As of 1.4.4, this must always be set: it signals ngAnimate * to not accidentally inherit a delay property from another CSS class */ * transition-duration: 0s; * } * .my-animation.ng-enter.ng-enter-active { * /* standard transition styles */ * opacity:1; * } * ``` * * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired. * * The following code will issue the **ng-leave-stagger** event on the element provided: * * ```js * var kids = parent.children(); * * $animate.leave(kids[0]); //stagger index=0 * $animate.leave(kids[1]); //stagger index=1 * $animate.leave(kids[2]); //stagger index=2 * $animate.leave(kids[3]); //stagger index=3 * $animate.leave(kids[4]); //stagger index=4 * * window.requestAnimationFrame(function() { * //stagger has reset itself * $animate.leave(kids[5]); //stagger index=0 * $animate.leave(kids[6]); //stagger index=1 * * $scope.$digest(); * }); * ``` * * Stagger animations are currently only supported within CSS-defined animations. * * ### The `ng-animate` CSS class * * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation. * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations). * * Therefore, animations can be applied to an element using this temporary class directly via CSS. * * ```css * .zipper.ng-animate { * transition:0.5s linear all; * } * .zipper.ng-enter { * opacity:0; * } * .zipper.ng-enter.ng-enter-active { * opacity:1; * } * .zipper.ng-leave { * opacity:1; * } * .zipper.ng-leave.ng-leave-active { * opacity:0; * } * ``` * * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove * the CSS class once an animation has completed.) * * * ### The `ng-[event]-prepare` class * * This is a special class that can be used to prevent unwanted flickering / flash of content before * the actual animation starts. The class is added as soon as an animation is initialized, but removed * before the actual animation starts (after waiting for a $digest). * It is also only added for *structural* animations (`enter`, `move`, and `leave`). * * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` * into elements that have class-based animations such as `ngClass`. * * ```html *
    *
    *
    *
    *
    * ``` * * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: * * ```css * .message.ng-enter-prepare { * opacity: 0; * } * * ``` * * ## JavaScript-based Animations * * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the * `module.animation()` module function we can register the animation. * * Let's see an example of a enter/leave animation using `ngRepeat`: * * ```html *
    * {{ item }} *
    * ``` * * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`: * * ```js * myModule.animation('.slide', [function() { * return { * // make note that other events (like addClass/removeClass) * // have different function input parameters * enter: function(element, doneFn) { * jQuery(element).fadeIn(1000, doneFn); * * // remember to call doneFn so that angular * // knows that the animation has concluded * }, * * move: function(element, doneFn) { * jQuery(element).fadeIn(1000, doneFn); * }, * * leave: function(element, doneFn) { * jQuery(element).fadeOut(1000, doneFn); * } * } * }]); * ``` * * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as * greensock.js and velocity.js. * * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define * our animations inside of the same registered animation, however, the function input arguments are a bit different: * * ```html *
    * this box is moody *
    * * * * ``` * * ```js * myModule.animation('.colorful', [function() { * return { * addClass: function(element, className, doneFn) { * // do some cool animation and call the doneFn * }, * removeClass: function(element, className, doneFn) { * // do some cool animation and call the doneFn * }, * setClass: function(element, addedClass, removedClass, doneFn) { * // do some cool animation and call the doneFn * } * } * }]); * ``` * * ## CSS + JS Animations Together * * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular, * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking * charge of the animation**: * * ```html *
    * Slide in and out *
    * ``` * * ```js * myModule.animation('.slide', [function() { * return { * enter: function(element, doneFn) { * jQuery(element).slideIn(1000, doneFn); * } * } * }]); * ``` * * ```css * .slide.ng-enter { * transition:0.5s linear all; * transform:translateY(-100px); * } * .slide.ng-enter.ng-enter-active { * transform:translateY(0); * } * ``` * * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from * our own JS-based animation code: * * ```js * myModule.animation('.slide', ['$animateCss', function($animateCss) { * return { * enter: function(element) { * // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. * return $animateCss(element, { * event: 'enter', * structural: true * }); * } * } * }]); * ``` * * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework. * * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that * data into `$animateCss` directly: * * ```js * myModule.animation('.slide', ['$animateCss', function($animateCss) { * return { * enter: function(element) { * return $animateCss(element, { * event: 'enter', * structural: true, * addClass: 'maroon-setting', * from: { height:0 }, * to: { height: 200 } * }); * } * } * }]); * ``` * * Now we can fill in the rest via our transition CSS code: * * ```css * /* the transition tells ngAnimate to make the animation happen */ * .slide.ng-enter { transition:0.5s linear all; } * * /* this extra CSS class will be absorbed into the transition * since the $animateCss code is adding the class */ * .maroon-setting { background:red; } * ``` * * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over. * * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}. * * ## Animation Anchoring (via `ng-animate-ref`) * * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between * structural areas of an application (like views) by pairing up elements using an attribute * called `ng-animate-ref`. * * Let's say for example we have two views that are managed by `ng-view` and we want to show * that there is a relationship between two components situated in within these views. By using the * `ng-animate-ref` attribute we can identify that the two components are paired together and we * can then attach an animation, which is triggered when the view changes. * * Say for example we have the following template code: * * ```html * *
    *
    * * * * * * * * * ``` * * Now, when the view changes (once the link is clicked), ngAnimate will examine the * HTML contents to see if there is a match reference between any components in the view * that is leaving and the view that is entering. It will scan both the view which is being * removed (leave) and inserted (enter) to see if there are any paired DOM elements that * contain a matching ref value. * * The two images match since they share the same ref value. ngAnimate will now create a * transport element (which is a clone of the first image element) and it will then attempt * to animate to the position of the second image element in the next view. For the animation to * work a special CSS class called `ng-anchor` will be added to the transported element. * * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then * ngAnimate will handle the entire transition for us as well as the addition and removal of * any changes of CSS classes between the elements: * * ```css * .banner.ng-anchor { * /* this animation will last for 1 second since there are * two phases to the animation (an `in` and an `out` phase) */ * transition:0.5s linear all; * } * ``` * * We also **must** include animations for the views that are being entered and removed * (otherwise anchoring wouldn't be possible since the new view would be inserted right away). * * ```css * .view-animation.ng-enter, .view-animation.ng-leave { * transition:0.5s linear all; * position:fixed; * left:0; * top:0; * width:100%; * } * .view-animation.ng-enter { * transform:translateX(100%); * } * .view-animation.ng-leave, * .view-animation.ng-enter.ng-enter-active { * transform:translateX(0%); * } * .view-animation.ng-leave.ng-leave-active { * transform:translateX(-100%); * } * ``` * * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur: * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away * from its origin. Once that animation is over then the `in` stage occurs which animates the * element to its destination. The reason why there are two animations is to give enough time * for the enter animation on the new element to be ready. * * The example above sets up a transition for both the in and out phases, but we can also target the out or * in phases directly via `ng-anchor-out` and `ng-anchor-in`. * * ```css * .banner.ng-anchor-out { * transition: 0.5s linear all; * * /* the scale will be applied during the out animation, * but will be animated away when the in animation runs */ * transform: scale(1.2); * } * * .banner.ng-anchor-in { * transition: 1s linear all; * } * ``` * * * * * ### Anchoring Demo * Home
    angular.module('anchoringExample', ['ngAnimate', 'ngRoute']) .config(['$routeProvider', function($routeProvider) { $routeProvider.when('/', { templateUrl: 'home.html', controller: 'HomeController as home' }); $routeProvider.when('/profile/:id', { templateUrl: 'profile.html', controller: 'ProfileController as profile' }); }]) .run(['$rootScope', function($rootScope) { $rootScope.records = [ { id: 1, title: 'Miss Beulah Roob' }, { id: 2, title: 'Trent Morissette' }, { id: 3, title: 'Miss Ava Pouros' }, { id: 4, title: 'Rod Pouros' }, { id: 5, title: 'Abdul Rice' }, { id: 6, title: 'Laurie Rutherford Sr.' }, { id: 7, title: 'Nakia McLaughlin' }, { id: 8, title: 'Jordon Blanda DVM' }, { id: 9, title: 'Rhoda Hand' }, { id: 10, title: 'Alexandrea Sauer' } ]; }]) .controller('HomeController', [function() { //empty }]) .controller('ProfileController', ['$rootScope', '$routeParams', function ProfileController($rootScope, $routeParams) { var index = parseInt($routeParams.id, 10); var record = $rootScope.records[index - 1]; this.title = record.title; this.id = record.id; }]);

    Welcome to the home page

    Please click on an element

    {{ record.title }}
    {{ profile.title }}
    .record { display:block; font-size:20px; } .profile { background:black; color:white; font-size:100px; } .view-container { position:relative; } .view-container > .view.ng-animate { position:absolute; top:0; left:0; width:100%; min-height:500px; } .view.ng-enter, .view.ng-leave, .record.ng-anchor { transition:0.5s linear all; } .view.ng-enter { transform:translateX(100%); } .view.ng-enter.ng-enter-active, .view.ng-leave { transform:translateX(0%); } .view.ng-leave.ng-leave-active { transform:translateX(-100%); } .record.ng-anchor-out { background:red; }
    * * ### How is the element transported? * * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element * will become visible since the shim class will be removed. * * ### How is the morphing handled? * * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out * what CSS classes differ between the starting element and the destination element. These different CSS classes * will be added/removed on the anchor element and a transition will be applied (the transition that is provided * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since * the cloned element is placed inside of root element which is likely close to the body element). * * Note that if the root element is on the `` element then the cloned node will be placed inside of body. * * * ## Using $animate in your directive code * * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's * imagine we have a greeting box that shows and hides itself when the data changes * * ```html * Hi there * ``` * * ```js * ngModule.directive('greetingBox', ['$animate', function($animate) { * return function(scope, element, attrs) { * attrs.$observe('active', function(value) { * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); * }); * }); * }]); * ``` * * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element * in our HTML code then we can trigger a CSS or JS animation to happen. * * ```css * /* normally we would create a CSS class to reference on the element */ * greeting-box.on { transition:0.5s linear all; background:green; color:white; } * ``` * * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's * possible be sure to visit the {@link ng.$animate $animate service API page}. * * * ## Callbacks and Promises * * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has * ended by chaining onto the returned promise that animation method returns. * * ```js * // somewhere within the depths of the directive * $animate.enter(element, parent).then(function() { * //the animation has completed * }); * ``` * * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case * anymore.) * * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view * routing controller to hook into that: * * ```js * ngModule.controller('HomePageController', ['$animate', function($animate) { * $animate.on('enter', ngViewElement, function(element) { * // the animation for this route has completed * }]); * }]) * ``` * * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) */ var copy; var extend; var forEach; var isArray; var isDefined; var isElement; var isFunction; var isObject; var isString; var isUndefined; var jqLite; var noop; /** * @ngdoc service * @name $animate * @kind object * * @description * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. * * Click here {@link ng.$animate to learn more about animations with `$animate`}. */ angular.module('ngAnimate', [], function initAngularHelpers() { // Access helpers from angular core. // Do it inside a `config` block to ensure `window.angular` is available. noop = angular.noop; copy = angular.copy; extend = angular.extend; jqLite = angular.element; forEach = angular.forEach; isArray = angular.isArray; isString = angular.isString; isObject = angular.isObject; isUndefined = angular.isUndefined; isDefined = angular.isDefined; isFunction = angular.isFunction; isElement = angular.isElement; }) .directive('ngAnimateSwap', ngAnimateSwapDirective) .directive('ngAnimateChildren', $$AnimateChildrenDirective) .factory('$$rAFScheduler', $$rAFSchedulerFactory) .provider('$$animateQueue', $$AnimateQueueProvider) .provider('$$animation', $$AnimationProvider) .provider('$animateCss', $AnimateCssProvider) .provider('$$animateCssDriver', $$AnimateCssDriverProvider) .provider('$$animateJs', $$AnimateJsProvider) .provider('$$animateJsDriver', $$AnimateJsDriverProvider); })(window, window.angular); angular-animate.min.js000066400000000000000000000621721325274564300403610ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-animate/* AngularJS v1.5.11 (c) 2010-2017 Google, Inc. http://angularjs.org License: MIT */ (function(R,B){'use strict';function Da(a,b,c){if(!a)throw Ma("areq",b||"?",c||"required");return a}function Ea(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;X(a)&&(a=a.join(" "));X(b)&&(b=b.join(" "));return a+" "+b}function Na(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function Y(a,b,c){var d="";a=X(a)?a:a&&G(a)&&a.length?a.split(/\s+/):[];s(a,function(a,l){a&&0=a&&(a=e,e=0,b.push(k),k=[]);k.push(g.fn);g.children.forEach(function(a){e++;c.push(a)});a--}k.length&&b.push(k);return b}(c)}var u=[],C=Z(a);return function(n,Q,t){function H(a){a= a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];s(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function T(a){var b=[],c={};s(a,function(a,d){var h=y(a.element),e=0<=["enter","move"].indexOf(a.event),h=a.structural?H(h):[];if(h.length){var k=e?"to":"from";s(h,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][k]={animationID:d,element:F(a)}})}else b.push(a)});var d={},e={};s(c,function(c,k){var r=c.from, p=c.to;if(r&&p){var z=a[r.animationID],g=a[p.animationID],A=r.animationID.toString();if(!e[A]){var n=e[A]={structural:!0,beforeStart:function(){z.beforeStart();g.beforeStart()},close:function(){z.close();g.close()},classes:O(z.classes,g.classes),from:z,to:g,anchors:[]};n.classes.length?b.push(n):(b.push(z),b.push(g))}e[A].anchors.push({out:r.element,"in":p.element})}else r=r?r.animationID:p.animationID,p=r.toString(),d[p]||(d[p]=!0,b.push(a[r]))});return b}function O(a,b){a=a.split(" ");b=b.split(" "); for(var c=[],d=0;d=R&&b>=m&&(F=!0,k())}function N(){function b(){if(!w){M(!1);s(x,function(a){h.style[a[0]]=a[1]});T(a,f);e.addClass(a,ea);if(q.recalculateTimingStyles){na= h.className+" "+ga;ia=B(h,na);D=H(h,na,ia);ca=D.maxDelay;J=Math.max(ca,0);m=D.maxDuration;if(0===m){k();return}q.hasTransitions=0l.expectedEndTime)?n.cancel(l.timer):g.push(k)}N&&(p=n(c,p,!1),g[0]={timer:p,expectedEndTime:d},g.push(k),a.data("$$animateCss",g));if(fa.length)a.on(fa.join(" "),z);f.to&&(f.cleanupStyles&&Ka(A,h,Object.keys(f.to)),Ga(a,f))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d UI ngModelCtrl.$render = function() { element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio))); }; //ui->model element.on(buttonsCtrl.toggleEvent, function() { if (attrs.disabled) { return; } var isActive = element.hasClass(buttonsCtrl.activeClass); if (!isActive || angular.isDefined(attrs.uncheckable)) { scope.$apply(function() { ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio)); ngModelCtrl.$render(); }); } }); if (attrs.uibUncheckable) { scope.$watch(uncheckableExpr, function(uncheckable) { attrs.$set('uncheckable', uncheckable ? '' : undefined); }); } } }; }]) .directive('uibBtnCheckbox', function() { return { require: ['uibBtnCheckbox', 'ngModel'], controller: 'UibButtonsController', controllerAs: 'button', link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; element.find('input').css({display: 'none'}); function getTrueValue() { return getCheckboxValue(attrs.btnCheckboxTrue, true); } function getFalseValue() { return getCheckboxValue(attrs.btnCheckboxFalse, false); } function getCheckboxValue(attribute, defaultValue) { return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue; } //model -> UI ngModelCtrl.$render = function() { element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); }; //ui->model element.on(buttonsCtrl.toggleEvent, function() { if (attrs.disabled) { return; } scope.$apply(function() { ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); ngModelCtrl.$render(); }); }); } }; }); angular.module('ui.bootstrap.carousel', []) .controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) { var self = this, slides = self.slides = $scope.slides = [], SLIDE_DIRECTION = 'uib-slideDirection', currentIndex = $scope.active, currentInterval, isPlaying; var destroyed = false; $element.addClass('carousel'); self.addSlide = function(slide, element) { slides.push({ slide: slide, element: element }); slides.sort(function(a, b) { return +a.slide.index - +b.slide.index; }); //if this is the first slide or the slide is set to active, select it if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) { if ($scope.$currentTransition) { $scope.$currentTransition = null; } currentIndex = slide.index; $scope.active = slide.index; setActive(currentIndex); self.select(slides[findSlideIndex(slide)]); if (slides.length === 1) { $scope.play(); } } }; self.getCurrentIndex = function() { for (var i = 0; i < slides.length; i++) { if (slides[i].slide.index === currentIndex) { return i; } } }; self.next = $scope.next = function() { var newIndex = (self.getCurrentIndex() + 1) % slides.length; if (newIndex === 0 && $scope.noWrap()) { $scope.pause(); return; } return self.select(slides[newIndex], 'next'); }; self.prev = $scope.prev = function() { var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; if ($scope.noWrap() && newIndex === slides.length - 1) { $scope.pause(); return; } return self.select(slides[newIndex], 'prev'); }; self.removeSlide = function(slide) { var index = findSlideIndex(slide); //get the index of the slide inside the carousel slides.splice(index, 1); if (slides.length > 0 && currentIndex === index) { if (index >= slides.length) { currentIndex = slides.length - 1; $scope.active = currentIndex; setActive(currentIndex); self.select(slides[slides.length - 1]); } else { currentIndex = index; $scope.active = currentIndex; setActive(currentIndex); self.select(slides[index]); } } else if (currentIndex > index) { currentIndex--; $scope.active = currentIndex; } //clean the active value when no more slide if (slides.length === 0) { currentIndex = null; $scope.active = null; } }; /* direction: "prev" or "next" */ self.select = $scope.select = function(nextSlide, direction) { var nextIndex = findSlideIndex(nextSlide.slide); //Decide direction if it's not given if (direction === undefined) { direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; } //Prevent this user-triggered transition from occurring if there is already one in progress if (nextSlide.slide.index !== currentIndex && !$scope.$currentTransition) { goNext(nextSlide.slide, nextIndex, direction); } }; /* Allow outside people to call indexOf on slides array */ $scope.indexOfSlide = function(slide) { return +slide.slide.index; }; $scope.isActive = function(slide) { return $scope.active === slide.slide.index; }; $scope.isPrevDisabled = function() { return $scope.active === 0 && $scope.noWrap(); }; $scope.isNextDisabled = function() { return $scope.active === slides.length - 1 && $scope.noWrap(); }; $scope.pause = function() { if (!$scope.noPause) { isPlaying = false; resetTimer(); } }; $scope.play = function() { if (!isPlaying) { isPlaying = true; restartTimer(); } }; $element.on('mouseenter', $scope.pause); $element.on('mouseleave', $scope.play); $scope.$on('$destroy', function() { destroyed = true; resetTimer(); }); $scope.$watch('noTransition', function(noTransition) { $animate.enabled($element, !noTransition); }); $scope.$watch('interval', restartTimer); $scope.$watchCollection('slides', resetTransition); $scope.$watch('active', function(index) { if (angular.isNumber(index) && currentIndex !== index) { for (var i = 0; i < slides.length; i++) { if (slides[i].slide.index === index) { index = i; break; } } var slide = slides[index]; if (slide) { setActive(index); self.select(slides[index]); currentIndex = index; } } }); function getSlideByIndex(index) { for (var i = 0, l = slides.length; i < l; ++i) { if (slides[i].index === index) { return slides[i]; } } } function setActive(index) { for (var i = 0; i < slides.length; i++) { slides[i].slide.active = i === index; } } function goNext(slide, index, direction) { if (destroyed) { return; } angular.extend(slide, {direction: direction}); angular.extend(slides[currentIndex].slide || {}, {direction: direction}); if ($animate.enabled($element) && !$scope.$currentTransition && slides[index].element && self.slides.length > 1) { slides[index].element.data(SLIDE_DIRECTION, slide.direction); var currentIdx = self.getCurrentIndex(); if (angular.isNumber(currentIdx) && slides[currentIdx].element) { slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction); } $scope.$currentTransition = true; $animate.on('addClass', slides[index].element, function(element, phase) { if (phase === 'close') { $scope.$currentTransition = null; $animate.off('addClass', element); } }); } $scope.active = slide.index; currentIndex = slide.index; setActive(index); //every time you change slides, reset the timer restartTimer(); } function findSlideIndex(slide) { for (var i = 0; i < slides.length; i++) { if (slides[i].slide === slide) { return i; } } } function resetTimer() { if (currentInterval) { $interval.cancel(currentInterval); currentInterval = null; } } function resetTransition(slides) { if (!slides.length) { $scope.$currentTransition = null; } } function restartTimer() { resetTimer(); var interval = +$scope.interval; if (!isNaN(interval) && interval > 0) { currentInterval = $interval(timerFn, interval); } } function timerFn() { var interval = +$scope.interval; if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) { $scope.next(); } else { $scope.pause(); } } }]) .directive('uibCarousel', function() { return { transclude: true, controller: 'UibCarouselController', controllerAs: 'carousel', restrict: 'A', templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/carousel/carousel.html'; }, scope: { active: '=', interval: '=', noTransition: '=', noPause: '=', noWrap: '&' } }; }) .directive('uibSlide', ['$animate', function($animate) { return { require: '^uibCarousel', restrict: 'A', transclude: true, templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/carousel/slide.html'; }, scope: { actual: '=?', index: '=?' }, link: function (scope, element, attrs, carouselCtrl) { element.addClass('item'); carouselCtrl.addSlide(scope, element); //when the scope is destroyed then remove the slide from the current slides array scope.$on('$destroy', function() { carouselCtrl.removeSlide(scope); }); scope.$watch('active', function(active) { $animate[active ? 'addClass' : 'removeClass'](element, 'active'); }); } }; }]) .animation('.item', ['$animateCss', function($animateCss) { var SLIDE_DIRECTION = 'uib-slideDirection'; function removeClass(element, className, callback) { element.removeClass(className); if (callback) { callback(); } } return { beforeAddClass: function(element, className, done) { if (className === 'active') { var stopped = false; var direction = element.data(SLIDE_DIRECTION); var directionClass = direction === 'next' ? 'left' : 'right'; var removeClassFn = removeClass.bind(this, element, directionClass + ' ' + direction, done); element.addClass(direction); $animateCss(element, {addClass: directionClass}) .start() .done(removeClassFn); return function() { stopped = true; }; } done(); }, beforeRemoveClass: function (element, className, done) { if (className === 'active') { var stopped = false; var direction = element.data(SLIDE_DIRECTION); var directionClass = direction === 'next' ? 'left' : 'right'; var removeClassFn = removeClass.bind(this, element, directionClass, done); $animateCss(element, {addClass: directionClass}) .start() .done(removeClassFn); return function() { stopped = true; }; } done(); } }; }]); angular.module('ui.bootstrap.dateparser', []) .service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) { // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; var localeId; var formatCodeToRegex; this.init = function() { localeId = $locale.id; this.parsers = {}; this.formatters = {}; formatCodeToRegex = [ { key: 'yyyy', regex: '\\d{4}', apply: function(value) { this.year = +value; }, formatter: function(date) { var _date = new Date(); _date.setFullYear(Math.abs(date.getFullYear())); return dateFilter(_date, 'yyyy'); } }, { key: 'yy', regex: '\\d{2}', apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; }, formatter: function(date) { var _date = new Date(); _date.setFullYear(Math.abs(date.getFullYear())); return dateFilter(_date, 'yy'); } }, { key: 'y', regex: '\\d{1,4}', apply: function(value) { this.year = +value; }, formatter: function(date) { var _date = new Date(); _date.setFullYear(Math.abs(date.getFullYear())); return dateFilter(_date, 'y'); } }, { key: 'M!', regex: '0?[1-9]|1[0-2]', apply: function(value) { this.month = value - 1; }, formatter: function(date) { var value = date.getMonth(); if (/^[0-9]$/.test(value)) { return dateFilter(date, 'MM'); } return dateFilter(date, 'M'); } }, { key: 'MMMM', regex: $locale.DATETIME_FORMATS.MONTH.join('|'), apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, formatter: function(date) { return dateFilter(date, 'MMMM'); } }, { key: 'MMM', regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, formatter: function(date) { return dateFilter(date, 'MMM'); } }, { key: 'MM', regex: '0[1-9]|1[0-2]', apply: function(value) { this.month = value - 1; }, formatter: function(date) { return dateFilter(date, 'MM'); } }, { key: 'M', regex: '[1-9]|1[0-2]', apply: function(value) { this.month = value - 1; }, formatter: function(date) { return dateFilter(date, 'M'); } }, { key: 'd!', regex: '[0-2]?[0-9]{1}|3[0-1]{1}', apply: function(value) { this.date = +value; }, formatter: function(date) { var value = date.getDate(); if (/^[1-9]$/.test(value)) { return dateFilter(date, 'dd'); } return dateFilter(date, 'd'); } }, { key: 'dd', regex: '[0-2][0-9]{1}|3[0-1]{1}', apply: function(value) { this.date = +value; }, formatter: function(date) { return dateFilter(date, 'dd'); } }, { key: 'd', regex: '[1-2]?[0-9]{1}|3[0-1]{1}', apply: function(value) { this.date = +value; }, formatter: function(date) { return dateFilter(date, 'd'); } }, { key: 'EEEE', regex: $locale.DATETIME_FORMATS.DAY.join('|'), formatter: function(date) { return dateFilter(date, 'EEEE'); } }, { key: 'EEE', regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), formatter: function(date) { return dateFilter(date, 'EEE'); } }, { key: 'HH', regex: '(?:0|1)[0-9]|2[0-3]', apply: function(value) { this.hours = +value; }, formatter: function(date) { return dateFilter(date, 'HH'); } }, { key: 'hh', regex: '0[0-9]|1[0-2]', apply: function(value) { this.hours = +value; }, formatter: function(date) { return dateFilter(date, 'hh'); } }, { key: 'H', regex: '1?[0-9]|2[0-3]', apply: function(value) { this.hours = +value; }, formatter: function(date) { return dateFilter(date, 'H'); } }, { key: 'h', regex: '[0-9]|1[0-2]', apply: function(value) { this.hours = +value; }, formatter: function(date) { return dateFilter(date, 'h'); } }, { key: 'mm', regex: '[0-5][0-9]', apply: function(value) { this.minutes = +value; }, formatter: function(date) { return dateFilter(date, 'mm'); } }, { key: 'm', regex: '[0-9]|[1-5][0-9]', apply: function(value) { this.minutes = +value; }, formatter: function(date) { return dateFilter(date, 'm'); } }, { key: 'sss', regex: '[0-9][0-9][0-9]', apply: function(value) { this.milliseconds = +value; }, formatter: function(date) { return dateFilter(date, 'sss'); } }, { key: 'ss', regex: '[0-5][0-9]', apply: function(value) { this.seconds = +value; }, formatter: function(date) { return dateFilter(date, 'ss'); } }, { key: 's', regex: '[0-9]|[1-5][0-9]', apply: function(value) { this.seconds = +value; }, formatter: function(date) { return dateFilter(date, 's'); } }, { key: 'a', regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), apply: function(value) { if (this.hours === 12) { this.hours = 0; } if (value === 'PM') { this.hours += 12; } }, formatter: function(date) { return dateFilter(date, 'a'); } }, { key: 'Z', regex: '[+-]\\d{4}', apply: function(value) { var matches = value.match(/([+-])(\d{2})(\d{2})/), sign = matches[1], hours = matches[2], minutes = matches[3]; this.hours += toInt(sign + hours); this.minutes += toInt(sign + minutes); }, formatter: function(date) { return dateFilter(date, 'Z'); } }, { key: 'ww', regex: '[0-4][0-9]|5[0-3]', formatter: function(date) { return dateFilter(date, 'ww'); } }, { key: 'w', regex: '[0-9]|[1-4][0-9]|5[0-3]', formatter: function(date) { return dateFilter(date, 'w'); } }, { key: 'GGGG', regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), formatter: function(date) { return dateFilter(date, 'GGGG'); } }, { key: 'GGG', regex: $locale.DATETIME_FORMATS.ERAS.join('|'), formatter: function(date) { return dateFilter(date, 'GGG'); } }, { key: 'GG', regex: $locale.DATETIME_FORMATS.ERAS.join('|'), formatter: function(date) { return dateFilter(date, 'GG'); } }, { key: 'G', regex: $locale.DATETIME_FORMATS.ERAS.join('|'), formatter: function(date) { return dateFilter(date, 'G'); } } ]; if (angular.version.major >= 1 && angular.version.minor > 4) { formatCodeToRegex.push({ key: 'LLLL', regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'), apply: function(value) { this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value); }, formatter: function(date) { return dateFilter(date, 'LLLL'); } }); } }; this.init(); function getFormatCodeToRegex(key) { return filterFilter(formatCodeToRegex, {key: key}, true)[0]; } this.getParser = function (key) { var f = getFormatCodeToRegex(key); return f && f.apply || null; }; this.overrideParser = function (key, parser) { var f = getFormatCodeToRegex(key); if (f && angular.isFunction(parser)) { this.parsers = {}; f.apply = parser; } }.bind(this); function createParser(format) { var map = [], regex = format.split(''); // check for literal values var quoteIndex = format.indexOf('\''); if (quoteIndex > -1) { var inLiteral = false; format = format.split(''); for (var i = quoteIndex; i < format.length; i++) { if (inLiteral) { if (format[i] === '\'') { if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote format[i+1] = '$'; regex[i+1] = ''; } else { // end of literal regex[i] = ''; inLiteral = false; } } format[i] = '$'; } else { if (format[i] === '\'') { // start of literal format[i] = '$'; regex[i] = ''; inLiteral = true; } } } format = format.join(''); } angular.forEach(formatCodeToRegex, function(data) { var index = format.indexOf(data.key); if (index > -1) { format = format.split(''); regex[index] = '(' + data.regex + ')'; format[index] = '$'; // Custom symbol to define consumed part of format for (var i = index + 1, n = index + data.key.length; i < n; i++) { regex[i] = ''; format[i] = '$'; } format = format.join(''); map.push({ index: index, key: data.key, apply: data.apply, matcher: data.regex }); } }); return { regex: new RegExp('^' + regex.join('') + '$'), map: orderByFilter(map, 'index') }; } function createFormatter(format) { var formatters = []; var i = 0; var formatter, literalIdx; while (i < format.length) { if (angular.isNumber(literalIdx)) { if (format.charAt(i) === '\'') { if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') { formatters.push(constructLiteralFormatter(format, literalIdx, i)); literalIdx = null; } } else if (i === format.length) { while (literalIdx < format.length) { formatter = constructFormatterFromIdx(format, literalIdx); formatters.push(formatter); literalIdx = formatter.endIdx; } } i++; continue; } if (format.charAt(i) === '\'') { literalIdx = i; i++; continue; } formatter = constructFormatterFromIdx(format, i); formatters.push(formatter.parser); i = formatter.endIdx; } return formatters; } function constructLiteralFormatter(format, literalIdx, endIdx) { return function() { return format.substr(literalIdx + 1, endIdx - literalIdx - 1); }; } function constructFormatterFromIdx(format, i) { var currentPosStr = format.substr(i); for (var j = 0; j < formatCodeToRegex.length; j++) { if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) { var data = formatCodeToRegex[j]; return { endIdx: i + data.key.length, parser: data.formatter }; } } return { endIdx: i + 1, parser: function() { return currentPosStr.charAt(0); } }; } this.filter = function(date, format) { if (!angular.isDate(date) || isNaN(date) || !format) { return ''; } format = $locale.DATETIME_FORMATS[format] || format; if ($locale.id !== localeId) { this.init(); } if (!this.formatters[format]) { this.formatters[format] = createFormatter(format); } var formatters = this.formatters[format]; return formatters.reduce(function(str, formatter) { return str + formatter(date); }, ''); }; this.parse = function(input, format, baseDate) { if (!angular.isString(input) || !format) { return input; } format = $locale.DATETIME_FORMATS[format] || format; format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); if ($locale.id !== localeId) { this.init(); } if (!this.parsers[format]) { this.parsers[format] = createParser(format, 'apply'); } var parser = this.parsers[format], regex = parser.regex, map = parser.map, results = input.match(regex), tzOffset = false; if (results && results.length) { var fields, dt; if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { fields = { year: baseDate.getFullYear(), month: baseDate.getMonth(), date: baseDate.getDate(), hours: baseDate.getHours(), minutes: baseDate.getMinutes(), seconds: baseDate.getSeconds(), milliseconds: baseDate.getMilliseconds() }; } else { if (baseDate) { $log.warn('dateparser:', 'baseDate is not a valid date'); } fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; } for (var i = 1, n = results.length; i < n; i++) { var mapper = map[i - 1]; if (mapper.matcher === 'Z') { tzOffset = true; } if (mapper.apply) { mapper.apply.call(fields, results[i]); } } var datesetter = tzOffset ? Date.prototype.setUTCFullYear : Date.prototype.setFullYear; var timesetter = tzOffset ? Date.prototype.setUTCHours : Date.prototype.setHours; if (isValid(fields.year, fields.month, fields.date)) { if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) { dt = new Date(baseDate); datesetter.call(dt, fields.year, fields.month, fields.date); timesetter.call(dt, fields.hours, fields.minutes, fields.seconds, fields.milliseconds); } else { dt = new Date(0); datesetter.call(dt, fields.year, fields.month, fields.date); timesetter.call(dt, fields.hours || 0, fields.minutes || 0, fields.seconds || 0, fields.milliseconds || 0); } } return dt; } }; // Check if date is valid for specific month (and year for February). // Month: 0 = Jan, 1 = Feb, etc function isValid(year, month, date) { if (date < 1) { return false; } if (month === 1 && date > 28) { return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0); } if (month === 3 || month === 5 || month === 8 || month === 10) { return date < 31; } return true; } function toInt(str) { return parseInt(str, 10); } this.toTimezone = toTimezone; this.fromTimezone = fromTimezone; this.timezoneToOffset = timezoneToOffset; this.addDateMinutes = addDateMinutes; this.convertTimezoneToLocal = convertTimezoneToLocal; function toTimezone(date, timezone) { return date && timezone ? convertTimezoneToLocal(date, timezone) : date; } function fromTimezone(date, timezone) { return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; } //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207 function timezoneToOffset(timezone, fallback) { timezone = timezone.replace(/:/g, ''); var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; } function addDateMinutes(date, minutes) { date = new Date(date.getTime()); date.setMinutes(date.getMinutes() + minutes); return date; } function convertTimezoneToLocal(date, timezone, reverse) { reverse = reverse ? -1 : 1; var dateTimezoneOffset = date.getTimezoneOffset(); var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); } }]); // Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to // at most one element. angular.module('ui.bootstrap.isClass', []) .directive('uibIsClass', [ '$animate', function ($animate) { // 11111111 22222222 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/; // 11111111 22222222 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/; var dataPerTracked = {}; return { restrict: 'A', compile: function(tElement, tAttrs) { var linkedScopes = []; var instances = []; var expToData = {}; var lastActivated = null; var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP); var onExp = onExpMatches[2]; var expsStr = onExpMatches[1]; var exps = expsStr.split(','); return linkFn; function linkFn(scope, element, attrs) { linkedScopes.push(scope); instances.push({ scope: scope, element: element }); exps.forEach(function(exp, k) { addForExp(exp, scope); }); scope.$on('$destroy', removeScope); } function addForExp(exp, scope) { var matches = exp.match(IS_REGEXP); var clazz = scope.$eval(matches[1]); var compareWithExp = matches[2]; var data = expToData[exp]; if (!data) { var watchFn = function(compareWithVal) { var newActivated = null; instances.some(function(instance) { var thisVal = instance.scope.$eval(onExp); if (thisVal === compareWithVal) { newActivated = instance; return true; } }); if (data.lastActivated !== newActivated) { if (data.lastActivated) { $animate.removeClass(data.lastActivated.element, clazz); } if (newActivated) { $animate.addClass(newActivated.element, clazz); } data.lastActivated = newActivated; } }; expToData[exp] = data = { lastActivated: null, scope: scope, watchFn: watchFn, compareWithExp: compareWithExp, watcher: scope.$watch(compareWithExp, watchFn) }; } data.watchFn(scope.$eval(compareWithExp)); } function removeScope(e) { var removedScope = e.targetScope; var index = linkedScopes.indexOf(removedScope); linkedScopes.splice(index, 1); instances.splice(index, 1); if (linkedScopes.length) { var newWatchScope = linkedScopes[0]; angular.forEach(expToData, function(data) { if (data.scope === removedScope) { data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn); data.scope = newWatchScope; } }); } else { expToData = {}; } } } }; }]); angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass']) .value('$datepickerSuppressError', false) .value('$datepickerLiteralWarning', true) .constant('uibDatepickerConfig', { datepickerMode: 'day', formatDay: 'dd', formatMonth: 'MMMM', formatYear: 'yyyy', formatDayHeader: 'EEE', formatDayTitle: 'MMMM yyyy', formatMonthTitle: 'yyyy', maxDate: null, maxMode: 'year', minDate: null, minMode: 'day', monthColumns: 3, ngModelOptions: {}, shortcutPropagation: false, showWeeks: true, yearColumns: 5, yearRows: 4 }) .controller('UibDatepickerController', ['$scope', '$element', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser', function($scope, $element, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl; ngModelOptions = {}, watchListeners = []; $element.addClass('uib-datepicker'); $attrs.$set('role', 'application'); if (!$scope.datepickerOptions) { $scope.datepickerOptions = {}; } // Modes chain this.modes = ['day', 'month', 'year']; [ 'customClass', 'dateDisabled', 'datepickerMode', 'formatDay', 'formatDayHeader', 'formatDayTitle', 'formatMonth', 'formatMonthTitle', 'formatYear', 'maxDate', 'maxMode', 'minDate', 'minMode', 'monthColumns', 'showWeeks', 'shortcutPropagation', 'startingDay', 'yearColumns', 'yearRows' ].forEach(function(key) { switch (key) { case 'customClass': case 'dateDisabled': $scope[key] = $scope.datepickerOptions[key] || angular.noop; break; case 'datepickerMode': $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ? $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode; break; case 'formatDay': case 'formatDayHeader': case 'formatDayTitle': case 'formatMonth': case 'formatMonthTitle': case 'formatYear': self[key] = angular.isDefined($scope.datepickerOptions[key]) ? $interpolate($scope.datepickerOptions[key])($scope.$parent) : datepickerConfig[key]; break; case 'monthColumns': case 'showWeeks': case 'shortcutPropagation': case 'yearColumns': case 'yearRows': self[key] = angular.isDefined($scope.datepickerOptions[key]) ? $scope.datepickerOptions[key] : datepickerConfig[key]; break; case 'startingDay': if (angular.isDefined($scope.datepickerOptions.startingDay)) { self.startingDay = $scope.datepickerOptions.startingDay; } else if (angular.isNumber(datepickerConfig.startingDay)) { self.startingDay = datepickerConfig.startingDay; } else { self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; } break; case 'maxDate': case 'minDate': $scope.$watch('datepickerOptions.' + key, function(value) { if (value) { if (angular.isDate(value)) { self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.getOption('timezone')); } else { if ($datepickerLiteralWarning) { $log.warn('Literal date support has been deprecated, please switch to date object usage'); } self[key] = new Date(dateFilter(value, 'medium')); } } else { self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.getOption('timezone')) : null; } self.refreshView(); }); break; case 'maxMode': case 'minMode': if ($scope.datepickerOptions[key]) { $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) { self[key] = $scope[key] = angular.isDefined(value) ? value : $scope.datepickerOptions[key]; if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) || key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) { $scope.datepickerMode = self[key]; $scope.datepickerOptions.datepickerMode = self[key]; } }); } else { self[key] = $scope[key] = datepickerConfig[key] || null; } break; } }); $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); $scope.disabled = angular.isDefined($attrs.disabled) || false; if (angular.isDefined($attrs.ngDisabled)) { watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) { $scope.disabled = disabled; self.refreshView(); })); } $scope.isActive = function(dateObject) { if (self.compare(dateObject.date, self.activeDate) === 0) { $scope.activeDateId = dateObject.uid; return true; } return false; }; this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelOptions = extractOptions(ngModelCtrl); if ($scope.datepickerOptions.initDate) { self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date(); $scope.$watch('datepickerOptions.initDate', function(initDate) { if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone')); self.refreshView(); } }); } else { self.activeDate = new Date(); } var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date(); this.activeDate = !isNaN(date) ? dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) : dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone')); ngModelCtrl.$render = function() { self.render(); }; }; this.render = function() { if (ngModelCtrl.$viewValue) { var date = new Date(ngModelCtrl.$viewValue), isValid = !isNaN(date); if (isValid) { this.activeDate = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')); } else if (!$datepickerSuppressError) { $log.error('Datepicker directive: "ng-model" value must be a Date object'); } } this.refreshView(); }; this.refreshView = function() { if (this.element) { $scope.selectedDt = null; this._refreshView(); if ($scope.activeDt) { $scope.activeDateId = $scope.activeDt.uid; } var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; date = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')); ngModelCtrl.$setValidity('dateDisabled', !date || this.element && !this.isDisabled(date)); } }; this.createDateObject = function(date, format) { var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; model = dateParser.fromTimezone(model, ngModelOptions.getOption('timezone')); var today = new Date(); today = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone')); var time = this.compare(date, today); var dt = { date: date, label: dateParser.filter(date, format), selected: model && this.compare(date, model) === 0, disabled: this.isDisabled(date), past: time < 0, current: time === 0, future: time > 0, customClass: this.customClass(date) || null }; if (model && this.compare(date, model) === 0) { $scope.selectedDt = dt; } if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) { $scope.activeDt = dt; } return dt; }; this.isDisabled = function(date) { return $scope.disabled || this.minDate && this.compare(date, this.minDate) < 0 || this.maxDate && this.compare(date, this.maxDate) > 0 || $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}); }; this.customClass = function(date) { return $scope.customClass({date: date, mode: $scope.datepickerMode}); }; // Split array into smaller arrays this.split = function(arr, size) { var arrays = []; while (arr.length > 0) { arrays.push(arr.splice(0, size)); } return arrays; }; $scope.select = function(date) { if ($scope.datepickerMode === self.minMode) { var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.getOption('timezone')) : new Date(0, 0, 0, 0, 0, 0, 0); dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); dt = dateParser.toTimezone(dt, ngModelOptions.getOption('timezone')); ngModelCtrl.$setViewValue(dt); ngModelCtrl.$render(); } else { self.activeDate = date; setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]); $scope.$emit('uib:datepicker.mode'); } $scope.$broadcast('uib:datepicker.focus'); }; $scope.move = function(direction) { var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), month = self.activeDate.getMonth() + direction * (self.step.months || 0); self.activeDate.setFullYear(year, month, 1); self.refreshView(); }; $scope.toggleMode = function(direction) { direction = direction || 1; if ($scope.datepickerMode === self.maxMode && direction === 1 || $scope.datepickerMode === self.minMode && direction === -1) { return; } setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]); $scope.$emit('uib:datepicker.mode'); }; // Key event mapper $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; var focusElement = function() { self.element[0].focus(); }; // Listen for focus requests from popup directive $scope.$on('uib:datepicker.focus', focusElement); $scope.keydown = function(evt) { var key = $scope.keys[evt.which]; if (!key || evt.shiftKey || evt.altKey || $scope.disabled) { return; } evt.preventDefault(); if (!self.shortcutPropagation) { evt.stopPropagation(); } if (key === 'enter' || key === 'space') { if (self.isDisabled(self.activeDate)) { return; // do nothing } $scope.select(self.activeDate); } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { $scope.toggleMode(key === 'up' ? 1 : -1); } else { self.handleKeyDown(key, evt); self.refreshView(); } }; $element.on('keydown', function(evt) { $scope.$apply(function() { $scope.keydown(evt); }); }); $scope.$on('$destroy', function() { //Clear all watch listeners on destroy while (watchListeners.length) { watchListeners.shift()(); } }); function setMode(mode) { $scope.datepickerMode = mode; $scope.datepickerOptions.datepickerMode = mode; } function extractOptions(ngModelCtrl) { var ngModelOptions; if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing // guarantee a value ngModelOptions = ngModelCtrl.$options || $scope.datepickerOptions.ngModelOptions || datepickerConfig.ngModelOptions || {}; // mimic 1.6+ api ngModelOptions.getOption = function (key) { return ngModelOptions[key]; }; } else { // in angular >=1.6 $options is always present // ng-model-options defaults timezone to null; don't let its precedence squash a non-null value var timezone = ngModelCtrl.$options.getOption('timezone') || ($scope.datepickerOptions.ngModelOptions ? $scope.datepickerOptions.ngModelOptions.timezone : null) || (datepickerConfig.ngModelOptions ? datepickerConfig.ngModelOptions.timezone : null); // values passed to createChild override existing values ngModelOptions = ngModelCtrl.$options // start with a ModelOptions instance .createChild(datepickerConfig.ngModelOptions) // lowest precedence .createChild($scope.datepickerOptions.ngModelOptions) .createChild(ngModelCtrl.$options) // highest precedence .createChild({timezone: timezone}); // to keep from squashing a non-null value } return ngModelOptions; } }]) .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; this.step = { months: 1 }; this.element = $element; function getDaysInMonth(year, month) { return month === 1 && year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month]; } this.init = function(ctrl) { angular.extend(ctrl, this); scope.showWeeks = ctrl.showWeeks; ctrl.refreshView(); }; this.getDates = function(startDate, n) { var dates = new Array(n), current = new Date(startDate), i = 0, date; while (i < n) { date = new Date(current); dates[i++] = date; current.setDate(current.getDate() + 1); } return dates; }; this._refreshView = function() { var year = this.activeDate.getFullYear(), month = this.activeDate.getMonth(), firstDayOfMonth = new Date(this.activeDate); firstDayOfMonth.setFullYear(year, month, 1); var difference = this.startingDay - firstDayOfMonth.getDay(), numDisplayedFromPreviousMonth = difference > 0 ? 7 - difference : - difference, firstDate = new Date(firstDayOfMonth); if (numDisplayedFromPreviousMonth > 0) { firstDate.setDate(-numDisplayedFromPreviousMonth + 1); } // 42 is the number of days on a six-week calendar var days = this.getDates(firstDate, 42); for (var i = 0; i < 42; i ++) { days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), { secondary: days[i].getMonth() !== month, uid: scope.uniqueId + '-' + i }); } scope.labels = new Array(7); for (var j = 0; j < 7; j++) { scope.labels[j] = { abbr: dateFilter(days[j].date, this.formatDayHeader), full: dateFilter(days[j].date, 'EEEE') }; } scope.title = dateFilter(this.activeDate, this.formatDayTitle); scope.rows = this.split(days, 7); if (scope.showWeeks) { scope.weekNumbers = []; var thursdayIndex = (4 + 7 - this.startingDay) % 7, numWeeks = scope.rows.length; for (var curWeek = 0; curWeek < numWeeks; curWeek++) { scope.weekNumbers.push( getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); } } }; this.compare = function(date1, date2) { var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); _date1.setFullYear(date1.getFullYear()); _date2.setFullYear(date2.getFullYear()); return _date1 - _date2; }; function getISO8601WeekNumber(date) { var checkDate = new Date(date); checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday var time = checkDate.getTime(); checkDate.setMonth(0); // Compare with Jan 1 checkDate.setDate(1); return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; } this.handleKeyDown = function(key, evt) { var date = this.activeDate.getDate(); if (key === 'left') { date = date - 1; } else if (key === 'up') { date = date - 7; } else if (key === 'right') { date = date + 1; } else if (key === 'down') { date = date + 7; } else if (key === 'pageup' || key === 'pagedown') { var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); this.activeDate.setMonth(month, 1); date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date); } else if (key === 'home') { date = 1; } else if (key === 'end') { date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()); } this.activeDate.setDate(date); }; }]) .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { this.step = { years: 1 }; this.element = $element; this.init = function(ctrl) { angular.extend(ctrl, this); ctrl.refreshView(); }; this._refreshView = function() { var months = new Array(12), year = this.activeDate.getFullYear(), date; for (var i = 0; i < 12; i++) { date = new Date(this.activeDate); date.setFullYear(year, i, 1); months[i] = angular.extend(this.createDateObject(date, this.formatMonth), { uid: scope.uniqueId + '-' + i }); } scope.title = dateFilter(this.activeDate, this.formatMonthTitle); scope.rows = this.split(months, this.monthColumns); scope.yearHeaderColspan = this.monthColumns > 3 ? this.monthColumns - 2 : 1; }; this.compare = function(date1, date2) { var _date1 = new Date(date1.getFullYear(), date1.getMonth()); var _date2 = new Date(date2.getFullYear(), date2.getMonth()); _date1.setFullYear(date1.getFullYear()); _date2.setFullYear(date2.getFullYear()); return _date1 - _date2; }; this.handleKeyDown = function(key, evt) { var date = this.activeDate.getMonth(); if (key === 'left') { date = date - 1; } else if (key === 'up') { date = date - this.monthColumns; } else if (key === 'right') { date = date + 1; } else if (key === 'down') { date = date + this.monthColumns; } else if (key === 'pageup' || key === 'pagedown') { var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); this.activeDate.setFullYear(year); } else if (key === 'home') { date = 0; } else if (key === 'end') { date = 11; } this.activeDate.setMonth(date); }; }]) .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { var columns, range; this.element = $element; function getStartingYear(year) { return parseInt((year - 1) / range, 10) * range + 1; } this.yearpickerInit = function() { columns = this.yearColumns; range = this.yearRows * columns; this.step = { years: range }; }; this._refreshView = function() { var years = new Array(range), date; for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) { date = new Date(this.activeDate); date.setFullYear(start + i, 0, 1); years[i] = angular.extend(this.createDateObject(date, this.formatYear), { uid: scope.uniqueId + '-' + i }); } scope.title = [years[0].label, years[range - 1].label].join(' - '); scope.rows = this.split(years, columns); scope.columns = columns; }; this.compare = function(date1, date2) { return date1.getFullYear() - date2.getFullYear(); }; this.handleKeyDown = function(key, evt) { var date = this.activeDate.getFullYear(); if (key === 'left') { date = date - 1; } else if (key === 'up') { date = date - columns; } else if (key === 'right') { date = date + 1; } else if (key === 'down') { date = date + columns; } else if (key === 'pageup' || key === 'pagedown') { date += (key === 'pageup' ? - 1 : 1) * range; } else if (key === 'home') { date = getStartingYear(this.activeDate.getFullYear()); } else if (key === 'end') { date = getStartingYear(this.activeDate.getFullYear()) + range - 1; } this.activeDate.setFullYear(date); }; }]) .directive('uibDatepicker', function() { return { templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/datepicker/datepicker.html'; }, scope: { datepickerOptions: '=?' }, require: ['uibDatepicker', '^ngModel'], restrict: 'A', controller: 'UibDatepickerController', controllerAs: 'datepicker', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; datepickerCtrl.init(ngModelCtrl); } }; }) .directive('uibDaypicker', function() { return { templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/datepicker/day.html'; }, require: ['^uibDatepicker', 'uibDaypicker'], restrict: 'A', controller: 'UibDaypickerController', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], daypickerCtrl = ctrls[1]; daypickerCtrl.init(datepickerCtrl); } }; }) .directive('uibMonthpicker', function() { return { templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/datepicker/month.html'; }, require: ['^uibDatepicker', 'uibMonthpicker'], restrict: 'A', controller: 'UibMonthpickerController', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], monthpickerCtrl = ctrls[1]; monthpickerCtrl.init(datepickerCtrl); } }; }) .directive('uibYearpicker', function() { return { templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/datepicker/year.html'; }, require: ['^uibDatepicker', 'uibYearpicker'], restrict: 'A', controller: 'UibYearpickerController', link: function(scope, element, attrs, ctrls) { var ctrl = ctrls[0]; angular.extend(ctrl, ctrls[1]); ctrl.yearpickerInit(); ctrl.refreshView(); } }; }); angular.module('ui.bootstrap.position', []) /** * A set of utility methods for working with the DOM. * It is meant to be used where we need to absolute-position elements in * relation to another element (this is the case for tooltips, popovers, * typeahead suggestions etc.). */ .factory('$uibPosition', ['$document', '$window', function($document, $window) { /** * Used by scrollbarWidth() function to cache scrollbar's width. * Do not access this variable directly, use scrollbarWidth() instead. */ var SCROLLBAR_WIDTH; /** * scrollbar on body and html element in IE and Edge overlay * content and should be considered 0 width. */ var BODY_SCROLLBAR_WIDTH; var OVERFLOW_REGEX = { normal: /(auto|scroll)/, hidden: /(auto|scroll|hidden)/ }; var PLACEMENT_REGEX = { auto: /\s?auto?\s?/i, primary: /^(top|bottom|left|right)$/, secondary: /^(top|bottom|left|right|center)$/, vertical: /^(top|bottom)$/ }; var BODY_REGEX = /(HTML|BODY)/; return { /** * Provides a raw DOM element from a jQuery/jQLite element. * * @param {element} elem - The element to convert. * * @returns {element} A HTML element. */ getRawNode: function(elem) { return elem.nodeName ? elem : elem[0] || elem; }, /** * Provides a parsed number for a style property. Strips * units and casts invalid numbers to 0. * * @param {string} value - The style value to parse. * * @returns {number} A valid number. */ parseStyle: function(value) { value = parseFloat(value); return isFinite(value) ? value : 0; }, /** * Provides the closest positioned ancestor. * * @param {element} element - The element to get the offest parent for. * * @returns {element} The closest positioned ancestor. */ offsetParent: function(elem) { elem = this.getRawNode(elem); var offsetParent = elem.offsetParent || $document[0].documentElement; function isStaticPositioned(el) { return ($window.getComputedStyle(el).position || 'static') === 'static'; } while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) { offsetParent = offsetParent.offsetParent; } return offsetParent || $document[0].documentElement; }, /** * Provides the scrollbar width, concept from TWBS measureScrollbar() * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js * In IE and Edge, scollbar on body and html element overlay and should * return a width of 0. * * @returns {number} The width of the browser scollbar. */ scrollbarWidth: function(isBody) { if (isBody) { if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) { var bodyElem = $document.find('body'); bodyElem.addClass('uib-position-body-scrollbar-measure'); BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth; BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0; bodyElem.removeClass('uib-position-body-scrollbar-measure'); } return BODY_SCROLLBAR_WIDTH; } if (angular.isUndefined(SCROLLBAR_WIDTH)) { var scrollElem = angular.element('
    '); $document.find('body').append(scrollElem); SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth; SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0; scrollElem.remove(); } return SCROLLBAR_WIDTH; }, /** * Provides the padding required on an element to replace the scrollbar. * * @returns {object} An object with the following properties: *
      *
    • **scrollbarWidth**: the width of the scrollbar
    • *
    • **widthOverflow**: whether the the width is overflowing
    • *
    • **right**: the amount of right padding on the element needed to replace the scrollbar
    • *
    • **rightOriginal**: the amount of right padding currently on the element
    • *
    • **heightOverflow**: whether the the height is overflowing
    • *
    • **bottom**: the amount of bottom padding on the element needed to replace the scrollbar
    • *
    • **bottomOriginal**: the amount of bottom padding currently on the element
    • *
    */ scrollbarPadding: function(elem) { elem = this.getRawNode(elem); var elemStyle = $window.getComputedStyle(elem); var paddingRight = this.parseStyle(elemStyle.paddingRight); var paddingBottom = this.parseStyle(elemStyle.paddingBottom); var scrollParent = this.scrollParent(elem, false, true); var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName)); return { scrollbarWidth: scrollbarWidth, widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth, right: paddingRight + scrollbarWidth, originalRight: paddingRight, heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight, bottom: paddingBottom + scrollbarWidth, originalBottom: paddingBottom }; }, /** * Checks to see if the element is scrollable. * * @param {element} elem - The element to check. * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, * default is false. * * @returns {boolean} Whether the element is scrollable. */ isScrollable: function(elem, includeHidden) { elem = this.getRawNode(elem); var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; var elemStyle = $window.getComputedStyle(elem); return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX); }, /** * Provides the closest scrollable ancestor. * A port of the jQuery UI scrollParent method: * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js * * @param {element} elem - The element to find the scroll parent of. * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, * default is false. * @param {boolean=} [includeSelf=false] - Should the element being passed be * included in the scrollable llokup. * * @returns {element} A HTML element. */ scrollParent: function(elem, includeHidden, includeSelf) { elem = this.getRawNode(elem); var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; var documentEl = $document[0].documentElement; var elemStyle = $window.getComputedStyle(elem); if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) { return elem; } var excludeStatic = elemStyle.position === 'absolute'; var scrollParent = elem.parentElement || documentEl; if (scrollParent === documentEl || elemStyle.position === 'fixed') { return documentEl; } while (scrollParent.parentElement && scrollParent !== documentEl) { var spStyle = $window.getComputedStyle(scrollParent); if (excludeStatic && spStyle.position !== 'static') { excludeStatic = false; } if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) { break; } scrollParent = scrollParent.parentElement; } return scrollParent; }, /** * Provides read-only equivalent of jQuery's position function: * http://api.jquery.com/position/ - distance to closest positioned * ancestor. Does not account for margins by default like jQuery position. * * @param {element} elem - The element to caclulate the position on. * @param {boolean=} [includeMargins=false] - Should margins be accounted * for, default is false. * * @returns {object} An object with the following properties: *
      *
    • **width**: the width of the element
    • *
    • **height**: the height of the element
    • *
    • **top**: distance to top edge of offset parent
    • *
    • **left**: distance to left edge of offset parent
    • *
    */ position: function(elem, includeMagins) { elem = this.getRawNode(elem); var elemOffset = this.offset(elem); if (includeMagins) { var elemStyle = $window.getComputedStyle(elem); elemOffset.top -= this.parseStyle(elemStyle.marginTop); elemOffset.left -= this.parseStyle(elemStyle.marginLeft); } var parent = this.offsetParent(elem); var parentOffset = {top: 0, left: 0}; if (parent !== $document[0].documentElement) { parentOffset = this.offset(parent); parentOffset.top += parent.clientTop - parent.scrollTop; parentOffset.left += parent.clientLeft - parent.scrollLeft; } return { width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth), height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight), top: Math.round(elemOffset.top - parentOffset.top), left: Math.round(elemOffset.left - parentOffset.left) }; }, /** * Provides read-only equivalent of jQuery's offset function: * http://api.jquery.com/offset/ - distance to viewport. Does * not account for borders, margins, or padding on the body * element. * * @param {element} elem - The element to calculate the offset on. * * @returns {object} An object with the following properties: *
      *
    • **width**: the width of the element
    • *
    • **height**: the height of the element
    • *
    • **top**: distance to top edge of viewport
    • *
    • **right**: distance to bottom edge of viewport
    • *
    */ offset: function(elem) { elem = this.getRawNode(elem); var elemBCR = elem.getBoundingClientRect(); return { width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth), height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight), top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)), left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)) }; }, /** * Provides offset distance to the closest scrollable ancestor * or viewport. Accounts for border and scrollbar width. * * Right and bottom dimensions represent the distance to the * respective edge of the viewport element. If the element * edge extends beyond the viewport, a negative value will be * reported. * * @param {element} elem - The element to get the viewport offset for. * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead * of the first scrollable element, default is false. * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element * be accounted for, default is true. * * @returns {object} An object with the following properties: *
      *
    • **top**: distance to the top content edge of viewport element
    • *
    • **bottom**: distance to the bottom content edge of viewport element
    • *
    • **left**: distance to the left content edge of viewport element
    • *
    • **right**: distance to the right content edge of viewport element
    • *
    */ viewportOffset: function(elem, useDocument, includePadding) { elem = this.getRawNode(elem); includePadding = includePadding !== false ? true : false; var elemBCR = elem.getBoundingClientRect(); var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0}; var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem); var offsetParentBCR = offsetParent.getBoundingClientRect(); offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop; offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft; if (offsetParent === $document[0].documentElement) { offsetBCR.top += $window.pageYOffset; offsetBCR.left += $window.pageXOffset; } offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight; offsetBCR.right = offsetBCR.left + offsetParent.clientWidth; if (includePadding) { var offsetParentStyle = $window.getComputedStyle(offsetParent); offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop); offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom); offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft); offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight); } return { top: Math.round(elemBCR.top - offsetBCR.top), bottom: Math.round(offsetBCR.bottom - elemBCR.bottom), left: Math.round(elemBCR.left - offsetBCR.left), right: Math.round(offsetBCR.right - elemBCR.right) }; }, /** * Provides an array of placement values parsed from a placement string. * Along with the 'auto' indicator, supported placement strings are: *
      *
    • top: element on top, horizontally centered on host element.
    • *
    • top-left: element on top, left edge aligned with host element left edge.
    • *
    • top-right: element on top, lerightft edge aligned with host element right edge.
    • *
    • bottom: element on bottom, horizontally centered on host element.
    • *
    • bottom-left: element on bottom, left edge aligned with host element left edge.
    • *
    • bottom-right: element on bottom, right edge aligned with host element right edge.
    • *
    • left: element on left, vertically centered on host element.
    • *
    • left-top: element on left, top edge aligned with host element top edge.
    • *
    • left-bottom: element on left, bottom edge aligned with host element bottom edge.
    • *
    • right: element on right, vertically centered on host element.
    • *
    • right-top: element on right, top edge aligned with host element top edge.
    • *
    • right-bottom: element on right, bottom edge aligned with host element bottom edge.
    • *
    * A placement string with an 'auto' indicator is expected to be * space separated from the placement, i.e: 'auto bottom-left' If * the primary and secondary placement values do not match 'top, * bottom, left, right' then 'top' will be the primary placement and * 'center' will be the secondary placement. If 'auto' is passed, true * will be returned as the 3rd value of the array. * * @param {string} placement - The placement string to parse. * * @returns {array} An array with the following values *
      *
    • **[0]**: The primary placement.
    • *
    • **[1]**: The secondary placement.
    • *
    • **[2]**: If auto is passed: true, else undefined.
    • *
    */ parsePlacement: function(placement) { var autoPlace = PLACEMENT_REGEX.auto.test(placement); if (autoPlace) { placement = placement.replace(PLACEMENT_REGEX.auto, ''); } placement = placement.split('-'); placement[0] = placement[0] || 'top'; if (!PLACEMENT_REGEX.primary.test(placement[0])) { placement[0] = 'top'; } placement[1] = placement[1] || 'center'; if (!PLACEMENT_REGEX.secondary.test(placement[1])) { placement[1] = 'center'; } if (autoPlace) { placement[2] = true; } else { placement[2] = false; } return placement; }, /** * Provides coordinates for an element to be positioned relative to * another element. Passing 'auto' as part of the placement parameter * will enable smart placement - where the element fits. i.e: * 'auto left-top' will check to see if there is enough space to the left * of the hostElem to fit the targetElem, if not place right (same for secondary * top placement). Available space is calculated using the viewportOffset * function. * * @param {element} hostElem - The element to position against. * @param {element} targetElem - The element to position. * @param {string=} [placement=top] - The placement for the targetElem, * default is 'top'. 'center' is assumed as secondary placement for * 'top', 'left', 'right', and 'bottom' placements. Available placements are: *
      *
    • top
    • *
    • top-right
    • *
    • top-left
    • *
    • bottom
    • *
    • bottom-left
    • *
    • bottom-right
    • *
    • left
    • *
    • left-top
    • *
    • left-bottom
    • *
    • right
    • *
    • right-top
    • *
    • right-bottom
    • *
    * @param {boolean=} [appendToBody=false] - Should the top and left values returned * be calculated from the body element, default is false. * * @returns {object} An object with the following properties: *
      *
    • **top**: Value for targetElem top.
    • *
    • **left**: Value for targetElem left.
    • *
    • **placement**: The resolved placement.
    • *
    */ positionElements: function(hostElem, targetElem, placement, appendToBody) { hostElem = this.getRawNode(hostElem); targetElem = this.getRawNode(targetElem); // need to read from prop to support tests. var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth'); var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight'); placement = this.parsePlacement(placement); var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem); var targetElemPos = {top: 0, left: 0, placement: ''}; if (placement[2]) { var viewportOffset = this.viewportOffset(hostElem, appendToBody); var targetElemStyle = $window.getComputedStyle(targetElem); var adjustedSize = { width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))), height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom))) }; placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' : placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' : placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' : placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' : placement[0]; placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' : placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' : placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' : placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' : placement[1]; if (placement[1] === 'center') { if (PLACEMENT_REGEX.vertical.test(placement[0])) { var xOverflow = hostElemPos.width / 2 - targetWidth / 2; if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) { placement[1] = 'left'; } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) { placement[1] = 'right'; } } else { var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2; if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) { placement[1] = 'top'; } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) { placement[1] = 'bottom'; } } } } switch (placement[0]) { case 'top': targetElemPos.top = hostElemPos.top - targetHeight; break; case 'bottom': targetElemPos.top = hostElemPos.top + hostElemPos.height; break; case 'left': targetElemPos.left = hostElemPos.left - targetWidth; break; case 'right': targetElemPos.left = hostElemPos.left + hostElemPos.width; break; } switch (placement[1]) { case 'top': targetElemPos.top = hostElemPos.top; break; case 'bottom': targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight; break; case 'left': targetElemPos.left = hostElemPos.left; break; case 'right': targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth; break; case 'center': if (PLACEMENT_REGEX.vertical.test(placement[0])) { targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2; } else { targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2; } break; } targetElemPos.top = Math.round(targetElemPos.top); targetElemPos.left = Math.round(targetElemPos.left); targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1]; return targetElemPos; }, /** * Provides a way to adjust the top positioning after first * render to correctly align element to top after content * rendering causes resized element height * * @param {array} placementClasses - The array of strings of classes * element should have. * @param {object} containerPosition - The object with container * position information * @param {number} initialHeight - The initial height for the elem. * @param {number} currentHeight - The current height for the elem. */ adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) { if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) { return { top: containerPosition.top - currentHeight + 'px' }; } }, /** * Provides a way for positioning tooltip & dropdown * arrows when using placement options beyond the standard * left, right, top, or bottom. * * @param {element} elem - The tooltip/dropdown element. * @param {string} placement - The placement for the elem. */ positionArrow: function(elem, placement) { elem = this.getRawNode(elem); var innerElem = elem.querySelector('.tooltip-inner, .popover-inner'); if (!innerElem) { return; } var isTooltip = angular.element(innerElem).hasClass('tooltip-inner'); var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow'); if (!arrowElem) { return; } var arrowCss = { top: '', bottom: '', left: '', right: '' }; placement = this.parsePlacement(placement); if (placement[1] === 'center') { // no adjustment necessary - just reset styles angular.element(arrowElem).css(arrowCss); return; } var borderProp = 'border-' + placement[0] + '-width'; var borderWidth = $window.getComputedStyle(arrowElem)[borderProp]; var borderRadiusProp = 'border-'; if (PLACEMENT_REGEX.vertical.test(placement[0])) { borderRadiusProp += placement[0] + '-' + placement[1]; } else { borderRadiusProp += placement[1] + '-' + placement[0]; } borderRadiusProp += '-radius'; var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp]; switch (placement[0]) { case 'top': arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth; break; case 'bottom': arrowCss.top = isTooltip ? '0' : '-' + borderWidth; break; case 'left': arrowCss.right = isTooltip ? '0' : '-' + borderWidth; break; case 'right': arrowCss.left = isTooltip ? '0' : '-' + borderWidth; break; } arrowCss[placement[1]] = borderRadius; angular.element(arrowElem).css(arrowCss); } }; }]); angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position']) .value('$datepickerPopupLiteralWarning', true) .constant('uibDatepickerPopupConfig', { altInputFormats: [], appendToBody: false, clearText: 'Clear', closeOnDateSelection: true, closeText: 'Done', currentText: 'Today', datepickerPopup: 'yyyy-MM-dd', datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html', datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html', html5Types: { date: 'yyyy-MM-dd', 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', 'month': 'yyyy-MM' }, onOpenFocus: true, showButtonBar: true, placement: 'auto bottom-left' }) .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning', function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) { var cache = {}, isHtml5DateInput = false; var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = []; this.init = function(_ngModel_) { ngModel = _ngModel_; ngModelOptions = extractOptions(ngModel); closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? $scope.$parent.$eval($attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? $scope.$parent.$eval($attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? $attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl; datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; altInputFormats = angular.isDefined($attrs.altInputFormats) ? $scope.$parent.$eval($attrs.altInputFormats) : datepickerPopupConfig.altInputFormats; $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? $scope.$parent.$eval($attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; if (datepickerPopupConfig.html5Types[$attrs.type]) { dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; isHtml5DateInput = true; } else { dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; $attrs.$observe('uibDatepickerPopup', function(value, oldValue) { var newDateFormat = value || datepickerPopupConfig.datepickerPopup; // Invalidate the $modelValue to ensure that formatters re-run // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 if (newDateFormat !== dateFormat) { dateFormat = newDateFormat; ngModel.$modelValue = null; if (!dateFormat) { throw new Error('uibDatepickerPopup must have a date format specified.'); } } }); } if (!dateFormat) { throw new Error('uibDatepickerPopup must have a date format specified.'); } if (isHtml5DateInput && $attrs.uibDatepickerPopup) { throw new Error('HTML5 date input types do not support custom formats.'); } // popup element used to display calendar popupEl = angular.element('
    '); popupEl.attr({ 'ng-model': 'date', 'ng-change': 'dateSelection(date)', 'template-url': datepickerPopupTemplateUrl }); // datepicker element datepickerEl = angular.element(popupEl.children()[0]); datepickerEl.attr('template-url', datepickerTemplateUrl); if (!$scope.datepickerOptions) { $scope.datepickerOptions = {}; } if (isHtml5DateInput) { if ($attrs.type === 'month') { $scope.datepickerOptions.datepickerMode = 'month'; $scope.datepickerOptions.minMode = 'month'; } } datepickerEl.attr('datepicker-options', 'datepickerOptions'); if (!isHtml5DateInput) { // Internal API to maintain the correct ng-invalid-[key] class ngModel.$$parserName = 'date'; ngModel.$validators.date = validator; ngModel.$parsers.unshift(parseDate); ngModel.$formatters.push(function(value) { if (ngModel.$isEmpty(value)) { $scope.date = value; return value; } if (angular.isNumber(value)) { value = new Date(value); } $scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone')); return dateParser.filter($scope.date, dateFormat); }); } else { ngModel.$formatters.push(function(value) { $scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone')); return value; }); } // Detect changes in the view from the text box ngModel.$viewChangeListeners.push(function() { $scope.date = parseDateString(ngModel.$viewValue); }); $element.on('keydown', inputKeydownBind); $popup = $compile(popupEl)($scope); // Prevent jQuery cache memory leak (template is now redundant after linking) popupEl.remove(); if (appendToBody) { $document.find('body').append($popup); } else { $element.after($popup); } $scope.$on('$destroy', function() { if ($scope.isOpen === true) { if (!$rootScope.$$phase) { $scope.$apply(function() { $scope.isOpen = false; }); } } $popup.remove(); $element.off('keydown', inputKeydownBind); $document.off('click', documentClickBind); if (scrollParentEl) { scrollParentEl.off('scroll', positionPopup); } angular.element($window).off('resize', positionPopup); //Clear all watch listeners on destroy while (watchListeners.length) { watchListeners.shift()(); } }); }; $scope.getText = function(key) { return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; }; $scope.isDisabled = function(date) { if (date === 'today') { date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone')); } var dates = {}; angular.forEach(['minDate', 'maxDate'], function(key) { if (!$scope.datepickerOptions[key]) { dates[key] = null; } else if (angular.isDate($scope.datepickerOptions[key])) { dates[key] = new Date($scope.datepickerOptions[key]); } else { if ($datepickerPopupLiteralWarning) { $log.warn('Literal date support has been deprecated, please switch to date object usage'); } dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium')); } }); return $scope.datepickerOptions && dates.minDate && $scope.compare(date, dates.minDate) < 0 || dates.maxDate && $scope.compare(date, dates.maxDate) > 0; }; $scope.compare = function(date1, date2) { return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); }; // Inner change $scope.dateSelection = function(dt) { $scope.date = dt; var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function $element.val(date); ngModel.$setViewValue(date); if (closeOnDateSelection) { $scope.isOpen = false; $element[0].focus(); } }; $scope.keydown = function(evt) { if (evt.which === 27) { evt.stopPropagation(); $scope.isOpen = false; $element[0].focus(); } }; $scope.select = function(date, evt) { evt.stopPropagation(); if (date === 'today') { var today = new Date(); if (angular.isDate($scope.date)) { date = new Date($scope.date); date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); } else { date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone')); date.setHours(0, 0, 0, 0); } } $scope.dateSelection(date); }; $scope.close = function(evt) { evt.stopPropagation(); $scope.isOpen = false; $element[0].focus(); }; $scope.disabled = angular.isDefined($attrs.disabled) || false; if ($attrs.ngDisabled) { watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { $scope.disabled = disabled; })); } $scope.$watch('isOpen', function(value) { if (value) { if (!$scope.disabled) { $timeout(function() { positionPopup(); if (onOpenFocus) { $scope.$broadcast('uib:datepicker.focus'); } $document.on('click', documentClickBind); var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; if (appendToBody || $position.parsePlacement(placement)[2]) { scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); if (scrollParentEl) { scrollParentEl.on('scroll', positionPopup); } } else { scrollParentEl = null; } angular.element($window).on('resize', positionPopup); }, 0, false); } else { $scope.isOpen = false; } } else { $document.off('click', documentClickBind); if (scrollParentEl) { scrollParentEl.off('scroll', positionPopup); } angular.element($window).off('resize', positionPopup); } }); function cameltoDash(string) { return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); } function parseDateString(viewValue) { var date = dateParser.parse(viewValue, dateFormat, $scope.date); if (isNaN(date)) { for (var i = 0; i < altInputFormats.length; i++) { date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); if (!isNaN(date)) { return date; } } } return date; } function parseDate(viewValue) { if (angular.isNumber(viewValue)) { // presumably timestamp to date object viewValue = new Date(viewValue); } if (!viewValue) { return null; } if (angular.isDate(viewValue) && !isNaN(viewValue)) { return viewValue; } if (angular.isString(viewValue)) { var date = parseDateString(viewValue); if (!isNaN(date)) { return dateParser.toTimezone(date, ngModelOptions.getOption('timezone')); } } return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined; } function validator(modelValue, viewValue) { var value = modelValue || viewValue; if (!$attrs.ngRequired && !value) { return true; } if (angular.isNumber(value)) { value = new Date(value); } if (!value) { return true; } if (angular.isDate(value) && !isNaN(value)) { return true; } if (angular.isString(value)) { return !isNaN(parseDateString(value)); } return false; } function documentClickBind(event) { if (!$scope.isOpen && $scope.disabled) { return; } var popup = $popup[0]; var dpContainsTarget = $element[0].contains(event.target); // The popup node may not be an element node // In some browsers (IE) only element nodes have the 'contains' function var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { $scope.$apply(function() { $scope.isOpen = false; }); } } function inputKeydownBind(evt) { if (evt.which === 27 && $scope.isOpen) { evt.preventDefault(); evt.stopPropagation(); $scope.$apply(function() { $scope.isOpen = false; }); $element[0].focus(); } else if (evt.which === 40 && !$scope.isOpen) { evt.preventDefault(); evt.stopPropagation(); $scope.$apply(function() { $scope.isOpen = true; }); } } function positionPopup() { if ($scope.isOpen) { var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; var position = $position.positionElements($element, dpElement, placement, appendToBody); dpElement.css({top: position.top + 'px', left: position.left + 'px'}); if (dpElement.hasClass('uib-position-measure')) { dpElement.removeClass('uib-position-measure'); } } } function extractOptions(ngModelCtrl) { var ngModelOptions; if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing // guarantee a value ngModelOptions = angular.isObject(ngModelCtrl.$options) ? ngModelCtrl.$options : { timezone: null }; // mimic 1.6+ api ngModelOptions.getOption = function (key) { return ngModelOptions[key]; }; } else { // in angular >=1.6 $options is always present ngModelOptions = ngModelCtrl.$options; } return ngModelOptions; } $scope.$on('uib:datepicker.mode', function() { $timeout(positionPopup, 0, false); }); }]) .directive('uibDatepickerPopup', function() { return { require: ['ngModel', 'uibDatepickerPopup'], controller: 'UibDatepickerPopupController', scope: { datepickerOptions: '=?', isOpen: '=?', currentText: '@', clearText: '@', closeText: '@' }, link: function(scope, element, attrs, ctrls) { var ngModel = ctrls[0], ctrl = ctrls[1]; ctrl.init(ngModel); } }; }) .directive('uibDatepickerPopupWrap', function() { return { restrict: 'A', transclude: true, templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html'; } }; }); angular.module('ui.bootstrap.debounce', []) /** * A helper, internal service that debounces a function */ .factory('$$debounce', ['$timeout', function($timeout) { return function(callback, debounceTime) { var timeoutPromise; return function() { var self = this; var args = Array.prototype.slice.call(arguments); if (timeoutPromise) { $timeout.cancel(timeoutPromise); } timeoutPromise = $timeout(function() { callback.apply(self, args); }, debounceTime); }; }; }]); angular.module('ui.bootstrap.multiMap', []) /** * A helper, internal data structure that stores all references attached to key */ .factory('$$multiMap', function() { return { createNew: function() { var map = {}; return { entries: function() { return Object.keys(map).map(function(key) { return { key: key, value: map[key] }; }); }, get: function(key) { return map[key]; }, hasKey: function(key) { return !!map[key]; }, keys: function() { return Object.keys(map); }, put: function(key, value) { if (!map[key]) { map[key] = []; } map[key].push(value); }, remove: function(key, value) { var values = map[key]; if (!values) { return; } var idx = values.indexOf(value); if (idx !== -1) { values.splice(idx, 1); } if (!values.length) { delete map[key]; } } }; } }; }); angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position']) .constant('uibDropdownConfig', { appendToOpenClass: 'uib-dropdown-open', openClass: 'open' }) .service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) { var openScope = null; var openedContainers = $$multiMap.createNew(); this.isOnlyOpen = function(dropdownScope, appendTo) { var openedDropdowns = openedContainers.get(appendTo); if (openedDropdowns) { var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) { if (dropdown.scope === dropdownScope) { return dropdown; } return toClose; }, {}); if (openDropdown) { return openedDropdowns.length === 1; } } return false; }; this.open = function(dropdownScope, element, appendTo) { if (!openScope) { $document.on('click', closeDropdown); } if (openScope && openScope !== dropdownScope) { openScope.isOpen = false; } openScope = dropdownScope; if (!appendTo) { return; } var openedDropdowns = openedContainers.get(appendTo); if (openedDropdowns) { var openedScopes = openedDropdowns.map(function(dropdown) { return dropdown.scope; }); if (openedScopes.indexOf(dropdownScope) === -1) { openedContainers.put(appendTo, { scope: dropdownScope }); } } else { openedContainers.put(appendTo, { scope: dropdownScope }); } }; this.close = function(dropdownScope, element, appendTo) { if (openScope === dropdownScope) { $document.off('click', closeDropdown); $document.off('keydown', this.keybindFilter); openScope = null; } if (!appendTo) { return; } var openedDropdowns = openedContainers.get(appendTo); if (openedDropdowns) { var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) { if (dropdown.scope === dropdownScope) { return dropdown; } return toClose; }, {}); if (dropdownToClose) { openedContainers.remove(appendTo, dropdownToClose); } } }; var closeDropdown = function(evt) { // This method may still be called during the same mouse event that // unbound this event handler. So check openScope before proceeding. if (!openScope || !openScope.isOpen) { return; } if (evt && openScope.getAutoClose() === 'disabled') { return; } if (evt && evt.which === 3) { return; } var toggleElement = openScope.getToggleElement(); if (evt && toggleElement && toggleElement[0].contains(evt.target)) { return; } var dropdownElement = openScope.getDropdownElement(); if (evt && openScope.getAutoClose() === 'outsideClick' && dropdownElement && dropdownElement[0].contains(evt.target)) { return; } openScope.focusToggleElement(); openScope.isOpen = false; if (!$rootScope.$$phase) { openScope.$apply(); } }; this.keybindFilter = function(evt) { if (!openScope) { // see this.close as ESC could have been pressed which kills the scope so we can not proceed return; } var dropdownElement = openScope.getDropdownElement(); var toggleElement = openScope.getToggleElement(); var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target); var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target); if (evt.which === 27) { evt.stopPropagation(); openScope.focusToggleElement(); closeDropdown(); } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) { evt.preventDefault(); evt.stopPropagation(); openScope.focusDropdownEntry(evt.which); } }; }]) .controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { var self = this, scope = $scope.$new(), // create a child scope so we are not polluting original one templateScope, appendToOpenClass = dropdownConfig.appendToOpenClass, openClass = dropdownConfig.openClass, getIsOpen, setIsOpen = angular.noop, toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, keynavEnabled = false, selectedOption = null, body = $document.find('body'); $element.addClass('dropdown'); this.init = function() { if ($attrs.isOpen) { getIsOpen = $parse($attrs.isOpen); setIsOpen = getIsOpen.assign; $scope.$watch(getIsOpen, function(value) { scope.isOpen = !!value; }); } keynavEnabled = angular.isDefined($attrs.keyboardNav); }; this.toggle = function(open) { scope.isOpen = arguments.length ? !!open : !scope.isOpen; if (angular.isFunction(setIsOpen)) { setIsOpen(scope, scope.isOpen); } return scope.isOpen; }; // Allow other directives to watch status this.isOpen = function() { return scope.isOpen; }; scope.getToggleElement = function() { return self.toggleElement; }; scope.getAutoClose = function() { return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' }; scope.getElement = function() { return $element; }; scope.isKeynavEnabled = function() { return keynavEnabled; }; scope.focusDropdownEntry = function(keyCode) { var elems = self.dropdownMenu ? //If append to body is used. angular.element(self.dropdownMenu).find('a') : $element.find('ul').eq(0).find('a'); switch (keyCode) { case 40: { if (!angular.isNumber(self.selectedOption)) { self.selectedOption = 0; } else { self.selectedOption = self.selectedOption === elems.length - 1 ? self.selectedOption : self.selectedOption + 1; } break; } case 38: { if (!angular.isNumber(self.selectedOption)) { self.selectedOption = elems.length - 1; } else { self.selectedOption = self.selectedOption === 0 ? 0 : self.selectedOption - 1; } break; } } elems[self.selectedOption].focus(); }; scope.getDropdownElement = function() { return self.dropdownMenu; }; scope.focusToggleElement = function() { if (self.toggleElement) { self.toggleElement[0].focus(); } }; function removeDropdownMenu() { $element.append(self.dropdownMenu); } scope.$watch('isOpen', function(isOpen, wasOpen) { var appendTo = null, appendToBody = false; if (angular.isDefined($attrs.dropdownAppendTo)) { var appendToEl = $parse($attrs.dropdownAppendTo)(scope); if (appendToEl) { appendTo = angular.element(appendToEl); } } if (angular.isDefined($attrs.dropdownAppendToBody)) { var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope); if (appendToBodyValue !== false) { appendToBody = true; } } if (appendToBody && !appendTo) { appendTo = body; } if (appendTo && self.dropdownMenu) { if (isOpen) { appendTo.append(self.dropdownMenu); $element.on('$destroy', removeDropdownMenu); } else { $element.off('$destroy', removeDropdownMenu); removeDropdownMenu(); } } if (appendTo && self.dropdownMenu) { var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true), css, rightalign, scrollbarPadding, scrollbarWidth = 0; css = { top: pos.top + 'px', display: isOpen ? 'block' : 'none' }; rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); if (!rightalign) { css.left = pos.left + 'px'; css.right = 'auto'; } else { css.left = 'auto'; scrollbarPadding = $position.scrollbarPadding(appendTo); if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { scrollbarWidth = scrollbarPadding.scrollbarWidth; } css.right = window.innerWidth - scrollbarWidth - (pos.left + $element.prop('offsetWidth')) + 'px'; } // Need to adjust our positioning to be relative to the appendTo container // if it's not the body element if (!appendToBody) { var appendOffset = $position.offset(appendTo); css.top = pos.top - appendOffset.top + 'px'; if (!rightalign) { css.left = pos.left - appendOffset.left + 'px'; } else { css.right = window.innerWidth - (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px'; } } self.dropdownMenu.css(css); } var openContainer = appendTo ? appendTo : $element; var dropdownOpenClass = appendTo ? appendToOpenClass : openClass; var hasOpenClass = openContainer.hasClass(dropdownOpenClass); var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo); if (hasOpenClass === !isOpen) { var toggleClass; if (appendTo) { toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass'; } else { toggleClass = isOpen ? 'addClass' : 'removeClass'; } $animate[toggleClass](openContainer, dropdownOpenClass).then(function() { if (angular.isDefined(isOpen) && isOpen !== wasOpen) { toggleInvoker($scope, { open: !!isOpen }); } }); } if (isOpen) { if (self.dropdownMenuTemplateUrl) { $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { templateScope = scope.$new(); $compile(tplContent.trim())(templateScope, function(dropdownElement) { var newEl = dropdownElement; self.dropdownMenu.replaceWith(newEl); self.dropdownMenu = newEl; $document.on('keydown', uibDropdownService.keybindFilter); }); }); } else { $document.on('keydown', uibDropdownService.keybindFilter); } scope.focusToggleElement(); uibDropdownService.open(scope, $element, appendTo); } else { uibDropdownService.close(scope, $element, appendTo); if (self.dropdownMenuTemplateUrl) { if (templateScope) { templateScope.$destroy(); } var newEl = angular.element('
      '); self.dropdownMenu.replaceWith(newEl); self.dropdownMenu = newEl; } self.selectedOption = null; } if (angular.isFunction(setIsOpen)) { setIsOpen($scope, isOpen); } }); }]) .directive('uibDropdown', function() { return { controller: 'UibDropdownController', link: function(scope, element, attrs, dropdownCtrl) { dropdownCtrl.init(); } }; }) .directive('uibDropdownMenu', function() { return { restrict: 'A', require: '?^uibDropdown', link: function(scope, element, attrs, dropdownCtrl) { if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { return; } element.addClass('dropdown-menu'); var tplUrl = attrs.templateUrl; if (tplUrl) { dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; } if (!dropdownCtrl.dropdownMenu) { dropdownCtrl.dropdownMenu = element; } } }; }) .directive('uibDropdownToggle', function() { return { require: '?^uibDropdown', link: function(scope, element, attrs, dropdownCtrl) { if (!dropdownCtrl) { return; } element.addClass('dropdown-toggle'); dropdownCtrl.toggleElement = element; var toggleDropdown = function(event) { event.preventDefault(); if (!element.hasClass('disabled') && !attrs.disabled) { scope.$apply(function() { dropdownCtrl.toggle(); }); } }; element.on('click', toggleDropdown); // WAI-ARIA element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); scope.$watch(dropdownCtrl.isOpen, function(isOpen) { element.attr('aria-expanded', !!isOpen); }); scope.$on('$destroy', function() { element.off('click', toggleDropdown); }); } }; }); angular.module('ui.bootstrap.stackedMap', []) /** * A helper, internal data structure that acts as a map but also allows getting / removing * elements in the LIFO order */ .factory('$$stackedMap', function() { return { createNew: function() { var stack = []; return { add: function(key, value) { stack.push({ key: key, value: value }); }, get: function(key) { for (var i = 0; i < stack.length; i++) { if (key === stack[i].key) { return stack[i]; } } }, keys: function() { var keys = []; for (var i = 0; i < stack.length; i++) { keys.push(stack[i].key); } return keys; }, top: function() { return stack[stack.length - 1]; }, remove: function(key) { var idx = -1; for (var i = 0; i < stack.length; i++) { if (key === stack[i].key) { idx = i; break; } } return stack.splice(idx, 1)[0]; }, removeTop: function() { return stack.pop(); }, length: function() { return stack.length; } }; } }; }); angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position']) /** * Pluggable resolve mechanism for the modal resolve resolution * Supports UI Router's $resolve service */ .provider('$uibResolve', function() { var resolve = this; this.resolver = null; this.setResolver = function(resolver) { this.resolver = resolver; }; this.$get = ['$injector', '$q', function($injector, $q) { var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null; return { resolve: function(invocables, locals, parent, self) { if (resolver) { return resolver.resolve(invocables, locals, parent, self); } var promises = []; angular.forEach(invocables, function(value) { if (angular.isFunction(value) || angular.isArray(value)) { promises.push($q.resolve($injector.invoke(value))); } else if (angular.isString(value)) { promises.push($q.resolve($injector.get(value))); } else { promises.push($q.resolve(value)); } }); return $q.all(promises).then(function(resolves) { var resolveObj = {}; var resolveIter = 0; angular.forEach(invocables, function(value, key) { resolveObj[key] = resolves[resolveIter++]; }); return resolveObj; }); } }; }]; }) /** * A helper directive for the $modal service. It creates a backdrop element. */ .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack', function($animate, $injector, $modalStack) { return { restrict: 'A', compile: function(tElement, tAttrs) { tElement.addClass(tAttrs.backdropClass); return linkFn; } }; function linkFn(scope, element, attrs) { if (attrs.modalInClass) { $animate.addClass(element, attrs.modalInClass); scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { var done = setIsAsync(); if (scope.modalOptions.animation) { $animate.removeClass(element, attrs.modalInClass).then(done); } else { done(); } }); } } }]) .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document', function($modalStack, $q, $animateCss, $document) { return { scope: { index: '@' }, restrict: 'A', transclude: true, templateUrl: function(tElement, tAttrs) { return tAttrs.templateUrl || 'uib/template/modal/window.html'; }, link: function(scope, element, attrs) { element.addClass(attrs.windowTopClass || ''); scope.size = attrs.size; scope.close = function(evt) { var modal = $modalStack.getTop(); if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && evt.target === evt.currentTarget) { evt.preventDefault(); evt.stopPropagation(); $modalStack.dismiss(modal.key, 'backdrop click'); } }; // moved from template to fix issue #2280 element.on('click', scope.close); // This property is only added to the scope for the purpose of detecting when this directive is rendered. // We can detect that by using this property in the template associated with this directive and then use // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. scope.$isRendered = true; // Deferred object that will be resolved when this modal is rendered. var modalRenderDeferObj = $q.defer(); // Resolve render promise post-digest scope.$$postDigest(function() { modalRenderDeferObj.resolve(); }); modalRenderDeferObj.promise.then(function() { var animationPromise = null; if (attrs.modalInClass) { animationPromise = $animateCss(element, { addClass: attrs.modalInClass }).start(); scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { var done = setIsAsync(); $animateCss(element, { removeClass: attrs.modalInClass }).start().then(done); }); } $q.when(animationPromise).then(function() { // Notify {@link $modalStack} that modal is rendered. var modal = $modalStack.getTop(); if (modal) { $modalStack.modalRendered(modal.key); } /** * If something within the freshly-opened modal already has focus (perhaps via a * directive that causes focus) then there's no need to try to focus anything. */ if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) { var inputWithAutofocus = element[0].querySelector('[autofocus]'); /** * Auto-focusing of a freshly-opened modal element causes any child elements * with the autofocus attribute to lose focus. This is an issue on touch * based devices which will show and then hide the onscreen keyboard. * Attempts to refocus the autofocus element via JavaScript will not reopen * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus * the modal element if the modal does not contain an autofocus element. */ if (inputWithAutofocus) { inputWithAutofocus.focus(); } else { element[0].focus(); } } }); }); } }; }]) .directive('uibModalAnimationClass', function() { return { compile: function(tElement, tAttrs) { if (tAttrs.modalAnimation) { tElement.addClass(tAttrs.uibModalAnimationClass); } } }; }) .directive('uibModalTransclude', ['$animate', function($animate) { return { link: function(scope, element, attrs, controller, transclude) { transclude(scope.$parent, function(clone) { element.empty(); $animate.enter(clone, element); }); } }; }]) .factory('$uibModalStack', ['$animate', '$animateCss', '$document', '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition', function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) { var OPENED_MODAL_CLASS = 'modal-open'; var backdropDomEl, backdropScope; var openedWindows = $$stackedMap.createNew(); var openedClasses = $$multiMap.createNew(); var $modalStack = { NOW_CLOSING_EVENT: 'modal.stack.now-closing' }; var topModalIndex = 0; var previousTopOpenedModal = null; var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count'; //Modal focus behavior var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' + 'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' + 'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]'; var scrollbarPadding; var SNAKE_CASE_REGEXP = /[A-Z]/g; // TODO: extract into common dependency with tooltip function snake_case(name) { var separator = '-'; return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { return (pos ? separator : '') + letter.toLowerCase(); }); } function isVisible(element) { return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length); } function backdropIndex() { var topBackdropIndex = -1; var opened = openedWindows.keys(); for (var i = 0; i < opened.length; i++) { if (openedWindows.get(opened[i]).value.backdrop) { topBackdropIndex = i; } } // If any backdrop exist, ensure that it's index is always // right below the top modal if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) { topBackdropIndex = topModalIndex; } return topBackdropIndex; } $rootScope.$watch(backdropIndex, function(newBackdropIndex) { if (backdropScope) { backdropScope.index = newBackdropIndex; } }); function removeModalWindow(modalInstance, elementToReceiveFocus) { var modalWindow = openedWindows.get(modalInstance).value; var appendToElement = modalWindow.appendTo; //clean up the stack openedWindows.remove(modalInstance); previousTopOpenedModal = openedWindows.top(); if (previousTopOpenedModal) { topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10); } removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; openedClasses.remove(modalBodyClass, modalInstance); var areAnyOpen = openedClasses.hasKey(modalBodyClass); appendToElement.toggleClass(modalBodyClass, areAnyOpen); if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { if (scrollbarPadding.originalRight) { appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'}); } else { appendToElement.css({paddingRight: ''}); } scrollbarPadding = null; } toggleTopWindowClass(true); }, modalWindow.closedDeferred); checkRemoveBackdrop(); //move focus to specified element if available, or else to body if (elementToReceiveFocus && elementToReceiveFocus.focus) { elementToReceiveFocus.focus(); } else if (appendToElement.focus) { appendToElement.focus(); } } // Add or remove "windowTopClass" from the top window in the stack function toggleTopWindowClass(toggleSwitch) { var modalWindow; if (openedWindows.length() > 0) { modalWindow = openedWindows.top().value; modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); } } function checkRemoveBackdrop() { //remove backdrop if no longer needed if (backdropDomEl && backdropIndex() === -1) { var backdropScopeRef = backdropScope; removeAfterAnimate(backdropDomEl, backdropScope, function() { backdropScopeRef = null; }); backdropDomEl = undefined; backdropScope = undefined; } } function removeAfterAnimate(domEl, scope, done, closedDeferred) { var asyncDeferred; var asyncPromise = null; var setIsAsync = function() { if (!asyncDeferred) { asyncDeferred = $q.defer(); asyncPromise = asyncDeferred.promise; } return function asyncDone() { asyncDeferred.resolve(); }; }; scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); // Note that it's intentional that asyncPromise might be null. // That's when setIsAsync has not been called during the // NOW_CLOSING_EVENT broadcast. return $q.when(asyncPromise).then(afterAnimating); function afterAnimating() { if (afterAnimating.done) { return; } afterAnimating.done = true; $animate.leave(domEl).then(function() { if (done) { done(); } domEl.remove(); if (closedDeferred) { closedDeferred.resolve(); } }); scope.$destroy(); } } $document.on('keydown', keydownListener); $rootScope.$on('$destroy', function() { $document.off('keydown', keydownListener); }); function keydownListener(evt) { if (evt.isDefaultPrevented()) { return evt; } var modal = openedWindows.top(); if (modal) { switch (evt.which) { case 27: { if (modal.value.keyboard) { evt.preventDefault(); $rootScope.$apply(function() { $modalStack.dismiss(modal.key, 'escape key press'); }); } break; } case 9: { var list = $modalStack.loadFocusElementList(modal); var focusChanged = false; if (evt.shiftKey) { if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { focusChanged = $modalStack.focusLastFocusableElement(list); } } else { if ($modalStack.isFocusInLastItem(evt, list)) { focusChanged = $modalStack.focusFirstFocusableElement(list); } } if (focusChanged) { evt.preventDefault(); evt.stopPropagation(); } break; } } } } $modalStack.open = function(modalInstance, modal) { var modalOpener = $document[0].activeElement, modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; toggleTopWindowClass(false); // Store the current top first, to determine what index we ought to use // for the current top modal previousTopOpenedModal = openedWindows.top(); openedWindows.add(modalInstance, { deferred: modal.deferred, renderDeferred: modal.renderDeferred, closedDeferred: modal.closedDeferred, modalScope: modal.scope, backdrop: modal.backdrop, keyboard: modal.keyboard, openedClass: modal.openedClass, windowTopClass: modal.windowTopClass, animation: modal.animation, appendTo: modal.appendTo }); openedClasses.put(modalBodyClass, modalInstance); var appendToElement = modal.appendTo, currBackdropIndex = backdropIndex(); if (currBackdropIndex >= 0 && !backdropDomEl) { backdropScope = $rootScope.$new(true); backdropScope.modalOptions = modal; backdropScope.index = currBackdropIndex; backdropDomEl = angular.element('
      '); backdropDomEl.attr({ 'class': 'modal-backdrop', 'ng-style': '{\'z-index\': 1040 + (index && 1 || 0) + index*10}', 'uib-modal-animation-class': 'fade', 'modal-in-class': 'in' }); if (modal.backdropClass) { backdropDomEl.addClass(modal.backdropClass); } if (modal.animation) { backdropDomEl.attr('modal-animation', 'true'); } $compile(backdropDomEl)(backdropScope); $animate.enter(backdropDomEl, appendToElement); if ($uibPosition.isScrollable(appendToElement)) { scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement); if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { appendToElement.css({paddingRight: scrollbarPadding.right + 'px'}); } } } var content; if (modal.component) { content = document.createElement(snake_case(modal.component.name)); content = angular.element(content); content.attr({ resolve: '$resolve', 'modal-instance': '$uibModalInstance', close: '$close($value)', dismiss: '$dismiss($value)' }); } else { content = modal.content; } // Set the top modal index based on the index of the previous top modal topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0; var angularDomEl = angular.element('
      '); angularDomEl.attr({ 'class': 'modal', 'template-url': modal.windowTemplateUrl, 'window-top-class': modal.windowTopClass, 'role': 'dialog', 'aria-labelledby': modal.ariaLabelledBy, 'aria-describedby': modal.ariaDescribedBy, 'size': modal.size, 'index': topModalIndex, 'animate': 'animate', 'ng-style': '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}', 'tabindex': -1, 'uib-modal-animation-class': 'fade', 'modal-in-class': 'in' }).append(content); if (modal.windowClass) { angularDomEl.addClass(modal.windowClass); } if (modal.animation) { angularDomEl.attr('modal-animation', 'true'); } appendToElement.addClass(modalBodyClass); if (modal.scope) { // we need to explicitly add the modal index to the modal scope // because it is needed by ngStyle to compute the zIndex property. modal.scope.$$topModalIndex = topModalIndex; } $animate.enter($compile(angularDomEl)(modal.scope), appendToElement); openedWindows.top().value.modalDomEl = angularDomEl; openedWindows.top().value.modalOpener = modalOpener; applyAriaHidden(angularDomEl); function applyAriaHidden(el) { if (!el || el[0].tagName === 'BODY') { return; } getSiblings(el).forEach(function(sibling) { var elemIsAlreadyHidden = sibling.getAttribute('aria-hidden') === 'true', ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10); if (!ariaHiddenCount) { ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0; } sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1); sibling.setAttribute('aria-hidden', 'true'); }); return applyAriaHidden(el.parent()); function getSiblings(el) { var children = el.parent() ? el.parent().children() : []; return Array.prototype.filter.call(children, function(child) { return child !== el[0]; }); } } }; function broadcastClosing(modalWindow, resultOrReason, closing) { return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; } function unhideBackgroundElements() { Array.prototype.forEach.call( document.querySelectorAll('[' + ARIA_HIDDEN_ATTRIBUTE_NAME + ']'), function(hiddenEl) { var ariaHiddenCount = parseInt(hiddenEl.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10), newHiddenCount = ariaHiddenCount - 1; hiddenEl.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, newHiddenCount); if (!newHiddenCount) { hiddenEl.removeAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME); hiddenEl.removeAttribute('aria-hidden'); } } ); } $modalStack.close = function(modalInstance, result) { var modalWindow = openedWindows.get(modalInstance); unhideBackgroundElements(); if (modalWindow && broadcastClosing(modalWindow, result, true)) { modalWindow.value.modalScope.$$uibDestructionScheduled = true; modalWindow.value.deferred.resolve(result); removeModalWindow(modalInstance, modalWindow.value.modalOpener); return true; } return !modalWindow; }; $modalStack.dismiss = function(modalInstance, reason) { var modalWindow = openedWindows.get(modalInstance); unhideBackgroundElements(); if (modalWindow && broadcastClosing(modalWindow, reason, false)) { modalWindow.value.modalScope.$$uibDestructionScheduled = true; modalWindow.value.deferred.reject(reason); removeModalWindow(modalInstance, modalWindow.value.modalOpener); return true; } return !modalWindow; }; $modalStack.dismissAll = function(reason) { var topModal = this.getTop(); while (topModal && this.dismiss(topModal.key, reason)) { topModal = this.getTop(); } }; $modalStack.getTop = function() { return openedWindows.top(); }; $modalStack.modalRendered = function(modalInstance) { var modalWindow = openedWindows.get(modalInstance); if (modalWindow) { modalWindow.value.renderDeferred.resolve(); } }; $modalStack.focusFirstFocusableElement = function(list) { if (list.length > 0) { list[0].focus(); return true; } return false; }; $modalStack.focusLastFocusableElement = function(list) { if (list.length > 0) { list[list.length - 1].focus(); return true; } return false; }; $modalStack.isModalFocused = function(evt, modalWindow) { if (evt && modalWindow) { var modalDomEl = modalWindow.value.modalDomEl; if (modalDomEl && modalDomEl.length) { return (evt.target || evt.srcElement) === modalDomEl[0]; } } return false; }; $modalStack.isFocusInFirstItem = function(evt, list) { if (list.length > 0) { return (evt.target || evt.srcElement) === list[0]; } return false; }; $modalStack.isFocusInLastItem = function(evt, list) { if (list.length > 0) { return (evt.target || evt.srcElement) === list[list.length - 1]; } return false; }; $modalStack.loadFocusElementList = function(modalWindow) { if (modalWindow) { var modalDomE1 = modalWindow.value.modalDomEl; if (modalDomE1 && modalDomE1.length) { var elements = modalDomE1[0].querySelectorAll(tabbableSelector); return elements ? Array.prototype.filter.call(elements, function(element) { return isVisible(element); }) : elements; } } }; return $modalStack; }]) .provider('$uibModal', function() { var $modalProvider = { options: { animation: true, backdrop: true, //can also be false or 'static' keyboard: true }, $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack', function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) { var $modal = {}; function getTemplatePromise(options) { return options.template ? $q.when(options.template) : $templateRequest(angular.isFunction(options.templateUrl) ? options.templateUrl() : options.templateUrl); } var promiseChain = null; $modal.getPromiseChain = function() { return promiseChain; }; $modal.open = function(modalOptions) { var modalResultDeferred = $q.defer(); var modalOpenedDeferred = $q.defer(); var modalClosedDeferred = $q.defer(); var modalRenderDeferred = $q.defer(); //prepare an instance of a modal to be injected into controllers and returned to a caller var modalInstance = { result: modalResultDeferred.promise, opened: modalOpenedDeferred.promise, closed: modalClosedDeferred.promise, rendered: modalRenderDeferred.promise, close: function (result) { return $modalStack.close(modalInstance, result); }, dismiss: function (reason) { return $modalStack.dismiss(modalInstance, reason); } }; //merge and clean up options modalOptions = angular.extend({}, $modalProvider.options, modalOptions); modalOptions.resolve = modalOptions.resolve || {}; modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0); if (!modalOptions.appendTo.length) { throw new Error('appendTo element not found. Make sure that the element passed is in DOM.'); } //verify options if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) { throw new Error('One of component or template or templateUrl options is required.'); } var templateAndResolvePromise; if (modalOptions.component) { templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null)); } else { templateAndResolvePromise = $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]); } function resolveWithTemplate() { return templateAndResolvePromise; } // Wait for the resolution of the existing promise chain. // Then switch to our own combined promise dependency (regardless of how the previous modal fared). // Then add to $modalStack and resolve opened. // Finally clean up the chain variable if no subsequent modal has overwritten it. var samePromise; samePromise = promiseChain = $q.all([promiseChain]) .then(resolveWithTemplate, resolveWithTemplate) .then(function resolveSuccess(tplAndVars) { var providedScope = modalOptions.scope || $rootScope; var modalScope = providedScope.$new(); modalScope.$close = modalInstance.close; modalScope.$dismiss = modalInstance.dismiss; modalScope.$on('$destroy', function() { if (!modalScope.$$uibDestructionScheduled) { modalScope.$dismiss('$uibUnscheduledDestruction'); } }); var modal = { scope: modalScope, deferred: modalResultDeferred, renderDeferred: modalRenderDeferred, closedDeferred: modalClosedDeferred, animation: modalOptions.animation, backdrop: modalOptions.backdrop, keyboard: modalOptions.keyboard, backdropClass: modalOptions.backdropClass, windowTopClass: modalOptions.windowTopClass, windowClass: modalOptions.windowClass, windowTemplateUrl: modalOptions.windowTemplateUrl, ariaLabelledBy: modalOptions.ariaLabelledBy, ariaDescribedBy: modalOptions.ariaDescribedBy, size: modalOptions.size, openedClass: modalOptions.openedClass, appendTo: modalOptions.appendTo }; var component = {}; var ctrlInstance, ctrlInstantiate, ctrlLocals = {}; if (modalOptions.component) { constructLocals(component, false, true, false); component.name = modalOptions.component; modal.component = component; } else if (modalOptions.controller) { constructLocals(ctrlLocals, true, false, true); // the third param will make the controller instantiate later,private api // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126 ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs); if (modalOptions.controllerAs && modalOptions.bindToController) { ctrlInstance = ctrlInstantiate.instance; ctrlInstance.$close = modalScope.$close; ctrlInstance.$dismiss = modalScope.$dismiss; angular.extend(ctrlInstance, { $resolve: ctrlLocals.$scope.$resolve }, providedScope); } ctrlInstance = ctrlInstantiate(); if (angular.isFunction(ctrlInstance.$onInit)) { ctrlInstance.$onInit(); } } if (!modalOptions.component) { modal.content = tplAndVars[0]; } $modalStack.open(modalInstance, modal); modalOpenedDeferred.resolve(true); function constructLocals(obj, template, instanceOnScope, injectable) { obj.$scope = modalScope; obj.$scope.$resolve = {}; if (instanceOnScope) { obj.$scope.$uibModalInstance = modalInstance; } else { obj.$uibModalInstance = modalInstance; } var resolves = template ? tplAndVars[1] : tplAndVars; angular.forEach(resolves, function(value, key) { if (injectable) { obj[key] = value; } obj.$scope.$resolve[key] = value; }); } }, function resolveError(reason) { modalOpenedDeferred.reject(reason); modalResultDeferred.reject(reason); })['finally'](function() { if (promiseChain === samePromise) { promiseChain = null; } }); return modalInstance; }; return $modal; } ] }; return $modalProvider; }); angular.module('ui.bootstrap.paging', []) /** * Helper internal service for generating common controller code between the * pager and pagination components */ .factory('uibPaging', ['$parse', function($parse) { return { create: function(ctrl, $scope, $attrs) { ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl ctrl._watchers = []; ctrl.init = function(ngModelCtrl, config) { ctrl.ngModelCtrl = ngModelCtrl; ctrl.config = config; ngModelCtrl.$render = function() { ctrl.render(); }; if ($attrs.itemsPerPage) { ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) { ctrl.itemsPerPage = parseInt(value, 10); $scope.totalPages = ctrl.calculateTotalPages(); ctrl.updatePage(); })); } else { ctrl.itemsPerPage = config.itemsPerPage; } $scope.$watch('totalItems', function(newTotal, oldTotal) { if (angular.isDefined(newTotal) || newTotal !== oldTotal) { $scope.totalPages = ctrl.calculateTotalPages(); ctrl.updatePage(); } }); }; ctrl.calculateTotalPages = function() { var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage); return Math.max(totalPages || 0, 1); }; ctrl.render = function() { $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1; }; $scope.selectPage = function(page, evt) { if (evt) { evt.preventDefault(); } var clickAllowed = !$scope.ngDisabled || !evt; if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) { if (evt && evt.target) { evt.target.blur(); } ctrl.ngModelCtrl.$setViewValue(page); ctrl.ngModelCtrl.$render(); } }; $scope.getText = function(key) { return $scope[key + 'Text'] || ctrl.config[key + 'Text']; }; $scope.noPrevious = function() { return $scope.page === 1; }; $scope.noNext = function() { return $scope.page === $scope.totalPages; }; ctrl.updatePage = function() { ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable if ($scope.page > $scope.totalPages) { $scope.selectPage($scope.totalPages); } else { ctrl.ngModelCtrl.$render(); } }; $scope.$on('$destroy', function() { while (ctrl._watchers.length) { ctrl._watchers.shift()(); } }); } }; }]); angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex']) .controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) { $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align; uibPaging.create(this, $scope, $attrs); }]) .constant('uibPagerConfig', { itemsPerPage: 10, previousText: '« Previous', nextText: 'Next »', align: true }) .directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) { return { scope: { totalItems: '=', previousText: '@', nextText: '@', ngDisabled: '=' }, require: ['uibPager', '?ngModel'], restrict: 'A', controller: 'UibPagerController', controllerAs: 'pager', templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/pager/pager.html'; }, link: function(scope, element, attrs, ctrls) { element.addClass('pager'); var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; if (!ngModelCtrl) { return; // do nothing if no ng-model } paginationCtrl.init(ngModelCtrl, uibPagerConfig); } }; }]); angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex']) .controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) { var ctrl = this; // Setup configuration parameters var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize, rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate, forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses, boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers, pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity; $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks; $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks; $attrs.$set('role', 'menu'); uibPaging.create(this, $scope, $attrs); if ($attrs.maxSize) { ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) { maxSize = parseInt(value, 10); ctrl.render(); })); } // Create page object used in template function makePage(number, text, isActive) { return { number: number, text: text, active: isActive }; } function getPages(currentPage, totalPages) { var pages = []; // Default page limits var startPage = 1, endPage = totalPages; var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages; // recompute if maxSize if (isMaxSized) { if (rotate) { // Current page is displayed in the middle of the visible ones startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1); endPage = startPage + maxSize - 1; // Adjust if limit is exceeded if (endPage > totalPages) { endPage = totalPages; startPage = endPage - maxSize + 1; } } else { // Visible pages are paginated with maxSize startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1; // Adjust last page if limit is exceeded endPage = Math.min(startPage + maxSize - 1, totalPages); } } // Add page number links for (var number = startPage; number <= endPage; number++) { var page = makePage(number, pageLabel(number), number === currentPage); pages.push(page); } // Add links to move between page sets if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) { if (startPage > 1) { if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning var previousPageSet = makePage(startPage - 1, '...', false); pages.unshift(previousPageSet); } if (boundaryLinkNumbers) { if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential var secondPageLink = makePage(2, '2', false); pages.unshift(secondPageLink); } //add the first page var firstPageLink = makePage(1, '1', false); pages.unshift(firstPageLink); } } if (endPage < totalPages) { if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end var nextPageSet = makePage(endPage + 1, '...', false); pages.push(nextPageSet); } if (boundaryLinkNumbers) { if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false); pages.push(secondToLastPageLink); } //add the last page var lastPageLink = makePage(totalPages, totalPages, false); pages.push(lastPageLink); } } } return pages; } var originalRender = this.render; this.render = function() { originalRender(); if ($scope.page > 0 && $scope.page <= $scope.totalPages) { $scope.pages = getPages($scope.page, $scope.totalPages); } }; }]) .constant('uibPaginationConfig', { itemsPerPage: 10, boundaryLinks: false, boundaryLinkNumbers: false, directionLinks: true, firstText: 'First', previousText: 'Previous', nextText: 'Next', lastText: 'Last', rotate: true, forceEllipses: false }) .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) { return { scope: { totalItems: '=', firstText: '@', previousText: '@', nextText: '@', lastText: '@', ngDisabled:'=' }, require: ['uibPagination', '?ngModel'], restrict: 'A', controller: 'UibPaginationController', controllerAs: 'pagination', templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/pagination/pagination.html'; }, link: function(scope, element, attrs, ctrls) { element.addClass('pagination'); var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; if (!ngModelCtrl) { return; // do nothing if no ng-model } paginationCtrl.init(ngModelCtrl, uibPaginationConfig); } }; }]); /** * The following features are still outstanding: animation as a * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html tooltips, and selector delegation. */ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap']) /** * The $tooltip service creates tooltip- and popover-like directives as well as * houses global options for them. */ .provider('$uibTooltip', function() { // The default options tooltip and popover. var defaultOptions = { placement: 'top', placementClassPrefix: '', animation: true, popupDelay: 0, popupCloseDelay: 0, useContentExp: false }; // Default hide triggers for each show trigger var triggerMap = { 'mouseenter': 'mouseleave', 'click': 'click', 'outsideClick': 'outsideClick', 'focus': 'blur', 'none': '' }; // The options specified to the provider globally. var globalOptions = {}; /** * `options({})` allows global configuration of all tooltips in the * application. * * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { * // place tooltips left instead of top by default * $tooltipProvider.options( { placement: 'left' } ); * }); */ this.options = function(value) { angular.extend(globalOptions, value); }; /** * This allows you to extend the set of trigger mappings available. E.g.: * * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } ); */ this.setTriggers = function setTriggers(triggers) { angular.extend(triggerMap, triggers); }; /** * This is a helper function for translating camel-case to snake_case. */ function snake_case(name) { var regexp = /[A-Z]/g; var separator = '-'; return name.replace(regexp, function(letter, pos) { return (pos ? separator : '') + letter.toLowerCase(); }); } /** * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) { var openedTooltips = $$stackedMap.createNew(); $document.on('keyup', keypressListener); $rootScope.$on('$destroy', function() { $document.off('keyup', keypressListener); }); function keypressListener(e) { if (e.which === 27) { var last = openedTooltips.top(); if (last) { last.value.close(); last = null; } } } return function $tooltip(ttType, prefix, defaultTriggerShow, options) { options = angular.extend({}, defaultOptions, globalOptions, options); /** * Returns an object of show and hide triggers. * * If a trigger is supplied, * it is used to show the tooltip; otherwise, it will use the `trigger` * option passed to the `$tooltipProvider.options` method; else it will * default to the trigger supplied to this directive factory. * * The hide trigger is based on the show trigger. If the `trigger` option * was passed to the `$tooltipProvider.options` method, it will use the * mapped trigger from `triggerMap` or the passed trigger if the map is * undefined; otherwise, it uses the `triggerMap` value of the show * trigger; else it will just use the show trigger. */ function getTriggers(trigger) { var show = (trigger || options.trigger || defaultTriggerShow).split(' '); var hide = show.map(function(trigger) { return triggerMap[trigger] || trigger; }); return { show: show, hide: hide }; } var directiveName = snake_case(ttType); var startSym = $interpolate.startSymbol(); var endSym = $interpolate.endSymbol(); var template = '
      ' + '
      '; return { compile: function(tElem, tAttrs) { var tooltipLinker = $compile(template); return function link(scope, element, attrs, tooltipCtrl) { var tooltip; var tooltipLinkedScope; var transitionTimeout; var showTimeout; var hideTimeout; var positionTimeout; var adjustmentTimeout; var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false; var triggers = getTriggers(undefined); var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); var ttScope = scope.$new(true); var repositionScheduled = false; var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; var observers = []; var lastPlacement; var positionTooltip = function() { // check if tooltip exists and is not empty if (!tooltip || !tooltip.html()) { return; } if (!positionTimeout) { positionTimeout = $timeout(function() { var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); var initialHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight'); var elementPos = appendToBody ? $position.offset(element) : $position.position(element); tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' }); var placementClasses = ttPosition.placement.split('-'); if (!tooltip.hasClass(placementClasses[0])) { tooltip.removeClass(lastPlacement.split('-')[0]); tooltip.addClass(placementClasses[0]); } if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) { tooltip.removeClass(options.placementClassPrefix + lastPlacement); tooltip.addClass(options.placementClassPrefix + ttPosition.placement); } adjustmentTimeout = $timeout(function() { var currentHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight'); var adjustment = $position.adjustTop(placementClasses, elementPos, initialHeight, currentHeight); if (adjustment) { tooltip.css(adjustment); } adjustmentTimeout = null; }, 0, false); // first time through tt element will have the // uib-position-measure class or if the placement // has changed we need to position the arrow. if (tooltip.hasClass('uib-position-measure')) { $position.positionArrow(tooltip, ttPosition.placement); tooltip.removeClass('uib-position-measure'); } else if (lastPlacement !== ttPosition.placement) { $position.positionArrow(tooltip, ttPosition.placement); } lastPlacement = ttPosition.placement; positionTimeout = null; }, 0, false); } }; // Set up the correct scope to allow transclusion later ttScope.origScope = scope; // By default, the tooltip is not open. // TODO add ability to start tooltip opened ttScope.isOpen = false; function toggleTooltipBind() { if (!ttScope.isOpen) { showTooltipBind(); } else { hideTooltipBind(); } } // Show the tooltip with delay if specified, otherwise show it immediately function showTooltipBind() { if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { return; } cancelHide(); prepareTooltip(); if (ttScope.popupDelay) { // Do nothing if the tooltip was already scheduled to pop-up. // This happens if show is triggered multiple times before any hide is triggered. if (!showTimeout) { showTimeout = $timeout(show, ttScope.popupDelay, false); } } else { show(); } } function hideTooltipBind() { cancelShow(); if (ttScope.popupCloseDelay) { if (!hideTimeout) { hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false); } } else { hide(); } } // Show the tooltip popup element. function show() { cancelShow(); cancelHide(); // Don't show empty tooltips. if (!ttScope.content) { return angular.noop; } createTooltip(); // And show the tooltip. ttScope.$evalAsync(function() { ttScope.isOpen = true; assignIsOpen(true); positionTooltip(); }); } function cancelShow() { if (showTimeout) { $timeout.cancel(showTimeout); showTimeout = null; } if (positionTimeout) { $timeout.cancel(positionTimeout); positionTimeout = null; } } // Hide the tooltip popup element. function hide() { if (!ttScope) { return; } // First things first: we don't show it anymore. ttScope.$evalAsync(function() { if (ttScope) { ttScope.isOpen = false; assignIsOpen(false); // And now we remove it from the DOM. However, if we have animation, we // need to wait for it to expire beforehand. // FIXME: this is a placeholder for a port of the transitions library. // The fade transition in TWBS is 150ms. if (ttScope.animation) { if (!transitionTimeout) { transitionTimeout = $timeout(removeTooltip, 150, false); } } else { removeTooltip(); } } }); } function cancelHide() { if (hideTimeout) { $timeout.cancel(hideTimeout); hideTimeout = null; } if (transitionTimeout) { $timeout.cancel(transitionTimeout); transitionTimeout = null; } } function createTooltip() { // There can only be one tooltip element per directive shown at once. if (tooltip) { return; } tooltipLinkedScope = ttScope.$new(); tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { if (appendToBody) { $document.find('body').append(tooltip); } else { element.after(tooltip); } }); openedTooltips.add(ttScope, { close: hide }); prepObservers(); } function removeTooltip() { cancelShow(); cancelHide(); unregisterObservers(); if (tooltip) { tooltip.remove(); tooltip = null; if (adjustmentTimeout) { $timeout.cancel(adjustmentTimeout); } } openedTooltips.remove(ttScope); if (tooltipLinkedScope) { tooltipLinkedScope.$destroy(); tooltipLinkedScope = null; } } /** * Set the initial scope values. Once * the tooltip is created, the observers * will be added to keep things in sync. */ function prepareTooltip() { ttScope.title = attrs[prefix + 'Title']; if (contentParse) { ttScope.content = contentParse(scope); } else { ttScope.content = attrs[ttType]; } ttScope.popupClass = attrs[prefix + 'Class']; ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; var placement = $position.parsePlacement(ttScope.placement); lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay; } function assignIsOpen(isOpen) { if (isOpenParse && angular.isFunction(isOpenParse.assign)) { isOpenParse.assign(scope, isOpen); } } ttScope.contentExp = function() { return ttScope.content; }; /** * Observe the relevant attributes. */ attrs.$observe('disabled', function(val) { if (val) { cancelShow(); } if (val && ttScope.isOpen) { hide(); } }); if (isOpenParse) { scope.$watch(isOpenParse, function(val) { if (ttScope && !val === ttScope.isOpen) { toggleTooltipBind(); } }); } function prepObservers() { observers.length = 0; if (contentParse) { observers.push( scope.$watch(contentParse, function(val) { ttScope.content = val; if (!val && ttScope.isOpen) { hide(); } }) ); observers.push( tooltipLinkedScope.$watch(function() { if (!repositionScheduled) { repositionScheduled = true; tooltipLinkedScope.$$postDigest(function() { repositionScheduled = false; if (ttScope && ttScope.isOpen) { positionTooltip(); } }); } }) ); } else { observers.push( attrs.$observe(ttType, function(val) { ttScope.content = val; if (!val && ttScope.isOpen) { hide(); } else { positionTooltip(); } }) ); } observers.push( attrs.$observe(prefix + 'Title', function(val) { ttScope.title = val; if (ttScope.isOpen) { positionTooltip(); } }) ); observers.push( attrs.$observe(prefix + 'Placement', function(val) { ttScope.placement = val ? val : options.placement; if (ttScope.isOpen) { positionTooltip(); } }) ); } function unregisterObservers() { if (observers.length) { angular.forEach(observers, function(observer) { observer(); }); observers.length = 0; } } // hide tooltips/popovers for outsideClick trigger function bodyHideTooltipBind(e) { if (!ttScope || !ttScope.isOpen || !tooltip) { return; } // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) { hideTooltipBind(); } } // KeyboardEvent handler to hide the tooltip on Escape key press function hideOnEscapeKey(e) { if (e.which === 27) { hideTooltipBind(); } } var unregisterTriggers = function() { triggers.show.forEach(function(trigger) { if (trigger === 'outsideClick') { element.off('click', toggleTooltipBind); } else { element.off(trigger, showTooltipBind); element.off(trigger, toggleTooltipBind); } element.off('keypress', hideOnEscapeKey); }); triggers.hide.forEach(function(trigger) { if (trigger === 'outsideClick') { $document.off('click', bodyHideTooltipBind); } else { element.off(trigger, hideTooltipBind); } }); }; function prepTriggers() { var showTriggers = [], hideTriggers = []; var val = scope.$eval(attrs[prefix + 'Trigger']); unregisterTriggers(); if (angular.isObject(val)) { Object.keys(val).forEach(function(key) { showTriggers.push(key); hideTriggers.push(val[key]); }); triggers = { show: showTriggers, hide: hideTriggers }; } else { triggers = getTriggers(val); } if (triggers.show !== 'none') { triggers.show.forEach(function(trigger, idx) { if (trigger === 'outsideClick') { element.on('click', toggleTooltipBind); $document.on('click', bodyHideTooltipBind); } else if (trigger === triggers.hide[idx]) { element.on(trigger, toggleTooltipBind); } else if (trigger) { element.on(trigger, showTooltipBind); element.on(triggers.hide[idx], hideTooltipBind); } element.on('keypress', hideOnEscapeKey); }); } } prepTriggers(); var animation = scope.$eval(attrs[prefix + 'Animation']); ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; var appendToBodyVal; var appendKey = prefix + 'AppendToBody'; if (appendKey in attrs && attrs[appendKey] === undefined) { appendToBodyVal = true; } else { appendToBodyVal = scope.$eval(attrs[appendKey]); } appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTooltip() { unregisterTriggers(); removeTooltip(); ttScope = null; }); }; } }; }; }]; }) // This is mostly ngInclude code but with a custom scope .directive('uibTooltipTemplateTransclude', [ '$animate', '$sce', '$compile', '$templateRequest', function ($animate, $sce, $compile, $templateRequest) { return { link: function(scope, elem, attrs) { var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); var changeCounter = 0, currentScope, previousElement, currentElement; var cleanupLastIncludeContent = function() { if (previousElement) { previousElement.remove(); previousElement = null; } if (currentScope) { currentScope.$destroy(); currentScope = null; } if (currentElement) { $animate.leave(currentElement).then(function() { previousElement = null; }); previousElement = currentElement; currentElement = null; } }; scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) { var thisChangeId = ++changeCounter; if (src) { //set the 2nd param to true to ignore the template request error so that the inner //contents and scope can be cleaned up. $templateRequest(src, true).then(function(response) { if (thisChangeId !== changeCounter) { return; } var newScope = origScope.$new(); var template = response; var clone = $compile(template)(newScope, function(clone) { cleanupLastIncludeContent(); $animate.enter(clone, elem); }); currentScope = newScope; currentElement = clone; currentScope.$emit('$includeContentLoaded', src); }, function() { if (thisChangeId === changeCounter) { cleanupLastIncludeContent(); scope.$emit('$includeContentError', src); } }); scope.$emit('$includeContentRequested', src); } else { cleanupLastIncludeContent(); } }); scope.$on('$destroy', cleanupLastIncludeContent); } }; }]) /** * Note that it's intentional that these classes are *not* applied through $animate. * They must not be animated as they're expected to be present on the tooltip on * initialization. */ .directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) { return { restrict: 'A', link: function(scope, element, attrs) { // need to set the primary position so the // arrow has space during position measure. // tooltip.positionTooltip() if (scope.placement) { // // There are no top-left etc... classes // // in TWBS, so we need the primary position. var position = $uibPosition.parsePlacement(scope.placement); element.addClass(position[0]); } if (scope.popupClass) { element.addClass(scope.popupClass); } if (scope.animation) { element.addClass(attrs.tooltipAnimationClass); } } }; }]) .directive('uibTooltipPopup', function() { return { restrict: 'A', scope: { content: '@' }, templateUrl: 'uib/template/tooltip/tooltip-popup.html' }; }) .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) { return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter'); }]) .directive('uibTooltipTemplatePopup', function() { return { restrict: 'A', scope: { contentExp: '&', originScope: '&' }, templateUrl: 'uib/template/tooltip/tooltip-template-popup.html' }; }) .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) { return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', { useContentExp: true }); }]) .directive('uibTooltipHtmlPopup', function() { return { restrict: 'A', scope: { contentExp: '&' }, templateUrl: 'uib/template/tooltip/tooltip-html-popup.html' }; }) .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) { return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', { useContentExp: true }); }]); /** * The following features are still outstanding: popup delay, animation as a * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, and selector delegatation. */ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip']) .directive('uibPopoverTemplatePopup', function() { return { restrict: 'A', scope: { uibTitle: '@', contentExp: '&', originScope: '&' }, templateUrl: 'uib/template/popover/popover-template.html' }; }) .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) { return $uibTooltip('uibPopoverTemplate', 'popover', 'click', { useContentExp: true }); }]) .directive('uibPopoverHtmlPopup', function() { return { restrict: 'A', scope: { contentExp: '&', uibTitle: '@' }, templateUrl: 'uib/template/popover/popover-html.html' }; }) .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) { return $uibTooltip('uibPopoverHtml', 'popover', 'click', { useContentExp: true }); }]) .directive('uibPopoverPopup', function() { return { restrict: 'A', scope: { uibTitle: '@', content: '@' }, templateUrl: 'uib/template/popover/popover.html' }; }) .directive('uibPopover', ['$uibTooltip', function($uibTooltip) { return $uibTooltip('uibPopover', 'popover', 'click'); }]); angular.module('ui.bootstrap.progressbar', []) .constant('uibProgressConfig', { animate: true, max: 100 }) .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) { var self = this, animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; this.bars = []; $scope.max = getMaxOrDefault(); this.addBar = function(bar, element, attrs) { if (!animate) { element.css({'transition': 'none'}); } this.bars.push(bar); bar.max = getMaxOrDefault(); bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; bar.$watch('value', function(value) { bar.recalculatePercentage(); }); bar.recalculatePercentage = function() { var totalPercentage = self.bars.reduce(function(total, bar) { bar.percent = +(100 * bar.value / bar.max).toFixed(2); return total + bar.percent; }, 0); if (totalPercentage > 100) { bar.percent -= totalPercentage - 100; } }; bar.$on('$destroy', function() { element = null; self.removeBar(bar); }); }; this.removeBar = function(bar) { this.bars.splice(this.bars.indexOf(bar), 1); this.bars.forEach(function (bar) { bar.recalculatePercentage(); }); }; //$attrs.$observe('maxParam', function(maxParam) { $scope.$watch('maxParam', function(maxParam) { self.bars.forEach(function(bar) { bar.max = getMaxOrDefault(); bar.recalculatePercentage(); }); }); function getMaxOrDefault () { return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max; } }]) .directive('uibProgress', function() { return { replace: true, transclude: true, controller: 'UibProgressController', require: 'uibProgress', scope: { maxParam: '=?max' }, templateUrl: 'uib/template/progressbar/progress.html' }; }) .directive('uibBar', function() { return { replace: true, transclude: true, require: '^uibProgress', scope: { value: '=', type: '@' }, templateUrl: 'uib/template/progressbar/bar.html', link: function(scope, element, attrs, progressCtrl) { progressCtrl.addBar(scope, element, attrs); } }; }) .directive('uibProgressbar', function() { return { replace: true, transclude: true, controller: 'UibProgressController', scope: { value: '=', maxParam: '=?max', type: '@' }, templateUrl: 'uib/template/progressbar/progressbar.html', link: function(scope, element, attrs, progressCtrl) { progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title}); } }; }); angular.module('ui.bootstrap.rating', []) .constant('uibRatingConfig', { max: 5, stateOn: null, stateOff: null, enableReset: true, titles: ['one', 'two', 'three', 'four', 'five'] }) .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) { var ngModelCtrl = { $setViewValue: angular.noop }, self = this; this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = this.render; ngModelCtrl.$formatters.push(function(value) { if (angular.isNumber(value) && value << 0 !== value) { value = Math.round(value); } return value; }); this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; this.enableReset = angular.isDefined($attrs.enableReset) ? $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset; var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles; this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ? tmpTitles : ratingConfig.titles; var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max); $scope.range = this.buildTemplateObjects(ratingStates); }; this.buildTemplateObjects = function(states) { for (var i = 0, n = states.length; i < n; i++) { states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]); } return states; }; this.getTitle = function(index) { if (index >= this.titles.length) { return index + 1; } return this.titles[index]; }; $scope.rate = function(value) { if (!$scope.readonly && value >= 0 && value <= $scope.range.length) { var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value; ngModelCtrl.$setViewValue(newViewValue); ngModelCtrl.$render(); } }; $scope.enter = function(value) { if (!$scope.readonly) { $scope.value = value; } $scope.onHover({value: value}); }; $scope.reset = function() { $scope.value = ngModelCtrl.$viewValue; $scope.onLeave(); }; $scope.onKeydown = function(evt) { if (/(37|38|39|40)/.test(evt.which)) { evt.preventDefault(); evt.stopPropagation(); $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1)); } }; this.render = function() { $scope.value = ngModelCtrl.$viewValue; $scope.title = self.getTitle($scope.value - 1); }; }]) .directive('uibRating', function() { return { require: ['uibRating', 'ngModel'], restrict: 'A', scope: { readonly: '=?readOnly', onHover: '&', onLeave: '&' }, controller: 'UibRatingController', templateUrl: 'uib/template/rating/rating.html', link: function(scope, element, attrs, ctrls) { var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; ratingCtrl.init(ngModelCtrl); } }; }); angular.module('ui.bootstrap.tabs', []) .controller('UibTabsetController', ['$scope', function ($scope) { var ctrl = this, oldIndex; ctrl.tabs = []; ctrl.select = function(index, evt) { if (!destroyed) { var previousIndex = findTabIndex(oldIndex); var previousSelected = ctrl.tabs[previousIndex]; if (previousSelected) { previousSelected.tab.onDeselect({ $event: evt, $selectedIndex: index }); if (evt && evt.isDefaultPrevented()) { return; } previousSelected.tab.active = false; } var selected = ctrl.tabs[index]; if (selected) { selected.tab.onSelect({ $event: evt }); selected.tab.active = true; ctrl.active = selected.index; oldIndex = selected.index; } else if (!selected && angular.isDefined(oldIndex)) { ctrl.active = null; oldIndex = null; } } }; ctrl.addTab = function addTab(tab) { ctrl.tabs.push({ tab: tab, index: tab.index }); ctrl.tabs.sort(function(t1, t2) { if (t1.index > t2.index) { return 1; } if (t1.index < t2.index) { return -1; } return 0; }); if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) { var newActiveIndex = findTabIndex(tab.index); ctrl.select(newActiveIndex); } }; ctrl.removeTab = function removeTab(tab) { var index; for (var i = 0; i < ctrl.tabs.length; i++) { if (ctrl.tabs[i].tab === tab) { index = i; break; } } if (ctrl.tabs[index].index === ctrl.active) { var newActiveTabIndex = index === ctrl.tabs.length - 1 ? index - 1 : index + 1 % ctrl.tabs.length; ctrl.select(newActiveTabIndex); } ctrl.tabs.splice(index, 1); }; $scope.$watch('tabset.active', function(val) { if (angular.isDefined(val) && val !== oldIndex) { ctrl.select(findTabIndex(val)); } }); var destroyed; $scope.$on('$destroy', function() { destroyed = true; }); function findTabIndex(index) { for (var i = 0; i < ctrl.tabs.length; i++) { if (ctrl.tabs[i].index === index) { return i; } } } }]) .directive('uibTabset', function() { return { transclude: true, replace: true, scope: {}, bindToController: { active: '=?', type: '@' }, controller: 'UibTabsetController', controllerAs: 'tabset', templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/tabs/tabset.html'; }, link: function(scope, element, attrs) { scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; } }; }) .directive('uibTab', ['$parse', function($parse) { return { require: '^uibTabset', replace: true, templateUrl: function(element, attrs) { return attrs.templateUrl || 'uib/template/tabs/tab.html'; }, transclude: true, scope: { heading: '@', index: '=?', classes: '@?', onSelect: '&select', //This callback is called in contentHeadingTransclude //once it inserts the tab's content into the dom onDeselect: '&deselect' }, controller: function() { //Empty controller so other directives can require being 'under' a tab }, controllerAs: 'tab', link: function(scope, elm, attrs, tabsetCtrl, transclude) { scope.disabled = false; if (attrs.disable) { scope.$parent.$watch($parse(attrs.disable), function(value) { scope.disabled = !! value; }); } if (angular.isUndefined(attrs.index)) { if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) { scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1; } else { scope.index = 0; } } if (angular.isUndefined(attrs.classes)) { scope.classes = ''; } scope.select = function(evt) { if (!scope.disabled) { var index; for (var i = 0; i < tabsetCtrl.tabs.length; i++) { if (tabsetCtrl.tabs[i].tab === scope) { index = i; break; } } tabsetCtrl.select(index, evt); } }; tabsetCtrl.addTab(scope); scope.$on('$destroy', function() { tabsetCtrl.removeTab(scope); }); //We need to transclude later, once the content container is ready. //when this link happens, we're inside a tab heading. scope.$transcludeFn = transclude; } }; }]) .directive('uibTabHeadingTransclude', function() { return { restrict: 'A', require: '^uibTab', link: function(scope, elm) { scope.$watch('headingElement', function updateHeadingElement(heading) { if (heading) { elm.html(''); elm.append(heading); } }); } }; }) .directive('uibTabContentTransclude', function() { return { restrict: 'A', require: '^uibTabset', link: function(scope, elm, attrs) { var tab = scope.$eval(attrs.uibTabContentTransclude).tab; //Now our tab is ready to be transcluded: both the tab heading area //and the tab content area are loaded. Transclude 'em both. tab.$transcludeFn(tab.$parent, function(contents) { angular.forEach(contents, function(node) { if (isTabHeading(node)) { //Let tabHeadingTransclude know. tab.headingElement = node; } else { elm.append(node); } }); }); } }; function isTabHeading(node) { return node.tagName && ( node.hasAttribute('uib-tab-heading') || node.hasAttribute('data-uib-tab-heading') || node.hasAttribute('x-uib-tab-heading') || node.tagName.toLowerCase() === 'uib-tab-heading' || node.tagName.toLowerCase() === 'data-uib-tab-heading' || node.tagName.toLowerCase() === 'x-uib-tab-heading' || node.tagName.toLowerCase() === 'uib:tab-heading' ); } }); angular.module('ui.bootstrap.timepicker', []) .constant('uibTimepickerConfig', { hourStep: 1, minuteStep: 1, secondStep: 1, showMeridian: true, showSeconds: false, meridians: null, readonlyInput: false, mousewheel: true, arrowkeys: true, showSpinners: true, templateUrl: 'uib/template/timepicker/timepicker.html' }) .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { var hoursModelCtrl, minutesModelCtrl, secondsModelCtrl; var selected = new Date(), watchers = [], ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS, padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true; $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; $element.removeAttr('tabindex'); this.init = function(ngModelCtrl_, inputs) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = this.render; ngModelCtrl.$formatters.unshift(function(modelValue) { return modelValue ? new Date(modelValue) : null; }); var hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1), secondsInputEl = inputs.eq(2); hoursModelCtrl = hoursInputEl.controller('ngModel'); minutesModelCtrl = minutesInputEl.controller('ngModel'); secondsModelCtrl = secondsInputEl.controller('ngModel'); var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; if (mousewheel) { this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl); } var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; if (arrowkeys) { this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl); } $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl); }; var hourStep = timepickerConfig.hourStep; if ($attrs.hourStep) { watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) { hourStep = +value; })); } var minuteStep = timepickerConfig.minuteStep; if ($attrs.minuteStep) { watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) { minuteStep = +value; })); } var min; watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) { var dt = new Date(value); min = isNaN(dt) ? undefined : dt; })); var max; watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) { var dt = new Date(value); max = isNaN(dt) ? undefined : dt; })); var disabled = false; if ($attrs.ngDisabled) { watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) { disabled = value; })); } $scope.noIncrementHours = function() { var incrementedSelected = addMinutes(selected, hourStep * 60); return disabled || incrementedSelected > max || incrementedSelected < selected && incrementedSelected < min; }; $scope.noDecrementHours = function() { var decrementedSelected = addMinutes(selected, -hourStep * 60); return disabled || decrementedSelected < min || decrementedSelected > selected && decrementedSelected > max; }; $scope.noIncrementMinutes = function() { var incrementedSelected = addMinutes(selected, minuteStep); return disabled || incrementedSelected > max || incrementedSelected < selected && incrementedSelected < min; }; $scope.noDecrementMinutes = function() { var decrementedSelected = addMinutes(selected, -minuteStep); return disabled || decrementedSelected < min || decrementedSelected > selected && decrementedSelected > max; }; $scope.noIncrementSeconds = function() { var incrementedSelected = addSeconds(selected, secondStep); return disabled || incrementedSelected > max || incrementedSelected < selected && incrementedSelected < min; }; $scope.noDecrementSeconds = function() { var decrementedSelected = addSeconds(selected, -secondStep); return disabled || decrementedSelected < min || decrementedSelected > selected && decrementedSelected > max; }; $scope.noToggleMeridian = function() { if (selected.getHours() < 12) { return disabled || addMinutes(selected, 12 * 60) > max; } return disabled || addMinutes(selected, -12 * 60) < min; }; var secondStep = timepickerConfig.secondStep; if ($attrs.secondStep) { watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) { secondStep = +value; })); } $scope.showSeconds = timepickerConfig.showSeconds; if ($attrs.showSeconds) { watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) { $scope.showSeconds = !!value; })); } // 12H / 24H mode $scope.showMeridian = timepickerConfig.showMeridian; if ($attrs.showMeridian) { watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) { $scope.showMeridian = !!value; if (ngModelCtrl.$error.time) { // Evaluate from template var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); if (angular.isDefined(hours) && angular.isDefined(minutes)) { selected.setHours(hours); refresh(); } } else { updateTemplate(); } })); } // Get $scope.hours in 24H mode if valid function getHoursFromTemplate() { var hours = +$scope.hours; var valid = $scope.showMeridian ? hours > 0 && hours < 13 : hours >= 0 && hours < 24; if (!valid || $scope.hours === '') { return undefined; } if ($scope.showMeridian) { if (hours === 12) { hours = 0; } if ($scope.meridian === meridians[1]) { hours = hours + 12; } } return hours; } function getMinutesFromTemplate() { var minutes = +$scope.minutes; var valid = minutes >= 0 && minutes < 60; if (!valid || $scope.minutes === '') { return undefined; } return minutes; } function getSecondsFromTemplate() { var seconds = +$scope.seconds; return seconds >= 0 && seconds < 60 ? seconds : undefined; } function pad(value, noPad) { if (value === null) { return ''; } return angular.isDefined(value) && value.toString().length < 2 && !noPad ? '0' + value : value.toString(); } // Respond on mousewheel spin this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { var isScrollingUp = function(e) { if (e.originalEvent) { e = e.originalEvent; } //pick correct delta variable depending on event var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY; return e.detail || delta > 0; }; hoursInputEl.on('mousewheel wheel', function(e) { if (!disabled) { $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); } e.preventDefault(); }); minutesInputEl.on('mousewheel wheel', function(e) { if (!disabled) { $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); } e.preventDefault(); }); secondsInputEl.on('mousewheel wheel', function(e) { if (!disabled) { $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds()); } e.preventDefault(); }); }; // Respond on up/down arrowkeys this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { hoursInputEl.on('keydown', function(e) { if (!disabled) { if (e.which === 38) { // up e.preventDefault(); $scope.incrementHours(); $scope.$apply(); } else if (e.which === 40) { // down e.preventDefault(); $scope.decrementHours(); $scope.$apply(); } } }); minutesInputEl.on('keydown', function(e) { if (!disabled) { if (e.which === 38) { // up e.preventDefault(); $scope.incrementMinutes(); $scope.$apply(); } else if (e.which === 40) { // down e.preventDefault(); $scope.decrementMinutes(); $scope.$apply(); } } }); secondsInputEl.on('keydown', function(e) { if (!disabled) { if (e.which === 38) { // up e.preventDefault(); $scope.incrementSeconds(); $scope.$apply(); } else if (e.which === 40) { // down e.preventDefault(); $scope.decrementSeconds(); $scope.$apply(); } } }); }; this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { if ($scope.readonlyInput) { $scope.updateHours = angular.noop; $scope.updateMinutes = angular.noop; $scope.updateSeconds = angular.noop; return; } var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) { ngModelCtrl.$setViewValue(null); ngModelCtrl.$setValidity('time', false); if (angular.isDefined(invalidHours)) { $scope.invalidHours = invalidHours; if (hoursModelCtrl) { hoursModelCtrl.$setValidity('hours', false); } } if (angular.isDefined(invalidMinutes)) { $scope.invalidMinutes = invalidMinutes; if (minutesModelCtrl) { minutesModelCtrl.$setValidity('minutes', false); } } if (angular.isDefined(invalidSeconds)) { $scope.invalidSeconds = invalidSeconds; if (secondsModelCtrl) { secondsModelCtrl.$setValidity('seconds', false); } } }; $scope.updateHours = function() { var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); ngModelCtrl.$setDirty(); if (angular.isDefined(hours) && angular.isDefined(minutes)) { selected.setHours(hours); selected.setMinutes(minutes); if (selected < min || selected > max) { invalidate(true); } else { refresh('h'); } } else { invalidate(true); } }; hoursInputEl.on('blur', function(e) { ngModelCtrl.$setTouched(); if (modelIsEmpty()) { makeValid(); } else if ($scope.hours === null || $scope.hours === '') { invalidate(true); } else if (!$scope.invalidHours && $scope.hours < 10) { $scope.$apply(function() { $scope.hours = pad($scope.hours, !padHours); }); } }); $scope.updateMinutes = function() { var minutes = getMinutesFromTemplate(), hours = getHoursFromTemplate(); ngModelCtrl.$setDirty(); if (angular.isDefined(minutes) && angular.isDefined(hours)) { selected.setHours(hours); selected.setMinutes(minutes); if (selected < min || selected > max) { invalidate(undefined, true); } else { refresh('m'); } } else { invalidate(undefined, true); } }; minutesInputEl.on('blur', function(e) { ngModelCtrl.$setTouched(); if (modelIsEmpty()) { makeValid(); } else if ($scope.minutes === null) { invalidate(undefined, true); } else if (!$scope.invalidMinutes && $scope.minutes < 10) { $scope.$apply(function() { $scope.minutes = pad($scope.minutes); }); } }); $scope.updateSeconds = function() { var seconds = getSecondsFromTemplate(); ngModelCtrl.$setDirty(); if (angular.isDefined(seconds)) { selected.setSeconds(seconds); refresh('s'); } else { invalidate(undefined, undefined, true); } }; secondsInputEl.on('blur', function(e) { if (modelIsEmpty()) { makeValid(); } else if (!$scope.invalidSeconds && $scope.seconds < 10) { $scope.$apply( function() { $scope.seconds = pad($scope.seconds); }); } }); }; this.render = function() { var date = ngModelCtrl.$viewValue; if (isNaN(date)) { ngModelCtrl.$setValidity('time', false); $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); } else { if (date) { selected = date; } if (selected < min || selected > max) { ngModelCtrl.$setValidity('time', false); $scope.invalidHours = true; $scope.invalidMinutes = true; } else { makeValid(); } updateTemplate(); } }; // Call internally when we know that model is valid. function refresh(keyboardChange) { makeValid(); ngModelCtrl.$setViewValue(new Date(selected)); updateTemplate(keyboardChange); } function makeValid() { if (hoursModelCtrl) { hoursModelCtrl.$setValidity('hours', true); } if (minutesModelCtrl) { minutesModelCtrl.$setValidity('minutes', true); } if (secondsModelCtrl) { secondsModelCtrl.$setValidity('seconds', true); } ngModelCtrl.$setValidity('time', true); $scope.invalidHours = false; $scope.invalidMinutes = false; $scope.invalidSeconds = false; } function updateTemplate(keyboardChange) { if (!ngModelCtrl.$modelValue) { $scope.hours = null; $scope.minutes = null; $scope.seconds = null; $scope.meridian = meridians[0]; } else { var hours = selected.getHours(), minutes = selected.getMinutes(), seconds = selected.getSeconds(); if ($scope.showMeridian) { hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system } $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours); if (keyboardChange !== 'm') { $scope.minutes = pad(minutes); } $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; if (keyboardChange !== 's') { $scope.seconds = pad(seconds); } $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; } } function addSecondsToSelected(seconds) { selected = addSeconds(selected, seconds); refresh(); } function addMinutes(selected, minutes) { return addSeconds(selected, minutes*60); } function addSeconds(date, seconds) { var dt = new Date(date.getTime() + seconds * 1000); var newDate = new Date(date); newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds()); return newDate; } function modelIsEmpty() { return ($scope.hours === null || $scope.hours === '') && ($scope.minutes === null || $scope.minutes === '') && (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === '')); } $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; $scope.incrementHours = function() { if (!$scope.noIncrementHours()) { addSecondsToSelected(hourStep * 60 * 60); } }; $scope.decrementHours = function() { if (!$scope.noDecrementHours()) { addSecondsToSelected(-hourStep * 60 * 60); } }; $scope.incrementMinutes = function() { if (!$scope.noIncrementMinutes()) { addSecondsToSelected(minuteStep * 60); } }; $scope.decrementMinutes = function() { if (!$scope.noDecrementMinutes()) { addSecondsToSelected(-minuteStep * 60); } }; $scope.incrementSeconds = function() { if (!$scope.noIncrementSeconds()) { addSecondsToSelected(secondStep); } }; $scope.decrementSeconds = function() { if (!$scope.noDecrementSeconds()) { addSecondsToSelected(-secondStep); } }; $scope.toggleMeridian = function() { var minutes = getMinutesFromTemplate(), hours = getHoursFromTemplate(); if (!$scope.noToggleMeridian()) { if (angular.isDefined(minutes) && angular.isDefined(hours)) { addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60)); } else { $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0]; } } }; $scope.blur = function() { ngModelCtrl.$setTouched(); }; $scope.$on('$destroy', function() { while (watchers.length) { watchers.shift()(); } }); }]) .directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) { return { require: ['uibTimepicker', '?^ngModel'], restrict: 'A', controller: 'UibTimepickerController', controllerAs: 'timepicker', scope: {}, templateUrl: function(element, attrs) { return attrs.templateUrl || uibTimepickerConfig.templateUrl; }, link: function(scope, element, attrs, ctrls) { var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; if (ngModelCtrl) { timepickerCtrl.init(ngModelCtrl, element.find('input')); } } }; }]); angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position']) /** * A helper service that can parse typeahead's syntax (string provided by users) * Extracted to a separate service for ease of unit testing */ .factory('uibTypeaheadParser', ['$parse', function($parse) { // 000001111111100000000000002222222200000000000000003333333333333330000000000044444444000 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; return { parse: function(input) { var match = input.match(TYPEAHEAD_REGEXP); if (!match) { throw new Error( 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + ' but got "' + input + '".'); } return { itemName: match[3], source: $parse(match[4]), viewMapper: $parse(match[2] || match[1]), modelMapper: $parse(match[1]) }; } }; }]) .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser', function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) { var HOT_KEYS = [9, 13, 27, 38, 40]; var eventDebounceTime = 200; var modelCtrl, ngModelOptions; //SUPPORTED ATTRIBUTES (OPTIONS) //minimal no of characters that needs to be entered before typeahead kicks-in var minLength = originalScope.$eval(attrs.typeaheadMinLength); if (!minLength && minLength !== 0) { minLength = 1; } originalScope.$watch(attrs.typeaheadMinLength, function (newVal) { minLength = !newVal && newVal !== 0 ? 1 : newVal; }); //minimal wait time after last character typed before typeahead kicks-in var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; //should it restrict model values to the ones selected from the popup only? var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; originalScope.$watch(attrs.typeaheadEditable, function (newVal) { isEditable = newVal !== false; }); //binding to a variable that indicates if matches are being retrieved asynchronously var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; //a function to determine if an event should cause selection var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) { var evt = vals.$event; return evt.which === 13 || evt.which === 9; }; //a callback executed when a match is selected var onSelectCallback = $parse(attrs.typeaheadOnSelect); //should it select highlighted popup value when losing focus? var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; //binding to a variable that indicates if there were no results after the query is completed var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop; var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; var appendTo = attrs.typeaheadAppendTo ? originalScope.$eval(attrs.typeaheadAppendTo) : null; var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; //If input matches an item of the list exactly, select it automatically var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; //binding to a variable that indicates if dropdown is open var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop; var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false; //INTERNAL VARIABLES //model setter executed upon match selection var parsedModel = $parse(attrs.ngModel); var invokeModelSetter = $parse(attrs.ngModel + '($$$p)'); var $setModelValue = function(scope, newValue) { if (angular.isFunction(parsedModel(originalScope)) && ngModelOptions.getOption('getterSetter')) { return invokeModelSetter(scope, {$$$p: newValue}); } return parsedModel.assign(scope, newValue); }; //expressions used by typeahead var parserResult = typeaheadParser.parse(attrs.uibTypeahead); var hasFocus; //Used to avoid bug in iOS webview where iOS keyboard does not fire //mousedown & mouseup events //Issue #3699 var selected; //create a child scope for the typeahead directive so we are not polluting original scope //with typeahead-specific data (matches, query etc.) var scope = originalScope.$new(); var offDestroy = originalScope.$on('$destroy', function() { scope.$destroy(); }); scope.$on('$destroy', offDestroy); // WAI-ARIA var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); element.attr({ 'aria-autocomplete': 'list', 'aria-expanded': false, 'aria-owns': popupId }); var inputsContainer, hintInputElem; //add read-only input to show hint if (showHint) { inputsContainer = angular.element('
      '); inputsContainer.css('position', 'relative'); element.after(inputsContainer); hintInputElem = element.clone(); hintInputElem.attr('placeholder', ''); hintInputElem.attr('tabindex', '-1'); hintInputElem.val(''); hintInputElem.css({ 'position': 'absolute', 'top': '0px', 'left': '0px', 'border-color': 'transparent', 'box-shadow': 'none', 'opacity': 1, 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)', 'color': '#999' }); element.css({ 'position': 'relative', 'vertical-align': 'top', 'background-color': 'transparent' }); if (hintInputElem.attr('id')) { hintInputElem.removeAttr('id'); // remove duplicate id if present. } inputsContainer.append(hintInputElem); hintInputElem.after(element); } //pop-up element used to display matches var popUpEl = angular.element('
      '); popUpEl.attr({ id: popupId, matches: 'matches', active: 'activeIdx', select: 'select(activeIdx, evt)', 'move-in-progress': 'moveInProgress', query: 'query', position: 'position', 'assign-is-open': 'assignIsOpen(isOpen)', debounce: 'debounceUpdate' }); //custom item template if (angular.isDefined(attrs.typeaheadTemplateUrl)) { popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); } if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); } var resetHint = function() { if (showHint) { hintInputElem.val(''); } }; var resetMatches = function() { scope.matches = []; scope.activeIdx = -1; element.attr('aria-expanded', false); resetHint(); }; var getMatchId = function(index) { return popupId + '-option-' + index; }; // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. // This attribute is added or removed automatically when the `activeIdx` changes. scope.$watch('activeIdx', function(index) { if (index < 0) { element.removeAttr('aria-activedescendant'); } else { element.attr('aria-activedescendant', getMatchId(index)); } }); var inputIsExactMatch = function(inputValue, index) { if (scope.matches.length > index && inputValue) { return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); } return false; }; var getMatchesAsync = function(inputValue, evt) { var locals = {$viewValue: inputValue}; isLoadingSetter(originalScope, true); isNoResultsSetter(originalScope, false); $q.when(parserResult.source(originalScope, locals)).then(function(matches) { //it might happen that several async queries were in progress if a user were typing fast //but we are interested only in responses that correspond to the current view value var onCurrentRequest = inputValue === modelCtrl.$viewValue; if (onCurrentRequest && hasFocus) { if (matches && matches.length > 0) { scope.activeIdx = focusFirst ? 0 : -1; isNoResultsSetter(originalScope, false); scope.matches.length = 0; //transform labels for (var i = 0; i < matches.length; i++) { locals[parserResult.itemName] = matches[i]; scope.matches.push({ id: getMatchId(i), label: parserResult.viewMapper(scope, locals), model: matches[i] }); } scope.query = inputValue; //position pop-up with matches - we need to re-calculate its position each time we are opening a window //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page //due to other elements being rendered recalculatePosition(); element.attr('aria-expanded', true); //Select the single remaining option if user input matches if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { $$debounce(function() { scope.select(0, evt); }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); } else { scope.select(0, evt); } } if (showHint) { var firstLabel = scope.matches[0].label; if (angular.isString(inputValue) && inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { hintInputElem.val(inputValue + firstLabel.slice(inputValue.length)); } else { hintInputElem.val(''); } } } else { resetMatches(); isNoResultsSetter(originalScope, true); } } if (onCurrentRequest) { isLoadingSetter(originalScope, false); } }, function() { resetMatches(); isLoadingSetter(originalScope, false); isNoResultsSetter(originalScope, true); }); }; // bind events only if appendToBody params exist - performance feature if (appendToBody) { angular.element($window).on('resize', fireRecalculating); $document.find('body').on('scroll', fireRecalculating); } // Declare the debounced function outside recalculating for // proper debouncing var debouncedRecalculate = $$debounce(function() { // if popup is visible if (scope.matches.length) { recalculatePosition(); } scope.moveInProgress = false; }, eventDebounceTime); // Default progress type scope.moveInProgress = false; function fireRecalculating() { if (!scope.moveInProgress) { scope.moveInProgress = true; scope.$digest(); } debouncedRecalculate(); } // recalculate actual position and set new values to scope // after digest loop is popup in right position function recalculatePosition() { scope.position = appendToBody ? $position.offset(element) : $position.position(element); scope.position.top += element.prop('offsetHeight'); } //we need to propagate user's query so we can higlight matches scope.query = undefined; //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later var timeoutPromise; var scheduleSearchWithTimeout = function(inputValue) { timeoutPromise = $timeout(function() { getMatchesAsync(inputValue); }, waitTime); }; var cancelPreviousTimeout = function() { if (timeoutPromise) { $timeout.cancel(timeoutPromise); } }; resetMatches(); scope.assignIsOpen = function (isOpen) { isOpenSetter(originalScope, isOpen); }; scope.select = function(activeIdx, evt) { //called from within the $digest() cycle var locals = {}; var model, item; selected = true; locals[parserResult.itemName] = item = scope.matches[activeIdx].model; model = parserResult.modelMapper(originalScope, locals); $setModelValue(originalScope, model); modelCtrl.$setValidity('editable', true); modelCtrl.$setValidity('parse', true); onSelectCallback(originalScope, { $item: item, $model: model, $label: parserResult.viewMapper(originalScope, locals), $event: evt }); resetMatches(); //return focus to the input element if a match was selected via a mouse click event // use timeout to avoid $rootScope:inprog error if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { $timeout(function() { element[0].focus(); }, 0, false); } }; //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) element.on('keydown', function(evt) { //typeahead is open and an "interesting" key was pressed if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { return; } var shouldSelect = isSelectEvent(originalScope, {$event: evt}); /** * if there's nothing selected (i.e. focusFirst) and enter or tab is hit * or * shift + tab is pressed to bring focus to the previous element * then clear the results */ if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) { resetMatches(); scope.$digest(); return; } evt.preventDefault(); var target; switch (evt.which) { case 27: // escape evt.stopPropagation(); resetMatches(); originalScope.$digest(); break; case 38: // up arrow scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; scope.$digest(); target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx]; target.parentNode.scrollTop = target.offsetTop; break; case 40: // down arrow scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; scope.$digest(); target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx]; target.parentNode.scrollTop = target.offsetTop; break; default: if (shouldSelect) { scope.$apply(function() { if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { $$debounce(function() { scope.select(scope.activeIdx, evt); }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); } else { scope.select(scope.activeIdx, evt); } }); } } }); element.on('focus', function (evt) { hasFocus = true; if (minLength === 0 && !modelCtrl.$viewValue) { $timeout(function() { getMatchesAsync(modelCtrl.$viewValue, evt); }, 0); } }); element.on('blur', function(evt) { if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { selected = true; scope.$apply(function() { if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) { $$debounce(function() { scope.select(scope.activeIdx, evt); }, scope.debounceUpdate.blur); } else { scope.select(scope.activeIdx, evt); } }); } if (!isEditable && modelCtrl.$error.editable) { modelCtrl.$setViewValue(); scope.$apply(function() { // Reset validity as we are clearing modelCtrl.$setValidity('editable', true); modelCtrl.$setValidity('parse', true); }); element.val(''); } hasFocus = false; selected = false; }); // Keep reference to click handler to unbind it. var dismissClickHandler = function(evt) { // Issue #3973 // Firefox treats right click as a click on document if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { resetMatches(); if (!$rootScope.$$phase) { originalScope.$digest(); } } }; $document.on('click', dismissClickHandler); originalScope.$on('$destroy', function() { $document.off('click', dismissClickHandler); if (appendToBody || appendTo) { $popup.remove(); } if (appendToBody) { angular.element($window).off('resize', fireRecalculating); $document.find('body').off('scroll', fireRecalculating); } // Prevent jQuery cache memory leak popUpEl.remove(); if (showHint) { inputsContainer.remove(); } }); var $popup = $compile(popUpEl)(scope); if (appendToBody) { $document.find('body').append($popup); } else if (appendTo) { angular.element(appendTo).eq(0).append($popup); } else { element.after($popup); } this.init = function(_modelCtrl) { modelCtrl = _modelCtrl; ngModelOptions = extractOptions(modelCtrl); scope.debounceUpdate = $parse(ngModelOptions.getOption('debounce'))(originalScope); //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue modelCtrl.$parsers.unshift(function(inputValue) { hasFocus = true; if (minLength === 0 || inputValue && inputValue.length >= minLength) { if (waitTime > 0) { cancelPreviousTimeout(); scheduleSearchWithTimeout(inputValue); } else { getMatchesAsync(inputValue); } } else { isLoadingSetter(originalScope, false); cancelPreviousTimeout(); resetMatches(); } if (isEditable) { return inputValue; } if (!inputValue) { // Reset in case user had typed something previously. modelCtrl.$setValidity('editable', true); return null; } modelCtrl.$setValidity('editable', false); return undefined; }); modelCtrl.$formatters.push(function(modelValue) { var candidateViewValue, emptyViewValue; var locals = {}; // The validity may be set to false via $parsers (see above) if // the model is restricted to selected values. If the model // is set manually it is considered to be valid. if (!isEditable) { modelCtrl.$setValidity('editable', true); } if (inputFormatter) { locals.$model = modelValue; return inputFormatter(originalScope, locals); } //it might happen that we don't have enough info to properly render input value //we need to check for this situation and simply return model value if we can't apply custom formatting locals[parserResult.itemName] = modelValue; candidateViewValue = parserResult.viewMapper(originalScope, locals); locals[parserResult.itemName] = undefined; emptyViewValue = parserResult.viewMapper(originalScope, locals); return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; }); }; function extractOptions(ngModelCtrl) { var ngModelOptions; if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing // guarantee a value ngModelOptions = ngModelCtrl.$options || {}; // mimic 1.6+ api ngModelOptions.getOption = function (key) { return ngModelOptions[key]; }; } else { // in angular >=1.6 $options is always present ngModelOptions = ngModelCtrl.$options; } return ngModelOptions; } }]) .directive('uibTypeahead', function() { return { controller: 'UibTypeaheadController', require: ['ngModel', 'uibTypeahead'], link: function(originalScope, element, attrs, ctrls) { ctrls[1].init(ctrls[0]); } }; }) .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) { return { scope: { matches: '=', query: '=', active: '=', position: '&', moveInProgress: '=', select: '&', assignIsOpen: '&', debounce: '&' }, replace: true, templateUrl: function(element, attrs) { return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html'; }, link: function(scope, element, attrs) { scope.templateUrl = attrs.templateUrl; scope.isOpen = function() { var isDropdownOpen = scope.matches.length > 0; scope.assignIsOpen({ isOpen: isDropdownOpen }); return isDropdownOpen; }; scope.isActive = function(matchIdx) { return scope.active === matchIdx; }; scope.selectActive = function(matchIdx) { scope.active = matchIdx; }; scope.selectMatch = function(activeIdx, evt) { var debounce = scope.debounce(); if (angular.isNumber(debounce) || angular.isObject(debounce)) { $$debounce(function() { scope.select({activeIdx: activeIdx, evt: evt}); }, angular.isNumber(debounce) ? debounce : debounce['default']); } else { scope.select({activeIdx: activeIdx, evt: evt}); } }; } }; }]) .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) { return { scope: { index: '=', match: '=', query: '=' }, link: function(scope, element, attrs) { var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html'; $templateRequest(tplUrl).then(function(tplContent) { var tplEl = angular.element(tplContent.trim()); element.replaceWith(tplEl); $compile(tplEl)(scope); }); } }; }]) .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) { var isSanitizePresent; isSanitizePresent = $injector.has('$sanitize'); function escapeRegexp(queryToEscape) { // Regex: capture the whole query string and replace it with the string that will be used to match // the results, for example if the capture is "a" the result will be \a return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } function containsHtml(matchItem) { return /<.*>/g.test(matchItem); } return function(matchItem, query) { if (!isSanitizePresent && containsHtml(matchItem)) { $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger } matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag if (!isSanitizePresent) { matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive } return matchItem; }; }]); angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/accordion/accordion-group.html", "
      \n" + "

      \n" + " {{heading}}\n" + "

      \n" + "
      \n" + "
      \n" + "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/accordion/accordion.html", "
      "); }]); angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/alert/alert.html", "\n" + "
      \n" + ""); }]); angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/carousel/carousel.html", "
      \n" + " 1\">\n" + " \n" + " previous\n" + "\n" + " 1\">\n" + " \n" + " next\n" + "\n" + "
        1\">\n" + "
      1. \n" + " slide {{ $index + 1 }} of {{ slides.length }}, currently active\n" + "
      2. \n" + "
      \n" + ""); }]); angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/carousel/slide.html", "
      \n" + ""); }]); angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/datepicker/datepicker.html", "
      \n" + "
      \n" + "
      \n" + "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/datepicker/day.html", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
      {{::label.abbr}}
      {{ weekNumbers[$index] }}\n" + " \n" + "
      \n" + ""); }]); angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/datepicker/month.html", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
      \n" + " \n" + "
      \n" + ""); }]); angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/datepicker/year.html", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
      \n" + " \n" + "
      \n" + ""); }]); angular.module("uib/template/datepickerPopup/popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/datepickerPopup/popup.html", "
        \n" + "
      • \n" + "
      • \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
      • \n" + "
      \n" + ""); }]); angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/modal/window.html", "
      \n" + ""); }]); angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/pager/pager.html", "

      Login History

      Presentation

      LemonLDAP::NG allows one to store user logins and login attempts in their persistent session.

      Users can see their own history in menu, if menu module Login history is enabled. Session history is always visible in session explorer for administrators.

      Configuration

      This feature can be enabled and configured in Manager, in General Parameters » Advanced Parameters » Login History. You can define how many logins and failed logins will be stored.

      A login is considered as successful if user get authenticated and is granted a session; as failed, if he fails to authenticate or if he is not allowed to open a session. In other cases which result on impossibility to authenticate user, to retrieve data or to create a session, nothing is stored.

      By default, login time and IP address are stored in history, and the error message prompted to the user for failed logins. It is possible to store any additional session data. For example to store authentication mode, you can set in Session data to store a new key $_auth with value Authentication mode. The value will be used to display the data.

      To allow the Login History tab in Menu, configure it in General Parameters > Portal > Menu > Modules (see portal menu configuration).

      You can also display a check box on the authentication form, to allow user to see their login history before being redirected to the protected application (see portal customization).

    • {{::getText('previous')}}
    • \n" + "
    • {{::getText('next')}}
    • \n" + ""); }]); angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/pagination/pagination.html", "
    • {{::getText('first')}}
    • \n" + "
    • {{::getText('previous')}}
    • \n" + "
    • {{page.text}}
    • \n" + "
    • {{::getText('next')}}
    • \n" + "
    • {{::getText('last')}}
    • \n" + ""); }]); angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/tooltip/tooltip-html-popup.html", "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/tooltip/tooltip-popup.html", "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/tooltip/tooltip-template-popup.html", "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/popover/popover-html.html", "
      \n" + "\n" + "
      \n" + "

      \n" + "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/popover/popover-template.html", "
      \n" + "\n" + "
      \n" + "

      \n" + "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/popover/popover.html", "
      \n" + "\n" + "
      \n" + "

      \n" + "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/progressbar/bar.html", "
      \n" + ""); }]); angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/progressbar/progress.html", "
      "); }]); angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/progressbar/progressbar.html", "
      \n" + "
      \n" + "
      \n" + ""); }]); angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/rating/rating.html", "\n" + " ({{ $index < value ? '*' : ' ' }})\n" + " \n" + "\n" + ""); }]); angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/tabs/tab.html", "
    • \n" + " {{heading}}\n" + "
    • \n" + ""); }]); angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/tabs/tabset.html", "
      \n" + "
        \n" + "
        \n" + "
        \n" + "
        \n" + "
        \n" + "
        \n" + ""); }]); angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/timepicker/timepicker.html", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
          
        \n" + " \n" + " :\n" + " \n" + " :\n" + " \n" + "
          
        \n" + ""); }]); angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/typeahead/typeahead-match.html", "\n" + ""); }]); angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("uib/template/typeahead/typeahead-popup.html", "
          \n" + "
        • \n" + "
          \n" + "
        • \n" + "
        \n" + ""); }]); angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && angular.element(document).find('head').prepend(''); angular.$$uibCarouselCss = true; }); angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibDatepickerCss = true; }); angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend(''); angular.$$uibPositionCss = true; }); angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend(''); angular.$$uibDatepickerpopupCss = true; }); angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend(''); angular.$$uibTooltipCss = true; }); angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend(''); angular.$$uibTimepickerCss = true; }); angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend(''); angular.$$uibTypeaheadCss = true; });ui-bootstrap-tpls.min.js000066400000000000000000003654401325274564300413270ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-bootstrap/* * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ * Version: 2.5.0 - 2017-01-28 * License: MIT */angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.collapse","ui.bootstrap.tabindex","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.multiMap","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/datepickerPopup/popup.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.collapse",[]).directive("uibCollapse",["$animate","$q","$parse","$injector",function(a,b,c,d){var e=d.has("$animateCss")?d.get("$animateCss"):null;return{link:function(d,f,g){function h(){r=!!("horizontal"in g),r?(s={width:""},t={width:"0"}):(s={height:""},t={height:"0"}),d.$eval(g.uibCollapse)||f.addClass("in").addClass("collapse").attr("aria-expanded",!0).attr("aria-hidden",!1).css(s)}function i(a){return r?{width:a.scrollWidth+"px"}:{height:a.scrollHeight+"px"}}function j(){f.hasClass("collapse")&&f.hasClass("in")||b.resolve(n(d)).then(function(){f.removeClass("collapse").addClass("collapsing").attr("aria-expanded",!0).attr("aria-hidden",!1),e?e(f,{addClass:"in",easing:"ease",css:{overflow:"hidden"},to:i(f[0])}).start()["finally"](k):a.addClass(f,"in",{css:{overflow:"hidden"},to:i(f[0])}).then(k)},angular.noop)}function k(){f.removeClass("collapsing").addClass("collapse").css(s),o(d)}function l(){return f.hasClass("collapse")||f.hasClass("in")?void b.resolve(p(d)).then(function(){f.css(i(f[0])).removeClass("collapse").addClass("collapsing").attr("aria-expanded",!1).attr("aria-hidden",!0),e?e(f,{removeClass:"in",to:t}).start()["finally"](m):a.removeClass(f,"in",{to:t}).then(m)},angular.noop):m()}function m(){f.css(t),f.removeClass("collapsing").addClass("collapse"),q(d)}var n=c(g.expanding),o=c(g.expanded),p=c(g.collapsing),q=c(g.collapsed),r=!1,s={},t={};h(),d.$watch(g.uibCollapse,function(a){a?l():j()})}}}]),angular.module("ui.bootstrap.tabindex",[]).directive("uibTabindexToggle",function(){return{restrict:"A",link:function(a,b,c){c.$observe("disabled",function(a){c.$set("tabindex",a?-1:null)})}}}),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse","ui.bootstrap.tabindex"]).constant("uibAccordionConfig",{closeOthers:!0}).controller("UibAccordionController",["$scope","$attrs","uibAccordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(c){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("uibAccordion",function(){return{controller:"UibAccordionController",controllerAs:"accordion",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion.html"}}}).directive("uibAccordionGroup",function(){return{require:"^uibAccordion",transclude:!0,restrict:"A",templateUrl:function(a,b){return b.templateUrl||"uib/template/accordion/accordion-group.html"},scope:{heading:"@",panelClass:"@?",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){b.addClass("panel"),d.addGroup(a),a.openClass=c.openClass||"panel-open",a.panelClass=c.panelClass||"panel-default",a.$watch("isOpen",function(c){b.toggleClass(a.openClass,!!c),c&&d.closeOthers(a)}),a.toggleOpen=function(b){a.isDisabled||b&&32!==b.which||(a.isOpen=!a.isOpen)};var e="accordiongroup-"+a.$id+"-"+Math.floor(1e4*Math.random());a.headingId=e+"-tab",a.panelId=e+"-panel"}}}).directive("uibAccordionHeading",function(){return{transclude:!0,template:"",replace:!0,require:"^uibAccordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,angular.noop))}}}).directive("uibAccordionTransclude",function(){function a(){return"uib-accordion-header,data-uib-accordion-header,x-uib-accordion-header,uib\\:accordion-header,[uib-accordion-header],[data-uib-accordion-header],[x-uib-accordion-header]"}return{require:"^uibAccordionGroup",link:function(b,c,d,e){b.$watch(function(){return e[d.uibAccordionTransclude]},function(b){if(b){var d=angular.element(c[0].querySelector(a()));d.html(""),d.append(b)}})}}}),angular.module("ui.bootstrap.alert",[]).controller("UibAlertController",["$scope","$element","$attrs","$interpolate","$timeout",function(a,b,c,d,e){a.closeable=!!c.close,b.addClass("alert"),c.$set("role","alert"),a.closeable&&b.addClass("alert-dismissible");var f=angular.isDefined(c.dismissOnTimeout)?d(c.dismissOnTimeout)(a.$parent):null;f&&e(function(){a.close()},parseInt(f,10))}]).directive("uibAlert",function(){return{controller:"UibAlertController",controllerAs:"alert",restrict:"A",templateUrl:function(a,b){return b.templateUrl||"uib/template/alert/alert.html"},transclude:!0,scope:{close:"&"}}}),angular.module("ui.bootstrap.buttons",[]).constant("uibButtonConfig",{activeClass:"active",toggleEvent:"click"}).controller("UibButtonsController",["uibButtonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("uibBtnRadio",["$parse",function(a){return{require:["uibBtnRadio","ngModel"],controller:"UibButtonsController",controllerAs:"buttons",link:function(b,c,d,e){var f=e[0],g=e[1],h=a(d.uibUncheckable);c.find("input").css({display:"none"}),g.$render=function(){c.toggleClass(f.activeClass,angular.equals(g.$modelValue,b.$eval(d.uibBtnRadio)))},c.on(f.toggleEvent,function(){if(!d.disabled){var a=c.hasClass(f.activeClass);a&&!angular.isDefined(d.uncheckable)||b.$apply(function(){g.$setViewValue(a?null:b.$eval(d.uibBtnRadio)),g.$render()})}}),d.uibUncheckable&&b.$watch(h,function(a){d.$set("uncheckable",a?"":void 0)})}}}]).directive("uibBtnCheckbox",function(){return{require:["uibBtnCheckbox","ngModel"],controller:"UibButtonsController",controllerAs:"button",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){return angular.isDefined(b)?a.$eval(b):c}var h=d[0],i=d[1];b.find("input").css({display:"none"}),i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.on(h.toggleEvent,function(){c.disabled||a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",[]).controller("UibCarouselController",["$scope","$element","$interval","$timeout","$animate",function(a,b,c,d,e){function f(a){for(var b=0;b1){p[d].element.data(q,c.direction);var h=o.getCurrentIndex();angular.isNumber(h)&&p[h].element&&p[h].element.data(q,c.direction),a.$currentTransition=!0,e.on("addClass",p[d].element,function(b,c){"close"===c&&(a.$currentTransition=null,e.off("addClass",b))})}a.active=c.index,r=c.index,f(d),k()}}function h(a){for(var b=0;b0&&(m=c(l,b))}function l(){var b=+a.interval;n&&!isNaN(b)&&b>0&&p.length?a.next():a.pause()}var m,n,o=this,p=o.slides=a.slides=[],q="uib-slideDirection",r=a.active,s=!1;b.addClass("carousel"),o.addSlide=function(b,c){p.push({slide:b,element:c}),p.sort(function(a,b){return+a.slide.index-+b.slide.index}),(b.index===a.active||1===p.length&&!angular.isNumber(a.active))&&(a.$currentTransition&&(a.$currentTransition=null),r=b.index,a.active=b.index,f(r),o.select(p[h(b)]),1===p.length&&a.play())},o.getCurrentIndex=function(){for(var a=0;a0&&r===c?c>=p.length?(r=p.length-1,a.active=r,f(r),o.select(p[p.length-1])):(r=c,a.active=r,f(r),o.select(p[c])):r>c&&(r--,a.active=r),0===p.length&&(r=null,a.active=null)},o.select=a.select=function(b,c){var d=h(b.slide);void 0===c&&(c=d>o.getCurrentIndex()?"next":"prev"),b.slide.index===r||a.$currentTransition||g(b.slide,d,c)},a.indexOfSlide=function(a){return+a.slide.index},a.isActive=function(b){return a.active===b.slide.index},a.isPrevDisabled=function(){return 0===a.active&&a.noWrap()},a.isNextDisabled=function(){return a.active===p.length-1&&a.noWrap()},a.pause=function(){a.noPause||(n=!1,i())},a.play=function(){n||(n=!0,k())},b.on("mouseenter",a.pause),b.on("mouseleave",a.play),a.$on("$destroy",function(){s=!0,i()}),a.$watch("noTransition",function(a){e.enabled(b,!a)}),a.$watch("interval",k),a.$watchCollection("slides",j),a.$watch("active",function(a){if(angular.isNumber(a)&&r!==a){for(var b=0;b-1){var f=!1;a=a.split("");for(var g=e;g-1){a=a.split(""),c[e]="("+d.regex+")",a[e]="$";for(var f=e+1,g=e+d.key.length;g>f;f++)c[f]="",a[f]="$";a=a.join(""),b.push({index:e,key:d.key,apply:d.apply,matcher:d.regex})}}),{regex:new RegExp("^"+c.join("")+"$"),map:d(b,"index")}}function h(a){for(var b,c,d=[],e=0;e=a.length||"'"!==a.charAt(e+1))&&(d.push(i(a,c,e)),c=null);else if(e===a.length)for(;cc?!1:1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}function l(a){return parseInt(a,10)}function m(a,b){return a&&b?q(a,b):a}function n(a,b){return a&&b?q(a,b,!0):a}function o(a,b){a=a.replace(/:/g,"");var c=Date.parse("Jan 01, 1970 00:00:00 "+a)/6e4;return isNaN(c)?b:c}function p(a,b){return a=new Date(a.getTime()),a.setMinutes(a.getMinutes()+b),a}function q(a,b,c){c=c?-1:1;var d=a.getTimezoneOffset(),e=o(b,d);return p(a,c*(e-d))}var r,s,t=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;this.init=function(){r=b.id,this.parsers={},this.formatters={},s=[{key:"yyyy",regex:"\\d{4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yyyy")}},{key:"yy",regex:"\\d{2}",apply:function(a){a=+a,this.year=69>a?a+2e3:a+1900},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"yy")}},{key:"y",regex:"\\d{1,4}",apply:function(a){this.year=+a},formatter:function(a){var b=new Date;return b.setFullYear(Math.abs(a.getFullYear())),c(b,"y")}},{key:"M!",regex:"0?[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){var b=a.getMonth();return/^[0-9]$/.test(b)?c(a,"MM"):c(a,"M")}},{key:"MMMM",regex:b.DATETIME_FORMATS.MONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.MONTH.indexOf(a)},formatter:function(a){return c(a,"MMMM")}},{key:"MMM",regex:b.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.SHORTMONTH.indexOf(a)},formatter:function(a){return c(a,"MMM")}},{key:"MM",regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"MM")}},{key:"M",regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1},formatter:function(a){return c(a,"M")}},{key:"d!",regex:"[0-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){var b=a.getDate();return/^[1-9]$/.test(b)?c(a,"dd"):c(a,"d")}},{key:"dd",regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"dd")}},{key:"d",regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a},formatter:function(a){return c(a,"d")}},{key:"EEEE",regex:b.DATETIME_FORMATS.DAY.join("|"),formatter:function(a){return c(a,"EEEE")}},{key:"EEE",regex:b.DATETIME_FORMATS.SHORTDAY.join("|"),formatter:function(a){return c(a,"EEE")}},{key:"HH",regex:"(?:0|1)[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"HH")}},{key:"hh",regex:"0[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"hh")}},{key:"H",regex:"1?[0-9]|2[0-3]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"H")}},{key:"h",regex:"[0-9]|1[0-2]",apply:function(a){this.hours=+a},formatter:function(a){return c(a,"h")}},{key:"mm",regex:"[0-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"mm")}},{key:"m",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.minutes=+a},formatter:function(a){return c(a,"m")}},{key:"sss",regex:"[0-9][0-9][0-9]",apply:function(a){this.milliseconds=+a},formatter:function(a){return c(a,"sss")}},{key:"ss",regex:"[0-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"ss")}},{key:"s",regex:"[0-9]|[1-5][0-9]",apply:function(a){this.seconds=+a},formatter:function(a){return c(a,"s")}},{key:"a",regex:b.DATETIME_FORMATS.AMPMS.join("|"),apply:function(a){12===this.hours&&(this.hours=0),"PM"===a&&(this.hours+=12)},formatter:function(a){return c(a,"a")}},{key:"Z",regex:"[+-]\\d{4}",apply:function(a){var b=a.match(/([+-])(\d{2})(\d{2})/),c=b[1],d=b[2],e=b[3];this.hours+=l(c+d),this.minutes+=l(c+e)},formatter:function(a){return c(a,"Z")}},{key:"ww",regex:"[0-4][0-9]|5[0-3]",formatter:function(a){return c(a,"ww")}},{key:"w",regex:"[0-9]|[1-4][0-9]|5[0-3]",formatter:function(a){return c(a,"w")}},{key:"GGGG",regex:b.DATETIME_FORMATS.ERANAMES.join("|").replace(/\s/g,"\\s"),formatter:function(a){return c(a,"GGGG")}},{key:"GGG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GGG")}},{key:"GG",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"GG")}},{key:"G",regex:b.DATETIME_FORMATS.ERAS.join("|"),formatter:function(a){return c(a,"G")}}],angular.version.major>=1&&angular.version.minor>4&&s.push({key:"LLLL",regex:b.DATETIME_FORMATS.STANDALONEMONTH.join("|"),apply:function(a){this.month=b.DATETIME_FORMATS.STANDALONEMONTH.indexOf(a)},formatter:function(a){return c(a,"LLLL")}})},this.init(),this.getParser=function(a){var b=f(a);return b&&b.apply||null},this.overrideParser=function(a,b){var c=f(a);c&&angular.isFunction(b)&&(this.parsers={},c.apply=b)}.bind(this),this.filter=function(a,c){if(!angular.isDate(a)||isNaN(a)||!c)return"";c=b.DATETIME_FORMATS[c]||c,b.id!==r&&this.init(),this.formatters[c]||(this.formatters[c]=h(c));var d=this.formatters[c];return d.reduce(function(b,c){return b+c(a)},"")},this.parse=function(c,d,e){if(!angular.isString(c)||!d)return c;d=b.DATETIME_FORMATS[d]||d,d=d.replace(t,"\\$&"),b.id!==r&&this.init(),this.parsers[d]||(this.parsers[d]=g(d,"apply"));var f=this.parsers[d],h=f.regex,i=f.map,j=c.match(h),l=!1;if(j&&j.length){var m,n;angular.isDate(e)&&!isNaN(e.getTime())?m={year:e.getFullYear(),month:e.getMonth(),date:e.getDate(),hours:e.getHours(),minutes:e.getMinutes(),seconds:e.getSeconds(),milliseconds:e.getMilliseconds()}:(e&&a.warn("dateparser:","baseDate is not a valid date"),m={year:1900,month:0,date:1,hours:0,minutes:0,seconds:0,milliseconds:0});for(var o=1,p=j.length;p>o;o++){var q=i[o-1];"Z"===q.matcher&&(l=!0),q.apply&&q.apply.call(m,j[o])}var s=l?Date.prototype.setUTCFullYear:Date.prototype.setFullYear,u=l?Date.prototype.setUTCHours:Date.prototype.setHours;return k(m.year,m.month,m.date)&&(!angular.isDate(e)||isNaN(e.getTime())||l?(n=new Date(0),s.call(n,m.year,m.month,m.date),u.call(n,m.hours||0,m.minutes||0,m.seconds||0,m.milliseconds||0)):(n=new Date(e),s.call(n,m.year,m.month,m.date),u.call(n,m.hours,m.minutes,m.seconds,m.milliseconds))),n}},this.toTimezone=m,this.fromTimezone=n,this.timezoneToOffset=o,this.addDateMinutes=p,this.convertTimezoneToLocal=q}]),angular.module("ui.bootstrap.isClass",[]).directive("uibIsClass",["$animate",function(a){var b=/^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/,c=/^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;return{restrict:"A",compile:function(d,e){function f(a,b,c){i.push(a),j.push({scope:a,element:b}),o.forEach(function(b,c){g(b,a)}),a.$on("$destroy",h)}function g(b,d){var e=b.match(c),f=d.$eval(e[1]),g=e[2],h=k[b];if(!h){var i=function(b){var c=null;j.some(function(a){var d=a.scope.$eval(m);return d===b?(c=a,!0):void 0}),h.lastActivated!==c&&(h.lastActivated&&a.removeClass(h.lastActivated.element,f),c&&a.addClass(c.element,f),h.lastActivated=c)};k[b]=h={lastActivated:null,scope:d,watchFn:i,compareWithExp:g,watcher:d.$watch(g,i)}}h.watchFn(d.$eval(g))}function h(a){var b=a.targetScope,c=i.indexOf(b);if(i.splice(c,1),j.splice(c,1),i.length){var d=i[0];angular.forEach(k,function(a){a.scope===b&&(a.watcher=d.$watch(a.compareWithExp,a.watchFn),a.scope=d)})}else k={}}var i=[],j=[],k={},l=e.uibIsClass.match(b),m=l[2],n=l[1],o=n.split(",");return f}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.isClass"]).value("$datepickerSuppressError",!1).value("$datepickerLiteralWarning",!0).constant("uibDatepickerConfig",{datepickerMode:"day",formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",maxDate:null,maxMode:"year",minDate:null,minMode:"day",monthColumns:3,ngModelOptions:{},shortcutPropagation:!1,showWeeks:!0,yearColumns:5,yearRows:4}).controller("UibDatepickerController",["$scope","$element","$attrs","$parse","$interpolate","$locale","$log","dateFilter","uibDatepickerConfig","$datepickerLiteralWarning","$datepickerSuppressError","uibDateParser",function(a,b,c,d,e,f,g,h,i,j,k,l){function m(b){a.datepickerMode=b,a.datepickerOptions.datepickerMode=b}function n(b){var c;if(angular.version.minor<6)c=b.$options||a.datepickerOptions.ngModelOptions||i.ngModelOptions||{},c.getOption=function(a){return c[a]};else{var d=b.$options.getOption("timezone")||(a.datepickerOptions.ngModelOptions?a.datepickerOptions.ngModelOptions.timezone:null)||(i.ngModelOptions?i.ngModelOptions.timezone:null);c=b.$options.createChild(i.ngModelOptions).createChild(a.datepickerOptions.ngModelOptions).createChild(b.$options).createChild({timezone:d})}return c}var o=this,p={$setViewValue:angular.noop},q={},r=[];b.addClass("uib-datepicker"),c.$set("role","application"),a.datepickerOptions||(a.datepickerOptions={}),this.modes=["day","month","year"],["customClass","dateDisabled","datepickerMode","formatDay","formatDayHeader","formatDayTitle","formatMonth","formatMonthTitle","formatYear","maxDate","maxMode","minDate","minMode","monthColumns","showWeeks","shortcutPropagation","startingDay","yearColumns","yearRows"].forEach(function(b){switch(b){case"customClass":case"dateDisabled":a[b]=a.datepickerOptions[b]||angular.noop;break;case"datepickerMode":a.datepickerMode=angular.isDefined(a.datepickerOptions.datepickerMode)?a.datepickerOptions.datepickerMode:i.datepickerMode;break;case"formatDay":case"formatDayHeader":case"formatDayTitle":case"formatMonth":case"formatMonthTitle":case"formatYear":o[b]=angular.isDefined(a.datepickerOptions[b])?e(a.datepickerOptions[b])(a.$parent):i[b];break;case"monthColumns":case"showWeeks":case"shortcutPropagation":case"yearColumns":case"yearRows":o[b]=angular.isDefined(a.datepickerOptions[b])?a.datepickerOptions[b]:i[b];break;case"startingDay":angular.isDefined(a.datepickerOptions.startingDay)?o.startingDay=a.datepickerOptions.startingDay:angular.isNumber(i.startingDay)?o.startingDay=i.startingDay:o.startingDay=(f.DATETIME_FORMATS.FIRSTDAYOFWEEK+8)%7;break;case"maxDate":case"minDate":a.$watch("datepickerOptions."+b,function(a){a?angular.isDate(a)?o[b]=l.fromTimezone(new Date(a),q.getOption("timezone")):(j&&g.warn("Literal date support has been deprecated, please switch to date object usage"),o[b]=new Date(h(a,"medium"))):o[b]=i[b]?l.fromTimezone(new Date(i[b]),q.getOption("timezone")):null,o.refreshView()});break;case"maxMode":case"minMode":a.datepickerOptions[b]?a.$watch(function(){return a.datepickerOptions[b]},function(c){o[b]=a[b]=angular.isDefined(c)?c:a.datepickerOptions[b],("minMode"===b&&o.modes.indexOf(a.datepickerOptions.datepickerMode)o.modes.indexOf(o[b]))&&(a.datepickerMode=o[b],a.datepickerOptions.datepickerMode=o[b])}):o[b]=a[b]=i[b]||null}}),a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),a.disabled=angular.isDefined(c.disabled)||!1,angular.isDefined(c.ngDisabled)&&r.push(a.$parent.$watch(c.ngDisabled,function(b){a.disabled=b,o.refreshView()})),a.isActive=function(b){return 0===o.compare(b.date,o.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(b){p=b,q=n(p),a.datepickerOptions.initDate?(o.activeDate=l.fromTimezone(a.datepickerOptions.initDate,q.getOption("timezone"))||new Date,a.$watch("datepickerOptions.initDate",function(a){a&&(p.$isEmpty(p.$modelValue)||p.$invalid)&&(o.activeDate=l.fromTimezone(a,q.getOption("timezone")),o.refreshView())})):o.activeDate=new Date;var c=p.$modelValue?new Date(p.$modelValue):new Date;this.activeDate=isNaN(c)?l.fromTimezone(new Date,q.getOption("timezone")):l.fromTimezone(c,q.getOption("timezone")),p.$render=function(){o.render()}},this.render=function(){if(p.$viewValue){var a=new Date(p.$viewValue),b=!isNaN(a);b?this.activeDate=l.fromTimezone(a,q.getOption("timezone")):k||g.error('Datepicker directive: "ng-model" value must be a Date object')}this.refreshView()},this.refreshView=function(){if(this.element){a.selectedDt=null,this._refreshView(),a.activeDt&&(a.activeDateId=a.activeDt.uid);var b=p.$viewValue?new Date(p.$viewValue):null;b=l.fromTimezone(b,q.getOption("timezone")),p.$setValidity("dateDisabled",!b||this.element&&!this.isDisabled(b))}},this.createDateObject=function(b,c){var d=p.$viewValue?new Date(p.$viewValue):null;d=l.fromTimezone(d,q.getOption("timezone"));var e=new Date;e=l.fromTimezone(e,q.getOption("timezone"));var f=this.compare(b,e),g={date:b,label:l.filter(b,c),selected:d&&0===this.compare(b,d),disabled:this.isDisabled(b),past:0>f,current:0===f,future:f>0,customClass:this.customClass(b)||null};return d&&0===this.compare(b,d)&&(a.selectedDt=g),o.activeDate&&0===this.compare(g.date,o.activeDate)&&(a.activeDt=g),g},this.isDisabled=function(b){return a.disabled||this.minDate&&this.compare(b,this.minDate)<0||this.maxDate&&this.compare(b,this.maxDate)>0||a.dateDisabled&&a.dateDisabled({date:b,mode:a.datepickerMode})},this.customClass=function(b){return a.customClass({date:b,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===o.minMode){var c=p.$viewValue?l.fromTimezone(new Date(p.$viewValue),q.getOption("timezone")):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),c=l.toTimezone(c,q.getOption("timezone")),p.$setViewValue(c),p.$render()}else o.activeDate=b,m(o.modes[o.modes.indexOf(a.datepickerMode)-1]),a.$emit("uib:datepicker.mode");a.$broadcast("uib:datepicker.focus")},a.move=function(a){var b=o.activeDate.getFullYear()+a*(o.step.years||0),c=o.activeDate.getMonth()+a*(o.step.months||0);o.activeDate.setFullYear(b,c,1),o.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===o.maxMode&&1===b||a.datepickerMode===o.minMode&&-1===b||(m(o.modes[o.modes.indexOf(a.datepickerMode)+b]),a.$emit("uib:datepicker.mode"))},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var s=function(){o.element[0].focus()};a.$on("uib:datepicker.focus",s),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey&&!a.disabled)if(b.preventDefault(),o.shortcutPropagation||b.stopPropagation(),"enter"===c||"space"===c){if(o.isDisabled(o.activeDate))return;a.select(o.activeDate)}else!b.ctrlKey||"up"!==c&&"down"!==c?(o.handleKeyDown(c,b),o.refreshView()):a.toggleMode("up"===c?1:-1)},b.on("keydown",function(b){a.$apply(function(){a.keydown(b)})}),a.$on("$destroy",function(){for(;r.length;)r.shift()()})}]).controller("UibDaypickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?f[b]:29}function e(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}var f=[31,28,31,30,31,30,31,31,30,31,30,31];this.step={months:1},this.element=b,this.init=function(b){angular.extend(b,this),a.showWeeks=b.showWeeks,b.refreshView()},this.getDates=function(a,b){for(var c,d=new Array(b),e=new Date(a),f=0;b>f;)c=new Date(e),d[f++]=c,e.setDate(e.getDate()+1);return d},this._refreshView=function(){var b=this.activeDate.getFullYear(),d=this.activeDate.getMonth(),f=new Date(this.activeDate);f.setFullYear(b,d,1);var g=this.startingDay-f.getDay(),h=g>0?7-g:-g,i=new Date(f);h>0&&i.setDate(-h+1);for(var j=this.getDates(i,42),k=0;42>k;k++)j[k]=angular.extend(this.createDateObject(j[k],this.formatDay),{secondary:j[k].getMonth()!==d,uid:a.uniqueId+"-"+k});a.labels=new Array(7);for(var l=0;7>l;l++)a.labels[l]={abbr:c(j[l].date,this.formatDayHeader),full:c(j[l].date,"EEEE")};if(a.title=c(this.activeDate,this.formatDayTitle),a.rows=this.split(j,7),a.showWeeks){a.weekNumbers=[];for(var m=(11-this.startingDay)%7,n=a.rows.length,o=0;n>o;o++)a.weekNumbers.push(e(a.rows[o][m].date))}},this.compare=function(a,b){var c=new Date(a.getFullYear(),a.getMonth(),a.getDate()),d=new Date(b.getFullYear(),b.getMonth(),b.getDate());return c.setFullYear(a.getFullYear()),d.setFullYear(b.getFullYear()),c-d},this.handleKeyDown=function(a,b){var c=this.activeDate.getDate();if("left"===a)c-=1;else if("up"===a)c-=7;else if("right"===a)c+=1;else if("down"===a)c+=7;else if("pageup"===a||"pagedown"===a){var e=this.activeDate.getMonth()+("pageup"===a?-1:1);this.activeDate.setMonth(e,1),c=Math.min(d(this.activeDate.getFullYear(),this.activeDate.getMonth()),c)}else"home"===a?c=1:"end"===a&&(c=d(this.activeDate.getFullYear(),this.activeDate.getMonth()));this.activeDate.setDate(c)}}]).controller("UibMonthpickerController",["$scope","$element","dateFilter",function(a,b,c){this.step={years:1},this.element=b,this.init=function(a){angular.extend(a,this),a.refreshView()},this._refreshView=function(){for(var b,d=new Array(12),e=this.activeDate.getFullYear(),f=0;12>f;f++)b=new Date(this.activeDate),b.setFullYear(e,f,1),d[f]=angular.extend(this.createDateObject(b,this.formatMonth),{uid:a.uniqueId+"-"+f});a.title=c(this.activeDate,this.formatMonthTitle),a.rows=this.split(d,this.monthColumns),a.yearHeaderColspan=this.monthColumns>3?this.monthColumns-2:1},this.compare=function(a,b){var c=new Date(a.getFullYear(),a.getMonth()),d=new Date(b.getFullYear(),b.getMonth());return c.setFullYear(a.getFullYear()),d.setFullYear(b.getFullYear()),c-d},this.handleKeyDown=function(a,b){var c=this.activeDate.getMonth();if("left"===a)c-=1;else if("up"===a)c-=this.monthColumns;else if("right"===a)c+=1;else if("down"===a)c+=this.monthColumns;else if("pageup"===a||"pagedown"===a){var d=this.activeDate.getFullYear()+("pageup"===a?-1:1);this.activeDate.setFullYear(d)}else"home"===a?c=0:"end"===a&&(c=11);this.activeDate.setMonth(c)}}]).controller("UibYearpickerController",["$scope","$element","dateFilter",function(a,b,c){function d(a){return parseInt((a-1)/f,10)*f+1}var e,f;this.element=b,this.yearpickerInit=function(){e=this.yearColumns,f=this.yearRows*e,this.step={years:f}},this._refreshView=function(){for(var b,c=new Array(f),g=0,h=d(this.activeDate.getFullYear());f>g;g++)b=new Date(this.activeDate),b.setFullYear(h+g,0,1),c[g]=angular.extend(this.createDateObject(b,this.formatYear),{uid:a.uniqueId+"-"+g});a.title=[c[0].label,c[f-1].label].join(" - "),a.rows=this.split(c,e),a.columns=e},this.compare=function(a,b){return a.getFullYear()-b.getFullYear()},this.handleKeyDown=function(a,b){var c=this.activeDate.getFullYear();"left"===a?c-=1:"up"===a?c-=e:"right"===a?c+=1:"down"===a?c+=e:"pageup"===a||"pagedown"===a?c+=("pageup"===a?-1:1)*f:"home"===a?c=d(this.activeDate.getFullYear()):"end"===a&&(c=d(this.activeDate.getFullYear())+f-1),this.activeDate.setFullYear(c)}}]).directive("uibDatepicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/datepicker.html"},scope:{datepickerOptions:"=?"},require:["uibDatepicker","^ngModel"],restrict:"A",controller:"UibDatepickerController",controllerAs:"datepicker",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}).directive("uibDaypicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/day.html"}, require:["^uibDatepicker","uibDaypicker"],restrict:"A",controller:"UibDaypickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibMonthpicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/month.html"},require:["^uibDatepicker","uibMonthpicker"],restrict:"A",controller:"UibMonthpickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibYearpicker",function(){return{templateUrl:function(a,b){return b.templateUrl||"uib/template/datepicker/year.html"},require:["^uibDatepicker","uibYearpicker"],restrict:"A",controller:"UibYearpickerController",link:function(a,b,c,d){var e=d[0];angular.extend(e,d[1]),e.yearpickerInit(),e.refreshView()}}}),angular.module("ui.bootstrap.position",[]).factory("$uibPosition",["$document","$window",function(a,b){var c,d,e={normal:/(auto|scroll)/,hidden:/(auto|scroll|hidden)/},f={auto:/\s?auto?\s?/i,primary:/^(top|bottom|left|right)$/,secondary:/^(top|bottom|left|right|center)$/,vertical:/^(top|bottom)$/},g=/(HTML|BODY)/;return{getRawNode:function(a){return a.nodeName?a:a[0]||a},parseStyle:function(a){return a=parseFloat(a),isFinite(a)?a:0},offsetParent:function(c){function d(a){return"static"===(b.getComputedStyle(a).position||"static")}c=this.getRawNode(c);for(var e=c.offsetParent||a[0].documentElement;e&&e!==a[0].documentElement&&d(e);)e=e.offsetParent;return e||a[0].documentElement},scrollbarWidth:function(e){if(e){if(angular.isUndefined(d)){var f=a.find("body");f.addClass("uib-position-body-scrollbar-measure"),d=b.innerWidth-f[0].clientWidth,d=isFinite(d)?d:0,f.removeClass("uib-position-body-scrollbar-measure")}return d}if(angular.isUndefined(c)){var g=angular.element('
        ');a.find("body").append(g),c=g[0].offsetWidth-g[0].clientWidth,c=isFinite(c)?c:0,g.remove()}return c},scrollbarPadding:function(a){a=this.getRawNode(a);var c=b.getComputedStyle(a),d=this.parseStyle(c.paddingRight),e=this.parseStyle(c.paddingBottom),f=this.scrollParent(a,!1,!0),h=this.scrollbarWidth(g.test(f.tagName));return{scrollbarWidth:h,widthOverflow:f.scrollWidth>f.clientWidth,right:d+h,originalRight:d,heightOverflow:f.scrollHeight>f.clientHeight,bottom:e+h,originalBottom:e}},isScrollable:function(a,c){a=this.getRawNode(a);var d=c?e.hidden:e.normal,f=b.getComputedStyle(a);return d.test(f.overflow+f.overflowY+f.overflowX)},scrollParent:function(c,d,f){c=this.getRawNode(c);var g=d?e.hidden:e.normal,h=a[0].documentElement,i=b.getComputedStyle(c);if(f&&g.test(i.overflow+i.overflowY+i.overflowX))return c;var j="absolute"===i.position,k=c.parentElement||h;if(k===h||"fixed"===i.position)return h;for(;k.parentElement&&k!==h;){var l=b.getComputedStyle(k);if(j&&"static"!==l.position&&(j=!1),!j&&g.test(l.overflow+l.overflowY+l.overflowX))break;k=k.parentElement}return k},position:function(c,d){c=this.getRawNode(c);var e=this.offset(c);if(d){var f=b.getComputedStyle(c);e.top-=this.parseStyle(f.marginTop),e.left-=this.parseStyle(f.marginLeft)}var g=this.offsetParent(c),h={top:0,left:0};return g!==a[0].documentElement&&(h=this.offset(g),h.top+=g.clientTop-g.scrollTop,h.left+=g.clientLeft-g.scrollLeft),{width:Math.round(angular.isNumber(e.width)?e.width:c.offsetWidth),height:Math.round(angular.isNumber(e.height)?e.height:c.offsetHeight),top:Math.round(e.top-h.top),left:Math.round(e.left-h.left)}},offset:function(c){c=this.getRawNode(c);var d=c.getBoundingClientRect();return{width:Math.round(angular.isNumber(d.width)?d.width:c.offsetWidth),height:Math.round(angular.isNumber(d.height)?d.height:c.offsetHeight),top:Math.round(d.top+(b.pageYOffset||a[0].documentElement.scrollTop)),left:Math.round(d.left+(b.pageXOffset||a[0].documentElement.scrollLeft))}},viewportOffset:function(c,d,e){c=this.getRawNode(c),e=e!==!1;var f=c.getBoundingClientRect(),g={top:0,left:0,bottom:0,right:0},h=d?a[0].documentElement:this.scrollParent(c),i=h.getBoundingClientRect();if(g.top=i.top+h.clientTop,g.left=i.left+h.clientLeft,h===a[0].documentElement&&(g.top+=b.pageYOffset,g.left+=b.pageXOffset),g.bottom=g.top+h.clientHeight,g.right=g.left+h.clientWidth,e){var j=b.getComputedStyle(h);g.top+=this.parseStyle(j.paddingTop),g.bottom-=this.parseStyle(j.paddingBottom),g.left+=this.parseStyle(j.paddingLeft),g.right-=this.parseStyle(j.paddingRight)}return{top:Math.round(f.top-g.top),bottom:Math.round(g.bottom-f.bottom),left:Math.round(f.left-g.left),right:Math.round(g.right-f.right)}},parsePlacement:function(a){var b=f.auto.test(a);return b&&(a=a.replace(f.auto,"")),a=a.split("-"),a[0]=a[0]||"top",f.primary.test(a[0])||(a[0]="top"),a[1]=a[1]||"center",f.secondary.test(a[1])||(a[1]="center"),b?a[2]=!0:a[2]=!1,a},positionElements:function(a,c,d,e){a=this.getRawNode(a),c=this.getRawNode(c);var g=angular.isDefined(c.offsetWidth)?c.offsetWidth:c.prop("offsetWidth"),h=angular.isDefined(c.offsetHeight)?c.offsetHeight:c.prop("offsetHeight");d=this.parsePlacement(d);var i=e?this.offset(a):this.position(a),j={top:0,left:0,placement:""};if(d[2]){var k=this.viewportOffset(a,e),l=b.getComputedStyle(c),m={width:g+Math.round(Math.abs(this.parseStyle(l.marginLeft)+this.parseStyle(l.marginRight))),height:h+Math.round(Math.abs(this.parseStyle(l.marginTop)+this.parseStyle(l.marginBottom)))};if(d[0]="top"===d[0]&&m.height>k.top&&m.height<=k.bottom?"bottom":"bottom"===d[0]&&m.height>k.bottom&&m.height<=k.top?"top":"left"===d[0]&&m.width>k.left&&m.width<=k.right?"right":"right"===d[0]&&m.width>k.right&&m.width<=k.left?"left":d[0],d[1]="top"===d[1]&&m.height-i.height>k.bottom&&m.height-i.height<=k.top?"bottom":"bottom"===d[1]&&m.height-i.height>k.top&&m.height-i.height<=k.bottom?"top":"left"===d[1]&&m.width-i.width>k.right&&m.width-i.width<=k.left?"right":"right"===d[1]&&m.width-i.width>k.left&&m.width-i.width<=k.right?"left":d[1],"center"===d[1])if(f.vertical.test(d[0])){var n=i.width/2-g/2;k.left+n<0&&m.width-i.width<=k.right?d[1]="left":k.right+n<0&&m.width-i.width<=k.left&&(d[1]="right")}else{var o=i.height/2-m.height/2;k.top+o<0&&m.height-i.height<=k.bottom?d[1]="top":k.bottom+o<0&&m.height-i.height<=k.top&&(d[1]="bottom")}}switch(d[0]){case"top":j.top=i.top-h;break;case"bottom":j.top=i.top+i.height;break;case"left":j.left=i.left-g;break;case"right":j.left=i.left+i.width}switch(d[1]){case"top":j.top=i.top;break;case"bottom":j.top=i.top+i.height-h;break;case"left":j.left=i.left;break;case"right":j.left=i.left+i.width-g;break;case"center":f.vertical.test(d[0])?j.left=i.left+i.width/2-g/2:j.top=i.top+i.height/2-h/2}return j.top=Math.round(j.top),j.left=Math.round(j.left),j.placement="center"===d[1]?d[0]:d[0]+"-"+d[1],j},adjustTop:function(a,b,c,d){return-1!==a.indexOf("top")&&c!==d?{top:b.top-d+"px"}:void 0},positionArrow:function(a,c){a=this.getRawNode(a);var d=a.querySelector(".tooltip-inner, .popover-inner");if(d){var e=angular.element(d).hasClass("tooltip-inner"),g=e?a.querySelector(".tooltip-arrow"):a.querySelector(".arrow");if(g){var h={top:"",bottom:"",left:"",right:""};if(c=this.parsePlacement(c),"center"===c[1])return void angular.element(g).css(h);var i="border-"+c[0]+"-width",j=b.getComputedStyle(g)[i],k="border-";k+=f.vertical.test(c[0])?c[0]+"-"+c[1]:c[1]+"-"+c[0],k+="-radius";var l=b.getComputedStyle(e?d:a)[k];switch(c[0]){case"top":h.bottom=e?"0":"-"+j;break;case"bottom":h.top=e?"0":"-"+j;break;case"left":h.right=e?"0":"-"+j;break;case"right":h.left=e?"0":"-"+j}h[c[1]]=l,angular.element(g).css(h)}}}}}]),angular.module("ui.bootstrap.datepickerPopup",["ui.bootstrap.datepicker","ui.bootstrap.position"]).value("$datepickerPopupLiteralWarning",!0).constant("uibDatepickerPopupConfig",{altInputFormats:[],appendToBody:!1,clearText:"Clear",closeOnDateSelection:!0,closeText:"Done",currentText:"Today",datepickerPopup:"yyyy-MM-dd",datepickerPopupTemplateUrl:"uib/template/datepickerPopup/popup.html",datepickerTemplateUrl:"uib/template/datepicker/datepicker.html",html5Types:{date:"yyyy-MM-dd","datetime-local":"yyyy-MM-ddTHH:mm:ss.sss",month:"yyyy-MM"},onOpenFocus:!0,showButtonBar:!0,placement:"auto bottom-left"}).controller("UibDatepickerPopupController",["$scope","$element","$attrs","$compile","$log","$parse","$window","$document","$rootScope","$uibPosition","dateFilter","uibDateParser","uibDatepickerPopupConfig","$timeout","uibDatepickerConfig","$datepickerPopupLiteralWarning",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){function q(b){var c=l.parse(b,x,a.date);if(isNaN(c))for(var d=0;d
      "),D.attr({"ng-model":"date","ng-change":"dateSelection(date)","template-url":B}),E=angular.element(D.children()[0]),E.attr("template-url",C),a.datepickerOptions||(a.datepickerOptions={}),K&&"month"===c.type&&(a.datepickerOptions.datepickerMode="month",a.datepickerOptions.minMode="month"),E.attr("datepicker-options","datepickerOptions"),K?G.$formatters.push(function(b){return a.date=l.fromTimezone(b,H.getOption("timezone")),b}):(G.$$parserName="date",G.$validators.date=s,G.$parsers.unshift(r),G.$formatters.push(function(b){return G.$isEmpty(b)?(a.date=b,b):(angular.isNumber(b)&&(b=new Date(b)),a.date=l.fromTimezone(b,H.getOption("timezone")),l.filter(a.date,x))})),G.$viewChangeListeners.push(function(){a.date=q(G.$viewValue)}),b.on("keydown",u),I=d(D)(a),D.remove(),z?h.find("body").append(I):b.after(I),a.$on("$destroy",function(){for(a.isOpen===!0&&(i.$$phase||a.$apply(function(){a.isOpen=!1})),I.remove(),b.off("keydown",u),h.off("click",t),F&&F.off("scroll",v),angular.element(g).off("resize",v);L.length;)L.shift()()})},a.getText=function(b){return a[b+"Text"]||m[b+"Text"]},a.isDisabled=function(b){"today"===b&&(b=l.fromTimezone(new Date,H.getOption("timezone")));var c={};return angular.forEach(["minDate","maxDate"],function(b){a.datepickerOptions[b]?angular.isDate(a.datepickerOptions[b])?c[b]=new Date(a.datepickerOptions[b]):(p&&e.warn("Literal date support has been deprecated, please switch to date object usage"),c[b]=new Date(k(a.datepickerOptions[b],"medium"))):c[b]=null}),a.datepickerOptions&&c.minDate&&a.compare(b,c.minDate)<0||c.maxDate&&a.compare(b,c.maxDate)>0},a.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth(),a.getDate())-new Date(b.getFullYear(),b.getMonth(),b.getDate())},a.dateSelection=function(c){a.date=c;var d=a.date?l.filter(a.date,x):null;b.val(d),G.$setViewValue(d),y&&(a.isOpen=!1,b[0].focus())},a.keydown=function(c){27===c.which&&(c.stopPropagation(),a.isOpen=!1,b[0].focus())},a.select=function(b,c){if(c.stopPropagation(),"today"===b){var d=new Date;angular.isDate(a.date)?(b=new Date(a.date),b.setFullYear(d.getFullYear(),d.getMonth(),d.getDate())):(b=l.fromTimezone(d,H.getOption("timezone")),b.setHours(0,0,0,0))}a.dateSelection(b)},a.close=function(c){c.stopPropagation(),a.isOpen=!1,b[0].focus()},a.disabled=angular.isDefined(c.disabled)||!1,c.ngDisabled&&L.push(a.$parent.$watch(f(c.ngDisabled),function(b){a.disabled=b})),a.$watch("isOpen",function(d){d?a.disabled?a.isOpen=!1:n(function(){v(),A&&a.$broadcast("uib:datepicker.focus"),h.on("click",t);var d=c.popupPlacement?c.popupPlacement:m.placement;z||j.parsePlacement(d)[2]?(F=F||angular.element(j.scrollParent(b)),F&&F.on("scroll",v)):F=null,angular.element(g).on("resize",v)},0,!1):(h.off("click",t),F&&F.off("scroll",v),angular.element(g).off("resize",v))}),a.$on("uib:datepicker.mode",function(){n(v,0,!1)})}]).directive("uibDatepickerPopup",function(){return{require:["ngModel","uibDatepickerPopup"],controller:"UibDatepickerPopupController",scope:{datepickerOptions:"=?",isOpen:"=?",currentText:"@",clearText:"@",closeText:"@"},link:function(a,b,c,d){var e=d[0],f=d[1];f.init(e)}}}).directive("uibDatepickerPopupWrap",function(){return{restrict:"A",transclude:!0,templateUrl:function(a,b){return b.templateUrl||"uib/template/datepickerPopup/popup.html"}}}),angular.module("ui.bootstrap.debounce",[]).factory("$$debounce",["$timeout",function(a){return function(b,c){var d;return function(){var e=this,f=Array.prototype.slice.call(arguments);d&&a.cancel(d),d=a(function(){b.apply(e,f)},c)}}}]),angular.module("ui.bootstrap.multiMap",[]).factory("$$multiMap",function(){return{createNew:function(){var a={};return{entries:function(){return Object.keys(a).map(function(b){return{key:b,value:a[b]}})},get:function(b){return a[b]},hasKey:function(b){return!!a[b]},keys:function(){return Object.keys(a)},put:function(b,c){a[b]||(a[b]=[]),a[b].push(c)},remove:function(b,c){var d=a[b];if(d){var e=d.indexOf(c);-1!==e&&d.splice(e,1),d.length||delete a[b]}}}}}}),angular.module("ui.bootstrap.dropdown",["ui.bootstrap.multiMap","ui.bootstrap.position"]).constant("uibDropdownConfig",{appendToOpenClass:"uib-dropdown-open",openClass:"open"}).service("uibDropdownService",["$document","$rootScope","$$multiMap",function(a,b,c){var d=null,e=c.createNew();this.isOnlyOpen=function(a,b){var c=e.get(b);if(c){var d=c.reduce(function(b,c){return c.scope===a?c:b},{});if(d)return 1===c.length}return!1},this.open=function(b,c,g){if(d||a.on("click",f),d&&d!==b&&(d.isOpen=!1),d=b,g){var h=e.get(g);if(h){var i=h.map(function(a){return a.scope});-1===i.indexOf(b)&&e.put(g,{scope:b})}else e.put(g,{scope:b})}},this.close=function(b,c,g){if(d===b&&(a.off("click",f),a.off("keydown",this.keybindFilter),d=null),g){var h=e.get(g);if(h){var i=h.reduce(function(a,c){return c.scope===b?c:a},{});i&&e.remove(g,i)}}};var f=function(a){if(d&&d.isOpen&&!(a&&"disabled"===d.getAutoClose()||a&&3===a.which)){var c=d.getToggleElement();if(!(a&&c&&c[0].contains(a.target))){var e=d.getDropdownElement();a&&"outsideClick"===d.getAutoClose()&&e&&e[0].contains(a.target)||(d.focusToggleElement(),d.isOpen=!1,b.$$phase||d.$apply())}}};this.keybindFilter=function(a){if(d){var b=d.getDropdownElement(),c=d.getToggleElement(),e=b&&b[0].contains(a.target),g=c&&c[0].contains(a.target);27===a.which?(a.stopPropagation(),d.focusToggleElement(),f()):d.isKeynavEnabled()&&-1!==[38,40].indexOf(a.which)&&d.isOpen&&(e||g)&&(a.preventDefault(),a.stopPropagation(),d.focusDropdownEntry(a.which))}}}]).controller("UibDropdownController",["$scope","$element","$attrs","$parse","uibDropdownConfig","uibDropdownService","$animate","$uibPosition","$document","$compile","$templateRequest",function(a,b,c,d,e,f,g,h,i,j,k){function l(){b.append(o.dropdownMenu)}var m,n,o=this,p=a.$new(),q=e.appendToOpenClass,r=e.openClass,s=angular.noop,t=c.onToggle?d(c.onToggle):angular.noop,u=!1,v=i.find("body");b.addClass("dropdown"),this.init=function(){c.isOpen&&(n=d(c.isOpen),s=n.assign,a.$watch(n,function(a){p.isOpen=!!a})),u=angular.isDefined(c.keyboardNav)},this.toggle=function(a){return p.isOpen=arguments.length?!!a:!p.isOpen,angular.isFunction(s)&&s(p,p.isOpen),p.isOpen},this.isOpen=function(){return p.isOpen},p.getToggleElement=function(){return o.toggleElement},p.getAutoClose=function(){return c.autoClose||"always"},p.getElement=function(){return b},p.isKeynavEnabled=function(){return u},p.focusDropdownEntry=function(a){var c=o.dropdownMenu?angular.element(o.dropdownMenu).find("a"):b.find("ul").eq(0).find("a");switch(a){case 40:angular.isNumber(o.selectedOption)?o.selectedOption=o.selectedOption===c.length-1?o.selectedOption:o.selectedOption+1:o.selectedOption=0;break;case 38:angular.isNumber(o.selectedOption)?o.selectedOption=0===o.selectedOption?0:o.selectedOption-1:o.selectedOption=c.length-1}c[o.selectedOption].focus()},p.getDropdownElement=function(){return o.dropdownMenu},p.focusToggleElement=function(){o.toggleElement&&o.toggleElement[0].focus()},p.$watch("isOpen",function(e,n){var u=null,w=!1;if(angular.isDefined(c.dropdownAppendTo)){var x=d(c.dropdownAppendTo)(p);x&&(u=angular.element(x))}if(angular.isDefined(c.dropdownAppendToBody)){var y=d(c.dropdownAppendToBody)(p);y!==!1&&(w=!0)}if(w&&!u&&(u=v),u&&o.dropdownMenu&&(e?(u.append(o.dropdownMenu),b.on("$destroy",l)):(b.off("$destroy",l),l())),u&&o.dropdownMenu){var z,A,B,C=h.positionElements(b,o.dropdownMenu,"bottom-left",!0),D=0;if(z={top:C.top+"px",display:e?"block":"none"},A=o.dropdownMenu.hasClass("dropdown-menu-right"),A?(z.left="auto",B=h.scrollbarPadding(u),B.heightOverflow&&B.scrollbarWidth&&(D=B.scrollbarWidth),z.right=window.innerWidth-D-(C.left+b.prop("offsetWidth"))+"px"):(z.left=C.left+"px",z.right="auto"),!w){var E=h.offset(u);z.top=C.top-E.top+"px",A?z.right=window.innerWidth-(C.left-E.left+b.prop("offsetWidth"))+"px":z.left=C.left-E.left+"px"}o.dropdownMenu.css(z)}var F=u?u:b,G=u?q:r,H=F.hasClass(G),I=f.isOnlyOpen(a,u);if(H===!e){var J;J=u?I?"removeClass":"addClass":e?"addClass":"removeClass",g[J](F,G).then(function(){angular.isDefined(e)&&e!==n&&t(a,{open:!!e})})}if(e)o.dropdownMenuTemplateUrl?k(o.dropdownMenuTemplateUrl).then(function(a){m=p.$new(),j(a.trim())(m,function(a){var b=a;o.dropdownMenu.replaceWith(b),o.dropdownMenu=b,i.on("keydown",f.keybindFilter)})}):i.on("keydown",f.keybindFilter),p.focusToggleElement(),f.open(p,b,u);else{if(f.close(p,b,u),o.dropdownMenuTemplateUrl){m&&m.$destroy();var K=angular.element('
        ');o.dropdownMenu.replaceWith(K),o.dropdownMenu=K}o.selectedOption=null}angular.isFunction(s)&&s(a,e)})}]).directive("uibDropdown",function(){return{controller:"UibDropdownController",link:function(a,b,c,d){d.init()}}}).directive("uibDropdownMenu",function(){return{restrict:"A",require:"?^uibDropdown",link:function(a,b,c,d){if(d&&!angular.isDefined(c.dropdownNested)){b.addClass("dropdown-menu");var e=c.templateUrl;e&&(d.dropdownMenuTemplateUrl=e),d.dropdownMenu||(d.dropdownMenu=b)}}}}).directive("uibDropdownToggle",function(){return{require:"?^uibDropdown",link:function(a,b,c,d){if(d){b.addClass("dropdown-toggle"),d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.on("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.off("click",e)})}}}}),angular.module("ui.bootstrap.stackedMap",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c-1&&A>a&&(a=A),a}function m(a,b){var c=x.get(a).value,d=c.appendTo;x.remove(a),B=x.top(),B&&(A=parseInt(B.value.modalDomEl.attr("index"),10)),p(c.modalDomEl,c.modalScope,function(){var b=c.openedClass||w;y.remove(b,a);var e=y.hasKey(b);d.toggleClass(b,e),!e&&v&&v.heightOverflow&&v.scrollbarWidth&&(v.originalRight?d.css({paddingRight:v.originalRight+"px"}):d.css({paddingRight:""}),v=null),n(!0)},c.closedDeferred),o(),b&&b.focus?b.focus():d.focus&&d.focus()}function n(a){var b;x.length()>0&&(b=x.top().value,b.modalDomEl.toggleClass(b.windowTopClass||"",a))}function o(){if(t&&-1===l()){var a=u;p(t,u,function(){a=null}),t=void 0,u=void 0}}function p(b,c,d,e){function g(){g.done||(g.done=!0,a.leave(b).then(function(){d&&d(),b.remove(),e&&e.resolve()}),c.$destroy())}var h,i=null,j=function(){return h||(h=f.defer(),i=h.promise),function(){h.resolve()}};return c.$broadcast(z.NOW_CLOSING_EVENT,j),f.when(i).then(g)}function q(a){if(a.isDefaultPrevented())return a;var b=x.top();if(b)switch(a.which){case 27:b.value.keyboard&&(a.preventDefault(),e.$apply(function(){z.dismiss(b.key,"escape key press")}));break;case 9:var c=z.loadFocusElementList(b),d=!1;a.shiftKey?(z.isFocusInFirstItem(a,c)||z.isModalFocused(a,b))&&(d=z.focusLastFocusableElement(c)):z.isFocusInLastItem(a,c)&&(d=z.focusFirstFocusableElement(c)),d&&(a.preventDefault(),a.stopPropagation())}}function r(a,b,c){return!a.value.modalScope.$broadcast("modal.closing",b,c).defaultPrevented}function s(){Array.prototype.forEach.call(document.querySelectorAll("["+C+"]"),function(a){var b=parseInt(a.getAttribute(C),10),c=b-1;a.setAttribute(C,c),c||(a.removeAttribute(C),a.removeAttribute("aria-hidden"))})}var t,u,v,w="modal-open",x=h.createNew(),y=g.createNew(),z={NOW_CLOSING_EVENT:"modal.stack.now-closing"},A=0,B=null,C="data-bootstrap-modal-aria-hidden-count",D="a[href], area[href], input:not([disabled]):not([tabindex='-1']), button:not([disabled]):not([tabindex='-1']),select:not([disabled]):not([tabindex='-1']), textarea:not([disabled]):not([tabindex='-1']), iframe, object, embed, *[tabindex]:not([tabindex='-1']), *[contenteditable=true]",E=/[A-Z]/g;return e.$watch(l,function(a){u&&(u.index=a)}),c.on("keydown",q),e.$on("$destroy",function(){c.off("keydown",q)}),z.open=function(b,f){function g(a){function b(a){var b=a.parent()?a.parent().children():[];return Array.prototype.filter.call(b,function(b){return b!==a[0]})}if(a&&"BODY"!==a[0].tagName)return b(a).forEach(function(a){var b="true"===a.getAttribute("aria-hidden"),c=parseInt(a.getAttribute(C),10);c||(c=b?1:0),a.setAttribute(C,c+1),a.setAttribute("aria-hidden","true")}),g(a.parent())}var h=c[0].activeElement,k=f.openedClass||w;n(!1),B=x.top(),x.add(b,{deferred:f.deferred,renderDeferred:f.renderDeferred,closedDeferred:f.closedDeferred,modalScope:f.scope,backdrop:f.backdrop,keyboard:f.keyboard,openedClass:f.openedClass,windowTopClass:f.windowTopClass,animation:f.animation,appendTo:f.appendTo}),y.put(k,b);var m=f.appendTo,o=l();o>=0&&!t&&(u=e.$new(!0),u.modalOptions=f,u.index=o,t=angular.element('
        '),t.attr({"class":"modal-backdrop","ng-style":"{'z-index': 1040 + (index && 1 || 0) + index*10}","uib-modal-animation-class":"fade","modal-in-class":"in"}),f.backdropClass&&t.addClass(f.backdropClass),f.animation&&t.attr("modal-animation","true"),d(t)(u),a.enter(t,m),i.isScrollable(m)&&(v=i.scrollbarPadding(m),v.heightOverflow&&v.scrollbarWidth&&m.css({paddingRight:v.right+"px"})));var p;f.component?(p=document.createElement(j(f.component.name)),p=angular.element(p),p.attr({resolve:"$resolve","modal-instance":"$uibModalInstance",close:"$close($value)",dismiss:"$dismiss($value)"})):p=f.content,A=B?parseInt(B.value.modalDomEl.attr("index"),10)+1:0;var q=angular.element('
        ');q.attr({"class":"modal","template-url":f.windowTemplateUrl,"window-top-class":f.windowTopClass,role:"dialog","aria-labelledby":f.ariaLabelledBy,"aria-describedby":f.ariaDescribedBy,size:f.size,index:A,animate:"animate","ng-style":"{'z-index': 1050 + $$topModalIndex*10, display: 'block'}",tabindex:-1,"uib-modal-animation-class":"fade","modal-in-class":"in"}).append(p),f.windowClass&&q.addClass(f.windowClass),f.animation&&q.attr("modal-animation","true"),m.addClass(k),f.scope&&(f.scope.$$topModalIndex=A),a.enter(d(q)(f.scope),m),x.top().value.modalDomEl=q,x.top().value.modalOpener=h,g(q)},z.close=function(a,b){var c=x.get(a);return s(),c&&r(c,b,!0)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.resolve(b),m(a,c.value.modalOpener),!0):!c},z.dismiss=function(a,b){var c=x.get(a);return s(),c&&r(c,b,!1)?(c.value.modalScope.$$uibDestructionScheduled=!0,c.value.deferred.reject(b),m(a,c.value.modalOpener),!0):!c},z.dismissAll=function(a){for(var b=this.getTop();b&&this.dismiss(b.key,a);)b=this.getTop()},z.getTop=function(){return x.top()},z.modalRendered=function(a){var b=x.get(a);b&&b.value.renderDeferred.resolve()},z.focusFirstFocusableElement=function(a){return a.length>0?(a[0].focus(),!0):!1},z.focusLastFocusableElement=function(a){return a.length>0?(a[a.length-1].focus(),!0):!1},z.isModalFocused=function(a,b){if(a&&b){var c=b.value.modalDomEl;if(c&&c.length)return(a.target||a.srcElement)===c[0]}return!1},z.isFocusInFirstItem=function(a,b){return b.length>0?(a.target||a.srcElement)===b[0]:!1},z.isFocusInLastItem=function(a,b){return b.length>0?(a.target||a.srcElement)===b[b.length-1]:!1},z.loadFocusElementList=function(a){if(a){var b=a.value.modalDomEl;if(b&&b.length){var c=b[0].querySelectorAll(D);return c?Array.prototype.filter.call(c,function(a){return k(a)}):c}}},z}]).provider("$uibModal",function(){var a={options:{animation:!0,backdrop:!0,keyboard:!0},$get:["$rootScope","$q","$document","$templateRequest","$controller","$uibResolve","$uibModalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?c.when(a.template):e(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl)}var j={},k=null;return j.getPromiseChain=function(){return k},j.open=function(e){function j(){return q}var l=c.defer(),m=c.defer(),n=c.defer(),o=c.defer(),p={result:l.promise,opened:m.promise,closed:n.promise,rendered:o.promise,close:function(a){return h.close(p,a)},dismiss:function(a){return h.dismiss(p,a)}};if(e=angular.extend({},a.options,e),e.resolve=e.resolve||{},e.appendTo=e.appendTo||d.find("body").eq(0),!e.appendTo.length)throw new Error("appendTo element not found. Make sure that the element passed is in DOM.");if(!e.component&&!e.template&&!e.templateUrl)throw new Error("One of component or template or templateUrl options is required.");var q;q=e.component?c.when(g.resolve(e.resolve,{},null,null)):c.all([i(e),g.resolve(e.resolve,{},null,null)]);var r;return r=k=c.all([k]).then(j,j).then(function(a){function c(b,c,d,e){b.$scope=g,b.$scope.$resolve={},d?b.$scope.$uibModalInstance=p:b.$uibModalInstance=p;var f=c?a[1]:a;angular.forEach(f,function(a,c){e&&(b[c]=a),b.$scope.$resolve[c]=a})}var d=e.scope||b,g=d.$new();g.$close=p.close,g.$dismiss=p.dismiss,g.$on("$destroy",function(){g.$$uibDestructionScheduled||g.$dismiss("$uibUnscheduledDestruction")});var i,j,k={scope:g,deferred:l,renderDeferred:o,closedDeferred:n,animation:e.animation,backdrop:e.backdrop,keyboard:e.keyboard,backdropClass:e.backdropClass,windowTopClass:e.windowTopClass,windowClass:e.windowClass,windowTemplateUrl:e.windowTemplateUrl,ariaLabelledBy:e.ariaLabelledBy,ariaDescribedBy:e.ariaDescribedBy,size:e.size,openedClass:e.openedClass,appendTo:e.appendTo},q={},r={};e.component?(c(q,!1,!0,!1),q.name=e.component,k.component=q):e.controller&&(c(r,!0,!1,!0),j=f(e.controller,r,!0,e.controllerAs),e.controllerAs&&e.bindToController&&(i=j.instance,i.$close=g.$close,i.$dismiss=g.$dismiss,angular.extend(i,{$resolve:r.$scope.$resolve},d)),i=j(),angular.isFunction(i.$onInit)&&i.$onInit()),e.component||(k.content=a[0]),h.open(p,k),m.resolve(!0)},function(a){m.reject(a),l.reject(a)})["finally"](function(){k===r&&(k=null)}),p},j}]};return a}),angular.module("ui.bootstrap.paging",[]).factory("uibPaging",["$parse",function(a){return{create:function(b,c,d){b.setNumPages=d.numPages?a(d.numPages).assign:angular.noop,b.ngModelCtrl={$setViewValue:angular.noop},b._watchers=[],b.init=function(a,e){b.ngModelCtrl=a,b.config=e,a.$render=function(){b.render()},d.itemsPerPage?b._watchers.push(c.$parent.$watch(d.itemsPerPage,function(a){ b.itemsPerPage=parseInt(a,10),c.totalPages=b.calculateTotalPages(),b.updatePage()})):b.itemsPerPage=e.itemsPerPage,c.$watch("totalItems",function(a,d){(angular.isDefined(a)||a!==d)&&(c.totalPages=b.calculateTotalPages(),b.updatePage())})},b.calculateTotalPages=function(){var a=b.itemsPerPage<1?1:Math.ceil(c.totalItems/b.itemsPerPage);return Math.max(a||0,1)},b.render=function(){c.page=parseInt(b.ngModelCtrl.$viewValue,10)||1},c.selectPage=function(a,d){d&&d.preventDefault();var e=!c.ngDisabled||!d;e&&c.page!==a&&a>0&&a<=c.totalPages&&(d&&d.target&&d.target.blur(),b.ngModelCtrl.$setViewValue(a),b.ngModelCtrl.$render())},c.getText=function(a){return c[a+"Text"]||b.config[a+"Text"]},c.noPrevious=function(){return 1===c.page},c.noNext=function(){return c.page===c.totalPages},b.updatePage=function(){b.setNumPages(c.$parent,c.totalPages),c.page>c.totalPages?c.selectPage(c.totalPages):b.ngModelCtrl.$render()},c.$on("$destroy",function(){for(;b._watchers.length;)b._watchers.shift()()})}}}]),angular.module("ui.bootstrap.pager",["ui.bootstrap.paging","ui.bootstrap.tabindex"]).controller("UibPagerController",["$scope","$attrs","uibPaging","uibPagerConfig",function(a,b,c,d){a.align=angular.isDefined(b.align)?a.$parent.$eval(b.align):d.align,c.create(this,a,b)}]).constant("uibPagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("uibPager",["uibPagerConfig",function(a){return{scope:{totalItems:"=",previousText:"@",nextText:"@",ngDisabled:"="},require:["uibPager","?ngModel"],restrict:"A",controller:"UibPagerController",controllerAs:"pager",templateUrl:function(a,b){return b.templateUrl||"uib/template/pager/pager.html"},link:function(b,c,d,e){c.addClass("pager");var f=e[0],g=e[1];g&&f.init(g,a)}}}]),angular.module("ui.bootstrap.pagination",["ui.bootstrap.paging","ui.bootstrap.tabindex"]).controller("UibPaginationController",["$scope","$attrs","$parse","uibPaging","uibPaginationConfig",function(a,b,c,d,e){function f(a,b,c){return{number:a,text:b,active:c}}function g(a,b){var c=[],d=1,e=b,g=angular.isDefined(i)&&b>i;g&&(j?(d=Math.max(a-Math.floor(i/2),1),e=d+i-1,e>b&&(e=b,d=e-i+1)):(d=(Math.ceil(a/i)-1)*i+1,e=Math.min(d+i-1,b)));for(var h=d;e>=h;h++){var n=f(h,m(h),h===a);c.push(n)}if(g&&i>0&&(!j||k||l)){if(d>1){if(!l||d>3){var o=f(d-1,"...",!1);c.unshift(o)}if(l){if(3===d){var p=f(2,"2",!1);c.unshift(p)}var q=f(1,"1",!1);c.unshift(q)}}if(b>e){if(!l||b-2>e){var r=f(e+1,"...",!1);c.push(r)}if(l){if(e===b-2){var s=f(b-1,b-1,!1);c.push(s)}var t=f(b,b,!1);c.push(t)}}}return c}var h=this,i=angular.isDefined(b.maxSize)?a.$parent.$eval(b.maxSize):e.maxSize,j=angular.isDefined(b.rotate)?a.$parent.$eval(b.rotate):e.rotate,k=angular.isDefined(b.forceEllipses)?a.$parent.$eval(b.forceEllipses):e.forceEllipses,l=angular.isDefined(b.boundaryLinkNumbers)?a.$parent.$eval(b.boundaryLinkNumbers):e.boundaryLinkNumbers,m=angular.isDefined(b.pageLabel)?function(c){return a.$parent.$eval(b.pageLabel,{$page:c})}:angular.identity;a.boundaryLinks=angular.isDefined(b.boundaryLinks)?a.$parent.$eval(b.boundaryLinks):e.boundaryLinks,a.directionLinks=angular.isDefined(b.directionLinks)?a.$parent.$eval(b.directionLinks):e.directionLinks,b.$set("role","menu"),d.create(this,a,b),b.maxSize&&h._watchers.push(a.$parent.$watch(c(b.maxSize),function(a){i=parseInt(a,10),h.render()}));var n=this.render;this.render=function(){n(),a.page>0&&a.page<=a.totalPages&&(a.pages=g(a.page,a.totalPages))}}]).constant("uibPaginationConfig",{itemsPerPage:10,boundaryLinks:!1,boundaryLinkNumbers:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0,forceEllipses:!1}).directive("uibPagination",["$parse","uibPaginationConfig",function(a,b){return{scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["uibPagination","?ngModel"],restrict:"A",controller:"UibPaginationController",controllerAs:"pagination",templateUrl:function(a,b){return b.templateUrl||"uib/template/pagination/pagination.html"},link:function(a,c,d,e){c.addClass("pagination");var f=e[0],g=e[1];g&&f.init(g,b)}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.stackedMap"]).provider("$uibTooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",placementClassPrefix:"",animation:!0,popupDelay:0,popupCloseDelay:0,useContentExp:!1},c={mouseenter:"mouseleave",click:"click",outsideClick:"outsideClick",focus:"blur",none:""},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$document","$uibPosition","$interpolate","$rootScope","$parse","$$stackedMap",function(e,f,g,h,i,j,k,l,m){function n(a){if(27===a.which){var b=o.top();b&&(b.value.close(),b=null)}}var o=m.createNew();return h.on("keyup",n),k.$on("$destroy",function(){h.off("keyup",n)}),function(e,k,m,n){function p(a){var b=(a||n.trigger||m).split(" "),d=b.map(function(a){return c[a]||a});return{show:b,hide:d}}n=angular.extend({},b,d,n);var q=a(e),r=j.startSymbol(),s=j.endSymbol(),t="
        ';return{compile:function(a,b){var c=f(t);return function(a,b,d,f){function j(){P.isOpen?q():m()}function m(){O&&!a.$eval(d[k+"Enable"])||(u(),x(),P.popupDelay?H||(H=g(r,P.popupDelay,!1)):r())}function q(){s(),P.popupCloseDelay?I||(I=g(t,P.popupCloseDelay,!1)):t()}function r(){return s(),u(),P.content?(v(),void P.$evalAsync(function(){P.isOpen=!0,y(!0),U()})):angular.noop}function s(){H&&(g.cancel(H),H=null),J&&(g.cancel(J),J=null)}function t(){P&&P.$evalAsync(function(){P&&(P.isOpen=!1,y(!1),P.animation?G||(G=g(w,150,!1)):w())})}function u(){I&&(g.cancel(I),I=null),G&&(g.cancel(G),G=null)}function v(){E||(F=P.$new(),E=c(F,function(a){M?h.find("body").append(a):b.after(a)}),o.add(P,{close:t}),z())}function w(){s(),u(),A(),E&&(E.remove(),E=null,K&&g.cancel(K)),o.remove(P),F&&(F.$destroy(),F=null)}function x(){P.title=d[k+"Title"],S?P.content=S(a):P.content=d[e],P.popupClass=d[k+"Class"],P.placement=angular.isDefined(d[k+"Placement"])?d[k+"Placement"]:n.placement;var b=i.parsePlacement(P.placement);L=b[1]?b[0]+"-"+b[1]:b[0];var c=parseInt(d[k+"PopupDelay"],10),f=parseInt(d[k+"PopupCloseDelay"],10);P.popupDelay=isNaN(c)?n.popupDelay:c,P.popupCloseDelay=isNaN(f)?n.popupCloseDelay:f}function y(b){R&&angular.isFunction(R.assign)&&R.assign(a,b)}function z(){T.length=0,S?(T.push(a.$watch(S,function(a){P.content=a,!a&&P.isOpen&&t()})),T.push(F.$watch(function(){Q||(Q=!0,F.$$postDigest(function(){Q=!1,P&&P.isOpen&&U()}))}))):T.push(d.$observe(e,function(a){P.content=a,!a&&P.isOpen?t():U()})),T.push(d.$observe(k+"Title",function(a){P.title=a,P.isOpen&&U()})),T.push(d.$observe(k+"Placement",function(a){P.placement=a?a:n.placement,P.isOpen&&U()}))}function A(){T.length&&(angular.forEach(T,function(a){a()}),T.length=0)}function B(a){P&&P.isOpen&&E&&(b[0].contains(a.target)||E[0].contains(a.target)||q())}function C(a){27===a.which&&q()}function D(){var c=[],e=[],f=a.$eval(d[k+"Trigger"]);V(),angular.isObject(f)?(Object.keys(f).forEach(function(a){c.push(a),e.push(f[a])}),N={show:c,hide:e}):N=p(f),"none"!==N.show&&N.show.forEach(function(a,c){"outsideClick"===a?(b.on("click",j),h.on("click",B)):a===N.hide[c]?b.on(a,j):a&&(b.on(a,m),b.on(N.hide[c],q)),b.on("keypress",C)})}var E,F,G,H,I,J,K,L,M=angular.isDefined(n.appendToBody)?n.appendToBody:!1,N=p(void 0),O=angular.isDefined(d[k+"Enable"]),P=a.$new(!0),Q=!1,R=angular.isDefined(d[k+"IsOpen"])?l(d[k+"IsOpen"]):!1,S=n.useContentExp?l(d[e]):!1,T=[],U=function(){E&&E.html()&&(J||(J=g(function(){var a=i.positionElements(b,E,P.placement,M),c=angular.isDefined(E.offsetHeight)?E.offsetHeight:E.prop("offsetHeight"),d=M?i.offset(b):i.position(b);E.css({top:a.top+"px",left:a.left+"px"});var e=a.placement.split("-");E.hasClass(e[0])||(E.removeClass(L.split("-")[0]),E.addClass(e[0])),E.hasClass(n.placementClassPrefix+a.placement)||(E.removeClass(n.placementClassPrefix+L),E.addClass(n.placementClassPrefix+a.placement)),K=g(function(){var a=angular.isDefined(E.offsetHeight)?E.offsetHeight:E.prop("offsetHeight"),b=i.adjustTop(e,d,c,a);b&&E.css(b),K=null},0,!1),E.hasClass("uib-position-measure")?(i.positionArrow(E,a.placement),E.removeClass("uib-position-measure")):L!==a.placement&&i.positionArrow(E,a.placement),L=a.placement,J=null},0,!1)))};P.origScope=a,P.isOpen=!1,P.contentExp=function(){return P.content},d.$observe("disabled",function(a){a&&s(),a&&P.isOpen&&t()}),R&&a.$watch(R,function(a){P&&!a===P.isOpen&&j()});var V=function(){N.show.forEach(function(a){"outsideClick"===a?b.off("click",j):(b.off(a,m),b.off(a,j)),b.off("keypress",C)}),N.hide.forEach(function(a){"outsideClick"===a?h.off("click",B):b.off(a,q)})};D();var W=a.$eval(d[k+"Animation"]);P.animation=angular.isDefined(W)?!!W:n.animation;var X,Y=k+"AppendToBody";X=Y in d&&void 0===d[Y]?!0:a.$eval(d[Y]),M=angular.isDefined(X)?X:M,a.$on("$destroy",function(){V(),w(),P=null})}}}}}]}).directive("uibTooltipTemplateTransclude",["$animate","$sce","$compile","$templateRequest",function(a,b,c,d){return{link:function(e,f,g){var h,i,j,k=e.$eval(g.tooltipTemplateTranscludeScope),l=0,m=function(){i&&(i.remove(),i=null),h&&(h.$destroy(),h=null),j&&(a.leave(j).then(function(){i=null}),i=j,j=null)};e.$watch(b.parseAsResourceUrl(g.uibTooltipTemplateTransclude),function(b){var g=++l;b?(d(b,!0).then(function(d){if(g===l){var e=k.$new(),i=d,n=c(i)(e,function(b){m(),a.enter(b,f)});h=e,j=n,h.$emit("$includeContentLoaded",b)}},function(){g===l&&(m(),e.$emit("$includeContentError",b))}),e.$emit("$includeContentRequested",b)):m()}),e.$on("$destroy",m)}}}]).directive("uibTooltipClasses",["$uibPosition",function(a){return{restrict:"A",link:function(b,c,d){if(b.placement){var e=a.parsePlacement(b.placement);c.addClass(e[0])}b.popupClass&&c.addClass(b.popupClass),b.animation&&c.addClass(d.tooltipAnimationClass)}}}]).directive("uibTooltipPopup",function(){return{restrict:"A",scope:{content:"@"},templateUrl:"uib/template/tooltip/tooltip-popup.html"}}).directive("uibTooltip",["$uibTooltip",function(a){return a("uibTooltip","tooltip","mouseenter")}]).directive("uibTooltipTemplatePopup",function(){return{restrict:"A",scope:{contentExp:"&",originScope:"&"},templateUrl:"uib/template/tooltip/tooltip-template-popup.html"}}).directive("uibTooltipTemplate",["$uibTooltip",function(a){return a("uibTooltipTemplate","tooltip","mouseenter",{useContentExp:!0})}]).directive("uibTooltipHtmlPopup",function(){return{restrict:"A",scope:{contentExp:"&"},templateUrl:"uib/template/tooltip/tooltip-html-popup.html"}}).directive("uibTooltipHtml",["$uibTooltip",function(a){return a("uibTooltipHtml","tooltip","mouseenter",{useContentExp:!0})}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("uibPopoverTemplatePopup",function(){return{restrict:"A",scope:{uibTitle:"@",contentExp:"&",originScope:"&"},templateUrl:"uib/template/popover/popover-template.html"}}).directive("uibPopoverTemplate",["$uibTooltip",function(a){return a("uibPopoverTemplate","popover","click",{useContentExp:!0})}]).directive("uibPopoverHtmlPopup",function(){return{restrict:"A",scope:{contentExp:"&",uibTitle:"@"},templateUrl:"uib/template/popover/popover-html.html"}}).directive("uibPopoverHtml",["$uibTooltip",function(a){return a("uibPopoverHtml","popover","click",{useContentExp:!0})}]).directive("uibPopoverPopup",function(){return{restrict:"A",scope:{uibTitle:"@",content:"@"},templateUrl:"uib/template/popover/popover.html"}}).directive("uibPopover",["$uibTooltip",function(a){return a("uibPopover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("uibProgressConfig",{animate:!0,max:100}).controller("UibProgressController",["$scope","$attrs","uibProgressConfig",function(a,b,c){function d(){return angular.isDefined(a.maxParam)?a.maxParam:c.max}var e=this,f=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=d(),this.addBar=function(a,b,c){f||b.css({transition:"none"}),this.bars.push(a),a.max=d(),a.title=c&&angular.isDefined(c.title)?c.title:"progressbar",a.$watch("value",function(b){a.recalculatePercentage()}),a.recalculatePercentage=function(){var b=e.bars.reduce(function(a,b){return b.percent=+(100*b.value/b.max).toFixed(2),a+b.percent},0);b>100&&(a.percent-=b-100)},a.$on("$destroy",function(){b=null,e.removeBar(a)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1),this.bars.forEach(function(a){a.recalculatePercentage()})},a.$watch("maxParam",function(a){e.bars.forEach(function(a){a.max=d(),a.recalculatePercentage()})})}]).directive("uibProgress",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",require:"uibProgress",scope:{maxParam:"=?max"},templateUrl:"uib/template/progressbar/progress.html"}}).directive("uibBar",function(){return{replace:!0,transclude:!0,require:"^uibProgress",scope:{value:"=",type:"@"},templateUrl:"uib/template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b,c)}}}).directive("uibProgressbar",function(){return{replace:!0,transclude:!0,controller:"UibProgressController",scope:{value:"=",maxParam:"=?max",type:"@"},templateUrl:"uib/template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]),{title:c.title})}}}),angular.module("ui.bootstrap.rating",[]).constant("uibRatingConfig",{max:5,stateOn:null,stateOff:null,enableReset:!0,titles:["one","two","three","four","five"]}).controller("UibRatingController",["$scope","$attrs","uibRatingConfig",function(a,b,c){var d={$setViewValue:angular.noop},e=this;this.init=function(e){d=e,d.$render=this.render,d.$formatters.push(function(a){return angular.isNumber(a)&&a<<0!==a&&(a=Math.round(a)),a}),this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff,this.enableReset=angular.isDefined(b.enableReset)?a.$parent.$eval(b.enableReset):c.enableReset;var f=angular.isDefined(b.titles)?a.$parent.$eval(b.titles):c.titles;this.titles=angular.isArray(f)&&f.length>0?f:c.titles;var g=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(g)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff,title:this.getTitle(b)},a[b]);return a},this.getTitle=function(a){return a>=this.titles.length?a+1:this.titles[a]},a.rate=function(b){if(!a.readonly&&b>=0&&b<=a.range.length){var c=e.enableReset&&d.$viewValue===b?0:b;d.$setViewValue(c),d.$render()}},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue,a.title=e.getTitle(a.value-1)}}]).directive("uibRating",function(){return{require:["uibRating","ngModel"],restrict:"A",scope:{readonly:"=?readOnly",onHover:"&",onLeave:"&"},controller:"UibRatingController",templateUrl:"uib/template/rating/rating.html",link:function(a,b,c,d){var e=d[0],f=d[1];e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("UibTabsetController",["$scope",function(a){function b(a){for(var b=0;bb.index?1:a.index0&&13>b:b>=0&&24>b;return c&&""!==a.hours?(a.showMeridian&&(12===b&&(b=0),a.meridian===y[1]&&(b+=12)),b):void 0}function i(){var b=+a.minutes,c=b>=0&&60>b;return c&&""!==a.minutes?b:void 0}function j(){var b=+a.seconds;return b>=0&&60>b?b:void 0}function k(a,b){return null===a?"":angular.isDefined(a)&&a.toString().length<2&&!b?"0"+a:a.toString()}function l(a){m(),x.$setViewValue(new Date(v)),n(a)}function m(){s&&s.$setValidity("hours",!0),t&&t.$setValidity("minutes",!0),u&&u.$setValidity("seconds",!0),x.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1,a.invalidSeconds=!1}function n(b){if(x.$modelValue){var c=v.getHours(),d=v.getMinutes(),e=v.getSeconds();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:k(c,!z),"m"!==b&&(a.minutes=k(d)),a.meridian=v.getHours()<12?y[0]:y[1],"s"!==b&&(a.seconds=k(e)),a.meridian=v.getHours()<12?y[0]:y[1]}else a.hours=null,a.minutes=null,a.seconds=null,a.meridian=y[0]}function o(a){v=q(v,a),l()}function p(a,b){return q(a,60*b)}function q(a,b){var c=new Date(a.getTime()+1e3*b),d=new Date(a);return d.setHours(c.getHours(),c.getMinutes(),c.getSeconds()),d}function r(){return(null===a.hours||""===a.hours)&&(null===a.minutes||""===a.minutes)&&(!a.showSeconds||a.showSeconds&&(null===a.seconds||""===a.seconds))}var s,t,u,v=new Date,w=[],x={$setViewValue:angular.noop},y=angular.isDefined(c.meridians)?a.$parent.$eval(c.meridians):g.meridians||f.DATETIME_FORMATS.AMPMS,z=angular.isDefined(c.padHours)?a.$parent.$eval(c.padHours):!0;a.tabindex=angular.isDefined(c.tabindex)?c.tabindex:0,b.removeAttr("tabindex"),this.init=function(b,d){x=b,x.$render=this.render,x.$formatters.unshift(function(a){return a?new Date(a):null});var e=d.eq(0),f=d.eq(1),h=d.eq(2);s=e.controller("ngModel"),t=f.controller("ngModel"),u=h.controller("ngModel");var i=angular.isDefined(c.mousewheel)?a.$parent.$eval(c.mousewheel):g.mousewheel;i&&this.setupMousewheelEvents(e,f,h);var j=angular.isDefined(c.arrowkeys)?a.$parent.$eval(c.arrowkeys):g.arrowkeys;j&&this.setupArrowkeyEvents(e,f,h),a.readonlyInput=angular.isDefined(c.readonlyInput)?a.$parent.$eval(c.readonlyInput):g.readonlyInput,this.setupInputEvents(e,f,h)};var A=g.hourStep;c.hourStep&&w.push(a.$parent.$watch(d(c.hourStep),function(a){A=+a}));var B=g.minuteStep;c.minuteStep&&w.push(a.$parent.$watch(d(c.minuteStep),function(a){B=+a}));var C;w.push(a.$parent.$watch(d(c.min),function(a){var b=new Date(a);C=isNaN(b)?void 0:b}));var D;w.push(a.$parent.$watch(d(c.max),function(a){var b=new Date(a);D=isNaN(b)?void 0:b}));var E=!1;c.ngDisabled&&w.push(a.$parent.$watch(d(c.ngDisabled),function(a){E=a})),a.noIncrementHours=function(){var a=p(v,60*A);return E||a>D||v>a&&C>a},a.noDecrementHours=function(){var a=p(v,60*-A);return E||C>a||a>v&&a>D},a.noIncrementMinutes=function(){var a=p(v,B);return E||a>D||v>a&&C>a},a.noDecrementMinutes=function(){var a=p(v,-B);return E||C>a||a>v&&a>D},a.noIncrementSeconds=function(){var a=q(v,F);return E||a>D||v>a&&C>a},a.noDecrementSeconds=function(){var a=q(v,-F);return E||C>a||a>v&&a>D},a.noToggleMeridian=function(){return v.getHours()<12?E||p(v,720)>D:E||p(v,-720)0};b.on("mousewheel wheel",function(b){E||a.$apply(e(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.on("mousewheel wheel",function(b){E||a.$apply(e(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()}),d.on("mousewheel wheel",function(b){E||a.$apply(e(b)?a.incrementSeconds():a.decrementSeconds()),b.preventDefault()})},this.setupArrowkeyEvents=function(b,c,d){b.on("keydown",function(b){E||(38===b.which?(b.preventDefault(),a.incrementHours(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementHours(),a.$apply()))}),c.on("keydown",function(b){E||(38===b.which?(b.preventDefault(),a.incrementMinutes(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementMinutes(),a.$apply()))}),d.on("keydown",function(b){E||(38===b.which?(b.preventDefault(),a.incrementSeconds(),a.$apply()):40===b.which&&(b.preventDefault(),a.decrementSeconds(),a.$apply()))})},this.setupInputEvents=function(b,c,d){if(a.readonlyInput)return a.updateHours=angular.noop,a.updateMinutes=angular.noop,void(a.updateSeconds=angular.noop);var e=function(b,c,d){x.$setViewValue(null),x.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b,s&&s.$setValidity("hours",!1)),angular.isDefined(c)&&(a.invalidMinutes=c,t&&t.$setValidity("minutes",!1)),angular.isDefined(d)&&(a.invalidSeconds=d,u&&u.$setValidity("seconds",!1))};a.updateHours=function(){var a=h(),b=i();x.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(v.setHours(a),v.setMinutes(b),C>v||v>D?e(!0):l("h")):e(!0)},b.on("blur",function(b){x.$setTouched(),r()?m():null===a.hours||""===a.hours?e(!0):!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=k(a.hours,!z)})}),a.updateMinutes=function(){var a=i(),b=h();x.$setDirty(),angular.isDefined(a)&&angular.isDefined(b)?(v.setHours(b),v.setMinutes(a),C>v||v>D?e(void 0,!0):l("m")):e(void 0,!0)},c.on("blur",function(b){x.$setTouched(),r()?m():null===a.minutes?e(void 0,!0):!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=k(a.minutes)})}),a.updateSeconds=function(){var a=j();x.$setDirty(),angular.isDefined(a)?(v.setSeconds(a),l("s")):e(void 0,void 0,!0)},d.on("blur",function(b){r()?m():!a.invalidSeconds&&a.seconds<10&&a.$apply(function(){a.seconds=k(a.seconds)})})},this.render=function(){var b=x.$viewValue;isNaN(b)?(x.$setValidity("time",!1),e.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(b&&(v=b),C>v||v>D?(x.$setValidity("time",!1),a.invalidHours=!0,a.invalidMinutes=!0):m(),n())},a.showSpinners=angular.isDefined(c.showSpinners)?a.$parent.$eval(c.showSpinners):g.showSpinners,a.incrementHours=function(){a.noIncrementHours()||o(60*A*60)},a.decrementHours=function(){a.noDecrementHours()||o(60*-A*60)},a.incrementMinutes=function(){a.noIncrementMinutes()||o(60*B)},a.decrementMinutes=function(){a.noDecrementMinutes()||o(60*-B)},a.incrementSeconds=function(){a.noIncrementSeconds()||o(F)},a.decrementSeconds=function(){a.noDecrementSeconds()||o(-F)},a.toggleMeridian=function(){var b=i(),c=h();a.noToggleMeridian()||(angular.isDefined(b)&&angular.isDefined(c)?o(720*(v.getHours()<12?60:-60)):a.meridian=a.meridian===y[0]?y[1]:y[0])},a.blur=function(){x.$setTouched()},a.$on("$destroy",function(){for(;w.length;)w.shift()()})}]).directive("uibTimepicker",["uibTimepickerConfig",function(a){return{require:["uibTimepicker","?^ngModel"],restrict:"A",controller:"UibTimepickerController",controllerAs:"timepicker",scope:{},templateUrl:function(b,c){return c.templateUrl||a.templateUrl},link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}]),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.debounce","ui.bootstrap.position"]).factory("uibTypeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).controller("UibTypeaheadController",["$scope","$element","$attrs","$compile","$parse","$q","$timeout","$document","$window","$rootScope","$$debounce","$uibPosition","uibTypeaheadParser",function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(){P.moveInProgress||(P.moveInProgress=!0,P.$digest()),$()}function o(){P.position=F?l.offset(b):l.position(b),P.position.top+=b.prop("offsetHeight")}function p(a){var b;return angular.version.minor<6?(b=a.$options||{},b.getOption=function(a){return b[a]}):b=a.$options,b}var q,r,s=[9,13,27,38,40],t=200,u=a.$eval(c.typeaheadMinLength);u||0===u||(u=1),a.$watch(c.typeaheadMinLength,function(a){u=a||0===a?a:1});var v=a.$eval(c.typeaheadWaitMs)||0,w=a.$eval(c.typeaheadEditable)!==!1;a.$watch(c.typeaheadEditable,function(a){w=a!==!1});var x,y,z=e(c.typeaheadLoading).assign||angular.noop,A=c.typeaheadShouldSelect?e(c.typeaheadShouldSelect):function(a,b){var c=b.$event;return 13===c.which||9===c.which},B=e(c.typeaheadOnSelect),C=angular.isDefined(c.typeaheadSelectOnBlur)?a.$eval(c.typeaheadSelectOnBlur):!1,D=e(c.typeaheadNoResults).assign||angular.noop,E=c.typeaheadInputFormatter?e(c.typeaheadInputFormatter):void 0,F=c.typeaheadAppendToBody?a.$eval(c.typeaheadAppendToBody):!1,G=c.typeaheadAppendTo?a.$eval(c.typeaheadAppendTo):null,H=a.$eval(c.typeaheadFocusFirst)!==!1,I=c.typeaheadSelectOnExact?a.$eval(c.typeaheadSelectOnExact):!1,J=e(c.typeaheadIsOpen).assign||angular.noop,K=a.$eval(c.typeaheadShowHint)||!1,L=e(c.ngModel),M=e(c.ngModel+"($$$p)"),N=function(b,c){return angular.isFunction(L(a))&&r.getOption("getterSetter")?M(b,{$$$p:c}):L.assign(b,c)},O=m.parse(c.uibTypeahead),P=a.$new(),Q=a.$on("$destroy",function(){P.$destroy()});P.$on("$destroy",Q);var R="typeahead-"+P.$id+"-"+Math.floor(1e4*Math.random());b.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":R});var S,T;K&&(S=angular.element("
        "),S.css("position","relative"),b.after(S),T=b.clone(),T.attr("placeholder",""),T.attr("tabindex","-1"),T.val(""),T.css({position:"absolute",top:"0px",left:"0px","border-color":"transparent","box-shadow":"none",opacity:1,background:"none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)",color:"#999"}),b.css({position:"relative","vertical-align":"top","background-color":"transparent"}),T.attr("id")&&T.removeAttr("id"),S.append(T),T.after(b));var U=angular.element("
        ");U.attr({id:R,matches:"matches",active:"activeIdx",select:"select(activeIdx, evt)","move-in-progress":"moveInProgress",query:"query",position:"position","assign-is-open":"assignIsOpen(isOpen)",debounce:"debounceUpdate"}),angular.isDefined(c.typeaheadTemplateUrl)&&U.attr("template-url",c.typeaheadTemplateUrl),angular.isDefined(c.typeaheadPopupTemplateUrl)&&U.attr("popup-template-url",c.typeaheadPopupTemplateUrl);var V=function(){K&&T.val("")},W=function(){P.matches=[],P.activeIdx=-1,b.attr("aria-expanded",!1),V()},X=function(a){return R+"-option-"+a};P.$watch("activeIdx",function(a){0>a?b.removeAttr("aria-activedescendant"):b.attr("aria-activedescendant",X(a))});var Y=function(a,b){return P.matches.length>b&&a?a.toUpperCase()===P.matches[b].label.toUpperCase():!1},Z=function(c,d){var e={$viewValue:c};z(a,!0),D(a,!1),f.when(O.source(a,e)).then(function(f){var g=c===q.$viewValue;if(g&&x)if(f&&f.length>0){P.activeIdx=H?0:-1,D(a,!1),P.matches.length=0;for(var h=0;h0&&i.slice(0,c.length).toUpperCase()===c.toUpperCase()?T.val(c+i.slice(c.length)):T.val("")}}else W(),D(a,!0);g&&z(a,!1)},function(){W(),z(a,!1),D(a,!0)})};F&&(angular.element(i).on("resize",n),h.find("body").on("scroll",n));var $=k(function(){P.matches.length&&o(),P.moveInProgress=!1},t);P.moveInProgress=!1,P.query=void 0;var _,aa=function(a){_=g(function(){Z(a)},v)},ba=function(){_&&g.cancel(_)};W(),P.assignIsOpen=function(b){J(a,b)},P.select=function(d,e){var f,h,i={};y=!0,i[O.itemName]=h=P.matches[d].model,f=O.modelMapper(a,i),N(a,f),q.$setValidity("editable",!0),q.$setValidity("parse",!0),B(a,{$item:h,$model:f,$label:O.viewMapper(a,i),$event:e}),W(),P.$eval(c.typeaheadFocusOnSelect)!==!1&&g(function(){b[0].focus()},0,!1)},b.on("keydown",function(b){if(0!==P.matches.length&&-1!==s.indexOf(b.which)){var c=A(a,{$event:b});if(-1===P.activeIdx&&c||9===b.which&&b.shiftKey)return W(),void P.$digest();b.preventDefault();var d;switch(b.which){case 27:b.stopPropagation(),W(),a.$digest();break;case 38:P.activeIdx=(P.activeIdx>0?P.activeIdx:P.matches.length)-1,P.$digest(),d=U[0].querySelectorAll(".uib-typeahead-match")[P.activeIdx],d.parentNode.scrollTop=d.offsetTop;break;case 40:P.activeIdx=(P.activeIdx+1)%P.matches.length,P.$digest(),d=U[0].querySelectorAll(".uib-typeahead-match")[P.activeIdx], d.parentNode.scrollTop=d.offsetTop;break;default:c&&P.$apply(function(){angular.isNumber(P.debounceUpdate)||angular.isObject(P.debounceUpdate)?k(function(){P.select(P.activeIdx,b)},angular.isNumber(P.debounceUpdate)?P.debounceUpdate:P.debounceUpdate["default"]):P.select(P.activeIdx,b)})}}}),b.on("focus",function(a){x=!0,0!==u||q.$viewValue||g(function(){Z(q.$viewValue,a)},0)}),b.on("blur",function(a){C&&P.matches.length&&-1!==P.activeIdx&&!y&&(y=!0,P.$apply(function(){angular.isObject(P.debounceUpdate)&&angular.isNumber(P.debounceUpdate.blur)?k(function(){P.select(P.activeIdx,a)},P.debounceUpdate.blur):P.select(P.activeIdx,a)})),!w&&q.$error.editable&&(q.$setViewValue(),P.$apply(function(){q.$setValidity("editable",!0),q.$setValidity("parse",!0)}),b.val("")),x=!1,y=!1});var ca=function(c){b[0]!==c.target&&3!==c.which&&0!==P.matches.length&&(W(),j.$$phase||a.$digest())};h.on("click",ca),a.$on("$destroy",function(){h.off("click",ca),(F||G)&&da.remove(),F&&(angular.element(i).off("resize",n),h.find("body").off("scroll",n)),U.remove(),K&&S.remove()});var da=d(U)(P);F?h.find("body").append(da):G?angular.element(G).eq(0).append(da):b.after(da),this.init=function(b){q=b,r=p(q),P.debounceUpdate=e(r.getOption("debounce"))(a),q.$parsers.unshift(function(b){return x=!0,0===u||b&&b.length>=u?v>0?(ba(),aa(b)):Z(b):(z(a,!1),ba(),W()),w?b:b?void q.$setValidity("editable",!1):(q.$setValidity("editable",!0),null)}),q.$formatters.push(function(b){var c,d,e={};return w||q.$setValidity("editable",!0),E?(e.$model=b,E(a,e)):(e[O.itemName]=b,c=O.viewMapper(a,e),e[O.itemName]=void 0,d=O.viewMapper(a,e),c!==d?c:b)})}}]).directive("uibTypeahead",function(){return{controller:"UibTypeaheadController",require:["ngModel","uibTypeahead"],link:function(a,b,c,d){d[1].init(d[0])}}}).directive("uibTypeaheadPopup",["$$debounce",function(a){return{scope:{matches:"=",query:"=",active:"=",position:"&",moveInProgress:"=",select:"&",assignIsOpen:"&",debounce:"&"},replace:!0,templateUrl:function(a,b){return b.popupTemplateUrl||"uib/template/typeahead/typeahead-popup.html"},link:function(b,c,d){b.templateUrl=d.templateUrl,b.isOpen=function(){var a=b.matches.length>0;return b.assignIsOpen({isOpen:a}),a},b.isActive=function(a){return b.active===a},b.selectActive=function(a){b.active=a},b.selectMatch=function(c,d){var e=b.debounce();angular.isNumber(e)||angular.isObject(e)?a(function(){b.select({activeIdx:c,evt:d})},angular.isNumber(e)?e:e["default"]):b.select({activeIdx:c,evt:d})}}}}]).directive("uibTypeaheadMatch",["$templateRequest","$compile","$parse",function(a,b,c){return{scope:{index:"=",match:"=",query:"="},link:function(d,e,f){var g=c(f.templateUrl)(d.$parent)||"uib/template/typeahead/typeahead-match.html";a(g).then(function(a){var c=angular.element(a.trim());e.replaceWith(c),b(c)(d)})}}}]).filter("uibTypeaheadHighlight",["$sce","$injector","$log",function(a,b,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function e(a){return/<.*>/g.test(a)}var f;return f=b.has("$sanitize"),function(b,g){return!f&&e(b)&&c.warn("Unsafe use of typeahead please use ngSanitize"),b=g?(""+b).replace(new RegExp(d(g),"gi"),"$&"):b,f||(b=a.trustAsHtml(b)),b}}]),angular.module("uib/template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion-group.html",'
        \n

        \n {{heading}}\n

        \n
        \n
        \n
        \n
        \n')}]),angular.module("uib/template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("uib/template/accordion/accordion.html",'
        ')}]),angular.module("uib/template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("uib/template/alert/alert.html",'\n
        \n')}]),angular.module("uib/template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("uib/template/carousel/carousel.html",'
        \n\n \n previous\n\n\n \n next\n\n
          \n
        1. \n slide {{ $index + 1 }} of {{ slides.length }}, currently active\n
        2. \n
        \n')}]),angular.module("uib/template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("uib/template/carousel/slide.html",'
        \n')}]),angular.module("uib/template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/datepicker.html",'
        \n
        \n
        \n
        \n
        \n')}]),angular.module("uib/template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
        {{::label.abbr}}
        {{ weekNumbers[$index] }}\n \n
        \n')}]),angular.module("uib/template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
        \n \n
        \n')}]),angular.module("uib/template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
        \n \n
        \n')}]),angular.module("uib/template/datepickerPopup/popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/datepickerPopup/popup.html",'
          \n
        • \n
        • \n \n \n \n \n \n
        • \n
        \n')}]),angular.module("uib/template/modal/window.html",[]).run(["$templateCache",function(a){a.put("uib/template/modal/window.html","
        \n")}]),angular.module("uib/template/pager/pager.html",[]).run(["$templateCache",function(a){a.put("uib/template/pager/pager.html",'
      • {{::getText(\'previous\')}}
      • \n
      • {{::getText(\'next\')}}
      • \n')}]),angular.module("uib/template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("uib/template/pagination/pagination.html",'
      • {{::getText(\'first\')}}
      • \n
      • {{::getText(\'previous\')}}
      • \n
      • {{page.text}}
      • \n
      • {{::getText(\'next\')}}
      • \n
      • {{::getText(\'last\')}}
      • \n')}]),angular.module("uib/template/tooltip/tooltip-html-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-html-popup.html",'
        \n
        \n')}]),angular.module("uib/template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-popup.html",'
        \n
        \n')}]),angular.module("uib/template/tooltip/tooltip-template-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/tooltip/tooltip-template-popup.html",'
        \n
        \n')}]),angular.module("uib/template/popover/popover-html.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-html.html",'
        \n\n
        \n

        \n
        \n
        \n')}]),angular.module("uib/template/popover/popover-template.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover-template.html",'
        \n\n
        \n

        \n
        \n
        \n')}]),angular.module("uib/template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("uib/template/popover/popover.html",'
        \n\n
        \n

        \n
        \n
        \n')}]),angular.module("uib/template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/bar.html",'
        \n')}]),angular.module("uib/template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/progress.html",'
        ')}]),angular.module("uib/template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("uib/template/progressbar/progressbar.html",'
        \n
        \n
        \n')}]),angular.module("uib/template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("uib/template/rating/rating.html",'\n ({{ $index < value ? \'*\' : \' \' }})\n \n\n')}]),angular.module("uib/template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tab.html",'
      • \n {{heading}}\n
      • \n')}]),angular.module("uib/template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("uib/template/tabs/tabset.html",'
        \n
          \n
          \n
          \n
          \n
          \n
          \n')}]),angular.module("uib/template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("uib/template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
            
          \n \n :\n \n :\n \n
            
          \n')}]),angular.module("uib/template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("uib/template/typeahead/typeahead-match.html",'\n')}]),angular.module("uib/template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("uib/template/typeahead/typeahead-popup.html",'
            \n
          • \n
            \n
          • \n
          \n')}]),angular.module("ui.bootstrap.carousel").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibCarouselCss&&angular.element(document).find("head").prepend(''),angular.$$uibCarouselCss=!0}),angular.module("ui.bootstrap.datepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerCss=!0}),angular.module("ui.bootstrap.position").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibPositionCss&&angular.element(document).find("head").prepend(''),angular.$$uibPositionCss=!0}),angular.module("ui.bootstrap.datepickerPopup").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibDatepickerpopupCss&&angular.element(document).find("head").prepend(''),angular.$$uibDatepickerpopupCss=!0}),angular.module("ui.bootstrap.tooltip").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTooltipCss&&angular.element(document).find("head").prepend(''),angular.$$uibTooltipCss=!0}),angular.module("ui.bootstrap.timepicker").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTimepickerCss&&angular.element(document).find("head").prepend(''),angular.$$uibTimepickerCss=!0}),angular.module("ui.bootstrap.typeahead").run(function(){!angular.$$csp().noInlineStyle&&!angular.$$uibTypeaheadCss&&angular.element(document).find("head").prepend(''),angular.$$uibTypeaheadCss=!0});angular-cookies/000077500000000000000000000000001325274564300342025ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwrangular-cookies.js000066400000000000000000000234531325274564300376320ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-cookies/** * @license AngularJS v1.5.11 * (c) 2010-2017 Google, Inc. http://angularjs.org * License: MIT */ (function(window, angular) {'use strict'; /** * @ngdoc module * @name ngCookies * @description * * # ngCookies * * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. * * *
          * * See {@link ngCookies.$cookies `$cookies`} for usage. */ angular.module('ngCookies', ['ng']). /** * @ngdoc provider * @name $cookiesProvider * @description * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service. * */ provider('$cookies', [/** @this */function $CookiesProvider() { /** * @ngdoc property * @name $cookiesProvider#defaults * @description * * Object containing default options to pass when setting cookies. * * The object may have following properties: * * - **path** - `{string}` - The cookie will be available only for this path and its * sub-paths. By default, this is the URL that appears in your `` tag. * - **domain** - `{string}` - The cookie will be available only for this domain and * its sub-domains. For security reasons the user agent will not accept the cookie * if the current domain is not a sub-domain of this domain or equal to it. * - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT" * or a Date object indicating the exact date/time this cookie will expire. * - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a * secured connection. * * Note: By default, the address that appears in your `` tag will be used as the path. * This is important so that cookies will be visible for all routes when html5mode is enabled. * * @example * * ```js * angular.module('cookiesProviderExample', ['ngCookies']) * .config(['$cookiesProvider', function($cookiesProvider) { * // Setting default options * $cookiesProvider.defaults.domain = 'foo.com'; * $cookiesProvider.defaults.secure = true; * }]); * ``` **/ var defaults = this.defaults = {}; function calcOptions(options) { return options ? angular.extend({}, defaults, options) : defaults; } /** * @ngdoc service * @name $cookies * * @description * Provides read/write access to browser's cookies. * *
          * Up until Angular 1.3, `$cookies` exposed properties that represented the * current browser cookie values. In version 1.4, this behavior has changed, and * `$cookies` now provides a standard api of getters, setters etc. *
          * * Requires the {@link ngCookies `ngCookies`} module to be installed. * * @example * * ```js * angular.module('cookiesExample', ['ngCookies']) * .controller('ExampleController', ['$cookies', function($cookies) { * // Retrieving a cookie * var favoriteCookie = $cookies.get('myFavorite'); * // Setting a cookie * $cookies.put('myFavorite', 'oatmeal'); * }]); * ``` */ this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) { return { /** * @ngdoc method * @name $cookies#get * * @description * Returns the value of given cookie key * * @param {string} key Id to use for lookup. * @returns {string} Raw cookie value. */ get: function(key) { return $$cookieReader()[key]; }, /** * @ngdoc method * @name $cookies#getObject * * @description * Returns the deserialized value of given cookie key * * @param {string} key Id to use for lookup. * @returns {Object} Deserialized cookie value. */ getObject: function(key) { var value = this.get(key); return value ? angular.fromJson(value) : value; }, /** * @ngdoc method * @name $cookies#getAll * * @description * Returns a key value object with all the cookies * * @returns {Object} All cookies */ getAll: function() { return $$cookieReader(); }, /** * @ngdoc method * @name $cookies#put * * @description * Sets a value for given cookie key * * @param {string} key Id for the `value`. * @param {string} value Raw value to be stored. * @param {Object=} options Options object. * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} */ put: function(key, value, options) { $$cookieWriter(key, value, calcOptions(options)); }, /** * @ngdoc method * @name $cookies#putObject * * @description * Serializes and sets a value for given cookie key * * @param {string} key Id for the `value`. * @param {Object} value Value to be stored. * @param {Object=} options Options object. * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} */ putObject: function(key, value, options) { this.put(key, angular.toJson(value), options); }, /** * @ngdoc method * @name $cookies#remove * * @description * Remove given cookie * * @param {string} key Id of the key-value pair to delete. * @param {Object=} options Options object. * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults} */ remove: function(key, options) { $$cookieWriter(key, undefined, calcOptions(options)); } }; }]; }]); angular.module('ngCookies'). /** * @ngdoc service * @name $cookieStore * @deprecated * sinceVersion="v1.4.0" * Please use the {@link ngCookies.$cookies `$cookies`} service instead. * * @requires $cookies * * @description * Provides a key-value (string-object) storage, that is backed by session cookies. * Objects put or retrieved from this storage are automatically serialized or * deserialized by angular's toJson/fromJson. * * Requires the {@link ngCookies `ngCookies`} module to be installed. * * @example * * ```js * angular.module('cookieStoreExample', ['ngCookies']) * .controller('ExampleController', ['$cookieStore', function($cookieStore) { * // Put cookie * $cookieStore.put('myFavorite','oatmeal'); * // Get cookie * var favoriteCookie = $cookieStore.get('myFavorite'); * // Removing a cookie * $cookieStore.remove('myFavorite'); * }]); * ``` */ factory('$cookieStore', ['$cookies', function($cookies) { return { /** * @ngdoc method * @name $cookieStore#get * * @description * Returns the value of given cookie key * * @param {string} key Id to use for lookup. * @returns {Object} Deserialized cookie value, undefined if the cookie does not exist. */ get: function(key) { return $cookies.getObject(key); }, /** * @ngdoc method * @name $cookieStore#put * * @description * Sets a value for given cookie key * * @param {string} key Id for the `value`. * @param {Object} value Value to be stored. */ put: function(key, value) { $cookies.putObject(key, value); }, /** * @ngdoc method * @name $cookieStore#remove * * @description * Remove given cookie * * @param {string} key Id of the key-value pair to delete. */ remove: function(key) { $cookies.remove(key); } }; }]); /** * @name $$cookieWriter * @requires $document * * @description * This is a private service for writing cookies * * @param {string} name Cookie name * @param {string=} value Cookie value (if undefined, cookie will be deleted) * @param {Object=} options Object with options that need to be stored for the cookie. */ function $$CookieWriter($document, $log, $browser) { var cookiePath = $browser.baseHref(); var rawDocument = $document[0]; function buildCookieString(name, value, options) { var path, expires; options = options || {}; expires = options.expires; path = angular.isDefined(options.path) ? options.path : cookiePath; if (angular.isUndefined(value)) { expires = 'Thu, 01 Jan 1970 00:00:00 GMT'; value = ''; } if (angular.isString(expires)) { expires = new Date(expires); } var str = encodeURIComponent(name) + '=' + encodeURIComponent(value); str += path ? ';path=' + path : ''; str += options.domain ? ';domain=' + options.domain : ''; str += expires ? ';expires=' + expires.toUTCString() : ''; str += options.secure ? ';secure' : ''; // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: // - 300 cookies // - 20 cookies per unique domain // - 4096 bytes per cookie var cookieLength = str.length + 1; if (cookieLength > 4096) { $log.warn('Cookie \'' + name + '\' possibly not set or overflowed because it was too large (' + cookieLength + ' > 4096 bytes)!'); } return str; } return function(name, value, options) { rawDocument.cookie = buildCookieString(name, value, options); }; } $$CookieWriter.$inject = ['$document', '$log', '$browser']; angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$CookieWriterProvider() { this.$get = $$CookieWriter; }); })(window, window.angular); angular-cookies.min.js000066400000000000000000000026501325274564300404100ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-cookies/* AngularJS v1.5.11 (c) 2010-2017 Google, Inc. http://angularjs.org License: MIT */ (function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore", ["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular); //# sourceMappingURL=angular-cookies.min.js.map angular-cookies.min.js.map000066400000000000000000000064741325274564300411740ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-cookies{ "version":3, "file":"angular-cookies.min.js", "lineCount":8, "mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkB,CAmR3BC,QAASA,EAAc,CAACC,CAAD,CAAYC,CAAZ,CAAkBC,CAAlB,CAA4B,CACjD,IAAIC,EAAaD,CAAAE,SAAA,EAAjB,CACIC,EAAcL,CAAA,CAAU,CAAV,CAmClB,OAAO,SAAQ,CAACM,CAAD,CAAOC,CAAP,CAAcC,CAAd,CAAuB,CAjCW,IAC3CC,CAD2C,CACrCC,CACVF,EAAA,CAgCoDA,CAhCpD,EAAqB,EACrBE,EAAA,CAAUF,CAAAE,QACVD,EAAA,CAAOX,CAAAa,UAAA,CAAkBH,CAAAC,KAAlB,CAAA,CAAkCD,CAAAC,KAAlC,CAAiDN,CACpDL,EAAAc,YAAA,CAAoBL,CAApB,CAAJ,GACEG,CACA,CADU,+BACV,CAAAH,CAAA,CAAQ,EAFV,CAIIT,EAAAe,SAAA,CAAiBH,CAAjB,CAAJ,GACEA,CADF,CACY,IAAII,IAAJ,CAASJ,CAAT,CADZ,CAIIK,EAAAA,CAAMC,kBAAA,CAqB6BV,CArB7B,CAANS,CAAiC,GAAjCA,CAAuCC,kBAAA,CAAmBT,CAAnB,CAE3CQ,EAAA,CADAA,CACA,EADON,CAAA,CAAO,QAAP,CAAkBA,CAAlB,CAAyB,EAChC,GAAOD,CAAAS,OAAA,CAAiB,UAAjB,CAA8BT,CAAAS,OAA9B,CAA+C,EAAtD,CACAF,EAAA,EAAOL,CAAA,CAAU,WAAV,CAAwBA,CAAAQ,YAAA,EAAxB,CAAgD,EACvDH,EAAA,EAAOP,CAAAW,OAAA,CAAiB,SAAjB,CAA6B,EAMhCC,EAAAA,CAAeL,CAAAM,OAAfD,CAA4B,CACb,KAAnB,CAAIA,CAAJ,EACEnB,CAAAqB,KAAA,CAAU,UAAV,CASqChB,CATrC,CACE,6DADF;AAEEc,CAFF,CAEiB,iBAFjB,CASFf,EAAAkB,OAAA,CAJOR,CAG6B,CArCW,CAjQnDjB,CAAA0B,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,SAAA,CAOY,UAPZ,CAOwB,CAAaC,QAAyB,EAAG,CAkC7D,IAAIC,EAAW,IAAAA,SAAXA,CAA2B,EAiC/B,KAAAC,KAAA,CAAY,CAAC,gBAAD,CAAmB,gBAAnB,CAAqC,QAAQ,CAACC,CAAD,CAAiBC,CAAjB,CAAiC,CACxF,MAAO,CAWLC,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOH,EAAA,EAAA,CAAiBG,CAAjB,CADU,CAXd,CAyBLC,UAAWA,QAAQ,CAACD,CAAD,CAAM,CAEvB,MAAO,CADHzB,CACG,CADK,IAAAwB,IAAA,CAASC,CAAT,CACL,EAAQlC,CAAAoC,SAAA,CAAiB3B,CAAjB,CAAR,CAAkCA,CAFlB,CAzBpB,CAuCL4B,OAAQA,QAAQ,EAAG,CACjB,MAAON,EAAA,EADU,CAvCd,CAuDLO,IAAKA,QAAQ,CAACJ,CAAD,CAAMzB,CAAN,CAAaC,CAAb,CAAsB,CACjCsB,CAAA,CAAeE,CAAf,CAAoBzB,CAApB,CAAuCC,CAvFpC,CAAUV,CAAAuC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAuF0BnB,CAvF1B,CAAV,CAAkDmB,CAuFrD,CADiC,CAvD9B,CAuELW,UAAWA,QAAQ,CAACN,CAAD,CAAMzB,CAAN,CAAaC,CAAb,CAAsB,CACvC,IAAA4B,IAAA,CAASJ,CAAT,CAAclC,CAAAyC,OAAA,CAAehC,CAAf,CAAd,CAAqCC,CAArC,CADuC,CAvEpC,CAsFLgC,OAAQA,QAAQ,CAACR,CAAD,CAAMxB,CAAN,CAAe,CAC7BsB,CAAA,CAAeE,CAAf,CAAoBS,IAAAA,EAApB,CAA2CjC,CAtHxC,CAAUV,CAAAuC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAsH8BnB,CAtH9B,CAAV,CAAkDmB,CAsHrD,CAD6B,CAtF1B,CADiF,CAA9E,CAnEiD,CAAzC,CAPxB,CAwKA7B,EAAA0B,OAAA,CAAe,WAAf,CAAAkB,QAAA,CA+BS,cA/BT;AA+ByB,CAAC,UAAD,CAAa,QAAQ,CAACC,CAAD,CAAW,CAErD,MAAO,CAWLZ,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOW,EAAAV,UAAA,CAAmBD,CAAnB,CADU,CAXd,CAyBLI,IAAKA,QAAQ,CAACJ,CAAD,CAAMzB,CAAN,CAAa,CACxBoC,CAAAL,UAAA,CAAmBN,CAAnB,CAAwBzB,CAAxB,CADwB,CAzBrB,CAsCLiC,OAAQA,QAAQ,CAACR,CAAD,CAAM,CACpBW,CAAAH,OAAA,CAAgBR,CAAhB,CADoB,CAtCjB,CAF8C,CAAhC,CA/BzB,CAmIAjC,EAAA6C,QAAA,CAAyB,CAAC,WAAD,CAAc,MAAd,CAAsB,UAAtB,CAEzB9C,EAAA0B,OAAA,CAAe,WAAf,CAAAC,SAAA,CAAqC,gBAArC,CAAoEoB,QAA+B,EAAG,CACpG,IAAAjB,KAAA,CAAY7B,CADwF,CAAtG,CA/T2B,CAA1B,CAAD,CAoUGF,MApUH,CAoUWA,MAAAC,QApUX;", "sources":["angular-cookies.js"], "names":["window","angular","$$CookieWriter","$document","$log","$browser","cookiePath","baseHref","rawDocument","name","value","options","path","expires","isDefined","isUndefined","isString","Date","str","encodeURIComponent","domain","toUTCString","secure","cookieLength","length","warn","cookie","module","provider","$CookiesProvider","defaults","$get","$$cookieReader","$$cookieWriter","get","key","getObject","fromJson","getAll","put","extend","putObject","toJson","remove","undefined","factory","$cookies","$inject","$$CookieWriterProvider"] } angular-ui-tree/000077500000000000000000000000001325274564300341205ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwrdist/000077500000000000000000000000001325274564300350635ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-ui-treeangular-ui-tree.js000066400000000000000000002074221325274564300404310ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-ui-tree/dist/** * @license Angular UI Tree v2.22.6 * (c) 2010-2017. https://github.com/angular-ui-tree/angular-ui-tree * License: MIT */ (function () { 'use strict'; angular.module('ui.tree', []) .constant('treeConfig', { treeClass: 'angular-ui-tree', emptyTreeClass: 'angular-ui-tree-empty', dropzoneClass: 'angular-ui-tree-dropzone', hiddenClass: 'angular-ui-tree-hidden', nodesClass: 'angular-ui-tree-nodes', nodeClass: 'angular-ui-tree-node', handleClass: 'angular-ui-tree-handle', placeholderClass: 'angular-ui-tree-placeholder', dragClass: 'angular-ui-tree-drag', dragThreshold: 3, defaultCollapsed: false, appendChildOnHover: true }); })(); (function () { 'use strict'; angular.module('ui.tree') .controller('TreeHandleController', ['$scope', '$element', function ($scope, $element) { this.scope = $scope; $scope.$element = $element; $scope.$nodeScope = null; $scope.$type = 'uiTreeHandle'; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .controller('TreeNodeController', ['$scope', '$element', function ($scope, $element) { this.scope = $scope; $scope.$element = $element; $scope.$modelValue = null; // Model value for node; $scope.$parentNodeScope = null; // uiTreeNode Scope of parent node; $scope.$childNodesScope = null; // uiTreeNodes Scope of child nodes. $scope.$parentNodesScope = null; // uiTreeNodes Scope of parent nodes. $scope.$treeScope = null; // uiTree scope $scope.$handleScope = null; // it's handle scope $scope.$type = 'uiTreeNode'; $scope.$$allowNodeDrop = false; $scope.collapsed = false; $scope.expandOnHover = false; //Called by uiTreeNode Directive on load. $scope.init = function (controllersArr) { var treeNodesCtrl = controllersArr[0]; $scope.$treeScope = controllersArr[1] ? controllersArr[1].scope : null; //Find the scope of it's parent node. $scope.$parentNodeScope = treeNodesCtrl.scope.$nodeScope; //modelValue for current node. $scope.$modelValue = treeNodesCtrl.scope.$modelValue[$scope.$index]; $scope.$parentNodesScope = treeNodesCtrl.scope; //Init sub nodes. treeNodesCtrl.scope.initSubNode($scope); $element.on('$destroy', function () { //Destroy sub nodes. treeNodesCtrl.scope.destroySubNode($scope); }); }; //Return the index of child node in parent node (nodesScope). $scope.index = function () { return $scope.$parentNodesScope.$modelValue.indexOf($scope.$modelValue); }; $scope.dragEnabled = function () { return !($scope.$treeScope && !$scope.$treeScope.dragEnabled); }; $scope.isSibling = function (targetNode) { return $scope.$parentNodesScope == targetNode.$parentNodesScope; }; $scope.isChild = function (targetNode) { var nodes = $scope.childNodes(); return nodes && nodes.indexOf(targetNode) > -1; }; //TODO(jcarter): This method is on uiTreeHelper already. $scope.prev = function () { var index = $scope.index(); if (index > 0) { return $scope.siblings()[index - 1]; } return null; }; //Calls childNodes on parent. $scope.siblings = function () { return $scope.$parentNodesScope.childNodes(); }; $scope.childNodesCount = function () { return $scope.childNodes() ? $scope.childNodes().length : 0; }; $scope.hasChild = function () { return $scope.childNodesCount() > 0; }; $scope.childNodes = function () { return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue ? $scope.$childNodesScope.childNodes() : null; }; $scope.accept = function (sourceNode, destIndex) { return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue && $scope.$childNodesScope.accept(sourceNode, destIndex); }; $scope.remove = function () { return $scope.$parentNodesScope.removeNode($scope); }; $scope.toggle = function () { $scope.collapsed = !$scope.collapsed; $scope.$treeScope.$callbacks.toggle($scope.collapsed, $scope); }; $scope.collapse = function () { $scope.collapsed = true; }; $scope.expand = function () { $scope.collapsed = false; }; $scope.depth = function () { var parentNode = $scope.$parentNodeScope; if (parentNode) { return parentNode.depth() + 1; } return 1; }; /** * Returns the depth of the deepest subtree under this node * @param scope a TreeNodesController scope object * @returns Depth of all nodes *beneath* this node. If scope belongs to a leaf node, the * result is 0 (it has no subtree). */ function countSubTreeDepth(scope) { if (!scope) { return 0; } var thisLevelDepth = 0, childNodes = scope.childNodes(), childNode, childDepth, i; if (!childNodes || childNodes.length === 0) { return 0; } for (i = childNodes.length - 1; i >= 0 ; i--) { childNode = childNodes[i], childDepth = 1 + countSubTreeDepth(childNode); thisLevelDepth = Math.max(thisLevelDepth, childDepth); } return thisLevelDepth; } $scope.maxSubDepth = function () { return $scope.$childNodesScope ? countSubTreeDepth($scope.$childNodesScope) : 0; }; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .controller('TreeNodesController', ['$scope', '$element', '$timeout', function ($scope, $element, $timeout) { this.scope = $scope; $scope.$element = $element; $scope.$modelValue = null; $scope.$nodeScope = null; // the scope of node which the nodes belongs to $scope.$treeScope = null; $scope.$type = 'uiTreeNodes'; $scope.$nodesMap = {}; $scope.nodropEnabled = false; $scope.maxDepth = 0; $scope.cloneEnabled = false; $scope.initSubNode = function (subNode) { if (!subNode.$modelValue) { return null; } $scope.$nodesMap[subNode.$modelValue.$$hashKey] = subNode; }; $scope.destroySubNode = function (subNode) { if (!subNode.$modelValue) { return null; } $scope.$nodesMap[subNode.$modelValue.$$hashKey] = null; }; $scope.accept = function (sourceNode, destIndex) { return $scope.$treeScope.$callbacks.accept(sourceNode, $scope, destIndex); }; $scope.beforeDrag = function (sourceNode) { return $scope.$treeScope.$callbacks.beforeDrag(sourceNode); }; $scope.isParent = function (node) { return node.$parentNodesScope == $scope; }; $scope.hasChild = function () { return $scope.$modelValue.length > 0; }; //Called in apply method of UiTreeHelper.dragInfo. $scope.removeNode = function (node) { var index = $scope.$modelValue.indexOf(node.$modelValue); if (index > -1) { $timeout(function () { $scope.$modelValue.splice(index, 1)[0]; }); return $scope.$treeScope.$callbacks.removed(node); } return null; }; //Called in apply method of UiTreeHelper.dragInfo. $scope.insertNode = function (index, nodeData) { $timeout(function () { $scope.$modelValue.splice(index, 0, nodeData); }); }; $scope.childNodes = function () { var i, nodes = []; if ($scope.$modelValue) { for (i = 0; i < $scope.$modelValue.length; i++) { nodes.push($scope.$nodesMap[$scope.$modelValue[i].$$hashKey]); } } return nodes; }; $scope.depth = function () { if ($scope.$nodeScope) { return $scope.$nodeScope.depth(); } return 0; // if it has no $nodeScope, it's root }; // check if depth limit has reached $scope.outOfDepth = function (sourceNode) { var maxDepth = $scope.maxDepth || $scope.$treeScope.maxDepth; if (maxDepth > 0) { return $scope.depth() + sourceNode.maxSubDepth() + 1 > maxDepth; } return false; }; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .controller('TreeController', ['$scope', '$element', function ($scope, $element) { this.scope = $scope; $scope.$element = $element; $scope.$nodesScope = null; // root nodes $scope.$type = 'uiTree'; $scope.$emptyElm = null; $scope.$dropzoneElm = null; $scope.$callbacks = null; $scope.dragEnabled = true; $scope.emptyPlaceholderEnabled = true; $scope.maxDepth = 0; $scope.dragDelay = 0; $scope.cloneEnabled = false; $scope.nodropEnabled = false; $scope.dropzoneEnabled = false; // Check if it's a empty tree $scope.isEmpty = function () { return ($scope.$nodesScope && $scope.$nodesScope.$modelValue && $scope.$nodesScope.$modelValue.length === 0); }; // add placeholder to empty tree $scope.place = function (placeElm) { $scope.$nodesScope.$element.append(placeElm); $scope.$emptyElm.remove(); }; this.resetEmptyElement = function () { if ((!$scope.$nodesScope.$modelValue || $scope.$nodesScope.$modelValue.length === 0) && $scope.emptyPlaceholderEnabled) { $element.append($scope.$emptyElm); } else { $scope.$emptyElm.remove(); } }; this.resetDropzoneElement = function () { if ((!$scope.$nodesScope.$modelValue || $scope.$nodesScope.$modelValue.length !== 0) && $scope.dropzoneEnabled) { $element.append($scope.$dropzoneElm); } else { $scope.$dropzoneElm.remove(); } }; $scope.resetEmptyElement = this.resetEmptyElement; $scope.resetDropzoneElement = this.resetDropzoneElement; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .directive('uiTree', ['treeConfig', '$window', function (treeConfig, $window) { return { restrict: 'A', scope: true, controller: 'TreeController', link: function (scope, element, attrs, ctrl) { var callbacks = { accept: null, beforeDrag: null }, config = {}, tdElm, $trElm, emptyElmColspan; //Adding configured class to uiTree. angular.extend(config, treeConfig); if (config.treeClass) { element.addClass(config.treeClass); } //Determining if uiTree is on a table. if (element.prop('tagName').toLowerCase() === 'table') { scope.$emptyElm = angular.element($window.document.createElement('tr')); $trElm = element.find('tr'); //If we can find a tr, then we can use its td children as the empty element colspan. if ($trElm.length > 0) { emptyElmColspan = angular.element($trElm).children().length; } else { //If not, by setting a huge colspan we make sure it takes full width. //TODO(jcarter): Check for negative side effects. emptyElmColspan = 1000000; } tdElm = angular.element($window.document.createElement('td')) .attr('colspan', emptyElmColspan); scope.$emptyElm.append(tdElm); } else { scope.$emptyElm = angular.element($window.document.createElement('div')); scope.$dropzoneElm = angular.element($window.document.createElement('div')); } if (config.emptyTreeClass) { scope.$emptyElm.addClass(config.emptyTreeClass); } if (config.dropzoneClass) { scope.$dropzoneElm.addClass(config.dropzoneClass); } scope.$watch('$nodesScope.$modelValue.length', function (val) { if (!angular.isNumber(val)) { return; } ctrl.resetEmptyElement(); ctrl.resetDropzoneElement(); }, true); scope.$watch(attrs.dragEnabled, function (val) { if ((typeof val) == 'boolean') { scope.dragEnabled = val; } }); scope.$watch(attrs.emptyPlaceholderEnabled, function (val) { if ((typeof val) == 'boolean') { scope.emptyPlaceholderEnabled = val; ctrl.resetEmptyElement(); } }); scope.$watch(attrs.nodropEnabled, function (val) { if ((typeof val) == 'boolean') { scope.nodropEnabled = val; } }); scope.$watch(attrs.dropzoneEnabled, function (val) { if ((typeof val) == 'boolean') { scope.dropzoneEnabled = val; ctrl.resetDropzoneElement(); } }); scope.$watch(attrs.cloneEnabled, function (val) { if ((typeof val) == 'boolean') { scope.cloneEnabled = val; } }); scope.$watch(attrs.maxDepth, function (val) { if ((typeof val) == 'number') { scope.maxDepth = val; } }); scope.$watch(attrs.dragDelay, function (val) { if ((typeof val) == 'number') { scope.dragDelay = val; } }); /** * Callback checks if the destination node can accept the dragged node. * By default, ui-tree will check that 'data-nodrop-enabled' is not set for the * destination ui-tree-nodes, and that the 'max-depth' attribute will not be exceeded * if it is set on the ui-tree or ui-tree-nodes. * This callback can be overridden, but callers must manually enforce nodrop and max-depth * themselves if they need those to be enforced. * @param sourceNodeScope Scope of the ui-tree-node being dragged * @param destNodesScope Scope of the ui-tree-nodes where the node is hovering * @param destIndex Index in the destination nodes array where the source node will drop * @returns {boolean} True if the node is permitted to be dropped here */ callbacks.accept = function (sourceNodeScope, destNodesScope, destIndex) { return !(destNodesScope.nodropEnabled || destNodesScope.$treeScope.nodropEnabled || destNodesScope.outOfDepth(sourceNodeScope)); }; callbacks.beforeDrag = function (sourceNodeScope) { return true; }; callbacks.expandTimeoutStart = function() { }; callbacks.expandTimeoutCancel = function() { }; callbacks.expandTimeoutEnd = function() { }; callbacks.removed = function (node) { }; /** * Callback is fired when a node is successfully dropped in a new location * @param event */ callbacks.dropped = function (event) { }; /** * Callback is fired each time the user starts dragging a node * @param event */ callbacks.dragStart = function (event) { }; /** * Callback is fired each time a dragged node is moved with the mouse/touch. * @param event */ callbacks.dragMove = function (event) { }; /** * Callback is fired when the tree exits drag mode. If the user dropped a node, the drop may have been * accepted or reverted. * @param event */ callbacks.dragStop = function (event) { }; /** * Callback is fired when a user drops a node (but prior to processing the drop action) * beforeDrop can return a Promise, truthy, or falsy (returning nothing is falsy). * If it returns falsy, or a resolve Promise, the node move is accepted * If it returns truthy, or a rejected Promise, the node move is reverted * @param event * @returns {Boolean|Promise} Truthy (or rejected Promise) to cancel node move; falsy (or resolved promise) */ callbacks.beforeDrop = function (event) { }; /** * Callback is fired when a user toggles node (but after processing the toggle action) * @param sourceNodeScope * @param collapsed */ callbacks.toggle = function (collapsed, sourceNodeScope) { }; scope.$watch(attrs.uiTree, function (newVal, oldVal) { angular.forEach(newVal, function (value, key) { if (callbacks[key]) { if (typeof value === 'function') { callbacks[key] = value; } } }); scope.$callbacks = callbacks; }, true); } }; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .directive('uiTreeHandle', ['treeConfig', function (treeConfig) { return { require: '^uiTreeNode', restrict: 'A', scope: true, controller: 'TreeHandleController', link: function (scope, element, attrs, treeNodeCtrl) { var config = {}; angular.extend(config, treeConfig); if (config.handleClass) { element.addClass(config.handleClass); } // connect with the tree node. if (scope != treeNodeCtrl.scope) { scope.$nodeScope = treeNodeCtrl.scope; treeNodeCtrl.scope.$handleScope = scope; } } }; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .directive('uiTreeNode', ['treeConfig', 'UiTreeHelper', '$window', '$document', '$timeout', '$q', function (treeConfig, UiTreeHelper, $window, $document, $timeout, $q) { return { require: ['^uiTreeNodes', '^uiTree'], restrict: 'A', controller: 'TreeNodeController', link: function (scope, element, attrs, controllersArr) { var config = {}, hasTouch = 'ontouchstart' in window, firstMoving, dragInfo, pos, placeElm, hiddenPlaceElm, dragElm, scrollContainerElm, unhover, treeScope = null, elements, // As a parameter for callbacks dragDelaying = true, dragStarted = false, dragTimer = null, body = document.body, html = document.documentElement, document_height, document_width, dragStart, tagName, dragMove, dragEnd, dragStartEvent, dragMoveEvent, dragEndEvent, dragCancelEvent, dragDelay, bindDragStartEvents, bindDragMoveEvents, unbindDragMoveEvents, keydownHandler, isHandleChild, el, isUiTreeRoot, treeOfOrigin; //Adding configured class to ui-tree-node. angular.extend(config, treeConfig); if (config.nodeClass) { element.addClass(config.nodeClass); } //Call init function in nodeCtrl, sets parent node and sets up sub nodes. scope.init(controllersArr); scope.collapsed = !!UiTreeHelper.getNodeAttribute(scope, 'collapsed') || treeConfig.defaultCollapsed; scope.expandOnHover = !!UiTreeHelper.getNodeAttribute(scope, 'expandOnHover'); scope.scrollContainer = UiTreeHelper.getNodeAttribute(scope, 'scrollContainer') || attrs.scrollContainer || null; scope.sourceOnly = scope.nodropEnabled || scope.$treeScope.nodropEnabled; scope.$watch(attrs.collapsed, function (val) { if ((typeof val) == 'boolean') { scope.collapsed = val; } }); //Watches to trigger behavior based on actions and settings. scope.$watch('collapsed', function (val) { UiTreeHelper.setNodeAttribute(scope, 'collapsed', val); attrs.$set('collapsed', val); }); scope.$watch(attrs.expandOnHover, function(val) { if ((typeof val) === 'boolean' || (typeof val) === 'number') { scope.expandOnHover = val; } }); scope.$watch('expandOnHover', function (val) { UiTreeHelper.setNodeAttribute(scope, 'expandOnHover', val); attrs.$set('expandOnHover', val); }); attrs.$observe('scrollContainer', function(val) { if ((typeof val) === 'string') { scope.scrollContainer = val; } }); scope.$watch('scrollContainer', function(val) { UiTreeHelper.setNodeAttribute(scope, 'scrollContainer', val); attrs.$set('scrollContainer', val); scrollContainerElm = document.querySelector(val); }); scope.$on('angular-ui-tree:collapse-all', function () { scope.collapsed = true; }); scope.$on('angular-ui-tree:expand-all', function () { scope.collapsed = false; }); /** * Called when the user has grabbed a node and started dragging it. * * @param {MouseEvent} e event that is triggered by DOM. * @return undefined? */ dragStart = function (e) { //Disable right click. if (!hasTouch && (e.button === 2 || e.which === 3)) { return; } //Event has already fired in other scope. if (e.uiTreeDragging || (e.originalEvent && e.originalEvent.uiTreeDragging)) { return; } //The node being dragged. var eventElm = angular.element(e.target), isHandleChild, cloneElm, eventElmTagName, tagName, eventObj, tdElm, hStyle, isTreeNode, isTreeNodeHandle; //If the target element is a child element of a ui-tree-handle, // use the containing handle element as target element. isHandleChild = UiTreeHelper.treeNodeHandlerContainerOfElement(eventElm); if (isHandleChild) { eventElm = angular.element(isHandleChild); } cloneElm = element.clone(); isTreeNode = UiTreeHelper.elementIsTreeNode(eventElm); isTreeNodeHandle = UiTreeHelper.elementIsTreeNodeHandle(eventElm); //If we are not triggering mousedown on our uiTree or any of it's parts, return. if (!isTreeNode && !isTreeNodeHandle) { return; } //If we are not triggering mousedown on our uiTree or any of it's parts, return. if (isTreeNode && UiTreeHelper.elementContainsTreeNodeHandler(eventElm)) { return; } //Dragging not allowed on inputs or buttons. eventElmTagName = eventElm.prop('tagName').toLowerCase(); if (eventElmTagName == 'input' || eventElmTagName == 'textarea' || eventElmTagName == 'button' || eventElmTagName == 'select') { return; } //Check if it or it's parents has a 'data-nodrag' attribute el = angular.element(e.target); isUiTreeRoot = el[0].attributes['ui-tree']; while (el && el[0] && el[0] !== element && !isUiTreeRoot) { //Checking that I can access attributes. if (el[0].attributes) { isUiTreeRoot = el[0].attributes['ui-tree']; } //If the node mark as `nodrag`, DONOT drag it. if (UiTreeHelper.nodrag(el)) { return; } el = el.parent(); } //If users beforeDrag calback returns falsey, do not initiate. if (!scope.beforeDrag(scope)) { return; } //Set property checked at start of function to prevent running logic again. e.uiTreeDragging = true; if (e.originalEvent) { e.originalEvent.uiTreeDragging = true; } e.preventDefault(); //Get original event if TouchEvent. eventObj = UiTreeHelper.eventObj(e); //Set boolean used to specify beginning of move. firstMoving = true; //Setting drag info properties and methods in scope of node being moved. dragInfo = UiTreeHelper.dragInfo(scope); //Setting original tree to adjust horizontal behavior in drag move. treeOfOrigin = dragInfo.source.$treeScope.$id; //Determine tage name of element ui-tree-node is on. tagName = element.prop('tagName'); if (tagName.toLowerCase() === 'tr') { //Create a new table column as placeholder. placeElm = angular.element($window.document.createElement(tagName)); //Create a column placeholder and set colspan to whole row length. tdElm = angular.element($window.document.createElement('td')) .addClass(config.placeholderClass) .attr('colspan', element[0].children.length); placeElm.append(tdElm); } else { //If not a table just duplicate element and add placeholder class. placeElm = angular.element($window.document.createElement(tagName)) .addClass(config.placeholderClass); } //Create a hidden placeholder and add class from config. hiddenPlaceElm = angular.element($window.document.createElement(tagName)); if (config.hiddenClass) { hiddenPlaceElm.addClass(config.hiddenClass); } //Getting starting position of element being moved. pos = UiTreeHelper.positionStarted(eventObj, element); placeElm.css('height', element.prop('offsetHeight') + 'px'); //Creating drag element to represent node. dragElm = angular.element($window.document.createElement(scope.$parentNodesScope.$element.prop('tagName'))) .addClass(scope.$parentNodesScope.$element.attr('class')).addClass(config.dragClass); dragElm.css('width', UiTreeHelper.width(element) + 'px'); dragElm.css('z-index', 9999); //Prevents cursor to change rapidly in Opera 12.16 and IE when dragging an element. hStyle = (element[0].querySelector('.angular-ui-tree-handle') || element[0]).currentStyle; if (hStyle) { document.body.setAttribute('ui-tree-cursor', $document.find('body').css('cursor') || ''); $document.find('body').css({'cursor': hStyle.cursor + '!important'}); } //If tree is sourceOnly (noDragDrop) don't show placeholder when moving about it. if (scope.sourceOnly) { placeElm.css('display', 'none'); } //Insert placeholder. element.after(placeElm); element.after(hiddenPlaceElm); if (dragInfo.isClone() && scope.sourceOnly) { dragElm.append(cloneElm); } else { dragElm.append(element); } //Create drag element. $document.find('body').append(dragElm); //Set drag elements position on screen. dragElm.css({ 'left': eventObj.pageX - pos.offsetX + 'px', 'top': eventObj.pageY - pos.offsetY + 'px' }); elements = { placeholder: placeElm, dragging: dragElm }; //Create all drag/move bindings. bindDragMoveEvents(); //Fire dragStart callback. scope.$apply(function () { scope.$treeScope.$callbacks.dragStart(dragInfo.eventArgs(elements, pos)); }); //Get bounds of document. document_height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); document_width = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); }; dragMove = function (e) { var eventObj = UiTreeHelper.eventObj(e), prev, next, leftElmPos, topElmPos, top_scroll, bottom_scroll, scrollContainerElmRect, target, targetX, targetY, displayElm, targetNode, targetElm, isEmpty, scrollDownBy, scrollUpBy, targetOffset, targetBefore, moveWithinTree, targetBeforeBuffer, targetHeight, targetChildElm, targetChildHeight, isDropzone; //If check ensures that drag element was created. if (dragElm) { e.preventDefault(); //Deselect anything (text, etc.) that was selected when move began. if ($window.getSelection) { $window.getSelection().removeAllRanges(); } else if ($window.document.selection) { $window.document.selection.empty(); } //Get top left positioning of element being moved. leftElmPos = eventObj.pageX - pos.offsetX; topElmPos = eventObj.pageY - pos.offsetY; //dragElm can't leave the screen on the left. if (leftElmPos < 0) { leftElmPos = 0; } //dragElm can't leave the screen on the top. if (topElmPos < 0) { topElmPos = 0; } //dragElm can't leave the screen on the bottom. if ((topElmPos + 10) > document_height) { topElmPos = document_height - 10; } //dragElm can't leave the screen on the right. if ((leftElmPos + 10) > document_width) { leftElmPos = document_width - 10; } //Updating element being moved css. dragElm.css({ 'left': leftElmPos + 'px', 'top': topElmPos + 'px' }); if (scrollContainerElm) { //Getting position to top and bottom of container element. scrollContainerElmRect = scrollContainerElm.getBoundingClientRect(); top_scroll = scrollContainerElm.scrollTop; bottom_scroll = top_scroll + scrollContainerElm.clientHeight; //To scroll down if cursor y-position is greater than the bottom position of the container vertical scroll if (scrollContainerElmRect.bottom < eventObj.clientY && bottom_scroll < scrollContainerElm.scrollHeight) { scrollDownBy = Math.min(scrollContainerElm.scrollHeight - bottom_scroll, 10); scrollContainerElm.scrollTop += scrollDownBy; } //To scroll top if cursor y-position is less than the top position of the container vertical scroll if (scrollContainerElmRect.top > eventObj.clientY && top_scroll > 0) { scrollUpBy = Math.min(top_scroll, 10); scrollContainerElm.scrollTop -= scrollUpBy; } } else { //Getting position to top and bottom of page. top_scroll = window.pageYOffset || $window.document.documentElement.scrollTop; bottom_scroll = top_scroll + (window.innerHeight || $window.document.clientHeight || $window.document.clientHeight); //To scroll down if cursor y-position is greater than the bottom position of the window vertical scroll if (bottom_scroll < eventObj.pageY && bottom_scroll < document_height) { scrollDownBy = Math.min(document_height - bottom_scroll, 10); window.scrollBy(0, scrollDownBy); } //To scroll top if cursor y-position is less than the top position of the window vertical scroll if (top_scroll > eventObj.pageY) { scrollUpBy = Math.min(top_scroll, 10); window.scrollBy(0, -scrollUpBy); } } //Calling service to update position coordinates based on move. UiTreeHelper.positionMoved(e, pos, firstMoving); if (firstMoving) { firstMoving = false; return; } //Setting X point for elementFromPoint. targetX = eventObj.pageX - ($window.pageXOffset || $window.document.body.scrollLeft || $window.document.documentElement.scrollLeft) - ($window.document.documentElement.clientLeft || 0); targetY = eventObj.pageY - ($window.pageYOffset || $window.document.body.scrollTop || $window.document.documentElement.scrollTop) - ($window.document.documentElement.clientTop || 0); //Select the drag target. Because IE does not support CSS 'pointer-events: none', it will always // pick the drag element itself as the target. To prevent this, we hide the drag element while // selecting the target. if (angular.isFunction(dragElm.hide)) { dragElm.hide(); } else { displayElm = dragElm[0].style.display; dragElm[0].style.display = 'none'; } //When using elementFromPoint() inside an iframe, you have to call // elementFromPoint() twice to make sure IE8 returns the correct value //MDN: The elementFromPoint() method of the Document interface returns the topmost element at the specified coordinates. $window.document.elementFromPoint(targetX, targetY); //Set target element (element in specified x/y coordinates). targetElm = angular.element($window.document.elementFromPoint(targetX, targetY)); //If the target element is a child element of a ui-tree-handle, // use the containing handle element as target element isHandleChild = UiTreeHelper.treeNodeHandlerContainerOfElement(targetElm); if (isHandleChild) { targetElm = angular.element(isHandleChild); } if (angular.isFunction(dragElm.show)) { dragElm.show(); } else { dragElm[0].style.display = displayElm; } //Assigning scope to target you are moving draggable over. if (UiTreeHelper.elementIsTree(targetElm)) { targetNode = targetElm.controller('uiTree').scope; } else if (UiTreeHelper.elementIsTreeNodeHandle(targetElm)) { targetNode = targetElm.controller('uiTreeHandle').scope; } else if (UiTreeHelper.elementIsTreeNode(targetElm)) { targetNode = targetElm.controller('uiTreeNode').scope; } else if (UiTreeHelper.elementIsTreeNodes(targetElm)) { targetNode = targetElm.controller('uiTreeNodes').scope; } else if (UiTreeHelper.elementIsPlaceholder(targetElm)) { targetNode = targetElm.controller('uiTreeNodes').scope; } else if (UiTreeHelper.elementIsDropzone(targetElm)) { targetNode = targetElm.controller('uiTree').scope; isDropzone = true; } else if (targetElm.controller('uiTreeNode')) { //Is a child element of a node. targetNode = targetElm.controller('uiTreeNode').scope; } moveWithinTree = (targetNode && targetNode.$treeScope && targetNode.$treeScope.$id && targetNode.$treeScope.$id === treeOfOrigin); /* (jcarter) Notes to developers: * pos.dirAx is either 0 or 1 * 1 means horizontal movement is happening * 0 means vertical movement is happening */ // Move nodes up and down in nesting level. if (moveWithinTree && pos.dirAx) { // increase horizontal level if previous sibling exists and is not collapsed // example 1.1.1 becomes 1.2 if (pos.distX > 0) { prev = dragInfo.prev(); if (prev && !prev.collapsed && prev.accept(scope, prev.childNodesCount())) { prev.$childNodesScope.$element.append(placeElm); dragInfo.moveTo(prev.$childNodesScope, prev.childNodes(), prev.childNodesCount()); } } // decrease horizontal level // example 1.2 become 1.1.1 if (pos.distX < 0) { // we can't decrease a level if an item preceeds the current one next = dragInfo.next(); if (!next) { target = dragInfo.parentNode(); // As a sibling of it's parent node if (target && target.$parentNodesScope.accept(scope, target.index() + 1)) { target.$element.after(placeElm); dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1); } } } } else { //Either in origin tree and moving horizontally OR you are moving within a new tree. //Check it's new position. isEmpty = false; //Exit if target is not a uiTree or child of one. if (!targetNode) { return; } //Show the placeholder if it was hidden for nodrop-enabled and this is a new tree if (targetNode.$treeScope && !targetNode.$parent.nodropEnabled && !targetNode.$treeScope.nodropEnabled) { placeElm.css('display', ''); } //Set whether target tree is empty or not. if (targetNode.$type === 'uiTree' && targetNode.dragEnabled) { isEmpty = targetNode.isEmpty(); } //If target is a handle set new target to handle's node. if (targetNode.$type === 'uiTreeHandle') { targetNode = targetNode.$nodeScope; } //Check if it is a uiTreeNode or it's an empty tree or it's a dropzone. if (targetNode.$type !== 'uiTreeNode' && !isEmpty && !isDropzone) { // Allow node to return to its original position if no longer hovering over target if (config.appendChildOnHover) { next = dragInfo.next(); if (!next && unhover) { target = dragInfo.parentNode(); target.$element.after(placeElm); dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1); unhover = false; } } return; } //If placeholder move from empty tree, reset it. if (treeScope && placeElm.parent()[0] != treeScope.$element[0]) { treeScope.resetEmptyElement(); treeScope.resetDropzoneElement(); treeScope = null; } //It's an empty tree if (isEmpty) { treeScope = targetNode; if (targetNode.$nodesScope.accept(scope, 0)) { dragInfo.moveTo(targetNode.$nodesScope, targetNode.$nodesScope.childNodes(), 0); } //It's a dropzone } else if (isDropzone) { treeScope = targetNode; if (targetNode.$nodesScope.accept(scope, targetNode.$nodesScope.childNodes().length)) { dragInfo.moveTo(targetNode.$nodesScope, targetNode.$nodesScope.childNodes(), targetNode.$nodesScope.childNodes().length); } //Not empty and drag enabled. } else if (targetNode.dragEnabled()) { //Setting/Resetting data for exanding on hover. if (angular.isDefined(scope.expandTimeoutOn) && scope.expandTimeoutOn !== targetNode.id) { $timeout.cancel(scope.expandTimeout); delete scope.expandTimeout; delete scope.expandTimeoutOn; scope.$callbacks.expandTimeoutCancel(); } //Determining if expansion is needed. if (targetNode.collapsed) { if (scope.expandOnHover === true || (angular.isNumber(scope.expandOnHover) && scope.expandOnHover === 0)) { targetNode.collapsed = false; targetNode.$treeScope.$callbacks.toggle(false, targetNode); } else if (scope.expandOnHover !== false && angular.isNumber(scope.expandOnHover) && scope.expandOnHover > 0) { //Triggering expansion. if (angular.isUndefined(scope.expandTimeoutOn)) { scope.expandTimeoutOn = targetNode.$id; scope.$callbacks.expandTimeoutStart(); scope.expandTimeout = $timeout(function() { scope.$callbacks.expandTimeoutEnd(); targetNode.collapsed = false; targetNode.$treeScope.$callbacks.toggle(false, targetNode); }, scope.expandOnHover); } } } //Get the element of ui-tree-node targetElm = targetNode.$element; targetOffset = UiTreeHelper.offset(targetElm); targetHeight = UiTreeHelper.height(targetElm); targetChildElm = targetNode.$childNodesScope ? targetNode.$childNodesScope.$element : null; targetChildHeight = targetChildElm ? UiTreeHelper.height(targetChildElm) : 0; targetHeight -= targetChildHeight; targetBeforeBuffer = config.appendChildOnHover ? targetHeight * 0.25 : UiTreeHelper.height(targetElm) / 2; targetBefore = eventObj.pageY < (targetOffset.top + targetBeforeBuffer); if (targetNode.$parentNodesScope.accept(scope, targetNode.index())) { if (targetBefore) { targetElm[0].parentNode.insertBefore(placeElm[0], targetElm[0]); dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index()); } else { // Try to append as a child if dragged upwards onto targetNode if (config.appendChildOnHover && targetNode.accept(scope, targetNode.childNodesCount())) { targetNode.$childNodesScope.$element.prepend(placeElm); dragInfo.moveTo(targetNode.$childNodesScope, targetNode.childNodes(), 0); unhover = true; } else { targetElm.after(placeElm); dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index() + 1); } } //We have to check if it can add the dragging node as a child. } else if (!targetBefore && targetNode.accept(scope, targetNode.childNodesCount())) { targetNode.$childNodesScope.$element.append(placeElm); dragInfo.moveTo(targetNode.$childNodesScope, targetNode.childNodes(), targetNode.childNodesCount()); } } } //Triggering dragMove callback. scope.$apply(function () { scope.$treeScope.$callbacks.dragMove(dragInfo.eventArgs(elements, pos)); }); } }; dragEnd = function (e) { var dragEventArgs = dragInfo.eventArgs(elements, pos); e.preventDefault(); //TODO(jcarter): Is dragStart need to be unbound? unbindDragMoveEvents(); //This cancel the collapse/expand login running. $timeout.cancel(scope.expandTimeout); scope.$treeScope.$apply(function () { $q.when(scope.$treeScope.$callbacks.beforeDrop(dragEventArgs)) //Promise resolved (or callback didn't return false) .then(function (allowDrop) { if (allowDrop !== false && scope.$$allowNodeDrop) { //Node drop accepted. dragInfo.apply(); //Fire the dropped callback only if the move was successful. scope.$treeScope.$callbacks.dropped(dragEventArgs); } else { //Drop canceled - revert the node to its original position. bindDragStartEvents(); } }) //Promise rejected - revert the node to its original position. .catch(function () { bindDragStartEvents(); }) .finally(function () { //Replace placeholder with newly dropped element. hiddenPlaceElm.replaceWith(scope.$element); placeElm.remove(); //Remove drag element if still in DOM. if (dragElm) { dragElm.remove(); dragElm = null; } //Fire dragStope callback. scope.$treeScope.$callbacks.dragStop(dragEventArgs); scope.$$allowNodeDrop = false; dragInfo = null; //Restore cursor in Opera 12.16 and IE var oldCur = document.body.getAttribute('ui-tree-cursor'); if (oldCur !== null) { $document.find('body').css({'cursor': oldCur}); document.body.removeAttribute('ui-tree-cursor'); } }); }); }; dragStartEvent = function (e) { if (scope.dragEnabled()) { dragStart(e); } }; dragMoveEvent = function (e) { dragMove(e); }; dragEndEvent = function (e) { scope.$$allowNodeDrop = true; dragEnd(e); }; dragCancelEvent = function (e) { dragEnd(e); }; dragDelay = (function () { var to; return { exec: function (fn, ms) { if (!ms) { ms = 0; } this.cancel(); to = $timeout(fn, ms); }, cancel: function () { $timeout.cancel(to); } }; })(); keydownHandler = function (e) { if (e.keyCode === 27) { dragEndEvent(e); } }; /** * Binds the mouse/touch events to enable drag start for this node. */ //This is outside of bindDragMoveEvents because of the potential for a delay setting. bindDragStartEvents = function () { element.bind('touchstart mousedown', function (e) { //Don't call drag delay if no delay was specified. if (scope.dragDelay > 0) { dragDelay.exec(function () { dragStartEvent(e); }, scope.dragDelay); } else { dragStartEvent(e); } }); element.bind('touchend touchcancel mouseup', function () { if (scope.dragDelay > 0) { dragDelay.cancel(); } }); }; bindDragStartEvents(); /** * Binds mouse/touch events that handle moving/dropping this dragged node */ bindDragMoveEvents = function () { angular.element($document).bind('touchend', dragEndEvent); angular.element($document).bind('touchcancel', dragEndEvent); angular.element($document).bind('touchmove', dragMoveEvent); angular.element($document).bind('mouseup', dragEndEvent); angular.element($document).bind('mousemove', dragMoveEvent); angular.element($document).bind('mouseleave', dragCancelEvent); angular.element($document).bind('keydown', keydownHandler); }; /** * Unbinds mouse/touch events that handle moving/dropping this dragged node. */ unbindDragMoveEvents = function () { angular.element($document).unbind('touchend', dragEndEvent); angular.element($document).unbind('touchcancel', dragEndEvent); angular.element($document).unbind('touchmove', dragMoveEvent); angular.element($document).unbind('mouseup', dragEndEvent); angular.element($document).unbind('mousemove', dragMoveEvent); angular.element($document).unbind('mouseleave', dragCancelEvent); angular.element($document).unbind('keydown', keydownHandler); }; } }; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') .directive('uiTreeNodes', ['treeConfig', '$window', function (treeConfig) { return { require: ['ngModel', '?^uiTreeNode', '^uiTree'], restrict: 'A', scope: true, controller: 'TreeNodesController', link: function (scope, element, attrs, controllersArr) { var config = {}, ngModel = controllersArr[0], treeNodeCtrl = controllersArr[1], treeCtrl = controllersArr[2]; angular.extend(config, treeConfig); if (config.nodesClass) { element.addClass(config.nodesClass); } if (treeNodeCtrl) { treeNodeCtrl.scope.$childNodesScope = scope; scope.$nodeScope = treeNodeCtrl.scope; } else { // find the root nodes if there is no parent node and have a parent ui-tree treeCtrl.scope.$nodesScope = scope; } scope.$treeScope = treeCtrl.scope; if (ngModel) { ngModel.$render = function () { scope.$modelValue = ngModel.$modelValue; }; } scope.$watch(function () { return attrs.maxDepth; }, function (val) { if ((typeof val) == 'number') { scope.maxDepth = val; } }); scope.$watch(function () { return attrs.nodropEnabled; }, function (newVal) { if ((typeof newVal) != 'undefined') { scope.nodropEnabled = true; } }, true); } }; } ]); })(); (function () { 'use strict'; angular.module('ui.tree') /** * @ngdoc service * @name ui.tree.service:UiTreeHelper * @requires ng.$document * @requires ng.$window * * @description * angular-ui-tree. */ .factory('UiTreeHelper', ['$document', '$window', 'treeConfig', function ($document, $window, treeConfig) { return { /** * A hashtable used to storage data of nodes * @type {Object} */ nodesData: {}, setNodeAttribute: function (scope, attrName, val) { if (!scope.$modelValue) { return null; } var data = this.nodesData[scope.$modelValue.$$hashKey]; if (!data) { data = {}; this.nodesData[scope.$modelValue.$$hashKey] = data; } data[attrName] = val; }, getNodeAttribute: function (scope, attrName) { if (!scope.$modelValue) { return null; } var data = this.nodesData[scope.$modelValue.$$hashKey]; if (data) { return data[attrName]; } return null; }, /** * @ngdoc method * @methodOf ui.tree.service:$nodrag * @param {Object} targetElm angular element * @return {Bool} check if the node can be dragged. */ nodrag: function (targetElm) { if (typeof targetElm.attr('data-nodrag') != 'undefined') { return targetElm.attr('data-nodrag') !== 'false'; } return false; }, /** * Get the event object for touches. * * @param {MouseEvent|TouchEvent} e MouseEvent or TouchEvent that kicked off dragX method. * @return {MouseEvent|TouchEvent} Object returned as original event object. */ eventObj: function (e) { var obj = e; if (e.targetTouches !== undefined) { //Set obj equal to the first Touch object in the TouchList. obj = e.targetTouches.item(0); //Logic to set obj to original TouchEvent. } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) { obj = e.originalEvent.targetTouches.item(0); } return obj; }, /** * Generate object used to store data about node being moved. * * {angular.$scope} node Scope of the node that is being moved. */ dragInfo: function (node) { return { source: node, sourceInfo: { cloneModel: node.$treeScope.cloneEnabled === true ? angular.copy(node.$modelValue) : undefined, nodeScope: node, index: node.index(), nodesScope: node.$parentNodesScope }, index: node.index(), //Slice(0) just duplicates an array. siblings: node.siblings().slice(0), parent: node.$parentNodesScope, //Reset parent to source parent. resetParent: function() { this.parent = node.$parentNodesScope; }, //Move the node to a new position, determining where the node will be inserted to when dropped happens here. moveTo: function (parent, siblings, index) { this.parent = parent; //Duplicate siblings array. this.siblings = siblings.slice(0); //If source node is in the target nodes var i = this.siblings.indexOf(this.source); if (i > -1) { this.siblings.splice(i, 1); if (this.source.index() < index) { index--; } } this.siblings.splice(index, 0, this.source); this.index = index; }, //Get parent nodes nodeScope. parentNode: function () { return this.parent.$nodeScope; }, //Get previous sibling node. prev: function () { if (this.index > 0) { return this.siblings[this.index - 1]; } return null; }, //Get next sibling node. next: function () { if (this.index < this.siblings.length - 1) { return this.siblings[this.index + 1]; } return null; }, //Return what cloneEnabled is set to on uiTree. isClone: function () { return this.source.$treeScope.cloneEnabled === true; }, //Returns a copy of node passed in. clonedNode: function (node) { return angular.copy(node); }, //Returns true if parent or index have changed (move happened within any uiTree). isDirty: function () { return this.source.$parentNodesScope != this.parent || this.source.index() != this.index; }, //Return whether node has a new parent (set on moveTo method). isForeign: function () { return this.source.$treeScope !== this.parent.$treeScope; }, //Sets arguments passed to user callbacks. eventArgs: function (elements, pos) { return { source: this.sourceInfo, dest: { index: this.index, nodesScope: this.parent }, elements: elements, pos: pos }; }, //Method that actually manipulates the node being moved. apply: function () { var nodeData = this.source.$modelValue; //Nodrop enabled on tree or parent if (this.parent.nodropEnabled || this.parent.$treeScope.nodropEnabled) { return; } //Node was dropped in the same place - do nothing. if (!this.isDirty()) { return; } //CloneEnabled and cross-tree so copy and do not remove from source. if (this.isClone() && this.isForeign()) { this.parent.insertNode(this.index, this.sourceInfo.cloneModel); //Any other case, remove and reinsert. } else { this.source.remove(); this.parent.insertNode(this.index, nodeData); } } }; }, /** * @ngdoc method * @name ui.tree#height * @methodOf ui.tree.service:UiTreeHelper * * @description * Get the height of an element. * * @param {Object} element Angular element. * @returns {String} Height */ height: function (element) { return element.prop('scrollHeight'); }, /** * @ngdoc method * @name ui.tree#width * @methodOf ui.tree.service:UiTreeHelper * * @description * Get the width of an element. * * @param {Object} element Angular element. * @returns {String} Width */ width: function (element) { return element.prop('scrollWidth'); }, /** * @ngdoc method * @name ui.tree#offset * @methodOf ui.nestedSortable.service:UiTreeHelper * * @description * Get the offset values of an element. * * @param {Object} element Angular element. * @returns {Object} Object with properties width, height, top and left */ offset: function (element) { var boundingClientRect = element[0].getBoundingClientRect(); return { width: element.prop('offsetWidth'), height: element.prop('offsetHeight'), top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) }; }, /** * @ngdoc method * @name ui.tree#positionStarted * @methodOf ui.tree.service:UiTreeHelper * * @description * Get the start position of the target element according to the provided event properties. * * @param {Object} e Event * @param {Object} target Target element * @returns {Object} Object with properties offsetX, offsetY, startX, startY, nowX and dirX. */ positionStarted: function (e, target) { var pos = {}, pageX = e.pageX, pageY = e.pageY; //Check to set correct data for TouchEvents if (e.originalEvent && e.originalEvent.touches && (e.originalEvent.touches.length > 0)) { pageX = e.originalEvent.touches[0].pageX; pageY = e.originalEvent.touches[0].pageY; } pos.offsetX = pageX - this.offset(target).left; pos.offsetY = pageY - this.offset(target).top; pos.startX = pos.lastX = pageX; pos.startY = pos.lastY = pageY; pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0; pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0; return pos; }, positionMoved: function (e, pos, firstMoving) { var pageX = e.pageX, pageY = e.pageY, newAx; //If there are multiple touch points, choose one to use as X and Y. if (e.originalEvent && e.originalEvent.touches && (e.originalEvent.touches.length > 0)) { pageX = e.originalEvent.touches[0].pageX; pageY = e.originalEvent.touches[0].pageY; } //Mouse position last event. pos.lastX = pos.nowX; pos.lastY = pos.nowY; //Mouse position this event. pos.nowX = pageX; pos.nowY = pageY; //Distance mouse moved between events. pos.distX = pos.nowX - pos.lastX; pos.distY = pos.nowY - pos.lastY; //Direction mouse was moving. pos.lastDirX = pos.dirX; pos.lastDirY = pos.dirY; //Direction mouse is now moving (on both axis). pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1; pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1; //Axis mouse is now moving on. newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0; //Do nothing on first move. if (firstMoving) { pos.dirAx = newAx; pos.moving = true; return; } //Calc distance moved on this axis (and direction). if (pos.dirAx !== newAx) { pos.distAxX = 0; pos.distAxY = 0; } else { pos.distAxX += Math.abs(pos.distX); if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) { pos.distAxX = 0; } pos.distAxY += Math.abs(pos.distY); if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) { pos.distAxY = 0; } } pos.dirAx = newAx; }, elementIsTreeNode: function (element) { return typeof element.attr('ui-tree-node') !== 'undefined'; }, elementIsTreeNodeHandle: function (element) { return typeof element.attr('ui-tree-handle') !== 'undefined'; }, elementIsTree: function (element) { return typeof element.attr('ui-tree') !== 'undefined'; }, elementIsTreeNodes: function (element) { return typeof element.attr('ui-tree-nodes') !== 'undefined'; }, elementIsPlaceholder: function (element) { return element.hasClass(treeConfig.placeholderClass); }, elementIsDropzone: function (element) { return element.hasClass(treeConfig.dropzoneClass); }, elementContainsTreeNodeHandler: function (element) { return element[0].querySelectorAll('[ui-tree-handle]').length >= 1; }, treeNodeHandlerContainerOfElement: function (element) { return findFirstParentElementWithAttribute('ui-tree-handle', element[0]); } }; } ]); // TODO: optimize this loop //(Jcarter): Suggest adding a parent element property on uiTree, then all these bubble // to can trigger to stop when they reach the parent. function findFirstParentElementWithAttribute(attributeName, childObj) { //Undefined if the mouse leaves the browser window if (childObj === undefined) { return null; } var testObj = childObj.parentNode, count = 1, //Check for setAttribute due to exception thrown by Firefox when a node is dragged outside the browser window res = (typeof testObj.setAttribute === 'function' && testObj.hasAttribute(attributeName)) ? testObj : null; while (testObj && typeof testObj.setAttribute === 'function' && !testObj.hasAttribute(attributeName)) { testObj = testObj.parentNode; res = testObj; //Stop once we reach top of page. if (testObj === document.documentElement) { res = null; break; } count++; } return res; } })();angular-ui-tree.min.css000066400000000000000000000022511325274564300413600ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-ui-tree/dist.angular-ui-tree-dropzone,.angular-ui-tree-empty{border:1px dashed #bbb;min-height:100px;background-color:#e5e5e5;background-image:-webkit-linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff),-webkit-linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff);background-image:linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff),linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff);background-size:60px 60px;background-position:0 0,30px 30px}.angular-ui-tree-empty{pointer-events:none}.angular-ui-tree-nodes{position:relative;margin:0;padding:0;list-style:none}.angular-ui-tree-nodes .angular-ui-tree-nodes{padding-left:20px}.angular-ui-tree-node,.angular-ui-tree-placeholder{position:relative;margin:0;padding:0;min-height:20px;line-height:20px}.angular-ui-tree-hidden{display:none}.angular-ui-tree-placeholder{margin:10px;padding:0;min-height:30px}.angular-ui-tree-handle{cursor:move;text-decoration:none;font-weight:700;box-sizing:border-box;min-height:20px;line-height:20px}.angular-ui-tree-drag{position:absolute;pointer-events:none;z-index:999;opacity:.8}.angular-ui-tree-drag .tree-node-content{margin-top:0}angular-ui-tree.min.js000066400000000000000000000532631325274564300412150ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular-ui-tree/dist/** * @license Angular UI Tree v2.22.6 * (c) 2010-2017. https://github.com/angular-ui-tree/angular-ui-tree * License: MIT */ !function(){"use strict";angular.module("ui.tree",[]).constant("treeConfig",{treeClass:"angular-ui-tree",emptyTreeClass:"angular-ui-tree-empty",dropzoneClass:"angular-ui-tree-dropzone",hiddenClass:"angular-ui-tree-hidden",nodesClass:"angular-ui-tree-nodes",nodeClass:"angular-ui-tree-node",handleClass:"angular-ui-tree-handle",placeholderClass:"angular-ui-tree-placeholder",dragClass:"angular-ui-tree-drag",dragThreshold:3,defaultCollapsed:!1,appendChildOnHover:!0})}(),function(){"use strict";angular.module("ui.tree").controller("TreeHandleController",["$scope","$element",function(e,n){this.scope=e,e.$element=n,e.$nodeScope=null,e.$type="uiTreeHandle"}])}(),function(){"use strict";angular.module("ui.tree").controller("TreeNodeController",["$scope","$element",function(e,n){function t(e){if(!e)return 0;var n,o,l,r=0,a=e.childNodes();if(!a||0===a.length)return 0;for(l=a.length-1;l>=0;l--)n=a[l],o=1+t(n),r=Math.max(r,o);return r}this.scope=e,e.$element=n,e.$modelValue=null,e.$parentNodeScope=null,e.$childNodesScope=null,e.$parentNodesScope=null,e.$treeScope=null,e.$handleScope=null,e.$type="uiTreeNode",e.$$allowNodeDrop=!1,e.collapsed=!1,e.expandOnHover=!1,e.init=function(t){var o=t[0];e.$treeScope=t[1]?t[1].scope:null,e.$parentNodeScope=o.scope.$nodeScope,e.$modelValue=o.scope.$modelValue[e.$index],e.$parentNodesScope=o.scope,o.scope.initSubNode(e),n.on("$destroy",function(){o.scope.destroySubNode(e)})},e.index=function(){return e.$parentNodesScope.$modelValue.indexOf(e.$modelValue)},e.dragEnabled=function(){return!(e.$treeScope&&!e.$treeScope.dragEnabled)},e.isSibling=function(n){return e.$parentNodesScope==n.$parentNodesScope},e.isChild=function(n){var t=e.childNodes();return t&&t.indexOf(n)>-1},e.prev=function(){var n=e.index();return n>0?e.siblings()[n-1]:null},e.siblings=function(){return e.$parentNodesScope.childNodes()},e.childNodesCount=function(){return e.childNodes()?e.childNodes().length:0},e.hasChild=function(){return e.childNodesCount()>0},e.childNodes=function(){return e.$childNodesScope&&e.$childNodesScope.$modelValue?e.$childNodesScope.childNodes():null},e.accept=function(n,t){return e.$childNodesScope&&e.$childNodesScope.$modelValue&&e.$childNodesScope.accept(n,t)},e.remove=function(){return e.$parentNodesScope.removeNode(e)},e.toggle=function(){e.collapsed=!e.collapsed,e.$treeScope.$callbacks.toggle(e.collapsed,e)},e.collapse=function(){e.collapsed=!0},e.expand=function(){e.collapsed=!1},e.depth=function(){var n=e.$parentNodeScope;return n?n.depth()+1:1},e.maxSubDepth=function(){return e.$childNodesScope?t(e.$childNodesScope):0}}])}(),function(){"use strict";angular.module("ui.tree").controller("TreeNodesController",["$scope","$element","$timeout",function(e,n,t){this.scope=e,e.$element=n,e.$modelValue=null,e.$nodeScope=null,e.$treeScope=null,e.$type="uiTreeNodes",e.$nodesMap={},e.nodropEnabled=!1,e.maxDepth=0,e.cloneEnabled=!1,e.initSubNode=function(n){if(!n.$modelValue)return null;e.$nodesMap[n.$modelValue.$$hashKey]=n},e.destroySubNode=function(n){if(!n.$modelValue)return null;e.$nodesMap[n.$modelValue.$$hashKey]=null},e.accept=function(n,t){return e.$treeScope.$callbacks.accept(n,e,t)},e.beforeDrag=function(n){return e.$treeScope.$callbacks.beforeDrag(n)},e.isParent=function(n){return n.$parentNodesScope==e},e.hasChild=function(){return e.$modelValue.length>0},e.removeNode=function(n){var o=e.$modelValue.indexOf(n.$modelValue);return o>-1?(t(function(){e.$modelValue.splice(o,1)[0]}),e.$treeScope.$callbacks.removed(n)):null},e.insertNode=function(n,o){t(function(){e.$modelValue.splice(n,0,o)})},e.childNodes=function(){var n,t=[];if(e.$modelValue)for(n=0;n0&&e.depth()+n.maxSubDepth()+1>t}}])}(),function(){"use strict";angular.module("ui.tree").controller("TreeController",["$scope","$element",function(e,n){this.scope=e,e.$element=n,e.$nodesScope=null,e.$type="uiTree",e.$emptyElm=null,e.$dropzoneElm=null,e.$callbacks=null,e.dragEnabled=!0,e.emptyPlaceholderEnabled=!0,e.maxDepth=0,e.dragDelay=0,e.cloneEnabled=!1,e.nodropEnabled=!1,e.dropzoneEnabled=!1,e.isEmpty=function(){return e.$nodesScope&&e.$nodesScope.$modelValue&&0===e.$nodesScope.$modelValue.length},e.place=function(n){e.$nodesScope.$element.append(n),e.$emptyElm.remove()},this.resetEmptyElement=function(){e.$nodesScope.$modelValue&&0!==e.$nodesScope.$modelValue.length||!e.emptyPlaceholderEnabled?e.$emptyElm.remove():n.append(e.$emptyElm)},this.resetDropzoneElement=function(){e.$nodesScope.$modelValue&&0===e.$nodesScope.$modelValue.length||!e.dropzoneEnabled?e.$dropzoneElm.remove():n.append(e.$dropzoneElm)},e.resetEmptyElement=this.resetEmptyElement,e.resetDropzoneElement=this.resetDropzoneElement}])}(),function(){"use strict";angular.module("ui.tree").directive("uiTree",["treeConfig","$window",function(e,n){return{restrict:"A",scope:!0,controller:"TreeController",link:function(t,o,l,r){var a,i,d,c={accept:null,beforeDrag:null},s={};angular.extend(s,e),s.treeClass&&o.addClass(s.treeClass),"table"===o.prop("tagName").toLowerCase()?(t.$emptyElm=angular.element(n.document.createElement("tr")),i=o.find("tr"),d=i.length>0?angular.element(i).children().length:1e6,a=angular.element(n.document.createElement("td")).attr("colspan",d),t.$emptyElm.append(a)):(t.$emptyElm=angular.element(n.document.createElement("div")),t.$dropzoneElm=angular.element(n.document.createElement("div"))),s.emptyTreeClass&&t.$emptyElm.addClass(s.emptyTreeClass),s.dropzoneClass&&t.$dropzoneElm.addClass(s.dropzoneClass),t.$watch("$nodesScope.$modelValue.length",function(e){angular.isNumber(e)&&(r.resetEmptyElement(),r.resetDropzoneElement())},!0),t.$watch(l.dragEnabled,function(e){"boolean"==typeof e&&(t.dragEnabled=e)}),t.$watch(l.emptyPlaceholderEnabled,function(e){"boolean"==typeof e&&(t.emptyPlaceholderEnabled=e,r.resetEmptyElement())}),t.$watch(l.nodropEnabled,function(e){"boolean"==typeof e&&(t.nodropEnabled=e)}),t.$watch(l.dropzoneEnabled,function(e){"boolean"==typeof e&&(t.dropzoneEnabled=e,r.resetDropzoneElement())}),t.$watch(l.cloneEnabled,function(e){"boolean"==typeof e&&(t.cloneEnabled=e)}),t.$watch(l.maxDepth,function(e){"number"==typeof e&&(t.maxDepth=e)}),t.$watch(l.dragDelay,function(e){"number"==typeof e&&(t.dragDelay=e)}),c.accept=function(e,n,t){return!(n.nodropEnabled||n.$treeScope.nodropEnabled||n.outOfDepth(e))},c.beforeDrag=function(e){return!0},c.expandTimeoutStart=function(){},c.expandTimeoutCancel=function(){},c.expandTimeoutEnd=function(){},c.removed=function(e){},c.dropped=function(e){},c.dragStart=function(e){},c.dragMove=function(e){},c.dragStop=function(e){},c.beforeDrop=function(e){},c.toggle=function(e,n){},t.$watch(l.uiTree,function(e,n){angular.forEach(e,function(e,n){c[n]&&"function"==typeof e&&(c[n]=e)}),t.$callbacks=c},!0)}}}])}(),function(){"use strict";angular.module("ui.tree").directive("uiTreeHandle",["treeConfig",function(e){return{require:"^uiTreeNode",restrict:"A",scope:!0,controller:"TreeHandleController",link:function(n,t,o,l){var r={};angular.extend(r,e),r.handleClass&&t.addClass(r.handleClass),n!=l.scope&&(n.$nodeScope=l.scope,l.scope.$handleScope=n)}}}])}(),function(){"use strict";angular.module("ui.tree").directive("uiTreeNode",["treeConfig","UiTreeHelper","$window","$document","$timeout","$q",function(e,n,t,o,l,r){return{require:["^uiTreeNodes","^uiTree"],restrict:"A",controller:"TreeNodeController",link:function(a,i,d,c){var s,u,p,m,f,h,$,g,b,v,N,S,E,y,x,C,T,w,D,H,O,Y,X,A,V,k,z,M={},I="ontouchstart"in window,P=null,L=document.body,W=document.documentElement;angular.extend(M,e),M.nodeClass&&i.addClass(M.nodeClass),a.init(c),a.collapsed=!!n.getNodeAttribute(a,"collapsed")||e.defaultCollapsed,a.expandOnHover=!!n.getNodeAttribute(a,"expandOnHover"),a.scrollContainer=n.getNodeAttribute(a,"scrollContainer")||d.scrollContainer||null,a.sourceOnly=a.nodropEnabled||a.$treeScope.nodropEnabled,a.$watch(d.collapsed,function(e){"boolean"==typeof e&&(a.collapsed=e)}),a.$watch("collapsed",function(e){n.setNodeAttribute(a,"collapsed",e),d.$set("collapsed",e)}),a.$watch(d.expandOnHover,function(e){"boolean"!=typeof e&&"number"!=typeof e||(a.expandOnHover=e)}),a.$watch("expandOnHover",function(e){n.setNodeAttribute(a,"expandOnHover",e),d.$set("expandOnHover",e)}),d.$observe("scrollContainer",function(e){"string"==typeof e&&(a.scrollContainer=e)}),a.$watch("scrollContainer",function(e){n.setNodeAttribute(a,"scrollContainer",e),d.$set("scrollContainer",e),$=document.querySelector(e)}),a.$on("angular-ui-tree:collapse-all",function(){a.collapsed=!0}),a.$on("angular-ui-tree:expand-all",function(){a.collapsed=!1}),S=function(e){if((I||2!==e.button&&3!==e.which)&&!(e.uiTreeDragging||e.originalEvent&&e.originalEvent.uiTreeDragging)){var l,r,d,c,$,g,S,E,y,x=angular.element(e.target);if(l=n.treeNodeHandlerContainerOfElement(x),l&&(x=angular.element(l)),r=i.clone(),E=n.elementIsTreeNode(x),y=n.elementIsTreeNodeHandle(x),(E||y)&&!(E&&n.elementContainsTreeNodeHandler(x)||"input"==(d=x.prop("tagName").toLowerCase())||"textarea"==d||"button"==d||"select"==d)){for(V=angular.element(e.target),k=V[0].attributes["ui-tree"];V&&V[0]&&V[0]!==i&&!k;){if(V[0].attributes&&(k=V[0].attributes["ui-tree"]),n.nodrag(V))return;V=V.parent()}a.beforeDrag(a)&&(e.uiTreeDragging=!0,e.originalEvent&&(e.originalEvent.uiTreeDragging=!0),e.preventDefault(),$=n.eventObj(e),s=!0,u=n.dragInfo(a),z=u.source.$treeScope.$id,c=i.prop("tagName"),"tr"===c.toLowerCase()?(m=angular.element(t.document.createElement(c)),g=angular.element(t.document.createElement("td")).addClass(M.placeholderClass).attr("colspan",i[0].children.length),m.append(g)):m=angular.element(t.document.createElement(c)).addClass(M.placeholderClass),f=angular.element(t.document.createElement(c)),M.hiddenClass&&f.addClass(M.hiddenClass),p=n.positionStarted($,i),m.css("height",i.prop("offsetHeight")+"px"),h=angular.element(t.document.createElement(a.$parentNodesScope.$element.prop("tagName"))).addClass(a.$parentNodesScope.$element.attr("class")).addClass(M.dragClass),h.css("width",n.width(i)+"px"),h.css("z-index",9999),S=(i[0].querySelector(".angular-ui-tree-handle")||i[0]).currentStyle,S&&(document.body.setAttribute("ui-tree-cursor",o.find("body").css("cursor")||""),o.find("body").css({cursor:S.cursor+"!important"})),a.sourceOnly&&m.css("display","none"),i.after(m),i.after(f),u.isClone()&&a.sourceOnly?h.append(r):h.append(i),o.find("body").append(h),h.css({left:$.pageX-p.offsetX+"px",top:$.pageY-p.offsetY+"px"}),b={placeholder:m,dragging:h},O(),a.$apply(function(){a.$treeScope.$callbacks.dragStart(u.eventArgs(b,p))}),v=Math.max(L.scrollHeight,L.offsetHeight,W.clientHeight,W.scrollHeight,W.offsetHeight),N=Math.max(L.scrollWidth,L.offsetWidth,W.clientWidth,W.scrollWidth,W.offsetWidth))}}},E=function(e){var o,r,i,d,c,f,S,E,y,x,C,T,w,D,H,O,Y,X,V,k,I,L,W=n.eventObj(e);if(h){if(e.preventDefault(),t.getSelection?t.getSelection().removeAllRanges():t.document.selection&&t.document.selection.empty(),r=W.pageX-p.offsetX,i=W.pageY-p.offsetY,r<0&&(r=0),i<0&&(i=0),i+10>v&&(i=v-10),r+10>N&&(r=N-10),h.css({left:r+"px",top:i+"px"}),$?(f=$.getBoundingClientRect(),d=$.scrollTop,c=d+$.clientHeight,f.bottomW.clientY&&d>0&&(H=Math.min(d,10),$.scrollTop-=H)):(d=window.pageYOffset||t.document.documentElement.scrollTop,c=d+(window.innerHeight||t.document.clientHeight||t.document.clientHeight),cW.pageY&&(H=Math.min(d,10),window.scrollBy(0,-H))),n.positionMoved(e,p,s),s)return void(s=!1);if(E=W.pageX-(t.pageXOffset||t.document.body.scrollLeft||t.document.documentElement.scrollLeft)-(t.document.documentElement.clientLeft||0),y=W.pageY-(t.pageYOffset||t.document.body.scrollTop||t.document.documentElement.scrollTop)-(t.document.documentElement.clientTop||0),angular.isFunction(h.hide)?h.hide():(x=h[0].style.display,h[0].style.display="none"),t.document.elementFromPoint(E,y),T=angular.element(t.document.elementFromPoint(E,y)),A=n.treeNodeHandlerContainerOfElement(T),A&&(T=angular.element(A)),angular.isFunction(h.show)?h.show():h[0].style.display=x,n.elementIsTree(T)?C=T.controller("uiTree").scope:n.elementIsTreeNodeHandle(T)?C=T.controller("uiTreeHandle").scope:n.elementIsTreeNode(T)?C=T.controller("uiTreeNode").scope:n.elementIsTreeNodes(T)?C=T.controller("uiTreeNodes").scope:n.elementIsPlaceholder(T)?C=T.controller("uiTreeNodes").scope:n.elementIsDropzone(T)?(C=T.controller("uiTree").scope,L=!0):T.controller("uiTreeNode")&&(C=T.controller("uiTreeNode").scope),C&&C.$treeScope&&C.$treeScope.$id&&C.$treeScope.$id===z&&p.dirAx)p.distX>0&&(o=u.prev())&&!o.collapsed&&o.accept(a,o.childNodesCount())&&(o.$childNodesScope.$element.append(m),u.moveTo(o.$childNodesScope,o.childNodes(),o.childNodesCount())),p.distX<0&&(u.next()||(S=u.parentNode())&&S.$parentNodesScope.accept(a,S.index()+1)&&(S.$element.after(m),u.moveTo(S.$parentNodesScope,S.siblings(),S.index()+1)));else{if(w=!1,!C)return;if(!C.$treeScope||C.$parent.nodropEnabled||C.$treeScope.nodropEnabled||m.css("display",""),"uiTree"===C.$type&&C.dragEnabled&&(w=C.isEmpty()),"uiTreeHandle"===C.$type&&(C=C.$nodeScope),"uiTreeNode"!==C.$type&&!w&&!L)return void(M.appendChildOnHover&&!u.next()&&g&&(S=u.parentNode(),S.$element.after(m),u.moveTo(S.$parentNodesScope,S.siblings(),S.index()+1),g=!1));P&&m.parent()[0]!=P.$element[0]&&(P.resetEmptyElement(),P.resetDropzoneElement(),P=null),w?(P=C,C.$nodesScope.accept(a,0)&&u.moveTo(C.$nodesScope,C.$nodesScope.childNodes(),0)):L?(P=C,C.$nodesScope.accept(a,C.$nodesScope.childNodes().length)&&u.moveTo(C.$nodesScope,C.$nodesScope.childNodes(),C.$nodesScope.childNodes().length)):C.dragEnabled()&&(angular.isDefined(a.expandTimeoutOn)&&a.expandTimeoutOn!==C.id&&(l.cancel(a.expandTimeout),delete a.expandTimeout,delete a.expandTimeoutOn,a.$callbacks.expandTimeoutCancel()),C.collapsed&&(!0===a.expandOnHover||angular.isNumber(a.expandOnHover)&&0===a.expandOnHover?(C.collapsed=!1,C.$treeScope.$callbacks.toggle(!1,C)):!1!==a.expandOnHover&&angular.isNumber(a.expandOnHover)&&a.expandOnHover>0&&angular.isUndefined(a.expandTimeoutOn)&&(a.expandTimeoutOn=C.$id,a.$callbacks.expandTimeoutStart(),a.expandTimeout=l(function(){a.$callbacks.expandTimeoutEnd(),C.collapsed=!1,C.$treeScope.$callbacks.toggle(!1,C)},a.expandOnHover))),T=C.$element,O=n.offset(T),V=n.height(T),k=C.$childNodesScope?C.$childNodesScope.$element:null,I=k?n.height(k):0,V-=I,X=M.appendChildOnHover?.25*V:n.height(T)/2,Y=W.pageY0?D.exec(function(){x(e)},a.dragDelay):x(e)}),i.bind("touchend touchcancel mouseup",function(){a.dragDelay>0&&D.cancel()})},H(),O=function(){angular.element(o).bind("touchend",T),angular.element(o).bind("touchcancel",T),angular.element(o).bind("touchmove",C),angular.element(o).bind("mouseup",T),angular.element(o).bind("mousemove",C),angular.element(o).bind("mouseleave",w),angular.element(o).bind("keydown",X)},Y=function(){angular.element(o).unbind("touchend",T),angular.element(o).unbind("touchcancel",T),angular.element(o).unbind("touchmove",C),angular.element(o).unbind("mouseup",T),angular.element(o).unbind("mousemove",C),angular.element(o).unbind("mouseleave",w),angular.element(o).unbind("keydown",X)}}}}])}(),function(){"use strict";angular.module("ui.tree").directive("uiTreeNodes",["treeConfig","$window",function(e){return{require:["ngModel","?^uiTreeNode","^uiTree"],restrict:"A",scope:!0,controller:"TreeNodesController",link:function(n,t,o,l){var r={},a=l[0],i=l[1],d=l[2];angular.extend(r,e),r.nodesClass&&t.addClass(r.nodesClass),i?(i.scope.$childNodesScope=n,n.$nodeScope=i.scope):d.scope.$nodesScope=n,n.$treeScope=d.scope,a&&(a.$render=function(){n.$modelValue=a.$modelValue}),n.$watch(function(){return o.maxDepth},function(e){"number"==typeof e&&(n.maxDepth=e)}),n.$watch(function(){return o.nodropEnabled},function(e){void 0!==e&&(n.nodropEnabled=!0)},!0)}}}])}(),function(){"use strict";function e(e,n){if(void 0===n)return null;for(var t=n.parentNode,o=1,l="function"==typeof t.setAttribute&&t.hasAttribute(e)?t:null;t&&"function"==typeof t.setAttribute&&!t.hasAttribute(e);){if(t=t.parentNode,l=t,t===document.documentElement){l=null;break}o++}return l}angular.module("ui.tree").factory("UiTreeHelper",["$document","$window","treeConfig",function(n,t,o){return{nodesData:{},setNodeAttribute:function(e,n,t){if(!e.$modelValue)return null;var o=this.nodesData[e.$modelValue.$$hashKey];o||(o={},this.nodesData[e.$modelValue.$$hashKey]=o),o[n]=t},getNodeAttribute:function(e,n){if(!e.$modelValue)return null;var t=this.nodesData[e.$modelValue.$$hashKey];return t?t[n]:null},nodrag:function(e){return void 0!==e.attr("data-nodrag")&&"false"!==e.attr("data-nodrag")},eventObj:function(e){var n=e;return void 0!==e.targetTouches?n=e.targetTouches.item(0):void 0!==e.originalEvent&&void 0!==e.originalEvent.targetTouches&&(n=e.originalEvent.targetTouches.item(0)),n},dragInfo:function(e){return{source:e,sourceInfo:{cloneModel:!0===e.$treeScope.cloneEnabled?angular.copy(e.$modelValue):void 0,nodeScope:e,index:e.index(),nodesScope:e.$parentNodesScope},index:e.index(),siblings:e.siblings().slice(0),parent:e.$parentNodesScope,resetParent:function(){this.parent=e.$parentNodesScope},moveTo:function(e,n,t){this.parent=e,this.siblings=n.slice(0);var o=this.siblings.indexOf(this.source);o>-1&&(this.siblings.splice(o,1),this.source.index()0?this.siblings[this.index-1]:null},next:function(){return this.index0&&(o=e.originalEvent.touches[0].pageX,l=e.originalEvent.touches[0].pageY),t.offsetX=o-this.offset(n).left,t.offsetY=l-this.offset(n).top,t.startX=t.lastX=o,t.startY=t.lastY=l,t.nowX=t.nowY=t.distX=t.distY=t.dirAx=0,t.dirX=t.dirY=t.lastDirX=t.lastDirY=t.distAxX=t.distAxY=0,t},positionMoved:function(e,n,t){var o,l=e.pageX,r=e.pageY;if(e.originalEvent&&e.originalEvent.touches&&e.originalEvent.touches.length>0&&(l=e.originalEvent.touches[0].pageX,r=e.originalEvent.touches[0].pageY),n.lastX=n.nowX,n.lastY=n.nowY,n.nowX=l,n.nowY=r,n.distX=n.nowX-n.lastX,n.distY=n.nowY-n.lastY,n.lastDirX=n.dirX,n.lastDirY=n.dirY,n.dirX=0===n.distX?0:n.distX>0?1:-1,n.dirY=0===n.distY?0:n.distY>0?1:-1,o=Math.abs(n.distX)>Math.abs(n.distY)?1:0,t)return n.dirAx=o,void(n.moving=!0);n.dirAx!==o?(n.distAxX=0,n.distAxY=0):(n.distAxX+=Math.abs(n.distX),0!==n.dirX&&n.dirX!==n.lastDirX&&(n.distAxX=0),n.distAxY+=Math.abs(n.distY),0!==n.dirY&&n.dirY!==n.lastDirY&&(n.distAxY=0)),n.dirAx=o},elementIsTreeNode:function(e){return void 0!==e.attr("ui-tree-node")},elementIsTreeNodeHandle:function(e){return void 0!==e.attr("ui-tree-handle")},elementIsTree:function(e){return void 0!==e.attr("ui-tree")},elementIsTreeNodes:function(e){return void 0!==e.attr("ui-tree-nodes")},elementIsPlaceholder:function(e){return e.hasClass(o.placeholderClass)},elementIsDropzone:function(e){return e.hasClass(o.dropzoneClass)},elementContainsTreeNodeHandler:function(e){return e[0].querySelectorAll("[ui-tree-handle]").length>=1},treeNodeHandlerContainerOfElement:function(n){return e("ui-tree-handle",n[0])}}}])}();angular/000077500000000000000000000000001325274564300325505ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwrangular-csp.css000066400000000000000000000005271325274564300355020ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular/* Include this file in your html if you are using the CSP mode. */ @charset "UTF-8"; [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak, .ng-hide:not(.ng-hide-animate) { display: none !important; } ng\:form { display: block; } .ng-animate-shim { visibility:hidden; } .ng-anchor { position:absolute; } angular-csp.min.css000066400000000000000000000003501325274564300362560ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular@charset "UTF-8";[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none!important}ng\:form{display:block}.ng-animate-shim{visibility:hidden}.ng-anchor{position:absolute}angular.js000066400000000000000000045211001325274564300345420ustar00rootroot00000000000000lemonldap-ng-v1.9.16-26971aaff01b369d3c4eca2748cff44b1cb6e636/lemonldap-ng-manager/site/static/bwr/angular/** * @license AngularJS v1.5.11 * (c) 2010-2017 Google, Inc. http://angularjs.org * License: MIT */ (function(window) {'use strict'; /** * @description * * This object provides a utility for producing rich Error messages within * Angular. It can be called as follows: * * var exampleMinErr = minErr('example'); * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); * * The above creates an instance of minErr in the example namespace. The * resulting error will have a namespaced error code of example.one. The * resulting error will replace {0} with the value of foo, and {1} with the * value of bar. The object is not restricted in the number of arguments it can * take. * * If fewer arguments are specified than necessary for interpolation, the extra * interpolation markers will be preserved in the final string. * * Since data will be parsed statically during a build step, some restrictions * are applied with respect to how minErr instances are created and called. * Instances should have names of the form namespaceMinErr for a minErr created * using minErr('namespace') . Error codes, namespaces and template strings * should all be static strings, not variables or general expressions. * * @param {string} module The namespace to use for the new minErr instance. * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning * error from returned function, for cases when a particular type of error is useful. * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance */ function minErr(module, ErrorConstructor) { ErrorConstructor = ErrorConstructor || Error; return function() { var SKIP_INDEXES = 2; var templateArgs = arguments, code = templateArgs[0], message = '[' + (module ? module + ':' : '') + code + '] ', template = templateArgs[1], paramPrefix, i; message += template.replace(/\{\d+\}/g, function(match) { var index = +match.slice(1, -1), shiftedIndex = index + SKIP_INDEXES; if (shiftedIndex < templateArgs.length) { return toDebugString(templateArgs[shiftedIndex]); } return match; }); message += '\nhttp://errors.angularjs.org/1.5.11/' + (module ? module + '/' : '') + code; for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + encodeURIComponent(toDebugString(templateArgs[i])); } return new ErrorConstructor(message); }; } /* We need to tell ESLint what variables are being exported */ /* exported angular, msie, jqLite, jQuery, slice, splice, push, toString, ngMinErr, angularModule, uid, REGEX_STRING_REGEXP, VALIDITY_STATE_PROPERTY, lowercase, uppercase, manualLowercase, manualUppercase, nodeName_, isArrayLike, forEach, forEachSorted, reverseParams, nextUid, setHashKey, extend, toInt, inherit, merge, noop, identity, valueFn, isUndefined, isDefined, isObject, isBlankObject, isString, isNumber, isNumberNaN, isDate, isArray, isFunction, isRegExp, isWindow, isScope, isFile, isFormData, isBlob, isBoolean, isPromiseLike, trim, escapeForRegexp, isElement, makeMap, includes, arrayRemove, copy, equals, csp, jq, concat, sliceArgs, bind, toJsonReplacer, toJson, fromJson, convertTimezoneToLocal, timezoneToOffset, startingTag, tryDecodeURIComponent, parseKeyValue, toKeyValue, encodeUriSegment, encodeUriQuery, angularInit, bootstrap, getTestability, snake_case, bindJQuery, assertArg, assertArgFn, assertNotHasOwnProperty, getter, getBlockNodes, hasOwnProperty, createMap, NODE_TYPE_ELEMENT, NODE_TYPE_ATTRIBUTE, NODE_TYPE_TEXT, NODE_TYPE_COMMENT, NODE_TYPE_DOCUMENT, NODE_TYPE_DOCUMENT_FRAGMENT */ //////////////////////////////////// /** * @ngdoc module * @name ng * @module ng * @installation * @description * * # ng (core module) * The ng module is loaded by default when an AngularJS application is started. The module itself * contains the essential components for an AngularJS application to function. The table below * lists a high level breakdown of each of the services/factories, filters, directives and testing * components available within this core module. * *
          */ var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; // The name of a form control's ValidityState property. // This is used so that it's possible for internal tests to create mock ValidityStates. var VALIDITY_STATE_PROPERTY = 'validity'; var hasOwnProperty = Object.prototype.hasOwnProperty; var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; var manualLowercase = function(s) { /* eslint-disable no-bitwise */ return isString(s) ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) : s; /* eslint-enable */ }; var manualUppercase = function(s) { /* eslint-disable no-bitwise */ return isString(s) ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) : s; /* eslint-enable */ }; // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods // with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387 if ('i' !== 'I'.toLowerCase()) { lowercase = manualLowercase; uppercase = manualUppercase; } var msie, // holds major version number for IE, or NaN if UA is not IE. jqLite, // delay binding since jQuery could be loaded after us. jQuery, // delay binding slice = [].slice, splice = [].splice, push = [].push, toString = Object.prototype.toString, getPrototypeOf = Object.getPrototypeOf, ngMinErr = minErr('ng'), /** @name angular */ angular = window.angular || (window.angular = {}), angularModule, uid = 0; /** * documentMode is an IE-only property * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx */ msie = window.document.documentMode; /** * @private * @param {*} obj * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, * String ...) */ function isArrayLike(obj) { // `null`, `undefined` and `window` are not array-like if (obj == null || isWindow(obj)) return false; // arrays, strings and jQuery/jqLite objects are array like // * jqLite is either the jQuery or jqLite constructor function // * we have to check the existence of jqLite first as this method is called // via the forEach method when constructing the jqLite object in the first place if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; // Support: iOS 8.2 (not reproducible in simulator) // "length" in obj used to prevent JIT error (gh-11508) var length = 'length' in Object(obj) && obj.length; // NodeList objects (with `item` method) and // other objects with suitable length characteristics are array-like return isNumber(length) && (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item === 'function'); } /** * @ngdoc function * @name angular.forEach * @module ng * @kind function * * @description * Invokes the `iterator` function once for each item in `obj` collection, which can be either an * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` * is the value of an object property or an array element, `key` is the object property key or * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. * * It is worth noting that `.forEach` does not iterate over inherited properties because it filters * using the `hasOwnProperty` method. * * Unlike ES262's * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just * return the value provided. * ```js var values = {name: 'misko', gender: 'male'}; var log = []; angular.forEach(values, function(value, key) { this.push(key + ': ' + value); }, log); expect(log).toEqual(['name: misko', 'gender: male']); ``` * * @param {Object|Array} obj Object to iterate over. * @param {Function} iterator Iterator function. * @param {Object=} context Object to become context (`this`) for the iterator function. * @returns {Object|Array} Reference to `obj`. */ function forEach(obj, iterator, context) { var key, length; if (obj) { if (isFunction(obj)) { for (key in obj) { // Need to check if hasOwnProperty exists, // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function if (key !== 'prototype' && key !== 'length' && key !== 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { iterator.call(context, obj[key], key, obj); } } } else if (isArray(obj) || isArrayLike(obj)) { var isPrimitive = typeof obj !== 'object'; for (key = 0, length = obj.length; key < length; key++) { if (isPrimitive || key in obj) { iterator.call(context, obj[key], key, obj); } } } else if (obj.forEach && obj.forEach !== forEach) { obj.forEach(iterator, context, obj); } else if (isBlankObject(obj)) { // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty for (key in obj) { iterator.call(context, obj[key], key, obj); } } else if (typeof obj.hasOwnProperty === 'function') { // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed for (key in obj) { if (obj.hasOwnProperty(key)) { iterator.call(context, obj[key], key, obj); } } } else { // Slow path for objects which do not have a method `hasOwnProperty` for (key in obj) { if (hasOwnProperty.call(obj, key)) { iterator.call(context, obj[key], key, obj); } } } } return obj; } function forEachSorted(obj, iterator, context) { var keys = Object.keys(obj).sort(); for (var i = 0; i < keys.length; i++) { iterator.call(context, obj[keys[i]], keys[i]); } return keys; } /** * when using forEach the params are value, key, but it is often useful to have key, value. * @param {function(string, *)} iteratorFn * @returns {function(*, string)} */ function reverseParams(iteratorFn) { return function(value, key) {iteratorFn(key, value);}; } /** * A consistent way of creating unique IDs in angular. * * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before * we hit number precision issues in JavaScript. * * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M * * @returns {number} an unique alpha-numeric string */ function nextUid() { return ++uid; } /** * Set or clear the hashkey for an object. * @param obj object * @param h the hashkey (!truthy to delete the hashkey) */ function setHashKey(obj, h) { if (h) { obj.$$hashKey = h; } else { delete obj.$$hashKey; } } function baseExtend(dst, objs, deep) { var h = dst.$$hashKey; for (var i = 0, ii = objs.length; i < ii; ++i) { var obj = objs[i]; if (!isObject(obj) && !isFunction(obj)) continue; var keys = Object.keys(obj); for (var j = 0, jj = keys.length; j < jj; j++) { var key = keys[j]; var src = obj[key]; if (deep && isObject(src)) { if (isDate(src)) { dst[key] = new Date(src.valueOf()); } else if (isRegExp(src)) { dst[key] = new RegExp(src); } else if (src.nodeName) { dst[key] = src.cloneNode(true); } else if (isElement(src)) { dst[key] = src.clone(); } else { if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; baseExtend(dst[key], [src], true); } } else { dst[key] = src; } } } setHashKey(dst, h); return dst; } /** * @ngdoc function * @name angular.extend * @module ng * @kind function * * @description * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. * * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use * {@link angular.merge} for this. * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). * @returns {Object} Reference to `dst`. */ function extend(dst) { return baseExtend(dst, slice.call(arguments, 1), false); } /** * @ngdoc function * @name angular.merge * @module ng * @kind function * * @description * Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so * by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. * * Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source * objects, performing a deep copy. * * @param {Object} dst Destination object. * @param {...Object} src Source object(s). * @returns {Object} Reference to `dst`. */ function merge(dst) { return baseExtend(dst, slice.call(arguments, 1), true); } function toInt(str) { return parseInt(str, 10); } var isNumberNaN = Number.isNaN || function isNumberNaN(num) { // eslint-disable-next-line no-self-compare return num !== num; }; function inherit(parent, extra) { return extend(Object.create(parent), extra); } /** * @ngdoc function * @name angular.noop * @module ng * @kind function * * @description * A function that performs no operations. This function can be useful when writing code in the * functional style. ```js function foo(callback) { var result = calculateResult(); (callback || angular.noop)(result); } ``` */ function noop() {} noop.$inject = []; /** * @ngdoc function * @name angular.identity * @module ng * @kind function * * @description * A function that returns its first argument. This function is useful when writing code in the * functional style. * ```js function transformer(transformationFn, value) { return (transformationFn || angular.identity)(value); }; // E.g. function getResult(fn, input) { return (fn || angular.identity)(input); }; getResult(function(n) { return n * 2; }, 21); // returns 42 getResult(null, 21); // returns 21 getResult(undefined, 21); // returns 21 ``` * * @param {*} value to be returned. * @returns {*} the value passed in. */ function identity($) {return $;} identity.$inject = []; function valueFn(value) {return function valueRef() {return value;};} function hasCustomToString(obj) { return isFunction(obj.toString) && obj.toString !== toString; } /** * @ngdoc function * @name angular.isUndefined * @module ng * @kind function * * @description * Determines if a reference is undefined. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is undefined. */ function isUndefined(value) {return typeof value === 'undefined';} /** * @ngdoc function * @name angular.isDefined * @module ng * @kind function * * @description * Determines if a reference is defined. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is defined. */ function isDefined(value) {return typeof value !== 'undefined';} /** * @ngdoc function * @name angular.isObject * @module ng * @kind function * * @description * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not * considered to be objects. Note that JavaScript arrays are objects. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Object` but not `null`. */ function isObject(value) { // http://jsperf.com/isobject4 return value !== null && typeof value === 'object'; } /** * Determine if a value is an object with a null prototype * * @returns {boolean} True if `value` is an `Object` with a null prototype */ function isBlankObject(value) { return value !== null && typeof value === 'object' && !getPrototypeOf(value); } /** * @ngdoc function * @name angular.isString * @module ng * @kind function * * @description * Determines if a reference is a `String`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `String`. */ function isString(value) {return typeof value === 'string';} /** * @ngdoc function * @name angular.isNumber * @module ng * @kind function * * @description * Determines if a reference is a `Number`. * * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. * * If you wish to exclude these then you can use the native * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) * method. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Number`. */ function isNumber(value) {return typeof value === 'number';} /** * @ngdoc function * @name angular.isDate * @module ng * @kind function * * @description * Determines if a value is a date. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Date`. */ function isDate(value) { return toString.call(value) === '[object Date]'; } /** * @ngdoc function * @name angular.isArray * @module ng * @kind function * * @description * Determines if a reference is an `Array`. Alias of Array.isArray. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is an `Array`. */ var isArray = Array.isArray; /** * @ngdoc function * @name angular.isFunction * @module ng * @kind function * * @description * Determines if a reference is a `Function`. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Function`. */ function isFunction(value) {return typeof value === 'function';} /** * Determines if a value is a regular expression object. * * @private * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `RegExp`. */ function isRegExp(value) { return toString.call(value) === '[object RegExp]'; } /** * Checks if `obj` is a window object. * * @private * @param {*} obj Object to check * @returns {boolean} True if `obj` is a window obj. */ function isWindow(obj) { return obj && obj.window === obj; } function isScope(obj) { return obj && obj.$evalAsync && obj.$watch; } function isFile(obj) { return toString.call(obj) === '[object File]'; } function isFormData(obj) { return toString.call(obj) === '[object FormData]'; } function isBlob(obj) { return toString.call(obj) === '[object Blob]'; } function isBoolean(value) { return typeof value === 'boolean'; } function isPromiseLike(obj) { return obj && isFunction(obj.then); } var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/; function isTypedArray(value) { return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); } function isArrayBuffer(obj) { return toString.call(obj) === '[object ArrayBuffer]'; } var trim = function(value) { return isString(value) ? value.trim() : value; }; // Copied from: // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 // Prereq: s is a string. var escapeForRegexp = function(s) { return s .replace(/([-()[\]{}+?*.$^|,:#= 0) { array.splice(index, 1); } return index; } /** * @ngdoc function * @name angular.copy * @module ng * @kind function * * @description * Creates a deep copy of `source`, which should be an object or an array. * * * If no destination is supplied, a copy of the object or array is created. * * If a destination is provided, all of its elements (for arrays) or properties (for objects) * are deleted and then all elements/properties from the source are copied to it. * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. * * If `source` is identical to `destination` an exception will be thrown. * *
          *
          * Only enumerable properties are taken into account. Non-enumerable properties (both on `source` * and on `destination`) will be ignored. *
          * * @param {*} source The source that will be used to make a copy. * Can be any type, including primitives, `null`, and `undefined`. * @param {(Object|Array)=} destination Destination into which the source is copied. If * provided, must be of the same type as `source`. * @returns {*} The copy or updated `destination`, if `destination` was specified. * * @example


          Gender:
          form = {{user | json}}
          master = {{master | json}}
          // Module: copyExample angular. module('copyExample', []). controller('ExampleController', ['$scope', function($scope) { $scope.master = {}; $scope.reset = function() { // Example with 1 argument $scope.user = angular.copy($scope.master); }; $scope.update = function(user) { // Example with 2 arguments angular.copy(user, $scope.master); }; $scope.reset(); }]);
          */ function copy(source, destination) { var stackSource = []; var stackDest = []; if (destination) { if (isTypedArray(destination) || isArrayBuffer(destination)) { throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.'); } if (source === destination) { throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.'); } // Empty the destination object if (isArray(destination)) { destination.length = 0; } else { forEach(destination, function(value, key) { if (key !== '$$hashKey') { delete destination[key]; } }); } stackSource.push(source); stackDest.push(destination); return copyRecurse(source, destination); } return copyElement(source); function copyRecurse(source, destination) { var h = destination.$$hashKey; var key; if (isArray(source)) { for (var i = 0, ii = source.length; i < ii; i++) { destination.push(copyElement(source[i])); } } else if (isBlankObject(source)) { // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty for (key in source) { destination[key] = copyElement(source[key]); } } else if (source && typeof source.hasOwnProperty === 'function') { // Slow path, which must rely on hasOwnProperty for (key in source) { if (source.hasOwnProperty(key)) { destination[key] = copyElement(source[key]); } } } else { // Slowest path --- hasOwnProperty can't be called as a method for (key in source) { if (hasOwnProperty.call(source, key)) { destination[key] = copyElement(source[key]); } } } setHashKey(destination, h); return destination; } function copyElement(source) { // Simple values if (!isObject(source)) { return source; } // Already copied values var index = stackSource.indexOf(source); if (index !== -1) { return stackDest[index]; } if (isWindow(source) || isScope(source)) { throw ngMinErr('cpws', 'Can\'t copy! Making copies of Window or Scope instances is not supported.'); } var needsRecurse = false; var destination = copyType(source); if (destination === undefined) { destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); needsRecurse = true; } stackSource.push(source); stackDest.push(destination); return needsRecurse ? copyRecurse(source, destination) : destination; } function copyType(source) { switch (toString.call(source)) { case '[object Int8Array]': case '[object Int16Array]': case '[object Int32Array]': case '[object Float32Array]': case '[object Float64Array]': case '[object Uint8Array]': case '[object Uint8ClampedArray]': case '[object Uint16Array]': case '[object Uint32Array]': return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length); case '[object ArrayBuffer]': // Support: IE10 if (!source.slice) { // If we're in this case we know the environment supports ArrayBuffer /* eslint-disable no-undef */ var copied = new ArrayBuffer(source.byteLength); new Uint8Array(copied).set(new Uint8Array(source)); /* eslint-enable */ return copied; } return source.slice(0); case '[object Boolean]': case '[object Number]': case '[object String]': case '[object Date]': return new source.constructor(source.valueOf()); case '[object RegExp]': var re = new RegExp(source.source, source.toString().match(/[^/]*$/)[0]); re.lastIndex = source.lastIndex; return re; case '[object Blob]': return new source.constructor([source], {type: source.type}); } if (isFunction(source.cloneNode)) { return source.cloneNode(true); } } } /** * @ngdoc function * @name angular.equals * @module ng * @kind function * * @description * Determines if two objects or two values are equivalent. Supports value types, regular * expressions, arrays and objects. * * Two objects or values are considered equivalent if at least one of the following is true: * * * Both objects or values pass `===` comparison. * * Both objects or values are of the same type and all of their properties are equal by * comparing them with `angular.equals`. * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) * * Both values represent the same regular expression (In JavaScript, * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual * representation matches). * * During a property comparison, properties of `function` type and properties with names * that begin with `$` are ignored. * * Scope and DOMWindow objects are being compared only by identify (`===`). * * @param {*} o1 Object or value to compare. * @param {*} o2 Object or value to compare. * @returns {boolean} True if arguments are equal. * * @example

          User 1

          Name: Age:

          User 2

          Name: Age:

          User 1:
          {{user1 | json}}
          User 2:
          {{user2 | json}}
          Equal:
          {{result}}
          angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { $scope.user1 = {}; $scope.user2 = {}; $scope.compare = function() { $scope.result = angular.equals($scope.user1, $scope.user2); }; }]);
          */ function equals(o1, o2) { if (o1 === o2) return true; if (o1 === null || o2 === null) return false; // eslint-disable-next-line no-self-compare if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN var t1 = typeof o1, t2 = typeof o2, length, key, keySet; if (t1 === t2 && t1 === 'object') { if (isArray(o1)) { if (!isArray(o2)) return false; if ((length = o1.length) === o2.length) { for (key = 0; key < length; key++) { if (!equals(o1[key], o2[key])) return false; } return true; } } else if (isDate(o1)) { if (!isDate(o2)) return false; return equals(o1.getTime(), o2.getTime()); } else if (isRegExp(o1)) { if (!isRegExp(o2)) return false; return o1.toString() === o2.toString(); } else { if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2) || isDate(o2) || isRegExp(o2)) return false; keySet = createMap(); for (key in o1) { if (key.charAt(0) === '$' || isFunction(o1[key])) continue; if (!equals(o1[key], o2[key])) return false; keySet[key] = true; } for (key in o2) { if (!(key in keySet) && key.charAt(0) !== '$' && isDefined(o2[key]) && !isFunction(o2[key])) return false; } return true; } } return false; } var csp = function() { if (!isDefined(csp.rules)) { var ngCspElement = (window.document.querySelector('[ng-csp]') || window.document.querySelector('[data-ng-csp]')); if (ngCspElement) { var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || ngCspElement.getAttribute('data-ng-csp'); csp.rules = { noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) }; } else { csp.rules = { noUnsafeEval: noUnsafeEval(), noInlineStyle: false }; } } return csp.rules; function noUnsafeEval() { try { // eslint-disable-next-line no-new, no-new-func new Function(''); return false; } catch (e) { return true; } } }; /** * @ngdoc directive * @module ng * @name ngJq * * @element ANY * @param {string=} ngJq the name of the library available under `window` * to be used for angular.element * @description * Use this directive to force the angular.element library. This should be * used to force either jqLite by leaving ng-jq blank or setting the name of * the jquery variable under window (eg. jQuery). * * Since angular looks for this directive when it is loaded (doesn't wait for the * DOMContentLoaded event), it must be placed on an element that comes before the script * which loads angular. Also, only the first instance of `ng-jq` will be used and all * others ignored. * * @example * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. ```html ... ... ``` * @example * This example shows how to use a jQuery based library of a different name. * The library name must be available at the top most 'window'. ```html ... ... ``` */ var jq = function() { if (isDefined(jq.name_)) return jq.name_; var el; var i, ii = ngAttrPrefixes.length, prefix, name; for (i = 0; i < ii; ++i) { prefix = ngAttrPrefixes[i]; el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]'); if (el) { name = el.getAttribute(prefix + 'jq'); break; } } return (jq.name_ = name); }; function concat(array1, array2, index) { return array1.concat(slice.call(array2, index)); } function sliceArgs(args, startIndex) { return slice.call(args, startIndex || 0); } /** * @ngdoc function * @name angular.bind * @module ng * @kind function * * @description * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for * `fn`). You can supply optional `args` that are prebound to the function. This feature is also * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). * * @param {Object} self Context which `fn` should be evaluated in. * @param {function()} fn Function to be bound. * @param {...*} args Optional arguments to be prebound to the `fn` function call. * @returns {function()} Function that wraps the `fn` with all the specified bindings. */ function bind(self, fn) { var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; if (isFunction(fn) && !(fn instanceof RegExp)) { return curryArgs.length ? function() { return arguments.length ? fn.apply(self, concat(curryArgs, arguments, 0)) : fn.apply(self, curryArgs); } : function() { return arguments.length ? fn.apply(self, arguments) : fn.call(self); }; } else { // In IE, native methods are not functions so they cannot be bound (note: they don't need to be). return fn; } } function toJsonReplacer(key, value) { var val = value; if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { val = undefined; } else if (isWindow(value)) { val = '$WINDOW'; } else if (value && window.document === value) { val = '$DOCUMENT'; } else if (isScope(value)) { val = '$SCOPE'; } return val; } /** * @ngdoc function * @name angular.toJson * @module ng * @kind function * * @description * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be * stripped since angular uses this notation internally. * * @param {Object|Array|Date|string|number|boolean} obj Input to be serialized into JSON. * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. * If set to an integer, the JSON output will contain that many spaces per indentation. * @returns {string|undefined} JSON-ified string representing `obj`. * @knownIssue * * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date` * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the * `Date.prototype.toJSON` method as follows: * * ``` * var _DatetoJSON = Date.prototype.toJSON; * Date.prototype.toJSON = function() { * try { * return _DatetoJSON.call(this); * } catch(e) { * if (e instanceof RangeError) { * return null; * } * throw e; * } * }; * ``` * * See https://github.com/angular/angular.js/pull/14221 for more information. */ function toJson(obj, pretty) { if (isUndefined(obj)) return undefined; if (!isNumber(pretty)) { pretty = pretty ? 2 : null; } return JSON.stringify(obj, toJsonReplacer, pretty); } /** * @ngdoc function * @name angular.fromJson * @module ng * @kind function * * @description * Deserializes a JSON string. * * @param {string} json JSON string to deserialize. * @returns {Object|Array|string|number} Deserialized JSON string. */ function fromJson(json) { return isString(json) ? JSON.parse(json) : json; } var ALL_COLONS = /:/g; function timezoneToOffset(timezone, fallback) { // IE/Edge do not "understand" colon (`:`) in timezone timezone = timezone.replace(ALL_COLONS, ''); var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; } function addDateMinutes(date, minutes) { date = new Date(date.getTime()); date.setMinutes(date.getMinutes() + minutes); return date; } function convertTimezoneToLocal(date, timezone, reverse) { reverse = reverse ? -1 : 1; var dateTimezoneOffset = date.getTimezoneOffset(); var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); } /** * @returns {string} Returns the string representation of the element. */ function startingTag(element) { element = jqLite(element).clone(); try { // turns out IE does not let you set .html() on elements which // are not allowed to have children. So we just ignore it. element.empty(); } catch (e) { /* empty */ } var elemHtml = jqLite('
          ').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : elemHtml. match(/^(<[^>]+>)/)[1]. replace(/^<([\w-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); } catch (e) { return lowercase(elemHtml); } } ///////////////////////////////////////////////// /** * Tries to decode the URI component without throwing an exception. * * @private * @param str value potential URI component to check. * @returns {boolean} True if `value` can be decoded * with the decodeURIComponent function. */ function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); } catch (e) { // Ignore any invalid uri component. } } /** * Parses an escaped url query string into key-value pairs. * @returns {Object.} */ function parseKeyValue(/**string*/keyValue) { var obj = {}; forEach((keyValue || '').split('&'), function(keyValue) { var splitPoint, key, val; if (keyValue) { key = keyValue = keyValue.replace(/\+/g,'%20'); splitPoint = keyValue.indexOf('='); if (splitPoint !== -1) { key = keyValue.substring(0, splitPoint); val = keyValue.substring(splitPoint + 1); } key = tryDecodeURIComponent(key); if (isDefined(key)) { val = isDefined(val) ? tryDecodeURIComponent(val) : true; if (!hasOwnProperty.call(obj, key)) { obj[key] = val; } else if (isArray(obj[key])) { obj[key].push(val); } else { obj[key] = [obj[key],val]; } } } }); return obj; } function toKeyValue(obj) { var parts = []; forEach(obj, function(value, key) { if (isArray(value)) { forEach(value, function(arrayValue) { parts.push(encodeUriQuery(key, true) + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); }); } else { parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true))); } }); return parts.length ? parts.join('&') : ''; } /** * We need our custom method because encodeURIComponent is too aggressive and doesn't follow * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path * segments: * segment = *pchar * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * pct-encoded = "%" HEXDIG HEXDIG * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" */ function encodeUriSegment(val) { return encodeUriQuery(val, true). replace(/%26/gi, '&'). replace(/%3D/gi, '='). replace(/%2B/gi, '+'); } /** * This method is intended for encoding *key* or *value* parts of query component. We need a custom * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be * encoded per http://tools.ietf.org/html/rfc3986: * query = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * pct-encoded = "%" HEXDIG HEXDIG * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" */ function encodeUriQuery(val, pctEncodeSpaces) { return encodeURIComponent(val). replace(/%40/gi, '@'). replace(/%3A/gi, ':'). replace(/%24/g, '$'). replace(/%2C/gi, ','). replace(/%3B/gi, ';'). replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); } var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; function getNgAttribute(element, ngAttr) { var attr, i, ii = ngAttrPrefixes.length; for (i = 0; i < ii; ++i) { attr = ngAttrPrefixes[i] + ngAttr; if (isString(attr = element.getAttribute(attr))) { return attr; } } return null; } function allowAutoBootstrap(document) { var script = document.currentScript; var src = script && script.getAttribute('src'); if (!src) { return true; } var link = document.createElement('a'); link.href = src; if (document.location.origin === link.origin) { // Same-origin resources are always allowed, even for non-whitelisted schemes. return true; } // Disabled bootstrapping unless angular.js was loaded from a known scheme used on the web. // This is to prevent angular.js bundled with browser extensions from being used to bypass the // content security policy in web pages and other browser extensions. switch (link.protocol) { case 'http:': case 'https:': case 'ftp:': case 'blob:': case 'file:': case 'data:': return true; default: return false; } } // Cached as it has to run during loading so that document.currentScript is available. var isAutoBootstrapAllowed = allowAutoBootstrap(window.document); /** * @ngdoc directive * @name ngApp * @module ng * * @element ANY * @param {angular.Module} ngApp an optional application * {@link angular.module module} name to load. * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be * created in "strict-di" mode. This means that the application will fail to invoke functions which * do not use explicit function annotation (and are thus unsuitable for minification), as described * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in * tracking down the root of these bugs. * * @description * * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive * designates the **root element** of the application and is typically placed near the root element * of the page - e.g. on the `` or `` tags. * * There are a few things to keep in mind when using `ngApp`: * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` * found in the document will be used to define the root element to auto-bootstrap as an * application. To run multiple applications in an HTML document you must manually bootstrap them using * {@link angular.bootstrap} instead. * - AngularJS applications cannot be nested within each other. * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and * {@link ngRoute.ngView `ngView`}. * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, * causing animations to stop working and making the injector inaccessible from outside the app. * * You can specify an **AngularJS module** to be used as the root module for the application. This * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It * should contain the application code needed or have dependencies on other modules that will * contain the code. See {@link angular.module} for more information. * * In the example below if the `ngApp` directive were not placed on the `html` element then the * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` * would not be resolved to `3`. * * `ngApp` is the easiest, and most common way to bootstrap an application. *
          I can add: {{a}} + {{b}} = {{ a+b }}
          angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { $scope.a = 1; $scope.b = 2; });
          * * Using `ngStrictDi`, you would see something like this: *
          I can add: {{a}} + {{b}} = {{ a+b }}

          This renders because the controller does not fail to instantiate, by using explicit annotation style (see script.js for details)

          Name:
          Hello, {{name}}!

          This renders because the controller does not fail to instantiate, by using explicit annotation style (see script.js for details)

          I can add: {{a}} + {{b}} = {{ a+b }}

          The controller could not be instantiated, due to relying on automatic function annotations (which are disabled in strict mode). As such, the content of this section is not interpolated, and there should be an error in your web console.

          angular.module('ngAppStrictDemo', []) // BadController will fail to instantiate, due to relying on automatic function annotation, // rather than an explicit annotation .controller('BadController', function($scope) { $scope.a = 1; $scope.b = 2; }) // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, // due to using explicit annotations using the array style and $inject property, respectively. .controller('GoodController1', ['$scope', function($scope) { $scope.a = 1; $scope.b = 2; }]) .controller('GoodController2', GoodController2); function GoodController2($scope) { $scope.name = 'World'; } GoodController2.$inject = ['$scope']; div[ng-controller] { margin-bottom: 1em; -webkit-border-radius: 4px; border-radius: 4px; border: 1px solid; padding: .5em; } div[ng-controller^=Good] { border-color: #d6e9c6; background-color: #dff0d8; color: #3c763d; } div[ng-controller^=Bad] { border-color: #ebccd1; background-color: #f2dede; color: #a94442; margin-bottom: 0; }
          */ function angularInit(element, bootstrap) { var appElement, module, config = {}; // The element `element` has priority over any other element. forEach(ngAttrPrefixes, function(prefix) { var name = prefix + 'app'; if (!appElement && element.hasAttribute && element.hasAttribute(name)) { appElement = element; module = element.getAttribute(name); } }); forEach(ngAttrPrefixes, function(prefix) { var name = prefix + 'app'; var candidate; if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { appElement = candidate; module = candidate.getAttribute(name); } }); if (appElement) { if (!isAutoBootstrapAllowed) { window.console.error('Angular: disabling automatic bootstrap. * * * * ``` * * @param {DOMElement} element DOM element which is the root of angular application. * @param {Array=} modules an array of modules to load into the application. * Each item in the array should be the name of a predefined module or a (DI annotated) * function that will be invoked by the injector as a `config` block. * See: {@link angular.module modules} * @param {Object=} config an object for defining configuration options for the application. The * following keys are supported: * * * `strictDi` - disable automatic function annotation for the application. This is meant to * assist in finding bugs which break minified code. Defaults to `false`. * * @returns {auto.$injector} Returns the newly created injector for this app. */ function bootstrap(element, modules, config) { if (!isObject(config)) config = {}; var defaultConfig = { strictDi: false }; config = extend(defaultConfig, config); var doBootstrap = function() { element = jqLite(element); if (element.injector()) { var tag = (element[0] === window.document) ? 'document' : startingTag(element); // Encode angle brackets to prevent input from being sanitized to empty string #8683. throw ngMinErr( 'btstrpd', 'App already bootstrapped with this element \'{0}\'', tag.replace(//,'>')); } modules = modules || []; modules.unshift(['$provide', function($provide) { $provide.value('$rootElement', element); }]); if (config.debugInfoEnabled) { // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. modules.push(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(true); }]); } modules.unshift('ng'); var injector = createInjector(modules, config.strictDi); injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', function bootstrapApply(scope, element, compile, injector) { scope.$apply(function() { element.data('$injector', injector); compile(element)(scope); }); }] ); return injector; }; var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { config.debugInfoEnabled = true; window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); } if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { return doBootstrap(); } window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); angular.resumeBootstrap = function(extraModules) { forEach(extraModules, function(module) { modules.push(module); }); return doBootstrap(); }; if (isFunction(angular.resumeDeferredBootstrap)) { angular.resumeDeferredBootstrap(); } } /** * @ngdoc function * @name angular.reloadWithDebugInfo * @module ng * @description * Use this function to reload the current application with debug information turned on. * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. * * See {@link ng.$compileProvider#debugInfoEnabled} for more. */ function reloadWithDebugInfo() { window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; window.location.reload(); } /** * @name angular.getTestability * @module ng * @description * Get the testability service for the instance of Angular on the given * element. * @param {DOMElement} element DOM element which is the root of angular application. */ function getTestability(rootElement) { var injector = angular.element(rootElement).injector(); if (!injector) { throw ngMinErr('test', 'no injector found for element argument to getTestability'); } return injector.get('$$testability'); } var SNAKE_CASE_REGEXP = /[A-Z]/g; function snake_case(name, separator) { separator = separator || '_'; return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { return (pos ? separator : '') + letter.toLowerCase(); }); } var bindJQueryFired = false; function bindJQuery() { var originalCleanData; if (bindJQueryFired) { return; } // bind to jQuery if present; var jqName = jq(); jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) !jqName ? undefined : // use jqLite window[jqName]; // use jQuery specified by `ngJq` // Use jQuery if it exists with proper functionality, otherwise default to us. // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older // versions. It will not work for sure with jQuery <1.7, though. if (jQuery && jQuery.fn.on) { jqLite = jQuery; extend(jQuery.fn, { scope: JQLitePrototype.scope, isolateScope: JQLitePrototype.isolateScope, controller: JQLitePrototype.controller, injector: JQLitePrototype.injector, inheritedData: JQLitePrototype.inheritedData }); // All nodes removed from the DOM via various jQuery APIs like .remove() // are passed through jQuery.cleanData. Monkey-patch this method to fire // the $destroy event on all removed nodes. originalCleanData = jQuery.cleanData; jQuery.cleanData = function(elems) { var events; for (var i = 0, elem; (elem = elems[i]) != null; i++) { events = jQuery._data(elem, 'events'); if (events && events.$destroy) { jQuery(elem).triggerHandler('$destroy'); } } originalCleanData(elems); }; } else { jqLite = JQLite; } angular.element = jqLite; // Prevent double-proxying. bindJQueryFired = true; } /** * throw error if the argument is falsy. */ function assertArg(arg, name, reason) { if (!arg) { throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); } return arg; } function assertArgFn(arg, name, acceptArrayAnnotation) { if (acceptArrayAnnotation && isArray(arg)) { arg = arg[arg.length - 1]; } assertArg(isFunction(arg), name, 'not a function, got ' + (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); return arg; } /** * throw error if the name given is hasOwnProperty * @param {String} name the name to test * @param {String} context the context in which the name is used, such as module or directive */ function assertNotHasOwnProperty(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); } } /** * Return the value accessible from the object by path. Any undefined traversals are ignored * @param {Object} obj starting object * @param {String} path path to traverse * @param {boolean} [bindFnToScope=true] * @returns {Object} value as accessible by path */ //TODO(misko): this function needs to be removed function getter(obj, path, bindFnToScope) { if (!path) return obj; var keys = path.split('.'); var key; var lastInstance = obj; var len = keys.length; for (var i = 0; i < len; i++) { key = keys[i]; if (obj) { obj = (lastInstance = obj)[key]; } } if (!bindFnToScope && isFunction(obj)) { return bind(lastInstance, obj); } return obj; } /** * Return the DOM siblings between the first and last node in the given array. * @param {Array} array like object * @returns {Array} the inputted object or a jqLite collection containing the nodes */ function getBlockNodes(nodes) { // TODO(perf): update `nodes` instead of creating a new object? var node = nodes[0]; var endNode = nodes[nodes.length - 1]; var blockNodes; for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { if (blockNodes || nodes[i] !== node) { if (!blockNodes) { blockNodes = jqLite(slice.call(nodes, 0, i)); } blockNodes.push(node); } } return blockNodes || nodes; } /** * Creates a new object without a prototype. This object is useful for lookup without having to * guard against prototypically inherited properties via hasOwnProperty. * * Related micro-benchmarks: * - http://jsperf.com/object-create2 * - http://jsperf.com/proto-map-lookup/2 * - http://jsperf.com/for-in-vs-object-keys2 * * @returns {Object} */ function createMap() { return Object.create(null); } var NODE_TYPE_ELEMENT = 1; var NODE_TYPE_ATTRIBUTE = 2; var NODE_TYPE_TEXT = 3; var NODE_TYPE_COMMENT = 8; var NODE_TYPE_DOCUMENT = 9; var NODE_TYPE_DOCUMENT_FRAGMENT = 11; /** * @ngdoc type * @name angular.Module * @module ng * @description * * Interface for configuring angular {@link angular.module modules}. */ function setupModuleLoader(window) { var $injectorMinErr = minErr('$injector'); var ngMinErr = minErr('ng'); function ensure(obj, name, factory) { return obj[name] || (obj[name] = factory()); } var angular = ensure(window, 'angular', Object); // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap angular.$$minErr = angular.$$minErr || minErr; return ensure(angular, 'module', function() { /** @type {Object.} */ var modules = {}; /** * @ngdoc function * @name angular.module * @module ng * @description * * The `angular.module` is a global place for creating, registering and retrieving Angular * modules. * All modules (angular core or 3rd party) that should be available to an application must be * registered using this mechanism. * * Passing one argument retrieves an existing {@link angular.Module}, * whereas passing more than one argument creates a new {@link angular.Module} * * * # Module * * A module is a collection of services, directives, controllers, filters, and configuration information. * `angular.module` is used to configure the {@link auto.$injector $injector}. * * ```js * // Create a new module * var myModule = angular.module('myModule', []); * * // register a new service * myModule.value('appName', 'MyCoolApp'); * * // configure existing services inside initialization blocks. * myModule.config(['$locationProvider', function($locationProvider) { * // Configure existing providers * $locationProvider.hashPrefix('!'); * }]); * ``` * * Then you can create an injector and load your modules like this: * * ```js * var injector = angular.injector(['ng', 'myModule']) * ``` * * However it's more likely that you'll just use * {@link ng.directive:ngApp ngApp} or * {@link angular.bootstrap} to simplify this process for you. * * @param {!string} name The name of the module to create or retrieve. * @param {!Array.=} requires If specified then new module is being created. If * unspecified then the module is being retrieved for further configuration. * @param {Function=} configFn Optional configuration function for the module. Same as * {@link angular.Module#config Module#config()}. * @returns {angular.Module} new module with the {@link angular.Module} api. */ return function module(name, requires, configFn) { var assertNotHasOwnProperty = function(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); } }; assertNotHasOwnProperty(name, 'module'); if (requires && modules.hasOwnProperty(name)) { modules[name] = null; } return ensure(modules, name, function() { if (!requires) { throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' + 'the module name or forgot to load it. If registering a module ensure that you ' + 'specify the dependencies as the second argument.', name); } /** @type {!Array.>} */ var invokeQueue = []; /** @type {!Array.} */ var configBlocks = []; /** @type {!Array.} */ var runBlocks = []; var config = invokeLater('$injector', 'invoke', 'push', configBlocks); /** @type {angular.Module} */ var moduleInstance = { // Private state _invokeQueue: invokeQueue, _configBlocks: configBlocks, _runBlocks: runBlocks, /** * @ngdoc property * @name angular.Module#requires * @module ng * * @description * Holds the list of modules which the injector will load before the current module is * loaded. */ requires: requires, /** * @ngdoc property * @name angular.Module#name * @module ng * * @description * Name of the module. */ name: name, /** * @ngdoc method * @name angular.Module#provider * @module ng * @param {string} name service name * @param {Function} providerType Construction function for creating new instance of the * service. * @description * See {@link auto.$provide#provider $provide.provider()}. */ provider: invokeLaterAndSetModuleName('$provide', 'provider'), /** * @ngdoc method * @name angular.Module#factory * @module ng * @param {string} name service name * @param {Function} providerFunction Function for creating new instance of the service. * @description * See {@link auto.$provide#factory $provide.factory()}. */ factory: invokeLaterAndSetModuleName('$provide', 'factory'), /** * @ngdoc method * @name angular.Module#service * @module ng * @param {string} name service name * @param {Function} constructor A constructor function that will be instantiated. * @description * See {@link auto.$provide#service $provide.service()}. */ service: invokeLaterAndSetModuleName('$provide', 'service'), /** * @ngdoc method * @name angular.Module#value * @module ng * @param {string} name service name * @param {*} object Service instance object. * @description * See {@link auto.$provide#value $provide.value()}. */ value: invokeLater('$provide', 'value'), /** * @ngdoc method * @name angular.Module#constant * @module ng * @param {string} name constant name * @param {*} object Constant value. * @description * Because the constants are fixed, they get applied before other provide methods. * See {@link auto.$provide#constant $provide.constant()}. */ constant: invokeLater('$provide', 'constant', 'unshift'), /** * @ngdoc method * @name angular.Module#decorator * @module ng * @param {string} name The name of the service to decorate. * @param {Function} decorFn This function will be invoked when the service needs to be * instantiated and should return the decorated service instance. * @description * See {@link auto.$provide#decorator $provide.decorator()}. */ decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), /** * @ngdoc method * @name angular.Module#animation * @module ng * @param {string} name animation name * @param {Function} animationFactory Factory function for creating new instance of an * animation. * @description * * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. * * * Defines an animation hook that can be later used with * {@link $animate $animate} service and directives that use this service. * * ```js * module.animation('.animation-name', function($inject1, $inject2) { * return { * eventName : function(element, done) { * //code to run the animation * //once complete, then run done() * return function cancellationFunction(element) { * //code to cancel the animation * } * } * } * }) * ``` * * See {@link ng.$animateProvider#register $animateProvider.register()} and * {@link ngAnimate ngAnimate module} for more information. */ animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), /** * @ngdoc method * @name angular.Module#filter * @module ng * @param {string} name Filter name - this must be a valid angular expression identifier * @param {Function} filterFactory Factory function for creating new instance of filter. * @description * See {@link ng.$filterProvider#register $filterProvider.register()}. * *
          * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * (`myapp_subsection_filterx`). *
          */ filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), /** * @ngdoc method * @name angular.Module#controller * @module ng * @param {string|Object} name Controller name, or an object map of controllers where the * keys are the names and the values are the constructors. * @param {Function} constructor Controller constructor function. * @description * See {@link ng.$controllerProvider#register $controllerProvider.register()}. */ controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), /** * @ngdoc method * @name angular.Module#directive * @module ng * @param {string|Object} name Directive name, or an object map of directives where the * keys are the names and the values are the factories. * @param {Function} directiveFactory Factory function for creating new instance of * directives. * @description * See {@link ng.$compileProvider#directive $compileProvider.directive()}. */ directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), /** * @ngdoc method * @name angular.Module#component * @module ng * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) * @param {Object} options Component definition object (a simplified * {@link ng.$compile#directive-definition-object directive definition object}) * * @description * See {@link ng.$compileProvider#component $compileProvider.component()}. */ component: invokeLaterAndSetModuleName('$compileProvider', 'component'), /** * @ngdoc method * @name angular.Module#config * @module ng * @param {Function} configFn Execute this function on module load. Useful for service * configuration. * @description * Use this method to register work which needs to be performed on module loading. * For more about how to configure services, see * {@link providers#provider-recipe Provider Recipe}. */ config: config, /** * @ngdoc method * @name angular.Module#run * @module ng * @param {Function} initializationFn Execute this function after injector creation. * Useful for application initialization. * @description * Use this method to register work which should be performed when the injector is done * loading all modules. */ run: function(block) { runBlocks.push(block); return this; } }; if (configFn) { config(configFn); } return moduleInstance; /** * @param {string} provider * @param {string} method * @param {String=} insertMethod * @returns {angular.Module} */ function invokeLater(provider, method, insertMethod, queue) { if (!queue) queue = invokeQueue; return function() { queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } /** * @param {string} provider * @param {string} method * @returns {angular.Module} */ function invokeLaterAndSetModuleName(provider, method) { return function(recipeName, factoryFunction) { if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; invokeQueue.push([provider, method, arguments]); return moduleInstance; }; } }); }; }); } /* global shallowCopy: true */ /** * Creates a shallow copy of an object, an array or a primitive. * * Assumes that there are no proto properties for objects. */ function shallowCopy(src, dst) { if (isArray(src)) { dst = dst || []; for (var i = 0, ii = src.length; i < ii; i++) { dst[i] = src[i]; } } else if (isObject(src)) { dst = dst || {}; for (var key in src) { if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { dst[key] = src[key]; } } } return dst || src; } /* global toDebugString: true */ function serializeObject(obj) { var seen = []; return JSON.stringify(obj, function(key, val) { val = toJsonReplacer(key, val); if (isObject(val)) { if (seen.indexOf(val) >= 0) return '...'; seen.push(val); } return val; }); } function toDebugString(obj) { if (typeof obj === 'function') { return obj.toString().replace(/ \{[\s\S]*$/, ''); } else if (isUndefined(obj)) { return 'undefined'; } else if (typeof obj !== 'string') { return serializeObject(obj); } return obj; } /* global angularModule: true, version: true, $CompileProvider, htmlAnchorDirective, inputDirective, inputDirective, formDirective, scriptDirective, selectDirective, optionDirective, ngBindDirective, ngBindHtmlDirective, ngBindTemplateDirective, ngClassDirective, ngClassEvenDirective, ngClassOddDirective, ngCloakDirective, ngControllerDirective, ngFormDirective, ngHideDirective, ngIfDirective, ngIncludeDirective, ngIncludeFillContentDirective, ngInitDirective, ngNonBindableDirective, ngPluralizeDirective, ngRepeatDirective, ngShowDirective, ngStyleDirective, ngSwitchDirective, ngSwitchWhenDirective, ngSwitchDefaultDirective, ngOptionsDirective, ngTranscludeDirective, ngModelDirective, ngListDirective, ngChangeDirective, patternDirective, patternDirective, requiredDirective, requiredDirective, minlengthDirective, minlengthDirective, maxlengthDirective, maxlengthDirective, ngValueDirective, ngModelOptionsDirective, ngAttributeAliasDirectives, ngEventDirectives, $AnchorScrollProvider, $AnimateProvider, $CoreAnimateCssProvider, $$CoreAnimateJsProvider, $$CoreAnimateQueueProvider, $$AnimateRunnerFactoryProvider, $$AnimateAsyncRunFactoryProvider, $BrowserProvider, $CacheFactoryProvider, $ControllerProvider, $DateProvider, $DocumentProvider, $ExceptionHandlerProvider, $FilterProvider, $$ForceReflowProvider, $InterpolateProvider, $IntervalProvider, $$HashMapProvider, $HttpProvider, $HttpParamSerializerProvider, $HttpParamSerializerJQLikeProvider, $HttpBackendProvider, $xhrFactoryProvider, $jsonpCallbacksProvider, $LocationProvider, $LogProvider, $ParseProvider, $RootScopeProvider, $QProvider, $$QProvider, $$SanitizeUriProvider, $SceProvider, $SceDelegateProvider, $SnifferProvider, $TemplateCacheProvider, $TemplateRequestProvider, $$TestabilityProvider, $TimeoutProvider, $$RAFProvider, $WindowProvider, $$jqLiteProvider, $$CookieReaderProvider */ /** * @ngdoc object * @name angular.version * @module ng * @description * An object that contains information about the current AngularJS version. * * This object has the following properties: * * - `full` – `{string}` – Full version string, such as "0.9.18". * - `major` – `{number}` – Major version number, such as "0". * - `minor` – `{number}` – Minor version number, such as "9". * - `dot` – `{number}` – Dot version number, such as "18". * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { // These placeholder strings will be replaced by grunt's `build` task. // They need to be double- or single-quoted. full: '1.5.11', major: 1, minor: 5, dot: 11, codeName: 'princely-quest' }; function publishExternalAPI(angular) { extend(angular, { 'bootstrap': bootstrap, 'copy': copy, 'extend': extend, 'merge': merge, 'equals': equals, 'element': jqLite, 'forEach': forEach, 'injector': createInjector, 'noop': noop, 'bind': bind, 'toJson': toJson, 'fromJson': fromJson, 'identity': identity, 'isUndefined': isUndefined, 'isDefined': isDefined, 'isString': isString, 'isFunction': isFunction, 'isObject': isObject, 'isNumber': isNumber, 'isElement': isElement, 'isArray': isArray, 'version': version, 'isDate': isDate, 'lowercase': lowercase, 'uppercase': uppercase, 'callbacks': {$$counter: 0}, 'getTestability': getTestability, '$$minErr': minErr, '$$csp': csp, 'reloadWithDebugInfo': reloadWithDebugInfo }); angularModule = setupModuleLoader(window); angularModule('ng', ['ngLocale'], ['$provide', function ngModule($provide) { // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. $provide.provider({ $$sanitizeUri: $$SanitizeUriProvider }); $provide.provider('$compile', $CompileProvider). directive({ a: htmlAnchorDirective, input: inputDirective, textarea: inputDirective, form: formDirective, script: scriptDirective, select: selectDirective, option: optionDirective, ngBind: ngBindDirective, ngBindHtml: ngBindHtmlDirective, ngBindTemplate: ngBindTemplateDirective, ngClass: ngClassDirective, ngClassEven: ngClassEvenDirective, ngClassOdd: ngClassOddDirective, ngCloak: ngCloakDirective, ngController: ngControllerDirective, ngForm: ngFormDirective, ngHide: ngHideDirective, ngIf: ngIfDirective, ngInclude: ngIncludeDirective, ngInit: ngInitDirective, ngNonBindable: ngNonBindableDirective, ngPluralize: ngPluralizeDirective, ngRepeat: ngRepeatDirective, ngShow: ngShowDirective, ngStyle: ngStyleDirective, ngSwitch: ngSwitchDirective, ngSwitchWhen: ngSwitchWhenDirective, ngSwitchDefault: ngSwitchDefaultDirective, ngOptions: ngOptionsDirective, ngTransclude: ngTranscludeDirective, ngModel: ngModelDirective, ngList: ngListDirective, ngChange: ngChangeDirective, pattern: patternDirective, ngPattern: patternDirective, required: requiredDirective, ngRequired: requiredDirective, minlength: minlengthDirective, ngMinlength: minlengthDirective, maxlength: maxlengthDirective, ngMaxlength: maxlengthDirective, ngValue: ngValueDirective, ngModelOptions: ngModelOptionsDirective }). directive({ ngInclude: ngIncludeFillContentDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives); $provide.provider({ $anchorScroll: $AnchorScrollProvider, $animate: $AnimateProvider, $animateCss: $CoreAnimateCssProvider, $$animateJs: $$CoreAnimateJsProvider, $$animateQueue: $$CoreAnimateQueueProvider, $$AnimateRunner: $$AnimateRunnerFactoryProvider, $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, $browser: $BrowserProvider, $cacheFactory: $CacheFactoryProvider, $controller: $ControllerProvider, $document: $DocumentProvider, $exceptionHandler: $ExceptionHandlerProvider, $filter: $FilterProvider, $$forceReflow: $$ForceReflowProvider, $interpolate: $InterpolateProvider, $interval: $IntervalProvider, $http: $HttpProvider, $httpParamSerializer: $HttpParamSerializerProvider, $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, $httpBackend: $HttpBackendProvider, $xhrFactory: $xhrFactoryProvider, $jsonpCallbacks: $jsonpCallbacksProvider, $location: $LocationProvider, $log: $LogProvider, $parse: $ParseProvider, $rootScope: $RootScopeProvider, $q: $QProvider, $$q: $$QProvider, $sce: $SceProvider, $sceDelegate: $SceDelegateProvider, $sniffer: $SnifferProvider, $templateCache: $TemplateCacheProvider, $templateRequest: $TemplateRequestProvider, $$testability: $$TestabilityProvider, $timeout: $TimeoutProvider, $window: $WindowProvider, $$rAF: $$RAFProvider, $$jqLite: $$jqLiteProvider, $$HashMap: $$HashMapProvider, $$cookieReader: $$CookieReaderProvider }); } ]); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* global JQLitePrototype: true, addEventListenerFn: true, removeEventListenerFn: true, BOOLEAN_ATTR: true, ALIASED_ATTR: true */ ////////////////////////////////// //JQLite ////////////////////////////////// /** * @ngdoc function * @name angular.element * @module ng * @kind function * * @description * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. * * If jQuery is available, `angular.element` is an alias for the * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. * * jqLite is a tiny, API-compatible subset of jQuery that allows * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most * commonly needed functionality with the goal of having a very small footprint. * * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a * specific version of jQuery if multiple versions exist on the page. * *
          **Note:** All element references in Angular are always wrapped with jQuery or * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.
          * *
          **Note:** Keep in mind that this function will not find elements * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.
          * * ## Angular's jqLite * jqLite provides only the following jQuery methods: * * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData * - [`children()`](http://api.jquery.com/children/) - Does not support selectors * - [`clone()`](http://api.jquery.com/clone/) * - [`contents()`](http://api.jquery.com/contents/) * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. * - [`data()`](http://api.jquery.com/data/) * - [`detach()`](http://api.jquery.com/detach/) * - [`empty()`](http://api.jquery.com/empty/) * - [`eq()`](http://api.jquery.com/eq/) * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name * - [`hasClass()`](http://api.jquery.com/hasClass/) * - [`html()`](http://api.jquery.com/html/) * - [`next()`](http://api.jquery.com/next/) - Does not support selectors * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors * - [`prepend()`](http://api.jquery.com/prepend/) * - [`prop()`](http://api.jquery.com/prop/) * - [`ready()`](http://api.jquery.com/ready/) * - [`remove()`](http://api.jquery.com/remove/) * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - Does not support multiple attributes * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument * - [`removeData()`](http://api.jquery.com/removeData/) * - [`replaceWith()`](http://api.jquery.com/replaceWith/) * - [`text()`](http://api.jquery.com/text/) * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter * - [`val()`](http://api.jquery.com/val/) * - [`wrap()`](http://api.jquery.com/wrap/) * * ## jQuery/jqLite Extras * Angular also provides the following additional methods and events to both jQuery and jqLite: * * ### Events * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM * element before it is removed. * * ### Methods * - `controller(name)` - retrieves the controller of the current element or its parent. By default * retrieves controller associated with the `ngController` directive. If `name` is provided as * camelCase directive name, then the controller for this directive will be retrieved (e.g. * `'ngModel'`). * - `injector()` - retrieves the injector of the current element or its parent. * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to * be enabled. * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the * current element. This getter should be used only on elements that contain a directive which starts a new isolate * scope. Calling `scope()` on this element always returns the original non-isolate scope. * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See * https://github.com/angular/angular.js/issues/14251 for more information. * * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. * @returns {Object} jQuery object. */ JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, jqId = 1, addEventListenerFn = function(element, type, fn) { element.addEventListener(type, fn, false); }, removeEventListenerFn = function(element, type, fn) { element.removeEventListener(type, fn, false); }; /* * !!! This is an undocumented "private" function !!! */ JQLite._data = function(node) { //jQuery always returns an object on cache miss return this.cache[node[this.expando]] || {}; }; function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; var jqLiteMinErr = minErr('jqLite'); /** * Converts snake_case to camelCase. * Also there is special case for Moz prefix starting with upper case letter. * @param name Name to normalize */ function camelCase(name) { return name. replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { return offset ? letter.toUpperCase() : letter; }). replace(MOZ_HACK_REGEXP, 'Moz$1'); } var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; var HTML_REGEXP = /<|&#?\w+;/; var TAG_NAME_REGEXP = /<([\w:-]+)/; var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; var wrapMap = { 'option': [1, ''], 'thead': [1, '', '
          '], 'col': [2, '', '
          '], 'tr': [2, '', '
          '], 'td': [3, '', '
          '], '_default': [0, '', ''] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html); } function jqLiteAcceptsData(node) { // The window object can accept data but has no nodeType // Otherwise we are only interested in elements (1) and documents (9) var nodeType = node.nodeType; return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; } function jqLiteHasData(node) { for (var key in jqCache[node.ng339]) { return true; } return false; } function jqLiteCleanData(nodes) { for (var i = 0, ii = nodes.length; i < ii; i++) { jqLiteRemoveData(nodes[i]); } } function jqLiteBuildFragment(html, context) { var tmp, tag, wrap, fragment = context.createDocumentFragment(), nodes = [], i; if (jqLiteIsTextNode(html)) { // Convert non-html into a text node nodes.push(context.createTextNode(html)); } else { // Convert html into DOM nodes tmp = fragment.appendChild(context.createElement('div')); tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase(); wrap = wrapMap[tag] || wrapMap._default; tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1>') + wrap[2]; // Descend through wrappers to the right content i = wrap[0]; while (i--) { tmp = tmp.lastChild; } nodes = concat(nodes, tmp.childNodes); tmp = fragment.firstChild; tmp.textContent = ''; } // Remove wrapper from fragment fragment.textContent = ''; fragment.innerHTML = ''; // Clear inner HTML forEach(nodes, function(node) { fragment.appendChild(node); }); return fragment; } function jqLiteParseHTML(html, context) { context = context || window.document; var parsed; if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { return [context.createElement(parsed[1])]; } if ((parsed = jqLiteBuildFragment(html, context))) { return parsed.childNodes; } return []; } function jqLiteWrapNode(node, wrapper) { var parent = node.parentNode; if (parent) { parent.replaceChild(wrapper, node); } wrapper.appendChild(node); } // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) { // eslint-disable-next-line no-bitwise return !!(this.compareDocumentPosition(arg) & 16); }; ///////////////////////////////////////////// function JQLite(element) { if (element instanceof JQLite) { return element; } var argIsString; if (isString(element)) { element = trim(element); argIsString = true; } if (!(this instanceof JQLite)) { if (argIsString && element.charAt(0) !== '<') { throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); } return new JQLite(element); } if (argIsString) { jqLiteAddNodes(this, jqLiteParseHTML(element)); } else { jqLiteAddNodes(this, element); } } function jqLiteClone(element) { return element.cloneNode(true); } function jqLiteDealoc(element, onlyDescendants) { if (!onlyDescendants) jqLiteRemoveData(element); if (element.querySelectorAll) { var descendants = element.querySelectorAll('*'); for (var i = 0, l = descendants.length; i < l; i++) { jqLiteRemoveData(descendants[i]); } } } function jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); var expandoStore = jqLiteExpandoStore(element); var events = expandoStore && expandoStore.events; var handle = expandoStore && expandoStore.handle; if (!handle) return; //no listeners registered if (!type) { for (type in events) { if (type !== '$destroy') { removeEventListenerFn(element, type, handle); } delete events[type]; } } else { var removeHandler = function(type) { var listenerFns = events[type]; if (isDefined(fn)) { arrayRemove(listenerFns || [], fn); } if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { removeEventListenerFn(element, type, handle); delete events[type]; } }; forEach(type.split(' '), function(type) { removeHandler(type); if (MOUSE_EVENT_MAP[type]) { removeHandler(MOUSE_EVENT_MAP[type]); } }); } } function jqLiteRemoveData(element, name) { var expandoId = element.ng339; var expandoStore = expandoId && jqCache[expandoId]; if (expandoStore) { if (name) { delete expandoStore.data[name]; return; } if (expandoStore.handle) { if (expandoStore.events.$destroy) { expandoStore.handle({}, '$destroy'); } jqLiteOff(element); } delete jqCache[expandoId]; element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it } } function jqLiteExpandoStore(element, createIfNecessary) { var expandoId = element.ng339, expandoStore = expandoId && jqCache[expandoId]; if (createIfNecessary && !expandoStore) { element.ng339 = expandoId = jqNextId(); expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; } return expandoStore; } function jqLiteData(element, key, value) { if (jqLiteAcceptsData(element)) { var isSimpleSetter = isDefined(value); var isSimpleGetter = !isSimpleSetter && key && !isObject(key); var massGetter = !key; var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); var data = expandoStore && expandoStore.data; if (isSimpleSetter) { // data('key', value) data[key] = value; } else { if (massGetter) { // data() return data; } else { if (isSimpleGetter) { // data('key') // don't force creation of expandoStore if it doesn't exist yet return data && data[key]; } else { // mass-setter: data({key1: val1, key2: val2}) extend(data, key); } } } } } function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' '). indexOf(' ' + selector + ' ') > -1); } function jqLiteRemoveClass(element, cssClasses) { if (cssClasses && element.setAttribute) { forEach(cssClasses.split(' '), function(cssClass) { element.setAttribute('class', trim( (' ' + (element.getAttribute('class') || '') + ' ') .replace(/[\n\t]/g, ' ') .replace(' ' + trim(cssClass) + ' ', ' ')) ); }); } } function jqLiteAddClass(element, cssClasses) { if (cssClasses && element.setAttribute) { var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') .replace(/[\n\t]/g, ' '); forEach(cssClasses.split(' '), function(cssClass) { cssClass = trim(cssClass); if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { existingClasses += cssClass + ' '; } }); element.setAttribute('class', trim(existingClasses)); } } function jqLiteAddNodes(root, elements) { // THIS CODE IS VERY HOT. Don't make changes without benchmarking. if (elements) { // if a Node (the most common case) if (elements.nodeType) { root[root.length++] = elements; } else { var length = elements.length; // if an Array or NodeList and not a Window if (typeof length === 'number' && elements.window !== elements) { if (length) { for (var i = 0; i < length; i++) { root[root.length++] = elements[i]; } } } else { root[root.length++] = elements; } } } } function jqLiteController(element, name) { return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); } function jqLiteInheritedData(element, name, value) { // if element is the document object work with the html element instead // this makes $(document).scope() possible if (element.nodeType === NODE_TYPE_DOCUMENT) { element = element.documentElement; } var names = isArray(name) ? name : [name]; while (element) { for (var i = 0, ii = names.length; i < ii; i++) { if (isDefined(value = jqLite.data(element, names[i]))) return value; } // If dealing with a document fragment node with a host element, and no parent, use the host // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM // to lookup parent controllers. element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); } } function jqLiteEmpty(element) { jqLiteDealoc(element, true); while (element.firstChild) { element.removeChild(element.firstChild); } } function jqLiteRemove(element, keepData) { if (!keepData) jqLiteDealoc(element); var parent = element.parentNode; if (parent) parent.removeChild(element); } function jqLiteDocumentLoaded(action, win) { win = win || window; if (win.document.readyState === 'complete') { // Force the action to be run async for consistent behavior // from the action's point of view // i.e. it will definitely not be in a $apply win.setTimeout(action); } else { // No need to unbind this handler as load is only ever called once jqLite(win).on('load', action); } } ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// var JQLitePrototype = JQLite.prototype = { ready: function(fn) { var fired = false; function trigger() { if (fired) return; fired = true; fn(); } // check if document is already loaded if (window.document.readyState === 'complete') { window.setTimeout(trigger); } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 // we can not use jqLite since we are not done loading and jQuery could be loaded later. // eslint-disable-next-line new-cap JQLite(window).on('load', trigger); // fallback to window.onload for others } }, toString: function() { var value = []; forEach(this, function(e) { value.push('' + e);}); return '[' + value.join(', ') + ']'; }, eq: function(index) { return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); }, length: 0, push: push, sort: [].sort, splice: [].splice }; ////////////////////////////////////////// // Functions iterating getter/setters. // these functions return self on setter and // value on get. ////////////////////////////////////////// var BOOLEAN_ATTR = {}; forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { BOOLEAN_ATTR[lowercase(value)] = value; }); var BOOLEAN_ELEMENTS = {}; forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { BOOLEAN_ELEMENTS[value] = true; }); var ALIASED_ATTR = { 'ngMinlength': 'minlength', 'ngMaxlength': 'maxlength', 'ngMin': 'min', 'ngMax': 'max', 'ngPattern': 'pattern' }; function getBooleanAttrName(element, name) { // check dom last since we will most likely fail on name var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; // booleanAttr is here twice to minimize DOM access return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; } function getAliasedAttrName(name) { return ALIASED_ATTR[name]; } forEach({ data: jqLiteData, removeData: jqLiteRemoveData, hasData: jqLiteHasData, cleanData: jqLiteCleanData }, function(fn, name) { JQLite[name] = fn; }); forEach({ data: jqLiteData, inheritedData: jqLiteInheritedData, scope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); }, isolateScope: function(element) { // Can't use jqLiteData here directly so we stay compatible with jQuery! return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); }, controller: jqLiteController, injector: function(element) { return jqLiteInheritedData(element, '$injector'); }, removeAttr: function(element, name) { element.removeAttribute(name); }, hasClass: jqLiteHasClass, css: function(element, name, value) { name = camelCase(name); if (isDefined(value)) { element.style[name] = value; } else { return element.style[name]; } }, attr: function(element, name, value) { var nodeType = element.nodeType; if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { return; } var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { if (value) { element[name] = true; element.setAttribute(name, lowercasedName); } else { element[name] = false; element.removeAttribute(lowercasedName); } } else { return (element[name] || (element.attributes.getNamedItem(name) || noop).specified) ? lowercasedName : undefined; } } else if (isDefined(value)) { element.setAttribute(name, value); } else if (element.getAttribute) { // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code // some elements (e.g. Document) don't have get attribute, so return undefined var ret = element.getAttribute(name, 2); // normalize non-existing attributes to undefined (as jQuery) return ret === null ? undefined : ret; } }, prop: function(element, name, value) { if (isDefined(value)) { element[name] = value; } else { return element[name]; } }, text: (function() { getText.$dv = ''; return getText; function getText(element, value) { if (isUndefined(value)) { var nodeType = element.nodeType; return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; } element.textContent = value; } })(), val: function(element, value) { if (isUndefined(value)) { if (element.multiple && nodeName_(element) === 'select') { var result = []; forEach(element.options, function(option) { if (option.selected) { result.push(option.value || option.text); } }); return result.length === 0 ? null : result; } return element.value; } element.value = value; }, html: function(element, value) { if (isUndefined(value)) { return element.innerHTML; } jqLiteDealoc(element, true); element.innerHTML = value; }, empty: jqLiteEmpty }, function(fn, name) { /** * Properties: writes return selection, reads return first value */ JQLite.prototype[name] = function(arg1, arg2) { var i, key; var nodeCount = this.length; // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it // in a way that survives minification. // jqLiteEmpty takes no arguments but is a setter. if (fn !== jqLiteEmpty && (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { if (isObject(arg1)) { // we are a write, but the object properties are the key/values for (i = 0; i < nodeCount; i++) { if (fn === jqLiteData) { // data() takes the whole object in jQuery fn(this[i], arg1); } else { for (key in arg1) { fn(this[i], key, arg1[key]); } } } // return self for chaining return this; } else { // we are a read, so read the first child. // TODO: do we still need this? var value = fn.$dv; // Only if we have $dv do we iterate over all, otherwise it is just the first element. var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; for (var j = 0; j < jj; j++) { var nodeValue = fn(this[j], arg1, arg2); value = value ? value + nodeValue : nodeValue; } return value; } } else { // we are a write, so apply to all children for (i = 0; i < nodeCount; i++) { fn(this[i], arg1, arg2); } // return self for chaining return this; } }; }); function createEventHandler(element, events) { var eventHandler = function(event, type) { // jQuery specific api event.isDefaultPrevented = function() { return event.defaultPrevented; }; var eventFns = events[type || event.type]; var eventFnsLength = eventFns ? eventFns.length : 0; if (!eventFnsLength) return; if (isUndefined(event.immediatePropagationStopped)) { var originalStopImmediatePropagation = event.stopImmediatePropagation; event.stopImmediatePropagation = function() { event.immediatePropagationStopped = true; if (event.stopPropagation) { event.stopPropagation(); } if (originalStopImmediatePropagation) { originalStopImmediatePropagation.call(event); } }; } event.isImmediatePropagationStopped = function() { return event.immediatePropagationStopped === true; }; // Some events have special handlers that wrap the real handler var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; // Copy event handlers in case event handlers array is modified during execution. if ((eventFnsLength > 1)) { eventFns = shallowCopy(eventFns); } for (var i = 0; i < eventFnsLength; i++) { if (!event.isImmediatePropagationStopped()) { handlerWrapper(element, event, eventFns[i]); } } }; // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all // events on `element` eventHandler.elem = element; return eventHandler; } function defaultHandlerWrapper(element, event, handler) { handler.call(element, event); } function specialMouseHandlerWrapper(target, event, handler) { // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 var related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if (!related || (related !== target && !jqLiteContains.call(target, related))) { handler.call(target, event); } } ////////////////////////////////////////// // Functions iterating traversal. // These functions chain results into a single // selector. ////////////////////////////////////////// forEach({ removeData: jqLiteRemoveData, on: function jqLiteOn(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); // Do not add event handlers to non-elements because they will not be cleaned up. if (!jqLiteAcceptsData(element)) { return; } var expandoStore = jqLiteExpandoStore(element, true); var events = expandoStore.events; var handle = expandoStore.handle; if (!handle) { handle = expandoStore.handle = createEventHandler(element, events); } // http://jsperf.com/string-indexof-vs-split var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; var i = types.length; var addHandler = function(type, specialHandlerWrapper, noEventListener) { var eventFns = events[type]; if (!eventFns) { eventFns = events[type] = []; eventFns.specialHandlerWrapper = specialHandlerWrapper; if (type !== '$destroy' && !noEventListener) { addEventListenerFn(element, type, handle); } } eventFns.push(fn); }; while (i--) { type = types[i]; if (MOUSE_EVENT_MAP[type]) { addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); addHandler(type, undefined, true); } else { addHandler(type); } } }, off: jqLiteOff, one: function(element, type, fn) { element = jqLite(element); //add the listener twice so that when it is called //you can remove the original function and still be //able to call element.off(ev, fn) normally element.on(type, function onFn() { element.off(type, fn); element.off(type, onFn); }); element.on(type, fn); }, replaceWith: function(element, replaceNode) { var index, parent = element.parentNode; jqLiteDealoc(element); forEach(new JQLite(replaceNode), function(node) { if (index) { parent.insertBefore(node, index.nextSibling); } else { parent.replaceChild(node, element); } index = node; }); }, children: function(element) { var children = []; forEach(element.childNodes, function(element) { if (element.nodeType === NODE_TYPE_ELEMENT) { children.push(element); } }); return children; }, contents: function(element) { return element.contentDocument || element.childNodes || []; }, append: function(element, node) { var nodeType = element.nodeType; if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; node = new JQLite(node); for (var i = 0, ii = node.length; i < ii; i++) { var child = node[i]; element.appendChild(child); } }, prepend: function(element, node) { if (element.nodeType === NODE_TYPE_ELEMENT) { var index = element.firstChild; forEach(new JQLite(node), function(child) { element.insertBefore(child, index); }); } }, wrap: function(element, wrapNode) { jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); }, remove: jqLiteRemove, detach: function(element) { jqLiteRemove(element, true); }, after: function(element, newElement) { var index = element, parent = element.parentNode; if (parent) { newElement = new JQLite(newElement); for (var i = 0, ii = newElement.length; i < ii; i++) { var node = newElement[i]; parent.insertBefore(node, index.nextSibling); index = node; } } }, addClass: jqLiteAddClass, removeClass: jqLiteRemoveClass, toggleClass: function(element, selector, condition) { if (selector) { forEach(selector.split(' '), function(className) { var classCondition = condition; if (isUndefined(classCondition)) { classCondition = !jqLiteHasClass(element, className); } (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); }); } }, parent: function(element) { var parent = element.parentNode; return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; }, next: function(element) { return element.nextElementSibling; }, find: function(element, selector) { if (element.getElementsByTagName) { return element.getElementsByTagName(selector); } else { return []; } }, clone: jqLiteClone, triggerHandler: function(element, event, extraParameters) { var dummyEvent, eventFnsCopy, handlerArgs; var eventName = event.type || event; var expandoStore = jqLiteExpandoStore(element); var events = expandoStore && expandoStore.events; var eventFns = events && events[eventName]; if (eventFns) { // Create a dummy event to pass to the handlers dummyEvent = { preventDefault: function() { this.defaultPrevented = true; }, isDefaultPrevented: function() { return this.defaultPrevented === true; }, stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, stopPropagation: noop, type: eventName, target: element }; // If a custom event was provided then extend our dummy event with it if (event.type) { dummyEvent = extend(dummyEvent, event); } // Copy event handlers in case event handlers array is modified during execution. eventFnsCopy = shallowCopy(eventFns); handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; forEach(eventFnsCopy, function(fn) { if (!dummyEvent.isImmediatePropagationStopped()) { fn.apply(element, handlerArgs); } }); } } }, function(fn, name) { /** * chaining functions */ JQLite.prototype[name] = function(arg1, arg2, arg3) { var value; for (var i = 0, ii = this.length; i < ii; i++) { if (isUndefined(value)) { value = fn(this[i], arg1, arg2, arg3); if (isDefined(value)) { // any function which returns a value needs to be wrapped value = jqLite(value); } } else { jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); } } return isDefined(value) ? value : this; }; }); // bind legacy bind/unbind to on/off JQLite.prototype.bind = JQLite.prototype.on; JQLite.prototype.unbind = JQLite.prototype.off; // Provider for private $$jqLite service /** @this */ function $$jqLiteProvider() { this.$get = function $$jqLite() { return extend(JQLite, { hasClass: function(node, classes) { if (node.attr) node = node[0]; return jqLiteHasClass(node, classes); }, addClass: function(node, classes) { if (node.attr) node = node[0]; return jqLiteAddClass(node, classes); }, removeClass: function(node, classes) { if (node.attr) node = node[0]; return jqLiteRemoveClass(node, classes); } }); }; } /** * Computes a hash of an 'obj'. * Hash of a: * string is string * number is number as string * object is either result of calling $$hashKey function on the object or uniquely generated id, * that is also assigned to the $$hashKey property of the object. * * @param obj * @returns {string} hash string such that the same input will have the same hash string. * The resulting string key is in 'type:hashKey' format. */ function hashKey(obj, nextUidFn) { var key = obj && obj.$$hashKey; if (key) { if (typeof key === 'function') { key = obj.$$hashKey(); } return key; } var objType = typeof obj; if (objType === 'function' || (objType === 'object' && obj !== null)) { key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); } else { key = objType + ':' + obj; } return key; } /** * HashMap which can use objects as keys */ function HashMap(array, isolatedUid) { if (isolatedUid) { var uid = 0; this.nextUid = function() { return ++uid; }; } forEach(array, this.put, this); } HashMap.prototype = { /** * Store key value pair * @param key key to store can be any type * @param value value to store can be any type */ put: function(key, value) { this[hashKey(key, this.nextUid)] = value; }, /** * @param key * @returns {Object} the value for the key */ get: function(key) { return this[hashKey(key, this.nextUid)]; }, /** * Remove the key/value pair * @param key */ remove: function(key) { var value = this[key = hashKey(key, this.nextUid)]; delete this[key]; return value; } }; var $$HashMapProvider = [/** @this */function() { this.$get = [function() { return HashMap; }]; }]; /** * @ngdoc function * @module ng * @name angular.injector * @kind function * * @description * Creates an injector object that can be used for retrieving services as well as for * dependency injection (see {@link guide/di dependency injection}). * * @param {Array.} modules A list of module functions or their aliases. See * {@link angular.module}. The `ng` module must be explicitly added. * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which * disallows argument name annotation inference. * @returns {injector} Injector object. See {@link auto.$injector $injector}. * * @example * Typical usage * ```js * // create an injector * var $injector = angular.injector(['ng']); * * // use the injector to kick off your application * // use the type inference to auto inject arguments, or use implicit injection * $injector.invoke(function($rootScope, $compile, $document) { * $compile($document)($rootScope); * $rootScope.$digest(); * }); * ``` * * Sometimes you want to get access to the injector of a currently running Angular app * from outside Angular. Perhaps, you want to inject and compile some markup after the * application has been bootstrapped. You can do this using the extra `injector()` added * to JQuery/jqLite elements. See {@link angular.element}. * * *This is fairly rare but could be the case if a third party library is injecting the * markup.* * * In the following example a new block of HTML containing a `ng-controller` * directive is added to the end of the document body by JQuery. We then compile and link * it into the current AngularJS scope. * * ```js * var $div = $('
          {{content.label}}
          '); * $(document.body).append($div); * * angular.element(document).injector().invoke(function($compile) { * var scope = angular.element($div).scope(); * $compile($div)(scope); * }); * ``` */ /** * @ngdoc module * @name auto * @installation * @description * * Implicit module which gets automatically added to each {@link auto.$injector $injector}. */ var ARROW_ARG = /^([^(]+?)=>/; var FN_ARGS = /^[^(]*\(\s*([^)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; var $injectorMinErr = minErr('$injector'); function stringifyFn(fn) { // Support: Chrome 50-51 only // Creating a new string by adding `' '` at the end, to hack around some bug in Chrome v50/51 // (See https://github.com/angular/angular.js/issues/14487.) // TODO (gkalpak): Remove workaround when Chrome v52 is released return Function.prototype.toString.call(fn) + ' '; } function extractArgs(fn) { var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''), args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); return args; } function anonFn(fn) { // For anonymous functions, showing at the very least the function signature can help in // debugging. var args = extractArgs(fn); if (args) { return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; } return 'fn'; } function annotate(fn, strictDi, name) { var $inject, argDecl, last; if (typeof fn === 'function') { if (!($inject = fn.$inject)) { $inject = []; if (fn.length) { if (strictDi) { if (!isString(name) || !name) { name = fn.name || anonFn(fn); } throw $injectorMinErr('strictdi', '{0} is not using explicit annotation and cannot be invoked in strict mode', name); } argDecl = extractArgs(fn); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { arg.replace(FN_ARG, function(all, underscore, name) { $inject.push(name); }); }); } fn.$inject = $inject; } } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn'); $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } return $inject; } /////////////////////////////////////// /** * @ngdoc service * @name $injector * * @description * * `$injector` is used to retrieve object instances as defined by * {@link auto.$provide provider}, instantiate types, invoke methods, * and load modules. * * The following always holds true: * * ```js * var $injector = angular.injector(); * expect($injector.get('$injector')).toBe($injector); * expect($injector.invoke(function($injector) { * return $injector; * })).toBe($injector); * ``` * * # Injection Function Annotation * * JavaScript does not have annotations, and annotations are needed for dependency injection. The * following are all valid ways of annotating function with injection arguments and are equivalent. * * ```js * // inferred (only works if code not minified/obfuscated) * $injector.invoke(function(serviceA){}); * * // annotated * function explicit(serviceA) {}; * explicit.$inject = ['serviceA']; * $injector.invoke(explicit); * * // inline * $injector.invoke(['serviceA', function(serviceA){}]); * ``` * * ## Inference * * In JavaScript calling `toString()` on a function returns the function definition. The definition * can then be parsed and the function arguments can be extracted. This method of discovering * annotations is disallowed when the injector is in strict mode. * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the * argument names. * * ## `$inject` Annotation * By adding an `$inject` property onto a function the injection parameters can be specified. * * ## Inline * As an array of injection names, where the last item in the array is the function to call. */ /** * @ngdoc method * @name $injector#get * * @description * Return an instance of the service. * * @param {string} name The name of the instance to retrieve. * @param {string=} caller An optional string to provide the origin of the function call for error messages. * @return {*} The instance. */ /** * @ngdoc method * @name $injector#invoke * * @description * Invoke the method and supply the method arguments from the `$injector`. * * @param {Function|Array.} fn The injectable function to invoke. Function parameters are * injected according to the {@link guide/di $inject Annotation} rules. * @param {Object=} self The `this` for the invoked method. * @param {Object=} locals Optional object. If preset then any argument names are read from this * object first, before the `$injector` is consulted. * @returns {*} the value returned by the invoked `fn` function. */ /** * @ngdoc method * @name $injector#has * * @description * Allows the user to query if the particular service exists. * * @param {string} name Name of the service to query. * @returns {boolean} `true` if injector has given service. */ /** * @ngdoc method * @name $injector#instantiate * @description * Create a new instance of JS type. The method takes a constructor function, invokes the new * operator, and supplies all of the arguments to the constructor function as specified by the * constructor annotation. * * @param {Function} Type Annotated constructor function. * @param {Object=} locals Optional object. If preset then any argument names are read from this * object first, before the `$injector` is consulted. * @returns {Object} new instance of `Type`. */ /** * @ngdoc method * @name $injector#annotate * * @description * Returns an array of service names which the function is requesting for injection. This API is * used by the injector to determine which services need to be injected into the function when the * function is invoked. There are three ways in which the function can be annotated with the needed * dependencies. * * # Argument names * * The simplest form is to extract the dependencies from the arguments of the function. This is done * by converting the function into a string using `toString()` method and extracting the argument * names. * ```js * // Given * function MyController($scope, $route) { * // ... * } * * // Then * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * * You can disallow this method by using strict injection mode. * * This method does not work with code minification / obfuscation. For this reason the following * annotation strategies are supported. * * # The `$inject` property * * If a function has an `$inject` property and its value is an array of strings, then the strings * represent names of services to be injected into the function. * ```js * // Given * var MyController = function(obfuscatedScope, obfuscatedRoute) { * // ... * } * // Define function dependencies * MyController['$inject'] = ['$scope', '$route']; * * // Then * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * ``` * * # The array notation * * It is often desirable to inline Injected functions and that's when setting the `$inject` property * is very inconvenient. In these situations using the array notation to specify the dependencies in * a way that survives minification is a better choice: * * ```js * // We wish to write this (not minification / obfuscation safe) * injector.invoke(function($compile, $rootScope) { * // ... * }); * * // We are forced to write break inlining * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { * // ... * }; * tmpFn.$inject = ['$compile', '$rootScope']; * injector.invoke(tmpFn); * * // To better support inline function the inline annotation is supported * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { * // ... * }]); * * // Therefore * expect(injector.annotate( * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) * ).toEqual(['$compile', '$rootScope']); * ``` * * @param {Function|Array.} fn Function for which dependent service names need to * be retrieved as described above. * * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. * * @returns {Array.} The names of the services which the function requires. */ /** * @ngdoc service * @name $provide * * @description * * The {@link auto.$provide $provide} service has a number of methods for registering components * with the {@link auto.$injector $injector}. Many of these functions are also exposed on * {@link angular.Module}. * * An Angular **service** is a singleton object created by a **service factory**. These **service * factories** are functions which, in turn, are created by a **service provider**. * The **service providers** are constructor functions. When instantiated they must contain a * property called `$get`, which holds the **service factory** function. * * When you request a service, the {@link auto.$injector $injector} is responsible for finding the * correct **service provider**, instantiating it and then calling its `$get` **service factory** * function to get the instance of the **service**. * * Often services have no configuration options and there is no need to add methods to the service * provider. The provider will be no more than a constructor function with a `$get` property. For * these cases the {@link auto.$provide $provide} service has additional helper methods to register * services without specifying a provider. * * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the * {@link auto.$injector $injector} * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by * providers and services. * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by * services, not providers. * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function** * that will be wrapped in a **service provider** object, whose `$get` property will contain the * given factory function. * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function** * that will be wrapped in a **service provider** object, whose `$get` property will instantiate * a new object using the given constructor function. * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that * will be able to modify or replace the implementation of another service. * * See the individual methods for more information and examples. */ /** * @ngdoc method * @name $provide#provider * @description * * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions * are constructor functions, whose instances are responsible for "providing" a factory for a * service. * * Service provider names start with the name of the service they provide followed by `Provider`. * For example, the {@link ng.$log $log} service has a provider called * {@link ng.$logProvider $logProvider}. * * Service provider objects can have additional methods which allow configuration of the provider * and its service. Importantly, you can configure what kind of service is created by the `$get` * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a * method {@link ng.$logProvider#debugEnabled debugEnabled} * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the * console or not. * * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key. * @param {(Object|function())} provider If the provider is: * * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. * - `Constructor`: a new instance of the provider will be created using * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. * * @returns {Object} registered provider instance * @example * * The following example shows how to create a simple event tracking service and register it using * {@link auto.$provide#provider $provide.provider()}. * * ```js * // Define the eventTracker provider * function EventTrackerProvider() { * var trackingUrl = '/track'; * * // A provider method for configuring where the tracked events should been saved * this.setTrackingUrl = function(url) { * trackingUrl = url; * }; * * // The service factory function * this.$get = ['$http', function($http) { * var trackedEvents = {}; * return { * // Call this to track an event * event: function(event) { * var count = trackedEvents[event] || 0; * count += 1; * trackedEvents[event] = count; * return count; * }, * // Call this to save the tracked events to the trackingUrl * save: function() { * $http.post(trackingUrl, trackedEvents); * } * }; * }]; * } * * describe('eventTracker', function() { * var postSpy; * * beforeEach(module(function($provide) { * // Register the eventTracker provider * $provide.provider('eventTracker', EventTrackerProvider); * })); * * beforeEach(module(function(eventTrackerProvider) { * // Configure eventTracker provider * eventTrackerProvider.setTrackingUrl('/custom-track'); * })); * * it('tracks events', inject(function(eventTracker) { * expect(eventTracker.event('login')).toEqual(1); * expect(eventTracker.event('login')).toEqual(2); * })); * * it('saves to the tracking url', inject(function(eventTracker, $http) { * postSpy = spyOn($http, 'post'); * eventTracker.event('login'); * eventTracker.save(); * expect(postSpy).toHaveBeenCalled(); * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); * })); * }); * ``` */ /** * @ngdoc method * @name $provide#factory * @description * * Register a **service factory**, which will be called to return the service instance. * This is short for registering a service where its provider consists of only a `$get` property, * which is the given service factory function. * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to * configure your service in a provider. * * @param {string} name The name of the instance. * @param {Function|Array.} $getFn The injectable $getFn for the instance creation. * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. * @returns {Object} registered provider instance * * @example * Here is an example of registering a service * ```js * $provide.factory('ping', ['$http', function($http) { * return function ping() { * return $http.send('/ping'); * }; * }]); * ``` * You would then inject and use this service like this: * ```js * someModule.controller('Ctrl', ['ping', function(ping) { * ping(); * }]); * ``` */ /** * @ngdoc method * @name $provide#service * @description * * Register a **service constructor**, which will be invoked with `new` to create the service * instance. * This is short for registering a service where its provider's `$get` property is a factory * function that returns an instance instantiated by the injector from the service constructor * function. * * Internally it looks a bit like this: * * ``` * { * $get: function() { * return $injector.instantiate(constructor); * } * } * ``` * * * You should use {@link auto.$provide#service $provide.service(class)} if you define your service * as a type/class. * * @param {string} name The name of the instance. * @param {Function|Array.} constructor An injectable class (constructor function) * that will be instantiated. * @returns {Object} registered provider instance * * @example * Here is an example of registering a service using * {@link auto.$provide#service $provide.service(class)}. * ```js * var Ping = function($http) { * this.$http = $http; * }; * * Ping.$inject = ['$http']; * * Ping.prototype.send = function() { * return this.$http.get('/ping'); * }; * $provide.service('ping', Ping); * ``` * You would then inject and use this service like this: * ```js * someModule.controller('Ctrl', ['ping', function(ping) { * ping.send(); * }]); * ``` */ /** * @ngdoc method * @name $provide#value * @description * * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a * number, an array, an object or a function. This is short for registering a service where its * provider's `$get` property is a factory function that takes no arguments and returns the **value * service**. That also means it is not possible to inject other services into a value service. * * Value services are similar to constant services, except that they cannot be injected into a * module configuration function (see {@link angular.Module#config}) but they can be overridden by * an Angular {@link auto.$provide#decorator decorator}. * * @param {string} name The name of the instance. * @param {*} value The value. * @returns {Object} registered provider instance * * @example * Here are some examples of creating value services. * ```js * $provide.value('ADMIN_USER', 'admin'); * * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); * * $provide.value('halfOf', function(value) { * return value / 2; * }); * ``` */ /** * @ngdoc method * @name $provide#constant * @description * * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not * possible to inject other services into a constant. * * But unlike {@link auto.$provide#value value}, a constant can be * injected into a module configuration function (see {@link angular.Module#config}) and it cannot * be overridden by an Angular {@link auto.$provide#decorator decorator}. * * @param {string} name The name of the constant. * @param {*} value The constant value. * @returns {Object} registered instance * * @example * Here a some examples of creating constants: * ```js * $provide.constant('SHARD_HEIGHT', 306); * * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); * * $provide.constant('double', function(value) { * return value * 2; * }); * ``` */ /** * @ngdoc method * @name $provide#decorator * @description * * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function * intercepts the creation of a service, allowing it to override or modify the behavior of the * service. The return value of the decorator function may be the original service, or a new service * that replaces (or wraps and delegates to) the original service. * * You can find out more about using decorators in the {@link guide/decorators} guide. * * @param {string} name The name of the service to decorate. * @param {Function|Array.} decorator This function will be invoked when the service needs to be * provided and should return the decorated service instance. The function is called using * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. * Local injection arguments: * * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured, * decorated or delegated to. * * @example * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting * calls to {@link ng.$log#error $log.warn()}. * ```js * $provide.decorator('$log', ['$delegate', function($delegate) { * $delegate.warn = $delegate.error; * return $delegate; * }]); * ``` */ function createInjector(modulesToLoad, strictDi) { strictDi = (strictDi === true); var INSTANTIATING = {}, providerSuffix = 'Provider', path = [], loadedModules = new HashMap([], true), providerCache = { $provide: { provider: supportObject(provider), factory: supportObject(factory), service: supportObject(service), value: supportObject(value), constant: supportObject(constant), decorator: decorator } }, providerInjector = (providerCache.$injector = createInternalInjector(providerCache, function(serviceName, caller) { if (angular.isString(caller)) { path.push(caller); } throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- ')); })), instanceCache = {}, protoInstanceInjector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke( provider.$get, provider, undefined, serviceName); }), instanceInjector = protoInstanceInjector; providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; var runBlocks = loadModules(modulesToLoad); instanceInjector = protoInstanceInjector.get('$injector'); instanceInjector.strictDi = strictDi; forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); return instanceInjector; //////////////////////////////////// // $provider //////////////////////////////////// function supportObject(delegate) { return function(key, value) { if (isObject(key)) { forEach(key, reverseParams(delegate)); } else { return delegate(key, value); } }; } function provider(name, provider_) { assertNotHasOwnProperty(name, 'service'); if (isFunction(provider_) || isArray(provider_)) { provider_ = providerInjector.instantiate(provider_); } if (!provider_.$get) { throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name); } return (providerCache[name + providerSuffix] = provider_); } function enforceReturnValue(name, factory) { return /** @this */ function enforcedReturnValue() { var result = instanceInjector.invoke(factory, this); if (isUndefined(result)) { throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name); } return result; }; } function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn }); } function service(name, constructor) { return factory(name, ['$injector', function($injector) { return $injector.instantiate(constructor); }]); } function value(name, val) { return factory(name, valueFn(val), false); } function constant(name, value) { assertNotHasOwnProperty(name, 'constant'); providerCache[name] = value; instanceCache[name] = value; } function decorator(serviceName, decorFn) { var origProvider = providerInjector.get(serviceName + providerSuffix), orig$get = origProvider.$get; origProvider.$get = function() { var origInstance = instanceInjector.invoke(orig$get, origProvider); return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); }; } //////////////////////////////////// // Module Loading //////////////////////////////////// function loadModules(modulesToLoad) { assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); var runBlocks = [], moduleFn; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; loadedModules.put(module, true); function runInvokeQueue(queue) { var i, ii; for (i = 0, ii = queue.length; i < ii; i++) { var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } } try { if (isString(module)) { moduleFn = angularModule(module); runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); runInvokeQueue(moduleFn._invokeQueue); runInvokeQueue(moduleFn._configBlocks); } else if (isFunction(module)) { runBlocks.push(providerInjector.invoke(module)); } else if (isArray(module)) { runBlocks.push(providerInjector.invoke(module)); } else { assertArgFn(module, 'module'); } } catch (e) { if (isArray(module)) { module = module[module.length - 1]; } if (e.message && e.stack && e.stack.indexOf(e.message) === -1) { // Safari & FF's stack traces don't contain error.message content // unlike those of Chrome and IE // So if stack doesn't contain message, we create a new string that contains both. // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. // eslint-disable-next-line no-ex-assign e = e.message + '\n' + e.stack; } throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', module, e.stack || e.message || e); } }); return runBlocks; } //////////////////////////////////// // internal Injector //////////////////////////////////// function createInternalInjector(cache, factory) { function getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', serviceName + ' <- ' + path.join(' <- ')); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; cache[serviceName] = factory(serviceName, caller); return cache[serviceName]; } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } } function injectionArgs(fn, locals, serviceName) { var args = [], $inject = createInjector.$$annotate(fn, strictDi, serviceName); for (var i = 0, length = $inject.length; i < length; i++) { var key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); } args.push(locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName)); } return args; } function isClass(func) { // IE 9-11 do not support classes and IE9 leaks with the code below. if (msie <= 11) { return false; } // Support: Edge 12-13 only // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/ return typeof func === 'function' && /^(?:class\b|constructor\()/.test(stringifyFn(func)); } function invoke(fn, self, locals, serviceName) { if (typeof locals === 'string') { serviceName = locals; locals = null; } var args = injectionArgs(fn, locals, serviceName); if (isArray(fn)) { fn = fn[fn.length - 1]; } if (!isClass(fn)) { // http://jsperf.com/angularjs-invoke-apply-vs-switch // #5388 return fn.apply(self, args); } else { args.unshift(null); return new (Function.prototype.bind.apply(fn, args))(); } } function instantiate(Type, locals, serviceName) { // Check if Type is annotated and use just the given function at n-1 as parameter // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); var args = injectionArgs(Type, locals, serviceName); // Empty object at position 0 is ignored for invocation with `new`, but required. args.unshift(null); return new (Function.prototype.bind.apply(ctor, args))(); } return { invoke: invoke, instantiate: instantiate, get: getService, annotate: createInjector.$$annotate, has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } }; } } createInjector.$$annotate = annotate; /** * @ngdoc provider * @name $anchorScrollProvider * @this * * @description * Use `$anchorScrollProvider` to disable automatic scrolling whenever * {@link ng.$location#hash $location.hash()} changes. */ function $AnchorScrollProvider() { var autoScrollingEnabled = true; /** * @ngdoc method * @name $anchorScrollProvider#disableAutoScrolling * * @description * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.
          * Use this method to disable automatic scrolling. * * If automatic scrolling is disabled, one must explicitly call * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the * current hash. */ this.disableAutoScrolling = function() { autoScrollingEnabled = false; }; /** * @ngdoc service * @name $anchorScroll * @kind function * @requires $window * @requires $location * @requires $rootScope * * @description * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified * in the * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document). * * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to * match any anchor whenever it changes. This can be disabled by calling * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. * * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a * vertical scroll-offset (either fixed or dynamic). * * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of * {@link ng.$location#hash $location.hash()} will be used. * * @property {(number|function|jqLite)} yOffset * If set, specifies a vertical scroll-offset. This is often useful when there are fixed * positioned elements at the top of the page, such as navbars, headers etc. * * `yOffset` can be specified in various ways: * - **number**: A fixed number of pixels to be used as offset.

          * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return * a number representing the offset (in pixels).

          * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from * the top of the page to the element's bottom will be used as offset.
          * **Note**: The element will be taken into account only as long as its `position` is set to * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust * their height and/or positioning according to the viewport's size. * *
          *
          * In order for `yOffset` to work properly, scrolling should take place on the document's root and * not some child element. *
          * * @example
          Go to bottom You're at the bottom!
          angular.module('anchorScrollExample', []) .controller('ScrollController', ['$scope', '$location', '$anchorScroll', function($scope, $location, $anchorScroll) { $scope.gotoBottom = function() { // set the location.hash to the id of // the element you wish to scroll to. $location.hash('bottom'); // call $anchorScroll() $anchorScroll(); }; }]); #scrollArea { height: 280px; overflow: auto; } #bottom { display: block; margin-top: 2000px; }
          * *
          * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. * * @example
          Go to anchor {{x}}
          Anchor {{x}} of 5
          angular.module('anchorScrollOffsetExample', []) .run(['$anchorScroll', function($anchorScroll) { $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels }]) .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', function($anchorScroll, $location, $scope) { $scope.gotoAnchor = function(x) { var newHash = 'anchor' + x; if ($location.hash() !== newHash) { // set the $location.hash to `newHash` and // $anchorScroll will automatically scroll to it $location.hash('anchor' + x); } else { // call $anchorScroll() explicitly, // since $location.hash hasn't changed $anchorScroll(); } }; } ]); body { padding-top: 50px; } .anchor { border: 2px dashed DarkOrchid; padding: 10px 10px 200px 10px; } .fixed-header { background-color: rgba(0, 0, 0, 0.2); height: 50px; position: fixed; top: 0; left: 0; right: 0; } .fixed-header > a { display: inline-block; margin: 5px 15px; }
          */ this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { var document = $window.document; // Helper function to get first anchor from a NodeList // (using `Array#some()` instead of `angular#forEach()` since it's more performant // and working in all supported browsers.) function getFirstAnchor(list) { var result = null; Array.prototype.some.call(list, function(element) { if (nodeName_(element) === 'a') { result = element; return true; } }); return result; } function getYOffset() { var offset = scroll.yOffset; if (isFunction(offset)) { offset = offset(); } else if (isElement(offset)) { var elem = offset[0]; var style = $window.getComputedStyle(elem); if (style.position !== 'fixed') { offset = 0; } else { offset = elem.getBoundingClientRect().bottom; } } else if (!isNumber(offset)) { offset = 0; } return offset; } function scrollTo(elem) { if (elem) { elem.scrollIntoView(); var offset = getYOffset(); if (offset) { // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the // top of the viewport. // // IF the number of pixels from the top of `elem` to the end of the page's content is less // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some // way down the page. // // This is often the case for elements near the bottom of the page. // // In such cases we do not need to scroll the whole `offset` up, just the difference between // the top of the element and the offset, which is enough to align the top of `elem` at the // desired position. var elemTop = elem.getBoundingClientRect().top; $window.scrollBy(0, elemTop - offset); } } else { $window.scrollTo(0, 0); } } function scroll(hash) { // Allow numeric hashes hash = isString(hash) ? hash : isNumber(hash) ? hash.toString() : $location.hash(); var elm; // empty hash, scroll to the top of the page if (!hash) scrollTo(null); // element with given id else if ((elm = document.getElementById(hash))) scrollTo(elm); // first anchor with given name :-D else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); // no element and hash === 'top', scroll to the top of the page else if (hash === 'top') scrollTo(null); } // does not scroll when user clicks on anchor link that is currently on // (no url change, no $location.hash() change), browser native does scroll if (autoScrollingEnabled) { $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, function autoScrollWatchAction(newVal, oldVal) { // skip the initial scroll if $location.hash is empty if (newVal === oldVal && newVal === '') return; jqLiteDocumentLoaded(function() { $rootScope.$evalAsync(scroll); }); }); } return scroll; }]; } var $animateMinErr = minErr('$animate'); var ELEMENT_NODE = 1; var NG_ANIMATE_CLASSNAME = 'ng-animate'; function mergeClasses(a,b) { if (!a && !b) return ''; if (!a) return b; if (!b) return a; if (isArray(a)) a = a.join(' '); if (isArray(b)) b = b.join(' '); return a + ' ' + b; } function extractElementNode(element) { for (var i = 0; i < element.length; i++) { var elm = element[i]; if (elm.nodeType === ELEMENT_NODE) { return elm; } } } function splitClasses(classes) { if (isString(classes)) { classes = classes.split(' '); } // Use createMap() to prevent class assumptions involving property names in // Object.prototype var obj = createMap(); forEach(classes, function(klass) { // sometimes the split leaves empty string values // incase extra spaces were applied to the options if (klass.length) { obj[klass] = true; } }); return obj; } // if any other type of options value besides an Object value is // passed into the $animate.method() animation then this helper code // will be run which will ignore it. While this patch is not the // greatest solution to this, a lot of existing plugins depend on // $animate to either call the callback (< 1.2) or return a promise // that can be changed. This helper function ensures that the options // are wiped clean incase a callback function is provided. function prepareAnimateOptions(options) { return isObject(options) ? options : {}; } var $$CoreAnimateJsProvider = /** @this */ function() { this.$get = noop; }; // this is prefixed with Core since it conflicts with // the animateQueueProvider defined in ngAnimate/animateQueue.js var $$CoreAnimateQueueProvider = /** @this */ function() { var postDigestQueue = new HashMap(); var postDigestElements = []; this.$get = ['$$AnimateRunner', '$rootScope', function($$AnimateRunner, $rootScope) { return { enabled: noop, on: noop, off: noop, pin: noop, push: function(element, event, options, domOperation) { if (domOperation) { domOperation(); } options = options || {}; if (options.from) { element.css(options.from); } if (options.to) { element.css(options.to); } if (options.addClass || options.removeClass) { addRemoveClassesPostDigest(element, options.addClass, options.removeClass); } var runner = new $$AnimateRunner(); // since there are no animations to run the runner needs to be // notified that the animation call is complete. runner.complete(); return runner; } }; function updateData(data, classes, value) { var changed = false; if (classes) { classes = isString(classes) ? classes.split(' ') : isArray(classes) ? classes : []; forEach(classes, function(className) { if (className) { changed = true; data[className] = value; } }); } return changed; } function handleCSSClassChanges() { forEach(postDigestElements, function(element) { var data = postDigestQueue.get(element); if (data) { var existing = splitClasses(element.attr('class')); var toAdd = ''; var toRemove = ''; forEach(data, function(status, className) { var hasClass = !!existing[className]; if (status !== hasClass) { if (status) { toAdd += (toAdd.length ? ' ' : '') + className; } else { toRemove += (toRemove.length ? ' ' : '') + className; } } }); forEach(element, function(elm) { if (toAdd) { jqLiteAddClass(elm, toAdd); } if (toRemove) { jqLiteRemoveClass(elm, toRemove); } }); postDigestQueue.remove(element); } }); postDigestElements.length = 0; } function addRemoveClassesPostDigest(element, add, remove) { var data = postDigestQueue.get(element) || {}; var classesAdded = updateData(data, add, true); var classesRemoved = updateData(data, remove, false); if (classesAdded || classesRemoved) { postDigestQueue.put(element, data); postDigestElements.push(element); if (postDigestElements.length === 1) { $rootScope.$$postDigest(handleCSSClassChanges); } } } }]; }; /** * @ngdoc provider * @name $animateProvider * * @description * Default implementation of $animate that doesn't perform any animations, instead just * synchronously performs DOM updates and resolves the returned runner promise. * * In order to enable animations the `ngAnimate` module has to be loaded. * * To see the functional implementation check out `src/ngAnimate/animate.js`. */ var $AnimateProvider = ['$provide', /** @this */ function($provide) { var provider = this; this.$$registeredAnimations = Object.create(null); /** * @ngdoc method * @name $animateProvider#register * * @description * Registers a new injectable animation factory function. The factory function produces the * animation object which contains callback functions for each event that is expected to be * animated. * * * `eventFn`: `function(element, ... , doneFunction, options)` * The element to animate, the `doneFunction` and the options fed into the animation. Depending * on the type of animation additional arguments will be injected into the animation function. The * list below explains the function signatures for the different animation methods: * * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) * - addClass: function(element, addedClasses, doneFunction, options) * - removeClass: function(element, removedClasses, doneFunction, options) * - enter, leave, move: function(element, doneFunction, options) * - animate: function(element, fromStyles, toStyles, doneFunction, options) * * Make sure to trigger the `doneFunction` once the animation is fully complete. * * ```js * return { * //enter, leave, move signature * eventFn : function(element, done, options) { * //code to run the animation * //once complete, then run done() * return function endFunction(wasCancelled) { * //code to cancel the animation * } * } * } * ``` * * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). * @param {Function} factory The factory function that will be executed to return the animation * object. */ this.register = function(name, factory) { if (name && name.charAt(0) !== '.') { throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name); } var key = name + '-animation'; provider.$$registeredAnimations[name.substr(1)] = key; $provide.factory(key, factory); }; /** * @ngdoc method * @name $animateProvider#classNameFilter * * @description * Sets and/or returns the CSS class regular expression that is checked when performing * an animation. Upon bootstrap the classNameFilter value is not set at all and will * therefore enable $animate to attempt to perform an animation on any element that is triggered. * When setting the `classNameFilter` value, animations will only be performed on elements * that successfully match the filter expression. This in turn can boost performance * for low-powered devices as well as applications containing a lot of structural operations. * @param {RegExp=} expression The className expression which will be checked against all animations * @return {RegExp} The current CSS className expression value. If null then there is no expression value */ this.classNameFilter = function(expression) { if (arguments.length === 1) { this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; if (this.$$classNameFilter) { var reservedRegex = new RegExp('(\\s+|\\/)' + NG_ANIMATE_CLASSNAME + '(\\s+|\\/)'); if (reservedRegex.test(this.$$classNameFilter.toString())) { throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); } } } return this.$$classNameFilter; }; this.$get = ['$$animateQueue', function($$animateQueue) { function domInsert(element, parentElement, afterElement) { // if for some reason the previous element was removed // from the dom sometime before this code runs then let's // just stick to using the parent element as the anchor if (afterElement) { var afterNode = extractElementNode(afterElement); if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { afterElement = null; } } if (afterElement) { afterElement.after(element); } else { parentElement.prepend(element); } } /** * @ngdoc service * @name $animate * @description The $animate service exposes a series of DOM utility methods that provide support * for animation hooks. The default behavior is the application of DOM operations, however, * when an animation is detected (and animations are enabled), $animate will do the heavy lifting * to ensure that animation runs with the triggered DOM operation. * * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't * included and only when it is active then the animation hooks that `$animate` triggers will be * functional. Once active then all structural `ng-` directives will trigger animations as they perform * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. * * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. * * To learn more about enabling animation support, click here to visit the * {@link ngAnimate ngAnimate module page}. */ return { // we don't call it directly since non-existant arguments may // be interpreted as null within the sub enabled function /** * * @ngdoc method * @name $animate#on * @kind function * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) * has fired on the given element or among any of its children. Once the listener is fired, the provided callback * is fired with the following params: * * ```js * $animate.on('enter', container, * function callback(element, phase) { * // cool we detected an enter animation within the container * } * ); * ``` * * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself * as well as among its children * @param {Function} callback the callback function that will be fired when the listener is triggered * * The arguments present in the callback function are: * * `element` - The captured DOM element that the animation was fired on. * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). */ on: $$animateQueue.on, /** * * @ngdoc method * @name $animate#off * @kind function * @description Deregisters an event listener based on the event which has been associated with the provided element. This method * can be used in three different ways depending on the arguments: * * ```js * // remove all the animation event listeners listening for `enter` * $animate.off('enter'); * * // remove listeners for all animation events from the container element * $animate.off(container); * * // remove all the animation event listeners listening for `enter` on the given element and its children * $animate.off('enter', container); * * // remove the event listener function provided by `callback` that is set * // to listen for `enter` on the given `container` as well as its children * $animate.off('enter', container, callback); * ``` * * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, * addClass, removeClass, etc...), or the container element. If it is the element, all other * arguments are ignored. * @param {DOMElement=} container the container element the event listener was placed on * @param {Function=} callback the callback function that was registered as the listener */ off: $$animateQueue.off, /** * @ngdoc method * @name $animate#pin * @kind function * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the * element despite being outside the realm of the application or within another application. Say for example if the application * was bootstrapped on an element that is somewhere inside of the `` tag, but we wanted to allow for an element to be situated * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. * * Note that this feature is only active when the `ngAnimate` module is used. * * @param {DOMElement} element the external element that will be pinned * @param {DOMElement} parentElement the host parent element that will be associated with the external element */ pin: $$animateQueue.pin, /** * * @ngdoc method * @name $animate#enabled * @kind function * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This * function can be called in four ways: * * ```js * // returns true or false * $animate.enabled(); * * // changes the enabled state for all animations * $animate.enabled(false); * $animate.enabled(true); * * // returns true or false if animations are enabled for an element * $animate.enabled(element); * * // changes the enabled state for an element and its children * $animate.enabled(element, true); * $animate.enabled(element, false); * ``` * * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state * @param {boolean=} enabled whether or not the animations will be enabled for the element * * @return {boolean} whether or not animations are enabled */ enabled: $$animateQueue.enabled, /** * @ngdoc method * @name $animate#cancel * @kind function * @description Cancels the provided animation. * * @param {Promise} animationPromise The animation promise that is returned when an animation is started. */ cancel: function(runner) { if (runner.end) { runner.end(); } }, /** * * @ngdoc method * @name $animate#enter * @kind function * @description Inserts the element into the DOM either after the `after` element (if provided) or * as the first child within the `parent` element and then triggers an animation. * A promise is returned that will be resolved during the next digest once the animation * has completed. * * @param {DOMElement} element the element which will be inserted into the DOM * @param {DOMElement} parent the parent element which will append the element as * a child (so long as the after element is not present) * @param {DOMElement=} after the sibling element after which the element will be appended * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ enter: function(element, parent, after, options) { parent = parent && jqLite(parent); after = after && jqLite(after); parent = parent || after.parent(); domInsert(element, parent, after); return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); }, /** * * @ngdoc method * @name $animate#move * @kind function * @description Inserts (moves) the element into its new position in the DOM either after * the `after` element (if provided) or as the first child within the `parent` element * and then triggers an animation. A promise is returned that will be resolved * during the next digest once the animation has completed. * * @param {DOMElement} element the element which will be moved into the new DOM position * @param {DOMElement} parent the parent element which will append the element as * a child (so long as the after element is not present) * @param {DOMElement=} after the sibling element after which the element will be appended * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ move: function(element, parent, after, options) { parent = parent && jqLite(parent); after = after && jqLite(after); parent = parent || after.parent(); domInsert(element, parent, after); return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); }, /** * @ngdoc method * @name $animate#leave * @kind function * @description Triggers an animation and then removes the element from the DOM. * When the function is called a promise is returned that will be resolved during the next * digest once the animation has completed. * * @param {DOMElement} element the element which will be removed from the DOM * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ leave: function(element, options) { return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { element.remove(); }); }, /** * @ngdoc method * @name $animate#addClass * @kind function * * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon * execution, the addClass operation will only be handled after the next digest and it will not trigger an * animation if element already contains the CSS class or if the class is removed at a later step. * Note that class-based animations are treated differently compared to structural animations * (like enter, move and leave) since the CSS classes may be added/removed at different points * depending if CSS or JavaScript animations are used. * * @param {DOMElement} element the element which the CSS classes will be applied to * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ addClass: function(element, className, options) { options = prepareAnimateOptions(options); options.addClass = mergeClasses(options.addclass, className); return $$animateQueue.push(element, 'addClass', options); }, /** * @ngdoc method * @name $animate#removeClass * @kind function * * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon * execution, the removeClass operation will only be handled after the next digest and it will not trigger an * animation if element does not contain the CSS class or if the class is added at a later step. * Note that class-based animations are treated differently compared to structural animations * (like enter, move and leave) since the CSS classes may be added/removed at different points * depending if CSS or JavaScript animations are used. * * @param {DOMElement} element the element which the CSS classes will be applied to * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ removeClass: function(element, className, options) { options = prepareAnimateOptions(options); options.removeClass = mergeClasses(options.removeClass, className); return $$animateQueue.push(element, 'removeClass', options); }, /** * @ngdoc method * @name $animate#setClass * @kind function * * @description Performs both the addition and removal of a CSS classes on an element and (during the process) * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has * passed. Note that class-based animations are treated differently compared to structural animations * (like enter, move and leave) since the CSS classes may be added/removed at different points * depending if CSS or JavaScript animations are used. * * @param {DOMElement} element the element which the CSS classes will be applied to * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ setClass: function(element, add, remove, options) { options = prepareAnimateOptions(options); options.addClass = mergeClasses(options.addClass, add); options.removeClass = mergeClasses(options.removeClass, remove); return $$animateQueue.push(element, 'setClass', options); }, /** * @ngdoc method * @name $animate#animate * @kind function * * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding * style in `to`, the style in `from` is applied immediately, and no animation is run. * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` * method (or as part of the `options` parameter): * * ```js * ngModule.animation('.my-inline-animation', function() { * return { * animate : function(element, from, to, done, options) { * //animation * done(); * } * } * }); * ``` * * @param {DOMElement} element the element which the CSS styles will be applied to * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. * (Note that if no animation is detected then this value will not be applied to the element.) * @param {object=} options an optional collection of options/styles that will be applied to the element. * The object can have the following properties: * * - **addClass** - `{string}` - space-separated CSS classes to add to element * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` * - **removeClass** - `{string}` - space-separated CSS classes to remove from element * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` * * @return {Promise} the animation callback promise */ animate: function(element, from, to, className, options) { options = prepareAnimateOptions(options); options.from = options.from ? extend(options.from, from) : from; options.to = options.to ? extend(options.to, to) : to; className = className || 'ng-inline-animate'; options.tempClasses = mergeClasses(options.tempClasses, className); return $$animateQueue.push(element, 'animate', options); } }; }]; }]; var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { this.$get = ['$$rAF', function($$rAF) { var waitQueue = []; function waitForTick(fn) { waitQueue.push(fn); if (waitQueue.length > 1) return; $$rAF(function() { for (var i = 0; i < waitQueue.length; i++) { waitQueue[i](); } waitQueue = []; }); } return function() { var passed = false; waitForTick(function() { passed = true; }); return function(callback) { if (passed) { callback(); } else { waitForTick(callback); } }; }; }]; }; var $$AnimateRunnerFactoryProvider = /** @this */ function() { this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { var INITIAL_STATE = 0; var DONE_PENDING_STATE = 1; var DONE_COMPLETE_STATE = 2; AnimateRunner.chain = function(chain, callback) { var index = 0; next(); function next() { if (index === chain.length) { callback(true); return; } chain[index](function(response) { if (response === false) { callback(false); return; } index++; next(); }); } }; AnimateRunner.all = function(runners, callback) { var count = 0; var status = true; forEach(runners, function(runner) { runner.done(onProgress); }); function onProgress(response) { status = status && response; if (++count === runners.length) { callback(status); } } }; function AnimateRunner(host) { this.setHost(host); var rafTick = $$animateAsyncRun(); var timeoutTick = function(fn) { $timeout(fn, 0, false); }; this._doneCallbacks = []; this._tick = function(fn) { var doc = $document[0]; // the document may not be ready or attached // to the module for some internal tests if (doc && doc.hidden) { timeoutTick(fn); } else { rafTick(fn); } }; this._state = 0; } AnimateRunner.prototype = { setHost: function(host) { this.host = host || {}; }, done: function(fn) { if (this._state === DONE_COMPLETE_STATE) { fn(); } else { this._doneCallbacks.push(fn); } }, progress: noop, getPromise: function() { if (!this.promise) { var self = this; this.promise = $q(function(resolve, reject) { self.done(function(status) { if (status === false) { reject(); } else { resolve(); } }); }); } return this.promise; }, then: function(resolveHandler, rejectHandler) { return this.getPromise().then(resolveHandler, rejectHandler); }, 'catch': function(handler) { return this.getPromise()['catch'](handler); }, 'finally': function(handler) { return this.getPromise()['finally'](handler); }, pause: function() { if (this.host.pause) { this.host.pause(); } }, resume: function() { if (this.host.resume) { this.host.resume(); } }, end: function() { if (this.host.end) { this.host.end(); } this._resolve(true); }, cancel: function() { if (this.host.cancel) { this.host.cancel(); } this._resolve(false); }, complete: function(response) { var self = this; if (self._state === INITIAL_STATE) { self._state = DONE_PENDING_STATE; self._tick(function() { self._resolve(response); }); } }, _resolve: function(response) { if (this._state !== DONE_COMPLETE_STATE) { forEach(this._doneCallbacks, function(fn) { fn(response); }); this._doneCallbacks.length = 0; this._state = DONE_COMPLETE_STATE; } } }; return AnimateRunner; }]; }; /* exported $CoreAnimateCssProvider */ /** * @ngdoc service * @name $animateCss * @kind object * @this * * @description * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, * then the `$animateCss` service will actually perform animations. * * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. */ var $CoreAnimateCssProvider = function() { this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { return function(element, initialOptions) { // all of the animation functions should create // a copy of the options data, however, if a // parent service has already created a copy then // we should stick to using that var options = initialOptions || {}; if (!options.$$prepared) { options = copy(options); } // there is no point in applying the styles since // there is no animation that goes on at all in // this version of $animateCss. if (options.cleanupStyles) { options.from = options.to = null; } if (options.from) { element.css(options.from); options.from = null; } var closed, runner = new $$AnimateRunner(); return { start: run, end: run }; function run() { $$rAF(function() { applyAnimationContents(); if (!closed) { runner.complete(); } closed = true; }); return runner; } function applyAnimationContents() { if (options.addClass) { element.addClass(options.addClass); options.addClass = null; } if (options.removeClass) { element.removeClass(options.removeClass); options.removeClass = null; } if (options.to) { element.css(options.to); options.to = null; } } }; }]; }; /* global stripHash: true */ /** * ! This is a private undocumented service ! * * @name $browser * @requires $log * @description * This object has two goals: * * - hide all the global state in the browser caused by the window object * - abstract away all the browser specific features and inconsistencies * * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` * service, which can be used for convenient testing of the application without the interaction with * the real browser apis. */ /** * @param {object} window The global window object. * @param {object} document jQuery wrapped document. * @param {object} $log window.console or an object with the same interface. * @param {object} $sniffer $sniffer service */ function Browser(window, document, $log, $sniffer) { var self = this, location = window.location, history = window.history, setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, pendingDeferIds = {}; self.isMock = false; var outstandingRequestCount = 0; var outstandingRequestCallbacks = []; // TODO(vojta): remove this temporary api self.$$completeOutstandingRequest = completeOutstandingRequest; self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; /** * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. */ function completeOutstandingRequest(fn) { try { fn.apply(null, sliceArgs(arguments, 1)); } finally { outstandingRequestCount--; if (outstandingRequestCount === 0) { while (outstandingRequestCallbacks.length) { try { outstandingRequestCallbacks.pop()(); } catch (e) { $log.error(e); } } } } } function getHash(url) { var index = url.indexOf('#'); return index === -1 ? '' : url.substr(index); } /** * @private * Note: this method is used only by scenario runner * TODO(vojta): prefix this method with $$ ? * @param {function()} callback Function that will be called when no outstanding request */ self.notifyWhenNoOutstandingRequests = function(callback) { if (outstandingRequestCount === 0) { callback(); } else { outstandingRequestCallbacks.push(callback); } }; ////////////////////////////////////////////////////////////// // URL API ////////////////////////////////////////////////////////////// var cachedState, lastHistoryState, lastBrowserUrl = location.href, baseElement = document.find('base'), pendingLocation = null, getCurrentState = !$sniffer.history ? noop : function getCurrentState() { try { return history.state; } catch (e) { // MSIE can reportedly throw when there is no state (UNCONFIRMED). } }; cacheState(); lastHistoryState = cachedState; /** * @name $browser#url * * @description * GETTER: * Without any argument, this method just returns current value of location.href. * * SETTER: * With at least one argument, this method sets url to new value. * If html5 history api supported, pushState/replaceState is used, otherwise * location.href/location.replace is used. * Returns its own instance to allow chaining * * NOTE: this api is intended for use only by the $location service. Please use the * {@link ng.$location $location service} to change url. * * @param {string} url New url (when used as setter) * @param {boolean=} replace Should new url replace current history record? * @param {object=} state object to use with pushState/replaceState */ self.url = function(url, replace, state) { // In modern browsers `history.state` is `null` by default; treating it separately // from `undefined` would cause `$browser.url('/foo')` to change `history.state` // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. if (isUndefined(state)) { state = null; } // Android Browser BFCache causes location, history reference to become stale. if (location !== window.location) location = window.location; if (history !== window.history) history = window.history; // setter if (url) { var sameState = lastHistoryState === state; // Don't change anything if previous and current URLs and states match. This also prevents // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. // See https://github.com/angular/angular.js/commit/ffb2701 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { return self; } var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); lastBrowserUrl = url; lastHistoryState = state; // Don't use history API if only the hash changed // due to a bug in IE10/IE11 which leads // to not firing a `hashchange` nor `popstate` event // in some cases (see #9143). if ($sniffer.history && (!sameBase || !sameState)) { history[replace ? 'replaceState' : 'pushState'](state, '', url); cacheState(); // Do the assignment again so that those two variables are referentially identical. lastHistoryState = cachedState; } else { if (!sameBase) { pendingLocation = url; } if (replace) { location.replace(url); } else if (!sameBase) { location.href = url; } else { location.hash = getHash(url); } if (location.href !== url) { pendingLocation = url; } } if (pendingLocation) { pendingLocation = url; } return self; // getter } else { // - pendingLocation is needed as browsers don't allow to read out // the new location.href if a reload happened or if there is a bug like in iOS 9 (see // https://openradar.appspot.com/22186109). // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 return pendingLocation || location.href.replace(/%27/g,'\''); } }; /** * @name $browser#state * * @description * This method is a getter. * * Return history.state or null if history.state is undefined. * * @returns {object} state */ self.state = function() { return cachedState; }; var urlChangeListeners = [], urlChangeInit = false; function cacheStateAndFireUrlChange() { pendingLocation = null; cacheState(); fireUrlChange(); } // This variable should be used *only* inside the cacheState function. var lastCachedState = null; function cacheState() { // This should be the only place in $browser where `history.state` is read. cachedState = getCurrentState(); cachedState = isUndefined(cachedState) ? null : cachedState; // Prevent callbacks fo fire twice if both hashchange & popstate were fired. if (equals(cachedState, lastCachedState)) { cachedState = lastCachedState; } lastCachedState = cachedState; } function fireUrlChange() { if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { return; } lastBrowserUrl = self.url(); lastHistoryState = cachedState; forEach(urlChangeListeners, function(listener) { listener(self.url(), cachedState); }); } /** * @name $browser#onUrlChange * * @description * Register callback function that will be called, when url changes. * * It's only called when the url is changed from outside of angular: * - user types different url into address bar * - user clicks on history (forward/back) button * - user clicks on a link * * It's not called when url is changed by $browser.url() method * * The listener gets called with new url as parameter. * * NOTE: this api is intended for use only by the $location service. Please use the * {@link ng.$location $location service} to monitor url changes in angular apps. * * @param {function(string)} listener Listener function to be called when url changes. * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. */ self.onUrlChange = function(callback) { // TODO(vojta): refactor to use node's syntax for events if (!urlChangeInit) { // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) // don't fire popstate when user change the address bar and don't fire hashchange when url // changed by push/replaceState // html5 history api - popstate event if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); // hashchange event jqLite(window).on('hashchange', cacheStateAndFireUrlChange); urlChangeInit = true; } urlChangeListeners.push(callback); return callback; }; /** * @private * Remove popstate and hashchange handler from window. * * NOTE: this api is intended for use only by $rootScope. */ self.$$applicationDestroyed = function() { jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); }; /** * Checks whether the url has changed outside of Angular. * Needs to be exported to be able to check for changes that have been done in sync, * as hashchange/popstate events fire in async. */ self.$$checkUrlChange = fireUrlChange; ////////////////////////////////////////////////////////////// // Misc API ////////////////////////////////////////////////////////////// /** * @name $browser#baseHref * * @description * Returns current * (always relative - without domain) * * @returns {string} The current base href */ self.baseHref = function() { var href = baseElement.attr('href'); return href ? href.replace(/^(https?:)?\/\/[^/]*/, '') : ''; }; /** * @name $browser#defer * @param {function()} fn A function, who's execution should be deferred. * @param {number=} [delay=0] of milliseconds to defer the function execution. * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. * * @description * Executes a fn asynchronously via `setTimeout(fn, delay)`. * * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed * via `$browser.defer.flush()`. * */ self.defer = function(fn, delay) { var timeoutId; outstandingRequestCount++; timeoutId = setTimeout(function() { delete pendingDeferIds[timeoutId]; completeOutstandingRequest(fn); }, delay || 0); pendingDeferIds[timeoutId] = true; return timeoutId; }; /** * @name $browser#defer.cancel * * @description * Cancels a deferred task identified with `deferId`. * * @param {*} deferId Token returned by the `$browser.defer` function. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully * canceled. */ self.defer.cancel = function(deferId) { if (pendingDeferIds[deferId]) { delete pendingDeferIds[deferId]; clearTimeout(deferId); completeOutstandingRequest(noop); return true; } return false; }; } /** @this */ function $BrowserProvider() { this.$get = ['$window', '$log', '$sniffer', '$document', function($window, $log, $sniffer, $document) { return new Browser($window, $document, $log, $sniffer); }]; } /** * @ngdoc service * @name $cacheFactory * @this * * @description * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to * them. * * ```js * * var cache = $cacheFactory('cacheId'); * expect($cacheFactory.get('cacheId')).toBe(cache); * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); * * cache.put("key", "value"); * cache.put("another key", "another value"); * * // We've specified no options on creation * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); * * ``` * * * @param {string} cacheId Name or id of the newly created cache. * @param {object=} options Options object that specifies the cache behavior. Properties: * * - `{number=}` `capacity` — turns the cache into LRU cache. * * @returns {object} Newly created cache object with the following set of methods: * * - `{object}` `info()` — Returns id, size, and options of cache. * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns * it. * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. * - `{void}` `removeAll()` — Removes all cached values. * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. * * @example

          Cached Values

          :

          Cache Info

          :
          angular.module('cacheExampleApp', []). controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { $scope.keys = []; $scope.cache = $cacheFactory('cacheId'); $scope.put = function(key, value) { if (angular.isUndefined($scope.cache.get(key))) { $scope.keys.push(key); } $scope.cache.put(key, angular.isUndefined(value) ? null : value); }; }]); p { margin: 10px 0 3px; }
          */ function $CacheFactoryProvider() { this.$get = function() { var caches = {}; function cacheFactory(cacheId, options) { if (cacheId in caches) { throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId); } var size = 0, stats = extend({}, options, {id: cacheId}), data = createMap(), capacity = (options && options.capacity) || Number.MAX_VALUE, lruHash = createMap(), freshEnd = null, staleEnd = null; /** * @ngdoc type * @name $cacheFactory.Cache * * @description * A cache object used to store and retrieve data, primarily used by * {@link $http $http} and the {@link ng.directive:script script} directive to cache * templates and other data. * * ```js * angular.module('superCache') * .factory('superCache', ['$cacheFactory', function($cacheFactory) { * return $cacheFactory('super-cache'); * }]); * ``` * * Example test: * * ```js * it('should behave like a cache', inject(function(superCache) { * superCache.put('key', 'value'); * superCache.put('another key', 'another value'); * * expect(superCache.info()).toEqual({ * id: 'super-cache', * size: 2 * }); * * superCache.remove('another key'); * expect(superCache.get('another key')).toBeUndefined(); * * superCache.removeAll(); * expect(superCache.info()).toEqual({ * id: 'super-cache', * size: 0 * }); * })); * ``` */ return (caches[cacheId] = { /** * @ngdoc method * @name $cacheFactory.Cache#put * @kind function * * @description * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be * retrieved later, and incrementing the size of the cache if the key was not already * present in the cache. If behaving like an LRU cache, it will also remove stale * entries from the set. * * It will not insert undefined values into the cache. * * @param {string} key the key under which the cached data is stored. * @param {*} value the value to store alongside the key. If it is undefined, the key * will not be stored. * @returns {*} the value stored. */ put: function(key, value) { if (isUndefined(value)) return; if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); refresh(lruEntry); } if (!(key in data)) size++; data[key] = value; if (size > capacity) { this.remove(staleEnd.key); } return value; }, /** * @ngdoc method * @name $cacheFactory.Cache#get * @kind function * * @description * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. * * @param {string} key the key of the data to be retrieved * @returns {*} the value stored. */ get: function(key) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key]; if (!lruEntry) return; refresh(lruEntry); } return data[key]; }, /** * @ngdoc method * @name $cacheFactory.Cache#remove * @kind function * * @description * Removes an entry from the {@link $cacheFactory.Cache Cache} object. * * @param {string} key the key of the entry to be removed */ remove: function(key) { if (capacity < Number.MAX_VALUE) { var lruEntry = lruHash[key]; if (!lruEntry) return; if (lruEntry === freshEnd) freshEnd = lruEntry.p; if (lruEntry === staleEnd) staleEnd = lruEntry.n; link(lruEntry.n,lruEntry.p); delete lruHash[key]; } if (!(key in data)) return; delete data[key]; size--; }, /** * @ngdoc method * @name $cacheFactory.Cache#removeAll * @kind function * * @description * Clears the cache object of any entries. */ removeAll: function() { data = createMap(); size = 0; lruHash = createMap(); freshEnd = staleEnd = null; }, /** * @ngdoc method * @name $cacheFactory.Cache#destroy * @kind function * * @description * Destroys the {@link $cacheFactory.Cache Cache} object entirely, * removing it from the {@link $cacheFactory $cacheFactory} set. */ destroy: function() { data = null; stats = null; lruHash = null; delete caches[cacheId]; }, /** * @ngdoc method * @name $cacheFactory.Cache#info * @kind function * * @description * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. * * @returns {object} an object with the following properties: *
            *
          • **id**: the id of the cache instance
          • *
          • **size**: the number of entries kept in the cache instance
          • *
          • **...**: any additional properties from the options object when creating the * cache.
          • *
          */ info: function() { return extend({}, stats, {size: size}); } }); /** * makes the `entry` the freshEnd of the LRU linked list */ function refresh(entry) { if (entry !== freshEnd) { if (!staleEnd) { staleEnd = entry; } else if (staleEnd === entry) { staleEnd = entry.n; } link(entry.n, entry.p); link(entry, freshEnd); freshEnd = entry; freshEnd.n = null; } } /** * bidirectionally links two entries of the LRU linked list */ function link(nextEntry, prevEntry) { if (nextEntry !== prevEntry) { if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify } } } /** * @ngdoc method * @name $cacheFactory#info * * @description * Get information about all the caches that have been created * * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` */ cacheFactory.info = function() { var info = {}; forEach(caches, function(cache, cacheId) { info[cacheId] = cache.info(); }); return info; }; /** * @ngdoc method * @name $cacheFactory#get * * @description * Get access to a cache object by the `cacheId` used when it was created. * * @param {string} cacheId Name or id of a cache to access. * @returns {object} Cache object identified by the cacheId or undefined if no such cache. */ cacheFactory.get = function(cacheId) { return caches[cacheId]; }; return cacheFactory; }; } /** * @ngdoc service * @name $templateCache * @this * * @description * The first time a template is used, it is loaded in the template cache for quick retrieval. You * can load templates directly into the cache in a `script` tag, or by consuming the * `$templateCache` service directly. * * Adding via the `script` tag: * * ```html * * ``` * * **Note:** the `script` tag containing the template does not need to be included in the `head` of * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, * element with ng-app attribute), otherwise the template will be ignored. * * Adding via the `$templateCache` service: * * ```js * var myApp = angular.module('myApp', []); * myApp.run(function($templateCache) { * $templateCache.put('templateId.html', 'This is the content of the template'); * }); * ``` * * To retrieve the template later, simply use it in your component: * ```js * myApp.component('myComponent', { * templateUrl: 'templateId.html' * }); * ``` * * or get it via the `$templateCache` service: * ```js * $templateCache.get('templateId.html') * ``` * * See {@link ng.$cacheFactory $cacheFactory}. * */ function $TemplateCacheProvider() { this.$get = ['$cacheFactory', function($cacheFactory) { return $cacheFactory('templates'); }]; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables like document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: * * - "node" - DOM Node * - "element" - DOM Element or Node * - "$node" or "$element" - jqLite-wrapped node or element * * * Compiler related stuff: * * - "linkFn" - linking fn of a single directive * - "nodeLinkFn" - function that aggregates all linking fns for a particular node * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) */ /** * @ngdoc service * @name $compile * @kind function * * @description * Compiles an HTML string or DOM into a template and produces a template function, which * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. * * The compilation is a process of walking the DOM tree and matching DOM elements to * {@link ng.$compileProvider#directive directives}. * *
          * **Note:** This document is an in-depth reference of all directive options. * For a gentle introduction to directives with examples of common use cases, * see the {@link guide/directive directive guide}. *
          * * ## Comprehensive Directive API * * There are many different options for a directive. * * The difference resides in the return value of the factory function. * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)} * that defines the directive properties, or just the `postLink` function (all other properties will have * the default values). * *
          * **Best Practice:** It's recommended to use the "directive definition object" form. *
          * * Here's an example directive declared with a Directive Definition Object: * * ```js * var myModule = angular.module(...); * * myModule.directive('directiveName', function factory(injectables) { * var directiveDefinitionObject = { * {@link $compile#-priority- priority}: 0, * {@link $compile#-template- template}: '
          ', // or // function(tElement, tAttrs) { ... }, * // or * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... }, * {@link $compile#-transclude- transclude}: false, * {@link $compile#-restrict- restrict}: 'A', * {@link $compile#-templatenamespace- templateNamespace}: 'html', * {@link $compile#-scope- scope}: false, * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier', * {@link $compile#-bindtocontroller- bindToController}: false, * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], * {@link $compile#-multielement- multiElement}: false, * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) { * return { * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } * } * // or * // return function postLink( ... ) { ... } * }, * // or * // {@link $compile#-link- link}: { * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } * // } * // or * // {@link $compile#-link- link}: function postLink( ... ) { ... } * }; * return directiveDefinitionObject; * }); * ``` * *
          * **Note:** Any unspecified options will use the default value. You can see the default values below. *
          * * Therefore the above can be simplified as: * * ```js * var myModule = angular.module(...); * * myModule.directive('directiveName', function factory(injectables) { * var directiveDefinitionObject = { * link: function postLink(scope, iElement, iAttrs) { ... } * }; * return directiveDefinitionObject; * // or * // return function postLink(scope, iElement, iAttrs) { ... } * }); * ``` * * ### Life-cycle hooks * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the * directive: * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and * had their bindings initialized (and before the pre & post linking functions for the directives on * this element). This is a good place to put initialization code for your controller. * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a * component such as cloning the bound value to prevent accidental mutation of the outer value. * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on * changes. Any actions that you wish to take in response to the changes that you detect must be * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments; * if detecting changes, you must store the previous value(s) for comparison to the current values. * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent * components will have their `$onDestroy()` hook called before child components. * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since * they are waiting for their template to load asynchronously and their own compilation and linking has been * suspended until that occurs. * * #### Comparison with Angular 2 life-cycle hooks * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2: * * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`. * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor. * In Angular 2 you can only define hooks on the prototype of the Component class. * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to * `ngDoCheck` in Angular 2 * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be * propagated throughout the application. * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an * error or do nothing depending upon the state of `enableProdMode()`. * * #### Life-cycle hook examples * * This example shows how you can check for mutations to a Date object even though the identity of the object * has not changed. * * * * angular.module('do-check-module', []) * .component('app', { * template: * 'Month: ' + * 'Date: {{ $ctrl.date }}' + * '', * controller: function() { * this.date = new Date(); * this.month = this.date.getMonth(); * this.updateDate = function() { * this.date.setMonth(this.month); * }; * } * }) * .component('test', { * bindings: { date: '<' }, * template: * '
          {{ $ctrl.log | json }}
          ', * controller: function() { * var previousValue; * this.log = []; * this.$doCheck = function() { * var currentValue = this.date && this.date.valueOf(); * if (previousValue !== currentValue) { * this.log.push('doCheck: date mutated: ' + this.date); * previousValue = currentValue; * } * }; * } * }); *
          * * * *
          * * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large * arrays or objects can have a negative impact on your application performance) * * * *
          * * *
          {{ items }}
          * *
          *
          * * angular.module('do-check-module', []) * .component('test', { * bindings: { items: '<' }, * template: * '
          {{ $ctrl.log | json }}
          ', * controller: function() { * this.log = []; * * this.$doCheck = function() { * if (this.items_ref !== this.items) { * this.log.push('doCheck: items changed'); * this.items_ref = this.items; * } * if (!angular.equals(this.items_clone, this.items)) { * this.log.push('doCheck: items mutated'); * this.items_clone = angular.copy(this.items); * } * }; * } * }); *
          *
          * * * ### Directive Definition Object * * The directive definition object provides instructions to the {@link ng.$compile * compiler}. The attributes are: * * #### `multiElement` * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them * together as the directive elements. It is recommended that this feature be used on directives * which are not strictly behavioral (such as {@link ngClick}), and which * do not manipulate or replace child nodes (such as {@link ngInclude}). * * #### `priority` * When there are multiple directives defined on a single DOM element, sometimes it * is necessary to specify the order in which the directives are applied. The `priority` is used * to sort the directives before their `compile` functions get called. Priority is defined as a * number. Directives with greater numerical `priority` are compiled first. Pre-link functions * are also run in priority order, but post-link functions are run in reverse order. The order * of directives with the same priority is undefined. The default priority is `0`. * * #### `terminal` * If set to true then the current `priority` will be the last set of directives * which will execute (any directives at the current priority will still execute * as the order of execution on same `priority` is undefined). Note that expressions * and other directives used in the directive's template will also be excluded from execution. * * #### `scope` * The scope property can be `false`, `true`, or an object: * * * **`false` (default):** No scope will be created for the directive. The directive will use its * parent's scope. * * * **`true`:** A new child scope that prototypically inherits from its parent will be created for * the directive's element. If multiple directives on the same element request a new scope, * only one new scope is created. * * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent * scope. This is useful when creating reusable components, which should not accidentally read or modify * data in the parent scope. * * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the * directive's element. These local properties are useful for aliasing values for templates. The keys in * the object hash map to the name of the property on the isolate scope; the values define how the property * is bound to the parent scope, via matching attributes on the directive's element: * * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is * always a string since DOM attributes are strings. If no `attr` name is specified then the * attribute name is assumed to be the same as the local name. Given `` and the isolate scope definition `scope: { localName:'@myAttr' }`, * the directive's scope property `localName` will reflect the interpolated value of `hello * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's * scope. The `name` is read from the parent scope (not the directive's scope). * * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. * If no `attr` name is specified then the attribute name is assumed to be the same as the local * name. Given `` and the isolate scope definition `scope: { * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) * will be thrown upon discovering changes to the local value, since it will be impossible to sync * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} * method is used for tracking changes, and the equality check is based on object identity. * However, if an object literal or an array literal is passed as the binding expression, the * equality check is done by value (using the {@link angular.equals} function). It's also possible * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). * * * `<` or `` and directive definition of * `scope: { localModel:'` and the isolate scope definition `scope: { * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope * via an expression to the parent scope. This can be done by passing a map of local variable names * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. * * In general it's possible to apply more than one directive to one element, but there might be limitations * depending on the type of scope required by the directives. The following points will help explain these limitations. * For simplicity only two directives are taken into account, but it is also applicable for several directives: * * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope * * **child scope** + **no scope** => Both directives will share one single child scope * * **child scope** + **child scope** => Both directives will share one single child scope * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use * its parent's scope * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot * be applied to the same element. * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives * cannot be applied to the same element. * * * #### `bindToController` * This property is used to bind scope properties directly to the controller. It can be either * `true` or an object hash with the same format as the `scope` property. * * When an isolate scope is used for a directive (see above), `bindToController: true` will * allow a component to have its properties bound to the controller, rather than to scope. * * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller * properties. You can access these bindings once they have been initialized by providing a controller method called * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings * initialized. * *
          * **Deprecation warning:** although bindings for non-ES6 class controllers are currently * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization * code that relies upon bindings inside a `$onInit` method on the controller, instead. *
          * * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. * This will set up the scope bindings to the controller directly. Note that `scope` can still be used * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate * scope (useful for component directives). * * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. * * * #### `controller` * Controller constructor function. The controller is instantiated before the * pre-linking phase and can be accessed by other directives (see * `require` attribute). This allows the directives to communicate with each other and augment * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: * * * `$scope` - Current scope associated with the element * * `$element` - Current element * * `$attrs` - Current attributes object for the element * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: * * `scope`: (optional) override the scope. * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. * * `futureParentElement` (optional): * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) * and when the `cloneLinkingFn` is passed, * as those elements need to created and cloned in a special way when they are defined outside their * usual containers (e.g. like ``). * * See also the `directive.templateNamespace` property. * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) * then the default transclusion is provided. * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns * `true` if the specified slot contains content (i.e. one or more DOM nodes). * * #### `require` * Require another directive and inject its controller as the fourth argument to the linking function. The * `require` property can be a string, an array or an object: * * a **string** containing the name of the directive to pass to the linking function * * an **array** containing the names of directives to pass to the linking function. The argument passed to the * linking function will be an array of controllers in the same order as the names in the `require` property * * an **object** whose property values are the names of the directives to pass to the linking function. The argument * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding * controllers. * * If the `require` property is an object and `bindToController` is truthy, then the required controllers are * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers * have been constructed but before `$onInit` is called. * If the name of the required controller is the same as the local name (the key), the name can be * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`. * See the {@link $compileProvider#component} helper for an example of how this can be used. * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is * raised (unless no link function is specified and the required controllers are not being bound to the directive * controller, in which case error checking is skipped). The name can be prefixed with: * * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass * `null` to the `link` fn if not found. * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass * `null` to the `link` fn if not found. * * * #### `controllerAs` * Identifier name for a reference to the controller in the directive's scope. * This allows the controller to be referenced from the directive template. This is especially * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the * `controllerAs` reference might overwrite a property that already exists on the parent scope. * * * #### `restrict` * String of subset of `EACM` which restricts the directive to a specific directive * declaration style. If omitted, the defaults (elements and attributes) are used. * * * `E` - Element name (default): `` * * `A` - Attribute (default): `
          ` * * `C` - Class: `
          ` * * `M` - Comment: `` * * * #### `templateNamespace` * String representing the document type used by the markup in the template. * AngularJS needs this information as those elements need to be created and cloned * in a special way when they are defined outside their usual containers like `` and ``. * * * `html` - All root nodes in the template are HTML. Root nodes may also be * top-level elements such as `` or ``. * * `svg` - The root nodes in the template are SVG elements (excluding ``). * * `math` - The root nodes in the template are MathML elements (excluding ``). * * If no `templateNamespace` is specified, then the namespace is considered to be `html`. * * #### `template` * HTML markup that may: * * Replace the contents of the directive's element (default). * * Replace the directive's element itself (if `replace` is true - DEPRECATED). * * Wrap the contents of the directive's element (if `transclude` is true). * * Value may be: * * * A string. For example `
          {{delete_str}}
          `. * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` * function api below) and returns a string value. * * * #### `templateUrl` * This is similar to `template` but the template is loaded from the specified URL, asynchronously. * * Because template loading is asynchronous the compiler will suspend compilation of directives on that element * for later when the template has been resolved. In the meantime it will continue to compile and link * sibling and parent elements as though this element had not contained any directives. * * The compiler does not suspend the entire compilation to wait for templates to be loaded because this * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the * case when only one deeply nested directive has `templateUrl`. * * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} * * You can specify `templateUrl` as a string representing the URL or as a function which takes two * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns * a string value representing the url. In either case, the template URL is passed through {@link * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. * * * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) * specify what the template should replace. Defaults to `false`. * * * `true` - the template will replace the directive's element. * * `false` - the template will replace the contents of the directive's element. * * The replacement process migrates all of the attributes / classes from the old element to the new * one. See the {@link guide/directive#template-expanding-directive * Directives Guide} for an example. * * There are very few scenarios where element replacement is required for the application function, * the main one being reusable custom components that are used within SVG contexts * (because SVG doesn't work with custom elements in the DOM tree). * * #### `transclude` * Extract the contents of the element where the directive appears and make it available to the directive. * The contents are compiled and provided to the directive as a **transclusion function**. See the * {@link $compile#transclusion Transclusion} section below. * * * #### `compile` * * ```js * function compile(tElement, tAttrs, transclude) { ... } * ``` * * The compile function deals with transforming the template DOM. Since most directives do not do * template transformation, it is not used often. The compile function takes the following arguments: * * * `tElement` - template element - The element where the directive has been declared. It is * safe to do template transformation on the element and child elements only. * * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared * between all directive compile functions. * * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` * *
          * **Note:** The template instance and the link instance may be different objects if the template has * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration * should be done in a linking function rather than in a compile function. *
          *
          * **Note:** The compile function cannot handle directives that recursively use themselves in their * own templates or compile functions. Compiling these directives results in an infinite loop and * stack overflow errors. * * This can be avoided by manually using $compile in the postLink function to imperatively compile * a directive's template instead of relying on automatic template compilation via `template` or * `templateUrl` declaration or manual compilation inside the compile function. *
          * *
          * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it * e.g. does not know about the right outer scope. Please use the transclude function that is passed * to the link function instead. *
          * A compile function can have a return value which can be either a function or an object. * * * returning a (post-link) function - is equivalent to registering the linking function via the * `link` property of the config object when the compile function is empty. * * * returning an object with function(s) registered via `pre` and `post` properties - allows you to * control when a linking function should be called during the linking phase. See info about * pre-linking and post-linking functions below. * * * #### `link` * This property is used only if the `compile` property is not defined. * * ```js * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } * ``` * * The link function is responsible for registering DOM listeners as well as updating the DOM. It is * executed after the template has been cloned. This is where most of the directive logic will be * put. * * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the * directive for registering {@link ng.$rootScope.Scope#$watch watches}. * * * `iElement` - instance element - The element where the directive is to be used. It is safe to * manipulate the children of the element only in `postLink` function since the children have * already been linked. * * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared * between all directive linking functions. * * * `controller` - the directive's required controller instance(s) - Instances are shared * among all directives, which allows the directives to use the controllers as a communication * channel. The exact value depends on the directive's `require` property: * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one * * `string`: the controller instance * * `array`: array of controller instances * * If a required controller cannot be found, and it is optional, the instance is `null`, * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. * * Note that you can also require the directive's own controller - it will be made available like * any other controller. * * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. * This is the same as the `$transclude` parameter of directive controllers, * see {@link ng.$compile#-controller- the controller section for details}. * `function([scope], cloneLinkingFn, futureParentElement)`. * * #### Pre-linking function * * Executed before the child elements are linked. Not safe to do DOM transformation since the * compiler linking function will fail to locate the correct elements for linking. * * #### Post-linking function * * Executed after the child elements are linked. * * Note that child elements that contain `templateUrl` directives will not have been compiled * and linked since they are waiting for their template to load asynchronously and their own * compilation and linking has been suspended until that occurs. * * It is safe to do DOM transformation in the post-linking function on elements that are not waiting * for their async templates to be resolved. * * * ### Transclusion * * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and * copying them to another part of the DOM, while maintaining their connection to the original AngularJS * scope from where they were taken. * * Transclusion is used (often with {@link ngTransclude}) to insert the * original contents of a directive's element into a specified place in the template of the directive. * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded * content has access to the properties on the scope from which it was taken, even if the directive * has isolated scope. * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. * * This makes it possible for the widget to have private state for its template, while the transcluded * content has access to its originating scope. * *
          * **Note:** When testing an element transclude directive you must not place the directive at the root of the * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives * Testing Transclusion Directives}. *
          * * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the * directive's element, the entire element or multiple parts of the element contents: * * * `true` - transclude the content (i.e. the child nodes) of the directive's element. * * `'element'` - transclude the whole of the directive's element including any directives on this * element that defined at a lower priority than this directive. When used, the `template` * property is ignored. * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. * * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. * * This object is a map where the keys are the name of the slot to fill and the value is an element selector * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). * * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} * * If the element selector is prefixed with a `?` then that slot is optional. * * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `` elements to * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. * * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and * injectable into the directive's controller. * * * #### Transclusion Functions * * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion * function** to the directive's `link` function and `controller`. This transclusion function is a special * **linking function** that will return the compiled contents linked to a new transclusion scope. * *
          * If you are just using {@link ngTransclude} then you don't need to worry about this function, since * ngTransclude will deal with it for us. *
          * * If you want to manually control the insertion and removal of the transcluded content in your directive * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery * object that contains the compiled DOM, which is linked to the correct transclusion scope. * * When you call a transclusion function you can pass in a **clone attach function**. This function accepts * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded * content and the `scope` is the newly created transclusion scope, which the clone will be linked to. * *
          * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. *
          * * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone * attach function**: * * ```js * var transcludedContent, transclusionScope; * * $transclude(function(clone, scope) { * element.append(clone); * transcludedContent = clone; * transclusionScope = scope; * }); * ``` * * Later, if you want to remove the transcluded content from your DOM then you should also destroy the * associated transclusion scope: * * ```js * transcludedContent.remove(); * transclusionScope.$destroy(); * ``` * *
          * **Best Practice**: if you intend to add and remove transcluded content manually in your directive * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), * then you are also responsible for calling `$destroy` on the transclusion scope. *
          * * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} * automatically destroy their transcluded clones as necessary so you do not need to worry about this if * you are simply using {@link ngTransclude} to inject the transclusion into your directive. * * * #### Transclusion Scopes * * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it * was taken. * * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look * like this: * * ```html *
          *
          *
          *
          *
          *
          * ``` * * The `$parent` scope hierarchy will look like this: * ``` - $rootScope - isolate - transclusion ``` * * but the scopes will inherit prototypically from different scopes to their `$parent`. * ``` - $rootScope - transclusion - isolate ``` * * * ### Attributes * * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the * `link()` or `compile()` functions. It has a variety of uses. * * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways: * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access * to the attributes. * * * *Directive inter-communication:* All directives share the same instance of the attributes * object which allows the directives to use the attributes object as inter directive * communication. * * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object * allowing other directives to read the interpolated value. * * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also * the only way to easily get the actual value because during the linking phase the interpolation * hasn't been evaluated yet and so the value is at this time set to `undefined`. * * ```js * function linkingFn(scope, elm, attrs, ctrl) { * // get the attribute value * console.log(attrs.ngModel); * * // change the attribute * attrs.$set('ngModel', 'new value'); * * // observe changes to interpolated attribute * attrs.$observe('ngModel', function(value) { * console.log('ngModel has changed value to ' + value); * }); * } * ``` * * ## Example * *
          * **Note**: Typically directives are registered with `module.directive`. The example below is * to illustrate how `$compile` works. *
          *


          it('should auto compile', function() { var textarea = $('textarea'); var output = $('div[compile]'); // The initial state reads 'Hello Angular'. expect(output.getText()).toBe('Hello Angular'); textarea.clear(); textarea.sendKeys('{{name}}!'); expect(output.getText()).toBe('Angular!'); });
          * * * @param {string|DOMElement} element Element or HTML string to compile into a template function. * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. * *
          * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it * e.g. will not use the right outer scope. Please pass the transclude function as a * `parentBoundTranscludeFn` to the link function instead. *
          * * @param {number} maxPriority only apply directives lower than given priority (Only effects the * root element(s), not their children) * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template * (a DOM element/tree) to a scope. Where: * * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the * `template` and call the `cloneAttachFn` function allowing the caller to attach the * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is * called as:
          `cloneAttachFn(clonedElement, scope)` where: * * * `clonedElement` - is a clone of the original `element` passed into the compiler. * * `scope` - is the current scope with which the linking function is working with. * * * `options` - An optional object hash with linking options. If `options` is provided, then the following * keys may be used to control linking behavior: * * * `parentBoundTranscludeFn` - the transclude function made available to * directives; if given, it will be passed through to the link functions of * directives found in `element` during compilation. * * `transcludeControllers` - an object hash with keys that map controller names * to a hash with the key `instance`, which maps to the controller instance; * if given, it will make the controllers available to directives on the compileNode: * ``` * { * parent: { * instance: parentControllerInstance * } * } * ``` * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add * the cloned elements; only needed for transcludes that are allowed to contain non html * elements (e.g. SVG elements). See also the directive.controller property. * * Calling the linking function returns the element of the template. It is either the original * element passed in, or the clone of the element if the `cloneAttachFn` is provided. * * After linking the view is not updated until after a call to $digest which typically is done by * Angular automatically. * * If you need access to the bound view, there are two ways to do it: * * - If you are not asking the linking function to clone the template, create the DOM element(s) * before you send them to the compiler and keep this reference around. * ```js * var element = $compile('

          {{total}}

          ')(scope); * ``` * * - if on the other hand, you need the element to be cloned, the view reference from the original * example would not point to the clone, but rather to the original template that was cloned. In * this case, you can access the clone via the cloneAttachFn: * ```js * var templateElement = angular.element('

          {{total}}

          '), * scope = ....; * * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { * //attach the clone to DOM document at the right place * }); * * //now we have reference to the cloned DOM via `clonedElement` * ``` * * * For information on how the compiler works, see the * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. * * @knownIssue * * ### Double Compilation * Double compilation occurs when an already compiled part of the DOM gets compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues, and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it section on double compilation} for an in-depth explanation and ways to avoid it. * */ var $compileMinErr = minErr('$compile'); function UNINITIALIZED_VALUE() {} var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); /** * @ngdoc provider * @name $compileProvider * * @description */ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; /** @this */ function $CompileProvider($provide, $$sanitizeUriProvider) { var hasDirectives = {}, Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive:\s*([\w-]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\w-]+)(?::([^;]+))?;?)/, ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with // 'on' and be composed of only English letters. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; var bindingCache = createMap(); function parseIsolateBindings(scope, directiveName, isController) { var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*([\w$]*)\s*$/; var bindings = createMap(); forEach(scope, function(definition, scopeName) { if (definition in bindingCache) { bindings[scopeName] = bindingCache[definition]; return; } var match = definition.match(LOCAL_REGEXP); if (!match) { throw $compileMinErr('iscp', 'Invalid {3} for directive \'{0}\'.' + ' Definition: {... {1}: \'{2}\' ...}', directiveName, scopeName, definition, (isController ? 'controller bindings definition' : 'isolate scope definition')); } bindings[scopeName] = { mode: match[1][0], collection: match[2] === '*', optional: match[3] === '?', attrName: match[4] || scopeName }; if (match[4]) { bindingCache[definition] = bindings[scopeName]; } }); return bindings; } function parseDirectiveBindings(directive, directiveName) { var bindings = { isolateScope: null, bindToController: null }; if (isObject(directive.scope)) { if (directive.bindToController === true) { bindings.bindToController = parseIsolateBindings(directive.scope, directiveName, true); bindings.isolateScope = {}; } else { bindings.isolateScope = parseIsolateBindings(directive.scope, directiveName, false); } } if (isObject(directive.bindToController)) { bindings.bindToController = parseIsolateBindings(directive.bindToController, directiveName, true); } if (bindings.bindToController && !directive.controller) { // There is no controller throw $compileMinErr('noctrl', 'Cannot bind to controller without directive \'{0}\'s controller.', directiveName); } return bindings; } function assertValidDirectiveName(name) { var letter = name.charAt(0); if (!letter || letter !== lowercase(letter)) { throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The first character must be a lowercase letter', name); } if (name !== name.trim()) { throw $compileMinErr('baddir', 'Directive/Component name \'{0}\' is invalid. The name should not contain leading or trailing whitespaces', name); } } function getDirectiveRequire(directive) { var require = directive.require || (directive.controller && directive.name); if (!isArray(require) && isObject(require)) { forEach(require, function(value, key) { var match = value.match(REQUIRE_PREFIX_REGEXP); var name = value.substring(match[0].length); if (!name) require[key] = match[0] + key; }); } return require; } function getDirectiveRestrict(restrict, name) { if (restrict && !(isString(restrict) && /[EACM]/.test(restrict))) { throw $compileMinErr('badrestrict', 'Restrict property \'{0}\' of directive \'{1}\' is invalid', restrict, name); } return restrict || 'EA'; } /** * @ngdoc method * @name $compileProvider#directive * @kind function * * @description * Register a new directive with the compiler. * * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which * will match as ng-bind), or an object map of directives where the keys are the * names and the values are the factories. * @param {Function|Array} directiveFactory An injectable directive factory function. See the * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. * @returns {ng.$compileProvider} Self for chaining. */ this.directive = function registerDirective(name, directiveFactory) { assertArg(name, 'name'); assertNotHasOwnProperty(name, 'directive'); if (isString(name)) { assertValidDirectiveName(name); assertArg(directiveFactory, 'directiveFactory'); if (!hasDirectives.hasOwnProperty(name)) { hasDirectives[name] = []; $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', function($injector, $exceptionHandler) { var directives = []; forEach(hasDirectives[name], function(directiveFactory, index) { try { var directive = $injector.invoke(directiveFactory); if (isFunction(directive)) { directive = { compile: valueFn(directive) }; } else if (!directive.compile && directive.link) { directive.compile = valueFn(directive.link); } directive.priority = directive.priority || 0; directive.index = index; directive.name = directive.name || name; directive.require = getDirectiveRequire(directive); directive.restrict = getDirectiveRestrict(directive.restrict, name); directive.$$moduleName = directiveFactory.$$moduleName; directives.push(directive); } catch (e) { $exceptionHandler(e); } }); return directives; }]); } hasDirectives[name].push(directiveFactory); } else { forEach(name, reverseParams(registerDirective)); } return this; }; /** * @ngdoc method * @name $compileProvider#component * @module ng * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match ``) * @param {Object} options Component definition object (a simplified * {@link ng.$compile#directive-definition-object directive definition object}), * with the following properties (all optional): * * - `controller` – `{(string|function()=}` – controller constructor function that should be * associated with newly created scope or the name of a {@link ng.$compile#-controller- * registered controller} if passed as a string. An empty `noop` function by default. * - `controllerAs` – `{string=}` – identifier name for to reference the controller in the component's scope. * If present, the controller will be published to scope under the `controllerAs` name. * If not present, this will default to be `$ctrl`. * - `template` – `{string=|function()=}` – html template as a string or a function that * returns an html template as a string which should be used as the contents of this component. * Empty string by default. * * If `template` is a function, then it is {@link auto.$injector#invoke injected} with * the following locals: * * - `$element` - Current element * - `$attrs` - Current attributes object for the element * * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html * template that should be used as the contents of this component. * * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with * the following locals: * * - `$element` - Current element * - `$attrs` - Current attributes object for the element * * - `bindings` – `{object=}` – defines bindings between DOM attributes and component properties. * Component properties are always bound to the component controller and not to the scope. * See {@link ng.$compile#-bindtocontroller- `bindToController`}. * - `transclude` – `{boolean=}` – whether {@link $compile#transclusion content transclusion} is enabled. * Disabled by default. * - `require` - `{Object=}` - requires the controllers of other directives and binds them to * this component's controller. The object keys specify the property names under which the required * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. * - `$...` – additional properties to attach to the directive factory function and the controller * constructor function. (This is used by the component router to annotate) * * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. * @description * Register a **component definition** with the compiler. This is a shorthand for registering a special * type of directive, which represents a self-contained UI component in your application. Such components * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). * * Component definitions are very simple and do not require as much configuration as defining general * directives. Component definitions usually consist only of a template and a controller backing it. * * In order to make the definition easier, components enforce best practices like use of `controllerAs`, * `bindToController`. They always have **isolate scope** and are restricted to elements. * * Here are a few examples of how you would usually define components: * * ```js * var myMod = angular.module(...); * myMod.component('myComp', { * template: '
          My name is {{$ctrl.name}}
          ', * controller: function() { * this.name = 'shahar'; * } * }); * * myMod.component('myComp', { * template: '
          My name is {{$ctrl.name}}
          ', * bindings: {name: '@'} * }); * * myMod.component('myComp', { * templateUrl: 'views/my-comp.html', * controller: 'MyCtrl', * controllerAs: 'ctrl', * bindings: {name: '@'} * }); * * ``` * For more examples, and an in-depth guide, see the {@link guide/component component guide}. * *
          * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. */ this.component = function registerComponent(name, options) { var controller = options.controller || function() {}; function factory($injector) { function makeInjectable(fn) { if (isFunction(fn) || isArray(fn)) { return /** @this */ function(tElement, tAttrs) { return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); }; } else { return fn; } } var template = (!options.template && !options.templateUrl ? '' : options.template); var ddo = { controller: controller, controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', template: makeInjectable(template), templateUrl: makeInjectable(options.templateUrl), transclude: options.transclude, scope: {}, bindToController: options.bindings || {}, restrict: 'E', require: options.require }; // Copy annotations (starting with $) over to the DDO forEach(options, function(val, key) { if (key.charAt(0) === '$') ddo[key] = val; }); return ddo; } // TODO(pete) remove the following `forEach` before we release 1.6.0 // The component-router@0.2.0 looks for the annotations on the controller constructor // Nothing in Angular looks for annotations on the factory function but we can't remove // it from 1.5.x yet. // Copy any annotation properties (starting with $) over to the factory and controller constructor functions // These could be used by libraries such as the new component router forEach(options, function(val, key) { if (key.charAt(0) === '$') { factory[key] = val; // Don't try to copy over annotations to named controller if (isFunction(controller)) controller[key] = val; } }); factory.$inject = ['$injector']; return this.directive(name, factory); }; /** * @ngdoc method * @name $compileProvider#aHrefSanitizationWhitelist * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * * The sanitization is a security measure aimed at preventing XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.aHrefSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); return this; } else { return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); } }; /** * @ngdoc method * @name $compileProvider#imgSrcSanitizationWhitelist * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during img[src] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to img[src] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.imgSrcSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); return this; } else { return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); } }; /** * @ngdoc method * @name $compileProvider#debugInfoEnabled * * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the * current debugInfoEnabled state * @returns {*} current value if used as getter or itself (chaining) if used as setter * * @kind function * * @description * Call this method to enable/disable various debug runtime information in the compiler such as adding * binding information and a reference to the current scope on to DOM elements. * If enabled, the compiler will add the following to DOM elements that have been bound to the scope * * `ng-binding` CSS class * * `$binding` data property containing an array of the binding expressions * * You may want to disable this in production for a significant performance boost. See * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. * * The default value is true. */ var debugInfoEnabled = true; this.debugInfoEnabled = function(enabled) { if (isDefined(enabled)) { debugInfoEnabled = enabled; return this; } return debugInfoEnabled; }; /** * @ngdoc method * @name $compileProvider#preAssignBindingsEnabled * * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the * current preAssignBindingsEnabled state * @returns {*} current value if used as getter or itself (chaining) if used as setter * * @kind function * * @description * Call this method to enable/disable whether directive controllers are assigned bindings before * calling the controller's constructor. * If enabled (true), the compiler assigns the value of each of the bindings to the * properties of the controller object before the constructor of this object is called. * * If disabled (false), the compiler calls the constructor first before assigning bindings. * * The default value is true in Angular 1.5.x but will switch to false in Angular 1.6.x. */ var preAssignBindingsEnabled = true; this.preAssignBindingsEnabled = function(enabled) { if (isDefined(enabled)) { preAssignBindingsEnabled = enabled; return this; } return preAssignBindingsEnabled; }; var TTL = 10; /** * @ngdoc method * @name $compileProvider#onChangesTtl * @description * * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and * assuming that the model is unstable. * * The current default is 10 iterations. * * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result * in several iterations of calls to these hooks. However if an application needs more than the default 10 * iterations to stabilize then you should investigate what is causing the model to continuously change during * the `$onChanges` hook execution. * * Increasing the TTL could have performance implications, so you should not change it without proper justification. * * @param {number} limit The number of `$onChanges` hook iterations. * @returns {number|object} the current limit (or `this` if called as a setter for chaining) */ this.onChangesTtl = function(value) { if (arguments.length) { TTL = value; return this; } return TTL; }; var commentDirectivesEnabledConfig = true; /** * @ngdoc method * @name $compileProvider#commentDirectivesEnabled * @description * * It indicates to the compiler * whether or not directives on comments should be compiled. * Defaults to `true`. * * Calling this function with false disables the compilation of directives * on comments for the whole application. * This results in a compilation performance gain, * as the compiler doesn't have to check comments when looking for directives. * This should however only be used if you are sure that no comment directives are used in * the application (including any 3rd party directives). * * @param {boolean} enabled `false` if the compiler may ignore directives on comments * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) */ this.commentDirectivesEnabled = function(value) { if (arguments.length) { commentDirectivesEnabledConfig = value; return this; } return commentDirectivesEnabledConfig; }; var cssClassDirectivesEnabledConfig = true; /** * @ngdoc method * @name $compileProvider#cssClassDirectivesEnabled * @description * * It indicates to the compiler * whether or not directives on element classes should be compiled. * Defaults to `true`. * * Calling this function with false disables the compilation of directives * on element classes for the whole application. * This results in a compilation performance gain, * as the compiler doesn't have to check element classes when looking for directives. * This should however only be used if you are sure that no class directives are used in * the application (including any 3rd party directives). * * @param {boolean} enabled `false` if the compiler may ignore directives on element classes * @returns {boolean|object} the current value (or `this` if called as a setter for chaining) */ this.cssClassDirectivesEnabled = function(value) { if (arguments.length) { cssClassDirectivesEnabledConfig = value; return this; } return cssClassDirectivesEnabledConfig; }; this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, $controller, $rootScope, $sce, $animate, $$sanitizeUri) { var SIMPLE_ATTR_NAME = /^\w/; var specialAttrHolder = window.document.createElement('div'); var commentDirectivesEnabled = commentDirectivesEnabledConfig; var cssClassDirectivesEnabled = cssClassDirectivesEnabledConfig; var onChangesTtl = TTL; // The onChanges hooks should all be run together in a single digest // When changes occur, the call to trigger their hooks will be added to this queue var onChangesQueue; // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest function flushOnChangesQueue() { try { if (!(--onChangesTtl)) { // We have hit the TTL limit so reset everything onChangesQueue = undefined; throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); } // We must run this hook in an apply since the $$postDigest runs outside apply $rootScope.$apply(function() { var errors = []; for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { try { onChangesQueue[i](); } catch (e) { errors.push(e); } } // Reset the queue to trigger a new schedule next time there is a change onChangesQueue = undefined; if (errors.length) { throw errors; } }); } finally { onChangesTtl++; } } function Attributes(element, attributesToCopy) { if (attributesToCopy) { var keys = Object.keys(attributesToCopy); var i, l, key; for (i = 0, l = keys.length; i < l; i++) { key = keys[i]; this[key] = attributesToCopy[key]; } } else { this.$attr = {}; } this.$$element = element; } Attributes.prototype = { /** * @ngdoc method * @name $compile.directive.Attributes#$normalize * @kind function * * @description * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or * `data-`) to its normalized, camelCase form. * * Also there is special case for Moz prefix starting with upper case letter. * * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} * * @param {string} name Name to normalize */ $normalize: directiveNormalize, /** * @ngdoc method * @name $compile.directive.Attributes#$addClass * @kind function * * @description * Adds the CSS class value specified by the classVal parameter to the element. If animations * are enabled then an animation will be triggered for the class addition. * * @param {string} classVal The className value that will be added to the element */ $addClass: function(classVal) { if (classVal && classVal.length > 0) { $animate.addClass(this.$$element, classVal); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$removeClass * @kind function * * @description * Removes the CSS class value specified by the classVal parameter from the element. If * animations are enabled then an animation will be triggered for the class removal. * * @param {string} classVal The className value that will be removed from the element */ $removeClass: function(classVal) { if (classVal && classVal.length > 0) { $animate.removeClass(this.$$element, classVal); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$updateClass * @kind function * * @description * Adds and removes the appropriate CSS class values to the element based on the difference * between the new and old CSS class values (specified as newClasses and oldClasses). * * @param {string} newClasses The current CSS className value * @param {string} oldClasses The former CSS className value */ $updateClass: function(newClasses, oldClasses) { var toAdd = tokenDifference(newClasses, oldClasses); if (toAdd && toAdd.length) { $animate.addClass(this.$$element, toAdd); } var toRemove = tokenDifference(oldClasses, newClasses); if (toRemove && toRemove.length) { $animate.removeClass(this.$$element, toRemove); } }, /** * Set a normalized attribute on the element in a way such that all directives * can share the attribute. This function properly handles boolean attributes. * @param {string} key Normalized key. (ie ngAttribute) * @param {string|boolean} value The value to set. If `null` attribute will be deleted. * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. * Defaults to true. * @param {string=} attrName Optional none normalized name. Defaults to key. */ $set: function(key, value, writeAttr, attrName) { // TODO: decide whether or not to throw an error if "class" //is set through this function since it may cause $updateClass to //become unstable. var node = this.$$element[0], booleanKey = getBooleanAttrName(node, key), aliasedKey = getAliasedAttrName(key), observer = key, nodeName; if (booleanKey) { this.$$element.prop(key, value); attrName = booleanKey; } else if (aliasedKey) { this[aliasedKey] = value; observer = aliasedKey; } this[key] = value; // translate normalized key to actual key if (attrName) { this.$attr[key] = attrName; } else { attrName = this.$attr[key]; if (!attrName) { this.$attr[key] = attrName = snake_case(key, '-'); } } nodeName = nodeName_(this.$$element); if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || (nodeName === 'img' && key === 'src')) { // sanitize a[href] and img[src] values this[key] = value = $$sanitizeUri(value, key === 'src'); } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) { // sanitize img[srcset] values var result = ''; // first check if there are spaces because it's not the same pattern var trimmedSrcset = trim(value); // ( 999x ,| 999w ,| ,|, ) var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; // split srcset into tuple of uri and descriptor except for the last item var rawUris = trimmedSrcset.split(pattern); // for each tuples var nbrUrisWith2parts = Math.floor(rawUris.length / 2); for (var i = 0; i < nbrUrisWith2parts; i++) { var innerIdx = i * 2; // sanitize the uri result += $$sanitizeUri(trim(rawUris[innerIdx]), true); // add the descriptor result += (' ' + trim(rawUris[innerIdx + 1])); } // split the last item into uri and descriptor var lastTuple = trim(rawUris[i * 2]).split(/\s/); // sanitize the last uri result += $$sanitizeUri(trim(lastTuple[0]), true); // and add the last descriptor if any if (lastTuple.length === 2) { result += (' ' + trim(lastTuple[1])); } this[key] = value = result; } if (writeAttr !== false) { if (value === null || isUndefined(value)) { this.$$element.removeAttr(attrName); } else { if (SIMPLE_ATTR_NAME.test(attrName)) { this.$$element.attr(attrName, value); } else { setSpecialAttr(this.$$element[0], attrName, value); } } } // fire observers var $$observers = this.$$observers; if ($$observers) { forEach($$observers[observer], function(fn) { try { fn(value); } catch (e) { $exceptionHandler(e); } }); } }, /** * @ngdoc method * @name $compile.directive.Attributes#$observe * @kind function * * @description * Observes an interpolated attribute. * * The observer function will be invoked once during the next `$digest` following * compilation. The observer is then invoked whenever the interpolated value * changes. * * @param {string} key Normalized key. (ie ngAttribute) . * @param {function(interpolatedValue)} fn Function that will be called whenever the interpolated value of the attribute changes. * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation * guide} for more info. * @returns {function()} Returns a deregistration function for this observer. */ $observe: function(key, fn) { var attrs = this, $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), listeners = ($$observers[key] || ($$observers[key] = [])); listeners.push(fn); $rootScope.$evalAsync(function() { if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { // no one registered attribute interpolation function, so lets call it manually fn(attrs[key]); } }); return function() { arrayRemove(listeners, fn); }; } }; function setSpecialAttr(element, attrName, value) { // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` // so we have to jump through some hoops to get such an attribute // https://github.com/angular/angular.js/pull/13318 specialAttrHolder.innerHTML = ''; var attributes = specialAttrHolder.firstChild.attributes; var attribute = attributes[0]; // We have to remove the attribute from its container element before we can add it to the destination element attributes.removeNamedItem(attribute.name); attribute.value = value; element.attributes.setNamedItem(attribute); } function safeAddClass($element, className) { try { $element.addClass(className); } catch (e) { // ignore, since it means that we are trying to set class on // SVG element, where class name is read-only. } } var startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), denormalizeTemplate = (startSymbol === '{{' && endSymbol === '}}') ? identity : function denormalizeTemplate(template) { return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); }, NG_ATTR_BINDING = /^ngAttr[A-Z]/; var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { var bindings = $element.data('$binding') || []; if (isArray(binding)) { bindings = bindings.concat(binding); } else { bindings.push(binding); } $element.data('$binding', bindings); } : noop; compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { safeAddClass($element, 'ng-binding'); } : noop; compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; $element.data(dataName, scope); } : noop; compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); } : noop; compile.$$createComment = function(directiveName, comment) { var content = ''; if (debugInfoEnabled) { content = ' ' + (directiveName || '') + ': '; if (comment) content += comment + ' '; } return window.document.createComment(content); }; return compile; //================================ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, whereas we need to preserve the original selector so that we can // modify it. $compileNodes = jqLite($compileNodes); } var NOT_EMPTY = /\S+/; // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in for (var i = 0, len = $compileNodes.length; i < len; i++) { var domNode = $compileNodes[i]; if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) { jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span')); } } var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext); compile.$$addScopeClass($compileNodes); var namespace = null; return function publicLinkFn(scope, cloneConnectFn, options) { assertArg(scope, 'scope'); if (previousCompileContext && previousCompileContext.needsNewScope) { // A parent directive did a replace and a directive on this element asked // for transclusion, which caused us to lose a layer of element on which // we could hold the new transclusion scope, so we will create it manually // here. scope = scope.$parent.$new(); } options = options || {}; var parentBoundTranscludeFn = options.parentBoundTranscludeFn, transcludeControllers = options.transcludeControllers, futureParentElement = options.futureParentElement; // When `parentBoundTranscludeFn` is passed, it is a // `controllersBoundTransclude` function (it was previously passed // as `transclude` to directive.link) so we must unwrap it to get // its `boundTranscludeFn` if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; } if (!namespace) { namespace = detectNamespaceForChildElements(futureParentElement); } var $linkNode; if (namespace !== 'html') { // When using a directive with replace:true and templateUrl the $compileNodes // (or a child element inside of them) // might change, so we need to recreate the namespace adapted compileNodes // for call to the link function. // Note: This will already clone the nodes... $linkNode = jqLite( wrapTemplate(namespace, jqLite('
          ').append($compileNodes).html()) ); } else if (cloneConnectFn) { // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. $linkNode = JQLitePrototype.clone.call($compileNodes); } else { $linkNode = $compileNodes; } if (transcludeControllers) { for (var controllerName in transcludeControllers) { $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); } } compile.$$addScopeInfo($linkNode, scope); if (cloneConnectFn) cloneConnectFn($linkNode, scope); if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); return $linkNode; }; } function detectNamespaceForChildElements(parentElement) { // TODO: Make this detect MathML as well... var node = parentElement && parentElement[0]; if (!node) { return 'html'; } else { return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; } } /** * Compile function matches each node in nodeList against the directives. Once all directives * for a particular node are collected their compile functions are executed. The compile * functions return values - the linking functions - are combined into a composite linking * function, which is the a linking function for the node. * * @param {NodeList} nodeList an array of nodes or NodeList to compile * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the * scope argument is auto-generated to the new child of the transcluded parent scope. * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then * the rootElement must be set the jqLite collection of the compile root. This is * needed so that the jqLite collection items can be replaced with widgets. * @param {number=} maxPriority Max directive priority. * @returns {Function} A composite linking function of all of the matched directives or null. */ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { var linkFns = [], attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; for (var i = 0; i < nodeList.length; i++) { attrs = new Attributes(); // we must always refer to nodeList[i] since the nodes can be replaced underneath us. directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective); nodeLinkFn = (directives.length) ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext) : null; if (nodeLinkFn && nodeLinkFn.scope) { compile.$$addScopeClass(attrs.$$element); } childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !(childNodes = nodeList[i].childNodes) || !childNodes.length) ? null : compileNodes(childNodes, nodeLinkFn ? ( (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) && nodeLinkFn.transclude) : transcludeFn); if (nodeLinkFn || childLinkFn) { linkFns.push(i, nodeLinkFn, childLinkFn); linkFnFound = true; nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; } //use the previous context only for the first element in the virtual group previousCompileContext = null; } // return a linking function if we have found anything, null otherwise return linkFnFound ? compositeLinkFn : null; function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; var stableNodeList; if (nodeLinkFnFound) { // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our // offsets don't get screwed up var nodeListLength = nodeList.length; stableNodeList = new Array(nodeListLength); // create a sparse array by only copying the elements which have a linkFn for (i = 0; i < linkFns.length; i += 3) { idx = linkFns[i]; stableNodeList[idx] = nodeList[idx]; } } else { stableNodeList = nodeList; } for (i = 0, ii = linkFns.length; i < ii;) { node = stableNodeList[linkFns[i++]]; nodeLinkFn = linkFns[i++]; childLinkFn = linkFns[i++]; if (nodeLinkFn) { if (nodeLinkFn.scope) { childScope = scope.$new(); compile.$$addScopeInfo(jqLite(node), childScope); } else { childScope = scope; } if (nodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn( scope, nodeLinkFn.transclude, parentBoundTranscludeFn); } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { childBoundTranscludeFn = parentBoundTranscludeFn; } else if (!parentBoundTranscludeFn && transcludeFn) { childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); } else { childBoundTranscludeFn = null; } nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); } else if (childLinkFn) { childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); } } } } function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { if (!transcludedScope) { transcludedScope = scope.$new(false, containingScope); transcludedScope.$$transcluded = true; } return transcludeFn(transcludedScope, cloneFn, { parentBoundTranscludeFn: previousBoundTranscludeFn, transcludeControllers: controllers, futureParentElement: futureParentElement }); } // We need to attach the transclusion slots onto the `boundTranscludeFn` // so that they are available inside the `controllersBoundTransclude` function var boundSlots = boundTranscludeFn.$$slots = createMap(); for (var slotName in transcludeFn.$$slots) { if (transcludeFn.$$slots[slotName]) { boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); } else { boundSlots[slotName] = null; } } return boundTranscludeFn; } /** * Looks for directives on the given node and adds them to the directive collection which is * sorted. * * @param node Node to search. * @param directives An array to which the directives are added to. This array is sorted before * the function returns. * @param attrs The shared attrs object which is used to populate the normalized attributes. * @param {number=} maxPriority Max directive priority. */ function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { var nodeType = node.nodeType, attrsMap = attrs.$attr, match, nodeName, className; switch (nodeType) { case NODE_TYPE_ELEMENT: /* Element */ nodeName = nodeName_(node); // use the node name: addDirective(directives, directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); // iterate over the attributes for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; attr = nAttrs[j]; name = attr.name; value = trim(attr.value); // support ngAttr attribute binding ngAttrName = directiveNormalize(name); isNgAttr = NG_ATTR_BINDING.test(ngAttrName); if (isNgAttr) { name = name.replace(PREFIX_REGEXP, '') .substr(8).replace(/_(.)/g, function(match, letter) { return letter.toUpperCase(); }); } var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { attrStartName = name; attrEndName = name.substr(0, name.length - 5) + 'end'; name = name.substr(0, name.length - 6); } nName = directiveNormalize(name.toLowerCase()); attrsMap[nName] = name; if (isNgAttr || !attrs.hasOwnProperty(nName)) { attrs[nName] = value; if (getBooleanAttrName(node, nName)) { attrs[nName] = true; // presence means true } } addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, attrEndName); } if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { // Hidden input elements can have strange behaviour when navigating back to the page // This tells the browser not to try to cache and reinstate previous values node.setAttribute('autocomplete', 'off'); } // use class as directive if (!cssClassDirectivesEnabled) break; className = node.className; if (isObject(className)) { // Maybe SVGAnimatedString className = className.animVal; } if (isString(className) && className !== '') { while ((match = CLASS_DIRECTIVE_REGEXP.exec(className))) { nName = directiveNormalize(match[2]); if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { attrs[nName] = trim(match[3]); } className = className.substr(match.index + match[0].length); } } break; case NODE_TYPE_TEXT: /* Text Node */ if (msie === 11) { // Workaround for #11781 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; node.parentNode.removeChild(node.nextSibling); } } addTextInterpolateDirective(directives, node.nodeValue); break; case NODE_TYPE_COMMENT: /* Comment */ if (!commentDirectivesEnabled) break; collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective); break; } directives.sort(byPriority); return directives; } function collectCommentDirectives(node, directives, attrs, maxPriority, ignoreDirective) { // function created because of performance, try/catch disables // the optimization of the whole function #14848 try { var match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); if (match) { var nName = directiveNormalize(match[1]); if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { attrs[nName] = trim(match[2]); } } } catch (e) { // turns out that under some circumstances IE9 throws errors when one attempts to read // comment's node value. // Just ignore it and continue. (Can't seem to reproduce in test case.) } } /** * Given a node with a directive-start it collects all of the siblings until it finds * directive-end. * @param node * @param attrStart * @param attrEnd * @returns {*} */ function groupScan(node, attrStart, attrEnd) { var nodes = []; var depth = 0; if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { do { if (!node) { throw $compileMinErr('uterdir', 'Unterminated attribute, found \'{0}\' but no matching \'{1}\' found.', attrStart, attrEnd); } if (node.nodeType === NODE_TYPE_ELEMENT) { if (node.hasAttribute(attrStart)) depth++; if (node.hasAttribute(attrEnd)) depth--; } nodes.push(node); node = node.nextSibling; } while (depth > 0); } else { nodes.push(node); } return jqLite(nodes); } /** * Wrapper for linking function which converts normal linking function into a grouped * linking function. * @param linkFn * @param attrStart * @param attrEnd * @returns {Function} */ function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { element = groupScan(element[0], attrStart, attrEnd); return linkFn(scope, element, attrs, controllers, transcludeFn); }; } /** * A function generator that is used to support both eager and lazy compilation * linking function. * @param eager * @param $compileNodes * @param transcludeFn * @param maxPriority * @param ignoreDirective * @param previousCompileContext * @returns {Function} */ function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { var compiled; if (eager) { return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); } return /** @this */ function lazyCompilation() { if (!compiled) { compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); // Null out all of these references in order to make them eligible for garbage collection // since this is a potentially long lived closure $compileNodes = transcludeFn = previousCompileContext = null; } return compiled.apply(this, arguments); }; } /** * Once the directives have been collected, their compile functions are executed. This method * is responsible for inlining directive templates as well as terminating the application * of the directives if the terminal directive has been reached. * * @param {Array} directives Array of collected directives to execute their compile function. * this needs to be pre-sorted by priority order. * @param {Node} compileNode The raw DOM node to apply the compile functions to * @param {Object} templateAttrs The shared attribute function * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the * scope argument is auto-generated to the new * child of the transcluded parent scope. * @param {JQLite} jqCollection If we are working on the root of the compile tree then this * argument has the root jqLite array so that we can replace nodes * on it. * @param {Object=} originalReplaceDirective An optional directive that will be ignored when * compiling the transclusion. * @param {Array.} preLinkFns * @param {Array.} postLinkFns * @param {Object} previousCompileContext Context used for previous compilation of the current * node * @returns {Function} linkFn */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { previousCompileContext = previousCompileContext || {}; var terminalPriority = -Number.MAX_VALUE, newScopeDirective = previousCompileContext.newScopeDirective, controllerDirectives = previousCompileContext.controllerDirectives, newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, templateDirective = previousCompileContext.templateDirective, nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, hasTranscludeDirective = false, hasTemplate = false, hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, $compileNode = templateAttrs.$$element = jqLite(compileNode), directive, directiveName, $template, replaceDirective = originalReplaceDirective, childTranscludeFn = transcludeFn, linkFn, didScanForMultipleTransclusion = false, mightHaveMultipleTransclusionError = false, directiveValue; // executes all directives on the current element for (var i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; var attrStart = directive.$$start; var attrEnd = directive.$$end; // collect multiblock sections if (attrStart) { $compileNode = groupScan(compileNode, attrStart, attrEnd); } $template = undefined; if (terminalPriority > directive.priority) { break; // prevent further processing of directives } directiveValue = directive.scope; if (directiveValue) { // skip the check for directives with async templates, we'll check the derived sync // directive when the template arrives if (!directive.templateUrl) { if (isObject(directiveValue)) { // This directive is trying to add an isolated scope. // Check that there is no scope of any kind already assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, directive, $compileNode); newIsolateScopeDirective = directive; } else { // This directive is trying to add a child scope. // Check that there is no isolated scope already assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, $compileNode); } } newScopeDirective = newScopeDirective || directive; } directiveName = directive.name; // If we encounter a condition that can result in transclusion on the directive, // then scan ahead in the remaining directives for others that may cause a multiple // transclusion error to be thrown during the compilation process. If a matching directive // is found, then we know that when we encounter a transcluded directive, we need to eagerly // compile the `transclude` function rather than doing it lazily in order to throw // exceptions at the correct time if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) || (directive.transclude && !directive.$$tlb))) { var candidateDirective; for (var scanningIndex = i + 1; (candidateDirective = directives[scanningIndex++]);) { if ((candidateDirective.transclude && !candidateDirective.$$tlb) || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { mightHaveMultipleTransclusionError = true; break; } } didScanForMultipleTransclusion = true; } if (!directive.templateUrl && directive.controller) { controllerDirectives = controllerDirectives || createMap(); assertNoDuplicate('\'' + directiveName + '\' controller', controllerDirectives[directiveName], directive, $compileNode); controllerDirectives[directiveName] = directive; } directiveValue = directive.transclude; if (directiveValue) { hasTranscludeDirective = true; // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. // This option should only be used by directives that know how to safely handle element transclusion, // where the transcluded nodes are added or replaced after linking. if (!directive.$$tlb) { assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); nonTlbTranscludeDirective = directive; } if (directiveValue === 'element') { hasElementTranscludeDirective = true; terminalPriority = directive.priority; $template = $compileNode; $compileNode = templateAttrs.$$element = jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); compileNode = $compileNode[0]; replaceWith(jqCollection, sliceArgs($template), compileNode); // Support: Chrome < 50 // https://github.com/angular/angular.js/issues/14041 // In the versions of V8 prior to Chrome 50, the document fragment that is created // in the `replaceWith` function is improperly garbage collected despite still // being referenced by the `parentNode` property of all of the child nodes. By adding // a reference to the fragment via a different property, we can avoid that incorrect // behavior. // TODO: remove this line after Chrome 50 has been released $template[0].$$parentNode = $template[0].parentNode; childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, replaceDirective && replaceDirective.name, { // Don't pass in: // - controllerDirectives - otherwise we'll create duplicates controllers // - newIsolateScopeDirective or templateDirective - combining templates with // element transclusion doesn't make sense. // // We need only nonTlbTranscludeDirective so that we prevent putting transclusion // on the same element more than once. nonTlbTranscludeDirective: nonTlbTranscludeDirective }); } else { var slots = createMap(); $template = jqLite(jqLiteClone(compileNode)).contents(); if (isObject(directiveValue)) { // We have transclusion slots, // collect them up, compile them and store their transclusion functions $template = []; var slotMap = createMap(); var filledSlots = createMap(); // Parse the element selectors forEach(directiveValue, function(elementSelector, slotName) { // If an element selector starts with a ? then it is optional var optional = (elementSelector.charAt(0) === '?'); elementSelector = optional ? elementSelector.substring(1) : elementSelector; slotMap[elementSelector] = slotName; // We explicitly assign `null` since this implies that a slot was defined but not filled. // Later when calling boundTransclusion functions with a slot name we only error if the // slot is `undefined` slots[slotName] = null; // filledSlots contains `true` for all slots that are either optional or have been // filled. This is used to check that we have not missed any required slots filledSlots[slotName] = optional; }); // Add the matching elements into their slot forEach($compileNode.contents(), function(node) { var slotName = slotMap[directiveNormalize(nodeName_(node))]; if (slotName) { filledSlots[slotName] = true; slots[slotName] = slots[slotName] || []; slots[slotName].push(node); } else { $template.push(node); } }); // Check for required slots that were not filled forEach(filledSlots, function(filled, slotName) { if (!filled) { throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); } }); for (var slotName in slots) { if (slots[slotName]) { // Only define a transclusion function if the slot was filled slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); } } } $compileNode.empty(); // clear contents childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); childTranscludeFn.$$slots = slots; } } if (directive.template) { hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; directiveValue = (isFunction(directive.template)) ? directive.template($compileNode, templateAttrs) : directive.template; directiveValue = denormalizeTemplate(directiveValue); if (directive.replace) { replaceDirective = directive; if (jqLiteIsTextNode(directiveValue)) { $template = []; } else { $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); } compileNode = $template[0]; if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { throw $compileMinErr('tplrt', 'Template for directive \'{0}\' must have exactly one root element. {1}', directiveName, ''); } replaceWith(jqCollection, $compileNode, compileNode); var newTemplateAttrs = {$attr: {}}; // combine directives from the original node and from the template: // - take the array of directives for this element // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) // - collect directives from the template and sort them by priority // - combine directives as: processed + template + unprocessed var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); if (newIsolateScopeDirective || newScopeDirective) { // The original directive caused the current element to be replaced but this element // also needs to have a new scope, so we need to tell the template directives // that they would need to get their scope from further up, if they require transclusion markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); } directives = directives.concat(templateDirectives).concat(unprocessedDirectives); mergeTemplateAttributes(templateAttrs, newTemplateAttrs); ii = directives.length; } else { $compileNode.html(directiveValue); } } if (directive.templateUrl) { hasTemplate = true; assertNoDuplicate('template', templateDirective, directive, $compileNode); templateDirective = directive; if (directive.replace) { replaceDirective = directive; } // eslint-disable-next-line no-func-assign nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { controllerDirectives: controllerDirectives, newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, newIsolateScopeDirective: newIsolateScopeDirective, templateDirective: templateDirective, nonTlbTranscludeDirective: nonTlbTranscludeDirective }); ii = directives.length; } else if (directive.compile) { try { linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); var context = directive.$$originalDirective || directive; if (isFunction(linkFn)) { addLinkFns(null, bind(context, linkFn), attrStart, attrEnd); } else if (linkFn) { addLinkFns(bind(context, linkFn.pre), bind(context, linkFn.post), attrStart, attrEnd); } } catch (e) { $exceptionHandler(e, startingTag($compileNode)); } } if (directive.terminal) { nodeLinkFn.terminal = true; terminalPriority = Math.max(terminalPriority, directive.priority); } } nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; nodeLinkFn.templateOnThisElement = hasTemplate; nodeLinkFn.transclude = childTranscludeFn; previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; // might be normal or delayed nodeLinkFn depending on if templateUrl is present return nodeLinkFn; //////////////////// function addLinkFns(pre, post, attrStart, attrEnd) { if (pre) { if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); pre.require = directive.require; pre.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { pre = cloneAndAnnotateFn(pre, {isolateScope: true}); } preLinkFns.push(pre); } if (post) { if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); post.require = directive.require; post.directiveName = directiveName; if (newIsolateScopeDirective === directive || directive.$$isolateScope) { post = cloneAndAnnotateFn(post, {isolateScope: true}); } postLinkFns.push(post); } } function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, attrs, scopeBindingInfo; if (compileNode === linkNode) { attrs = templateAttrs; $element = templateAttrs.$$element; } else { $element = jqLite(linkNode); attrs = new Attributes($element, templateAttrs); } controllerScope = scope; if (newIsolateScopeDirective) { isolateScope = scope.$new(true); } else if (newScopeDirective) { controllerScope = scope.$parent; } if (boundTranscludeFn) { // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` transcludeFn = controllersBoundTransclude; transcludeFn.$$boundTransclude = boundTranscludeFn; // expose the slots on the `$transclude` function transcludeFn.isSlotFilled = function(slotName) { return !!boundTranscludeFn.$$slots[slotName]; }; } if (controllerDirectives) { elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); } if (newIsolateScopeDirective) { // Initialize isolate scope bindings for new isolate scope directive. compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || templateDirective === newIsolateScopeDirective.$$originalDirective))); compile.$$addScopeClass($element, true); isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings; scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, isolateScope.$$isolateBindings, newIsolateScopeDirective); if (scopeBindingInfo.removeWatches) { isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); } } // Initialize bindToController bindings for (var name in elementControllers) { var controllerDirective = controllerDirectives[name]; var controller = elementControllers[name]; var bindings = controllerDirective.$$bindings.bindToController; if (preAssignBindingsEnabled) { if (bindings) { controller.bindingInfo = initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); } else { controller.bindingInfo = {}; } var controllerResult = controller(); if (controllerResult !== controller.instance) { // If the controller constructor has a return value, overwrite the instance // from setupControllers controller.instance = controllerResult; $element.data('$' + controllerDirective.name + 'Controller', controllerResult); if (controller.bindingInfo.removeWatches) { controller.bindingInfo.removeWatches(); } controller.bindingInfo = initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); } } else { controller.instance = controller(); $element.data('$' + controllerDirective.name + 'Controller', controller.instance); controller.bindingInfo = initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); } } // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy forEach(controllerDirectives, function(controllerDirective, name) { var require = controllerDirective.require; if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); } }); // Handle the init and destroy lifecycle hooks on all controllers that have them forEach(elementControllers, function(controller) { var controllerInstance = controller.instance; if (isFunction(controllerInstance.$onChanges)) { try { controllerInstance.$onChanges(controller.bindingInfo.initialChanges); } catch (e) { $exceptionHandler(e); } } if (isFunction(controllerInstance.$onInit)) { try { controllerInstance.$onInit(); } catch (e) { $exceptionHandler(e); } } if (isFunction(controllerInstance.$doCheck)) { controllerScope.$watch(function() { controllerInstance.$doCheck(); }); controllerInstance.$doCheck(); } if (isFunction(controllerInstance.$onDestroy)) { controllerScope.$on('$destroy', function callOnDestroyHook() { controllerInstance.$onDestroy(); }); } }); // PRELINKING for (i = 0, ii = preLinkFns.length; i < ii; i++) { linkFn = preLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn ); } // RECURSION // We only pass the isolate scope, if the isolate directive has a template, // otherwise the child elements do not belong to the isolate directive. var scopeToChild = scope; if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { scopeToChild = isolateScope; } if (childLinkFn) { childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); } // POSTLINKING for (i = postLinkFns.length - 1; i >= 0; i--) { linkFn = postLinkFns[i]; invokeLinkFn(linkFn, linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn ); } // Trigger $postLink lifecycle hooks forEach(elementControllers, function(controller) { var controllerInstance = controller.instance; if (isFunction(controllerInstance.$postLink)) { controllerInstance.$postLink(); } }); // This is the function that is injected as `$transclude`. // Note: all arguments are optional! function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { var transcludeControllers; // No scope passed in: if (!isScope(scope)) { slotName = futureParentElement; futureParentElement = cloneAttachFn; cloneAttachFn = scope; scope = undefined; } if (hasElementTranscludeDirective) { transcludeControllers = elementControllers; } if (!futureParentElement) { futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; } if (slotName) { // slotTranscludeFn can be one of three things: // * a transclude function - a filled slot // * `null` - an optional slot that was not filled // * `undefined` - a slot that was not declared (i.e. invalid) var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; if (slotTranscludeFn) { return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); } else if (isUndefined(slotTranscludeFn)) { throw $compileMinErr('noslot', 'No parent directive that requires a transclusion with slot name "{0}". ' + 'Element: {1}', slotName, startingTag($element)); } } else { return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); } } } } function getControllers(directiveName, require, $element, elementControllers) { var value; if (isString(require)) { var match = require.match(REQUIRE_PREFIX_REGEXP); var name = require.substring(match[0].length); var inheritType = match[1] || match[3]; var optional = match[2] === '?'; //If only parents then start at the parent element if (inheritType === '^^') { $element = $element.parent(); //Otherwise attempt getting the controller from elementControllers in case //the element is transcluded (and has no data) and to avoid .data if possible } else { value = elementControllers && elementControllers[name]; value = value && value.instance; } if (!value) { var dataName = '$' + name + 'Controller'; value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); } if (!value && !optional) { throw $compileMinErr('ctreq', 'Controller \'{0}\', required by directive \'{1}\', can\'t be found!', name, directiveName); } } else if (isArray(require)) { value = []; for (var i = 0, ii = require.length; i < ii; i++) { value[i] = getControllers(directiveName, require[i], $element, elementControllers); } } else if (isObject(require)) { value = {}; forEach(require, function(controller, property) { value[property] = getControllers(directiveName, controller, $element, elementControllers); }); } return value || null; } function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { var elementControllers = createMap(); for (var controllerKey in controllerDirectives) { var directive = controllerDirectives[controllerKey]; var locals = { $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, $element: $element, $attrs: attrs, $transclude: transcludeFn }; var controller = directive.controller; if (controller === '@') { controller = attrs[directive.name]; } var controllerInstance = $controller(controller, locals, true, directive.controllerAs); // For directives with element transclusion the element is a comment. // In this case .data will not attach any data. // Instead, we save the controllers for the element in a local hash and attach to .data // later, once we have the actual element. elementControllers[directive.name] = controllerInstance; $element.data('$' + directive.name + 'Controller', controllerInstance.instance); } return elementControllers; } // Depending upon the context in which a directive finds itself it might need to have a new isolated // or child scope created. For instance: // * if the directive has been pulled into a template because another directive with a higher priority // asked for element transclusion // * if the directive itself asks for transclusion but it is at the root of a template and the original // element was replaced. See https://github.com/angular/angular.js/issues/12936 function markDirectiveScope(directives, isolateScope, newScope) { for (var j = 0, jj = directives.length; j < jj; j++) { directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); } } /** * looks up the directive and decorates it with exception handling and proper parameters. We * call this the boundDirective. * * @param {string} name name of the directive to look up. * @param {string} location The directive must be found in specific format. * String containing any of theses characters: * * * `E`: element name * * `A': attribute * * `C`: class * * `M`: comment * @returns {boolean} true if directive was added. */ function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) { if (name === ignoreDirective) return null; var match = null; if (hasDirectives.hasOwnProperty(name)) { for (var directive, directives = $injector.get(name + Suffix), i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; if ((isUndefined(maxPriority) || maxPriority > directive.priority) && directive.restrict.indexOf(location) !== -1) { if (startAttrName) { directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); } if (!directive.$$bindings) { var bindings = directive.$$bindings = parseDirectiveBindings(directive, directive.name); if (isObject(bindings.isolateScope)) { directive.$$isolateBindings = bindings.isolateScope; } } tDirectives.push(directive); match = directive; } } } return match; } /** * looks up the directive and returns true if it is a multi-element directive, * and therefore requires DOM nodes between -start and -end markers to be grouped * together. * * @param {string} name name of the directive to look up. * @returns true if directive was registered as multi-element. */ function directiveIsMultiElement(name) { if (hasDirectives.hasOwnProperty(name)) { for (var directive, directives = $injector.get(name + Suffix), i = 0, ii = directives.length; i < ii; i++) { directive = directives[i]; if (directive.multiElement) { return true; } } } return false; } /** * When the element is replaced with HTML template then the new attributes * on the template need to be merged with the existing attributes in the DOM. * The desired effect is to have both of the attributes present. * * @param {object} dst destination attributes (original DOM) * @param {object} src source attributes (from the directive template) */ function mergeTemplateAttributes(dst, src) { var srcAttr = src.$attr, dstAttr = dst.$attr; // reapply the old attributes to the new element forEach(dst, function(value, key) { if (key.charAt(0) !== '$') { if (src[key] && src[key] !== value) { value += (key === 'style' ? ';' : ' ') + src[key]; } dst.$set(key, value, true, srcAttr[key]); } }); // copy the new attributes on the old attrs object forEach(src, function(value, key) { // Check if we already set this attribute in the loop above. // `dst` will never contain hasOwnProperty as DOM parser won't let it. // You will get an "InvalidCharacterError: DOM Exception 5" error if you // have an attribute like "has-own-property" or "data-has-own-property", etc. if (!dst.hasOwnProperty(key) && key.charAt(0) !== '$') { dst[key] = value; if (key !== 'class' && key !== 'style') { dstAttr[key] = srcAttr[key]; } } }); } function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], afterTemplateNodeLinkFn, afterTemplateChildLinkFn, beforeTemplateCompileNode = $compileNode[0], origAsyncDirective = directives.shift(), derivedSyncDirective = inherit(origAsyncDirective, { templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective }), templateUrl = (isFunction(origAsyncDirective.templateUrl)) ? origAsyncDirective.templateUrl($compileNode, tAttrs) : origAsyncDirective.templateUrl, templateNamespace = origAsyncDirective.templateNamespace; $compileNode.empty(); $templateRequest(templateUrl) .then(function(content) { var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; content = denormalizeTemplate(content); if (origAsyncDirective.replace) { if (jqLiteIsTextNode(content)) { $template = []; } else { $template = removeComments(wrapTemplate(templateNamespace, trim(content))); } compileNode = $template[0]; if ($template.length !== 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { throw $compileMinErr('tplrt', 'Template for directive \'{0}\' must have exactly one root element. {1}', origAsyncDirective.name, templateUrl); } tempTemplateAttrs = {$attr: {}}; replaceWith($rootElement, $compileNode, compileNode); var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); if (isObject(origAsyncDirective.scope)) { // the original directive that caused the template to be loaded async required // an isolate scope markDirectiveScope(templateDirectives, true); } directives = templateDirectives.concat(directives); mergeTemplateAttributes(tAttrs, tempTemplateAttrs); } else { compileNode = beforeTemplateCompileNode; $compileNode.html(content); } directives.unshift(derivedSyncDirective); afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, previousCompileContext); forEach($rootElement, function(node, i) { if (node === compileNode) { $rootElement[i] = $compileNode[0]; } }); afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); while (linkQueue.length) { var scope = linkQueue.shift(), beforeTemplateLinkNode = linkQueue.shift(), linkRootElement = linkQueue.shift(), boundTranscludeFn = linkQueue.shift(), linkNode = $compileNode[0]; if (scope.$$destroyed) continue; if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { var oldClasses = beforeTemplateLinkNode.className; if (!(previousCompileContext.hasElementTranscludeDirective && origAsyncDirective.replace)) { // it was cloned therefore we have to clone as well. linkNode = jqLiteClone(compileNode); } replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); // Copy in CSS classes from original node safeAddClass(jqLite(linkNode), oldClasses); } if (afterTemplateNodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } else { childBoundTranscludeFn = boundTranscludeFn; } afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, childBoundTranscludeFn); } linkQueue = null; }); return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { var childBoundTranscludeFn = boundTranscludeFn; if (scope.$$destroyed) return; if (linkQueue) { linkQueue.push(scope, node, rootElement, childBoundTranscludeFn); } else { if (afterTemplateNodeLinkFn.transcludeOnThisElement) { childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); } afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); } }; } /** * Sorting function for bound directives. */ function byPriority(a, b) { var diff = b.priority - a.priority; if (diff !== 0) return diff; if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; return a.index - b.index; } function assertNoDuplicate(what, previousDirective, directive, element) { function wrapModuleNameIfDefined(moduleName) { return moduleName ? (' (module: ' + moduleName + ')') : ''; } if (previousDirective) { throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); } } function addTextInterpolateDirective(directives, text) { var interpolateFn = $interpolate(text, true); if (interpolateFn) { directives.push({ priority: 0, compile: function textInterpolateCompileFn(templateNode) { var templateNodeParent = templateNode.parent(), hasCompileParent = !!templateNodeParent.length; // When transcluding a template that has bindings in the root // we don't have a parent and thus need to add the class during linking fn. if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); return function textInterpolateLinkFn(scope, node) { var parent = node.parent(); if (!hasCompileParent) compile.$$addBindingClass(parent); compile.$$addBindingInfo(parent, interpolateFn.expressions); scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { node[0].nodeValue = value; }); }; } }); } } function wrapTemplate(type, template) { type = lowercase(type || 'html'); switch (type) { case 'svg': case 'math': var wrapper = window.document.createElement('div'); wrapper.innerHTML = '<' + type + '>' + template + ''; return wrapper.childNodes[0].childNodes; default: return template; } } function getTrustedContext(node, attrNormalizedName) { if (attrNormalizedName === 'srcdoc') { return $sce.HTML; } var tag = nodeName_(node); // All tags with src attributes require a RESOURCE_URL value, except for // img and various html5 media tags. if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) { return $sce.RESOURCE_URL; } // maction[xlink:href] can source SVG. It's not limited to . } else if (attrNormalizedName === 'xlinkHref' || (tag === 'form' && attrNormalizedName === 'action') ) { return $sce.RESOURCE_URL; } } function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) { var trustedContext = getTrustedContext(node, name); var mustHaveExpression = !isNgAttr; var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr; var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing); // no interpolation found -> ignore if (!interpolateFn) return; if (name === 'multiple' && nodeName_(node) === 'select') { throw $compileMinErr('selmulti', 'Binding to the \'multiple\' attribute is not supported. Element: {0}', startingTag(node)); } directives.push({ priority: 100, compile: function() { return { pre: function attrInterpolatePreLinkFn(scope, element, attr) { var $$observers = (attr.$$observers || (attr.$$observers = createMap())); if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { throw $compileMinErr('nodomevents', 'Interpolations for HTML DOM event attributes are disallowed. Please use the ' + 'ng- versions (such as ng-click instead of onclick) instead.'); } // If the attribute has changed since last $interpolate()ed var newValue = attr[name]; if (newValue !== value) { // we need to interpolate again since the attribute value has been updated // (e.g. by another directive's compile function) // ensure unset/empty values make interpolateFn falsy interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); value = newValue; } // if attribute was updated so that there is no interpolation going on we don't want to // register any observers if (!interpolateFn) return; // initialize attr object so that it's ready in case we need the value for isolate // scope initialization, otherwise the value would not be available from isolate // directive's linking fn during linking phase attr[name] = interpolateFn(scope); ($$observers[name] || ($$observers[name] = [])).$$inter = true; (attr.$$observers && attr.$$observers[name].$$scope || scope). $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { //special case for class attribute addition + removal //so that class changes can tap into the animation //hooks provided by the $animate service. Be sure to //skip animations when the first digest occurs (when //both the new and the old values are the same) since //the CSS classes are the non-interpolated values if (name === 'class' && newValue !== oldValue) { attr.$updateClass(newValue, oldValue); } else { attr.$set(name, newValue); } }); } }; } }); } /** * This is a special jqLite.replaceWith, which can replace items which * have no parents, provided that the containing jqLite collection is provided. * * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes * in the root of the tree. * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep * the shell, but replace its DOM node reference. * @param {Node} newNode The new DOM node. */ function replaceWith($rootElement, elementsToRemove, newNode) { var firstElementToRemove = elementsToRemove[0], removeCount = elementsToRemove.length, parent = firstElementToRemove.parentNode, i, ii; if ($rootElement) { for (i = 0, ii = $rootElement.length; i < ii; i++) { if ($rootElement[i] === firstElementToRemove) { $rootElement[i++] = newNode; for (var j = i, j2 = j + removeCount - 1, jj = $rootElement.length; j < jj; j++, j2++) { if (j2 < jj) { $rootElement[j] = $rootElement[j2]; } else { delete $rootElement[j]; } } $rootElement.length -= removeCount - 1; // If the replaced element is also the jQuery .context then replace it // .context is a deprecated jQuery api, so we should set it only when jQuery set it // http://api.jquery.com/context/ if ($rootElement.context === firstElementToRemove) { $rootElement.context = newNode; } break; } } } if (parent) { parent.replaceChild(newNode, firstElementToRemove); } // Append all the `elementsToRemove` to a fragment. This will... // - remove them from the DOM // - allow them to still be traversed with .nextSibling // - allow a single fragment.qSA to fetch all elements being removed var fragment = window.document.createDocumentFragment(); for (i = 0; i < removeCount; i++) { fragment.appendChild(elementsToRemove[i]); } if (jqLite.hasData(firstElementToRemove)) { // Copy over user data (that includes Angular's $scope etc.). Don't copy private // data here because there's no public interface in jQuery to do that and copying over // event listeners (which is the main use of private data) wouldn't work anyway. jqLite.data(newNode, jqLite.data(firstElementToRemove)); // Remove $destroy event listeners from `firstElementToRemove` jqLite(firstElementToRemove).off('$destroy'); } // Cleanup any data/listeners on the elements and children. // This includes invoking the $destroy event on any elements with listeners. jqLite.cleanData(fragment.querySelectorAll('*')); // Update the jqLite collection to only contain the `newNode` for (i = 1; i < removeCount; i++) { delete elementsToRemove[i]; } elementsToRemove[0] = newNode; elementsToRemove.length = 1; } function cloneAndAnnotateFn(fn, annotation) { return extend(function() { return fn.apply(null, arguments); }, fn, annotation); } function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { try { linkFn(scope, $element, attrs, controllers, transcludeFn); } catch (e) { $exceptionHandler(e, startingTag($element)); } } // Set up $watches for isolate scope and controller bindings. function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { var removeWatchCollection = []; var initialChanges = {}; var changes; forEach(bindings, function initializeBinding(definition, scopeName) { var attrName = definition.attrName, optional = definition.optional, mode = definition.mode, // @, =, <, or & lastValue, parentGet, parentSet, compare, removeWatch; switch (mode) { case '@': if (!optional && !hasOwnProperty.call(attrs, attrName)) { destination[scopeName] = attrs[attrName] = undefined; } removeWatch = attrs.$observe(attrName, function(value) { if (isString(value) || isBoolean(value)) { var oldValue = destination[scopeName]; recordChanges(scopeName, value, oldValue); destination[scopeName] = value; } }); attrs.$$observers[attrName].$$scope = scope; lastValue = attrs[attrName]; if (isString(lastValue)) { // If the attribute has been provided then we trigger an interpolation to ensure // the value is there for use in the link fn destination[scopeName] = $interpolate(lastValue)(scope); } else if (isBoolean(lastValue)) { // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted // the value to boolean rather than a string, so we special case this situation destination[scopeName] = lastValue; } initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); removeWatchCollection.push(removeWatch); break; case '=': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; parentGet = $parse(attrs[attrName]); if (parentGet.literal) { compare = equals; } else { // eslint-disable-next-line no-self-compare compare = function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }; } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest lastValue = destination[scopeName] = parentGet(scope); throw $compileMinErr('nonassign', 'Expression \'{0}\' in attribute \'{1}\' used with directive \'{2}\' is non-assignable!', attrs[attrName], attrName, directive.name); }; lastValue = destination[scopeName] = parentGet(scope); var parentValueWatch = function parentValueWatch(parentValue) { if (!compare(parentValue, destination[scopeName])) { // we are out of sync and need to copy if (!compare(parentValue, lastValue)) { // parent changed and it has precedence destination[scopeName] = parentValue; } else { // if the parent can be assigned then do so parentSet(scope, parentValue = destination[scopeName]); } } lastValue = parentValue; return lastValue; }; parentValueWatch.$stateful = true; if (definition.collection) { removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); } else { removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); } removeWatchCollection.push(removeWatch); break; case '<': if (!hasOwnProperty.call(attrs, attrName)) { if (optional) break; attrs[attrName] = undefined; } if (optional && !attrs[attrName]) break; parentGet = $parse(attrs[attrName]); var deepWatch = parentGet.literal; var initialValue = destination[scopeName] = parentGet(scope); initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) { if (oldValue === newValue) { if (oldValue === initialValue || (deepWatch && equals(oldValue, initialValue))) { return; } oldValue = initialValue; } recordChanges(scopeName, newValue, oldValue); destination[scopeName] = newValue; }, deepWatch); removeWatchCollection.push(removeWatch); break; case '&': // Don't assign Object.prototype method to scope parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; // Don't assign noop to destination if expression is not valid if (parentGet === noop && optional) break; destination[scopeName] = function(locals) { return parentGet(scope, locals); }; break; } }); function recordChanges(key, currentValue, previousValue) { if (isFunction(destination.$onChanges) && currentValue !== previousValue && // eslint-disable-next-line no-self-compare (currentValue === currentValue || previousValue === previousValue)) { // If we have not already scheduled the top level onChangesQueue handler then do so now if (!onChangesQueue) { scope.$$postDigest(flushOnChangesQueue); onChangesQueue = []; } // If we have not already queued a trigger of onChanges for this controller then do so now if (!changes) { changes = {}; onChangesQueue.push(triggerOnChangesHook); } // If the has been a change on this property already then we need to reuse the previous value if (changes[key]) { previousValue = changes[key].previousValue; } // Store this change changes[key] = new SimpleChange(previousValue, currentValue); } } function triggerOnChangesHook() { destination.$onChanges(changes); // Now clear the changes so that we schedule onChanges when more changes arrive changes = undefined; } return { initialChanges: initialChanges, removeWatches: removeWatchCollection.length && function removeWatches() { for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { removeWatchCollection[i](); } } }; } }]; } function SimpleChange(previous, current) { this.previousValue = previous; this.currentValue = current; } SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; var PREFIX_REGEXP = /^((?:x|data)[:\-_])/i; /** * Converts all accepted directives format into proper directive name. * @param name Name to normalize */ function directiveNormalize(name) { return camelCase(name.replace(PREFIX_REGEXP, '')); } /** * @ngdoc type * @name $compile.directive.Attributes * * @description * A shared object between directive compile / linking functions which contains normalized DOM * element attributes. The values reflect current binding state `{{ }}`. The normalization is * needed since all of these are treated as equivalent in Angular: * * ``` * * ``` */ /** * @ngdoc property * @name $compile.directive.Attributes#$attr * * @description * A map of DOM element attribute names to the normalized name. This is * needed to do reverse lookup from normalized name back to actual name. */ /** * @ngdoc method * @name $compile.directive.Attributes#$set * @kind function * * @description * Set DOM element attribute value. * * * @param {string} name Normalized element attribute name of the property to modify. The name is * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} * property to the original name. * @param {string} value Value to set the attribute to. The value can be an interpolated string. */ /** * Closure compiler type information */ function nodesetLinkingFn( /* angular.Scope */ scope, /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ) {} function directiveLinkingFn( /* nodesetLinkingFn */ nodesetLinkingFn, /* angular.Scope */ scope, /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn ) {} function tokenDifference(str1, str2) { var values = '', tokens1 = str1.split(/\s+/), tokens2 = str2.split(/\s+/); outer: for (var i = 0; i < tokens1.length; i++) { var token = tokens1[i]; for (var j = 0; j < tokens2.length; j++) { if (token === tokens2[j]) continue outer; } values += (values.length > 0 ? ' ' : '') + token; } return values; } function removeComments(jqNodes) { jqNodes = jqLite(jqNodes); var i = jqNodes.length; if (i <= 1) { return jqNodes; } while (i--) { var node = jqNodes[i]; if (node.nodeType === NODE_TYPE_COMMENT || (node.nodeType === NODE_TYPE_TEXT && node.nodeValue.trim() === '')) { splice.call(jqNodes, i, 1); } } return jqNodes; } var $controllerMinErr = minErr('$controller'); var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; function identifierForController(controller, ident) { if (ident && isString(ident)) return ident; if (isString(controller)) { var match = CNTRL_REG.exec(controller); if (match) return match[3]; } } /** * @ngdoc provider * @name $controllerProvider * @this * * @description * The {@link ng.$controller $controller service} is used by Angular to create new * controllers. * * This provider allows controller registration via the * {@link ng.$controllerProvider#register register} method. */ function $ControllerProvider() { var controllers = {}, globals = false; /** * @ngdoc method * @name $controllerProvider#has * @param {string} name Controller name to check. */ this.has = function(name) { return controllers.hasOwnProperty(name); }; /** * @ngdoc method * @name $controllerProvider#register * @param {string|Object} name Controller name, or an object map of controllers where the keys are * the names and the values are the constructors. * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI * annotations in the array notation). */ this.register = function(name, constructor) { assertNotHasOwnProperty(name, 'controller'); if (isObject(name)) { extend(controllers, name); } else { controllers[name] = constructor; } }; /** * @ngdoc method * @name $controllerProvider#allowGlobals * * @deprecated * sinceVersion="v1.3.0" * removeVersion="v1.7.0" * This method of finding controllers has been deprecated. * * @description If called, allows `$controller` to find controller constructors on `window` * */ this.allowGlobals = function() { globals = true; }; this.$get = ['$injector', '$window', function($injector, $window) { /** * @ngdoc service * @name $controller * @requires $injector * * @param {Function|string} constructor If called with a function then it's considered to be the * controller constructor function. Otherwise it's considered to be a string which is used * to retrieve the controller constructor using the following steps: * * * check if a controller with given name is registered via `$controllerProvider` * * check if evaluating the string on the current scope returns a constructor * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global * `window` object (not recommended) * * The string can use the `controller as property` syntax, where the controller instance is published * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this * to work correctly. * * @param {Object} locals Injection locals for Controller. * @return {Object} Instance of given controller. * * @description * `$controller` service is responsible for instantiating controllers. * * It's just a simple call to {@link auto.$injector $injector}, but extracted into * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). */ return function $controller(expression, locals, later, ident) { // PRIVATE API: // param `later` --- indicates that the controller's constructor is invoked at a later time. // If true, $controller will allocate the object with the correct // prototype chain, but will not invoke the controller until a returned // callback is invoked. // param `ident` --- An optional label which overrides the label parsed from the controller // expression, if any. var instance, match, constructor, identifier; later = later === true; if (ident && isString(ident)) { identifier = ident; } if (isString(expression)) { match = expression.match(CNTRL_REG); if (!match) { throw $controllerMinErr('ctrlfmt', 'Badly formed controller string \'{0}\'. ' + 'Must match `__name__ as __id__` or `__name__`.', expression); } constructor = match[1]; identifier = identifier || match[3]; expression = controllers.hasOwnProperty(constructor) ? controllers[constructor] : getter(locals.$scope, constructor, true) || (globals ? getter($window, constructor, true) : undefined); if (!expression) { throw $controllerMinErr('ctrlreg', 'The controller with the name \'{0}\' is not registered.', constructor); } assertArgFn(expression, constructor, true); } if (later) { // Instantiate controller later: // This machinery is used to create an instance of the object before calling the // controller's constructor itself. // // This allows properties to be added to the controller before the constructor is // invoked. Primarily, this is used for isolate scope bindings in $compile. // // This feature is not intended for use by applications, and is thus not documented // publicly. // Object creation: http://jsperf.com/create-constructor/2 var controllerPrototype = (isArray(expression) ? expression[expression.length - 1] : expression).prototype; instance = Object.create(controllerPrototype || null); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); } return extend(function $controllerInit() { var result = $injector.invoke(expression, instance, locals, constructor); if (result !== instance && (isObject(result) || isFunction(result))) { instance = result; if (identifier) { // If result changed, re-assign controllerAs value to scope. addIdentifier(locals, identifier, instance, constructor || expression.name); } } return instance; }, { instance: instance, identifier: identifier }); } instance = $injector.instantiate(expression, locals, constructor); if (identifier) { addIdentifier(locals, identifier, instance, constructor || expression.name); } return instance; }; function addIdentifier(locals, identifier, instance, name) { if (!(locals && isObject(locals.$scope))) { throw minErr('$controller')('noscp', 'Cannot export controller \'{0}\' as \'{1}\'! No $scope object provided via `locals`.', name, identifier); } locals.$scope[identifier] = instance; } }]; } /** * @ngdoc service * @name $document * @requires $window * @this * * @description * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. * * @example

          $document title:

          window.document title:

          angular.module('documentExample', []) .controller('ExampleController', ['$scope', '$document', function($scope, $document) { $scope.title = $document[0].title; $scope.windowTitle = angular.element(window.document)[0].title; }]);
          */ function $DocumentProvider() { this.$get = ['$window', function(window) { return jqLite(window.document); }]; } /** * @ngdoc service * @name $exceptionHandler * @requires ng.$log * @this * * @description * Any uncaught exception in angular expressions is delegated to this service. * The default implementation simply delegates to `$log.error` which logs it into * the browser console. * * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. * * ## Example: * * The example below will overwrite the default `$exceptionHandler` in order to (a) log uncaught * errors to the backend for later inspection by the developers and (b) to use `$log.warn()` instead * of `$log.error()`. * * ```js * angular. * module('exceptionOverwrite', []). * factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) { * return function myExceptionHandler(exception, cause) { * logErrorsToBackend(exception, cause); * $log.warn(exception, cause); * }; * }]); * ``` * *
          * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} * (unless executed during a digest). * * If you wish, you can manually delegate exceptions, e.g. * `try { ... } catch(e) { $exceptionHandler(e); }` * * @param {Error} exception Exception associated with the error. * @param {string=} cause Optional information about the context in which * the error was thrown. * */ function $ExceptionHandlerProvider() { this.$get = ['$log', function($log) { return function(exception, cause) { $log.error.apply($log, arguments); }; }]; } var $$ForceReflowProvider = /** @this */ function() { this.$get = ['$document', function($document) { return function(domNode) { //the line below will force the browser to perform a repaint so //that all the animated elements within the animation frame will //be properly updated and drawn on screen. This is required to //ensure that the preparation animation is properly flushed so that //the active state picks up from there. DO NOT REMOVE THIS LINE. //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND //WILL TAKE YEARS AWAY FROM YOUR LIFE. if (domNode) { if (!domNode.nodeType && domNode instanceof jqLite) { domNode = domNode[0]; } } else { domNode = $document[0].body; } return domNode.offsetWidth + 1; }; }]; }; var APPLICATION_JSON = 'application/json'; var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; var JSON_START = /^\[|^\{(?!\{)/; var JSON_ENDS = { '[': /]$/, '{': /}$/ }; var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/; var $httpMinErr = minErr('$http'); var $httpMinErrLegacyFn = function(method) { return function() { throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); }; }; function serializeValue(v) { if (isObject(v)) { return isDate(v) ? v.toISOString() : toJson(v); } return v; } /** @this */ function $HttpParamSerializerProvider() { /** * @ngdoc service * @name $httpParamSerializer * @description * * Default {@link $http `$http`} params serializer that converts objects to strings * according to the following rules: * * * `{'foo': 'bar'}` results in `foo=bar` * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object) * * Note that serializer will sort the request parameters alphabetically. * */ this.$get = function() { return function ngParamSerializer(params) { if (!params) return ''; var parts = []; forEachSorted(params, function(value, key) { if (value === null || isUndefined(value)) return; if (isArray(value)) { forEach(value, function(v) { parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); }); } else { parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); } }); return parts.join('&'); }; }; } /** @this */ function $HttpParamSerializerJQLikeProvider() { /** * @ngdoc service * @name $httpParamSerializerJQLike * * @description * * Alternative {@link $http `$http`} params serializer that follows * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. * The serializer will also sort the params alphabetically. * * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: * * ```js * $http({ * url: myUrl, * method: 'GET', * params: myParams, * paramSerializer: '$httpParamSerializerJQLike' * }); * ``` * * It is also possible to set it as the default `paramSerializer` in the * {@link $httpProvider#defaults `$httpProvider`}. * * Additionally, you can inject the serializer and use it explicitly, for example to serialize * form data for submission: * * ```js * .controller(function($http, $httpParamSerializerJQLike) { * //... * * $http({ * url: myUrl, * method: 'POST', * data: $httpParamSerializerJQLike(myData), * headers: { * 'Content-Type': 'application/x-www-form-urlencoded' * } * }); * * }); * ``` * * */ this.$get = function() { return function jQueryLikeParamSerializer(params) { if (!params) return ''; var parts = []; serialize(params, '', true); return parts.join('&'); function serialize(toSerialize, prefix, topLevel) { if (toSerialize === null || isUndefined(toSerialize)) return; if (isArray(toSerialize)) { forEach(toSerialize, function(value, index) { serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); }); } else if (isObject(toSerialize) && !isDate(toSerialize)) { forEachSorted(toSerialize, function(value, key) { serialize(value, prefix + (topLevel ? '' : '[') + key + (topLevel ? '' : ']')); }); } else { parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); } } }; }; } function defaultHttpResponseTransform(data, headers) { if (isString(data)) { // Strip json vulnerability protection prefix and trim whitespace var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); if (tempData) { var contentType = headers('Content-Type'); if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { data = fromJson(tempData); } } } return data; } function isJsonLike(str) { var jsonStart = str.match(JSON_START); return jsonStart && JSON_ENDS[jsonStart[0]].test(str); } /** * Parse headers into key value object * * @param {string} headers Raw headers as a string * @returns {Object} Parsed headers as key value object */ function parseHeaders(headers) { var parsed = createMap(), i; function fillInParsed(key, val) { if (key) { parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; } } if (isString(headers)) { forEach(headers.split('\n'), function(line) { i = line.indexOf(':'); fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); }); } else if (isObject(headers)) { forEach(headers, function(headerVal, headerKey) { fillInParsed(lowercase(headerKey), trim(headerVal)); }); } return parsed; } /** * Returns a function that provides access to parsed headers. * * Headers are lazy parsed when first requested. * @see parseHeaders * * @param {(string|Object)} headers Headers to provide access to. * @returns {function(string=)} Returns a getter function which if called with: * * - if called with an argument returns a single header value or null * - if called with no arguments returns an object containing all headers. */ function headersGetter(headers) { var headersObj; return function(name) { if (!headersObj) headersObj = parseHeaders(headers); if (name) { var value = headersObj[lowercase(name)]; if (value === undefined) { value = null; } return value; } return headersObj; }; } /** * Chain all given functions * * This function is used for both request and response transforming * * @param {*} data Data to transform. * @param {function(string=)} headers HTTP headers getter fn. * @param {number} status HTTP status code of the response. * @param {(Function|Array.)} fns Function or an array of functions. * @returns {*} Transformed data. */ function transformData(data, headers, status, fns) { if (isFunction(fns)) { return fns(data, headers, status); } forEach(fns, function(fn) { data = fn(data, headers, status); }); return data; } function isSuccess(status) { return 200 <= status && status < 300; } /** * @ngdoc provider * @name $httpProvider * @this * * @description * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. * */ function $HttpProvider() { /** * @ngdoc property * @name $httpProvider#defaults * @description * * Object containing default values for all {@link ng.$http $http} requests. * * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses * by default. See {@link $http#caching $http Caching} for more information. * * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. * Defaults value is `'XSRF-TOKEN'`. * * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. * * - **`defaults.headers`** - {Object} - Default headers for all $http requests. * Refer to {@link ng.$http#setting-http-headers $http} for documentation on * setting default headers. * - **`defaults.headers.common`** * - **`defaults.headers.post`** * - **`defaults.headers.put`** * - **`defaults.headers.patch`** * * * - **`defaults.paramSerializer`** - `{string|function(Object):string}` - A function * used to the prepare string representation of request parameters (specified as an object). * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. * **/ var defaults = this.defaults = { // transform incoming response data transformResponse: [defaultHttpResponseTransform], // transform outgoing request data transformRequest: [function(d) { return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; }], // default headers headers: { common: { 'Accept': 'application/json, text/plain, */*' }, post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) }, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', paramSerializer: '$httpParamSerializer' }; var useApplyAsync = false; /** * @ngdoc method * @name $httpProvider#useApplyAsync * @description * * Configure $http service to combine processing of multiple http responses received at around * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in * significant performance improvement for bigger applications that make many HTTP requests * concurrently (common during application bootstrap). * * Defaults to false. If no value is specified, returns the current configured value. * * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window * to load and share the same digest cycle. * * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. * otherwise, returns the current configured value. **/ this.useApplyAsync = function(value) { if (isDefined(value)) { useApplyAsync = !!value; return this; } return useApplyAsync; }; var useLegacyPromise = true; /** * @ngdoc method * @name $httpProvider#useLegacyPromiseExtensions * @description * * @deprecated * sinceVersion="v1.4.4" * removeVersion="v1.6.0" * This method will be removed in v1.6.0 along with the legacy promise methods. * * Configure `$http` service to return promises without the shorthand methods `success` and `error`. * This should be used to make sure that applications work without these methods. * * Defaults to true. If no value is specified, returns the current configured value. * * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. * * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. * otherwise, returns the current configured value. **/ this.useLegacyPromiseExtensions = function(value) { if (isDefined(value)) { useLegacyPromise = !!value; return this; } return useLegacyPromise; }; /** * @ngdoc property * @name $httpProvider#interceptors * @description * * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} * pre-processing of request or postprocessing of responses. * * These service factories are ordered by request, i.e. they are applied in the same order as the * array, on request, but reverse order, on response. * * {@link ng.$http#interceptors Interceptors detailed info} **/ var interceptorFactories = this.interceptors = []; this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { var defaultCache = $cacheFactory('$http'); /** * Make sure that default param serializer is exposed as a function */ defaults.paramSerializer = isString(defaults.paramSerializer) ? $injector.get(defaults.paramSerializer) : defaults.paramSerializer; /** * Interceptors stored in reverse order. Inner interceptors before outer interceptors. * The reversal is needed so that we can build up the interception chain around the * server request. */ var reversedInterceptors = []; forEach(interceptorFactories, function(interceptorFactory) { reversedInterceptors.unshift(isString(interceptorFactory) ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); }); /** * @ngdoc service * @kind function * @name $http * @requires ng.$httpBackend * @requires $cacheFactory * @requires $rootScope * @requires $q * @requires $injector * * @description * The `$http` service is a core Angular service that facilitates communication with the remote * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). * * For unit testing applications that use `$http` service, see * {@link ngMock.$httpBackend $httpBackend mock}. * * For a higher level of abstraction, please check out the {@link ngResource.$resource * $resource} service. * * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage * it is important to familiarize yourself with these APIs and the guarantees they provide. * * * ## General usage * The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} — * that is used to generate an HTTP request and returns a {@link ng.$q promise}. * * ```js * // Simple GET request example: * $http({ * method: 'GET', * url: '/someUrl' * }).then(function successCallback(response) { * // this callback will be called asynchronously * // when the response is available * }, function errorCallback(response) { * // called asynchronously if an error occurs * // or server returns response with an error status. * }); * ``` * * The response object has these properties: * * - **data** – `{string|Object}` – The response body transformed with the transform * functions. * - **status** – `{number}` – HTTP status code of the response. * - **headers** – `{function([headerName])}` – Header getter function. * - **config** – `{Object}` – The configuration object that was used to generate the request. * - **statusText** – `{string}` – HTTP status text of the response. * * A response status code between 200 and 299 is considered a success status and will result in * the success callback being called. Any response status code outside of that range is * considered an error status and will result in the error callback being called. * Also, status codes less than -1 are normalized to zero. -1 usually means the request was * aborted, e.g. using a `config.timeout`. * Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning * that the outcome (success or error) will be determined by the final response status code. * * * ## Shortcut methods * * Shortcut methods are also available. All shortcut methods require passing in the URL, and * request data must be passed in for POST/PUT requests. An optional config can be passed as the * last argument. * * ```js * $http.get('/someUrl', config).then(successCallback, errorCallback); * $http.post('/someUrl', data, config).then(successCallback, errorCallback); * ``` * * Complete list of shortcut methods: * * - {@link ng.$http#get $http.get} * - {@link ng.$http#head $http.head} * - {@link ng.$http#post $http.post} * - {@link ng.$http#put $http.put} * - {@link ng.$http#delete $http.delete} * - {@link ng.$http#jsonp $http.jsonp} * - {@link ng.$http#patch $http.patch} * * * ## Writing Unit Tests that use $http * When unit testing (using {@link ngMock ngMock}), it is necessary to call * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending * request using trained responses. * * ``` * $httpBackend.expectGET(...); * $http.get(...); * $httpBackend.flush(); * ``` * * ## Deprecation Notice *
          * The `$http` legacy promise methods `success` and `error` have been deprecated and will be * removed in v1.6.0. * Use the standard `then` method instead. * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. *
          * * ## Setting HTTP Headers * * The $http service will automatically add certain HTTP headers to all requests. These defaults * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration * object, which currently contains this default configuration: * * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): * - Accept: application/json, text/plain, \*/\* * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) * - `Content-Type: application/json` * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) * - `Content-Type: application/json` * * To add or overwrite these defaults, simply add or remove a property from these configuration * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object * with the lowercased HTTP method name as the key, e.g. * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. * * The defaults can also be set at runtime via the `$http.defaults` object in the same * fashion. For example: * * ``` * module.run(function($http) { * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; * }); * ``` * * In addition, you can supply a `headers` property in the config object passed when * calling `$http(config)`, which overrides the defaults without changing them globally. * * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, * Use the `headers` property, setting the desired header to `undefined`. For example: * * ```js * var req = { * method: 'POST', * url: 'http://example.com', * headers: { * 'Content-Type': undefined * }, * data: { test: 'test' } * } * * $http(req).then(function(){...}, function(){...}); * ``` * * ## Transforming Requests and Responses * * Both requests and responses can be transformed using transformation functions: `transformRequest` * and `transformResponse`. These properties can be a single function that returns * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, * which allows you to `push` or `unshift` a new transformation function into the transformation chain. * *
          * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest * function will be reflected on the scope and in any templates where the object is data-bound. * To prevent this, transform functions should have no side-effects. * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. *
          * * ### Default Transformations * * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and * `defaults.transformResponse` properties. If a request does not provide its own transformations * then these will be applied. * * You can augment or replace the default transformations by modifying these properties by adding to or * replacing the array. * * Angular provides the following default transformations: * * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): * * - If the `data` property of the request configuration object contains an object, serialize it * into JSON format. * * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): * * - If XSRF prefix is detected, strip it (see Security Considerations section below). * - If JSON response is detected, deserialize it using a JSON parser. * * * ### Overriding the Default Transformations Per Request * * If you wish to override the request/response transformations only for a single request then provide * `transformRequest` and/or `transformResponse` properties on the configuration object passed * into `$http`. * * Note that if you provide these properties on the config object the default transformations will be * overwritten. If you wish to augment the default transformations then you must include them in your * local transformation array. * * The following code demonstrates adding a new response transformation to be run after the default response * transformations have been run. * * ```js * function appendTransform(defaults, transform) { * * // We can't guarantee that the default transformation is an array * defaults = angular.isArray(defaults) ? defaults : [defaults]; * * // Append the new transformation to the defaults * return defaults.concat(transform); * } * * $http({ * url: '...', * method: 'GET', * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { * return doTransform(value); * }) * }); * ``` * * * ## Caching * * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must * set the config.cache value or the default cache value to TRUE or to a cache object (created * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes * precedence over the default cache value. * * In order to: * * cache all responses - set the default cache value to TRUE or to a cache object * * cache a specific response - set config.cache value to TRUE or to a cache object * * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, * then the default `$cacheFactory("$http")` object is used. * * The default cache value can be set by updating the * {@link ng.$http#defaults `$http.defaults.cache`} property or the * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. * * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using * the relevant cache object. The next time the same request is made, the response is returned * from the cache without sending a request to the server. * * Take note that: * * * Only GET and JSONP requests are cached. * * The cache key is the request URL including search parameters; headers are not considered. * * Cached responses are returned asynchronously, in the same way as responses from the server. * * If multiple identical requests are made using the same cache, which is not yet populated, * one request will be made to the server and remaining requests will return the same response. * * A cache-control header on the response does not affect if or how responses are cached. * * * ## Interceptors * * Before you start creating interceptors, be sure to understand the * {@link ng.$q $q and deferred/promise APIs}. * * For purposes of global error handling, authentication, or any kind of synchronous or * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be * able to intercept requests before they are handed to the server and * responses before they are handed over to the application code that * initiated these requests. The interceptors leverage the {@link ng.$q * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. * * The interceptors are service factories that are registered with the `$httpProvider` by * adding them to the `$httpProvider.interceptors` array. The factory is called and * injected with dependencies (if specified) and returns the interceptor. * * There are two kinds of interceptors (and two kinds of rejection interceptors): * * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to * modify the `config` object or create a new one. The function needs to return the `config` * object directly, or a promise containing the `config` or a new `config` object. * * `requestError`: interceptor gets called when a previous interceptor threw an error or * resolved with a rejection. * * `response`: interceptors get called with http `response` object. The function is free to * modify the `response` object or create a new one. The function needs to return the `response` * object directly, or as a promise containing the `response` or a new `response` object. * * `responseError`: interceptor gets called when a previous interceptor threw an error or * resolved with a rejection. * * * ```js * // register the interceptor as a service * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { * return { * // optional method * 'request': function(config) { * // do something on success * return config; * }, * * // optional method * 'requestError': function(rejection) { * // do something on error * if (canRecover(rejection)) { * return responseOrNewPromise * } * return $q.reject(rejection); * }, * * * * // optional method * 'response': function(response) { * // do something on success * return response; * }, * * // optional method * 'responseError': function(rejection) { * // do something on error * if (canRecover(rejection)) { * return responseOrNewPromise * } * return $q.reject(rejection); * } * }; * }); * * $httpProvider.interceptors.push('myHttpInterceptor'); * * * // alternatively, register the interceptor via an anonymous factory * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { * return { * 'request': function(config) { * // same as above * }, * * 'response': function(response) { * // same as above * } * }; * }); * ``` * * ## Security Considerations * * When designing web applications, consider security threats from: * * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) * * Both server and the client must cooperate in order to eliminate these threats. Angular comes * pre-configured with strategies that address these issues, but for this to work backend server * cooperation is required. * * ### JSON Vulnerability Protection * * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) * allows third party website to turn your JSON resource URL into * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To * counter this your server can prefix all JSON requests with following string `")]}',\n"`. * Angular will automatically strip the prefix before processing it as JSON. * * For example if your server needs to return: * ```js * ['one','two'] * ``` * * which is vulnerable to attack, your server can return: * ```js * )]}', * ['one','two'] * ``` * * Angular will strip the prefix, before processing the JSON. * * * ### Cross Site Request Forgery (XSRF) Protection * * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by * which the attacker can trick an authenticated user into unknowingly executing actions on your * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the * cookie, your server can be assured that the XHR came from JavaScript running on your domain. * The header will not be set for cross-domain requests. * * To take advantage of this, your server needs to set a token in a JavaScript readable session * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure * that only JavaScript running on your domain could have sent the request. The token must be * unique for each user and must be verifiable by the server (to prevent the JavaScript from * making up its own tokens). We recommend that the token is a digest of your site's * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) * for added security. * * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, * or the per-request config object. * * In order to prevent collisions in environments where multiple Angular apps share the * same domain or subdomain, we recommend that each application uses unique cookie name. * * @param {object} config Object describing the request to be made and how it should be * processed. The object has following properties: * * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. * - **params** – `{Object.}` – Map of strings or objects which will be serialized * with the `paramSerializer` and appended as GET parameters. * - **data** – `{string|Object}` – Data to be sent as the request message data. * - **headers** – `{Object}` – Map of strings or functions which return strings representing * HTTP headers to send to the server. If the return value of a function is null, the * header will not be sent. Functions accept a config object as an argument. * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. * The handler will be called in the context of a `$apply` block. * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. * The handler will be called in the context of a `$apply` block. * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. * - **transformRequest** – * `{function(data, headersGetter)|Array.}` – * transform function or an array of such functions. The transform function takes the http * request body and headers and returns its transformed (typically serialized) version. * See {@link ng.$http#overriding-the-default-transformations-per-request * Overriding the Default Transformations} * - **transformResponse** – * `{function(data, headersGetter, status)|Array.}` – * transform function or an array of such functions. The transform function takes the http * response body, headers and status and returns its transformed (typically deserialized) version. * See {@link ng.$http#overriding-the-default-transformations-per-request * Overriding the Default Transformations} * - **paramSerializer** - `{string|function(Object):string}` - A function used to * prepare the string representation of request parameters (specified as an object). * If specified as string, it is interpreted as function registered with the * {@link $injector $injector}, which means you can create your own serializer * by registering it as a {@link auto.$provide#service service}. * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} * - **cache** – `{boolean|Object}` – A boolean value or object created with * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. * See {@link $http#caching $http Caching} for more information. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} * that should abort the request when resolved. * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) * for more information. * - **responseType** - `{string}` - see * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). * * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object * when the request succeeds or fails. * * * @property {Array.} pendingRequests Array of config objects for currently pending * requests. This is primarily meant to be used for debugging purposes. * * * @example

          http status code: {{status}}
          http response data: {{data}}
          angular.module('httpExample', []) .controller('FetchController', ['$scope', '$http', '$templateCache', function($scope, $http, $templateCache) { $scope.method = 'GET'; $scope.url = 'http-hello.html'; $scope.fetch = function() { $scope.code = null; $scope.response = null; $http({method: $scope.method, url: $scope.url, cache: $templateCache}). then(function(response) { $scope.status = response.status; $scope.data = response.data; }, function(response) { $scope.data = response.data || 'Request failed'; $scope.status = response.status; }); }; $scope.updateModel = function(method, url) { $scope.method = method; $scope.url = url; }; }]); Hello, $http! var status = element(by.binding('status')); var data = element(by.binding('data')); var fetchBtn = element(by.id('fetchbtn')); var sampleGetBtn = element(by.id('samplegetbtn')); var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); it('should make an xhr GET request', function() { sampleGetBtn.click(); fetchBtn.click(); expect(status.getText()).toMatch('200'); expect(data.getText()).toMatch(/Hello, \$http!/); }); // Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 // it('should make a JSONP request to angularjs.org', function() { // var sampleJsonpBtn = element(by.id('samplejsonpbtn')); // sampleJsonpBtn.click(); // fetchBtn.click(); // expect(status.getText()).toMatch('200'); // expect(data.getText()).toMatch(/Super Hero!/); // }); it('should make JSONP request to invalid URL and invoke the error handler', function() { invalidJsonpBtn.click(); fetchBtn.click(); expect(status.getText()).toMatch('0'); expect(data.getText()).toMatch('Request failed'); });
          */ function $http(requestConfig) { if (!isObject(requestConfig)) { throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); } if (!isString(requestConfig.url)) { throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); } var config = extend({ method: 'get', transformRequest: defaults.transformRequest, transformResponse: defaults.transformResponse, paramSerializer: defaults.paramSerializer }, requestConfig); config.headers = mergeHeaders(requestConfig); config.method = uppercase(config.method); config.paramSerializer = isString(config.paramSerializer) ? $injector.get(config.paramSerializer) : config.paramSerializer; var requestInterceptors = []; var responseInterceptors = []; var promise = $q.when(config); // apply interceptors forEach(reversedInterceptors, function(interceptor) { if (interceptor.request || interceptor.requestError) { requestInterceptors.unshift(interceptor.request, interceptor.requestError); } if (interceptor.response || interceptor.responseError) { responseInterceptors.push(interceptor.response, interceptor.responseError); } }); promise = chainInterceptors(promise, requestInterceptors); promise = promise.then(serverRequest); promise = chainInterceptors(promise, responseInterceptors); if (useLegacyPromise) { promise.success = function(fn) { assertArgFn(fn, 'fn'); promise.then(function(response) { fn(response.data, response.status, response.headers, config); }); return promise; }; promise.error = function(fn) { assertArgFn(fn, 'fn'); promise.then(null, function(response) { fn(response.data, response.status, response.headers, config); }); return promise; }; } else { promise.success = $httpMinErrLegacyFn('success'); promise.error = $httpMinErrLegacyFn('error'); } return promise; function chainInterceptors(promise, interceptors) { for (var i = 0, ii = interceptors.length; i < ii;) { var thenFn = interceptors[i++]; var rejectFn = interceptors[i++]; promise = promise.then(thenFn, rejectFn); } interceptors.length = 0; return promise; } function executeHeaderFns(headers, config) { var headerContent, processedHeaders = {}; forEach(headers, function(headerFn, header) { if (isFunction(headerFn)) { headerContent = headerFn(config); if (headerContent != null) { processedHeaders[header] = headerContent; } } else { processedHeaders[header] = headerFn; } }); return processedHeaders; } function mergeHeaders(config) { var defHeaders = defaults.headers, reqHeaders = extend({}, config.headers), defHeaderName, lowercaseDefHeaderName, reqHeaderName; defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); // using for-in instead of forEach to avoid unnecessary iteration after header has been found defaultHeadersIteration: for (defHeaderName in defHeaders) { lowercaseDefHeaderName = lowercase(defHeaderName); for (reqHeaderName in reqHeaders) { if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { continue defaultHeadersIteration; } } reqHeaders[defHeaderName] = defHeaders[defHeaderName]; } // execute if header value is a function for merged headers return executeHeaderFns(reqHeaders, shallowCopy(config)); } function serverRequest(config) { var headers = config.headers; var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); // strip content-type if data is undefined if (isUndefined(reqData)) { forEach(headers, function(value, header) { if (lowercase(header) === 'content-type') { delete headers[header]; } }); } if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { config.withCredentials = defaults.withCredentials; } // send request return sendReq(config, reqData).then(transformResponse, transformResponse); } function transformResponse(response) { // make a copy since the response must be cacheable var resp = extend({}, response); resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); return (isSuccess(response.status)) ? resp : $q.reject(resp); } } $http.pendingRequests = []; /** * @ngdoc method * @name $http#get * * @description * Shortcut method to perform `GET` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#delete * * @description * Shortcut method to perform `DELETE` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#head * * @description * Shortcut method to perform `HEAD` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#jsonp * * @description * Shortcut method to perform `JSONP` request. * If you would like to customize where and how the callbacks are stored then try overriding * or decorating the {@link $jsonpCallbacks} service. * * @param {string} url Relative or absolute URL specifying the destination of the request. * The name of the callback should be the string `JSON_CALLBACK`. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ createShortMethods('get', 'delete', 'head', 'jsonp'); /** * @ngdoc method * @name $http#post * * @description * Shortcut method to perform `POST` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#put * * @description * Shortcut method to perform `PUT` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ /** * @ngdoc method * @name $http#patch * * @description * Shortcut method to perform `PATCH` request. * * @param {string} url Relative or absolute URL specifying the destination of the request * @param {*} data Request content * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ createShortMethodsWithData('post', 'put', 'patch'); /** * @ngdoc property * @name $http#defaults * * @description * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of * default headers, withCredentials as well as request and response transformations. * * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. */ $http.defaults = defaults; return $http; function createShortMethods(names) { forEach(arguments, function(name) { $http[name] = function(url, config) { return $http(extend({}, config || {}, { method: name, url: url })); }; }); } function createShortMethodsWithData(name) { forEach(arguments, function(name) { $http[name] = function(url, data, config) { return $http(extend({}, config || {}, { method: name, url: url, data: data })); }; }); } /** * Makes the request. * * !!! ACCESSES CLOSURE VARS: * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests */ function sendReq(config, reqData) { var deferred = $q.defer(), promise = deferred.promise, cache, cachedResp, reqHeaders = config.headers, url = buildUrl(config.url, config.paramSerializer(config.params)); $http.pendingRequests.push(config); promise.then(removePendingReq, removePendingReq); if ((config.cache || defaults.cache) && config.cache !== false && (config.method === 'GET' || config.method === 'JSONP')) { cache = isObject(config.cache) ? config.cache : isObject(defaults.cache) ? defaults.cache : defaultCache; } if (cache) { cachedResp = cache.get(url); if (isDefined(cachedResp)) { if (isPromiseLike(cachedResp)) { // cached request has already been sent, but there is no response yet cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); } else { // serving from cache if (isArray(cachedResp)) { resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); } else { resolvePromise(cachedResp, 200, {}, 'OK'); } } } else { // put the promise for the non-transformed response into cache as a placeholder cache.put(url, promise); } } // if we won't have the response in cache, set the xsrf headers and // send the request to the backend if (isUndefined(cachedResp)) { var xsrfValue = urlIsSameOrigin(config.url) ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] : undefined; if (xsrfValue) { reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; } $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, config.withCredentials, config.responseType, createApplyHandlers(config.eventHandlers), createApplyHandlers(config.uploadEventHandlers)); } return promise; function createApplyHandlers(eventHandlers) { if (eventHandlers) { var applyHandlers = {}; forEach(eventHandlers, function(eventHandler, key) { applyHandlers[key] = function(event) { if (useApplyAsync) { $rootScope.$applyAsync(callEventHandler); } else if ($rootScope.$$phase) { callEventHandler(); } else { $rootScope.$apply(callEventHandler); } function callEventHandler() { eventHandler(event); } }; }); return applyHandlers; } } /** * Callback registered to $httpBackend(): * - caches the response if desired * - resolves the raw $http promise * - calls $apply */ function done(status, response, headersString, statusText) { if (cache) { if (isSuccess(status)) { cache.put(url, [status, response, parseHeaders(headersString), statusText]); } else { // remove promise from the cache cache.remove(url); } } function resolveHttpPromise() { resolvePromise(response, status, headersString, statusText); } if (useApplyAsync) { $rootScope.$applyAsync(resolveHttpPromise); } else { resolveHttpPromise(); if (!$rootScope.$$phase) $rootScope.$apply(); } } /** * Resolves the raw $http promise. */ function resolvePromise(response, status, headers, statusText) { //status: HTTP response status code, 0, -1 (aborted by timeout / promise) status = status >= -1 ? status : 0; (isSuccess(status) ? deferred.resolve : deferred.reject)({ data: response, status: status, headers: headersGetter(headers), config: config, statusText: statusText }); } function resolvePromiseWithResult(result) { resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); } function removePendingReq() { var idx = $http.pendingRequests.indexOf(config); if (idx !== -1) $http.pendingRequests.splice(idx, 1); } } function buildUrl(url, serializedParams) { if (serializedParams.length > 0) { url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams; } return url; } }]; } /** * @ngdoc service * @name $xhrFactory * @this * * @description * Factory function used to create XMLHttpRequest objects. * * Replace or decorate this service to create your own custom XMLHttpRequest objects. * * ``` * angular.module('myApp', []) * .factory('$xhrFactory', function() { * return function createXhr(method, url) { * return new window.XMLHttpRequest({mozSystem: true}); * }; * }); * ``` * * @param {string} method HTTP method of the request (GET, POST, PUT, ..) * @param {string} url URL of the request. */ function $xhrFactoryProvider() { this.$get = function() { return function createXhr() { return new window.XMLHttpRequest(); }; }; } /** * @ngdoc service * @name $httpBackend * @requires $jsonpCallbacks * @requires $document * @requires $xhrFactory * @this * * @description * HTTP backend used by the {@link ng.$http service} that delegates to * XMLHttpRequest object or JSONP and deals with browser incompatibilities. * * You should never need to use this service directly, instead use the higher-level abstractions: * {@link ng.$http $http} or {@link ngResource.$resource $resource}. * * During testing this implementation is swapped with {@link ngMock.$httpBackend mock * $httpBackend} which can be trained with responses. */ function $HttpBackendProvider() { this.$get = ['$browser', '$jsonpCallbacks', '$document', '$xhrFactory', function($browser, $jsonpCallbacks, $document, $xhrFactory) { return createHttpBackend($browser, $xhrFactory, $browser.defer, $jsonpCallbacks, $document[0]); }]; } function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { // TODO(vojta): fix the signature return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { $browser.$$incOutstandingRequestCount(); url = url || $browser.url(); if (lowercase(method) === 'jsonp') { var callbackPath = callbacks.createCallback(url); var jsonpDone = jsonpReq(url, callbackPath, function(status, text) { // jsonpReq only ever sets status to 200 (OK), 404 (ERROR) or -1 (WAITING) var response = (status === 200) && callbacks.getResponse(callbackPath); completeRequest(callback, status, response, '', text); callbacks.removeCallback(callbackPath); }); } else { var xhr = createXhr(method, url); xhr.open(method, url, true); forEach(headers, function(value, key) { if (isDefined(value)) { xhr.setRequestHeader(key, value); } }); xhr.onload = function requestLoaded() { var statusText = xhr.statusText || ''; // responseText is the old-school way of retrieving response (supported by IE9) // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) var response = ('response' in xhr) ? xhr.response : xhr.responseText; // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) var status = xhr.status === 1223 ? 204 : xhr.status; // fix status code when it is 0 (0 status is undocumented). // Occurs when accessing file resources or on Android 4.1 stock browser // while retrieving files from application cache. if (status === 0) { status = response ? 200 : urlResolve(url).protocol === 'file' ? 404 : 0; } completeRequest(callback, status, response, xhr.getAllResponseHeaders(), statusText); }; var requestError = function() { // The response is always empty // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error completeRequest(callback, -1, null, null, ''); }; xhr.onerror = requestError; xhr.onabort = requestError; xhr.ontimeout = requestError; forEach(eventHandlers, function(value, key) { xhr.addEventListener(key, value); }); forEach(uploadEventHandlers, function(value, key) { xhr.upload.addEventListener(key, value); }); if (withCredentials) { xhr.withCredentials = true; } if (responseType) { try { xhr.responseType = responseType; } catch (e) { // WebKit added support for the json responseType value on 09/03/2013 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are // known to throw when setting the value "json" as the response type. Other older // browsers implementing the responseType // // The json response type can be ignored if not supported, because JSON payloads are // parsed on the client-side regardless. if (responseType !== 'json') { throw e; } } } xhr.send(isUndefined(post) ? null : post); } if (timeout > 0) { var timeoutId = $browserDefer(timeoutRequest, timeout); } else if (isPromiseLike(timeout)) { timeout.then(timeoutRequest); } function timeoutRequest() { if (jsonpDone) { jsonpDone(); } if (xhr) { xhr.abort(); } } function completeRequest(callback, status, response, headersString, statusText) { // cancel timeout and subsequent timeout promise resolution if (isDefined(timeoutId)) { $browserDefer.cancel(timeoutId); } jsonpDone = xhr = null; callback(status, response, headersString, statusText); $browser.$$completeOutstandingRequest(noop); } }; function jsonpReq(url, callbackPath, done) { url = url.replace('JSON_CALLBACK', callbackPath); // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: // - fetches local scripts via XHR and evals them // - adds and immediately removes script elements from the document var script = rawDocument.createElement('script'), callback = null; script.type = 'text/javascript'; script.src = url; script.async = true; callback = function(event) { removeEventListenerFn(script, 'load', callback); removeEventListenerFn(script, 'error', callback); rawDocument.body.removeChild(script); script = null; var status = -1; var text = 'unknown'; if (event) { if (event.type === 'load' && !callbacks.wasCalled(callbackPath)) { event = { type: 'error' }; } text = event.type; status = event.type === 'error' ? 404 : 200; } if (done) { done(status, text); } }; addEventListenerFn(script, 'load', callback); addEventListenerFn(script, 'error', callback); rawDocument.body.appendChild(script); return callback; } } var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); $interpolateMinErr.throwNoconcat = function(text) { throw $interpolateMinErr('noconcat', 'Error while interpolating: {0}\nStrict Contextual Escaping disallows ' + 'interpolations that concatenate multiple expressions when a trusted value is ' + 'required. See http://docs.angularjs.org/api/ng.$sce', text); }; $interpolateMinErr.interr = function(text, err) { return $interpolateMinErr('interr', 'Can\'t interpolate: {0}\n{1}', text, err.toString()); }; /** * @ngdoc provider * @name $interpolateProvider * @this * * @description * * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. * *
          * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular * template within a Python Jinja template (or any other template language). Mixing templating * languages is **very dangerous**. The embedding template language will not safely escape Angular * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) * security bugs! *
          * * @example
          //demo.label//
          it('should interpolate binding with custom symbols', function() { expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); });
          */ function $InterpolateProvider() { var startSymbol = '{{'; var endSymbol = '}}'; /** * @ngdoc method * @name $interpolateProvider#startSymbol * @description * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. * * @param {string=} value new value to set the starting symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ this.startSymbol = function(value) { if (value) { startSymbol = value; return this; } else { return startSymbol; } }; /** * @ngdoc method * @name $interpolateProvider#endSymbol * @description * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. * * @param {string=} value new value to set the ending symbol to. * @returns {string|self} Returns the symbol when used as getter and self if used as setter. */ this.endSymbol = function(value) { if (value) { endSymbol = value; return this; } else { return endSymbol; } }; this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { var startSymbolLength = startSymbol.length, endSymbolLength = endSymbol.length, escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); function escape(ch) { return '\\\\\\' + ch; } function unescapeText(text) { return text.replace(escapedStartRegexp, startSymbol). replace(escapedEndRegexp, endSymbol); } function stringify(value) { if (value == null) { // null || undefined return ''; } switch (typeof value) { case 'string': break; case 'number': value = '' + value; break; default: value = toJson(value); } return value; } // TODO: this is the same as the constantWatchDelegate in parse.js function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { var unwatch = scope.$watch(function constantInterpolateWatch(scope) { unwatch(); return constantInterp(scope); }, listener, objectEquality); return unwatch; } /** * @ngdoc service * @name $interpolate * @kind function * * @requires $parse * @requires $sce * * @description * * Compiles a string with markup into an interpolation function. This service is used by the * HTML {@link ng.$compile $compile} service for data binding. See * {@link ng.$interpolateProvider $interpolateProvider} for configuring the * interpolation markup. * * * ```js * var $interpolate = ...; // injected * var exp = $interpolate('Hello {{name | uppercase}}!'); * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); * ``` * * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is * `true`, the interpolation function will return `undefined` unless all embedded expressions * evaluate to a value other than `undefined`. * * ```js * var $interpolate = ...; // injected * var context = {greeting: 'Hello', name: undefined }; * * // default "forgiving" mode * var exp = $interpolate('{{greeting}} {{name}}!'); * expect(exp(context)).toEqual('Hello !'); * * // "allOrNothing" mode * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); * expect(exp(context)).toBeUndefined(); * context.name = 'Angular'; * expect(exp(context)).toEqual('Hello Angular!'); * ``` * * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. * * #### Escaped Interpolation * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). * It will be rendered as a regular start/end marker, and will not be interpreted as an expression * or binding. * * This enables web-servers to prevent script injection attacks and defacing attacks, to some * degree, while also enabling code examples to work without relying on the * {@link ng.directive:ngNonBindable ngNonBindable} directive. * * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all * interpolation start/end markers with their escaped counterparts.** * * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered * output when the $interpolate service processes the text. So, for HTML elements interpolated * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, * this is typically useful only when user-data is used in rendering a template from the server, or * when otherwise untrusted data is used by a directive. * * * *
          *

          {{apptitle}}: \{\{ username = "defaced value"; \}\} *

          *

          {{username}} attempts to inject code which will deface the * application, but fails to accomplish their task, because the server has correctly * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) * characters.

          *

          Instead, the result of the attempted script injection is visible, and can be removed * from the database by an administrator.

          *
          *
          *
          * * @knownIssue * It is currently not possible for an interpolated expression to contain the interpolation end * symbol. For example, `{{ '}}' }}` will be incorrectly interpreted as `{{ ' }}` + `' }}`, i.e. * an interpolated expression consisting of a single-quote (`'`) and the `' }}` string. * * @knownIssue * All directives and components must use the standard `{{` `}}` interpolation symbols * in their templates. If you change the application interpolation symbols the {@link $compile} * service will attempt to denormalize the standard symbols to the custom symbols. * The denormalization process is not clever enough to know not to replace instances of the standard * symbols where they would not normally be treated as interpolation symbols. For example in the following * code snippet the closing braces of the literal object will get incorrectly denormalized: * * ``` *
          * ``` * * See https://github.com/angular/angular.js/pull/14610#issuecomment-219401099 for more information. * * @param {string} text The text with markup to interpolate. * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have * embedded expression in order to return an interpolation function. Strings with no * embedded expression will return null for the interpolation function. * @param {string=} trustedContext when provided, the returned function passes the interpolated * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that * provides Strict Contextual Escaping for details. * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined * unless all embedded expressions evaluate to a value other than `undefined`. * @returns {function(context)} an interpolation function which is used to compute the * interpolated string. The function has these parameters: * * - `context`: evaluation context for all expressions embedded in the interpolated text */ function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { // Provide a quick exit and simplified result function for text with no interpolation if (!text.length || text.indexOf(startSymbol) === -1) { var constantInterp; if (!mustHaveExpression) { var unescapedText = unescapeText(text); constantInterp = valueFn(unescapedText); constantInterp.exp = text; constantInterp.expressions = []; constantInterp.$$watchDelegate = constantWatchDelegate; } return constantInterp; } allOrNothing = !!allOrNothing; var startIndex, endIndex, index = 0, expressions = [], parseFns = [], textLength = text.length, exp, concat = [], expressionPositions = []; while (index < textLength) { if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) { if (index !== startIndex) { concat.push(unescapeText(text.substring(index, startIndex))); } exp = text.substring(startIndex + startSymbolLength, endIndex); expressions.push(exp); parseFns.push($parse(exp, parseStringifyInterceptor)); index = endIndex + endSymbolLength; expressionPositions.push(concat.length); concat.push(''); } else { // we did not find an interpolation, so we have to add the remainder to the separators array if (index !== textLength) { concat.push(unescapeText(text.substring(index))); } break; } } // Concatenating expressions makes it hard to reason about whether some combination of // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a // single expression be used for iframe[src], object[src], etc., we ensure that the value // that's used is assigned or constructed by some JS code somewhere that is more testable or // make it obvious that you bound the value to some user controlled value. This helps reduce // the load when auditing for XSS issues. if (trustedContext && concat.length > 1) { $interpolateMinErr.throwNoconcat(text); } if (!mustHaveExpression || expressions.length) { var compute = function(values) { for (var i = 0, ii = expressions.length; i < ii; i++) { if (allOrNothing && isUndefined(values[i])) return; concat[expressionPositions[i]] = values[i]; } return concat.join(''); }; var getValue = function(value) { return trustedContext ? $sce.getTrusted(trustedContext, value) : $sce.valueOf(value); }; return extend(function interpolationFn(context) { var i = 0; var ii = expressions.length; var values = new Array(ii); try { for (; i < ii; i++) { values[i] = parseFns[i](context); } return compute(values); } catch (err) { $exceptionHandler($interpolateMinErr.interr(text, err)); } }, { // all of these properties are undocumented for now exp: text, //just for compatibility with regular watchers created via $watch expressions: expressions, $$watchDelegate: function(scope, listener) { var lastValue; return scope.$watchGroup(parseFns, /** @this */ function interpolateFnWatcher(values, oldValues) { var currValue = compute(values); if (isFunction(listener)) { listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); } lastValue = currValue; }); } }); } function parseStringifyInterceptor(value) { try { value = getValue(value); return allOrNothing && !isDefined(value) ? value : stringify(value); } catch (err) { $exceptionHandler($interpolateMinErr.interr(text, err)); } } } /** * @ngdoc method * @name $interpolate#startSymbol * @description * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. * * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change * the symbol. * * @returns {string} start symbol. */ $interpolate.startSymbol = function() { return startSymbol; }; /** * @ngdoc method * @name $interpolate#endSymbol * @description * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. * * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change * the symbol. * * @returns {string} end symbol. */ $interpolate.endSymbol = function() { return endSymbol; }; return $interpolate; }]; } /** @this */ function $IntervalProvider() { this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', function($rootScope, $window, $q, $$q, $browser) { var intervals = {}; /** * @ngdoc service * @name $interval * * @description * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` * milliseconds. * * The return value of registering an interval function is a promise. This promise will be * notified upon each tick of the interval, and will be resolved after `count` iterations, or * run indefinitely if `count` is not defined. The value of the notification will be the * number of iterations that have run. * To cancel an interval, call `$interval.cancel(promise)`. * * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to * move forward by `millis` milliseconds and trigger any functions scheduled to run in that * time. * *
          * **Note**: Intervals created by this service must be explicitly destroyed when you are finished * with them. In particular they are not automatically destroyed when a controller's scope or a * directive's element are destroyed. * You should take this into consideration and make sure to always cancel the interval at the * appropriate moment. See the example below for more details on how and when to do this. *
          * * @param {function()} fn A function that should be called repeatedly. If no additional arguments * are passed (see below), the function is called with the current iteration count. * @param {number} delay Number of milliseconds between each function call. * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat * indefinitely. * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. * @param {...*=} Pass additional parameters to the executed function. * @returns {promise} A promise which will be notified on each iteration. * * @example * * * * *
          *
          *
          * Current time is: *
          * Blood 1 : {{blood_1}} * Blood 2 : {{blood_2}} * * * *
          *
          * *
          *
          */ function interval(fn, delay, count, invokeApply) { var hasParams = arguments.length > 4, args = hasParams ? sliceArgs(arguments, 4) : [], setInterval = $window.setInterval, clearInterval = $window.clearInterval, iteration = 0, skipApply = (isDefined(invokeApply) && !invokeApply), deferred = (skipApply ? $$q : $q).defer(), promise = deferred.promise; count = isDefined(count) ? count : 0; promise.$$intervalId = setInterval(function tick() { if (skipApply) { $browser.defer(callback); } else { $rootScope.$evalAsync(callback); } deferred.notify(iteration++); if (count > 0 && iteration >= count) { deferred.resolve(iteration); clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; } if (!skipApply) $rootScope.$apply(); }, delay); intervals[promise.$$intervalId] = deferred; return promise; function callback() { if (!hasParams) { fn(iteration); } else { fn.apply(null, args); } } } /** * @ngdoc method * @name $interval#cancel * * @description * Cancels a task associated with the `promise`. * * @param {Promise=} promise returned by the `$interval` function. * @returns {boolean} Returns `true` if the task was successfully canceled. */ interval.cancel = function(promise) { if (promise && promise.$$intervalId in intervals) { intervals[promise.$$intervalId].reject('canceled'); $window.clearInterval(promise.$$intervalId); delete intervals[promise.$$intervalId]; return true; } return false; }; return interval; }]; } /** * @ngdoc service * @name $jsonpCallbacks * @requires $window * @description * This service handles the lifecycle of callbacks to handle JSONP requests. * Override this service if you wish to customise where the callbacks are stored and * how they vary compared to the requested url. */ var $jsonpCallbacksProvider = /** @this */ function() { this.$get = ['$window', function($window) { var callbacks = $window.angular.callbacks; var callbackMap = {}; function createCallback(callbackId) { var callback = function(data) { callback.data = data; callback.called = true; }; callback.id = callbackId; return callback; } return { /** * @ngdoc method * @name $jsonpCallbacks#createCallback * @param {string} url the url of the JSONP request * @returns {string} the callback path to send to the server as part of the JSONP request * @description * {@link $httpBackend} calls this method to create a callback and get hold of the path to the callback * to pass to the server, which will be used to call the callback with its payload in the JSONP response. */ createCallback: function(url) { var callbackId = '_' + (callbacks.$$counter++).toString(36); var callbackPath = 'angular.callbacks.' + callbackId; var callback = createCallback(callbackId); callbackMap[callbackPath] = callbacks[callbackId] = callback; return callbackPath; }, /** * @ngdoc method * @name $jsonpCallbacks#wasCalled * @param {string} callbackPath the path to the callback that was sent in the JSONP request * @returns {boolean} whether the callback has been called, as a result of the JSONP response * @description * {@link $httpBackend} calls this method to find out whether the JSONP response actually called the * callback that was passed in the request. */ wasCalled: function(callbackPath) { return callbackMap[callbackPath].called; }, /** * @ngdoc method * @name $jsonpCallbacks#getResponse * @param {string} callbackPath the path to the callback that was sent in the JSONP request * @returns {*} the data received from the response via the registered callback * @description * {@link $httpBackend} calls this method to get hold of the data that was provided to the callback * in the JSONP response. */ getResponse: function(callbackPath) { return callbackMap[callbackPath].data; }, /** * @ngdoc method * @name $jsonpCallbacks#removeCallback * @param {string} callbackPath the path to the callback that was sent in the JSONP request * @description * {@link $httpBackend} calls this method to remove the callback after the JSONP request has * completed or timed-out. */ removeCallback: function(callbackPath) { var callback = callbackMap[callbackPath]; delete callbacks[callback.id]; delete callbackMap[callbackPath]; } }; }]; }; /** * @ngdoc service * @name $locale * * @description * $locale service provides localization rules for various Angular components. As of right now the * only public api is: * * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) */ var PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/, DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; var $locationMinErr = minErr('$location'); /** * Encode path using encodeUriSegment, ignoring forward slashes * * @param {string} path Path to encode * @returns {string} */ function encodePath(path) { var segments = path.split('/'), i = segments.length; while (i--) { segments[i] = encodeUriSegment(segments[i]); } return segments.join('/'); } function parseAbsoluteUrl(absoluteUrl, locationObj) { var parsedUrl = urlResolve(absoluteUrl); locationObj.$$protocol = parsedUrl.protocol; locationObj.$$host = parsedUrl.hostname; locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; } var DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/; function parseAppUrl(url, locationObj) { if (DOUBLE_SLASH_REGEX.test(url)) { throw $locationMinErr('badpath', 'Invalid url "{0}".', url); } var prefixed = (url.charAt(0) !== '/'); if (prefixed) { url = '/' + url; } var match = urlResolve(url); locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname); locationObj.$$search = parseKeyValue(match.search); locationObj.$$hash = decodeURIComponent(match.hash); // make sure path starts with '/'; if (locationObj.$$path && locationObj.$$path.charAt(0) !== '/') { locationObj.$$path = '/' + locationObj.$$path; } } function startsWith(str, search) { return str.slice(0, search.length) === search; } /** * * @param {string} base * @param {string} url * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with * the expected string. */ function stripBaseUrl(base, url) { if (startsWith(url, base)) { return url.substr(base.length); } } function stripHash(url) { var index = url.indexOf('#'); return index === -1 ? url : url.substr(0, index); } function trimEmptyHash(url) { return url.replace(/(#.+)|#$/, '$1'); } function stripFile(url) { return url.substr(0, stripHash(url).lastIndexOf('/') + 1); } /* return the server only (scheme://host:port) */ function serverBase(url) { return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); } /** * LocationHtml5Url represents a URL * This object is exposed as $location service when HTML5 mode is enabled and supported * * @constructor * @param {string} appBase application base URL * @param {string} appBaseNoFile application base URL stripped of any filename * @param {string} basePrefix URL path prefix */ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { this.$$html5 = true; basePrefix = basePrefix || ''; parseAbsoluteUrl(appBase, this); /** * Parse given HTML5 (regular) URL string into properties * @param {string} url HTML5 URL * @private */ this.$$parse = function(url) { var pathUrl = stripBaseUrl(appBaseNoFile, url); if (!isString(pathUrl)) { throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, appBaseNoFile); } parseAppUrl(pathUrl, this); if (!this.$$path) { this.$$path = '/'; } this.$$compose(); }; /** * Compose url and update `absUrl` property * @private */ this.$$compose = function() { var search = toKeyValue(this.$$search), hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' }; this.$$parseLinkUrl = function(url, relHref) { if (relHref && relHref[0] === '#') { // special case for links to hash fragments: // keep the old url and only replace the hash fragment this.hash(relHref.slice(1)); return true; } var appUrl, prevAppUrl; var rewrittenUrl; if (isDefined(appUrl = stripBaseUrl(appBase, url))) { prevAppUrl = appUrl; if (basePrefix && isDefined(appUrl = stripBaseUrl(basePrefix, appUrl))) { rewrittenUrl = appBaseNoFile + (stripBaseUrl('/', appUrl) || appUrl); } else { rewrittenUrl = appBase + prevAppUrl; } } else if (isDefined(appUrl = stripBaseUrl(appBaseNoFile, url))) { rewrittenUrl = appBaseNoFile + appUrl; } else if (appBaseNoFile === url + '/') { rewrittenUrl = appBaseNoFile; } if (rewrittenUrl) { this.$$parse(rewrittenUrl); } return !!rewrittenUrl; }; } /** * LocationHashbangUrl represents URL * This object is exposed as $location service when developer doesn't opt into html5 mode. * It also serves as the base class for html5 mode fallback on legacy browsers. * * @constructor * @param {string} appBase application base URL * @param {string} appBaseNoFile application base URL stripped of any filename * @param {string} hashPrefix hashbang prefix */ function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { parseAbsoluteUrl(appBase, this); /** * Parse given hashbang URL into properties * @param {string} url Hashbang URL * @private */ this.$$parse = function(url) { var withoutBaseUrl = stripBaseUrl(appBase, url) || stripBaseUrl(appBaseNoFile, url); var withoutHashUrl; if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { // The rest of the URL starts with a hash so we have // got either a hashbang path or a plain hash fragment withoutHashUrl = stripBaseUrl(hashPrefix, withoutBaseUrl); if (isUndefined(withoutHashUrl)) { // There was no hashbang prefix so we just have a hash fragment withoutHashUrl = withoutBaseUrl; } } else { // There was no hashbang path nor hash fragment: // If we are in HTML5 mode we use what is left as the path; // Otherwise we ignore what is left if (this.$$html5) { withoutHashUrl = withoutBaseUrl; } else { withoutHashUrl = ''; if (isUndefined(withoutBaseUrl)) { appBase = url; this.replace(); } } } parseAppUrl(withoutHashUrl, this); this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); this.$$compose(); /* * In Windows, on an anchor node on documents loaded from * the filesystem, the browser will return a pathname * prefixed with the drive name ('/C:/path') when a * pathname without a drive is set: * * a.setAttribute('href', '/foo') * * a.pathname === '/C:/foo' //true * * Inside of Angular, we're always using pathnames that * do not include drive names for routing. */ function removeWindowsDriveName(path, url, base) { /* Matches paths for file protocol on windows, such as /C:/foo/bar, and captures only /foo/bar. */ var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; var firstPathSegmentMatch; //Get the relative path from the input URL. if (startsWith(url, base)) { url = url.replace(base, ''); } // The input URL intentionally contains a first path segment that ends with a colon. if (windowsFilePathExp.exec(url)) { return path; } firstPathSegmentMatch = windowsFilePathExp.exec(path); return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; } }; /** * Compose hashbang URL and update `absUrl` property * @private */ this.$$compose = function() { var search = toKeyValue(this.$$search), hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); }; this.$$parseLinkUrl = function(url, relHref) { if (stripHash(appBase) === stripHash(url)) { this.$$parse(url); return true; } return false; }; } /** * LocationHashbangUrl represents URL * This object is exposed as $location service when html5 history api is enabled but the browser * does not support it. * * @constructor * @param {string} appBase application base URL * @param {string} appBaseNoFile application base URL stripped of any filename * @param {string} hashPrefix hashbang prefix */ function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { this.$$html5 = true; LocationHashbangUrl.apply(this, arguments); this.$$parseLinkUrl = function(url, relHref) { if (relHref && relHref[0] === '#') { // special case for links to hash fragments: // keep the old url and only replace the hash fragment this.hash(relHref.slice(1)); return true; } var rewrittenUrl; var appUrl; if (appBase === stripHash(url)) { rewrittenUrl = url; } else if ((appUrl = stripBaseUrl(appBaseNoFile, url))) { rewrittenUrl = appBase + hashPrefix + appUrl; } else if (appBaseNoFile === url + '/') { rewrittenUrl = appBaseNoFile; } if (rewrittenUrl) { this.$$parse(rewrittenUrl); } return !!rewrittenUrl; }; this.$$compose = function() { var search = toKeyValue(this.$$search), hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' this.$$absUrl = appBase + hashPrefix + this.$$url; }; } var locationPrototype = { /** * Ensure absolute URL is initialized. * @private */ $$absUrl:'', /** * Are we in html5 mode? * @private */ $$html5: false, /** * Has any change been replacing? * @private */ $$replace: false, /** * @ngdoc method * @name $location#absUrl * * @description * This method is getter only. * * Return full URL representation with all segments encoded according to rules specified in * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var absUrl = $location.absUrl(); * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" * ``` * * @return {string} full URL */ absUrl: locationGetter('$$absUrl'), /** * @ngdoc method * @name $location#url * * @description * This method is getter / setter. * * Return URL (e.g. `/path?a=b#hash`) when called without any parameter. * * Change path, search and hash, when called with parameter and return `$location`. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var url = $location.url(); * // => "/some/path?foo=bar&baz=xoxo" * ``` * * @param {string=} url New URL without base prefix (e.g. `/path?a=b#hash`) * @return {string} url */ url: function(url) { if (isUndefined(url)) { return this.$$url; } var match = PATH_MATCH.exec(url); if (match[1] || url === '') this.path(decodeURIComponent(match[1])); if (match[2] || match[1] || url === '') this.search(match[3] || ''); this.hash(match[5] || ''); return this; }, /** * @ngdoc method * @name $location#protocol * * @description * This method is getter only. * * Return protocol of current URL. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var protocol = $location.protocol(); * // => "http" * ``` * * @return {string} protocol of current URL */ protocol: locationGetter('$$protocol'), /** * @ngdoc method * @name $location#host * * @description * This method is getter only. * * Return host of current URL. * * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var host = $location.host(); * // => "example.com" * * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo * host = $location.host(); * // => "example.com" * host = location.host; * // => "example.com:8080" * ``` * * @return {string} host of current URL. */ host: locationGetter('$$host'), /** * @ngdoc method * @name $location#port * * @description * This method is getter only. * * Return port of current URL. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var port = $location.port(); * // => 80 * ``` * * @return {Number} port */ port: locationGetter('$$port'), /** * @ngdoc method * @name $location#path * * @description * This method is getter / setter. * * Return path of current URL when called without any parameter. * * Change path when called with parameter and return `$location`. * * Note: Path should always begin with forward slash (/), this method will add the forward slash * if it is missing. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var path = $location.path(); * // => "/some/path" * ``` * * @param {(string|number)=} path New path * @return {(string|object)} path if called with no parameters, or `$location` if called with a parameter */ path: locationGetterSetter('$$path', function(path) { path = path !== null ? path.toString() : ''; return path.charAt(0) === '/' ? path : '/' + path; }), /** * @ngdoc method * @name $location#search * * @description * This method is getter / setter. * * Return search part (as object) of current URL when called without any parameter. * * Change search part when called with parameter and return `$location`. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo * var searchObject = $location.search(); * // => {foo: 'bar', baz: 'xoxo'} * * // set foo to 'yipee' * $location.search('foo', 'yipee'); * // $location.search() => {foo: 'yipee', baz: 'xoxo'} * ``` * * @param {string|Object.|Object.>} search New search params - string or * hash object. * * When called with a single argument the method acts as a setter, setting the `search` component * of `$location` to the specified value. * * If the argument is a hash object containing an array of values, these values will be encoded * as duplicate search parameters in the URL. * * @param {(string|Number|Array|boolean)=} paramValue If `search` is a string or number, then `paramValue` * will override only a single search property. * * If `paramValue` is an array, it will override the property of the `search` component of * `$location` specified via the first argument. * * If `paramValue` is `null`, the property specified via the first argument will be deleted. * * If `paramValue` is `true`, the property specified via the first argument will be added with no * value nor trailing equal sign. * * @return {Object} If called with no arguments returns the parsed `search` object. If called with * one or more arguments returns `$location` object itself. */ search: function(search, paramValue) { switch (arguments.length) { case 0: return this.$$search; case 1: if (isString(search) || isNumber(search)) { search = search.toString(); this.$$search = parseKeyValue(search); } else if (isObject(search)) { search = copy(search, {}); // remove object undefined or null properties forEach(search, function(value, key) { if (value == null) delete search[key]; }); this.$$search = search; } else { throw $locationMinErr('isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); } break; default: if (isUndefined(paramValue) || paramValue === null) { delete this.$$search[search]; } else { this.$$search[search] = paramValue; } } this.$$compose(); return this; }, /** * @ngdoc method * @name $location#hash * * @description * This method is getter / setter. * * Returns the hash fragment when called without any parameters. * * Changes the hash fragment when called with a parameter and returns `$location`. * * * ```js * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue * var hash = $location.hash(); * // => "hashValue" * ``` * * @param {(string|number)=} hash New hash fragment * @return {string} hash */ hash: locationGetterSetter('$$hash', function(hash) { return hash !== null ? hash.toString() : ''; }), /** * @ngdoc method * @name $location#replace * * @description * If called, all changes to $location during the current `$digest` will replace the current history * record, instead of adding a new one. */ replace: function() { this.$$replace = true; return this; } }; forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { Location.prototype = Object.create(locationPrototype); /** * @ngdoc method * @name $location#state * * @description * This method is getter / setter. * * Return the history state object when called without any parameter. * * Change the history state object when called with one parameter and return `$location`. * The state object is later passed to `pushState` or `replaceState`. * * NOTE: This method is supported only in HTML5 mode and only in browsers supporting * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support * older browsers (like IE9 or Android < 4.0), don't use this method. * * @param {object=} state State object for pushState or replaceState * @return {object} state */ Location.prototype.state = function(state) { if (!arguments.length) { return this.$$state; } if (Location !== LocationHtml5Url || !this.$$html5) { throw $locationMinErr('nostate', 'History API state support is available only ' + 'in HTML5 mode and only in browsers supporting HTML5 History API'); } // The user might modify `stateObject` after invoking `$location.state(stateObject)` // but we're changing the $$state reference to $browser.state() during the $digest // so the modification window is narrow. this.$$state = isUndefined(state) ? null : state; return this; }; }); function locationGetter(property) { return /** @this */ function() { return this[property]; }; } function locationGetterSetter(property, preprocess) { return /** @this */ function(value) { if (isUndefined(value)) { return this[property]; } this[property] = preprocess(value); this.$$compose(); return this; }; } /** * @ngdoc service * @name $location * * @requires $rootElement * * @description * The $location service parses the URL in the browser address bar (based on the * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL * available to your application. Changes to the URL in the address bar are reflected into * $location service and changes to $location are reflected into the browser address bar. * * **The $location service:** * * - Exposes the current URL in the browser address bar, so you can * - Watch and observe the URL. * - Change the URL. * - Synchronizes the URL with the browser when the user * - Changes the address bar. * - Clicks the back or forward button (or clicks a History link). * - Clicks on a link. * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). * * For more information see {@link guide/$location Developer Guide: Using $location} */ /** * @ngdoc provider * @name $locationProvider * @this * * @description * Use the `$locationProvider` to configure how the application deep linking paths are stored. */ function $LocationProvider() { var hashPrefix = '', html5Mode = { enabled: false, requireBase: true, rewriteLinks: true }; /** * @ngdoc method * @name $locationProvider#hashPrefix * @description * The default value for the prefix is `''`. * @param {string=} prefix Prefix for hash part (containing path and search) * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.hashPrefix = function(prefix) { if (isDefined(prefix)) { hashPrefix = prefix; return this; } else { return hashPrefix; } }; /** * @ngdoc method * @name $locationProvider#html5Mode * @description * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported * properties: * - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not * support `pushState`. * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies * whether or not a tag is required to be present. If `enabled` and `requireBase` are * true, and a base tag is not present, an error will be thrown when `$location` is injected. * See the {@link guide/$location $location guide for more information} * - **rewriteLinks** - `{boolean|string}` - (default: `true`) When html5Mode is enabled, * enables/disables URL rewriting for relative links. If set to a string, URL rewriting will * only happen on links with an attribute that matches the given string. For example, if set * to `'internal-link'`, then the URL will only be rewritten for `` links. * Note that [attribute name normalization](guide/directive#normalization) does not apply * here, so `'internalLink'` will **not** match `'internal-link'`. * * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter */ this.html5Mode = function(mode) { if (isBoolean(mode)) { html5Mode.enabled = mode; return this; } else if (isObject(mode)) { if (isBoolean(mode.enabled)) { html5Mode.enabled = mode.enabled; } if (isBoolean(mode.requireBase)) { html5Mode.requireBase = mode.requireBase; } if (isBoolean(mode.rewriteLinks) || isString(mode.rewriteLinks)) { html5Mode.rewriteLinks = mode.rewriteLinks; } return this; } else { return html5Mode; } }; /** * @ngdoc event * @name $location#$locationChangeStart * @eventType broadcast on root scope * @description * Broadcasted before a URL will change. * * This change can be prevented by calling * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more * details about event object. Upon successful change * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. * * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when * the browser supports the HTML5 History API. * * @param {Object} angularEvent Synthetic event object. * @param {string} newUrl New URL * @param {string=} oldUrl URL that was before it was changed. * @param {string=} newState New history state object * @param {string=} oldState History state object that was before it was changed. */ /** * @ngdoc event * @name $location#$locationChangeSuccess * @eventType broadcast on root scope * @description * Broadcasted after a URL was changed. * * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when * the browser supports the HTML5 History API. * * @param {Object} angularEvent Synthetic event object. * @param {string} newUrl New URL * @param {string=} oldUrl URL that was before it was changed. * @param {string=} newState New history state object * @param {string=} oldState History state object that was before it was changed. */ this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', function($rootScope, $browser, $sniffer, $rootElement, $window) { var $location, LocationMode, baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' initialUrl = $browser.url(), appBase; if (html5Mode.enabled) { if (!baseHref && html5Mode.requireBase) { throw $locationMinErr('nobase', '$location in HTML5 mode requires a tag to be present!'); } appBase = serverBase(initialUrl) + (baseHref || '/'); LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; } else { appBase = stripHash(initialUrl); LocationMode = LocationHashbangUrl; } var appBaseNoFile = stripFile(appBase); $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); $location.$$parseLinkUrl(initialUrl, initialUrl); $location.$$state = $browser.state(); var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; function setBrowserUrlWithFallback(url, replace, state) { var oldUrl = $location.url(); var oldState = $location.$$state; try { $browser.url(url, replace, state); // Make sure $location.state() returns referentially identical (not just deeply equal) // state object; this makes possible quick checking if the state changed in the digest // loop. Checking deep equality would be too expensive. $location.$$state = $browser.state(); } catch (e) { // Restore old values if pushState fails $location.url(oldUrl); $location.$$state = oldState; throw e; } } $rootElement.on('click', function(event) { var rewriteLinks = html5Mode.rewriteLinks; // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) // currently we open nice url link and redirect then if (!rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 || event.button === 2) return; var elm = jqLite(event.target); // traverse the DOM up to find first A tag while (nodeName_(elm[0]) !== 'a') { // ignore rewriting if no A tag (reached root element, or no parent - removed from document) if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; } if (isString(rewriteLinks) && isUndefined(elm.attr(rewriteLinks))) return; var absHref = elm.prop('href'); // get the actual href attribute - see // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx var relHref = elm.attr('href') || elm.attr('xlink:href'); if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during // an animation. absHref = urlResolve(absHref.animVal).href; } // Ignore when url is started with javascript: or mailto: if (IGNORE_URI_REGEXP.test(absHref)) return; if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { if ($location.$$parseLinkUrl(absHref, relHref)) { // We do a preventDefault for all urls that are part of the angular application, // in html5mode and also without, so that we are able to abort navigation without // getting double entries in the location history. event.preventDefault(); // update location manually if ($location.absUrl() !== $browser.url()) { $rootScope.$apply(); // hack to work around FF6 bug 684208 when scenario runner clicks on links $window.angular['ff-684208-preventDefault'] = true; } } } }); // rewrite hashbang url <> html5 url if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) { $browser.url($location.absUrl(), true); } var initializing = true; // update $location when $browser url changes $browser.onUrlChange(function(newUrl, newState) { if (isUndefined(stripBaseUrl(appBaseNoFile, newUrl))) { // If we are navigating outside of the app then force a reload $window.location.href = newUrl; return; } $rootScope.$evalAsync(function() { var oldUrl = $location.absUrl(); var oldState = $location.$$state; var defaultPrevented; newUrl = trimEmptyHash(newUrl); $location.$$parse(newUrl); $location.$$state = newState; defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, newState, oldState).defaultPrevented; // if the location was changed by a `$locationChangeStart` handler then stop // processing this location change if ($location.absUrl() !== newUrl) return; if (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; setBrowserUrlWithFallback(oldUrl, false, oldState); } else { initializing = false; afterLocationChange(oldUrl, oldState); } }); if (!$rootScope.$$phase) $rootScope.$digest(); }); // update browser $rootScope.$watch(function $locationWatch() { var oldUrl = trimEmptyHash($browser.url()); var newUrl = trimEmptyHash($location.absUrl()); var oldState = $browser.state(); var currentReplace = $location.$$replace; var urlOrStateChanged = oldUrl !== newUrl || ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); if (initializing || urlOrStateChanged) { initializing = false; $rootScope.$evalAsync(function() { var newUrl = $location.absUrl(); var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, $location.$$state, oldState).defaultPrevented; // if the location was changed by a `$locationChangeStart` handler then stop // processing this location change if ($location.absUrl() !== newUrl) return; if (defaultPrevented) { $location.$$parse(oldUrl); $location.$$state = oldState; } else { if (urlOrStateChanged) { setBrowserUrlWithFallback(newUrl, currentReplace, oldState === $location.$$state ? null : $location.$$state); } afterLocationChange(oldUrl, oldState); } }); } $location.$$replace = false; // we don't need to return anything because $evalAsync will make the digest loop dirty when // there is a change }); return $location; function afterLocationChange(oldUrl, oldState) { $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, $location.$$state, oldState); } }]; } /** * @ngdoc service * @name $log * @requires $window * * @description * Simple service for logging. Default implementation safely writes the message * into the browser's console (if present). * * The main purpose of this service is to simplify debugging and troubleshooting. * * The default is to log `debug` messages. You can use * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. * * @example angular.module('logExample', []) .controller('LogController', ['$scope', '$log', function($scope, $log) { $scope.$log = $log; $scope.message = 'Hello World!'; }]);

          Reload this page with open console, enter text and hit the log button...

          */ /** * @ngdoc provider * @name $logProvider * @this * * @description * Use the `$logProvider` to configure how the application logs messages */ function $LogProvider() { var debug = true, self = this; /** * @ngdoc method * @name $logProvider#debugEnabled * @description * @param {boolean=} flag enable or disable debug level messages * @returns {*} current value if used as getter or itself (chaining) if used as setter */ this.debugEnabled = function(flag) { if (isDefined(flag)) { debug = flag; return this; } else { return debug; } }; this.$get = ['$window', function($window) { return { /** * @ngdoc method * @name $log#log * * @description * Write a log message */ log: consoleLog('log'), /** * @ngdoc method * @name $log#info * * @description * Write an information message */ info: consoleLog('info'), /** * @ngdoc method * @name $log#warn * * @description * Write a warning message */ warn: consoleLog('warn'), /** * @ngdoc method * @name $log#error * * @description * Write an error message */ error: consoleLog('error'), /** * @ngdoc method * @name $log#debug * * @description * Write a debug message */ debug: (function() { var fn = consoleLog('debug'); return function() { if (debug) { fn.apply(self, arguments); } }; })() }; function formatError(arg) { if (arg instanceof Error) { if (arg.stack) { arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ? 'Error: ' + arg.message + '\n' + arg.stack : arg.stack; } else if (arg.sourceURL) { arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; } } return arg; } function consoleLog(type) { var console = $window.console || {}, logFn = console[type] || console.log || noop, hasApply = false; // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. // The reason behind this is that console.log has type "object" in IE8... try { hasApply = !!logFn.apply; } catch (e) { /* empty */ } if (hasApply) { return function() { var args = []; forEach(arguments, function(arg) { args.push(formatError(arg)); }); return logFn.apply(console, args); }; } // we are IE which either doesn't have window.console => this is noop and we do nothing, // or we are IE where console.log doesn't have apply so we log at least first 2 args return function(arg1, arg2) { logFn(arg1, arg2 == null ? '' : arg2); }; } }]; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ var $parseMinErr = minErr('$parse'); var ARRAY_CTOR = [].constructor; var BOOLEAN_CTOR = (false).constructor; var FUNCTION_CTOR = Function.constructor; var NUMBER_CTOR = (0).constructor; var OBJECT_CTOR = {}.constructor; var STRING_CTOR = ''.constructor; var ARRAY_CTOR_PROTO = ARRAY_CTOR.prototype; var BOOLEAN_CTOR_PROTO = BOOLEAN_CTOR.prototype; var FUNCTION_CTOR_PROTO = FUNCTION_CTOR.prototype; var NUMBER_CTOR_PROTO = NUMBER_CTOR.prototype; var OBJECT_CTOR_PROTO = OBJECT_CTOR.prototype; var STRING_CTOR_PROTO = STRING_CTOR.prototype; var CALL = FUNCTION_CTOR_PROTO.call; var APPLY = FUNCTION_CTOR_PROTO.apply; var BIND = FUNCTION_CTOR_PROTO.bind; var objectValueOf = OBJECT_CTOR_PROTO.valueOf; // Sandboxing Angular Expressions // ------------------------------ // Angular expressions are generally considered safe because these expressions only have direct // access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by // obtaining a reference to native JS functions such as the Function constructor. // // As an example, consider the following Angular expression: // // {}.toString.constructor('alert("evil JS code")') // // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits // against the expression language, but not to prevent exploits that were enabled by exposing // sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good // practice and therefore we are not even trying to protect against interaction with an object // explicitly exposed in this way. // // In general, it is not possible to access a Window object from an angular expression unless a // window or some DOM object that has a reference to window is published onto a Scope. // Similarly we prevent invocations of function known to be dangerous, as well as assignments to // native objects. // // See https://docs.angularjs.org/guide/security function ensureSafeMemberName(name, fullExpression) { if (name === '__defineGetter__' || name === '__defineSetter__' || name === '__lookupGetter__' || name === '__lookupSetter__' || name === '__proto__') { throw $parseMinErr('isecfld', 'Attempting to access a disallowed field in Angular expressions! ' + 'Expression: {0}', fullExpression); } return name; } function getStringValue(name) { // Property names must be strings. This means that non-string objects cannot be used // as keys in an object. Any non-string object, including a number, is typecasted // into a string via the toString method. // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names // // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it // to a string. It's not always possible. If `name` is an object and its `toString` method is // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: // // TypeError: Cannot convert object to primitive value // // For performance reasons, we don't catch this error here and allow it to propagate up the call // stack. Note that you'll get the same error in JavaScript if you try to access a property using // such a 'broken' object as a key. return name + ''; } function ensureSafeObject(obj, fullExpression) { // nifty check if obj is Function that is fast and works across iframes and other contexts if (obj) { if (obj.constructor === obj) { throw $parseMinErr('isecfn', 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (// isWindow(obj) obj.window === obj) { throw $parseMinErr('isecwindow', 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (// isElement(obj) obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { throw $parseMinErr('isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (// block Object so that we can't get hold of dangerous Object.* methods obj === Object) { throw $parseMinErr('isecobj', 'Referencing Object in Angular expressions is disallowed! Expression: {0}', fullExpression); } } return obj; } function ensureSafeFunction(obj, fullExpression) { if (obj) { if (obj.constructor === obj) { throw $parseMinErr('isecfn', 'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression); } else if (obj === CALL || obj === APPLY || obj === BIND) { throw $parseMinErr('isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', fullExpression); } } } function ensureSafeAssignContext(obj, fullExpression) { if (obj) { if (obj === ARRAY_CTOR || obj === BOOLEAN_CTOR || obj === FUNCTION_CTOR || obj === NUMBER_CTOR || obj === OBJECT_CTOR || obj === STRING_CTOR || obj === ARRAY_CTOR_PROTO || obj === BOOLEAN_CTOR_PROTO || obj === FUNCTION_CTOR_PROTO || obj === NUMBER_CTOR_PROTO || obj === OBJECT_CTOR_PROTO || obj === STRING_CTOR_PROTO) { throw $parseMinErr('isecaf', 'Assigning to a constructor or its prototype is disallowed! Expression: {0}', fullExpression); } } } var OPERATORS = createMap(); forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); var ESCAPE = {'n':'\n', 'f':'\f', 'r':'\r', 't':'\t', 'v':'\v', '\'':'\'', '"':'"'}; ///////////////////////////////////////// /** * @constructor */ var Lexer = function Lexer(options) { this.options = options; }; Lexer.prototype = { constructor: Lexer, lex: function(text) { this.text = text; this.index = 0; this.tokens = []; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); if (ch === '"' || ch === '\'') { this.readString(ch); } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { this.readNumber(); } else if (this.isIdentifierStart(this.peekMultichar())) { this.readIdent(); } else if (this.is(ch, '(){}[].,;:?')) { this.tokens.push({index: this.index, text: ch}); this.index++; } else if (this.isWhitespace(ch)) { this.index++; } else { var ch2 = ch + this.peek(); var ch3 = ch2 + this.peek(2); var op1 = OPERATORS[ch]; var op2 = OPERATORS[ch2]; var op3 = OPERATORS[ch3]; if (op1 || op2 || op3) { var token = op3 ? ch3 : (op2 ? ch2 : ch); this.tokens.push({index: this.index, text: token, operator: true}); this.index += token.length; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } } } return this.tokens; }, is: function(ch, chars) { return chars.indexOf(ch) !== -1; }, peek: function(i) { var num = i || 1; return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; }, isNumber: function(ch) { return ('0' <= ch && ch <= '9') && typeof ch === 'string'; }, isWhitespace: function(ch) { // IE treats non-breaking space as \u00A0 return (ch === ' ' || ch === '\r' || ch === '\t' || ch === '\n' || ch === '\v' || ch === '\u00A0'); }, isIdentifierStart: function(ch) { return this.options.isIdentifierStart ? this.options.isIdentifierStart(ch, this.codePointAt(ch)) : this.isValidIdentifierStart(ch); }, isValidIdentifierStart: function(ch) { return ('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' === ch || ch === '$'); }, isIdentifierContinue: function(ch) { return this.options.isIdentifierContinue ? this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : this.isValidIdentifierContinue(ch); }, isValidIdentifierContinue: function(ch, cp) { return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); }, codePointAt: function(ch) { if (ch.length === 1) return ch.charCodeAt(0); // eslint-disable-next-line no-bitwise return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; }, peekMultichar: function() { var ch = this.text.charAt(this.index); var peek = this.peek(); if (!peek) { return ch; } var cp1 = ch.charCodeAt(0); var cp2 = peek.charCodeAt(0); if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { return ch + peek; } return ch; }, isExpOperator: function(ch) { return (ch === '-' || ch === '+' || this.isNumber(ch)); }, throwError: function(error, start, end) { end = end || this.index; var colStr = (isDefined(start) ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' : ' ' + end); throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', error, colStr, this.text); }, readNumber: function() { var number = ''; var start = this.index; while (this.index < this.text.length) { var ch = lowercase(this.text.charAt(this.index)); if (ch === '.' || this.isNumber(ch)) { number += ch; } else { var peekCh = this.peek(); if (ch === 'e' && this.isExpOperator(peekCh)) { number += ch; } else if (this.isExpOperator(ch) && peekCh && this.isNumber(peekCh) && number.charAt(number.length - 1) === 'e') { number += ch; } else if (this.isExpOperator(ch) && (!peekCh || !this.isNumber(peekCh)) && number.charAt(number.length - 1) === 'e') { this.throwError('Invalid exponent'); } else { break; } } this.index++; } this.tokens.push({ index: start, text: number, constant: true, value: Number(number) }); }, readIdent: function() { var start = this.index; this.index += this.peekMultichar().length; while (this.index < this.text.length) { var ch = this.peekMultichar(); if (!this.isIdentifierContinue(ch)) { break; } this.index += ch.length; } this.tokens.push({ index: start, text: this.text.slice(start, this.index), identifier: true }); }, readString: function(quote) { var start = this.index; this.index++; var string = ''; var rawString = quote; var escape = false; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); rawString += ch; if (escape) { if (ch === 'u') { var hex = this.text.substring(this.index + 1, this.index + 5); if (!hex.match(/[\da-f]{4}/i)) { this.throwError('Invalid unicode escape [\\u' + hex + ']'); } this.index += 4; string += String.fromCharCode(parseInt(hex, 16)); } else { var rep = ESCAPE[ch]; string = string + (rep || ch); } escape = false; } else if (ch === '\\') { escape = true; } else if (ch === quote) { this.index++; this.tokens.push({ index: start, text: rawString, constant: true, value: string }); return; } else { string += ch; } this.index++; } this.throwError('Unterminated quote', start); } }; var AST = function AST(lexer, options) { this.lexer = lexer; this.options = options; }; AST.Program = 'Program'; AST.ExpressionStatement = 'ExpressionStatement'; AST.AssignmentExpression = 'AssignmentExpression'; AST.ConditionalExpression = 'ConditionalExpression'; AST.LogicalExpression = 'LogicalExpression'; AST.BinaryExpression = 'BinaryExpression'; AST.UnaryExpression = 'UnaryExpression'; AST.CallExpression = 'CallExpression'; AST.MemberExpression = 'MemberExpression'; AST.Identifier = 'Identifier'; AST.Literal = 'Literal'; AST.ArrayExpression = 'ArrayExpression'; AST.Property = 'Property'; AST.ObjectExpression = 'ObjectExpression'; AST.ThisExpression = 'ThisExpression'; AST.LocalsExpression = 'LocalsExpression'; // Internal use only AST.NGValueParameter = 'NGValueParameter'; AST.prototype = { ast: function(text) { this.text = text; this.tokens = this.lexer.lex(text); var value = this.program(); if (this.tokens.length !== 0) { this.throwError('is an unexpected token', this.tokens[0]); } return value; }, program: function() { var body = []; while (true) { if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) body.push(this.expressionStatement()); if (!this.expect(';')) { return { type: AST.Program, body: body}; } } }, expressionStatement: function() { return { type: AST.ExpressionStatement, expression: this.filterChain() }; }, filterChain: function() { var left = this.expression(); while (this.expect('|')) { left = this.filter(left); } return left; }, expression: function() { return this.assignment(); }, assignment: function() { var result = this.ternary(); if (this.expect('=')) { if (!isAssignable(result)) { throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); } result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; } return result; }, ternary: function() { var test = this.logicalOR(); var alternate; var consequent; if (this.expect('?')) { alternate = this.expression(); if (this.consume(':')) { consequent = this.expression(); return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; } } return test; }, logicalOR: function() { var left = this.logicalAND(); while (this.expect('||')) { left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; } return left; }, logicalAND: function() { var left = this.equality(); while (this.expect('&&')) { left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; } return left; }, equality: function() { var left = this.relational(); var token; while ((token = this.expect('==','!=','===','!=='))) { left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; } return left; }, relational: function() { var left = this.additive(); var token; while ((token = this.expect('<', '>', '<=', '>='))) { left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; } return left; }, additive: function() { var left = this.multiplicative(); var token; while ((token = this.expect('+','-'))) { left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; } return left; }, multiplicative: function() { var left = this.unary(); var token; while ((token = this.expect('*','/','%'))) { left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; } return left; }, unary: function() { var token; if ((token = this.expect('+', '-', '!'))) { return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; } else { return this.primary(); } }, primary: function() { var primary; if (this.expect('(')) { primary = this.filterChain(); this.consume(')'); } else if (this.expect('[')) { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { primary = copy(this.selfReferential[this.consume().text]); } else if (this.options.literals.hasOwnProperty(this.peek().text)) { primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; } else if (this.peek().identifier) { primary = this.identifier(); } else if (this.peek().constant) { primary = this.constant(); } else { this.throwError('not a primary expression', this.peek()); } var next; while ((next = this.expect('(', '[', '.'))) { if (next.text === '(') { primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; this.consume(')'); } else if (next.text === '[') { primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; this.consume(']'); } else if (next.text === '.') { primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; } else { this.throwError('IMPOSSIBLE'); } } return primary; }, filter: function(baseExpression) { var args = [baseExpression]; var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; while (this.expect(':')) { args.push(this.expression()); } return result; }, parseArguments: function() { var args = []; if (this.peekToken().text !== ')') { do { args.push(this.filterChain()); } while (this.expect(',')); } return args; }, identifier: function() { var token = this.consume(); if (!token.identifier) { this.throwError('is not a valid identifier', token); } return { type: AST.Identifier, name: token.text }; }, constant: function() { // TODO check that it is a constant return { type: AST.Literal, value: this.consume().value }; }, arrayDeclaration: function() { var elements = []; if (this.peekToken().text !== ']') { do { if (this.peek(']')) { // Support trailing commas per ES5.1. break; } elements.push(this.expression()); } while (this.expect(',')); } this.consume(']'); return { type: AST.ArrayExpression, elements: elements }; }, object: function() { var properties = [], property; if (this.peekToken().text !== '}') { do { if (this.peek('}')) { // Support trailing commas per ES5.1. break; } property = {type: AST.Property, kind: 'init'}; if (this.peek().constant) { property.key = this.constant(); property.computed = false; this.consume(':'); property.value = this.expression(); } else if (this.peek().identifier) { property.key = this.identifier(); property.computed = false; if (this.peek(':')) { this.consume(':'); property.value = this.expression(); } else { property.value = property.key; } } else if (this.peek('[')) { this.consume('['); property.key = this.expression(); this.consume(']'); property.computed = true; this.consume(':'); property.value = this.expression(); } else { this.throwError('invalid key', this.peek()); } properties.push(property); } while (this.expect(',')); } this.consume('}'); return {type: AST.ObjectExpression, properties: properties }; }, throwError: function(msg, token) { throw $parseMinErr('syntax', 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); }, consume: function(e1) { if (this.tokens.length === 0) { throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); } var token = this.expect(e1); if (!token) { this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); } return token; }, peekToken: function() { if (this.tokens.length === 0) { throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); } return this.tokens[0]; }, peek: function(e1, e2, e3, e4) { return this.peekAhead(0, e1, e2, e3, e4); }, peekAhead: function(i, e1, e2, e3, e4) { if (this.tokens.length > i) { var token = this.tokens[i]; var t = token.text; if (t === e1 || t === e2 || t === e3 || t === e4 || (!e1 && !e2 && !e3 && !e4)) { return token; } } return false; }, expect: function(e1, e2, e3, e4) { var token = this.peek(e1, e2, e3, e4); if (token) { this.tokens.shift(); return token; } return false; }, selfReferential: { 'this': {type: AST.ThisExpression }, '$locals': {type: AST.LocalsExpression } } }; function ifDefined(v, d) { return typeof v !== 'undefined' ? v : d; } function plusFn(l, r) { if (typeof l === 'undefined') return r; if (typeof r === 'undefined') return l; return l + r; } function isStateless($filter, filterName) { var fn = $filter(filterName); return !fn.$stateful; } function findConstantAndWatchExpressions(ast, $filter) { var allConstants; var argsToWatch; var isStatelessFilter; switch (ast.type) { case AST.Program: allConstants = true; forEach(ast.body, function(expr) { findConstantAndWatchExpressions(expr.expression, $filter); allConstants = allConstants && expr.expression.constant; }); ast.constant = allConstants; break; case AST.Literal: ast.constant = true; ast.toWatch = []; break; case AST.UnaryExpression: findConstantAndWatchExpressions(ast.argument, $filter); ast.constant = ast.argument.constant; ast.toWatch = ast.argument.toWatch; break; case AST.BinaryExpression: findConstantAndWatchExpressions(ast.left, $filter); findConstantAndWatchExpressions(ast.right, $filter); ast.constant = ast.left.constant && ast.right.constant; ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); break; case AST.LogicalExpression: findConstantAndWatchExpressions(ast.left, $filter); findConstantAndWatchExpressions(ast.right, $filter); ast.constant = ast.left.constant && ast.right.constant; ast.toWatch = ast.constant ? [] : [ast]; break; case AST.ConditionalExpression: findConstantAndWatchExpressions(ast.test, $filter); findConstantAndWatchExpressions(ast.alternate, $filter); findConstantAndWatchExpressions(ast.consequent, $filter); ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; ast.toWatch = ast.constant ? [] : [ast]; break; case AST.Identifier: ast.constant = false; ast.toWatch = [ast]; break; case AST.MemberExpression: findConstantAndWatchExpressions(ast.object, $filter); if (ast.computed) { findConstantAndWatchExpressions(ast.property, $filter); } ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); ast.toWatch = [ast]; break; case AST.CallExpression: isStatelessFilter = ast.filter ? isStateless($filter, ast.callee.name) : false; allConstants = isStatelessFilter; argsToWatch = []; forEach(ast.arguments, function(expr) { findConstantAndWatchExpressions(expr, $filter); allConstants = allConstants && expr.constant; if (!expr.constant) { argsToWatch.push.apply(argsToWatch, expr.toWatch); } }); ast.constant = allConstants; ast.toWatch = isStatelessFilter ? argsToWatch : [ast]; break; case AST.AssignmentExpression: findConstantAndWatchExpressions(ast.left, $filter); findConstantAndWatchExpressions(ast.right, $filter); ast.constant = ast.left.constant && ast.right.constant; ast.toWatch = [ast]; break; case AST.ArrayExpression: allConstants = true; argsToWatch = []; forEach(ast.elements, function(expr) { findConstantAndWatchExpressions(expr, $filter); allConstants = allConstants && expr.constant; if (!expr.constant) { argsToWatch.push.apply(argsToWatch, expr.toWatch); } }); ast.constant = allConstants; ast.toWatch = argsToWatch; break; case AST.ObjectExpression: allConstants = true; argsToWatch = []; forEach(ast.properties, function(property) { findConstantAndWatchExpressions(property.value, $filter); allConstants = allConstants && property.value.constant && !property.computed; if (!property.value.constant) { argsToWatch.push.apply(argsToWatch, property.value.toWatch); } }); ast.constant = allConstants; ast.toWatch = argsToWatch; break; case AST.ThisExpression: ast.constant = false; ast.toWatch = []; break; case AST.LocalsExpression: ast.constant = false; ast.toWatch = []; break; } } function getInputs(body) { if (body.length !== 1) return; var lastExpression = body[0].expression; var candidate = lastExpression.toWatch; if (candidate.length !== 1) return candidate; return candidate[0] !== lastExpression ? candidate : undefined; } function isAssignable(ast) { return ast.type === AST.Identifier || ast.type === AST.MemberExpression; } function assignableAST(ast) { if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; } } function isLiteral(ast) { return ast.body.length === 0 || ast.body.length === 1 && ( ast.body[0].expression.type === AST.Literal || ast.body[0].expression.type === AST.ArrayExpression || ast.body[0].expression.type === AST.ObjectExpression); } function isConstant(ast) { return ast.constant; } function ASTCompiler(astBuilder, $filter) { this.astBuilder = astBuilder; this.$filter = $filter; } ASTCompiler.prototype = { compile: function(expression, expensiveChecks) { var self = this; var ast = this.astBuilder.ast(expression); this.state = { nextId: 0, filters: {}, expensiveChecks: expensiveChecks, fn: {vars: [], body: [], own: {}}, assign: {vars: [], body: [], own: {}}, inputs: [] }; findConstantAndWatchExpressions(ast, self.$filter); var extra = ''; var assignable; this.stage = 'assign'; if ((assignable = assignableAST(ast))) { this.state.computing = 'assign'; var result = this.nextId(); this.recurse(assignable, result); this.return_(result); extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); } var toWatch = getInputs(ast.body); self.stage = 'inputs'; forEach(toWatch, function(watch, key) { var fnKey = 'fn' + key; self.state[fnKey] = {vars: [], body: [], own: {}}; self.state.computing = fnKey; var intoId = self.nextId(); self.recurse(watch, intoId); self.return_(intoId); self.state.inputs.push(fnKey); watch.watchId = key; }); this.state.computing = 'fn'; this.stage = 'main'; this.recurse(ast); var fnString = // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. // This is a workaround for this until we do a better job at only removing the prefix only when we should. '"' + this.USE + ' ' + this.STRICT + '";\n' + this.filterPrefix() + 'var fn=' + this.generateFunction('fn', 's,l,a,i') + extra + this.watchFns() + 'return fn;'; // eslint-disable-next-line no-new-func var fn = (new Function('$filter', 'ensureSafeMemberName', 'ensureSafeObject', 'ensureSafeFunction', 'getStringValue', 'ensureSafeAssignContext', 'ifDefined', 'plus', 'text', fnString))( this.$filter, ensureSafeMemberName, ensureSafeObject, ensureSafeFunction, getStringValue, ensureSafeAssignContext, ifDefined, plusFn, expression); this.state = this.stage = undefined; fn.literal = isLiteral(ast); fn.constant = isConstant(ast); return fn; }, USE: 'use', STRICT: 'strict', watchFns: function() { var result = []; var fns = this.state.inputs; var self = this; forEach(fns, function(name) { result.push('var ' + name + '=' + self.generateFunction(name, 's')); }); if (fns.length) { result.push('fn.inputs=[' + fns.join(',') + '];'); } return result.join(''); }, generateFunction: function(name, params) { return 'function(' + params + '){' + this.varsPrefix(name) + this.body(name) + '};'; }, filterPrefix: function() { var parts = []; var self = this; forEach(this.state.filters, function(id, filter) { parts.push(id + '=$filter(' + self.escape(filter) + ')'); }); if (parts.length) return 'var ' + parts.join(',') + ';'; return ''; }, varsPrefix: function(section) { return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; }, body: function(section) { return this.state[section].body.join(''); }, recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { var left, right, self = this, args, expression, computed; recursionFn = recursionFn || noop; if (!skipWatchIdCheck && isDefined(ast.watchId)) { intoId = intoId || this.nextId(); this.if_('i', this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) ); return; } switch (ast.type) { case AST.Program: forEach(ast.body, function(expression, pos) { self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); if (pos !== ast.body.length - 1) { self.current().body.push(right, ';'); } else { self.return_(right); } }); break; case AST.Literal: expression = this.escape(ast.value); this.assign(intoId, expression); recursionFn(expression); break; case AST.UnaryExpression: this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; this.assign(intoId, expression); recursionFn(expression); break; case AST.BinaryExpression: this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); if (ast.operator === '+') { expression = this.plus(left, right); } else if (ast.operator === '-') { expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); } else { expression = '(' + left + ')' + ast.operator + '(' + right + ')'; } this.assign(intoId, expression); recursionFn(expression); break; case AST.LogicalExpression: intoId = intoId || this.nextId(); self.recurse(ast.left, intoId); self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); recursionFn(intoId); break; case AST.ConditionalExpression: intoId = intoId || this.nextId(); self.recurse(ast.test, intoId); self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); recursionFn(intoId); break; case AST.Identifier: intoId = intoId || this.nextId(); if (nameId) { nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); nameId.computed = false; nameId.name = ast.name; } ensureSafeMemberName(ast.name); self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), function() { self.if_(self.stage === 'inputs' || 's', function() { if (create && create !== 1) { self.if_( self.not(self.nonComputedMember('s', ast.name)), self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); } self.assign(intoId, self.nonComputedMember('s', ast.name)); }); }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) ); if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { self.addEnsureSafeObject(intoId); } recursionFn(intoId); break; case AST.MemberExpression: left = nameId && (nameId.context = this.nextId()) || this.nextId(); intoId = intoId || this.nextId(); self.recurse(ast.object, left, undefined, function() { self.if_(self.notNull(left), function() { if (create && create !== 1) { self.addEnsureSafeAssignContext(left); } if (ast.computed) { right = self.nextId(); self.recurse(ast.property, right); self.getStringValue(right); self.addEnsureSafeMemberName(right); if (create && create !== 1) { self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); } expression = self.ensureSafeObject(self.computedMember(left, right)); self.assign(intoId, expression); if (nameId) { nameId.computed = true; nameId.name = right; } } else { ensureSafeMemberName(ast.property.name); if (create && create !== 1) { self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); } expression = self.nonComputedMember(left, ast.property.name); if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { expression = self.ensureSafeObject(expression); } self.assign(intoId, expression); if (nameId) { nameId.computed = false; nameId.name = ast.property.name; } } }, function() { self.assign(intoId, 'undefined'); }); recursionFn(intoId); }, !!create); break; case AST.CallExpression: intoId = intoId || this.nextId(); if (ast.filter) { right = self.filter(ast.callee.name); args = []; forEach(ast.arguments, function(expr) { var argument = self.nextId(); self.recurse(expr, argument); args.push(argument); }); expression = right + '(' + args.join(',') + ')'; self.assign(intoId, expression); recursionFn(intoId); } else { right = self.nextId(); left = {}; args = []; self.recurse(ast.callee, right, left, function() { self.if_(self.notNull(right), function() { self.addEnsureSafeFunction(right); forEach(ast.arguments, function(expr) { self.recurse(expr, self.nextId(), undefined, function(argument) { args.push(self.ensureSafeObject(argument)); }); }); if (left.name) { if (!self.state.expensiveChecks) { self.addEnsureSafeObject(left.context); } expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; } else { expression = right + '(' + args.join(',') + ')'; } expression = self.ensureSafeObject(expression); self.assign(intoId, expression); }, function() { self.assign(intoId, 'undefined'); }); recursionFn(intoId); }); } break; case AST.AssignmentExpression: right = this.nextId(); left = {}; this.recurse(ast.left, undefined, left, function() { self.if_(self.notNull(left.context), function() { self.recurse(ast.right, right); self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); self.addEnsureSafeAssignContext(left.context); expression = self.member(left.context, left.name, left.computed) + ast.operator + right; self.assign(intoId, expression); recursionFn(intoId || expression); }); }, 1); break; case AST.ArrayExpression: args = []; forEach(ast.elements, function(expr) { self.recurse(expr, self.nextId(), undefined, function(argument) { args.push(argument); }); }); expression = '[' + args.join(',') + ']'; this.assign(intoId, expression); recursionFn(expression); break; case AST.ObjectExpression: args = []; computed = false; forEach(ast.properties, function(property) { if (property.computed) { computed = true; } }); if (computed) { intoId = intoId || this.nextId(); this.assign(intoId, '{}'); forEach(ast.properties, function(property) { if (property.computed) { left = self.nextId(); self.recurse(property.key, left); } else { left = property.key.type === AST.Identifier ? property.key.name : ('' + property.key.value); } right = self.nextId(); self.recurse(property.value, right); self.assign(self.member(intoId, left, property.computed), right); }); } else { forEach(ast.properties, function(property) { self.recurse(property.value, ast.constant ? undefined : self.nextId(), undefined, function(expr) { args.push(self.escape( property.key.type === AST.Identifier ? property.key.name : ('' + property.key.value)) + ':' + expr); }); }); expression = '{' + args.join(',') + '}'; this.assign(intoId, expression); } recursionFn(intoId || expression); break; case AST.ThisExpression: this.assign(intoId, 's'); recursionFn('s'); break; case AST.LocalsExpression: this.assign(intoId, 'l'); recursionFn('l'); break; case AST.NGValueParameter: this.assign(intoId, 'v'); recursionFn('v'); break; } }, getHasOwnProperty: function(element, property) { var key = element + '.' + property; var own = this.current().own; if (!own.hasOwnProperty(key)) { own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); } return own[key]; }, assign: function(id, value) { if (!id) return; this.current().body.push(id, '=', value, ';'); return id; }, filter: function(filterName) { if (!this.state.filters.hasOwnProperty(filterName)) { this.state.filters[filterName] = this.nextId(true); } return this.state.filters[filterName]; }, ifDefined: function(id, defaultValue) { return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; }, plus: function(left, right) { return 'plus(' + left + ',' + right + ')'; }, return_: function(id) { this.current().body.push('return ', id, ';'); }, if_: function(test, alternate, consequent) { if (test === true) { alternate(); } else { var body = this.current().body; body.push('if(', test, '){'); alternate(); body.push('}'); if (consequent) { body.push('else{'); consequent(); body.push('}'); } } }, not: function(expression) { return '!(' + expression + ')'; }, notNull: function(expression) { return expression + '!=null'; }, nonComputedMember: function(left, right) { var SAFE_IDENTIFIER = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/; var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; if (SAFE_IDENTIFIER.test(right)) { return left + '.' + right; } else { return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; } }, computedMember: function(left, right) { return left + '[' + right + ']'; }, member: function(left, right, computed) { if (computed) return this.computedMember(left, right); return this.nonComputedMember(left, right); }, addEnsureSafeObject: function(item) { this.current().body.push(this.ensureSafeObject(item), ';'); }, addEnsureSafeMemberName: function(item) { this.current().body.push(this.ensureSafeMemberName(item), ';'); }, addEnsureSafeFunction: function(item) { this.current().body.push(this.ensureSafeFunction(item), ';'); }, addEnsureSafeAssignContext: function(item) { this.current().body.push(this.ensureSafeAssignContext(item), ';'); }, ensureSafeObject: function(item) { return 'ensureSafeObject(' + item + ',text)'; }, ensureSafeMemberName: function(item) { return 'ensureSafeMemberName(' + item + ',text)'; }, ensureSafeFunction: function(item) { return 'ensureSafeFunction(' + item + ',text)'; }, getStringValue: function(item) { this.assign(item, 'getStringValue(' + item + ')'); }, ensureSafeAssignContext: function(item) { return 'ensureSafeAssignContext(' + item + ',text)'; }, lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { var self = this; return function() { self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); }; }, lazyAssign: function(id, value) { var self = this; return function() { self.assign(id, value); }; }, stringEscapeRegex: /[^ a-zA-Z0-9]/g, stringEscapeFn: function(c) { return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); }, escape: function(value) { if (isString(value)) return '\'' + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + '\''; if (isNumber(value)) return value.toString(); if (value === true) return 'true'; if (value === false) return 'false'; if (value === null) return 'null'; if (typeof value === 'undefined') return 'undefined'; throw $parseMinErr('esc', 'IMPOSSIBLE'); }, nextId: function(skip, init) { var id = 'v' + (this.state.nextId++); if (!skip) { this.current().vars.push(id + (init ? '=' + init : '')); } return id; }, current: function() { return this.state[this.state.computing]; } }; function ASTInterpreter(astBuilder, $filter) { this.astBuilder = astBuilder; this.$filter = $filter; } ASTInterpreter.prototype = { compile: function(expression, expensiveChecks) { var self = this; var ast = this.astBuilder.ast(expression); this.expression = expression; this.expensiveChecks = expensiveChecks; findConstantAndWatchExpressions(ast, self.$filter); var assignable; var assign; if ((assignable = assignableAST(ast))) { assign = this.recurse(assignable); } var toWatch = getInputs(ast.body); var inputs; if (toWatch) { inputs = []; forEach(toWatch, function(watch, key) { var input = self.recurse(watch); watch.input = input; inputs.push(input); watch.watchId = key; }); } var expressions = []; forEach(ast.body, function(expression) { expressions.push(self.recurse(expression.expression)); }); var fn = ast.body.length === 0 ? noop : ast.body.length === 1 ? expressions[0] : function(scope, locals) { var lastValue; forEach(expressions, function(exp) { lastValue = exp(scope, locals); }); return lastValue; }; if (assign) { fn.assign = function(scope, value, locals) { return assign(scope, locals, value); }; } if (inputs) { fn.inputs = inputs; } fn.literal = isLiteral(ast); fn.constant = isConstant(ast); return fn; }, recurse: function(ast, context, create) { var left, right, self = this, args; if (ast.input) { return this.inputs(ast.input, ast.watchId); } switch (ast.type) { case AST.Literal: return this.value(ast.value, context); case AST.UnaryExpression: right = this.recurse(ast.argument); return this['unary' + ast.operator](right, context); case AST.BinaryExpression: left = this.recurse(ast.left); right = this.recurse(ast.right); return this['binary' + ast.operator](left, right, context); case AST.LogicalExpression: left = this.recurse(ast.left); right = this.recurse(ast.right); return this['binary' + ast.operator](left, right, context); case AST.ConditionalExpression: return this['ternary?:']( this.recurse(ast.test), this.recurse(ast.alternate), this.recurse(ast.consequent), context ); case AST.Identifier: ensureSafeMemberName(ast.name, self.expression); return self.identifier(ast.name, self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), context, create, self.expression); case AST.MemberExpression: left = this.recurse(ast.object, false, !!create); if (!ast.computed) { ensureSafeMemberName(ast.property.name, self.expression); right = ast.property.name; } if (ast.computed) right = this.recurse(ast.property); return ast.computed ? this.computedMember(left, right, context, create, self.expression) : this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); case AST.CallExpression: args = []; forEach(ast.arguments, function(expr) { args.push(self.recurse(expr)); }); if (ast.filter) right = this.$filter(ast.callee.name); if (!ast.filter) right = this.recurse(ast.callee, true); return ast.filter ? function(scope, locals, assign, inputs) { var values = []; for (var i = 0; i < args.length; ++i) { values.push(args[i](scope, locals, assign, inputs)); } var value = right.apply(undefined, values, inputs); return context ? {context: undefined, name: undefined, value: value} : value; } : function(scope, locals, assign, inputs) { var rhs = right(scope, locals, assign, inputs); var value; if (rhs.value != null) { ensureSafeObject(rhs.context, self.expression); ensureSafeFunction(rhs.value, self.expression); var values = []; for (var i = 0; i < args.length; ++i) { values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); } value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); } return context ? {value: value} : value; }; case AST.AssignmentExpression: left = this.recurse(ast.left, true, 1); right = this.recurse(ast.right); return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); var rhs = right(scope, locals, assign, inputs); ensureSafeObject(lhs.value, self.expression); ensureSafeAssignContext(lhs.context); lhs.context[lhs.name] = rhs; return context ? {value: rhs} : rhs; }; case AST.ArrayExpression: args = []; forEach(ast.elements, function(expr) { args.push(self.recurse(expr)); }); return function(scope, locals, assign, inputs) { var value = []; for (var i = 0; i < args.length; ++i) { value.push(args[i](scope, locals, assign, inputs)); } return context ? {value: value} : value; }; case AST.ObjectExpression: args = []; forEach(ast.properties, function(property) { if (property.computed) { args.push({key: self.recurse(property.key), computed: true, value: self.recurse(property.value) }); } else { args.push({key: property.key.type === AST.Identifier ? property.key.name : ('' + property.key.value), computed: false, value: self.recurse(property.value) }); } }); return function(scope, locals, assign, inputs) { var value = {}; for (var i = 0; i < args.length; ++i) { if (args[i].computed) { value[args[i].key(scope, locals, assign, inputs)] = args[i].value(scope, locals, assign, inputs); } else { value[args[i].key] = args[i].value(scope, locals, assign, inputs); } } return context ? {value: value} : value; }; case AST.ThisExpression: return function(scope) { return context ? {value: scope} : scope; }; case AST.LocalsExpression: return function(scope, locals) { return context ? {value: locals} : locals; }; case AST.NGValueParameter: return function(scope, locals, assign) { return context ? {value: assign} : assign; }; } }, 'unary+': function(argument, context) { return function(scope, locals, assign, inputs) { var arg = argument(scope, locals, assign, inputs); if (isDefined(arg)) { arg = +arg; } else { arg = 0; } return context ? {value: arg} : arg; }; }, 'unary-': function(argument, context) { return function(scope, locals, assign, inputs) { var arg = argument(scope, locals, assign, inputs); if (isDefined(arg)) { arg = -arg; } else { arg = 0; } return context ? {value: arg} : arg; }; }, 'unary!': function(argument, context) { return function(scope, locals, assign, inputs) { var arg = !argument(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary+': function(left, right, context) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); var rhs = right(scope, locals, assign, inputs); var arg = plusFn(lhs, rhs); return context ? {value: arg} : arg; }; }, 'binary-': function(left, right, context) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); var rhs = right(scope, locals, assign, inputs); var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); return context ? {value: arg} : arg; }; }, 'binary*': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary/': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary%': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary===': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary!==': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary==': function(left, right, context) { return function(scope, locals, assign, inputs) { // eslint-disable-next-line eqeqeq var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary!=': function(left, right, context) { return function(scope, locals, assign, inputs) { // eslint-disable-next-line eqeqeq var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary<': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary>': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary<=': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary>=': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary&&': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'binary||': function(left, right, context) { return function(scope, locals, assign, inputs) { var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, 'ternary?:': function(test, alternate, consequent, context) { return function(scope, locals, assign, inputs) { var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); return context ? {value: arg} : arg; }; }, value: function(value, context) { return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; }, identifier: function(name, expensiveChecks, context, create, expression) { return function(scope, locals, assign, inputs) { var base = locals && (name in locals) ? locals : scope; if (create && create !== 1 && base && !(base[name])) { base[name] = {}; } var value = base ? base[name] : undefined; if (expensiveChecks) { ensureSafeObject(value, expression); } if (context) { return {context: base, name: name, value: value}; } else { return value; } }; }, computedMember: function(left, right, context, create, expression) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); var rhs; var value; if (lhs != null) { rhs = right(scope, locals, assign, inputs); rhs = getStringValue(rhs); ensureSafeMemberName(rhs, expression); if (create && create !== 1) { ensureSafeAssignContext(lhs); if (lhs && !(lhs[rhs])) { lhs[rhs] = {}; } } value = lhs[rhs]; ensureSafeObject(value, expression); } if (context) { return {context: lhs, name: rhs, value: value}; } else { return value; } }; }, nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { return function(scope, locals, assign, inputs) { var lhs = left(scope, locals, assign, inputs); if (create && create !== 1) { ensureSafeAssignContext(lhs); if (lhs && !(lhs[right])) { lhs[right] = {}; } } var value = lhs != null ? lhs[right] : undefined; if (expensiveChecks || isPossiblyDangerousMemberName(right)) { ensureSafeObject(value, expression); } if (context) { return {context: lhs, name: right, value: value}; } else { return value; } }; }, inputs: function(input, watchId) { return function(scope, value, locals, inputs) { if (inputs) return inputs[watchId]; return input(scope, value, locals); }; } }; /** * @constructor */ var Parser = function Parser(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; this.ast = new AST(lexer, options); this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : new ASTCompiler(this.ast, $filter); }; Parser.prototype = { constructor: Parser, parse: function(text) { return this.astCompiler.compile(text, this.options.expensiveChecks); } }; function isPossiblyDangerousMemberName(name) { return name === 'constructor'; } function getValueOf(value) { return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); } /////////////////////////////////// /** * @ngdoc service * @name $parse * @kind function * * @description * * Converts Angular {@link guide/expression expression} into a function. * * ```js * var getter = $parse('user.name'); * var setter = getter.assign; * var context = {user:{name:'angular'}}; * var locals = {user:{name:'local'}}; * * expect(getter(context)).toEqual('angular'); * setter(context, 'newValue'); * expect(context.user.name).toEqual('newValue'); * expect(getter(context, locals)).toEqual('local'); * ``` * * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. * * The returned function also has the following properties: * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript * literal. * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript * constant literals. * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be * set to a function to change its value on the given context. * */ /** * @ngdoc provider * @name $parseProvider * @this * * @description * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} * service. */ function $ParseProvider() { var cacheDefault = createMap(); var cacheExpensive = createMap(); var literals = { 'true': true, 'false': false, 'null': null, 'undefined': undefined }; var identStart, identContinue; /** * @ngdoc method * @name $parseProvider#addLiteral * @description * * Configure $parse service to add literal values that will be present as literal at expressions. * * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. * **/ this.addLiteral = function(literalName, literalValue) { literals[literalName] = literalValue; }; /** * @ngdoc method * @name $parseProvider#setIdentifierFns * * @description * * Allows defining the set of characters that are allowed in Angular expressions. The function * `identifierStart` will get called to know if a given character is a valid character to be the * first character for an identifier. The function `identifierContinue` will get called to know if * a given character is a valid character to be a follow-up identifier character. The functions * `identifierStart` and `identifierContinue` will receive as arguments the single character to be * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in * mind that the `string` parameter can be two characters long depending on the character * representation. It is expected for the function to return `true` or `false`, whether that * character is allowed or not. * * Since this function will be called extensively, keep the implementation of these functions fast, * as the performance of these functions have a direct impact on the expressions parsing speed. * * @param {function=} identifierStart The function that will decide whether the given character is * a valid identifier start character. * @param {function=} identifierContinue The function that will decide whether the given character is * a valid identifier continue character. */ this.setIdentifierFns = function(identifierStart, identifierContinue) { identStart = identifierStart; identContinue = identifierContinue; return this; }; this.$get = ['$filter', function($filter) { var noUnsafeEval = csp().noUnsafeEval; var $parseOptions = { csp: noUnsafeEval, expensiveChecks: false, literals: copy(literals), isIdentifierStart: isFunction(identStart) && identStart, isIdentifierContinue: isFunction(identContinue) && identContinue }, $parseOptionsExpensive = { csp: noUnsafeEval, expensiveChecks: true, literals: copy(literals), isIdentifierStart: isFunction(identStart) && identStart, isIdentifierContinue: isFunction(identContinue) && identContinue }; var runningChecksEnabled = false; $parse.$$runningExpensiveChecks = function() { return runningChecksEnabled; }; return $parse; function $parse(exp, interceptorFn, expensiveChecks) { var parsedExpression, oneTime, cacheKey; expensiveChecks = expensiveChecks || runningChecksEnabled; switch (typeof exp) { case 'string': exp = exp.trim(); cacheKey = exp; var cache = (expensiveChecks ? cacheExpensive : cacheDefault); parsedExpression = cache[cacheKey]; if (!parsedExpression) { if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { oneTime = true; exp = exp.substring(2); } var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; var lexer = new Lexer(parseOptions); var parser = new Parser(lexer, $filter, parseOptions); parsedExpression = parser.parse(exp); if (parsedExpression.constant) { parsedExpression.$$watchDelegate = constantWatchDelegate; } else if (oneTime) { parsedExpression.$$watchDelegate = parsedExpression.literal ? oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; } else if (parsedExpression.inputs) { parsedExpression.$$watchDelegate = inputsWatchDelegate; } if (expensiveChecks) { parsedExpression = expensiveChecksInterceptor(parsedExpression); } cache[cacheKey] = parsedExpression; } return addInterceptor(parsedExpression, interceptorFn); case 'function': return addInterceptor(exp, interceptorFn); default: return addInterceptor(noop, interceptorFn); } } function expensiveChecksInterceptor(fn) { if (!fn) return fn; expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); expensiveCheckFn.constant = fn.constant; expensiveCheckFn.literal = fn.literal; for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); } expensiveCheckFn.inputs = fn.inputs; return expensiveCheckFn; function expensiveCheckFn(scope, locals, assign, inputs) { var expensiveCheckOldValue = runningChecksEnabled; runningChecksEnabled = true; try { return fn(scope, locals, assign, inputs); } finally { runningChecksEnabled = expensiveCheckOldValue; } } } function expressionInputDirtyCheck(newValue, oldValueOfValue) { if (newValue == null || oldValueOfValue == null) { // null/undefined return newValue === oldValueOfValue; } if (typeof newValue === 'object') { // attempt to convert the value to a primitive type // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can // be cheaply dirty-checked newValue = getValueOf(newValue); if (typeof newValue === 'object') { // objects/arrays are not supported - deep-watching them would be too expensive return false; } // fall-through to the primitive equality check } //Primitive or NaN // eslint-disable-next-line no-self-compare return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); } function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { var inputExpressions = parsedExpression.inputs; var lastResult; if (inputExpressions.length === 1) { var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails inputExpressions = inputExpressions[0]; return scope.$watch(function expressionInputWatch(scope) { var newInputValue = inputExpressions(scope); if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); oldInputValueOf = newInputValue && getValueOf(newInputValue); } return lastResult; }, listener, objectEquality, prettyPrintExpression); } var oldInputValueOfValues = []; var oldInputValues = []; for (var i = 0, ii = inputExpressions.length; i < ii; i++) { oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails oldInputValues[i] = null; } return scope.$watch(function expressionInputsWatch(scope) { var changed = false; for (var i = 0, ii = inputExpressions.length; i < ii; i++) { var newInputValue = inputExpressions[i](scope); if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { oldInputValues[i] = newInputValue; oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); } } if (changed) { lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); } return lastResult; }, listener, objectEquality, prettyPrintExpression); } function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch, lastValue; unwatch = scope.$watch(function oneTimeWatch(scope) { return parsedExpression(scope); }, /** @this */ function oneTimeListener(value, old, scope) { lastValue = value; if (isFunction(listener)) { listener.apply(this, arguments); } if (isDefined(value)) { scope.$$postDigest(function() { if (isDefined(lastValue)) { unwatch(); } }); } }, objectEquality); return unwatch; } function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch, lastValue; unwatch = scope.$watch(function oneTimeWatch(scope) { return parsedExpression(scope); }, /** @this */ function oneTimeListener(value, old, scope) { lastValue = value; if (isFunction(listener)) { listener.call(this, value, old, scope); } if (isAllDefined(value)) { scope.$$postDigest(function() { if (isAllDefined(lastValue)) unwatch(); }); } }, objectEquality); return unwatch; function isAllDefined(value) { var allDefined = true; forEach(value, function(val) { if (!isDefined(val)) allDefined = false; }); return allDefined; } } function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch = scope.$watch(function constantWatch(scope) { unwatch(); return parsedExpression(scope); }, listener, objectEquality); return unwatch; } function addInterceptor(parsedExpression, interceptorFn) { if (!interceptorFn) return parsedExpression; var watchDelegate = parsedExpression.$$watchDelegate; var useInputs = false; var regularWatch = watchDelegate !== oneTimeLiteralWatchDelegate && watchDelegate !== oneTimeWatchDelegate; var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); return interceptorFn(value, scope, locals); } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { var value = parsedExpression(scope, locals, assign, inputs); var result = interceptorFn(value, scope, locals); // we only return the interceptor's result if the // initial value is defined (for bind-once) return isDefined(value) ? result : value; }; // Propagate $$watchDelegates other then inputsWatchDelegate if (parsedExpression.$$watchDelegate && parsedExpression.$$watchDelegate !== inputsWatchDelegate) { fn.$$watchDelegate = parsedExpression.$$watchDelegate; } else if (!interceptorFn.$stateful) { // If there is an interceptor, but no watchDelegate then treat the interceptor like // we treat filters - it is assumed to be a pure function unless flagged with $stateful fn.$$watchDelegate = inputsWatchDelegate; useInputs = !parsedExpression.inputs; fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; } return fn; } }]; } /** * @ngdoc service * @name $q * @requires $rootScope * @this * * @description * A service that helps you run functions asynchronously, and use their return values (or exceptions) * when they are done processing. * * This is an implementation of promises/deferred objects inspired by * [Kris Kowal's Q](https://github.com/kriskowal/q). * * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred * implementations, and the other which resembles ES6 (ES2015) promises to some degree. * * # $q constructor * * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` * function as the first argument. This is similar to the native Promise implementation from ES6, * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). * * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are * available yet. * * It can be used like so: * * ```js * // for the purpose of this example let's assume that variables `$q` and `okToGreet` * // are available in the current lexical scope (they could have been injected or passed in). * * function asyncGreet(name) { * // perform some asynchronous operation, resolve or reject the promise when appropriate. * return $q(function(resolve, reject) { * setTimeout(function() { * if (okToGreet(name)) { * resolve('Hello, ' + name + '!'); * } else { * reject('Greeting ' + name + ' is not allowed.'); * } * }, 1000); * }); * } * * var promise = asyncGreet('Robin Hood'); * promise.then(function(greeting) { * alert('Success: ' + greeting); * }, function(reason) { * alert('Failed: ' + reason); * }); * ``` * * Note: progress/notify callbacks are not currently supported via the ES6-style interface. * * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. * * However, the more traditional CommonJS-style usage is still available, and documented below. * * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an * interface for interacting with an object that represents the result of an action that is * performed asynchronously, and may or may not be finished at any given point in time. * * From the perspective of dealing with error handling, deferred and promise APIs are to * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. * * ```js * // for the purpose of this example let's assume that variables `$q` and `okToGreet` * // are available in the current lexical scope (they could have been injected or passed in). * * function asyncGreet(name) { * var deferred = $q.defer(); * * setTimeout(function() { * deferred.notify('About to greet ' + name + '.'); * * if (okToGreet(name)) { * deferred.resolve('Hello, ' + name + '!'); * } else { * deferred.reject('Greeting ' + name + ' is not allowed.'); * } * }, 1000); * * return deferred.promise; * } * * var promise = asyncGreet('Robin Hood'); * promise.then(function(greeting) { * alert('Success: ' + greeting); * }, function(reason) { * alert('Failed: ' + reason); * }, function(update) { * alert('Got notification: ' + update); * }); * ``` * * At first it might not be obvious why this extra complexity is worth the trouble. The payoff * comes in the way of guarantees that promise and deferred APIs make, see * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. * * Additionally the promise api allows for composition that is very hard to do with the * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the * section on serial or parallel joining of promises. * * # The Deferred API * * A new instance of deferred is constructed by calling `$q.defer()`. * * The purpose of the deferred object is to expose the associated Promise instance as well as APIs * that can be used for signaling the successful or unsuccessful completion, as well as the status * of the task. * * **Methods** * * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection * constructed via `$q.reject`, the promise will be rejected instead. * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to * resolving it with a rejection constructed via `$q.reject`. * - `notify(value)` - provides updates on the status of the promise's execution. This may be called * multiple times before the promise is either resolved or rejected. * * **Properties** * * - promise – `{Promise}` – promise object associated with this deferred. * * * # The Promise API * * A new promise instance is created when a deferred instance is created and can be retrieved by * calling `deferred.promise`. * * The purpose of the promise object is to allow for interested parties to get access to the result * of the deferred task when it completes. * * **Methods** * * - `then(successCallback, [errorCallback], [notifyCallback])` – regardless of when the promise was or * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously * as soon as the result is available. The callbacks are called with a single argument: the result * or rejection reason. Additionally, the notify callback may be called zero or more times to * provide a progress indication, before the promise is resolved or rejected. * * This method *returns a new promise* which is resolved or rejected via the return value of the * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved * with the value which is resolved in that promise using * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). * It also notifies via the return value of the `notifyCallback` method. The promise cannot be * resolved or rejected from the notifyCallback method. The errorCallback and notifyCallback * arguments are optional. * * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` * * - `finally(callback, notifyCallback)` – allows you to observe either the fulfillment or rejection of a promise, * but to do so without modifying the final value. This is useful to release resources or do some * clean-up that needs to be done whether the promise was rejected or resolved. See the [full * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for * more information. * * # Chaining promises * * Because calling the `then` method of a promise returns a new derived promise, it is easily * possible to create a chain of promises: * * ```js * promiseB = promiseA.then(function(result) { * return result + 1; * }); * * // promiseB will be resolved immediately after promiseA is resolved and its value * // will be the result of promiseA incremented by 1 * ``` * * It is possible to create chains of any length and since a promise can be resolved with another * promise (which will defer its resolution further), it is possible to pause/defer resolution of * the promises at any point in the chain. This makes it possible to implement powerful APIs like * $http's response interceptors. * * * # Differences between Kris Kowal's Q and $q * * There are two main differences: * * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation * mechanism in angular, which means faster propagation of resolution or rejection into your * models and avoiding unnecessary browser repaints, which would result in flickering UI. * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains * all the important functionality needed for common async tasks. * * # Testing * * ```js * it('should simulate promise', inject(function($q, $rootScope) { * var deferred = $q.defer(); * var promise = deferred.promise; * var resolvedValue; * * promise.then(function(value) { resolvedValue = value; }); * expect(resolvedValue).toBeUndefined(); * * // Simulate resolving of promise * deferred.resolve(123); * // Note that the 'then' function does not get called synchronously. * // This is because we want the promise API to always be async, whether or not * // it got called synchronously or asynchronously. * expect(resolvedValue).toBeUndefined(); * * // Propagate promise resolution to 'then' functions using $apply(). * $rootScope.$apply(); * expect(resolvedValue).toEqual(123); * })); * ``` * * @param {function(function, function)} resolver Function which is responsible for resolving or * rejecting the newly created promise. The first parameter is a function which resolves the * promise, the second parameter is a function which rejects the promise. * * @returns {Promise} The newly created promise. */ function $QProvider() { this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { return qFactory(function(callback) { $rootScope.$evalAsync(callback); }, $exceptionHandler); }]; } /** @this */ function $$QProvider() { this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { return qFactory(function(callback) { $browser.defer(callback); }, $exceptionHandler); }]; } /** * Constructs a promise manager. * * @param {function(function)} nextTick Function for executing functions in the next turn. * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for * debugging purposes. * @returns {object} Promise manager. */ function qFactory(nextTick, exceptionHandler) { var $qMinErr = minErr('$q', TypeError); /** * @ngdoc method * @name ng.$q#defer * @kind function * * @description * Creates a `Deferred` object which represents a task which will finish in the future. * * @returns {Deferred} Returns a new instance of deferred. */ function defer() { var d = new Deferred(); //Necessary to support unbound execution :/ d.resolve = simpleBind(d, d.resolve); d.reject = simpleBind(d, d.reject); d.notify = simpleBind(d, d.notify); return d; } function Promise() { this.$$state = { status: 0 }; } extend(Promise.prototype, { then: function(onFulfilled, onRejected, progressBack) { if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { return this; } var result = new Deferred(); this.$$state.pending = this.$$state.pending || []; this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); return result.promise; }, 'catch': function(callback) { return this.then(null, callback); }, 'finally': function(callback, progressBack) { return this.then(function(value) { return handleCallback(value, resolve, callback); }, function(error) { return handleCallback(error, reject, callback); }, progressBack); } }); //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native function simpleBind(context, fn) { return function(value) { fn.call(context, value); }; } function processQueue(state) { var fn, deferred, pending; pending = state.pending; state.processScheduled = false; state.pending = undefined; for (var i = 0, ii = pending.length; i < ii; ++i) { deferred = pending[i][0]; fn = pending[i][state.status]; try { if (isFunction(fn)) { deferred.resolve(fn(state.value)); } else if (state.status === 1) { deferred.resolve(state.value); } else { deferred.reject(state.value); } } catch (e) { deferred.reject(e); exceptionHandler(e); } } } function scheduleProcessQueue(state) { if (state.processScheduled || !state.pending) return; state.processScheduled = true; nextTick(function() { processQueue(state); }); } function Deferred() { this.promise = new Promise(); } extend(Deferred.prototype, { resolve: function(val) { if (this.promise.$$state.status) return; if (val === this.promise) { this.$$reject($qMinErr( 'qcycle', 'Expected promise to be resolved with value other than itself \'{0}\'', val)); } else { this.$$resolve(val); } }, $$resolve: function(val) { var then; var that = this; var done = false; try { if ((isObject(val) || isFunction(val))) then = val && val.then; if (isFunction(then)) { this.promise.$$state.status = -1; then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); } else { this.promise.$$state.value = val; this.promise.$$state.status = 1; scheduleProcessQueue(this.promise.$$state); } } catch (e) { rejectPromise(e); exceptionHandler(e); } function resolvePromise(val) { if (done) return; done = true; that.$$resolve(val); } function rejectPromise(val) { if (done) return; done = true; that.$$reject(val); } }, reject: function(reason) { if (this.promise.$$state.status) return; this.$$reject(reason); }, $$reject: function(reason) { this.promise.$$state.value = reason; this.promise.$$state.status = 2; scheduleProcessQueue(this.promise.$$state); }, notify: function(progress) { var callbacks = this.promise.$$state.pending; if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { nextTick(function() { var callback, result; for (var i = 0, ii = callbacks.length; i < ii; i++) { result = callbacks[i][0]; callback = callbacks[i][3]; try { result.notify(isFunction(callback) ? callback(progress) : progress); } catch (e) { exceptionHandler(e); } } }); } } }); /** * @ngdoc method * @name $q#reject * @kind function * * @description * Creates a promise that is resolved as rejected with the specified `reason`. This api should be * used to forward rejection in a chain of promises. If you are dealing with the last promise in * a promise chain, you don't need to worry about it. * * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via * a promise error callback and you want to forward the error to the promise derived from the * current promise, you have to "rethrow" the error by returning a rejection constructed via * `reject`. * * ```js * promiseB = promiseA.then(function(result) { * // success: do something and resolve promiseB * // with the old or a new result * return result; * }, function(reason) { * // error: handle the error if possible and * // resolve promiseB with newPromiseOrValue, * // otherwise forward the rejection to promiseB * if (canHandle(reason)) { * // handle the error and recover * return newPromiseOrValue; * } * return $q.reject(reason); * }); * ``` * * @param {*} reason Constant, message, exception or an object representing the rejection reason. * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. */ function reject(reason) { var result = new Deferred(); result.reject(reason); return result.promise; } function handleCallback(value, resolver, callback) { var callbackOutput = null; try { if (isFunction(callback)) callbackOutput = callback(); } catch (e) { return reject(e); } if (isPromiseLike(callbackOutput)) { return callbackOutput.then(function() { return resolver(value); }, reject); } else { return resolver(value); } } /** * @ngdoc method * @name $q#when * @kind function * * @description * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. * This is useful when you are dealing with an object that might or might not be a promise, or if * the promise comes from a source that can't be trusted. * * @param {*} value Value or a promise * @param {Function=} successCallback * @param {Function=} errorCallback * @param {Function=} progressCallback * @returns {Promise} Returns a promise of the passed value or promise */ function when(value, callback, errback, progressBack) { var result = new Deferred(); result.resolve(value); return result.promise.then(callback, errback, progressBack); } /** * @ngdoc method * @name $q#resolve * @kind function * * @description * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. * * @param {*} value Value or a promise * @param {Function=} successCallback * @param {Function=} errorCallback * @param {Function=} progressCallback * @returns {Promise} Returns a promise of the passed value or promise */ var resolve = when; /** * @ngdoc method * @name $q#all * @kind function * * @description * Combines multiple promises into a single promise that is resolved when all of the input * promises are resolved. * * @param {Array.|Object.} promises An array or hash of promises. * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, * each value corresponding to the promise at the same index/key in the `promises` array/hash. * If any of the promises is resolved with a rejection, this resulting promise will be rejected * with the same rejection value. */ function all(promises) { var deferred = new Deferred(), counter = 0, results = isArray(promises) ? [] : {}; forEach(promises, function(promise, key) { counter++; when(promise).then(function(value) { results[key] = value; if (!(--counter)) deferred.resolve(results); }, function(reason) { deferred.reject(reason); }); }); if (counter === 0) { deferred.resolve(results); } return deferred.promise; } /** * @ngdoc method * @name $q#race * @kind function * * @description * Returns a promise that resolves or rejects as soon as one of those promises * resolves or rejects, with the value or reason from that promise. * * @param {Array.|Object.} promises An array or hash of promises. * @returns {Promise} a promise that resolves or rejects as soon as one of the `promises` * resolves or rejects, with the value or reason from that promise. */ function race(promises) { var deferred = defer(); forEach(promises, function(promise) { when(promise).then(deferred.resolve, deferred.reject); }); return deferred.promise; } function $Q(resolver) { if (!isFunction(resolver)) { throw $qMinErr('norslvr', 'Expected resolverFn, got \'{0}\'', resolver); } var deferred = new Deferred(); function resolveFn(value) { deferred.resolve(value); } function rejectFn(reason) { deferred.reject(reason); } resolver(resolveFn, rejectFn); return deferred.promise; } // Let's make the instanceof operator work for promises, so that // `new $q(fn) instanceof $q` would evaluate to true. $Q.prototype = Promise.prototype; $Q.defer = defer; $Q.reject = reject; $Q.when = when; $Q.resolve = resolve; $Q.all = all; $Q.race = race; return $Q; } /** @this */ function $$RAFProvider() { //rAF this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame; var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; var rafSupported = !!requestAnimationFrame; var raf = rafSupported ? function(fn) { var id = requestAnimationFrame(fn); return function() { cancelAnimationFrame(id); }; } : function(fn) { var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 return function() { $timeout.cancel(timer); }; }; raf.supported = rafSupported; return raf; }]; } /** * DESIGN NOTES * * The design decisions behind the scope are heavily favored for speed and memory consumption. * * The typical use of scope is to watch the expressions, which most of the time return the same * value as last time so we optimize the operation. * * Closures construction is expensive in terms of speed as well as memory: * - No closures, instead use prototypical inheritance for API * - Internal state needs to be stored on scope directly, which means that private state is * exposed as $$____ properties * * Loop operations are optimized by using while(count--) { ... } * - This means that in order to keep the same order of execution as addition we have to add * items to the array at the beginning (unshift) instead of at the end (push) * * Child scopes are created and removed often * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists * * There are fewer watches than observers. This is why you don't want the observer to be implemented * in the same way as watch. Watch requires return of the initialization function which is expensive * to construct. */ /** * @ngdoc provider * @name $rootScopeProvider * @description * * Provider for the $rootScope service. */ /** * @ngdoc method * @name $rootScopeProvider#digestTtl * @description * * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and * assuming that the model is unstable. * * The current default is 10 iterations. * * In complex applications it's possible that the dependencies between `$watch`s will result in * several digest iterations. However if an application needs more than the default 10 digest * iterations for its model to stabilize then you should investigate what is causing the model to * continuously change during the digest. * * Increasing the TTL could have performance implications, so you should not change it without * proper justification. * * @param {number} limit The number of digest iterations. */ /** * @ngdoc service * @name $rootScope * @this * * @description * * Every application has a single root {@link ng.$rootScope.Scope scope}. * All other scopes are descendant scopes of the root scope. Scopes provide separation * between the model and the view, via a mechanism for watching the model for changes. * They also provide event emission/broadcast and subscription facility. See the * {@link guide/scope developer guide on scopes}. */ function $RootScopeProvider() { var TTL = 10; var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; var applyAsyncId = null; this.digestTtl = function(value) { if (arguments.length) { TTL = value; } return TTL; }; function createChildScopeClass(parent) { function ChildScope() { this.$$watchers = this.$$nextSibling = this.$$childHead = this.$$childTail = null; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount = 0; this.$id = nextUid(); this.$$ChildScope = null; } ChildScope.prototype = parent; return ChildScope; } this.$get = ['$exceptionHandler', '$parse', '$browser', function($exceptionHandler, $parse, $browser) { function destroyChildScope($event) { $event.currentScope.$$destroyed = true; } function cleanUpScope($scope) { if (msie === 9) { // There is a memory leak in IE9 if all child scopes are not disconnected // completely when a scope is destroyed. So this code will recurse up through // all this scopes children // // See issue https://github.com/angular/angular.js/issues/10706 if ($scope.$$childHead) { cleanUpScope($scope.$$childHead); } if ($scope.$$nextSibling) { cleanUpScope($scope.$$nextSibling); } } // The code below works around IE9 and V8's memory leaks // // See: // - https://code.google.com/p/v8/issues/detail?id=2073#c26 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = $scope.$$childTail = $scope.$root = $scope.$$watchers = null; } /** * @ngdoc type * @name $rootScope.Scope * * @description * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the * {@link auto.$injector $injector}. Child scopes are created using the * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for * an in-depth introduction and usage examples. * * * # Inheritance * A scope can inherit from a parent scope, as in this example: * ```js var parent = $rootScope; var child = parent.$new(); parent.salutation = "Hello"; expect(child.salutation).toEqual('Hello'); child.salutation = "Welcome"; expect(child.salutation).toEqual('Welcome'); expect(parent.salutation).toEqual('Hello'); * ``` * * When interacting with `Scope` in tests, additional helper methods are available on the * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional * details. * * * @param {Object.=} providers Map of service factory which need to be * provided for the current scope. Defaults to {@link ng}. * @param {Object.=} instanceCache Provides pre-instantiated services which should * append/override services provided by `providers`. This is handy * when unit-testing and having the need to override a default * service. * @returns {Object} Newly created scope. * */ function Scope() { this.$id = nextUid(); this.$$phase = this.$parent = this.$$watchers = this.$$nextSibling = this.$$prevSibling = this.$$childHead = this.$$childTail = null; this.$root = this; this.$$destroyed = false; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount = 0; this.$$isolateBindings = null; } /** * @ngdoc property * @name $rootScope.Scope#$id * * @description * Unique scope ID (monotonically increasing) useful for debugging. */ /** * @ngdoc property * @name $rootScope.Scope#$parent * * @description * Reference to the parent scope. */ /** * @ngdoc property * @name $rootScope.Scope#$root * * @description * Reference to the root scope. */ Scope.prototype = { constructor: Scope, /** * @ngdoc method * @name $rootScope.Scope#$new * @kind function * * @description * Creates a new child {@link ng.$rootScope.Scope scope}. * * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. * * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is * desired for the scope and its child scopes to be permanently detached from the parent and * thus stop participating in model change detection and listener notification by invoking. * * @param {boolean} isolate If true, then the scope does not prototypically inherit from the * parent scope. The scope is isolated, as it can not see parent scope properties. * When creating widgets, it is useful for the widget to not accidentally read parent * state. * * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` * of the newly created scope. Defaults to `this` scope if not provided. * This is used when creating a transclude scope to correctly place it * in the scope hierarchy while maintaining the correct prototypical * inheritance. * * @returns {Object} The newly created child scope. * */ $new: function(isolate, parent) { var child; parent = parent || this; if (isolate) { child = new Scope(); child.$root = this.$root; } else { // Only create a child scope class if somebody asks for one, // but cache it to allow the VM to optimize lookups. if (!this.$$ChildScope) { this.$$ChildScope = createChildScopeClass(this); } child = new this.$$ChildScope(); } child.$parent = parent; child.$$prevSibling = parent.$$childTail; if (parent.$$childHead) { parent.$$childTail.$$nextSibling = child; parent.$$childTail = child; } else { parent.$$childHead = parent.$$childTail = child; } // When the new scope is not isolated or we inherit from `this`, and // the parent scope is destroyed, the property `$$destroyed` is inherited // prototypically. In all other cases, this property needs to be set // when the parent scope is destroyed. // The listener needs to be added after the parent is set if (isolate || parent !== this) child.$on('$destroy', destroyChildScope); return child; }, /** * @ngdoc method * @name $rootScope.Scope#$watch * @kind function * * @description * Registers a `listener` callback to be executed whenever the `watchExpression` changes. * * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest * $digest()} and should return the value that will be watched. (`watchExpression` should not change * its value when executed multiple times with the same input because it may be executed multiple * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be * [idempotent](http://en.wikipedia.org/wiki/Idempotence).) * - The `listener` is called only when the value from the current `watchExpression` and the * previous call to `watchExpression` are not equal (with the exception of the initial run, * see below). Inequality is determined according to reference inequality, * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) * via the `!==` Javascript operator, unless `objectEquality == true` * (see next point) * - When `objectEquality == true`, inequality of the `watchExpression` is determined * according to the {@link angular.equals} function. To save the value of the object for * later comparison, the {@link angular.copy} function is used. This therefore means that * watching complex objects will have adverse memory and performance implications. * - The watch `listener` may change the model, which may trigger other `listener`s to fire. * This is achieved by rerunning the watchers until no changes are detected. The rerun * iteration limit is 10 to prevent an infinite loop deadlock. * * * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, * you can register a `watchExpression` function with no `listener`. (Be prepared for * multiple calls to your `watchExpression` because it will execute multiple times in a * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) * * After a watcher is registered with the scope, the `listener` fn is called asynchronously * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the * watcher. In rare cases, this is undesirable because the listener is called when the result * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the * listener was called due to initialization. * * * * # Example * ```js // let's assume that scope was dependency injected as the $rootScope var scope = $rootScope; scope.name = 'misko'; scope.counter = 0; expect(scope.counter).toEqual(0); scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; }); expect(scope.counter).toEqual(0); scope.$digest(); // the listener is always called during the first $digest loop after it was registered expect(scope.counter).toEqual(1); scope.$digest(); // but now it will not be called unless the value changes expect(scope.counter).toEqual(1); scope.name = 'adam'; scope.$digest(); expect(scope.counter).toEqual(2); // Using a function as a watchExpression var food; scope.foodCounter = 0; expect(scope.foodCounter).toEqual(0); scope.$watch( // This function returns the value being watched. It is called for each turn of the $digest loop function() { return food; }, // This is the change listener, called when the value returned from the above function changes function(newValue, oldValue) { if ( newValue !== oldValue ) { // Only increment the counter if the value changed scope.foodCounter = scope.foodCounter + 1; } } ); // No digest has been run so the counter will be zero expect(scope.foodCounter).toEqual(0); // Run the digest but since food has not changed count will still be zero scope.$digest(); expect(scope.foodCounter).toEqual(0); // Update food and run digest. Now the counter will increment food = 'cheeseburger'; scope.$digest(); expect(scope.foodCounter).toEqual(1); * ``` * * * * @param {(function()|string)} watchExpression Expression that is evaluated on each * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers * a call to the `listener`. * * - `string`: Evaluated as {@link guide/expression expression} * - `function(scope)`: called with current `scope` as a parameter. * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value * of `watchExpression` changes. * * - `newVal` contains the current value of the `watchExpression` * - `oldVal` contains the previous value of the `watchExpression` * - `scope` refers to the current scope * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of * comparing for reference equality. * @returns {function()} Returns a deregistration function for this listener. */ $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { var get = $parse(watchExp); if (get.$$watchDelegate) { return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); } var scope = this, array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: prettyPrintExpression || watchExp, eq: !!objectEquality }; lastDirtyWatch = null; if (!isFunction(listener)) { watcher.fn = noop; } if (!array) { array = scope.$$watchers = []; array.$$digestWatchIndex = -1; } // we use unshift since we use a while loop in $digest for speed. // the while loop reads in reverse order. array.unshift(watcher); array.$$digestWatchIndex++; incrementWatchersCount(this, 1); return function deregisterWatch() { var index = arrayRemove(array, watcher); if (index >= 0) { incrementWatchersCount(scope, -1); if (index < array.$$digestWatchIndex) { array.$$digestWatchIndex--; } } lastDirtyWatch = null; }; }, /** * @ngdoc method * @name $rootScope.Scope#$watchGroup * @kind function * * @description * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. * If any one expression in the collection changes the `listener` is executed. * * - The items in the `watchExpressions` array are observed via the standard `$watch` operation. Their return * values are examined for changes on every call to `$digest`. * - The `listener` is called whenever any expression in the `watchExpressions` array changes. * * @param {Array.} watchExpressions Array of expressions that will be individually * watched using {@link ng.$rootScope.Scope#$watch $watch()} * * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any * expression in `watchExpressions` changes * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching * those of `watchExpression` * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching * those of `watchExpression` * The `scope` refers to the current scope. * @returns {function()} Returns a de-registration function for all listeners. */ $watchGroup: function(watchExpressions, listener) { var oldValues = new Array(watchExpressions.length); var newValues = new Array(watchExpressions.length); var deregisterFns = []; var self = this; var changeReactionScheduled = false; var firstRun = true; if (!watchExpressions.length) { // No expressions means we call the listener ASAP var shouldCall = true; self.$evalAsync(function() { if (shouldCall) listener(newValues, newValues, self); }); return function deregisterWatchGroup() { shouldCall = false; }; } if (watchExpressions.length === 1) { // Special case size of one return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { newValues[0] = value; oldValues[0] = oldValue; listener(newValues, (value === oldValue) ? newValues : oldValues, scope); }); } forEach(watchExpressions, function(expr, i) { var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { newValues[i] = value; oldValues[i] = oldValue; if (!changeReactionScheduled) { changeReactionScheduled = true; self.$evalAsync(watchGroupAction); } }); deregisterFns.push(unwatchFn); }); function watchGroupAction() { changeReactionScheduled = false; if (firstRun) { firstRun = false; listener(newValues, newValues, self); } else { listener(newValues, oldValues, self); } } return function deregisterWatchGroup() { while (deregisterFns.length) { deregisterFns.shift()(); } }; }, /** * @ngdoc method * @name $rootScope.Scope#$watchCollection * @kind function * * @description * Shallow watches the properties of an object and fires whenever any of the properties change * (for arrays, this implies watching the array items; for object maps, this implies watching * the properties). If a change is detected, the `listener` callback is fired. * * - The `obj` collection is observed via standard $watch operation and is examined on every * call to $digest() to see if any items have been added, removed, or moved. * - The `listener` is called whenever anything within the `obj` has changed. Examples include * adding, removing, and moving items belonging to an object or array. * * * # Example * ```js $scope.names = ['igor', 'matias', 'misko', 'james']; $scope.dataCount = 4; $scope.$watchCollection('names', function(newNames, oldNames) { $scope.dataCount = newNames.length; }); expect($scope.dataCount).toEqual(4); $scope.$digest(); //still at 4 ... no changes expect($scope.dataCount).toEqual(4); $scope.names.pop(); $scope.$digest(); //now there's been a change expect($scope.dataCount).toEqual(3); * ``` * * * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The * expression value should evaluate to an object or an array which is observed on each * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the * collection will trigger a call to the `listener`. * * @param {function(newCollection, oldCollection, scope)} listener a callback function called * when a change is detected. * - The `newCollection` object is the newly modified data obtained from the `obj` expression * - The `oldCollection` object is a copy of the former collection data. * Due to performance considerations, the`oldCollection` value is computed only if the * `listener` function declares two or more arguments. * - The `scope` argument refers to the current scope. * * @returns {function()} Returns a de-registration function for this listener. When the * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { $watchCollectionInterceptor.$stateful = true; var self = this; // the current value, updated on each dirty-check run var newValue; // a shallow copy of the newValue from the last dirty-check run, // updated to match newValue during dirty-check run var oldValue; // a shallow copy of the newValue from when the last change happened var veryOldValue; // only track veryOldValue if the listener is asking for it var trackVeryOldValue = (listener.length > 1); var changeDetected = 0; var changeDetector = $parse(obj, $watchCollectionInterceptor); var internalArray = []; var internalObject = {}; var initRun = true; var oldLength = 0; function $watchCollectionInterceptor(_value) { newValue = _value; var newLength, key, bothNaN, newItem, oldItem; // If the new value is undefined, then return undefined as the watch may be a one-time watch if (isUndefined(newValue)) return; if (!isObject(newValue)) { // if primitive if (oldValue !== newValue) { oldValue = newValue; changeDetected++; } } else if (isArrayLike(newValue)) { if (oldValue !== internalArray) { // we are transitioning from something which was not an array into array. oldValue = internalArray; oldLength = oldValue.length = 0; changeDetected++; } newLength = newValue.length; if (oldLength !== newLength) { // if lengths do not match we need to trigger change notification changeDetected++; oldValue.length = oldLength = newLength; } // copy the items to oldValue and look for changes. for (var i = 0; i < newLength; i++) { oldItem = oldValue[i]; newItem = newValue[i]; // eslint-disable-next-line no-self-compare bothNaN = (oldItem !== oldItem) && (newItem !== newItem); if (!bothNaN && (oldItem !== newItem)) { changeDetected++; oldValue[i] = newItem; } } } else { if (oldValue !== internalObject) { // we are transitioning from something which was not an object into object. oldValue = internalObject = {}; oldLength = 0; changeDetected++; } // copy the items to oldValue and look for changes. newLength = 0; for (key in newValue) { if (hasOwnProperty.call(newValue, key)) { newLength++; newItem = newValue[key]; oldItem = oldValue[key]; if (key in oldValue) { // eslint-disable-next-line no-self-compare bothNaN = (oldItem !== oldItem) && (newItem !== newItem); if (!bothNaN && (oldItem !== newItem)) { changeDetected++; oldValue[key] = newItem; } } else { oldLength++; oldValue[key] = newItem; changeDetected++; } } } if (oldLength > newLength) { // we used to have more keys, need to find them and destroy them. changeDetected++; for (key in oldValue) { if (!hasOwnProperty.call(newValue, key)) { oldLength--; delete oldValue[key]; } } } } return changeDetected; } function $watchCollectionAction() { if (initRun) { initRun = false; listener(newValue, newValue, self); } else { listener(newValue, veryOldValue, self); } // make a copy for the next time a collection is changed if (trackVeryOldValue) { if (!isObject(newValue)) { //primitive veryOldValue = newValue; } else if (isArrayLike(newValue)) { veryOldValue = new Array(newValue.length); for (var i = 0; i < newValue.length; i++) { veryOldValue[i] = newValue[i]; } } else { // if object veryOldValue = {}; for (var key in newValue) { if (hasOwnProperty.call(newValue, key)) { veryOldValue[key] = newValue[key]; } } } } } return this.$watch(changeDetector, $watchCollectionAction); }, /** * @ngdoc method * @name $rootScope.Scope#$digest * @kind function * * @description * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} * until no more listeners are firing. This means that it is possible to get into an infinite * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of * iterations exceeds 10. * * Usually, you don't call `$digest()` directly in * {@link ng.directive:ngController controllers} or in * {@link ng.$compileProvider#directive directives}. * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. * * If you want to be notified whenever `$digest()` is called, * you can register a `watchExpression` function with * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. * * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. * * # Example * ```js var scope = ...; scope.name = 'misko'; scope.counter = 0; expect(scope.counter).toEqual(0); scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; }); expect(scope.counter).toEqual(0); scope.$digest(); // the listener is always called during the first $digest loop after it was registered expect(scope.counter).toEqual(1); scope.$digest(); // but now it will not be called unless the value changes expect(scope.counter).toEqual(1); scope.name = 'adam'; scope.$digest(); expect(scope.counter).toEqual(2); * ``` * */ $digest: function() { var watch, value, last, fn, get, watchers, dirty, ttl = TTL, next, current, target = this, watchLog = [], logIdx, asyncTask; beginPhase('$digest'); // Check for changes to browser url that happened in sync before the call to $digest $browser.$$checkUrlChange(); if (this === $rootScope && applyAsyncId !== null) { // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then // cancel the scheduled $apply and flush the queue of expressions to be evaluated. $browser.defer.cancel(applyAsyncId); flushApplyAsync(); } lastDirtyWatch = null; do { // "while dirty" loop dirty = false; current = target; // It's safe for asyncQueuePosition to be a local variable here because this loop can't // be reentered recursively. Calling $digest from a function passed to $applyAsync would // lead to a '$digest already in progress' error. for (var asyncQueuePosition = 0; asyncQueuePosition < asyncQueue.length; asyncQueuePosition++) { try { asyncTask = asyncQueue[asyncQueuePosition]; asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); } catch (e) { $exceptionHandler(e); } lastDirtyWatch = null; } asyncQueue.length = 0; traverseScopesLoop: do { // "traverse the scopes" loop if ((watchers = current.$$watchers)) { // process our watches watchers.$$digestWatchIndex = watchers.length; while (watchers.$$digestWatchIndex--) { try { watch = watchers[watchers.$$digestWatchIndex]; // Most common watches are on primitives, in which case we can short // circuit it with === operator, only when === fails do we use .equals if (watch) { get = watch.get; if ((value = get(current)) !== (last = watch.last) && !(watch.eq ? equals(value, last) : (isNumberNaN(value) && isNumberNaN(last)))) { dirty = true; lastDirtyWatch = watch; watch.last = watch.eq ? copy(value, null) : value; fn = watch.fn; fn(value, ((last === initWatchVal) ? value : last), current); if (ttl < 5) { logIdx = 4 - ttl; if (!watchLog[logIdx]) watchLog[logIdx] = []; watchLog[logIdx].push({ msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, newVal: value, oldVal: last }); } } else if (watch === lastDirtyWatch) { // If the most recently dirty watcher is now clean, short circuit since the remaining watchers // have already been tested. dirty = false; break traverseScopesLoop; } } } catch (e) { $exceptionHandler(e); } } } // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $broadcast if (!(next = ((current.$$watchersCount && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } while ((current = next)); // `break traverseScopesLoop;` takes us to here if ((dirty || asyncQueue.length) && !(ttl--)) { clearPhase(); throw $rootScopeMinErr('infdig', '{0} $digest() iterations reached. Aborting!\n' + 'Watchers fired in the last 5 iterations: {1}', TTL, watchLog); } } while (dirty || asyncQueue.length); clearPhase(); // postDigestQueuePosition isn't local here because this loop can be reentered recursively. while (postDigestQueuePosition < postDigestQueue.length) { try { postDigestQueue[postDigestQueuePosition++](); } catch (e) { $exceptionHandler(e); } } postDigestQueue.length = postDigestQueuePosition = 0; }, /** * @ngdoc event * @name $rootScope.Scope#$destroy * @eventType broadcast on scope being destroyed * * @description * Broadcasted when a scope and its children are being destroyed. * * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to * clean up DOM bindings before an element is removed from the DOM. */ /** * @ngdoc method * @name $rootScope.Scope#$destroy * @kind function * * @description * Removes the current scope (and all of its children) from the parent scope. Removal implies * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer * propagate to the current scope and its children. Removal also implies that the current * scope is eligible for garbage collection. * * The `$destroy()` is usually used by directives such as * {@link ng.directive:ngRepeat ngRepeat} for managing the * unrolling of the loop. * * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. * Application code can register a `$destroy` event handler that will give it a chance to * perform any necessary cleanup. * * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to * clean up DOM bindings before an element is removed from the DOM. */ $destroy: function() { // We can't destroy a scope that has been already destroyed. if (this.$$destroyed) return; var parent = this.$parent; this.$broadcast('$destroy'); this.$$destroyed = true; if (this === $rootScope) { //Remove handlers attached to window when $rootScope is removed $browser.$$applicationDestroyed(); } incrementWatchersCount(this, -this.$$watchersCount); for (var eventName in this.$$listenerCount) { decrementListenerCount(this, this.$$listenerCount[eventName], eventName); } // sever all the references to parent scopes (after this cleanup, the current scope should // not be retained by any of our references and should be eligible for garbage collection) if (parent && parent.$$childHead === this) parent.$$childHead = this.$$nextSibling; if (parent && parent.$$childTail === this) parent.$$childTail = this.$$prevSibling; if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; // Disable listeners, watchers and apply/digest methods this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; this.$on = this.$watch = this.$watchGroup = function() { return noop; }; this.$$listeners = {}; // Disconnect the next sibling to prevent `cleanUpScope` destroying those too this.$$nextSibling = null; cleanUpScope(this); }, /** * @ngdoc method * @name $rootScope.Scope#$eval * @kind function * * @description * Executes the `expression` on the current scope and returns the result. Any exceptions in * the expression are propagated (uncaught). This is useful when evaluating Angular * expressions. * * # Example * ```js var scope = ng.$rootScope.Scope(); scope.a = 1; scope.b = 2; expect(scope.$eval('a+b')).toEqual(3); expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); * ``` * * @param {(string|function())=} expression An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * * @param {(object)=} locals Local variables object, useful for overriding values in scope. * @returns {*} The result of evaluating the expression. */ $eval: function(expr, locals) { return $parse(expr)(this, locals); }, /** * @ngdoc method * @name $rootScope.Scope#$evalAsync * @kind function * * @description * Executes the expression on the current scope at a later point in time. * * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only * that: * * - it will execute after the function that scheduled the evaluation (preferably before DOM * rendering). * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after * `expression` execution. * * Any exceptions from the execution of the expression are forwarded to the * {@link ng.$exceptionHandler $exceptionHandler} service. * * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle * will be scheduled. However, it is encouraged to always call code that changes the model * from within an `$apply` call. That includes code evaluated via `$evalAsync`. * * @param {(string|function())=} expression An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with the current `scope` parameter. * * @param {(object)=} locals Local variables object, useful for overriding values in scope. */ $evalAsync: function(expr, locals) { // if we are outside of an $digest loop and this is the first time we are scheduling async // task also schedule async auto-flush if (!$rootScope.$$phase && !asyncQueue.length) { $browser.defer(function() { if (asyncQueue.length) { $rootScope.$digest(); } }); } asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); }, $$postDigest: function(fn) { postDigestQueue.push(fn); }, /** * @ngdoc method * @name $rootScope.Scope#$apply * @kind function * * @description * `$apply()` is used to execute an expression in angular from outside of the angular * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). * Because we are calling into the angular framework we need to perform proper scope life * cycle of {@link ng.$exceptionHandler exception handling}, * {@link ng.$rootScope.Scope#$digest executing watches}. * * ## Life cycle * * # Pseudo-Code of `$apply()` * ```js function $apply(expr) { try { return $eval(expr); } catch (e) { $exceptionHandler(e); } finally { $root.$digest(); } } * ``` * * * Scope's `$apply()` method transitions through the following stages: * * 1. The {@link guide/expression expression} is executed using the * {@link ng.$rootScope.Scope#$eval $eval()} method. * 2. Any exceptions from the execution of the expression are forwarded to the * {@link ng.$exceptionHandler $exceptionHandler} service. * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. * * * @param {(string|function())=} exp An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. * * @returns {*} The result of evaluating the expression. */ $apply: function(expr) { try { beginPhase('$apply'); try { return this.$eval(expr); } finally { clearPhase(); } } catch (e) { $exceptionHandler(e); } finally { try { $rootScope.$digest(); } catch (e) { $exceptionHandler(e); // eslint-disable-next-line no-unsafe-finally throw e; } } }, /** * @ngdoc method * @name $rootScope.Scope#$applyAsync * @kind function * * @description * Schedule the invocation of $apply to occur at a later time. The actual time difference * varies across browsers, but is typically around ~10 milliseconds. * * This can be used to queue up multiple expressions which need to be evaluated in the same * digest. * * @param {(string|function())=} exp An angular expression to be executed. * * - `string`: execute using the rules as defined in {@link guide/expression expression}. * - `function(scope)`: execute the function with current `scope` parameter. */ $applyAsync: function(expr) { var scope = this; if (expr) { applyAsyncQueue.push($applyAsyncExpression); } expr = $parse(expr); scheduleApplyAsync(); function $applyAsyncExpression() { scope.$eval(expr); } }, /** * @ngdoc method * @name $rootScope.Scope#$on * @kind function * * @description * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for * discussion of event life cycle. * * The event listener function format is: `function(event, args...)`. The `event` object * passed into the listener has the following attributes: * * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or * `$broadcast`-ed. * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the * event propagates through the scope hierarchy, this property is set to null. * - `name` - `{string}`: name of the event. * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel * further event propagation (available only for events that were `$emit`-ed). * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag * to true. * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. * * @param {string} name Event name to listen on. * @param {function(event, ...args)} listener Function to call when the event is emitted. * @returns {function()} Returns a deregistration function for this listener. */ $on: function(name, listener) { var namedListeners = this.$$listeners[name]; if (!namedListeners) { this.$$listeners[name] = namedListeners = []; } namedListeners.push(listener); var current = this; do { if (!current.$$listenerCount[name]) { current.$$listenerCount[name] = 0; } current.$$listenerCount[name]++; } while ((current = current.$parent)); var self = this; return function() { var indexOfListener = namedListeners.indexOf(listener); if (indexOfListener !== -1) { namedListeners[indexOfListener] = null; decrementListenerCount(self, 1, name); } }; }, /** * @ngdoc method * @name $rootScope.Scope#$emit * @kind function * * @description * Dispatches an event `name` upwards through the scope hierarchy notifying the * registered {@link ng.$rootScope.Scope#$on} listeners. * * The event life cycle starts at the scope on which `$emit` was called. All * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get * notified. Afterwards, the event traverses upwards toward the root scope and calls all * registered listeners along the way. The event will stop propagating if one of the listeners * cancels it. * * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to emit. * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). */ $emit: function(name, args) { var empty = [], namedListeners, scope = this, stopPropagation = false, event = { name: name, targetScope: scope, stopPropagation: function() {stopPropagation = true;}, preventDefault: function() { event.defaultPrevented = true; }, defaultPrevented: false }, listenerArgs = concat([event], arguments, 1), i, length; do { namedListeners = scope.$$listeners[name] || empty; event.currentScope = scope; for (i = 0, length = namedListeners.length; i < length; i++) { // if listeners were deregistered, defragment the array if (!namedListeners[i]) { namedListeners.splice(i, 1); i--; length--; continue; } try { //allow all listeners attached to the current scope to run namedListeners[i].apply(null, listenerArgs); } catch (e) { $exceptionHandler(e); } } //if any listener on the current scope stops propagation, prevent bubbling if (stopPropagation) { event.currentScope = null; return event; } //traverse upwards scope = scope.$parent; } while (scope); event.currentScope = null; return event; }, /** * @ngdoc method * @name $rootScope.Scope#$broadcast * @kind function * * @description * Dispatches an event `name` downwards to all child scopes (and their children) notifying the * registered {@link ng.$rootScope.Scope#$on} listeners. * * The event life cycle starts at the scope on which `$broadcast` was called. All * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get * notified. Afterwards, the event propagates to all direct and indirect scopes of the current * scope and calls all registered listeners along the way. The event cannot be canceled. * * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed * onto the {@link ng.$exceptionHandler $exceptionHandler} service. * * @param {string} name Event name to broadcast. * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} */ $broadcast: function(name, args) { var target = this, current = target, next = target, event = { name: name, targetScope: target, preventDefault: function() { event.defaultPrevented = true; }, defaultPrevented: false }; if (!target.$$listenerCount[name]) return event; var listenerArgs = concat([event], arguments, 1), listeners, i, length; //down while you can, then up and next sibling or up and next sibling until back at root while ((current = next)) { event.currentScope = current; listeners = current.$$listeners[name] || []; for (i = 0, length = listeners.length; i < length; i++) { // if listeners were deregistered, defragment the array if (!listeners[i]) { listeners.splice(i, 1); i--; length--; continue; } try { listeners[i].apply(null, listenerArgs); } catch (e) { $exceptionHandler(e); } } // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! // this piece should be kept in sync with the traversal in $digest // (though it differs due to having the extra check for $$listenerCount) if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; } } } event.currentScope = null; return event; } }; var $rootScope = new Scope(); //The internal queues. Expose them on the $rootScope for debugging/testing purposes. var asyncQueue = $rootScope.$$asyncQueue = []; var postDigestQueue = $rootScope.$$postDigestQueue = []; var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; var postDigestQueuePosition = 0; return $rootScope; function beginPhase(phase) { if ($rootScope.$$phase) { throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); } $rootScope.$$phase = phase; } function clearPhase() { $rootScope.$$phase = null; } function incrementWatchersCount(current, count) { do { current.$$watchersCount += count; } while ((current = current.$parent)); } function decrementListenerCount(current, count, name) { do { current.$$listenerCount[name] -= count; if (current.$$listenerCount[name] === 0) { delete current.$$listenerCount[name]; } } while ((current = current.$parent)); } /** * function used as an initial value for watchers. * because it's unique we can easily tell it apart from other values */ function initWatchVal() {} function flushApplyAsync() { while (applyAsyncQueue.length) { try { applyAsyncQueue.shift()(); } catch (e) { $exceptionHandler(e); } } applyAsyncId = null; } function scheduleApplyAsync() { if (applyAsyncId === null) { applyAsyncId = $browser.defer(function() { $rootScope.$apply(flushApplyAsync); }); } } }]; } /** * @ngdoc service * @name $rootElement * * @description * The root element of Angular application. This is either the element where {@link * ng.directive:ngApp ngApp} was declared or the element passed into * {@link angular.bootstrap}. The element represents the root element of application. It is also the * location where the application's {@link auto.$injector $injector} service gets * published, and can be retrieved using `$rootElement.injector()`. */ // the implementation is in angular.bootstrap /** * @this * @description * Private service to sanitize uris for links and images. Used by $compile and $sanitize. */ function $$SanitizeUriProvider() { var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; /** * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during a[href] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to a[href] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.aHrefSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { aHrefSanitizationWhitelist = regexp; return this; } return aHrefSanitizationWhitelist; }; /** * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe * urls during img[src] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * * Any url about to be assigned to img[src] via data-binding is first normalized and turned into * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` * regular expression. If a match is found, the original url is written into the dom. Otherwise, * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ this.imgSrcSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { imgSrcSanitizationWhitelist = regexp; return this; } return imgSrcSanitizationWhitelist; }; this.$get = function() { return function sanitizeUri(uri, isImage) { var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; var normalizedVal; normalizedVal = urlResolve(uri).href; if (normalizedVal !== '' && !normalizedVal.match(regex)) { return 'unsafe:' + normalizedVal; } return uri; }; }; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* exported $SceProvider, $SceDelegateProvider */ var $sceMinErr = minErr('$sce'); var SCE_CONTEXTS = { HTML: 'html', CSS: 'css', URL: 'url', // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a // url. (e.g. ng-include, script src, templateUrl) RESOURCE_URL: 'resourceUrl', JS: 'js' }; // Helper functions follow. function adjustMatcher(matcher) { if (matcher === 'self') { return matcher; } else if (isString(matcher)) { // Strings match exactly except for 2 wildcards - '*' and '**'. // '*' matches any character except those from the set ':/.?&'. // '**' matches any character (like .* in a RegExp). // More than 2 *'s raises an error as it's ill defined. if (matcher.indexOf('***') > -1) { throw $sceMinErr('iwcard', 'Illegal sequence *** in string matcher. String: {0}', matcher); } matcher = escapeForRegexp(matcher). replace(/\\\*\\\*/g, '.*'). replace(/\\\*/g, '[^:/.?&;]*'); return new RegExp('^' + matcher + '$'); } else if (isRegExp(matcher)) { // The only other type of matcher allowed is a Regexp. // Match entire URL / disallow partial matches. // Flags are reset (i.e. no global, ignoreCase or multiline) return new RegExp('^' + matcher.source + '$'); } else { throw $sceMinErr('imatcher', 'Matchers may only be "self", string patterns or RegExp objects'); } } function adjustMatchers(matchers) { var adjustedMatchers = []; if (isDefined(matchers)) { forEach(matchers, function(matcher) { adjustedMatchers.push(adjustMatcher(matcher)); }); } return adjustedMatchers; } /** * @ngdoc service * @name $sceDelegate * @kind function * * @description * * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict * Contextual Escaping (SCE)} services to AngularJS. * * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things * work because `$sce` delegates to `$sceDelegate` for these operations. * * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. * * The default instance of `$sceDelegate` should work out of the box with little pain. While you * can override it completely to change the behavior of `$sce`, the common case would * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist * $sceDelegateProvider.resourceUrlWhitelist} and {@link * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} */ /** * @ngdoc provider * @name $sceDelegateProvider * @this * * @description * * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure * that the URLs used for sourcing Angular templates are safe. Refer {@link * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} * * For the general details about this service in Angular, read the main page for {@link ng.$sce * Strict Contextual Escaping (SCE)}. * * **Example**: Consider the following case.
          * * - your app is hosted at url `http://myapp.example.com/` * - but some of your templates are hosted on other domains you control such as * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. * * Here is what a secure configuration for this scenario might look like: * * ``` * angular.module('myApp', []).config(function($sceDelegateProvider) { * $sceDelegateProvider.resourceUrlWhitelist([ * // Allow same origin resource loads. * 'self', * // Allow loading from our assets domain. Notice the difference between * and **. * 'http://srv*.assets.example.com/**' * ]); * * // The blacklist overrides the whitelist so the open redirect here is blocked. * $sceDelegateProvider.resourceUrlBlacklist([ * 'http://myapp.example.com/clickThru**' * ]); * }); * ``` */ function $SceDelegateProvider() { this.SCE_CONTEXTS = SCE_CONTEXTS; // Resource URLs can also be trusted by policy. var resourceUrlWhitelist = ['self'], resourceUrlBlacklist = []; /** * @ngdoc method * @name $sceDelegateProvider#resourceUrlWhitelist * @kind function * * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value * provided. This must be an array or null. A snapshot of this array is used so further * changes to the array are ignored. * * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items * allowed in this array. * *
          * **Note:** an empty whitelist array will block all URLs! *
          * * @return {Array} the currently set whitelist array. * * The **default value** when no whitelist has been explicitly set is `['self']` allowing only * same origin resource requests. * * @description * Sets/Gets the whitelist of trusted resource URLs. */ this.resourceUrlWhitelist = function(value) { if (arguments.length) { resourceUrlWhitelist = adjustMatchers(value); } return resourceUrlWhitelist; }; /** * @ngdoc method * @name $sceDelegateProvider#resourceUrlBlacklist * @kind function * * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value * provided. This must be an array or null. A snapshot of this array is used so further * changes to the array are ignored. * * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items * allowed in this array. * * The typical usage for the blacklist is to **block * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as * these would otherwise be trusted but actually return content from the redirected domain. * * Finally, **the blacklist overrides the whitelist** and has the final say. * * @return {Array} the currently set blacklist array. * * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there * is no blacklist.) * * @description * Sets/Gets the blacklist of trusted resource URLs. */ this.resourceUrlBlacklist = function(value) { if (arguments.length) { resourceUrlBlacklist = adjustMatchers(value); } return resourceUrlBlacklist; }; this.$get = ['$injector', function($injector) { var htmlSanitizer = function htmlSanitizer(html) { throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); }; if ($injector.has('$sanitize')) { htmlSanitizer = $injector.get('$sanitize'); } function matchUrl(matcher, parsedUrl) { if (matcher === 'self') { return urlIsSameOrigin(parsedUrl); } else { // definitely a regex. See adjustMatchers() return !!matcher.exec(parsedUrl.href); } } function isResourceUrlAllowedByPolicy(url) { var parsedUrl = urlResolve(url.toString()); var i, n, allowed = false; // Ensure that at least one item from the whitelist allows this url. for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { allowed = true; break; } } if (allowed) { // Ensure that no item from the blacklist blocked this url. for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { allowed = false; break; } } } return allowed; } function generateHolderType(Base) { var holderType = function TrustedValueHolderType(trustedValue) { this.$$unwrapTrustedValue = function() { return trustedValue; }; }; if (Base) { holderType.prototype = new Base(); } holderType.prototype.valueOf = function sceValueOf() { return this.$$unwrapTrustedValue(); }; holderType.prototype.toString = function sceToString() { return this.$$unwrapTrustedValue().toString(); }; return holderType; } var trustedValueHolderBase = generateHolderType(), byType = {}; byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); /** * @ngdoc method * @name $sceDelegate#trustAs * * @description * Returns an object that is trusted by angular for use in specified strict * contextual escaping contexts (such as ng-bind-html, ng-include, any src * attribute interpolation, any dom event binding attribute interpolation * such as for onclick, etc.) that uses the provided value. * See {@link ng.$sce $sce} for enabling strict contextual escaping. * * @param {string} type The kind of context in which this value is safe for use. e.g. url, * resourceUrl, html, js and css. * @param {*} value The value that that should be considered trusted/safe. * @returns {*} A value that can be used to stand in for the provided `value` in places * where Angular expects a $sce.trustAs() return value. */ function trustAs(type, trustedValue) { var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); if (!Constructor) { throw $sceMinErr('icontext', 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', type, trustedValue); } if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { return trustedValue; } // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting // mutable objects, we ensure here that the value passed in is actually a string. if (typeof trustedValue !== 'string') { throw $sceMinErr('itype', 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', type); } return new Constructor(trustedValue); } /** * @ngdoc method * @name $sceDelegate#valueOf * * @description * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. * * If the passed parameter is not a value that had been returned by {@link * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. * * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} * call or anything else. * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns * `value` unchanged. */ function valueOf(maybeTrusted) { if (maybeTrusted instanceof trustedValueHolderBase) { return maybeTrusted.$$unwrapTrustedValue(); } else { return maybeTrusted; } } /** * @ngdoc method * @name $sceDelegate#getTrusted * * @description * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and * returns the originally supplied value if the queried context type is a supertype of the * created type. If this condition isn't satisfied, throws an exception. * *
          * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting * (XSS) vulnerability in your application. *
          * * @param {string} type The kind of context in which this value is to be used. * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} call. * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. */ function getTrusted(type, maybeTrusted) { if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { return maybeTrusted; } var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); if (constructor && maybeTrusted instanceof constructor) { return maybeTrusted.$$unwrapTrustedValue(); } // If we get here, then we may only take one of two actions. // 1. sanitize the value for the requested type, or // 2. throw an exception. if (type === SCE_CONTEXTS.RESOURCE_URL) { if (isResourceUrlAllowedByPolicy(maybeTrusted)) { return maybeTrusted; } else { throw $sceMinErr('insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', maybeTrusted.toString()); } } else if (type === SCE_CONTEXTS.HTML) { return htmlSanitizer(maybeTrusted); } throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); } return { trustAs: trustAs, getTrusted: getTrusted, valueOf: valueOf }; }]; } /** * @ngdoc provider * @name $sceProvider * @this * * @description * * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. * - enable/disable Strict Contextual Escaping (SCE) in a module * - override the default implementation with a custom delegate * * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. */ /** * @ngdoc service * @name $sce * @kind function * * @description * * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. * * # Strict Contextual Escaping * * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain * contexts to result in a value that is marked as safe to use for that context. One example of * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer * to these contexts as privileged or SCE contexts. * * As of version 1.2, Angular ships with SCE enabled by default. * * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow * one to execute arbitrary javascript by the use of the expression() syntax. Refer * to learn more about them. * You can ensure your document is in standards mode and not quirks mode by adding `` * to the top of your HTML document. * * SCE assists in writing code in a way that (a) is secure by default and (b) makes auditing for * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. * * Here's an example of a binding in a privileged context: * * ``` * *
          * ``` * * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE * disabled, this application allows the user to render arbitrary HTML into the DIV. * In a more realistic example, one may be rendering user comments, blog articles, etc. via * bindings. (HTML is just one example of a context where rendering user controlled input creates * security vulnerabilities.) * * For the case of HTML, you might use a library, either on the client side, or on the server side, * to sanitize unsafe HTML before binding to the value and rendering it in the document. * * How would you ensure that every place that used these types of bindings was bound to a value that * was sanitized by your library (or returned as safe for rendering by your server?) How can you * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some * properties/fields and forgot to update the binding to the sanitized value? * * To be secure by default, you want to ensure that any such bindings are disallowed unless you can * determine that something explicitly says it's safe to use a value for binding in that * context. You can then audit your code (a simple grep would do) to ensure that this is only done * for those values that you can easily tell are safe - because they were received from your server, * sanitized by your library, etc. You can organize your codebase to help with this - perhaps * allowing only the files in a specific directory to do this. Ensuring that the internal API * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. * * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to * obtain values that will be accepted by SCE / privileged contexts. * * * ## How does it work? * * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. * * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly * simplified): * * ``` * var ngBindHtmlDirective = ['$sce', function($sce) { * return function(scope, element, attr) { * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { * element.html(value || ''); * }); * }; * }]; * ``` * * ## Impact on loading templates * * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as * `templateUrl`'s specified by {@link guide/directive directives}. * * By default, Angular only loads templates from the same domain and protocol as the application * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. * * *Please note*: * The browser's * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) * policy apply in addition to this and may further restrict whether the template is successfully * loaded. This means that without the right CORS policy, loading templates from a different domain * won't work on all browsers. Also, loading templates from `file://` URL does not work on some * browsers. * * ## This feels like too much overhead * * It's important to remember that SCE only applies to interpolation expressions. * * If your expressions are constant literals, they're automatically trusted and you don't need to * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. * `
          `) just works. * * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. * * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load * templates in `ng-include` from your application's domain without having to even know about SCE. * It blocks loading templates from other domains or loading templates over http from an https * served document. You can change these by setting your own custom {@link * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. * * This significantly reduces the overhead. It is far easier to pay the small overhead and have an * application that's secure and can be audited to verify that with much more ease than bolting * security onto an application later. * * * ## What trusted context types are supported? * * | Context | Notes | * |---------------------|----------------| * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
          Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | * * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
          * * Each element in these arrays must be one of the following: * * - **'self'** * - The special **string**, `'self'`, can be used to match against all URLs of the **same * domain** as the application document using the **same protocol**. * - **String** (except the special value `'self'`) * - The string is matched against the full *normalized / absolute URL* of the resource * being tested (substring matches are not good enough.) * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters * match themselves. * - `*`: matches zero or more occurrences of any character other than one of the following 6 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use * in a whitelist. * - `**`: matches zero or more occurrences of *any* character. As such, it's not * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might * not have been the intention.) Its usage at the very end of the path is ok. (e.g. * http://foo.example.com/templates/**). * - **RegExp** (*see caveat below*) * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to * accidentally introduce a bug when one updates a complex expression (imho, all regexes should * have good test coverage). For instance, the use of `.` in the regex is correct only in a * small number of cases. A `.` character in the regex used when matching the scheme or a * subdomain could be matched against a `:` or literal `.` that was likely not intended. It * is highly recommended to use the string patterns and only fall back to regular expressions * as a last resort. * - The regular expression must be an instance of RegExp (i.e. not a string.) It is * matched against the **entire** *normalized / absolute URL* of the resource being tested * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags * present on the RegExp (such as multiline, global, ignoreCase) are ignored. * - If you are generating your JavaScript from some other templating engine (not * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), * remember to escape your regular expression (and be aware that you might need more than * one level of escaping depending on your templating engine and the way you interpolated * the value.) Do make use of your platform's escaping mechanism as it might be good * enough before coding your own. E.g. Ruby has * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). * Javascript lacks a similar built in function for escaping. Take a look at Google * Closure library's [goog.string.regExpEscape(s)]( * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). * * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. * * ## Show me an example using SCE. * * * *
          *

          * User comments
          * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when * $sanitize is available. If $sanitize isn't available, this results in an error instead of an * exploit. *
          *
          * {{userComment.name}}: * *
          *
          *
          *
          *
          * * * angular.module('mySceApp', ['ngSanitize']) * .controller('AppController', ['$http', '$templateCache', '$sce', * function AppController($http, $templateCache, $sce) { * var self = this; * $http.get('test_data.json', {cache: $templateCache}).success(function(userComments) { * self.userComments = userComments; * }); * self.explicitlyTrustedHtml = $sce.trustAsHtml( * 'Hover over this text.'); * }]); * * * * [ * { "name": "Alice", * "htmlComment": * "Is anyone reading this?" * }, * { "name": "Bob", * "htmlComment": "Yes! Am I the only other one?" * } * ] * * * * describe('SCE doc demo', function() { * it('should sanitize untrusted values', function() { * expect(element.all(by.css('.htmlComment')).first().getAttribute('innerHTML')) * .toBe('Is anyone reading this?'); * }); * * it('should NOT sanitize explicitly trusted values', function() { * expect(element(by.id('explicitlyTrustedHtml')).getAttribute('innerHTML')).toBe( * 'Hover over this text.'); * }); * }); * *
          * * * * ## Can I disable SCE completely? * * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits * for little coding overhead. It will be much harder to take an SCE disabled application and * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE * for cases where you have a lot of existing code that was written before SCE was introduced and * you're migrating them a module at a time. * * That said, here's how you can completely disable SCE: * * ``` * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { * // Completely disable SCE. For demonstration purposes only! * // Do not use in new projects. * $sceProvider.enabled(false); * }); * ``` * */ function $SceProvider() { var enabled = true; /** * @ngdoc method * @name $sceProvider#enabled * @kind function * * @param {boolean=} value If provided, then enables/disables SCE. * @return {boolean} true if SCE is enabled, false otherwise. * * @description * Enables/disables SCE and returns the current value. */ this.enabled = function(value) { if (arguments.length) { enabled = !!value; } return enabled; }; /* Design notes on the default implementation for SCE. * * The API contract for the SCE delegate * ------------------------------------- * The SCE delegate object must provide the following 3 methods: * * - trustAs(contextEnum, value) * This method is used to tell the SCE service that the provided value is OK to use in the * contexts specified by contextEnum. It must return an object that will be accepted by * getTrusted() for a compatible contextEnum and return this value. * * - valueOf(value) * For values that were not produced by trustAs(), return them as is. For values that were * produced by trustAs(), return the corresponding input value to trustAs. Basically, if * trustAs is wrapping the given values into some type, this operation unwraps it when given * such a value. * * - getTrusted(contextEnum, value) * This function should return the a value that is safe to use in the context specified by * contextEnum or throw and exception otherwise. * * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be * opaque or wrapped in some holder object. That happens to be an implementation detail. For * instance, an implementation could maintain a registry of all trusted objects by context. In * such a case, trustAs() would return the same object that was passed in. getTrusted() would * return the same object passed in if it was found in the registry under a compatible context or * throw an exception otherwise. An implementation might only wrap values some of the time based * on some criteria. getTrusted() might return a value and not throw an exception for special * constants or objects even if not wrapped. All such implementations fulfill this contract. * * * A note on the inheritance model for SCE contexts * ------------------------------------------------ * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This * is purely an implementation details. * * The contract is simply this: * * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) * will also succeed. * * Inheritance happens to capture this in a natural way. In some future, we * may not use inheritance anymore. That is OK because no code outside of * sce.js and sceSpecs.js would need to be aware of this detail. */ this.$get = ['$parse', '$sceDelegate', function( $parse, $sceDelegate) { // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow // the "expression(javascript expression)" syntax which is insecure. if (enabled && msie < 8) { throw $sceMinErr('iequirks', 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 'mode. You can fix this by adding the text to the top of your HTML ' + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); } var sce = shallowCopy(SCE_CONTEXTS); /** * @ngdoc method * @name $sce#isEnabled * @kind function * * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. * * @description * Returns a boolean indicating if SCE is enabled. */ sce.isEnabled = function() { return enabled; }; sce.trustAs = $sceDelegate.trustAs; sce.getTrusted = $sceDelegate.getTrusted; sce.valueOf = $sceDelegate.valueOf; if (!enabled) { sce.trustAs = sce.getTrusted = function(type, value) { return value; }; sce.valueOf = identity; } /** * @ngdoc method * @name $sce#parseAs * * @description * Converts Angular {@link guide/expression expression} into a function. This is like {@link * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, * *result*)} * * @param {string} type The kind of SCE context in which this result will be used. * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ sce.parseAs = function sceParseAs(type, expr) { var parsed = $parse(expr); if (parsed.literal && parsed.constant) { return parsed; } else { return $parse(expr, function(value) { return sce.getTrusted(type, value); }); } }; /** * @ngdoc method * @name $sce#trustAs * * @description * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, * returns an object that is trusted by angular for use in specified strict contextual * escaping contexts (such as ng-bind-html, ng-include, any src attribute * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual * escaping. * * @param {string} type The kind of context in which this value is safe for use. e.g. url, * resourceUrl, html, js and css. * @param {*} value The value that that should be considered trusted/safe. * @returns {*} A value that can be used to stand in for the provided `value` in places * where Angular expects a $sce.trustAs() return value. */ /** * @ngdoc method * @name $sce#trustAsHtml * * @description * Shorthand method. `$sce.trustAsHtml(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the * return value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#trustAsUrl * * @description * Shorthand method. `$sce.trustAsUrl(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the * return value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#trustAsResourceUrl * * @description * Shorthand method. `$sce.trustAsResourceUrl(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the return * value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#trustAsJs * * @description * Shorthand method. `$sce.trustAsJs(value)` → * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} * * @param {*} value The value to trustAs. * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives * only accept expressions that are either literal constants or are the * return value of {@link ng.$sce#trustAs $sce.trustAs}.) */ /** * @ngdoc method * @name $sce#getTrusted * * @description * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the * originally supplied value if the queried context type is a supertype of the created type. * If this condition isn't satisfied, throws an exception. * * @param {string} type The kind of context in which this value is to be used. * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} * call. * @returns {*} The value the was originally provided to * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. * Otherwise, throws an exception. */ /** * @ngdoc method * @name $sce#getTrustedHtml * * @description * Shorthand method. `$sce.getTrustedHtml(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` */ /** * @ngdoc method * @name $sce#getTrustedCss * * @description * Shorthand method. `$sce.getTrustedCss(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` */ /** * @ngdoc method * @name $sce#getTrustedUrl * * @description * Shorthand method. `$sce.getTrustedUrl(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` */ /** * @ngdoc method * @name $sce#getTrustedResourceUrl * * @description * Shorthand method. `$sce.getTrustedResourceUrl(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} * * @param {*} value The value to pass to `$sceDelegate.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` */ /** * @ngdoc method * @name $sce#getTrustedJs * * @description * Shorthand method. `$sce.getTrustedJs(value)` → * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} * * @param {*} value The value to pass to `$sce.getTrusted`. * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` */ /** * @ngdoc method * @name $sce#parseAsHtml * * @description * Shorthand method. `$sce.parseAsHtml(expression string)` → * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsCss * * @description * Shorthand method. `$sce.parseAsCss(value)` → * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsUrl * * @description * Shorthand method. `$sce.parseAsUrl(value)` → * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsResourceUrl * * @description * Shorthand method. `$sce.parseAsResourceUrl(value)` → * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ /** * @ngdoc method * @name $sce#parseAsJs * * @description * Shorthand method. `$sce.parseAsJs(value)` → * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} * * @param {string} expression String expression to compile. * @returns {function(context, locals)} a function which represents the compiled expression: * * * `context` – `{object}` – an object against which any expressions embedded in the strings * are evaluated against (typically a scope object). * * `locals` – `{object=}` – local variables context object, useful for overriding values in * `context`. */ // Shorthand delegations. var parse = sce.parseAs, getTrusted = sce.getTrusted, trustAs = sce.trustAs; forEach(SCE_CONTEXTS, function(enumValue, name) { var lName = lowercase(name); sce[camelCase('parse_as_' + lName)] = function(expr) { return parse(enumValue, expr); }; sce[camelCase('get_trusted_' + lName)] = function(value) { return getTrusted(enumValue, value); }; sce[camelCase('trust_as_' + lName)] = function(value) { return trustAs(enumValue, value); }; }); return sce; }]; } /* exported $SnifferProvider */ /** * !!! This is an undocumented "private" service !!! * * @name $sniffer * @requires $window * @requires $document * @this * * @property {boolean} history Does the browser support html5 history api ? * @property {boolean} transitions Does the browser support CSS transition events ? * @property {boolean} animations Does the browser support CSS animation events ? * * @description * This is very simple implementation of testing browser's features. */ function $SnifferProvider() { this.$get = ['$window', '$document', function($window, $document) { var eventSupport = {}, // Chrome Packaged Apps are not allowed to access `history.pushState`. // If not sandboxed, they can be detected by the presence of `chrome.app.runtime` // (see https://developer.chrome.com/apps/api_index). If sandboxed, they can be detected by // the presence of an extension runtime ID and the absence of other Chrome runtime APIs // (see https://developer.chrome.com/apps/manifest/sandbox). isChromePackagedApp = $window.chrome && ($window.chrome.app && $window.chrome.app.runtime || !$window.chrome.app && $window.chrome.runtime && $window.chrome.runtime.id), hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, android = toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), boxee = /Boxee/i.test(($window.navigator || {}).userAgent), document = $document[0] || {}, vendorPrefix, vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, bodyStyle = document.body && document.body.style, transitions = false, animations = false, match; if (bodyStyle) { for (var prop in bodyStyle) { if ((match = vendorRegex.exec(prop))) { vendorPrefix = match[0]; vendorPrefix = vendorPrefix[0].toUpperCase() + vendorPrefix.substr(1); break; } } if (!vendorPrefix) { vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; } transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); if (android && (!transitions || !animations)) { transitions = isString(bodyStyle.webkitTransition); animations = isString(bodyStyle.webkitAnimation); } } return { // Android has history.pushState, but it does not update location correctly // so let's not use the history API at all. // http://code.google.com/p/android/issues/detail?id=17471 // https://github.com/angular/angular.js/issues/904 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has // so let's not use the history API also // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined history: !!(hasHistoryPushState && !(android < 4) && !boxee), hasEvent: function(event) { // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have // it. In particular the event is not fired when backspace or delete key are pressed or // when cut operation is performed. // IE10+ implements 'input' event but it erroneously fires under various situations, // e.g. when placeholder changes, or a form is focused. if (event === 'input' && msie <= 11) return false; if (isUndefined(eventSupport[event])) { var divElm = document.createElement('div'); eventSupport[event] = 'on' + event in divElm; } return eventSupport[event]; }, csp: csp(), vendorPrefix: vendorPrefix, transitions: transitions, animations: animations, android: android }; }]; } var $templateRequestMinErr = minErr('$compile'); /** * @ngdoc provider * @name $templateRequestProvider * @this * * @description * Used to configure the options passed to the {@link $http} service when making a template request. * * For example, it can be used for specifying the "Accept" header that is sent to the server, when * requesting a template. */ function $TemplateRequestProvider() { var httpOptions; /** * @ngdoc method * @name $templateRequestProvider#httpOptions * @description * The options to be passed to the {@link $http} service when making the request. * You can use this to override options such as the "Accept" header for template requests. * * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the * options if not overridden here. * * @param {string=} value new value for the {@link $http} options. * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. */ this.httpOptions = function(val) { if (val) { httpOptions = val; return this; } return httpOptions; }; /** * @ngdoc service * @name $templateRequest * * @description * The `$templateRequest` service runs security checks then downloads the provided template using * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted * when `tpl` is of type string and `$templateCache` has the matching entry. * * If you want to pass custom options to the `$http` service, such as setting the Accept header you * can configure this via {@link $templateRequestProvider#httpOptions}. * * @param {string|TrustedResourceUrl} tpl The HTTP request template URL * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty * * @return {Promise} a promise for the HTTP response data of the given URL. * * @property {number} totalPendingRequests total amount of pending template requests being downloaded. */ this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { function handleRequestFn(tpl, ignoreRequestError) { handleRequestFn.totalPendingRequests++; // We consider the template cache holds only trusted templates, so // there's no need to go through whitelisting again for keys that already // are included in there. This also makes Angular accept any script // directive, no matter its name. However, we still need to unwrap trusted // types. if (!isString(tpl) || isUndefined($templateCache.get(tpl))) { tpl = $sce.getTrustedResourceUrl(tpl); } var transformResponse = $http.defaults && $http.defaults.transformResponse; if (isArray(transformResponse)) { transformResponse = transformResponse.filter(function(transformer) { return transformer !== defaultHttpResponseTransform; }); } else if (transformResponse === defaultHttpResponseTransform) { transformResponse = null; } return $http.get(tpl, extend({ cache: $templateCache, transformResponse: transformResponse }, httpOptions) )['finally'](function() { handleRequestFn.totalPendingRequests--; }) .then(function(response) { $templateCache.put(tpl, response.data); return response.data; }, handleError); function handleError(resp) { if (!ignoreRequestError) { throw $templateRequestMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', tpl, resp.status, resp.statusText); } return $q.reject(resp); } } handleRequestFn.totalPendingRequests = 0; return handleRequestFn; }]; } /** @this */ function $$TestabilityProvider() { this.$get = ['$rootScope', '$browser', '$location', function($rootScope, $browser, $location) { /** * @name $testability * * @description * The private $$testability service provides a collection of methods for use when debugging * or by automated test and debugging tools. */ var testability = {}; /** * @name $$testability#findBindings * * @description * Returns an array of elements that are bound (via ng-bind or {{}}) * to expressions matching the input. * * @param {Element} element The element root to search from. * @param {string} expression The binding expression to match. * @param {boolean} opt_exactMatch If true, only returns exact matches * for the expression. Filters and whitespace are ignored. */ testability.findBindings = function(element, expression, opt_exactMatch) { var bindings = element.getElementsByClassName('ng-binding'); var matches = []; forEach(bindings, function(binding) { var dataBinding = angular.element(binding).data('$binding'); if (dataBinding) { forEach(dataBinding, function(bindingName) { if (opt_exactMatch) { var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); if (matcher.test(bindingName)) { matches.push(binding); } } else { if (bindingName.indexOf(expression) !== -1) { matches.push(binding); } } }); } }); return matches; }; /** * @name $$testability#findModels * * @description * Returns an array of elements that are two-way found via ng-model to * expressions matching the input. * * @param {Element} element The element root to search from. * @param {string} expression The model expression to match. * @param {boolean} opt_exactMatch If true, only returns exact matches * for the expression. */ testability.findModels = function(element, expression, opt_exactMatch) { var prefixes = ['ng-', 'data-ng-', 'ng\\:']; for (var p = 0; p < prefixes.length; ++p) { var attributeEquals = opt_exactMatch ? '=' : '*='; var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; var elements = element.querySelectorAll(selector); if (elements.length) { return elements; } } }; /** * @name $$testability#getLocation * * @description * Shortcut for getting the location in a browser agnostic way. Returns * the path, search, and hash. (e.g. /path?a=b#hash) */ testability.getLocation = function() { return $location.url(); }; /** * @name $$testability#setLocation * * @description * Shortcut for navigating to a location without doing a full page reload. * * @param {string} url The location url (path, search and hash, * e.g. /path?a=b#hash) to go to. */ testability.setLocation = function(url) { if (url !== $location.url()) { $location.url(url); $rootScope.$digest(); } }; /** * @name $$testability#whenStable * * @description * Calls the callback when $timeout and $http requests are completed. * * @param {function} callback */ testability.whenStable = function(callback) { $browser.notifyWhenNoOutstandingRequests(callback); }; return testability; }]; } /** @this */ function $TimeoutProvider() { this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', function($rootScope, $browser, $q, $$q, $exceptionHandler) { var deferreds = {}; /** * @ngdoc service * @name $timeout * * @description * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch * block and delegates any exceptions to * {@link ng.$exceptionHandler $exceptionHandler} service. * * The return value of calling `$timeout` is a promise, which will be resolved when * the delay has passed and the timeout function, if provided, is executed. * * To cancel a timeout request, call `$timeout.cancel(promise)`. * * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to * synchronously flush the queue of deferred functions. * * If you only want a promise that will be resolved after some specified delay * then you can call `$timeout` without the `fn` function. * * @param {function()=} fn A function, whose execution should be delayed. * @param {number=} [delay=0] Delay in milliseconds. * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. * @param {...*=} Pass additional parameters to the executed function. * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise * will be resolved with the return value of the `fn` function. * */ function timeout(fn, delay, invokeApply) { if (!isFunction(fn)) { invokeApply = delay; delay = fn; fn = noop; } var args = sliceArgs(arguments, 3), skipApply = (isDefined(invokeApply) && !invokeApply), deferred = (skipApply ? $$q : $q).defer(), promise = deferred.promise, timeoutId; timeoutId = $browser.defer(function() { try { deferred.resolve(fn.apply(null, args)); } catch (e) { deferred.reject(e); $exceptionHandler(e); } finally { delete deferreds[promise.$$timeoutId]; } if (!skipApply) $rootScope.$apply(); }, delay); promise.$$timeoutId = timeoutId; deferreds[timeoutId] = deferred; return promise; } /** * @ngdoc method * @name $timeout#cancel * * @description * Cancels a task associated with the `promise`. As a result of this, the promise will be * resolved with a rejection. * * @param {Promise=} promise Promise returned by the `$timeout` function. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully * canceled. */ timeout.cancel = function(promise) { if (promise && promise.$$timeoutId in deferreds) { deferreds[promise.$$timeoutId].reject('canceled'); delete deferreds[promise.$$timeoutId]; return $browser.defer.cancel(promise.$$timeoutId); } return false; }; return timeout; }]; } // NOTE: The usage of window and document instead of $window and $document here is // deliberate. This service depends on the specific behavior of anchor nodes created by the // browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and // cause us to break tests. In addition, when the browser resolves a URL for XHR, it // doesn't know about mocked locations and resolves URLs to the real document - which is // exactly the behavior needed here. There is little value is mocking these out for this // service. var urlParsingNode = window.document.createElement('a'); var originUrl = urlResolve(window.location.href); /** * * Implementation Notes for non-IE browsers * ---------------------------------------- * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, * results both in the normalizing and parsing of the URL. Normalizing means that a relative * URL will be resolved into an absolute URL in the context of the application document. * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related * properties are all populated to reflect the normalized URL. This approach has wide * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html * * Implementation Notes for IE * --------------------------- * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other * browsers. However, the parsed components will not be set if the URL assigned did not specify * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We * work around that by performing the parsing in a 2nd step by taking a previously normalized * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the * properties such as protocol, hostname, port, etc. * * References: * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html * http://url.spec.whatwg.org/#urlutils * https://github.com/angular/angular.js/pull/2902 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ * * @kind function * @param {string} url The URL to be parsed. * @description Normalizes and parses a URL. * @returns {object} Returns the normalized URL as a dictionary. * * | member name | Description | * |---------------|----------------| * | href | A normalized version of the provided URL if it was not an absolute URL | * | protocol | The protocol including the trailing colon | * | host | The host and port (if the port is non-default) of the normalizedUrl | * | search | The search params, minus the question mark | * | hash | The hash string, minus the hash symbol * | hostname | The hostname * | port | The port, without ":" * | pathname | The pathname, beginning with "/" * */ function urlResolve(url) { var href = url; if (msie) { // Normalize before parse. Refer Implementation Notes on why this is // done in two steps on IE. urlParsingNode.setAttribute('href', href); href = urlParsingNode.href; } urlParsingNode.setAttribute('href', href); // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils return { href: urlParsingNode.href, protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', host: urlParsingNode.host, search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', hostname: urlParsingNode.hostname, port: urlParsingNode.port, pathname: (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname : '/' + urlParsingNode.pathname }; } /** * Parse a request URL and determine whether this is a same-origin request as the application document. * * @param {string|object} requestUrl The url of the request as a string that will be resolved * or a parsed URL object. * @returns {boolean} Whether the request is for the same origin as the application document. */ function urlIsSameOrigin(requestUrl) { var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; return (parsed.protocol === originUrl.protocol && parsed.host === originUrl.host); } /** * @ngdoc service * @name $window * @this * * @description * A reference to the browser's `window` object. While `window` * is globally available in JavaScript, it causes testability problems, because * it is a global variable. In angular we always refer to it through the * `$window` service, so it may be overridden, removed or mocked for testing. * * Expressions, like the one defined for the `ngClick` directive in the example * below, are evaluated with respect to the current scope. Therefore, there is * no risk of inadvertently coding in a dependency on a global value in such an * expression. * * @example
          it('should display the greeting in the input box', function() { element(by.model('greeting')).sendKeys('Hello, E2E Tests'); // If we click the button it will block the test runner // element(':button').click(); });
          */ function $WindowProvider() { this.$get = valueFn(window); } /** * @name $$cookieReader * @requires $document * * @description * This is a private service for reading cookies used by $http and ngCookies * * @return {Object} a key/value map of the current cookies */ function $$CookieReader($document) { var rawDocument = $document[0] || {}; var lastCookies = {}; var lastCookieString = ''; function safeGetCookie(rawDocument) { try { return rawDocument.cookie || ''; } catch (e) { return ''; } } function safeDecodeURIComponent(str) { try { return decodeURIComponent(str); } catch (e) { return str; } } return function() { var cookieArray, cookie, i, index, name; var currentCookieString = safeGetCookie(rawDocument); if (currentCookieString !== lastCookieString) { lastCookieString = currentCookieString; cookieArray = lastCookieString.split('; '); lastCookies = {}; for (i = 0; i < cookieArray.length; i++) { cookie = cookieArray[i]; index = cookie.indexOf('='); if (index > 0) { //ignore nameless cookies name = safeDecodeURIComponent(cookie.substring(0, index)); // the first value that is seen for a cookie is the most // specific one. values for the same cookie name that // follow are for less specific paths. if (isUndefined(lastCookies[name])) { lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); } } } } return lastCookies; }; } $$CookieReader.$inject = ['$document']; /** @this */ function $$CookieReaderProvider() { this.$get = $$CookieReader; } /* global currencyFilter: true, dateFilter: true, filterFilter: true, jsonFilter: true, limitToFilter: true, lowercaseFilter: true, numberFilter: true, orderByFilter: true, uppercaseFilter: true, */ /** * @ngdoc provider * @name $filterProvider * @description * * Filters are just functions which transform input to an output. However filters need to be * Dependency Injected. To achieve this a filter definition consists of a factory function which is * annotated with dependencies and is responsible for creating a filter function. * *
          * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * (`myapp_subsection_filterx`). *
          * * ```js * // Filter registration * function MyModule($provide, $filterProvider) { * // create a service to demonstrate injection (not always needed) * $provide.value('greet', function(name){ * return 'Hello ' + name + '!'; * }); * * // register a filter factory which uses the * // greet service to demonstrate DI. * $filterProvider.register('greet', function(greet){ * // return the filter function which uses the greet service * // to generate salutation * return function(text) { * // filters need to be forgiving so check input validity * return text && greet(text) || text; * }; * }); * } * ``` * * The filter function is registered with the `$injector` under the filter name suffix with * `Filter`. * * ```js * it('should be the same instance', inject( * function($filterProvider) { * $filterProvider.register('reverse', function(){ * return ...; * }); * }, * function($filter, reverseFilter) { * expect($filter('reverse')).toBe(reverseFilter); * }); * ``` * * * For more information about how angular filters work, and how to create your own filters, see * {@link guide/filter Filters} in the Angular Developer Guide. */ /** * @ngdoc service * @name $filter * @kind function * @description * Filters are used for formatting data displayed to the user. * * They can be used in view templates, controllers or services.Angular comes * with a collection of [built-in filters](api/ng/filter), but it is easy to * define your own as well. * * The general syntax in templates is as follows: * * ```html * {{ expression [| filter_name[:parameter_value] ... ] }} * ``` * * @param {String} name Name of the filter function to retrieve * @return {Function} the filter function * @example

          {{ originalText }}

          {{ filteredText }}

          angular.module('filterExample', []) .controller('MainCtrl', function($scope, $filter) { $scope.originalText = 'hello'; $scope.filteredText = $filter('uppercase')($scope.originalText); });
          */ $FilterProvider.$inject = ['$provide']; /** @this */ function $FilterProvider($provide) { var suffix = 'Filter'; /** * @ngdoc method * @name $filterProvider#register * @param {string|Object} name Name of the filter function, or an object map of filters where * the keys are the filter names and the values are the filter factories. * *
          * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * (`myapp_subsection_filterx`). *
          * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. * @returns {Object} Registered filter instance, or if a map of filters was provided then a map * of the registered filter instances. */ function register(name, factory) { if (isObject(name)) { var filters = {}; forEach(name, function(filter, key) { filters[key] = register(key, filter); }); return filters; } else { return $provide.factory(name + suffix, factory); } } this.register = register; this.$get = ['$injector', function($injector) { return function(name) { return $injector.get(name + suffix); }; }]; //////////////////////////////////////// /* global currencyFilter: false, dateFilter: false, filterFilter: false, jsonFilter: false, limitToFilter: false, lowercaseFilter: false, numberFilter: false, orderByFilter: false, uppercaseFilter: false */ register('currency', currencyFilter); register('date', dateFilter); register('filter', filterFilter); register('json', jsonFilter); register('limitTo', limitToFilter); register('lowercase', lowercaseFilter); register('number', numberFilter); register('orderBy', orderByFilter); register('uppercase', uppercaseFilter); } /** * @ngdoc filter * @name filter * @kind function * * @description * Selects a subset of items from `array` and returns it as a new array. * * @param {Array} array The source array. * @param {string|Object|function()} expression The predicate to be used for selecting items from * `array`. * * Can be one of: * * - `string`: The string is used for matching against the contents of the `array`. All strings or * objects with string properties in `array` that match this string will be returned. This also * applies to nested object properties. * The predicate can be negated by prefixing the string with `!`. * * - `Object`: A pattern object can be used to filter specific properties on objects contained * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items * which have property `name` containing "M" and property `phone` containing "1". A special * property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match * against any property of the object or its nested object properties. That's equivalent to the * simple substring match with a `string` as described above. The special property name can be * overwritten, using the `anyPropertyKey` parameter. * The predicate can be negated by prefixing the string with `!`. * For example `{name: "!M"}` predicate will return an array of items which have property `name` * not containing "M". * * Note that a named property will match properties on the same level only, while the special * `$` property will match properties on the same level or deeper. E.g. an array item like * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but * **will** be matched by `{$: 'John'}`. * * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. * The function is called for each element of the array, with the element, its index, and * the entire array itself as arguments. * * The final result is an array of those elements that the predicate returned true for. * * @param {function(actual, expected)|true|false} [comparator] Comparator which is used in * determining if the expected value (from the filter expression) and actual value (from * the object in the array) should be considered a match. * * Can be one of: * * - `function(actual, expected)`: * The function will be given the object value and the predicate value to compare and * should return true if both values should be considered equal. * * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. * This is essentially strict comparison of expected and actual. * * - `false`: A short hand for a function which will look for a substring match in a case * insensitive way. Primitive values are converted to strings. Objects are not compared against * primitives, unless they have a custom `toString` method (e.g. `Date` objects). * * * Defaults to `false`. * * @param {string} [anyPropertyKey] The special property name that matches against any property. * By default `$`. * * @example
          NamePhone
          {{friend.name}} {{friend.phone}}





          NamePhone
          {{friendObj.name}} {{friendObj.phone}}
          var expectFriendNames = function(expectedNames, key) { element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { arr.forEach(function(wd, i) { expect(wd.getText()).toMatch(expectedNames[i]); }); }); }; it('should search across all fields when filtering with a string', function() { var searchText = element(by.model('searchText')); searchText.clear(); searchText.sendKeys('m'); expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); searchText.clear(); searchText.sendKeys('76'); expectFriendNames(['John', 'Julie'], 'friend'); }); it('should search in specific fields when filtering with a predicate object', function() { var searchAny = element(by.model('search.$')); searchAny.clear(); searchAny.sendKeys('i'); expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); }); it('should use a equal comparison when comparator is true', function() { var searchName = element(by.model('search.name')); var strict = element(by.model('strict')); searchName.clear(); searchName.sendKeys('Julie'); strict.click(); expectFriendNames(['Julie'], 'friendObj'); });
          */ function filterFilter() { return function(array, expression, comparator, anyPropertyKey) { if (!isArrayLike(array)) { if (array == null) { return array; } else { throw minErr('filter')('notarray', 'Expected array but received: {0}', array); } } anyPropertyKey = anyPropertyKey || '$'; var expressionType = getTypeForFilter(expression); var predicateFn; var matchAgainstAnyProp; switch (expressionType) { case 'function': predicateFn = expression; break; case 'boolean': case 'null': case 'number': case 'string': matchAgainstAnyProp = true; // falls through case 'object': predicateFn = createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp); break; default: return array; } return Array.prototype.filter.call(array, predicateFn); }; } // Helper functions for `filterFilter` function createPredicateFn(expression, comparator, anyPropertyKey, matchAgainstAnyProp) { var shouldMatchPrimitives = isObject(expression) && (anyPropertyKey in expression); var predicateFn; if (comparator === true) { comparator = equals; } else if (!isFunction(comparator)) { comparator = function(actual, expected) { if (isUndefined(actual)) { // No substring matching against `undefined` return false; } if ((actual === null) || (expected === null)) { // No substring matching against `null`; only match against `null` return actual === expected; } if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { // Should not compare primitives against objects, unless they have custom `toString` method return false; } actual = lowercase('' + actual); expected = lowercase('' + expected); return actual.indexOf(expected) !== -1; }; } predicateFn = function(item) { if (shouldMatchPrimitives && !isObject(item)) { return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false); } return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp); }; return predicateFn; } function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) { var actualType = getTypeForFilter(actual); var expectedType = getTypeForFilter(expected); if ((expectedType === 'string') && (expected.charAt(0) === '!')) { return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp); } else if (isArray(actual)) { // In case `actual` is an array, consider it a match // if ANY of it's items matches `expected` return actual.some(function(item) { return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp); }); } switch (actualType) { case 'object': var key; if (matchAgainstAnyProp) { for (key in actual) { if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) { return true; } } return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, anyPropertyKey, false); } else if (expectedType === 'object') { for (key in expected) { var expectedVal = expected[key]; if (isFunction(expectedVal) || isUndefined(expectedVal)) { continue; } var matchAnyProperty = key === anyPropertyKey; var actualVal = matchAnyProperty ? actual : actual[key]; if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) { return false; } } return true; } else { return comparator(actual, expected); } case 'function': return false; default: return comparator(actual, expected); } } // Used for easily differentiating between `null` and actual `object` function getTypeForFilter(val) { return (val === null) ? 'null' : typeof val; } var MAX_DIGITS = 22; var DECIMAL_SEP = '.'; var ZERO_CHAR = '0'; /** * @ngdoc filter * @name currency * @kind function * * @description * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default * symbol for current locale is used. * * @param {number} amount Input to filter. * @param {string=} symbol Currency symbol or identifier to be displayed. * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale * @returns {string} Formatted number. * * * @example

          default currency symbol ($): {{amount | currency}}
          custom currency identifier (USD$): {{amount | currency:"USD$"}} no fractions (0): {{amount | currency:"USD$":0}}
          it('should init with 1234.56', function() { expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); }); it('should update', function() { if (browser.params.browser === 'safari') { // Safari does not understand the minus key. See // https://github.com/angular/protractor/issues/481 return; } element(by.model('amount')).clear(); element(by.model('amount')).sendKeys('-1234'); expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); });
          */ currencyFilter.$inject = ['$locale']; function currencyFilter($locale) { var formats = $locale.NUMBER_FORMATS; return function(amount, currencySymbol, fractionSize) { if (isUndefined(currencySymbol)) { currencySymbol = formats.CURRENCY_SYM; } if (isUndefined(fractionSize)) { fractionSize = formats.PATTERNS[1].maxFrac; } // if null or undefined pass it through return (amount == null) ? amount : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). replace(/\u00A4/g, currencySymbol); }; } /** * @ngdoc filter * @name number * @kind function * * @description * Formats a number as text. * * If the input is null or undefined, it will just be returned. * If the input is infinite (Infinity or -Infinity), the Infinity symbol '∞' or '-∞' is returned, respectively. * If the input is not a number an empty string is returned. * * * @param {number|string} number Number to format. * @param {(number|string)=} fractionSize Number of decimal places to round the number to. * If this is not provided then the fraction size is computed from the current locale's number * formatting pattern. In the case of the default locale, it will be 3. * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current * locale (e.g., in the en_US locale it will have "." as the decimal separator and * include "," group separators after each third digit). * * @example

          Default formatting: {{val | number}}
          No fractions: {{val | number:0}}
          Negative number: {{-val | number:4}}
          it('should format numbers', function() { expect(element(by.id('number-default')).getText()).toBe('1,234.568'); expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); }); it('should update', function() { element(by.model('val')).clear(); element(by.model('val')).sendKeys('3374.333'); expect(element(by.id('number-default')).getText()).toBe('3,374.333'); expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); });
          */ numberFilter.$inject = ['$locale']; function numberFilter($locale) { var formats = $locale.NUMBER_FORMATS; return function(number, fractionSize) { // if null or undefined pass it through return (number == null) ? number : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize); }; } /** * Parse a number (as a string) into three components that can be used * for formatting the number. * * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) * * @param {string} numStr The number to parse * @return {object} An object describing this number, containing the following keys: * - d : an array of digits containing leading zeros as necessary * - i : the number of the digits in `d` that are to the left of the decimal point * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` * */ function parse(numStr) { var exponent = 0, digits, numberOfIntegerDigits; var i, j, zeros; // Decimal point? if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { numStr = numStr.replace(DECIMAL_SEP, ''); } // Exponential form? if ((i = numStr.search(/e/i)) > 0) { // Work out the exponent. if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; numberOfIntegerDigits += +numStr.slice(i + 1); numStr = numStr.substring(0, i); } else if (numberOfIntegerDigits < 0) { // There was no decimal point or exponent so it is an integer. numberOfIntegerDigits = numStr.length; } // Count the number of leading zeros. for (i = 0; numStr.charAt(i) === ZERO_CHAR; i++) { /* empty */ } if (i === (zeros = numStr.length)) { // The digits are all zero. digits = [0]; numberOfIntegerDigits = 1; } else { // Count the number of trailing zeros zeros--; while (numStr.charAt(zeros) === ZERO_CHAR) zeros--; // Trailing zeros are insignificant so ignore them numberOfIntegerDigits -= i; digits = []; // Convert string to array of digits without leading/trailing zeros. for (j = 0; i <= zeros; i++, j++) { digits[j] = +numStr.charAt(i); } } // If the number overflows the maximum allowed digits then use an exponent. if (numberOfIntegerDigits > MAX_DIGITS) { digits = digits.splice(0, MAX_DIGITS - 1); exponent = numberOfIntegerDigits - 1; numberOfIntegerDigits = 1; } return { d: digits, e: exponent, i: numberOfIntegerDigits }; } /** * Round the parsed number to the specified number of decimal places * This function changed the parsedNumber in-place */ function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { var digits = parsedNumber.d; var fractionLen = digits.length - parsedNumber.i; // determine fractionSize if it is not specified; `+fractionSize` converts it to a number fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; // The index of the digit to where rounding is to occur var roundAt = fractionSize + parsedNumber.i; var digit = digits[roundAt]; if (roundAt > 0) { // Drop fractional digits beyond `roundAt` digits.splice(Math.max(parsedNumber.i, roundAt)); // Set non-fractional digits beyond `roundAt` to 0 for (var j = roundAt; j < digits.length; j++) { digits[j] = 0; } } else { // We rounded to zero so reset the parsedNumber fractionLen = Math.max(0, fractionLen); parsedNumber.i = 1; digits.length = Math.max(1, roundAt = fractionSize + 1); digits[0] = 0; for (var i = 1; i < roundAt; i++) digits[i] = 0; } if (digit >= 5) { if (roundAt - 1 < 0) { for (var k = 0; k > roundAt; k--) { digits.unshift(0); parsedNumber.i++; } digits.unshift(1); parsedNumber.i++; } else { digits[roundAt - 1]++; } } // Pad out with zeros to get the required fraction length for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); // Do any carrying, e.g. a digit was rounded up to 10 var carry = digits.reduceRight(function(carry, d, i, digits) { d = d + carry; digits[i] = d % 10; return Math.floor(d / 10); }, 0); if (carry) { digits.unshift(carry); parsedNumber.i++; } } /** * Format a number into a string * @param {number} number The number to format * @param {{ * minFrac, // the minimum number of digits required in the fraction part of the number * maxFrac, // the maximum number of digits required in the fraction part of the number * gSize, // number of digits in each group of separated digits * lgSize, // number of digits in the last group of digits before the decimal separator * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) * posPre, // the string to go in front of a positive number * negSuf, // the string to go after a negative number (e.g. `)`) * posSuf // the string to go after a positive number * }} pattern * @param {string} groupSep The string to separate groups of number (e.g. `,`) * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) * @param {[type]} fractionSize The size of the fractional part of the number * @return {string} The number formatted as a string */ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; var isInfinity = !isFinite(number); var isZero = false; var numStr = Math.abs(number) + '', formattedText = '', parsedNumber; if (isInfinity) { formattedText = '\u221e'; } else { parsedNumber = parse(numStr); roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); var digits = parsedNumber.d; var integerLen = parsedNumber.i; var exponent = parsedNumber.e; var decimals = []; isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); // pad zeros for small numbers while (integerLen < 0) { digits.unshift(0); integerLen++; } // extract decimals digits if (integerLen > 0) { decimals = digits.splice(integerLen, digits.length); } else { decimals = digits; digits = [0]; } // format the integer digits with grouping separators var groups = []; if (digits.length >= pattern.lgSize) { groups.unshift(digits.splice(-pattern.lgSize, digits.length).join('')); } while (digits.length > pattern.gSize) { groups.unshift(digits.splice(-pattern.gSize, digits.length).join('')); } if (digits.length) { groups.unshift(digits.join('')); } formattedText = groups.join(groupSep); // append the decimal digits if (decimals.length) { formattedText += decimalSep + decimals.join(''); } if (exponent) { formattedText += 'e+' + exponent; } } if (number < 0 && !isZero) { return pattern.negPre + formattedText + pattern.negSuf; } else { return pattern.posPre + formattedText + pattern.posSuf; } } function padNumber(num, digits, trim, negWrap) { var neg = ''; if (num < 0 || (negWrap && num <= 0)) { if (negWrap) { num = -num + 1; } else { num = -num; neg = '-'; } } num = '' + num; while (num.length < digits) num = ZERO_CHAR + num; if (trim) { num = num.substr(num.length - digits); } return neg + num; } function dateGetter(name, size, offset, trim, negWrap) { offset = offset || 0; return function(date) { var value = date['get' + name](); if (offset > 0 || value > -offset) { value += offset; } if (value === 0 && offset === -12) value = 12; return padNumber(value, size, trim, negWrap); }; } function dateStrGetter(name, shortForm, standAlone) { return function(date, formats) { var value = date['get' + name](); var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); var get = uppercase(propPrefix + name); return formats[get][value]; }; } function timeZoneGetter(date, formats, offset) { var zone = -1 * offset; var paddedZone = (zone >= 0) ? '+' : ''; paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + padNumber(Math.abs(zone % 60), 2); return paddedZone; } function getFirstThursdayOfYear(year) { // 0 = index of January var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); // 4 = index of Thursday (+1 to account for 1st = 5) // 11 = index of *next* Thursday (+1 account for 1st = 12) return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); } function getThursdayThisWeek(datetime) { return new Date(datetime.getFullYear(), datetime.getMonth(), // 4 = index of Thursday datetime.getDate() + (4 - datetime.getDay())); } function weekGetter(size) { return function(date) { var firstThurs = getFirstThursdayOfYear(date.getFullYear()), thisThurs = getThursdayThisWeek(date); var diff = +thisThurs - +firstThurs, result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week return padNumber(result, size); }; } function ampmGetter(date, formats) { return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; } function eraGetter(date, formats) { return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; } function longEraGetter(date, formats) { return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; } var DATE_FORMATS = { yyyy: dateGetter('FullYear', 4, 0, false, true), yy: dateGetter('FullYear', 2, 0, true, true), y: dateGetter('FullYear', 1, 0, false, true), MMMM: dateStrGetter('Month'), MMM: dateStrGetter('Month', true), MM: dateGetter('Month', 2, 1), M: dateGetter('Month', 1, 1), LLLL: dateStrGetter('Month', false, true), dd: dateGetter('Date', 2), d: dateGetter('Date', 1), HH: dateGetter('Hours', 2), H: dateGetter('Hours', 1), hh: dateGetter('Hours', 2, -12), h: dateGetter('Hours', 1, -12), mm: dateGetter('Minutes', 2), m: dateGetter('Minutes', 1), ss: dateGetter('Seconds', 2), s: dateGetter('Seconds', 1), // while ISO 8601 requires fractions to be prefixed with `.` or `,` // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions sss: dateGetter('Milliseconds', 3), EEEE: dateStrGetter('Day'), EEE: dateStrGetter('Day', true), a: ampmGetter, Z: timeZoneGetter, ww: weekGetter(2), w: weekGetter(1), G: eraGetter, GG: eraGetter, GGG: eraGetter, GGGG: longEraGetter }; var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, NUMBER_STRING = /^-?\d+$/; /** * @ngdoc filter * @name date * @kind function * * @description * Formats `date` to a string based on the requested `format`. * * `format` string can be composed of the following elements: * * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) * * `'MMMM'`: Month in year (January-December) * * `'MMM'`: Month in year (Jan-Dec) * * `'MM'`: Month in year, padded (01-12) * * `'M'`: Month in year (1-12) * * `'LLLL'`: Stand-alone month in year (January-December) * * `'dd'`: Day in month, padded (01-31) * * `'d'`: Day in month (1-31) * * `'EEEE'`: Day in Week,(Sunday-Saturday) * * `'EEE'`: Day in Week, (Sun-Sat) * * `'HH'`: Hour in day, padded (00-23) * * `'H'`: Hour in day (0-23) * * `'hh'`: Hour in AM/PM, padded (01-12) * * `'h'`: Hour in AM/PM, (1-12) * * `'mm'`: Minute in hour, padded (00-59) * * `'m'`: Minute in hour (0-59) * * `'ss'`: Second in minute, padded (00-59) * * `'s'`: Second in minute (0-59) * * `'sss'`: Millisecond in second, padded (000-999) * * `'a'`: AM/PM marker * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') * * `format` string can also be one of the following predefined * {@link guide/i18n localizable formats}: * * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale * (e.g. Sep 3, 2010 12:05:08 PM) * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale * (e.g. Friday, September 3, 2010) * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) * * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence * (e.g. `"h 'o''clock'"`). * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is * specified in the string input, the time is considered to be in the local timezone. * @param {string=} format Formatting rules (see Description). If not specified, * `mediumDate` is used. * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the * continental US time zone abbreviations, but for general use, use a time zone offset, for * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) * If not specified, the timezone of the browser will be used. * @returns {string} Formatted string or the input if input is not recognized as date/millis. * * @example {{1288323623006 | date:'medium'}}: {{1288323623006 | date:'medium'}}
          {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
          {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
          {{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}: {{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}
          it('should format date', function() { expect(element(by.binding("1288323623006 | date:'medium'")).getText()). toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). toMatch(/2010-10-2\d \d{2}:\d{2}:\d{2} (-|\+)?\d{4}/); expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); });
          */ dateFilter.$inject = ['$locale']; function dateFilter($locale) { var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; // 1 2 3 4 5 6 7 8 9 10 11 function jsonStringToDate(string) { var match; if ((match = string.match(R_ISO8601_STR))) { var date = new Date(0), tzHour = 0, tzMin = 0, dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, timeSetter = match[8] ? date.setUTCHours : date.setHours; if (match[9]) { tzHour = toInt(match[9] + match[10]); tzMin = toInt(match[9] + match[11]); } dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); var h = toInt(match[4] || 0) - tzHour; var m = toInt(match[5] || 0) - tzMin; var s = toInt(match[6] || 0); var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); timeSetter.call(date, h, m, s, ms); return date; } return string; } return function(date, format, timezone) { var text = '', parts = [], fn, match; format = format || 'mediumDate'; format = $locale.DATETIME_FORMATS[format] || format; if (isString(date)) { date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); } if (isNumber(date)) { date = new Date(date); } if (!isDate(date) || !isFinite(date.getTime())) { return date; } while (format) { match = DATE_FORMATS_SPLIT.exec(format); if (match) { parts = concat(parts, match, 1); format = parts.pop(); } else { parts.push(format); format = null; } } var dateTimezoneOffset = date.getTimezoneOffset(); if (timezone) { dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); date = convertTimezoneToLocal(date, timezone, true); } forEach(parts, function(value) { fn = DATE_FORMATS[value]; text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\''); }); return text; }; } /** * @ngdoc filter * @name json * @kind function * * @description * Allows you to convert a JavaScript object into JSON string. * * This filter is mostly useful for debugging. When using the double curly {{value}} notation * the binding is automatically converted to JSON. * * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. * @returns {string} JSON string. * * * @example
          {{ {'name':'value'} | json }}
          {{ {'name':'value'} | json:4 }}
          it('should jsonify filtered objects', function() { expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n {2}"name": ?"value"\n}/); expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n {4}"name": ?"value"\n}/); });
          * */ function jsonFilter() { return function(object, spacing) { if (isUndefined(spacing)) { spacing = 2; } return toJson(object, spacing); }; } /** * @ngdoc filter * @name lowercase * @kind function * @description * Converts string to lowercase. * @see angular.lowercase */ var lowercaseFilter = valueFn(lowercase); /** * @ngdoc filter * @name uppercase * @kind function * @description * Converts string to uppercase. * @see angular.uppercase */ var uppercaseFilter = valueFn(uppercase); /** * @ngdoc filter * @name limitTo * @kind function * * @description * Creates a new array or string containing only a specified number of elements. The elements are * taken from either the beginning or the end of the source array, string or number, as specified by * the value and sign (positive or negative) of `limit`. Other array-like objects are also supported * (e.g. array subclasses, NodeLists, jqLite/jQuery collections etc). If a number is used as input, * it is converted to a string. * * @param {Array|ArrayLike|string|number} input - Array/array-like, string or number to be limited. * @param {string|number} limit - The length of the returned array or string. If the `limit` number * is positive, `limit` number of items from the beginning of the source array/string are copied. * If the number is negative, `limit` number of items from the end of the source array/string * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, * the input will be returned unchanged. * @param {(string|number)=} begin - Index at which to begin limitation. As a negative index, * `begin` indicates an offset from the end of `input`. Defaults to `0`. * @returns {Array|string} A new sub-array or substring of length `limit` or less if the input had * less than `limit` elements. * * @example

          Output numbers: {{ numbers | limitTo:numLimit }}

          Output letters: {{ letters | limitTo:letterLimit }}

          Output long number: {{ longNumber | limitTo:longNumberLimit }}

          var numLimitInput = element(by.model('numLimit')); var letterLimitInput = element(by.model('letterLimit')); var longNumberLimitInput = element(by.model('longNumberLimit')); var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); it('should limit the number array to first three items', function() { expect(numLimitInput.getAttribute('value')).toBe('3'); expect(letterLimitInput.getAttribute('value')).toBe('3'); expect(longNumberLimitInput.getAttribute('value')).toBe('3'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); expect(limitedLetters.getText()).toEqual('Output letters: abc'); expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); }); // There is a bug in safari and protractor that doesn't like the minus key // it('should update the output when -3 is entered', function() { // numLimitInput.clear(); // numLimitInput.sendKeys('-3'); // letterLimitInput.clear(); // letterLimitInput.sendKeys('-3'); // longNumberLimitInput.clear(); // longNumberLimitInput.sendKeys('-3'); // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); // }); it('should not exceed the maximum size of input array', function() { numLimitInput.clear(); numLimitInput.sendKeys('100'); letterLimitInput.clear(); letterLimitInput.sendKeys('100'); longNumberLimitInput.clear(); longNumberLimitInput.sendKeys('100'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); });
          */ function limitToFilter() { return function(input, limit, begin) { if (Math.abs(Number(limit)) === Infinity) { limit = Number(limit); } else { limit = toInt(limit); } if (isNumberNaN(limit)) return input; if (isNumber(input)) input = input.toString(); if (!isArrayLike(input)) return input; begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; if (limit >= 0) { return sliceFn(input, begin, begin + limit); } else { if (begin === 0) { return sliceFn(input, limit, input.length); } else { return sliceFn(input, Math.max(0, begin + limit), begin); } } }; } function sliceFn(input, begin, end) { if (isString(input)) return input.slice(begin, end); return slice.call(input, begin, end); } /** * @ngdoc filter * @name orderBy * @kind function * * @description * Returns an array containing the items from the specified `collection`, ordered by a `comparator` * function based on the values computed using the `expression` predicate. * * For example, `[{id: 'foo'}, {id: 'bar'}] | orderBy:'id'` would result in * `[{id: 'bar'}, {id: 'foo'}]`. * * The `collection` can be an Array or array-like object (e.g. NodeList, jQuery object, TypedArray, * String, etc). * * The `expression` can be a single predicate, or a list of predicates each serving as a tie-breaker * for the preceding one. The `expression` is evaluated against each item and the output is used * for comparing with other items. * * You can change the sorting order by setting `reverse` to `true`. By default, items are sorted in * ascending order. * * The comparison is done using the `comparator` function. If none is specified, a default, built-in * comparator is used (see below for details - in a nutshell, it compares numbers numerically and * strings alphabetically). * * ### Under the hood * * Ordering the specified `collection` happens in two phases: * * 1. All items are passed through the predicate (or predicates), and the returned values are saved * along with their type (`string`, `number` etc). For example, an item `{label: 'foo'}`, passed * through a predicate that extracts the value of the `label` property, would be transformed to: * ``` * { * value: 'foo', * type: 'string', * index: ... * } * ``` * 2. The comparator function is used to sort the items, based on the derived values, types and * indices. * * If you use a custom comparator, it will be called with pairs of objects of the form * `{value: ..., type: '...', index: ...}` and is expected to return `0` if the objects are equal * (as far as the comparator is concerned), `-1` if the 1st one should be ranked higher than the * second, or `1` otherwise. * * In order to ensure that the sorting will be deterministic across platforms, if none of the * specified predicates can distinguish between two items, `orderBy` will automatically introduce a * dummy predicate that returns the item's index as `value`. * (If you are using a custom comparator, make sure it can handle this predicate as well.) * * Finally, in an attempt to simplify things, if a predicate returns an object as the extracted * value for an item, `orderBy` will try to convert that object to a primitive value, before passing * it to the comparator. The following rules govern the conversion: * * 1. If the object has a `valueOf()` method that returns a primitive, its return value will be * used instead.
          * (If the object has a `valueOf()` method that returns another object, then the returned object * will be used in subsequent steps.) * 2. If the object has a custom `toString()` method (i.e. not the one inherited from `Object`) that * returns a primitive, its return value will be used instead.
          * (If the object has a `toString()` method that returns another object, then the returned object * will be used in subsequent steps.) * 3. No conversion; the object itself is used. * * ### The default comparator * * The default, built-in comparator should be sufficient for most usecases. In short, it compares * numbers numerically, strings alphabetically (and case-insensitively), for objects falls back to * using their index in the original collection, and sorts values of different types by type. * * More specifically, it follows these steps to determine the relative order of items: * * 1. If the compared values are of different types, compare the types themselves alphabetically. * 2. If both values are of type `string`, compare them alphabetically in a case- and * locale-insensitive way. * 3. If both values are objects, compare their indices instead. * 4. Otherwise, return: * - `0`, if the values are equal (by strict equality comparison, i.e. using `===`). * - `-1`, if the 1st value is "less than" the 2nd value (compared using the `<` operator). * - `1`, otherwise. * * **Note:** If you notice numbers not being sorted as expected, make sure they are actually being * saved as numbers and not strings. * **Note:** For the purpose of sorting, `null` values are treated as the string `'null'` (i.e. * `type: 'string'`, `value: 'null'`). This may cause unexpected sort order relative to * other values. * * @param {Array|ArrayLike} collection - The collection (array or array-like object) to sort. * @param {(Function|string|Array.)=} expression - A predicate (or list of * predicates) to be used by the comparator to determine the order of elements. * * Can be one of: * * - `Function`: A getter function. This function will be called with each item as argument and * the return value will be used for sorting. * - `string`: An Angular expression. This expression will be evaluated against each item and the * result will be used for sorting. For example, use `'label'` to sort by a property called * `label` or `'label.substring(0, 3)'` to sort by the first 3 characters of the `label` * property.
          * (The result of a constant expression is interpreted as a property name to be used for * comparison. For example, use `'"special name"'` (note the extra pair of quotes) to sort by a * property called `special name`.)
          * An expression can be optionally prefixed with `+` or `-` to control the sorting direction, * ascending or descending. For example, `'+label'` or `'-label'`. If no property is provided, * (e.g. `'+'` or `'-'`), the collection element itself is used in comparisons. * - `Array`: An array of function and/or string predicates. If a predicate cannot determine the * relative order of two items, the next predicate is used as a tie-breaker. * * **Note:** If the predicate is missing or empty then it defaults to `'+'`. * * @param {boolean=} reverse - If `true`, reverse the sorting order. * @param {(Function)=} comparator - The comparator function used to determine the relative order of * value pairs. If omitted, the built-in comparator will be used. * * @returns {Array} - The sorted array. * * * @example * ### Ordering a table with `ngRepeat` * * The example below demonstrates a simple {@link ngRepeat ngRepeat}, where the data is sorted by * age in descending order (expression is set to `'-age'`). The `comparator` is not set, which means * it defaults to the built-in comparator. *
          Name Phone Number Age
          {{friend.name}} {{friend.phone}} {{friend.age}}
          angular.module('orderByExample1', []) .controller('ExampleController', ['$scope', function($scope) { $scope.friends = [ {name: 'John', phone: '555-1212', age: 10}, {name: 'Mary', phone: '555-9876', age: 19}, {name: 'Mike', phone: '555-4321', age: 21}, {name: 'Adam', phone: '555-5678', age: 35}, {name: 'Julie', phone: '555-8765', age: 29} ]; }]); .friends { border-collapse: collapse; } .friends th { border-bottom: 1px solid; } .friends td, .friends th { border-left: 1px solid; padding: 5px 10px; } .friends td:first-child, .friends th:first-child { border-left: none; } // Element locators var names = element.all(by.repeater('friends').column('friend.name')); it('should sort friends by age in reverse order', function() { expect(names.get(0).getText()).toBe('Adam'); expect(names.get(1).getText()).toBe('Julie'); expect(names.get(2).getText()).toBe('Mike'); expect(names.get(3).getText()).toBe('Mary'); expect(names.get(4).getText()).toBe('John'); });
          *
          * * @example * ### Changing parameters dynamically * * All parameters can be changed dynamically. The next example shows how you can make the columns of * a table sortable, by binding the `expression` and `reverse` parameters to scope properties. *
          Sort by = {{propertyName}}; reverse = {{reverse}}


          {{friend.name}} {{friend.phone}} {{friend.age}}
          angular.module('orderByExample2', []) .controller('ExampleController', ['$scope', function($scope) { var friends = [ {name: 'John', phone: '555-1212', age: 10}, {name: 'Mary', phone: '555-9876', age: 19}, {name: 'Mike', phone: '555-4321', age: 21}, {name: 'Adam', phone: '555-5678', age: 35}, {name: 'Julie', phone: '555-8765', age: 29} ]; $scope.propertyName = 'age'; $scope.reverse = true; $scope.friends = friends; $scope.sortBy = function(propertyName) { $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false; $scope.propertyName = propertyName; }; }]); .friends { border-collapse: collapse; } .friends th { border-bottom: 1px solid; } .friends td, .friends th { border-left: 1px solid; padding: 5px 10px; } .friends td:first-child, .friends th:first-child { border-left: none; } .sortorder:after { content: '\25b2'; // BLACK UP-POINTING TRIANGLE } .sortorder.reverse:after { content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE } // Element locators var unsortButton = element(by.partialButtonText('unsorted')); var nameHeader = element(by.partialButtonText('Name')); var phoneHeader = element(by.partialButtonText('Phone')); var ageHeader = element(by.partialButtonText('Age')); var firstName = element(by.repeater('friends').column('friend.name').row(0)); var lastName = element(by.repeater('friends').column('friend.name').row(4)); it('should sort friends by some property, when clicking on the column header', function() { expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); phoneHeader.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Mary'); nameHeader.click(); expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('Mike'); ageHeader.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Adam'); }); it('should sort friends in reverse order, when clicking on the same column', function() { expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); ageHeader.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Adam'); ageHeader.click(); expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); }); it('should restore the original order, when clicking "Set to unsorted"', function() { expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); unsortButton.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Julie'); });
          *
          * * @example * ### Using `orderBy` inside a controller * * It is also possible to call the `orderBy` filter manually, by injecting `orderByFilter`, and * calling it with the desired parameters. (Alternatively, you could inject the `$filter` factory * and retrieve the `orderBy` filter with `$filter('orderBy')`.) *
          Sort by = {{propertyName}}; reverse = {{reverse}}


          {{friend.name}} {{friend.phone}} {{friend.age}}
          angular.module('orderByExample3', []) .controller('ExampleController', ['$scope', 'orderByFilter', function($scope, orderBy) { var friends = [ {name: 'John', phone: '555-1212', age: 10}, {name: 'Mary', phone: '555-9876', age: 19}, {name: 'Mike', phone: '555-4321', age: 21}, {name: 'Adam', phone: '555-5678', age: 35}, {name: 'Julie', phone: '555-8765', age: 29} ]; $scope.propertyName = 'age'; $scope.reverse = true; $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); $scope.sortBy = function(propertyName) { $scope.reverse = (propertyName !== null && $scope.propertyName === propertyName) ? !$scope.reverse : false; $scope.propertyName = propertyName; $scope.friends = orderBy(friends, $scope.propertyName, $scope.reverse); }; }]); .friends { border-collapse: collapse; } .friends th { border-bottom: 1px solid; } .friends td, .friends th { border-left: 1px solid; padding: 5px 10px; } .friends td:first-child, .friends th:first-child { border-left: none; } .sortorder:after { content: '\25b2'; // BLACK UP-POINTING TRIANGLE } .sortorder.reverse:after { content: '\25bc'; // BLACK DOWN-POINTING TRIANGLE } // Element locators var unsortButton = element(by.partialButtonText('unsorted')); var nameHeader = element(by.partialButtonText('Name')); var phoneHeader = element(by.partialButtonText('Phone')); var ageHeader = element(by.partialButtonText('Age')); var firstName = element(by.repeater('friends').column('friend.name').row(0)); var lastName = element(by.repeater('friends').column('friend.name').row(4)); it('should sort friends by some property, when clicking on the column header', function() { expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); phoneHeader.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Mary'); nameHeader.click(); expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('Mike'); ageHeader.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Adam'); }); it('should sort friends in reverse order, when clicking on the same column', function() { expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); ageHeader.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Adam'); ageHeader.click(); expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); }); it('should restore the original order, when clicking "Set to unsorted"', function() { expect(firstName.getText()).toBe('Adam'); expect(lastName.getText()).toBe('John'); unsortButton.click(); expect(firstName.getText()).toBe('John'); expect(lastName.getText()).toBe('Julie'); });
          *
          * * @example * ### Using a custom comparator * * If you have very specific requirements about the way items are sorted, you can pass your own * comparator function. For example, you might need to compare some strings in a locale-sensitive * way. (When specifying a custom comparator, you also need to pass a value for the `reverse` * argument - passing `false` retains the default sorting order, i.e. ascending.) *

          Locale-sensitive Comparator

          Name Favorite Letter
          {{friend.name}} {{friend.favoriteLetter}}

          Default Comparator

          Name Favorite Letter
          {{friend.name}} {{friend.favoriteLetter}}
          angular.module('orderByExample4', []) .controller('ExampleController', ['$scope', function($scope) { $scope.friends = [ {name: 'John', favoriteLetter: 'Ä'}, {name: 'Mary', favoriteLetter: 'Ü'}, {name: 'Mike', favoriteLetter: 'Ö'}, {name: 'Adam', favoriteLetter: 'H'}, {name: 'Julie', favoriteLetter: 'Z'} ]; $scope.localeSensitiveComparator = function(v1, v2) { // If we don't get strings, just compare by index if (v1.type !== 'string' || v2.type !== 'string') { return (v1.index < v2.index) ? -1 : 1; } // Compare strings alphabetically, taking locale into account return v1.value.localeCompare(v2.value); }; }]); .friends-container { display: inline-block; margin: 0 30px; } .friends { border-collapse: collapse; } .friends th { border-bottom: 1px solid; } .friends td, .friends th { border-left: 1px solid; padding: 5px 10px; } .friends td:first-child, .friends th:first-child { border-left: none; } // Element locators var container = element(by.css('.custom-comparator')); var names = container.all(by.repeater('friends').column('friend.name')); it('should sort friends by favorite letter (in correct alphabetical order)', function() { expect(names.get(0).getText()).toBe('John'); expect(names.get(1).getText()).toBe('Adam'); expect(names.get(2).getText()).toBe('Mike'); expect(names.get(3).getText()).toBe('Mary'); expect(names.get(4).getText()).toBe('Julie'); });
          * */ orderByFilter.$inject = ['$parse']; function orderByFilter($parse) { return function(array, sortPredicate, reverseOrder, compareFn) { if (array == null) return array; if (!isArrayLike(array)) { throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); } if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } if (sortPredicate.length === 0) { sortPredicate = ['+']; } var predicates = processPredicates(sortPredicate); var descending = reverseOrder ? -1 : 1; // Define the `compare()` function. Use a default comparator if none is specified. var compare = isFunction(compareFn) ? compareFn : defaultCompare; // The next three lines are a version of a Swartzian Transform idiom from Perl // (sometimes called the Decorate-Sort-Undecorate idiom) // See https://en.wikipedia.org/wiki/Schwartzian_transform var compareValues = Array.prototype.map.call(array, getComparisonObject); compareValues.sort(doComparison); array = compareValues.map(function(item) { return item.value; }); return array; function getComparisonObject(value, index) { // NOTE: We are adding an extra `tieBreaker` value based on the element's index. // This will be used to keep the sort stable when none of the input predicates can // distinguish between two elements. return { value: value, tieBreaker: {value: index, type: 'number', index: index}, predicateValues: predicates.map(function(predicate) { return getPredicateValue(predicate.get(value), index); }) }; } function doComparison(v1, v2) { for (var i = 0, ii = predicates.length; i < ii; i++) { var result = compare(v1.predicateValues[i], v2.predicateValues[i]); if (result) { return result * predicates[i].descending * descending; } } return compare(v1.tieBreaker, v2.tieBreaker) * descending; } }; function processPredicates(sortPredicates) { return sortPredicates.map(function(predicate) { var descending = 1, get = identity; if (isFunction(predicate)) { get = predicate; } else if (isString(predicate)) { if ((predicate.charAt(0) === '+' || predicate.charAt(0) === '-')) { descending = predicate.charAt(0) === '-' ? -1 : 1; predicate = predicate.substring(1); } if (predicate !== '') { get = $parse(predicate); if (get.constant) { var key = get(); get = function(value) { return value[key]; }; } } } return {get: get, descending: descending}; }); } function isPrimitive(value) { switch (typeof value) { case 'number': /* falls through */ case 'boolean': /* falls through */ case 'string': return true; default: return false; } } function objectValue(value) { // If `valueOf` is a valid function use that if (isFunction(value.valueOf)) { value = value.valueOf(); if (isPrimitive(value)) return value; } // If `toString` is a valid function and not the one from `Object.prototype` use that if (hasCustomToString(value)) { value = value.toString(); if (isPrimitive(value)) return value; } return value; } function getPredicateValue(value, index) { var type = typeof value; if (value === null) { type = 'string'; value = 'null'; } else if (type === 'object') { value = objectValue(value); } return {value: value, type: type, index: index}; } function defaultCompare(v1, v2) { var result = 0; var type1 = v1.type; var type2 = v2.type; if (type1 === type2) { var value1 = v1.value; var value2 = v2.value; if (type1 === 'string') { // Compare strings case-insensitively value1 = value1.toLowerCase(); value2 = value2.toLowerCase(); } else if (type1 === 'object') { // For basic objects, use the position of the object // in the collection instead of the value if (isObject(value1)) value1 = v1.index; if (isObject(value2)) value2 = v2.index; } if (value1 !== value2) { result = value1 < value2 ? -1 : 1; } } else { result = type1 < type2 ? -1 : 1; } return result; } } function ngDirective(directive) { if (isFunction(directive)) { directive = { link: directive }; } directive.restrict = directive.restrict || 'AC'; return valueFn(directive); } /** * @ngdoc directive * @name a * @restrict E * * @description * Modifies the default behavior of the html a tag so that the default action is prevented when * the href attribute is empty. * * For dynamically creating `href` attributes for a tags, see the {@link ng.ngHref `ngHref`} directive. */ var htmlAnchorDirective = valueFn({ restrict: 'E', compile: function(element, attr) { if (!attr.href && !attr.xlinkHref) { return function(scope, element) { // If the linked element is not an anchor tag anymore, do nothing if (element[0].nodeName.toLowerCase() !== 'a') return; // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 'xlink:href' : 'href'; element.on('click', function(event) { // if we have no href url, then don't navigate anywhere. if (!element.attr(href)) { event.preventDefault(); } }); }; } } }); /** * @ngdoc directive * @name ngHref * @restrict A * @priority 99 * * @description * Using Angular markup like `{{hash}}` in an href attribute will * make the link go to the wrong URL if the user clicks it before * Angular has a chance to replace the `{{hash}}` markup with its * value. Until Angular replaces the markup the link will be broken * and will most likely return a 404 error. The `ngHref` directive * solves this problem. * * The wrong way to write it: * ```html * link1 * ``` * * The correct way to write it: * ```html * link1 * ``` * * @element A * @param {template} ngHref any string which can contain `{{}}` markup. * * @example * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes * in links and their different behaviors:
          link 1 (link, don't reload)
          link 2 (link, don't reload)
          link 3 (link, reload!)
          anchor (link, don't reload)
          anchor (no link)
          link (link, change location)
          it('should execute ng-click but not reload when href without value', function() { element(by.id('link-1')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('1'); expect(element(by.id('link-1')).getAttribute('href')).toBe(''); }); it('should execute ng-click but not reload when href empty string', function() { element(by.id('link-2')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('2'); expect(element(by.id('link-2')).getAttribute('href')).toBe(''); }); it('should execute ng-click and change url when ng-href specified', function() { expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); element(by.id('link-3')).click(); // At this point, we navigate away from an Angular page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/123$/); }); }, 5000, 'page should navigate to /123'); }); it('should execute ng-click but not reload when href empty string and name specified', function() { element(by.id('link-4')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('4'); expect(element(by.id('link-4')).getAttribute('href')).toBe(''); }); it('should execute ng-click but not reload when no href but name specified', function() { element(by.id('link-5')).click(); expect(element(by.model('value')).getAttribute('value')).toEqual('5'); expect(element(by.id('link-5')).getAttribute('href')).toBe(null); }); it('should only change url when only ng-href', function() { element(by.model('value')).clear(); element(by.model('value')).sendKeys('6'); expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); element(by.id('link-6')).click(); // At this point, we navigate away from an Angular page, so we need // to use browser.driver to get the base webdriver. browser.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { return url.match(/\/6$/); }); }, 5000, 'page should navigate to /6'); });
          */ /** * @ngdoc directive * @name ngSrc * @restrict A * @priority 99 * * @description * Using Angular markup like `{{hash}}` in a `src` attribute doesn't * work right: The browser will fetch from the URL with the literal * text `{{hash}}` until Angular replaces the expression inside * `{{hash}}`. The `ngSrc` directive solves this problem. * * The buggy way to write it: * ```html * Description * ``` * * The correct way to write it: * ```html * Description * ``` * * @element IMG * @param {template} ngSrc any string which can contain `{{}}` markup. */ /** * @ngdoc directive * @name ngSrcset * @restrict A * @priority 99 * * @description * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't * work right: The browser will fetch from the URL with the literal * text `{{hash}}` until Angular replaces the expression inside * `{{hash}}`. The `ngSrcset` directive solves this problem. * * The buggy way to write it: * ```html * Description * ``` * * The correct way to write it: * ```html * Description * ``` * * @element IMG * @param {template} ngSrcset any string which can contain `{{}}` markup. */ /** * @ngdoc directive * @name ngDisabled * @restrict A * @priority 100 * * @description * * This directive sets the `disabled` attribute on the element if the * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. * * A special directive is necessary because we cannot use interpolation inside the `disabled` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * @example
          it('should toggle button', function() { expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); element(by.model('checked')).click(); expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); });
          * * @element INPUT * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, * then the `disabled` attribute will be set on the element */ /** * @ngdoc directive * @name ngChecked * @restrict A * @priority 100 * * @description * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. * * Note that this directive should not be used together with {@link ngModel `ngModel`}, * as this can lead to unexpected behavior. * * A special directive is necessary because we cannot use interpolation inside the `checked` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * @example
          it('should check both checkBoxes', function() { expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); element(by.model('master')).click(); expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); });
          * * @element INPUT * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, * then the `checked` attribute will be set on the element */ /** * @ngdoc directive * @name ngReadonly * @restrict A * @priority 100 * * @description * * Sets the `readonly` attribute on the element, if the expression inside `ngReadonly` is truthy. * Note that `readonly` applies only to `input` elements with specific types. [See the input docs on * MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-readonly) for more information. * * A special directive is necessary because we cannot use interpolation inside the `readonly` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * @example
          it('should toggle readonly attr', function() { expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); element(by.model('checked')).click(); expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); });
          * * @element INPUT * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, * then special attribute "readonly" will be set on the element */ /** * @ngdoc directive * @name ngSelected * @restrict A * @priority 100 * * @description * * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. * * A special directive is necessary because we cannot use interpolation inside the `selected` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * *
          * **Note:** `ngSelected` does not interact with the `select` and `ngModel` directives, it only * sets the `selected` attribute on the element. If you are using `ngModel` on the select, you * should not use `ngSelected` on the options, as `ngModel` will set the select value and * selected options. *
          * * @example
          it('should select Greetings!', function() { expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); element(by.model('selected')).click(); expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); });
          * * @element OPTION * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, * then special attribute "selected" will be set on the element */ /** * @ngdoc directive * @name ngOpen * @restrict A * @priority 100 * * @description * * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. * * A special directive is necessary because we cannot use interpolation inside the `open` * attribute. See the {@link guide/interpolation interpolation guide} for more info. * * ## A note about browser compatibility * * Edge, Firefox, and Internet Explorer do not support the `details` element, it is * recommended to use {@link ng.ngShow} and {@link ng.ngHide} instead. * * @example
          Show/Hide me
          it('should toggle open', function() { expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); element(by.model('open')).click(); expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); });
          * * @element DETAILS * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, * then special attribute "open" will be set on the element */ var ngAttributeAliasDirectives = {}; // boolean attrs are evaluated forEach(BOOLEAN_ATTR, function(propName, attrName) { // binding to multiple is not supported if (propName === 'multiple') return; function defaultLinkFn(scope, element, attr) { scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { attr.$set(attrName, !!value); }); } var normalized = directiveNormalize('ng-' + attrName); var linkFn = defaultLinkFn; if (propName === 'checked') { linkFn = function(scope, element, attr) { // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input if (attr.ngModel !== attr[normalized]) { defaultLinkFn(scope, element, attr); } }; } ngAttributeAliasDirectives[normalized] = function() { return { restrict: 'A', priority: 100, link: linkFn }; }; }); // aliased input attrs are evaluated forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { ngAttributeAliasDirectives[ngAttr] = function() { return { priority: 100, link: function(scope, element, attr) { //special case ngPattern when a literal regular expression value //is used as the expression (this way we don't have to watch anything). if (ngAttr === 'ngPattern' && attr.ngPattern.charAt(0) === '/') { var match = attr.ngPattern.match(REGEX_STRING_REGEXP); if (match) { attr.$set('ngPattern', new RegExp(match[1], match[2])); return; } } scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { attr.$set(ngAttr, value); }); } }; }; }); // ng-src, ng-srcset, ng-href are interpolated forEach(['src', 'srcset', 'href'], function(attrName) { var normalized = directiveNormalize('ng-' + attrName); ngAttributeAliasDirectives[normalized] = function() { return { priority: 99, // it needs to run after the attributes are interpolated link: function(scope, element, attr) { var propName = attrName, name = attrName; if (attrName === 'href' && toString.call(element.prop('href')) === '[object SVGAnimatedString]') { name = 'xlinkHref'; attr.$attr[name] = 'xlink:href'; propName = null; } attr.$observe(normalized, function(value) { if (!value) { if (attrName === 'href') { attr.$set(name, null); } return; } attr.$set(name, value); // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need // to set the property as well to achieve the desired effect. // we use attr[attrName] value since $set can sanitize the url. if (msie && propName) element.prop(propName, attr[name]); }); } }; }; }); /* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true */ var nullFormCtrl = { $addControl: noop, $$renameControl: nullFormRenameControl, $removeControl: noop, $setValidity: noop, $setDirty: noop, $setPristine: noop, $setSubmitted: noop }, SUBMITTED_CLASS = 'ng-submitted'; function nullFormRenameControl(control, name) { control.$name = name; } /** * @ngdoc type * @name form.FormController * * @property {boolean} $pristine True if user has not interacted with the form yet. * @property {boolean} $dirty True if user has already interacted with the form. * @property {boolean} $valid True if all of the containing forms and controls are valid. * @property {boolean} $invalid True if at least one containing control or form is invalid. * @property {boolean} $pending True if at least one containing control or form is pending. * @property {boolean} $submitted True if user has submitted the form even if its invalid. * * @property {Object} $error Is an object hash, containing references to controls or * forms with failing validators, where: * * - keys are validation tokens (error names), * - values are arrays of controls or forms that have a failing validator for given error name. * * Built-in validation tokens: * * - `email` * - `max` * - `maxlength` * - `min` * - `minlength` * - `number` * - `pattern` * - `required` * - `url` * - `date` * - `datetimelocal` * - `time` * - `week` * - `month` * * @description * `FormController` keeps track of all its controls and nested forms as well as the state of them, * such as being valid/invalid or dirty/pristine. * * Each {@link ng.directive:form form} directive creates an instance * of `FormController`. * */ //asks for $scope to fool the BC controller module FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; function FormController(element, attrs, $scope, $animate, $interpolate) { var form = this, controls = []; // init state form.$error = {}; form.$$success = {}; form.$pending = undefined; form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); form.$dirty = false; form.$pristine = true; form.$valid = true; form.$invalid = false; form.$submitted = false; form.$$parentForm = nullFormCtrl; /** * @ngdoc method * @name form.FormController#$rollbackViewValue * * @description * Rollback all form controls pending updates to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. This method is typically needed by the reset button of * a form that uses `ng-model-options` to pend updates. */ form.$rollbackViewValue = function() { forEach(controls, function(control) { control.$rollbackViewValue(); }); }; /** * @ngdoc method * @name form.FormController#$commitViewValue * * @description * Commit all form controls pending updates to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ form.$commitViewValue = function() { forEach(controls, function(control) { control.$commitViewValue(); }); }; /** * @ngdoc method * @name form.FormController#$addControl * @param {object} control control object, either a {@link form.FormController} or an * {@link ngModel.NgModelController} * * @description * Register a control with the form. Input elements using ngModelController do this automatically * when they are linked. * * Note that the current state of the control will not be reflected on the new parent form. This * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` * state. * * However, if the method is used programmatically, for example by adding dynamically created controls, * or controls that have been previously removed without destroying their corresponding DOM element, * it's the developers responsibility to make sure the current state propagates to the parent form. * * For example, if an input control is added that is already `$dirty` and has `$error` properties, * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. */ form.$addControl = function(control) { // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored // and not added to the scope. Now we throw an error. assertNotHasOwnProperty(control.$name, 'input'); controls.push(control); if (control.$name) { form[control.$name] = control; } control.$$parentForm = form; }; // Private API: rename a form control form.$$renameControl = function(control, newName) { var oldName = control.$name; if (form[oldName] === control) { delete form[oldName]; } form[newName] = control; control.$name = newName; }; /** * @ngdoc method * @name form.FormController#$removeControl * @param {object} control control object, either a {@link form.FormController} or an * {@link ngModel.NgModelController} * * @description * Deregister a control from the form. * * Input elements using ngModelController do this automatically when they are destroyed. * * Note that only the removed control's validation state (`$errors`etc.) will be removed from the * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be * different from case to case. For example, removing the only `$dirty` control from a form may or * may not mean that the form is still `$dirty`. */ form.$removeControl = function(control) { if (control.$name && form[control.$name] === control) { delete form[control.$name]; } forEach(form.$pending, function(value, name) { form.$setValidity(name, null, control); }); forEach(form.$error, function(value, name) { form.$setValidity(name, null, control); }); forEach(form.$$success, function(value, name) { form.$setValidity(name, null, control); }); arrayRemove(controls, control); control.$$parentForm = nullFormCtrl; }; /** * @ngdoc method * @name form.FormController#$setValidity * * @description * Sets the validity of a form control. * * This method will also propagate to parent forms. */ addSetValidityMethod({ ctrl: this, $element: element, set: function(object, property, controller) { var list = object[property]; if (!list) { object[property] = [controller]; } else { var index = list.indexOf(controller); if (index === -1) { list.push(controller); } } }, unset: function(object, property, controller) { var list = object[property]; if (!list) { return; } arrayRemove(list, controller); if (list.length === 0) { delete object[property]; } }, $animate: $animate }); /** * @ngdoc method * @name form.FormController#$setDirty * * @description * Sets the form to a dirty state. * * This method can be called to add the 'ng-dirty' class and set the form to a dirty * state (ng-dirty class). This method will also propagate to parent forms. */ form.$setDirty = function() { $animate.removeClass(element, PRISTINE_CLASS); $animate.addClass(element, DIRTY_CLASS); form.$dirty = true; form.$pristine = false; form.$$parentForm.$setDirty(); }; /** * @ngdoc method * @name form.FormController#$setPristine * * @description * Sets the form to its pristine state. * * This method sets the form's `$pristine` state to true, the `$dirty` state to false, removes * the `ng-dirty` class and adds the `ng-pristine` class. Additionally, it sets the `$submitted` * state to false. * * This method will also propagate to all the controls contained in this form. * * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after * saving or resetting it. */ form.$setPristine = function() { $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); form.$dirty = false; form.$pristine = true; form.$submitted = false; forEach(controls, function(control) { control.$setPristine(); }); }; /** * @ngdoc method * @name form.FormController#$setUntouched * * @description * Sets the form to its untouched state. * * This method can be called to remove the 'ng-touched' class and set the form controls to their * untouched state (ng-untouched class). * * Setting a form controls back to their untouched state is often useful when setting the form * back to its pristine state. */ form.$setUntouched = function() { forEach(controls, function(control) { control.$setUntouched(); }); }; /** * @ngdoc method * @name form.FormController#$setSubmitted * * @description * Sets the form to its submitted state. */ form.$setSubmitted = function() { $animate.addClass(element, SUBMITTED_CLASS); form.$submitted = true; form.$$parentForm.$setSubmitted(); }; } /** * @ngdoc directive * @name ngForm * @restrict EAC * * @description * Nestable alias of {@link ng.directive:form `form`} directive. HTML * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a * sub-group of controls needs to be determined. * * Note: the purpose of `ngForm` is to group controls, * but not to be a replacement for the `
          ` tag with all of its capabilities * (e.g. posting to the server, ...). * * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into * related scope, under this name. * */ /** * @ngdoc directive * @name form * @restrict E * * @description * Directive that instantiates * {@link form.FormController FormController}. * * If the `name` attribute is specified, the form controller is published onto the current scope under * this name. * * # Alias: {@link ng.directive:ngForm `ngForm`} * * In Angular, forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `` elements, so * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group * of controls needs to be determined. * * # CSS classes * - `ng-valid` is set if the form is valid. * - `ng-invalid` is set if the form is invalid. * - `ng-pending` is set if the form is pending. * - `ng-pristine` is set if the form is pristine. * - `ng-dirty` is set if the form is dirty. * - `ng-submitted` is set if the form was submitted. * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * * # Submitting a form and preventing the default action * * Since the role of forms in client-side Angular applications is different than in classical * roundtrip apps, it is desirable for the browser not to translate the form submission into a full * page reload that sends the data to the server. Instead some javascript logic should be triggered * to handle the form submission in an application-specific way. * * For this reason, Angular prevents the default action (form submission to the server) unless the * `` element has an `action` attribute specified. * * You can use one of the following two ways to specify what javascript method should be called when * a form is submitted: * * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element * - {@link ng.directive:ngClick ngClick} directive on the first * button or input field of type submit (input[type=submit]) * * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} * or {@link ng.directive:ngClick ngClick} directives. * This is because of the following form submission rules in the HTML specification: * * - If a form has only one input field then hitting enter in this field triggers form submit * (`ngSubmit`) * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter * doesn't trigger submit * - if a form has one or more input fields and one or more buttons or input[type=submit] then * hitting enter in any of the input fields will trigger the click handler on the *first* button or * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) * * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * * ## Animation Hooks * * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any * other validations that are performed within the form. Animations in ngForm are similar to how * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well * as JS animations. * * The following example shows a simple way to utilize CSS transitions to style a form element * that has been rendered as invalid after it has been validated: * *
           * //be sure to include ngAnimate as a module to hook into more
           * //advanced animations
           * .my-form {
           *   transition:0.5s linear all;
           *   background: white;
           * }
           * .my-form.ng-invalid {
           *   background: red;
           *   color:white;
           * }
           * 
          * * @example userType: Required!
          userType = {{userType}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          it('should initialize to model', function() { var userType = element(by.binding('userType')); var valid = element(by.binding('myForm.input.$valid')); expect(userType.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { var userType = element(by.binding('userType')); var valid = element(by.binding('myForm.input.$valid')); var userInput = element(by.model('userType')); userInput.clear(); userInput.sendKeys(''); expect(userType.getText()).toEqual('userType ='); expect(valid.getText()).toContain('false'); });
          * * @param {string=} name Name of the form. If specified, the form controller will be published into * related scope, under this name. */ var formDirectiveFactory = function(isNgForm) { return ['$timeout', '$parse', function($timeout, $parse) { var formDirective = { name: 'form', restrict: isNgForm ? 'EAC' : 'E', require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form controller: FormController, compile: function ngFormCompile(formElement, attr) { // Setup initial state of the control formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); return { pre: function ngFormPreLink(scope, formElement, attr, ctrls) { var controller = ctrls[0]; // if `action` attr is not present on the form, prevent the default action (submission) if (!('action' in attr)) { // we can't use jq events because if a form is destroyed during submission the default // action is not prevented. see #1238 // // IE 9 is not affected because it doesn't fire a submit event and try to do a full // page reload if the form was destroyed by submission of the form via a click handler // on a button in the form. Looks like an IE9 specific bug. var handleFormSubmission = function(event) { scope.$apply(function() { controller.$commitViewValue(); controller.$setSubmitted(); }); event.preventDefault(); }; addEventListenerFn(formElement[0], 'submit', handleFormSubmission); // unregister the preventDefault listener so that we don't not leak memory but in a // way that will achieve the prevention of the default action. formElement.on('$destroy', function() { $timeout(function() { removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); }, 0, false); }); } var parentFormCtrl = ctrls[1] || controller.$$parentForm; parentFormCtrl.$addControl(controller); var setter = nameAttr ? getSetter(controller.$name) : noop; if (nameAttr) { setter(scope, controller); attr.$observe(nameAttr, function(newValue) { if (controller.$name === newValue) return; setter(scope, undefined); controller.$$parentForm.$$renameControl(controller, newValue); setter = getSetter(controller.$name); setter(scope, controller); }); } formElement.on('$destroy', function() { controller.$$parentForm.$removeControl(controller); setter(scope, undefined); extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards }); } }; } }; return formDirective; function getSetter(expression) { if (expression === '') { //create an assignable expression, so forms with an empty name can be renamed later return $parse('this[""]').assign; } return $parse(expression).assign || noop; } }]; }; var formDirective = formDirectiveFactory(); var ngFormDirective = formDirectiveFactory(true); /* global VALID_CLASS: false, INVALID_CLASS: false, PRISTINE_CLASS: false, DIRTY_CLASS: false, ngModelMinErr: false */ // Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/; // See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) // Note: We are being more lenient, because browsers are too. // 1. Scheme // 2. Slashes // 3. Username // 4. Password // 5. Hostname // 6. Port // 7. Path // 8. Query // 9. Fragment // 1111111111111111 222 333333 44444 55555555555555555555555 666 77777777 8888888 999 var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; // eslint-disable-next-line max-len var EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/; var NUMBER_REGEXP = /^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; var PARTIAL_VALIDATION_TYPES = createMap(); forEach('date,datetime-local,month,time,week'.split(','), function(type) { PARTIAL_VALIDATION_TYPES[type] = true; }); var inputType = { /** * @ngdoc input * @name input[text] * * @description * Standard HTML text input with angular data binding, inherited by most of the `input` elements. * * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Adds `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
          * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. * This parameter is ignored for input[type=password] controls, which will never trim the * input. * * @example
          Required! Single word only!
          text = {{example.text}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var text = element(by.binding('example.text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('example.text')); it('should initialize to model', function() { expect(text.getText()).toContain('guest'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if multi word', function() { input.clear(); input.sendKeys('hello world'); expect(valid.getText()).toContain('false'); });
          */ 'text': textInputType, /** * @ngdoc input * @name input[date] * * @description * Input with date validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many * modern browsers do not yet support this input type, it is important to provide cues to users on the * expected input format via a placeholder or label. * * The model must always be a Date object, otherwise Angular will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 * constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 * constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not a valid date!
          value = {{example.value | date: "yyyy-MM-dd"}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (see https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-10-22'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01-01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
          */ 'date': createDateInputType('date', DATE_REGEXP, createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 'yyyy-MM-dd'), /** * @ngdoc input * @name input[datetime-local] * * @description * Input with datetime validation and transformation. In browsers that do not yet support * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. * * The model must always be a Date object, otherwise Angular will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). * Note that `min` will also add native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). * Note that `max` will also add native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not a valid date!
          value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2010-12-28T14:57:00'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01-01T23:59:00'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
          */ 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 'yyyy-MM-ddTHH:mm:ss.sss'), /** * @ngdoc input * @name input[time] * * @description * Input with time validation and transformation. In browsers that do not yet support * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. * * The model must always be a Date object, otherwise Angular will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add * native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add * native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not a valid date!
          value = {{example.value | date: "HH:mm:ss"}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var value = element(by.binding('example.value | date: "HH:mm:ss"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('14:57:00'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('23:59:00'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
          */ 'time': createDateInputType('time', TIME_REGEXP, createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 'HH:mm:ss.sss'), /** * @ngdoc input * @name input[week] * * @description * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * week format (yyyy-W##), for example: `2013-W02`. * * The model must always be a Date object, otherwise Angular will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add * native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add * native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not a valid date!
          value = {{example.value | date: "yyyy-Www"}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var value = element(by.binding('example.value | date: "yyyy-Www"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-W01'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-W01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
          */ 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), /** * @ngdoc input * @name input[month] * * @description * Input with month validation and transformation. In browsers that do not yet support * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 * month format (yyyy-MM), for example: `2009-01`. * * The model must always be a Date object, otherwise Angular will throw an error. * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. * If the model is not set to the first of the month, the next view to model update will set it * to the first of the month. * * The timezone to be used to read/write the `Date` instance in the model can be defined using * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add * native HTML5 constraint validation. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add * native HTML5 constraint validation. * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not a valid month!
          value = {{example.value | date: "yyyy-MM"}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var value = element(by.binding('example.value | date: "yyyy-MM"')); var valid = element(by.binding('myForm.input.$valid')); // currently protractor/webdriver does not support // sending keys to all known HTML5 input controls // for various browsers (https://github.com/angular/protractor/issues/562). function setInput(val) { // set the value of the element and force validation. var scr = "var ipt = document.getElementById('exampleInput'); " + "ipt.value = '" + val + "';" + "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; browser.executeScript(scr); } it('should initialize to model', function() { expect(value.getText()).toContain('2013-10'); expect(valid.getText()).toContain('myForm.input.$valid = true'); }); it('should be invalid if empty', function() { setInput(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('myForm.input.$valid = false'); }); it('should be invalid if over max', function() { setInput('2015-01'); expect(value.getText()).toContain(''); expect(valid.getText()).toContain('myForm.input.$valid = false'); });
          */ 'month': createDateInputType('month', MONTH_REGEXP, createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 'yyyy-MM'), /** * @ngdoc input * @name input[number] * * @description * Text input with number validation and transformation. Sets the `number` validation * error if not a valid number. * *
          * The model must always be of type `number` otherwise Angular will throw an error. * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} * error docs for more information and an example of how to convert your model if necessary. *
          * * ## Issues with HTML5 constraint validation * * In browsers that follow the * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. * If a non-number is entered in the input, the browser will report the value as an empty string, * which means the view / model values in `ngModel` and subsequently the scope value * will also be an empty string. * * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
          * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not valid number!
          value = {{example.value}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          var value = element(by.binding('example.value')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('example.value')); it('should initialize to model', function() { expect(value.getText()).toContain('12'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if over max', function() { input.clear(); input.sendKeys('123'); expect(value.getText()).toEqual('value ='); expect(valid.getText()).toContain('false'); });
          */ 'number': numberInputType, /** * @ngdoc input * @name input[url] * * @description * Text input with URL validation. Sets the `url` validation error key if the content is not a * valid URL. * *
          * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify * the built-in validators (see the {@link guide/forms Forms guide}) *
          * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
          * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          var text = element(by.binding('url.text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('url.text')); it('should initialize to model', function() { expect(text.getText()).toContain('http://google.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not url', function() { input.clear(); input.sendKeys('box'); expect(valid.getText()).toContain('false'); });
          */ 'url': urlInputType, /** * @ngdoc input * @name input[email] * * @description * Text input with email validation. Sets the `email` validation error key if not a valid email * address. * *
          * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) *
          * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of * any length. * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string * that contains the regular expression body that will be converted to a regular expression * as in the ngPattern directive. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
          * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example
          Required! Not valid email!
          text = {{email.text}}
          myForm.input.$valid = {{myForm.input.$valid}}
          myForm.input.$error = {{myForm.input.$error}}
          myForm.$valid = {{myForm.$valid}}
          myForm.$error.required = {{!!myForm.$error.required}}
          myForm.$error.email = {{!!myForm.$error.email}}
          var text = element(by.binding('email.text')); var valid = element(by.binding('myForm.input.$valid')); var input = element(by.model('email.text')); it('should initialize to model', function() { expect(text.getText()).toContain('me@example.com'); expect(valid.getText()).toContain('true'); }); it('should be invalid if empty', function() { input.clear(); input.sendKeys(''); expect(text.getText()).toEqual('text ='); expect(valid.getText()).toContain('false'); }); it('should be invalid if not email', function() { input.clear(); input.sendKeys('xxx'); expect(valid.getText()).toContain('false'); });
          */ 'email': emailInputType, /** * @ngdoc input * @name input[radio] * * @description * HTML radio button. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string} value The value to which the `ngModel` expression should be set when selected. * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, * too. Use `ngValue` if you need complex models (`number`, `object`, ...). * @param {string=} name Property name of the form under which the control is published. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio * is selected. Should be used instead of the `value` attribute if you need * a non-string `ngModel` (`boolean`, `array`, ...). * * @example



          color = {{color.name | json}}
          Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
          it('should change state', function() { var inputs = element.all(by.model('color.name')); var color = element(by.binding('color.name')); expect(color.getText()).toContain('blue'); inputs.get(0).click(); expect(color.getText()).toContain('red'); inputs.get(1).click(); expect(color.getText()).toContain('green'); });
          */ 'radio': radioInputType, /** * @ngdoc input * @name input[range] * * @description * Native range input with validation and transformation. * *
          *

          * In v1.5.9+, in order to avoid interfering with already existing, custom directives for * `input[range]`, you need to let Angular know that you want to enable its built-in support. * You can do this by adding the `ng-input-range` attribute to the input element. E.g.: * `` *


          *

          * Input elements without the `ng-input-range` attibute will continue to be treated the same * as in previous versions (e.g. their model value will be a string not a number and Angular * will not take `min`/`max`/`step` attributes and properties into account). *


          *

          * **Note:** From v1.6.x onwards, the support for `input[range]` will be always enabled and * the `ng-input-range` attribute will have no effect. *


          *

          * This documentation page refers to elements which have the built-in support enabled; i.e. * elements _with_ the `ng-input-range` attribute. *

          *
          * * The model for the range input must always be a `Number`. * * IE9 and other browsers that do not support the `range` type fall back * to a text input without any default values for `min`, `max` and `step`. Model binding, * validation and number parsing are nevertheless supported. * * Browsers that support range (latest Chrome, Safari, Firefox, Edge) treat `input[range]` * in a way that never allows the input to hold an invalid value. That means: * - any non-numerical value is set to `(max + min) / 2`. * - any numerical value that is less than the current min val, or greater than the current max val * is set to the min / max val respectively. * - additionally, the current `step` is respected, so the nearest value that satisfies a step * is used. * * See the [HTML Spec on input[type=range]](https://www.w3.org/TR/html5/forms.html#range-state-(type=range)) * for more info. * * This has the following consequences for Angular: * * Since the element value should always reflect the current model value, a range input * will set the bound ngModel expression to the value that the browser has set for the * input element. For example, in the following input ``, * if the application sets `model.value = null`, the browser will set the input to `'50'`. * Angular will then set the model to `50`, to prevent input and model value being out of sync. * * That means the model for range will immediately be set to `50` after `ngModel` has been * initialized. It also means a range input can never have the required error. * * This does not only affect changes to the model value, but also to the values of the `min`, * `max`, and `step` attributes. When these change in a way that will cause the browser to modify * the input value, Angular will also update the model value. * * Automatic value adjustment also means that a range input element can never have the `required`, * `min`, or `max` errors. * * However, `step` is currently only fully implemented by Firefox. Other browsers have problems * when the step value changes dynamically - they do not adjust the element value correctly, but * instead may set the `stepMismatch` error. If that's the case, the Angular will set the `step` * error on the input, and set the model to `undefined`. * * Note that `input[range]` is not compatible with `ngMax`, `ngMin`, and `ngStep`, because they do * not set the `min` and `max` attributes, which means that the browser won't automatically adjust * the input value based on their values, and will always assume min = 0, max = 100, and step = 1. * * @param ngInputRange The presense of this attribute enables the built-in support for * `input[range]`. * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} min Sets the `min` validation to ensure that the value entered is greater * than `min`. Can be interpolated. * @param {string=} max Sets the `max` validation to ensure that the value entered is less than `max`. * Can be interpolated. * @param {string=} step Sets the `step` validation to ensure that the value entered matches the `step` * Can be interpolated. * @param {string=} ngChange Angular expression to be executed when the ngModel value changes due * to user interaction with the input element. * @param {expression=} ngChecked If the expression is truthy, then the `checked` attribute will be set on the * element. **Note** : `ngChecked` should not be used alongside `ngModel`. * Checkout {@link ng.directive:ngChecked ngChecked} for usage. * * @example
          Model as range:
          Model as number:
          Min:
          Max:
          value = {{value}}
          myForm.range.$valid = {{myForm.range.$valid}}
          myForm.range.$error = {{myForm.range.$error}}
          * ## Range Input with ngMin & ngMax attributes * @example
          Model as range:
          Model as number:
          Min:
          Max:
          value = {{value}}
          myForm.range.$valid = {{myForm.range.$valid}}
          myForm.range.$error = {{myForm.range.$error}}
          */ 'range': rangeInputType, /** * @ngdoc input * @name input[checkbox] * * @description * HTML checkbox. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {expression=} ngTrueValue The value to which the expression should be set when selected. * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * * @example


          value1 = {{checkboxModel.value1}}
          value2 = {{checkboxModel.value2}}
          it('should change state', function() { var value1 = element(by.binding('checkboxModel.value1')); var value2 = element(by.binding('checkboxModel.value2')); expect(value1.getText()).toContain('true'); expect(value2.getText()).toContain('YES'); element(by.model('checkboxModel.value1')).click(); element(by.model('checkboxModel.value2')).click(); expect(value1.getText()).toContain('false'); expect(value2.getText()).toContain('NO'); });
          */ 'checkbox': checkboxInputType, 'hidden': noop, 'button': noop, 'submit': noop, 'reset': noop, 'file': noop }; function stringBasedInputType(ctrl) { ctrl.$formatters.push(function(value) { return ctrl.$isEmpty(value) ? value : value.toString(); }); } function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); } function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { var type = lowercase(element[0].type); // In composition mode, users are still inputting intermediate text buffer, // hold the listener until composition is done. // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent if (!$sniffer.android) { var composing = false; element.on('compositionstart', function() { composing = true; }); element.on('compositionend', function() { composing = false; listener(); }); } var timeout; var listener = function(ev) { if (timeout) { $browser.defer.cancel(timeout); timeout = null; } if (composing) return; var value = element.val(), event = ev && ev.type; // By default we will trim the value // If the attribute ng-trim exists we will avoid trimming // If input type is 'password', the value is never trimmed if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { value = trim(value); } // If a control is suffering from bad input (due to native validators), browsers discard its // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the // control's value is the same empty value twice in a row. if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { ctrl.$setViewValue(value, event); } }; // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the // input event on backspace, delete or cut if ($sniffer.hasEvent('input')) { element.on('input', listener); } else { var deferListener = function(ev, input, origValue) { if (!timeout) { timeout = $browser.defer(function() { timeout = null; if (!input || input.value !== origValue) { listener(ev); } }); } }; element.on('keydown', /** @this */ function(event) { var key = event.keyCode; // ignore // command modifiers arrows if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; deferListener(event, this, this.value); }); // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it if ($sniffer.hasEvent('paste')) { element.on('paste cut', deferListener); } } // if user paste into input using mouse on older browser // or form autocomplete on newer browser, we need "change" event to catch it element.on('change', listener); // Some native input types (date-family) have the ability to change validity without // firing any input/change events. // For these event types, when native validators are present and the browser supports the type, // check for validity changes on various DOM events. if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { if (!timeout) { var validity = this[VALIDITY_STATE_PROPERTY]; var origBadInput = validity.badInput; var origTypeMismatch = validity.typeMismatch; timeout = $browser.defer(function() { timeout = null; if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { listener(ev); } }); } }); } ctrl.$render = function() { // Workaround for Firefox validation #12102. var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; if (element.val() !== value) { element.val(value); } }; } function weekParser(isoWeek, existingDate) { if (isDate(isoWeek)) { return isoWeek; } if (isString(isoWeek)) { WEEK_REGEXP.lastIndex = 0; var parts = WEEK_REGEXP.exec(isoWeek); if (parts) { var year = +parts[1], week = +parts[2], hours = 0, minutes = 0, seconds = 0, milliseconds = 0, firstThurs = getFirstThursdayOfYear(year), addDays = (week - 1) * 7; if (existingDate) { hours = existingDate.getHours(); minutes = existingDate.getMinutes(); seconds = existingDate.getSeconds(); milliseconds = existingDate.getMilliseconds(); } return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); } } return NaN; } function createDateParser(regexp, mapping) { return function(iso, date) { var parts, map; if (isDate(iso)) { return iso; } if (isString(iso)) { // When a date is JSON'ified to wraps itself inside of an extra // set of double quotes. This makes the date parsing code unable // to match the date string and parse it as a date. if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { iso = iso.substring(1, iso.length - 1); } if (ISO_DATE_REGEXP.test(iso)) { return new Date(iso); } regexp.lastIndex = 0; parts = regexp.exec(iso); if (parts) { parts.shift(); if (date) { map = { yyyy: date.getFullYear(), MM: date.getMonth() + 1, dd: date.getDate(), HH: date.getHours(), mm: date.getMinutes(), ss: date.getSeconds(), sss: date.getMilliseconds() / 1000 }; } else { map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; } forEach(parts, function(part, index) { if (index < mapping.length) { map[mapping[index]] = +part; } }); return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); } } return NaN; }; } function createDateInputType(type, regexp, parseDate, format) { return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { badInputChecker(scope, element, attr, ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; var previousDate; ctrl.$$parserName = type; ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; if (regexp.test(value)) { // Note: We cannot read ctrl.$modelValue, as there might be a different // parser/formatter in the processing chain so that the model // contains some different data format! var parsedDate = parseDate(value, previousDate); if (timezone) { parsedDate = convertTimezoneToLocal(parsedDate, timezone); } return parsedDate; } return undefined; }); ctrl.$formatters.push(function(value) { if (value && !isDate(value)) { throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); } if (isValidDate(value)) { previousDate = value; if (previousDate && timezone) { previousDate = convertTimezoneToLocal(previousDate, timezone, true); } return $filter('date')(value, format, timezone); } else { previousDate = null; return ''; } }); if (isDefined(attr.min) || attr.ngMin) { var minVal; ctrl.$validators.min = function(value) { return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; }; attr.$observe('min', function(val) { minVal = parseObservedDateValue(val); ctrl.$validate(); }); } if (isDefined(attr.max) || attr.ngMax) { var maxVal; ctrl.$validators.max = function(value) { return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; }; attr.$observe('max', function(val) { maxVal = parseObservedDateValue(val); ctrl.$validate(); }); } function isValidDate(value) { // Invalid Date: getTime() returns NaN return value && !(value.getTime && value.getTime() !== value.getTime()); } function parseObservedDateValue(val) { return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; } }; } function badInputChecker(scope, element, attr, ctrl) { var node = element[0]; var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); if (nativeValidation) { ctrl.$parsers.push(function(value) { var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; return validity.badInput || validity.typeMismatch ? undefined : value; }); } } function numberFormatterParser(ctrl) { ctrl.$$parserName = 'number'; ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; if (NUMBER_REGEXP.test(value)) return parseFloat(value); return undefined; }); ctrl.$formatters.push(function(value) { if (!ctrl.$isEmpty(value)) { if (!isNumber(value)) { throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); } value = value.toString(); } return value; }); } function parseNumberAttrVal(val) { if (isDefined(val) && !isNumber(val)) { val = parseFloat(val); } return !isNumberNaN(val) ? val : undefined; } function isNumberInteger(num) { // See http://stackoverflow.com/questions/14636536/how-to-check-if-a-variable-is-an-integer-in-javascript#14794066 // (minus the assumption that `num` is a number) // eslint-disable-next-line no-bitwise return (num | 0) === num; } function countDecimals(num) { var numString = num.toString(); var decimalSymbolIndex = numString.indexOf('.'); if (decimalSymbolIndex === -1) { if (-1 < num && num < 1) { // It may be in the exponential notation format (`1e-X`) var match = /e-(\d+)$/.exec(numString); if (match) { return Number(match[1]); } } return 0; } return numString.length - decimalSymbolIndex - 1; } function isValidForStep(viewValue, stepBase, step) { // At this point `stepBase` and `step` are expected to be non-NaN values // and `viewValue` is expected to be a valid stringified number. var value = Number(viewValue); // Due to limitations in Floating Point Arithmetic (e.g. `0.3 - 0.2 !== 0.1` or // `0.5 % 0.1 !== 0`), we need to convert all numbers to integers. if (!isNumberInteger(value) || !isNumberInteger(stepBase) || !isNumberInteger(step)) { var decimalCount = Math.max(countDecimals(value), countDecimals(stepBase), countDecimals(step)); var multiplier = Math.pow(10, decimalCount); value = value * multiplier; stepBase = stepBase * multiplier; step = step * multiplier; } return (value - stepBase) % step === 0; } function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { badInputChecker(scope, element, attr, ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); numberFormatterParser(ctrl); var minVal; var maxVal; if (isDefined(attr.min) || attr.ngMin) { ctrl.$validators.min = function(value) { return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; }; attr.$observe('min', function(val) { minVal = parseNumberAttrVal(val); // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); }); } if (isDefined(attr.max) || attr.ngMax) { ctrl.$validators.max = function(value) { return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; }; attr.$observe('max', function(val) { maxVal = parseNumberAttrVal(val); // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); }); } } function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { badInputChecker(scope, element, attr, ctrl); numberFormatterParser(ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', minVal = supportsRange ? 0 : undefined, maxVal = supportsRange ? 100 : undefined, stepVal = supportsRange ? 1 : undefined, validity = element[0].validity, hasMinAttr = isDefined(attr.min), hasMaxAttr = isDefined(attr.max), hasStepAttr = isDefined(attr.step); var originalRender = ctrl.$render; ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? //Browsers that implement range will set these values automatically, but reading the adjusted values after //$render would cause the min / max validators to be applied with the wrong value function rangeRender() { originalRender(); ctrl.$setViewValue(element.val()); } : originalRender; if (hasMinAttr) { ctrl.$validators.min = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMinValidator() { return true; } : // non-support browsers validate the min val function minValidator(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; }; setInitialValueAndObserver('min', minChange); } if (hasMaxAttr) { ctrl.$validators.max = supportsRange ? // Since all browsers set the input to a valid value, we don't need to check validity function noopMaxValidator() { return true; } : // non-support browsers validate the max val function maxValidator(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; }; setInitialValueAndObserver('max', maxChange); } if (hasStepAttr) { ctrl.$validators.step = supportsRange ? function nativeStepValidator() { // Currently, only FF implements the spec on step change correctly (i.e. adjusting the // input element value to a valid value). It's possible that other browsers set the stepMismatch // validity error instead, so we can at least report an error in that case. return !validity.stepMismatch; } : // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would function stepValidator(modelValue, viewValue) { return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || isValidForStep(viewValue, minVal || 0, stepVal); }; setInitialValueAndObserver('step', stepChange); } function setInitialValueAndObserver(htmlAttrName, changeFn) { // interpolated attributes set the attribute value only after a digest, but we need the // attribute value when the input is first rendered, so that the browser can adjust the // input value based on the min/max value element.attr(htmlAttrName, attr[htmlAttrName]); attr.$observe(htmlAttrName, changeFn); } function minChange(val) { minVal = parseNumberAttrVal(val); // ignore changes before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } if (supportsRange) { var elVal = element.val(); // IE11 doesn't set the el val correctly if the minVal is greater than the element value if (minVal > elVal) { elVal = minVal; element.val(elVal); } ctrl.$setViewValue(elVal); } else { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } } function maxChange(val) { maxVal = parseNumberAttrVal(val); // ignore changes before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } if (supportsRange) { var elVal = element.val(); // IE11 doesn't set the el val correctly if the maxVal is less than the element value if (maxVal < elVal) { element.val(maxVal); // IE11 and Chrome don't set the value to the minVal when max < min elVal = maxVal < minVal ? minVal : maxVal; } ctrl.$setViewValue(elVal); } else { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } } function stepChange(val) { stepVal = parseNumberAttrVal(val); // ignore changes before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } // Some browsers don't adjust the input value correctly, but set the stepMismatch error if (supportsRange && ctrl.$viewValue !== element.val()) { ctrl.$setViewValue(element.val()); } else { // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); } } } function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { // Note: no badInputChecker here by purpose as `url` is only a validation // in browsers, i.e. we can always read out input.value even if it is not valid! baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); ctrl.$$parserName = 'url'; ctrl.$validators.url = function(modelValue, viewValue) { var value = modelValue || viewValue; return ctrl.$isEmpty(value) || URL_REGEXP.test(value); }; } function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { // Note: no badInputChecker here by purpose as `url` is only a validation // in browsers, i.e. we can always read out input.value even if it is not valid! baseInputType(scope, element, attr, ctrl, $sniffer, $browser); stringBasedInputType(ctrl); ctrl.$$parserName = 'email'; ctrl.$validators.email = function(modelValue, viewValue) { var value = modelValue || viewValue; return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); }; } function radioInputType(scope, element, attr, ctrl) { // make the name unique, if not defined if (isUndefined(attr.name)) { element.attr('name', nextUid()); } var listener = function(ev) { if (element[0].checked) { ctrl.$setViewValue(attr.value, ev && ev.type); } }; element.on('click', listener); ctrl.$render = function() { var value = attr.value; // We generally use strict comparison. This is behavior we cannot change without a BC. // eslint-disable-next-line eqeqeq element[0].checked = (value == ctrl.$viewValue); }; attr.$observe('value', ctrl.$render); } function parseConstantExpr($parse, context, name, expression, fallback) { var parseFn; if (isDefined(expression)) { parseFn = $parse(expression); if (!parseFn.constant) { throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + '`{1}`.', name, expression); } return parseFn(context); } return fallback; } function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); var listener = function(ev) { ctrl.$setViewValue(element[0].checked, ev && ev.type); }; element.on('click', listener); ctrl.$render = function() { element[0].checked = ctrl.$viewValue; }; // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert // it to a boolean. ctrl.$isEmpty = function(value) { return value === false; }; ctrl.$formatters.push(function(value) { return equals(value, trueValue); }); ctrl.$parsers.push(function(value) { return value ? trueValue : falseValue; }); } /** * @ngdoc directive * @name textarea * @restrict E * * @description * HTML textarea element control with angular data-binding. The data-binding and validation * properties of this element are exactly the same as those of the * {@link ng.directive:input input element}. * * @param {string} ngModel Assignable angular expression to data-bind to. * @param {string=} name Property name of the form under which the control is published. * @param {string=} required Sets `required` validation error key if the value is not entered. * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of * `required` when you want to data-bind to the `required` attribute. * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * minlength. * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any * length. * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} * does not match a RegExp found by evaluating the Angular expression given in the attribute value. * If the expression evaluates to a RegExp object, then this is used directly. * If the expression evaluates to a string, then it will be converted to a RegExp * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to * `new RegExp('^abc$')`.
          * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to * start at the index of the last search's match, thus not taking the whole input value into * account. * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. * * @knownIssue * * When specifying the `placeholder` attribute of ` *
          {{ list | json }}
          * * * it("should split the text by newlines", function() { * var listInput = element(by.model('list')); * var output = element(by.binding('list | json')); * listInput.sendKeys('abc\ndef\nghi'); * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); * }); * * * * @element input * @param {string=} ngList optional delimiter that should be used to split the value. */ var ngListDirective = function() { return { restrict: 'A', priority: 100, require: 'ngModel', link: function(scope, element, attr, ctrl) { // We want to control whitespace trimming so we use this convoluted approach // to access the ngList attribute, which doesn't pre-trim the attribute var ngList = element.attr(attr.$attr.ngList) || ', '; var trimValues = attr.ngTrim !== 'false'; var separator = trimValues ? trim(ngList) : ngList; var parse = function(viewValue) { // If the viewValue is invalid (say required but empty) it will be `undefined` if (isUndefined(viewValue)) return; var list = []; if (viewValue) { forEach(viewValue.split(separator), function(value) { if (value) list.push(trimValues ? trim(value) : value); }); } return list; }; ctrl.$parsers.push(parse); ctrl.$formatters.push(function(value) { if (isArray(value)) { return value.join(ngList); } return undefined; }); // Override the standard $isEmpty because an empty array means the input is empty. ctrl.$isEmpty = function(value) { return !value || !value.length; }; } }; }; /* global VALID_CLASS: true, INVALID_CLASS: true, PRISTINE_CLASS: true, DIRTY_CLASS: true, UNTOUCHED_CLASS: true, TOUCHED_CLASS: true */ var VALID_CLASS = 'ng-valid', INVALID_CLASS = 'ng-invalid', PRISTINE_CLASS = 'ng-pristine', DIRTY_CLASS = 'ng-dirty', UNTOUCHED_CLASS = 'ng-untouched', TOUCHED_CLASS = 'ng-touched', PENDING_CLASS = 'ng-pending', EMPTY_CLASS = 'ng-empty', NOT_EMPTY_CLASS = 'ng-not-empty'; var ngModelMinErr = minErr('ngModel'); /** * @ngdoc type * @name ngModel.NgModelController * * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue * is set. * @property {*} $modelValue The value in the model that the control is bound to. * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever the control reads value from the DOM. The functions are called in array order, each passing its return value through to the next. The last return value is forwarded to the {@link ngModel.NgModelController#$validators `$validators`} collection. Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue `$viewValue`}. Returning `undefined` from a parser means a parse error occurred. In that case, no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is set to `true`. The parse error is stored in `ngModel.$error.parse`. * * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever the model value changes. The functions are called in reverse array order, each passing the value through to the next. The last return value is used as the actual DOM value. Used to format / convert values for display in the control. * ```js * function formatter(value) { * if (value) { * return value.toUpperCase(); * } * } * ngModel.$formatters.push(formatter); * ``` * * @property {Object.} $validators A collection of validators that are applied * whenever the model value changes. The key value within the object refers to the name of the * validator while the function refers to the validation operation. The validation operation is * provided with the model value as an argument and must return a true or false value depending * on the response of that validation. * * ```js * ngModel.$validators.validCharacters = function(modelValue, viewValue) { * var value = modelValue || viewValue; * return /[0-9]+/.test(value) && * /[a-z]+/.test(value) && * /[A-Z]+/.test(value) && * /\W+/.test(value); * }; * ``` * * @property {Object.} $asyncValidators A collection of validations that are expected to * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided * is expected to return a promise when it is run during the model validation process. Once the promise * is delivered then the validation status will be set to true when fulfilled and false when rejected. * When the asynchronous validators are triggered, each of the validators will run in parallel and the model * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators * will only run once all synchronous validators have passed. * * Please note that if $http is used then it is important that the server returns a success HTTP response code * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. * * ```js * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { * var value = modelValue || viewValue; * * // Lookup user by username * return $http.get('/api/users/' + value). * then(function resolved() { * //username exists, this means validation fails * return $q.reject('exists'); * }, function rejected() { * //username does not exist, therefore this validation passes * return true; * }); * }; * ``` * * @property {Array.} $viewChangeListeners Array of functions to execute whenever the * view value has changed. It is called with no arguments, and its return value is ignored. * This can be used in place of additional $watches against the model value. * * @property {Object} $error An object hash with all failing validator ids as keys. * @property {Object} $pending An object hash with all pending validator ids as keys. * * @property {boolean} $untouched True if control has not lost focus yet. * @property {boolean} $touched True if control has lost focus. * @property {boolean} $pristine True if user has not interacted with the control yet. * @property {boolean} $dirty True if user has already interacted with the control. * @property {boolean} $valid True if there is no error. * @property {boolean} $invalid True if at least one error on the control. * @property {string} $name The name attribute of the control. * * @description * * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. * The controller contains services for data-binding, validation, CSS updates, and value formatting * and parsing. It purposefully does not contain any logic which deals with DOM rendering or * listening to DOM events. * Such DOM related logic should be provided by other directives which make use of * `NgModelController` for data-binding to control elements. * Angular provides this DOM logic for most {@link input `input`} elements. * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. * * @example * ### Custom Control Example * This example shows how to use `NgModelController` with a custom control to achieve * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * collaborate together to achieve the desired result. * * `contenteditable` is an HTML5 attribute, which tells the browser to let the element * contents be edited in place by the user. * * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} * module to automatically remove "bad" content like inline event listener (e.g. ``). * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks * that content using the `$sce` service. * * [contenteditable] { border: 1px solid black; background-color: white; min-height: 20px; } .ng-invalid { border: 1px solid red; } angular.module('customControl', ['ngSanitize']). directive('contenteditable', ['$sce', function($sce) { return { restrict: 'A', // only activate on element attribute require: '?ngModel', // get a hold of NgModelController link: function(scope, element, attrs, ngModel) { if (!ngModel) return; // do nothing if no ng-model // Specify how UI should be updated ngModel.$render = function() { element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); }; // Listen for change events to enable binding element.on('blur keyup change', function() { scope.$evalAsync(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a
          behind // If strip-br attribute is provided then we strip this out if (attrs.stripBr && html === '
          ') { html = ''; } ngModel.$setViewValue(html); } } }; }]);
          Change me!
          Required!
          it('should data-bind and become invalid', function() { if (browser.params.browser === 'safari' || browser.params.browser === 'firefox') { // SafariDriver can't handle contenteditable // and Firefox driver can't clear contenteditables very well return; } var contentEditable = element(by.css('[contenteditable]')); var content = 'Change me!'; expect(contentEditable.getText()).toEqual(content); contentEditable.clear(); contentEditable.sendKeys(protractor.Key.BACK_SPACE); expect(contentEditable.getText()).toEqual(''); expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); }); *
          * * */ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', /** @this */ function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { this.$viewValue = Number.NaN; this.$modelValue = Number.NaN; this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. this.$validators = {}; this.$asyncValidators = {}; this.$parsers = []; this.$formatters = []; this.$viewChangeListeners = []; this.$untouched = true; this.$touched = false; this.$pristine = true; this.$dirty = false; this.$valid = true; this.$invalid = false; this.$error = {}; // keep invalid keys here this.$$success = {}; // keep valid keys here this.$pending = undefined; // keep pending keys here this.$name = $interpolate($attr.name || '', false)($scope); this.$$parentForm = nullFormCtrl; var parsedNgModel = $parse($attr.ngModel), parsedNgModelAssign = parsedNgModel.assign, ngModelGet = parsedNgModel, ngModelSet = parsedNgModelAssign, pendingDebounce = null, parserValid, ctrl = this; this.$$setOptions = function(options) { ctrl.$options = options; if (options && options.getterSetter) { var invokeModelGetter = $parse($attr.ngModel + '()'), invokeModelSetter = $parse($attr.ngModel + '($$$p)'); ngModelGet = function($scope) { var modelValue = parsedNgModel($scope); if (isFunction(modelValue)) { modelValue = invokeModelGetter($scope); } return modelValue; }; ngModelSet = function($scope, newValue) { if (isFunction(parsedNgModel($scope))) { invokeModelSetter($scope, {$$$p: newValue}); } else { parsedNgModelAssign($scope, newValue); } }; } else if (!parsedNgModel.assign) { throw ngModelMinErr('nonassign', 'Expression \'{0}\' is non-assignable. Element: {1}', $attr.ngModel, startingTag($element)); } }; /** * @ngdoc method * @name ngModel.NgModelController#$render * * @description * Called when the view needs to be updated. It is expected that the user of the ng-model * directive will implement this method. * * The `$render()` method is invoked in the following situations: * * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last * committed value then `$render()` is called to update the input control. * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and * the `$viewValue` are different from last time. * * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be * invoked if you only change a property on the objects. */ this.$render = noop; /** * @ngdoc method * @name ngModel.NgModelController#$isEmpty * * @description * This is called when we need to determine if the value of an input is empty. * * For instance, the required directive does this to work out if the input has data or not. * * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. * * You can override this for input directives whose concept of being empty is different from the * default. The `checkboxInputType` directive does this because in its case a value of `false` * implies empty. * * @param {*} value The value of the input to check for emptiness. * @returns {boolean} True if `value` is "empty". */ this.$isEmpty = function(value) { // eslint-disable-next-line no-self-compare return isUndefined(value) || value === '' || value === null || value !== value; }; this.$$updateEmptyClasses = function(value) { if (ctrl.$isEmpty(value)) { $animate.removeClass($element, NOT_EMPTY_CLASS); $animate.addClass($element, EMPTY_CLASS); } else { $animate.removeClass($element, EMPTY_CLASS); $animate.addClass($element, NOT_EMPTY_CLASS); } }; var currentValidationRunId = 0; /** * @ngdoc method * @name ngModel.NgModelController#$setValidity * * @description * Change the validity state, and notify the form. * * This method can be called within $parsers/$formatters or a custom validation implementation. * However, in most cases it should be sufficient to use the `ngModel.$validators` and * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. * * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. * The `validationErrorKey` should be in camelCase and will get converted into dash-case * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` * class and can be bound to as `{{someForm.someControl.$error.myError}}` . * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. * Skipped is used by Angular when validators do not run because of parse errors and * when `$asyncValidators` do not run because any of the `$validators` failed. */ addSetValidityMethod({ ctrl: this, $element: $element, set: function(object, property) { object[property] = true; }, unset: function(object, property) { delete object[property]; }, $animate: $animate }); /** * @ngdoc method * @name ngModel.NgModelController#$setPristine * * @description * Sets the control to its pristine state. * * This method can be called to remove the `ng-dirty` class and set the control to its pristine * state (`ng-pristine` class). A model is considered to be pristine when the control * has not been changed from when first compiled. */ this.$setPristine = function() { ctrl.$dirty = false; ctrl.$pristine = true; $animate.removeClass($element, DIRTY_CLASS); $animate.addClass($element, PRISTINE_CLASS); }; /** * @ngdoc method * @name ngModel.NgModelController#$setDirty * * @description * Sets the control to its dirty state. * * This method can be called to remove the `ng-pristine` class and set the control to its dirty * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed * from when first compiled. */ this.$setDirty = function() { ctrl.$dirty = true; ctrl.$pristine = false; $animate.removeClass($element, PRISTINE_CLASS); $animate.addClass($element, DIRTY_CLASS); ctrl.$$parentForm.$setDirty(); }; /** * @ngdoc method * @name ngModel.NgModelController#$setUntouched * * @description * Sets the control to its untouched state. * * This method can be called to remove the `ng-touched` class and set the control to its * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched * by default, however this function can be used to restore that state if the model has * already been touched by the user. */ this.$setUntouched = function() { ctrl.$touched = false; ctrl.$untouched = true; $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); }; /** * @ngdoc method * @name ngModel.NgModelController#$setTouched * * @description * Sets the control to its touched state. * * This method can be called to remove the `ng-untouched` class and set the control to its * touched state (`ng-touched` class). A model is considered to be touched when the user has * first focused the control element and then shifted focus away from the control (blur event). */ this.$setTouched = function() { ctrl.$touched = true; ctrl.$untouched = false; $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); }; /** * @ngdoc method * @name ngModel.NgModelController#$rollbackViewValue * * @description * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, * which may be caused by a pending debounced event or because the input is waiting for some * future event. * * If you have an input that uses `ng-model-options` to set up debounced updates or updates that * depend on special events such as `blur`, there can be a period when the `$viewValue` is out of * sync with the ngModel's `$modelValue`. * * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update * and reset the input to the last committed view value. * * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` * programmatically before these debounced/future events have resolved/occurred, because Angular's * dirty checking mechanism is not able to tell whether the model has actually changed or not. * * The `$rollbackViewValue()` method should be called before programmatically changing the model of an * input which may have such events pending. This is important in order to make sure that the * input field will be updated with the new model value and any pending operations are cancelled. * * * * angular.module('cancel-update-example', []) * * .controller('CancelUpdateController', ['$scope', function($scope) { * $scope.model = {value1: '', value2: ''}; * * $scope.setEmpty = function(e, value, rollback) { * if (e.keyCode === 27) { * e.preventDefault(); * if (rollback) { * $scope.myForm[value].$rollbackViewValue(); * } * $scope.model[value] = ''; * } * }; * }]); * * *
          *

          Both of these inputs are only updated if they are blurred. Hitting escape should * empty them. Follow these steps and observe the difference:

          *
            *
          1. Type something in the input. You will see that the model is not yet updated
          2. *
          3. Press the Escape key. *
              *
            1. In the first example, nothing happens, because the model is already '', and no * update is detected. If you blur the input, the model will be set to the current view. *
            2. *
            3. In the second example, the pending update is cancelled, and the input is set back * to the last committed view value (''). Blurring the input does nothing. *
            4. *
            *
          4. *
          * *
          *
          *

          Without $rollbackViewValue():

          * * value1: "{{ model.value1 }}" *
          * *
          *

          With $rollbackViewValue():

          * * value2: "{{ model.value2 }}" *
          *
          *
          *
          div { display: table-cell; } div:nth-child(1) { padding-right: 30px; } *
          */ this.$rollbackViewValue = function() { $timeout.cancel(pendingDebounce); ctrl.$viewValue = ctrl.$$lastCommittedViewValue; ctrl.$render(); }; /** * @ngdoc method * @name ngModel.NgModelController#$validate * * @description * Runs each of the registered validators (first synchronous validators and then * asynchronous validators). * If the validity changes to invalid, the model will be set to `undefined`, * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. * If the validity changes to valid, it will set the model to the last available valid * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. */ this.$validate = function() { // ignore $validate before model is initialized if (isNumberNaN(ctrl.$modelValue)) { return; } var viewValue = ctrl.$$lastCommittedViewValue; // Note: we use the $$rawModelValue as $modelValue might have been // set to undefined during a view -> model update that found validation // errors. We can't parse the view here, since that could change // the model although neither viewValue nor the model on the scope changed var modelValue = ctrl.$$rawModelValue; var prevValid = ctrl.$valid; var prevModelValue = ctrl.$modelValue; var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; ctrl.$$runValidators(modelValue, viewValue, function(allValid) { // If there was no change in validity, don't update the model // This prevents changing an invalid modelValue to undefined if (!allowInvalid && prevValid !== allValid) { // Note: Don't check ctrl.$valid here, as we could have // external validators (e.g. calculated on the server), // that just call $setValidity and need the model value // to calculate their validity. ctrl.$modelValue = allValid ? modelValue : undefined; if (ctrl.$modelValue !== prevModelValue) { ctrl.$$writeModelToScope(); } } }); }; this.$$runValidators = function(modelValue, viewValue, doneCallback) { currentValidationRunId++; var localValidationRunId = currentValidationRunId; // check parser error if (!processParseErrors()) { validationDone(false); return; } if (!processSyncValidators()) { validationDone(false); return; } processAsyncValidators(); function processParseErrors() { var errorKey = ctrl.$$parserName || 'parse'; if (isUndefined(parserValid)) { setValidity(errorKey, null); } else { if (!parserValid) { forEach(ctrl.$validators, function(v, name) { setValidity(name, null); }); forEach(ctrl.$asyncValidators, function(v, name) { setValidity(name, null); }); } // Set the parse error last, to prevent unsetting it, should a $validators key == parserName setValidity(errorKey, parserValid); return parserValid; } return true; } function processSyncValidators() { var syncValidatorsValid = true; forEach(ctrl.$validators, function(validator, name) { var result = validator(modelValue, viewValue); syncValidatorsValid = syncValidatorsValid && result; setValidity(name, result); }); if (!syncValidatorsValid) { forEach(ctrl.$asyncValidators, function(v, name) { setValidity(name, null); }); return false; } return true; } function processAsyncValidators() { var validatorPromises = []; var allValid = true; forEach(ctrl.$asyncValidators, function(validator, name) { var promise = validator(modelValue, viewValue); if (!isPromiseLike(promise)) { throw ngModelMinErr('nopromise', 'Expected asynchronous validator to return a promise but got \'{0}\' instead.', promise); } setValidity(name, undefined); validatorPromises.push(promise.then(function() { setValidity(name, true); }, function() { allValid = false; setValidity(name, false); })); }); if (!validatorPromises.length) { validationDone(true); } else { $q.all(validatorPromises).then(function() { validationDone(allValid); }, noop); } } function setValidity(name, isValid) { if (localValidationRunId === currentValidationRunId) { ctrl.$setValidity(name, isValid); } } function validationDone(allValid) { if (localValidationRunId === currentValidationRunId) { doneCallback(allValid); } } }; /** * @ngdoc method * @name ngModel.NgModelController#$commitViewValue * * @description * Commit a pending update to the `$modelValue`. * * Updates may be pending by a debounced event or because the input is waiting for a some future * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` * usually handles calling this in response to input events. */ this.$commitViewValue = function() { var viewValue = ctrl.$viewValue; $timeout.cancel(pendingDebounce); // If the view value has not changed then we should just exit, except in the case where there is // a native validator on the element. In this case the validation state may have changed even though // the viewValue has stayed empty. if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { return; } ctrl.$$updateEmptyClasses(viewValue); ctrl.$$lastCommittedViewValue = viewValue; // change to dirty if (ctrl.$pristine) { this.$setDirty(); } this.$$parseAndValidate(); }; this.$$parseAndValidate = function() { var viewValue = ctrl.$$lastCommittedViewValue; var modelValue = viewValue; parserValid = isUndefined(modelValue) ? undefined : true; if (parserValid) { for (var i = 0; i < ctrl.$parsers.length; i++) { modelValue = ctrl.$parsers[i](modelValue); if (isUndefined(modelValue)) { parserValid = false; break; } } } if (isNumberNaN(ctrl.$modelValue)) { // ctrl.$modelValue has not been touched yet... ctrl.$modelValue = ngModelGet($scope); } var prevModelValue = ctrl.$modelValue; var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; ctrl.$$rawModelValue = modelValue; if (allowInvalid) { ctrl.$modelValue = modelValue; writeToModelIfNeeded(); } // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. // This can happen if e.g. $setViewValue is called from inside a parser ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { if (!allowInvalid) { // Note: Don't check ctrl.$valid here, as we could have // external validators (e.g. calculated on the server), // that just call $setValidity and need the model value // to calculate their validity. ctrl.$modelValue = allValid ? modelValue : undefined; writeToModelIfNeeded(); } }); function writeToModelIfNeeded() { if (ctrl.$modelValue !== prevModelValue) { ctrl.$$writeModelToScope(); } } }; this.$$writeModelToScope = function() { ngModelSet($scope, ctrl.$modelValue); forEach(ctrl.$viewChangeListeners, function(listener) { try { listener(); } catch (e) { $exceptionHandler(e); } }); }; /** * @ngdoc method * @name ngModel.NgModelController#$setViewValue * * @description * Update the view value. * * This method should be called when a control wants to change the view value; typically, * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} * directive calls it when the value of the input changes and {@link ng.directive:select select} * calls it when an option is selected. * * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged * value sent directly for processing, finally to be applied to `$modelValue` and then the * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, * in the `$viewChangeListeners` list, are called. * * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` * and the `default` trigger is not listed, all those actions will remain pending until one of the * `updateOn` events is triggered on the DOM element. * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} * directive is used with a custom debounce for this particular event. * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` * is specified, once the timer runs out. * * When used with standard inputs, the view value will always be a string (which is in some cases * parsed into another type, such as a `Date` object for `input[date]`.) * However, custom controls might also pass objects to this method. In this case, we should make * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not * perform a deep watch of objects, it only looks for a change of identity. If you only change * the property of the object then ngModel will not realize that the object has changed and * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should * not change properties of the copy once it has been passed to `$setViewValue`. * Otherwise you may cause the model value on the scope to change incorrectly. * *
          * In any case, the value passed to the method should always reflect the current value * of the control. For example, if you are calling `$setViewValue` for an input element, * you should pass the input DOM value. Otherwise, the control and the scope model become * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change * the control's DOM value in any way. If we want to change the control's DOM value * programmatically, we should update the `ngModel` scope expression. Its new value will be * picked up by the model controller, which will run it through the `$formatters`, `$render` it * to update the DOM, and finally call `$validate` on it. *
          * * @param {*} value value from the view. * @param {string} trigger Event that triggered the update. */ this.$setViewValue = function(value, trigger) { ctrl.$viewValue = value; if (!ctrl.$options || ctrl.$options.updateOnDefault) { ctrl.$$debounceViewValueCommit(trigger); } }; this.$$debounceViewValueCommit = function(trigger) { var debounceDelay = 0, options = ctrl.$options, debounce; if (options && isDefined(options.debounce)) { debounce = options.debounce; if (isNumber(debounce)) { debounceDelay = debounce; } else if (isNumber(debounce[trigger])) { debounceDelay = debounce[trigger]; } else if (isNumber(debounce['default'])) { debounceDelay = debounce['default']; } } $timeout.cancel(pendingDebounce); if (debounceDelay) { pendingDebounce = $timeout(function() { ctrl.$commitViewValue(); }, debounceDelay); } else if ($rootScope.$$phase) { ctrl.$commitViewValue(); } else { $scope.$apply(function() { ctrl.$commitViewValue(); }); } }; // model -> value // Note: we cannot use a normal scope.$watch as we want to detect the following: // 1. scope value is 'a' // 2. user enters 'b' // 3. ng-change kicks in and reverts scope value to 'a' // -> scope value did not change since the last digest as // ng-change executes in apply phase // 4. view should be changed back to 'a' $scope.$watch(function ngModelWatch() { var modelValue = ngModelGet($scope); // if scope model value and ngModel value are out of sync // TODO(perf): why not move this to the action fn? if (modelValue !== ctrl.$modelValue && // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator // eslint-disable-next-line no-self-compare (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) ) { ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; parserValid = undefined; var formatters = ctrl.$formatters, idx = formatters.length; var viewValue = modelValue; while (idx--) { viewValue = formatters[idx](viewValue); } if (ctrl.$viewValue !== viewValue) { ctrl.$$updateEmptyClasses(viewValue); ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; ctrl.$render(); // It is possible that model and view value have been updated during render ctrl.$$runValidators(ctrl.$modelValue, ctrl.$viewValue, noop); } } return modelValue; }); }]; /** * @ngdoc directive * @name ngModel * * @element input * @priority 1 * * @description * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a * property on the scope using {@link ngModel.NgModelController NgModelController}, * which is created and exposed by this directive. * * `ngModel` is responsible for: * * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` * require. * - Providing validation behavior (i.e. required, number, email, url). * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. * - Registering the control with its parent {@link ng.directive:form form}. * * Note: `ngModel` will try to bind to the property given by evaluating the expression on the * current scope. If the property doesn't already exist on this scope, it will be created * implicitly and added to the scope. * * For best practices on using `ngModel`, see: * * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) * * For basic examples, how to use `ngModel`, see: * * - {@link ng.directive:input input} * - {@link input[text] text} * - {@link input[checkbox] checkbox} * - {@link input[radio] radio} * - {@link input[number] number} * - {@link input[email] email} * - {@link input[url] url} * - {@link input[date] date} * - {@link input[datetime-local] datetime-local} * - {@link input[time] time} * - {@link input[month] month} * - {@link input[week] week} * - {@link ng.directive:select select} * - {@link ng.directive:textarea textarea} * * # Complex Models (objects or collections) * * By default, `ngModel` watches the model by reference, not value. This is important to know when * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. * * The model must be assigned an entirely new object or collection before a re-rendering will occur. * * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or * if the select is given the `multiple` attribute. * * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the * first level of the object (or only changing the properties of an item in the collection if it's an array) will still * not trigger a re-rendering of the model. * * # CSS classes * The following CSS classes are added and removed on the associated input/select/textarea element * depending on the validity of the model. * * - `ng-valid`: the model is valid * - `ng-invalid`: the model is invalid * - `ng-valid-[key]`: for each valid key added by `$setValidity` * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` * - `ng-pristine`: the control hasn't been interacted with yet * - `ng-dirty`: the control has been interacted with * - `ng-touched`: the control has been blurred * - `ng-untouched`: the control hasn't been blurred * - `ng-pending`: any `$asyncValidators` are unfulfilled * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined * by the {@link ngModel.NgModelController#$isEmpty} method * - `ng-not-empty`: the view contains a non-empty value * * Keep in mind that ngAnimate can detect each of these classes when added and removed. * * ## Animation Hooks * * Animations within models are triggered when any of the associated CSS classes are added and removed * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. * The animations that are triggered within ngModel are similar to how they work in ngClass and * animations can be hooked into using CSS transitions, keyframes as well as JS animations. * * The following example shows a simple way to utilize CSS transitions to style an input element * that has been rendered as invalid after it has been validated: * *
           * //be sure to include ngAnimate as a module to hook into more
           * //advanced animations
           * .my-input {
           *   transition:0.5s linear all;
           *   background: white;
           * }
           * .my-input.ng-invalid {
           *   background: red;
           *   color:white;
           * }
           * 
          * * @example *

          Update input to see transitions when valid/invalid. Integer is a valid value.

          *
          * * ## Binding to a getter/setter * * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a * function that returns a representation of the model when called with zero arguments, and sets * the internal state of a model when called with an argument. It's sometimes useful to use this * for models that have an internal representation that's different from what the model exposes * to the view. * *
          * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more * frequently than other parts of your code. *
          * * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to * a `
          `, which will enable this behavior for all ``s within it. See * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. * * The following example shows how to use `ngModel` with a getter/setter: * * @example *
          user.name = 
          angular.module('getterSetterExample', []) .controller('ExampleController', ['$scope', function($scope) { var _name = 'Brian'; $scope.user = { name: function(newName) { // Note that newName can be undefined for two reasons: // 1. Because it is called as a getter and thus called with no arguments // 2. Because the property should actually be set to undefined. This happens e.g. if the // input is invalid return arguments.length ? (_name = newName) : _name; } }; }]); *
          */ var ngModelDirective = ['$rootScope', function($rootScope) { return { restrict: 'A', require: ['ngModel', '^?form', '^?ngModelOptions'], controller: NgModelController, // Prelink needs to run before any input directive // so that we can set the NgModelOptions in NgModelController // before anyone else uses it. priority: 1, compile: function ngModelCompile(element) { // Setup initial state of the control element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); return { pre: function ngModelPreLink(scope, element, attr, ctrls) { var modelCtrl = ctrls[0], formCtrl = ctrls[1] || modelCtrl.$$parentForm; modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); // notify others, especially parent forms formCtrl.$addControl(modelCtrl); attr.$observe('name', function(newValue) { if (modelCtrl.$name !== newValue) { modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); } }); scope.$on('$destroy', function() { modelCtrl.$$parentForm.$removeControl(modelCtrl); }); }, post: function ngModelPostLink(scope, element, attr, ctrls) { var modelCtrl = ctrls[0]; if (modelCtrl.$options && modelCtrl.$options.updateOn) { element.on(modelCtrl.$options.updateOn, function(ev) { modelCtrl.$$debounceViewValueCommit(ev && ev.type); }); } element.on('blur', function() { if (modelCtrl.$touched) return; if ($rootScope.$$phase) { scope.$evalAsync(modelCtrl.$setTouched); } else { scope.$apply(modelCtrl.$setTouched); } }); } }; } }; }]; var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; /** * @ngdoc directive * @name ngModelOptions * * @description * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of * events that will trigger a model update and/or a debouncing delay so that the actual update only * takes place when a timer expires; this timer will be reset after another change takes place. * * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might * be different from the value in the actual model. This means that if you update the model you * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in * order to make sure it is synchronized with the model and that any debounced action is canceled. * * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} * method is by making sure the input is placed inside a form that has a `name` attribute. This is * important because `form` controllers are published to the related scope under the name in their * `name` attribute. * * Any pending changes will take place immediately when an enclosing form is submitted via the * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` * to have access to the updated model. * * `ngModelOptions` has an effect on the element it's declared on and its descendants. * * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: * - `updateOn`: string specifying which event should the input be bound to. You can set several * events using an space delimited list. There is a special event called `default` that * matches the default events belonging of the control. * - `debounce`: integer value which contains the debounce model update value in milliseconds. A * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a * custom value for each event. For example: * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` * - `allowInvalid`: boolean value which indicates that the model can be set with values that did * not validate correctly instead of the default behavior of setting the model to undefined. * - `getterSetter`: boolean value which determines whether or not to treat functions bound to `ngModel` as getters/setters. * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for * ``, ``, ... . It understands UTC/GMT and the * continental US time zone abbreviations, but for general use, use a time zone offset, for * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) * If not specified, the timezone of the browser will be used. * * @example The following example shows how to override immediate updates. Changes on the inputs within the form will update the model only when the control loses focus (blur event). If `escape` key is pressed while the input field is focused, the value is reset to the value in the current model.


          user.name = 
          user.data = 
          angular.module('optionsExample', []) .controller('ExampleController', ['$scope', function($scope) { $scope.user = { name: 'John', data: '' }; $scope.cancel = function(e) { if (e.keyCode === 27) { $scope.userForm.userName.$rollbackViewValue(); } }; }]); var model = element(by.binding('user.name')); var input = element(by.model('user.name')); var other = element(by.model('user.data')); it('should allow custom events', function() { input.sendKeys(' Doe'); input.click(); expect(model.getText()).toEqual('John'); other.click(); expect(model.getText()).toEqual('John Doe'); }); it('should $rollbackViewValue when model changes', function() { input.sendKeys(' Doe'); expect(input.getAttribute('value')).toEqual('John Doe'); input.sendKeys(protractor.Key.ESCAPE); expect(input.getAttribute('value')).toEqual('John'); other.click(); expect(model.getText()).toEqual('John'); });
          This one shows how to debounce model changes. Model will be updated only 1 sec after last change. If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.

          user.name = 
          angular.module('optionsExample', []) .controller('ExampleController', ['$scope', function($scope) { $scope.user = { name: 'Igor' }; }]);
          This one shows how to bind to getter/setters:
          user.name = 
          angular.module('getterSetterExample', []) .controller('ExampleController', ['$scope', function($scope) { var _name = 'Brian'; $scope.user = { name: function(newName) { // Note that newName can be undefined for two reasons: // 1. Because it is called as a getter and thus called with no arguments // 2. Because the property should actually be set to undefined. This happens e.g. if the // input is invalid return arguments.length ? (_name = newName) : _name; } }; }]);
          */ var ngModelOptionsDirective = function() { return { restrict: 'A', controller: ['$scope', '$attrs', function NgModelOptionsController($scope, $attrs) { var that = this; this.$options = copy($scope.$eval($attrs.ngModelOptions)); // Allow adding/overriding bound events if (isDefined(this.$options.updateOn)) { this.$options.updateOnDefault = false; // extract "default" pseudo-event from list of events that can trigger a model update this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { that.$options.updateOnDefault = true; return ' '; })); } else { this.$options.updateOnDefault = true; } }] }; }; // helper methods function addSetValidityMethod(context) { var ctrl = context.ctrl, $element = context.$element, classCache = {}, set = context.set, unset = context.unset, $animate = context.$animate; classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); ctrl.$setValidity = setValidity; function setValidity(validationErrorKey, state, controller) { if (isUndefined(state)) { createAndSet('$pending', validationErrorKey, controller); } else { unsetAndCleanup('$pending', validationErrorKey, controller); } if (!isBoolean(state)) { unset(ctrl.$error, validationErrorKey, controller); unset(ctrl.$$success, validationErrorKey, controller); } else { if (state) { unset(ctrl.$error, validationErrorKey, controller); set(ctrl.$$success, validationErrorKey, controller); } else { set(ctrl.$error, validationErrorKey, controller); unset(ctrl.$$success, validationErrorKey, controller); } } if (ctrl.$pending) { cachedToggleClass(PENDING_CLASS, true); ctrl.$valid = ctrl.$invalid = undefined; toggleValidationCss('', null); } else { cachedToggleClass(PENDING_CLASS, false); ctrl.$valid = isObjectEmpty(ctrl.$error); ctrl.$invalid = !ctrl.$valid; toggleValidationCss('', ctrl.$valid); } // re-read the state as the set/unset methods could have // combined state in ctrl.$error[validationError] (used for forms), // where setting/unsetting only increments/decrements the value, // and does not replace it. var combinedState; if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { combinedState = undefined; } else if (ctrl.$error[validationErrorKey]) { combinedState = false; } else if (ctrl.$$success[validationErrorKey]) { combinedState = true; } else { combinedState = null; } toggleValidationCss(validationErrorKey, combinedState); ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); } function createAndSet(name, value, controller) { if (!ctrl[name]) { ctrl[name] = {}; } set(ctrl[name], value, controller); } function unsetAndCleanup(name, value, controller) { if (ctrl[name]) { unset(ctrl[name], value, controller); } if (isObjectEmpty(ctrl[name])) { ctrl[name] = undefined; } } function cachedToggleClass(className, switchValue) { if (switchValue && !classCache[className]) { $animate.addClass($element, className); classCache[className] = true; } else if (!switchValue && classCache[className]) { $animate.removeClass($element, className); classCache[className] = false; } } function toggleValidationCss(validationErrorKey, isValid) { validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); } } function isObjectEmpty(obj) { if (obj) { for (var prop in obj) { if (obj.hasOwnProperty(prop)) { return false; } } } return true; } /** * @ngdoc directive * @name ngNonBindable * @restrict AC * @priority 1000 * * @description * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current * DOM element. This is useful if the element contains what appears to be Angular directives and * bindings but which should be ignored by Angular. This could be the case if you have a site that * displays snippets of code, for instance. * * @element ANY * * @example * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, * but the one wrapped in `ngNonBindable` is left alone. * * @example
          Normal: {{1 + 2}}
          Ignored: {{1 + 2}}
          it('should check ng-non-bindable', function() { expect(element(by.binding('1 + 2')).getText()).toContain('3'); expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); });
          */ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); /* exported ngOptionsDirective */ /* global jqLiteRemove */ var ngOptionsMinErr = minErr('ngOptions'); /** * @ngdoc directive * @name ngOptions * @restrict A * * @description * * The `ngOptions` attribute can be used to dynamically generate a list of `` * DOM element. * * `disable`: The result of this expression will be used to disable the rendered `
          it('should show correct pluralized string', function() { var withoutOffset = element.all(by.css('ng-pluralize')).get(0); var withOffset = element.all(by.css('ng-pluralize')).get(1); var countInput = element(by.model('personCount')); expect(withoutOffset.getText()).toEqual('1 person is viewing.'); expect(withOffset.getText()).toEqual('Igor is viewing.'); countInput.clear(); countInput.sendKeys('0'); expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); expect(withOffset.getText()).toEqual('Nobody is viewing.'); countInput.clear(); countInput.sendKeys('2'); expect(withoutOffset.getText()).toEqual('2 people are viewing.'); expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); countInput.clear(); countInput.sendKeys('3'); expect(withoutOffset.getText()).toEqual('3 people are viewing.'); expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); countInput.clear(); countInput.sendKeys('4'); expect(withoutOffset.getText()).toEqual('4 people are viewing.'); expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); }); it('should show data-bound names', function() { var withOffset = element.all(by.css('ng-pluralize')).get(1); var personCount = element(by.model('personCount')); var person1 = element(by.model('person1')); var person2 = element(by.model('person2')); personCount.clear(); personCount.sendKeys('4'); person1.clear(); person1.sendKeys('Di'); person2.clear(); person2.sendKeys('Vojta'); expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); }); */ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { var BRACE = /{}/g, IS_WHEN = /^when(Minus)?(.+)$/; return { link: function(scope, element, attr) { var numberExp = attr.count, whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs offset = attr.offset || 0, whens = scope.$eval(whenExp) || {}, whensExpFns = {}, startSymbol = $interpolate.startSymbol(), endSymbol = $interpolate.endSymbol(), braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, watchRemover = angular.noop, lastCount; forEach(attr, function(expression, attributeName) { var tmpMatch = IS_WHEN.exec(attributeName); if (tmpMatch) { var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); whens[whenKey] = element.attr(attr.$attr[attributeName]); } }); forEach(whens, function(expression, key) { whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); }); scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { var count = parseFloat(newVal); var countIsNaN = isNumberNaN(count); if (!countIsNaN && !(count in whens)) { // If an explicit number rule such as 1, 2, 3... is defined, just use it. // Otherwise, check it against pluralization rules in $locale service. count = $locale.pluralCat(count - offset); } // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. // In JS `NaN !== NaN`, so we have to explicitly check. if ((count !== lastCount) && !(countIsNaN && isNumberNaN(lastCount))) { watchRemover(); var whenExpFn = whensExpFns[count]; if (isUndefined(whenExpFn)) { if (newVal != null) { $log.debug('ngPluralize: no rule defined for \'' + count + '\' in ' + whenExp); } watchRemover = noop; updateElementText(); } else { watchRemover = scope.$watch(whenExpFn, updateElementText); } lastCount = count; } }); function updateElementText(newText) { element.text(newText || ''); } } }; }]; /* exported ngRepeatDirective */ /** * @ngdoc directive * @name ngRepeat * @multiElement * * @description * The `ngRepeat` directive instantiates a template once per item from a collection. Each template * instance gets its own scope, where the given loop variable is set to the current collection item, * and `$index` is set to the item index or key. * * Special properties are exposed on the local scope of each template instance, including: * * | Variable | Type | Details | * |-----------|-----------------|-----------------------------------------------------------------------------| * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | * *
          * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. * This may be useful when, for instance, nesting ngRepeats. *
          * * * # Iterating over object properties * * It is possible to get `ngRepeat` to iterate over the properties of an object using the following * syntax: * * ```js *
          ...
          * ``` * * However, there are a few limitations compared to array iteration: * * - The JavaScript specification does not define the order of keys * returned for an object, so Angular relies on the order returned by the browser * when running `for key in myObj`. Browsers generally follow the strategy of providing * keys in the order in which they were defined, although there are exceptions when keys are deleted * and reinstated. See the * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). * * - `ngRepeat` will silently *ignore* object keys starting with `$`, because * it's a prefix used by Angular for public (`$`) and private (`$$`) properties. * * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with * objects, and will throw an error if used with one. * * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) * or implement a `$watch` on the object yourself. * * * # Tracking and Duplicates * * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in * the collection. When a change happens, `ngRepeat` then makes the corresponding changes to the DOM: * * * When an item is added, a new instance of the template is added to the DOM. * * When an item is removed, its template instance is removed from the DOM. * * When items are reordered, their respective templates are reordered in the DOM. * * To minimize creation of DOM elements, `ngRepeat` uses a function * to "keep track" of all items in the collection and their corresponding DOM elements. * For example, if an item is added to the collection, `ngRepeat` will know that all other items * already have DOM elements, and will not re-render them. * * The default tracking function (which tracks items by their identity) does not allow * duplicate items in arrays. This is because when there are duplicates, it is not possible * to maintain a one-to-one mapping between collection items and DOM elements. * * If you do need to repeat duplicate items, you can substitute the default tracking behavior * with your own using the `track by` expression. * * For example, you may track items by the index of each item in the collection, using the * special scope property `$index`: * ```html *
          * {{n}} *
          * ``` * * You may also use arbitrary expressions in `track by`, including references to custom functions * on the scope: * ```html *
          * {{n}} *
          * ``` * *
          * If you are working with objects that have a unique identifier property, you should track * by this identifier instead of the object instance. Should you reload your data later, `ngRepeat` * will not have to rebuild the DOM elements for items it has already rendered, even if the * JavaScript objects in the collection have been substituted for new ones. For large collections, * this significantly improves rendering performance. If you don't have a unique identifier, * `track by $index` can also provide a performance boost. *
          * * ```html *
          * {{model.name}} *
          * ``` * *
          *
          * Avoid using `track by $index` when the repeated template contains * {@link guide/expression#one-time-binding one-time bindings}. In such cases, the `nth` DOM * element will always be matched with the `nth` item of the array, so the bindings on that element * will not be updated even when the corresponding item changes, essentially causing the view to get * out-of-sync with the underlying data. *
          * * When no `track by` expression is provided, it is equivalent to tracking by the built-in * `$id` function, which tracks items by their identity: * ```html *
          * {{obj.prop}} *
          * ``` * *
          *
          * **Note:** `track by` must always be the last expression: *
          * ``` *
          * {{model.name}} *
          * ``` * * * # Special repeat start and end points * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) * up to and including the ending HTML tag where **ng-repeat-end** is placed. * * The example below makes use of this feature: * ```html *
          * Header {{ item }} *
          *
          * Body {{ item }} *
          *
          * Footer {{ item }} *
          * ``` * * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: * ```html *
          * Header A *
          *
          * Body A *
          *
          * Footer A *
          *
          * Header B *
          *
          * Body B *
          *
          * Footer B *
          * ``` * * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). * * @animations * | Animation | Occurs | * |----------------------------------|-------------------------------------| * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | * * See the example below for defining CSS animations with ngRepeat. * * @element ANY * @scope * @priority 1000 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These * formats are currently supported: * * * `variable in expression` – where variable is the user defined loop variable and `expression` * is a scope expression giving the collection to enumerate. * * For example: `album in artist.albums`. * * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, * and `expression` is the scope expression giving the collection to enumerate. * * For example: `(name, age) in {'adam':10, 'amalie':12}`. * * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression * is specified, ng-repeat associates elements by identity. It is an error to have * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are * mapped to the same DOM element, which is not possible.) * * Note that the tracking expression must come last, after any filters, and the alias expression. * * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements * will be associated by item identity in the array. * * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements * with the corresponding item in the array by identity. Moving the same object in array would move the DOM * element in the same way in the DOM. * * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this * case the object identity does not matter. Two objects are considered equivalent as long as their `id` * property is same. * * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter * to items in conjunction with a tracking expression. * * * `variable in expression as alias_expression` – You can also provide an optional alias expression which will then store the * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message * when a filter is active on the repeater, but the filtered result set is empty. * * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after * the items have been processed through the filter. * * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end * (and not as operator, inside an expression). * * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . * * @example * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed * results by name or by age. New (entering) and removed (leaving) items are animated.
          I have {{friends.length}} friends. They are:
          • [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
          • No results found...
          angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { $scope.friends = [ {name:'John', age:25, gender:'boy'}, {name:'Jessie', age:30, gender:'girl'}, {name:'Johanna', age:28, gender:'girl'}, {name:'Joy', age:15, gender:'girl'}, {name:'Mary', age:28, gender:'girl'}, {name:'Peter', age:95, gender:'boy'}, {name:'Sebastian', age:50, gender:'boy'}, {name:'Erika', age:27, gender:'girl'}, {name:'Patrick', age:40, gender:'boy'}, {name:'Samantha', age:60, gender:'girl'} ]; }); .example-animate-container { background:white; border:1px solid black; list-style:none; margin:0; padding:0 10px; } .animate-repeat { line-height:30px; list-style:none; box-sizing:border-box; } .animate-repeat.ng-move, .animate-repeat.ng-enter, .animate-repeat.ng-leave { transition:all linear 0.5s; } .animate-repeat.ng-leave.ng-leave-active, .animate-repeat.ng-move, .animate-repeat.ng-enter { opacity:0; max-height:0; } .animate-repeat.ng-leave, .animate-repeat.ng-move.ng-move-active, .animate-repeat.ng-enter.ng-enter-active { opacity:1; max-height:30px; } var friends = element.all(by.repeater('friend in friends')); it('should render initial data set', function() { expect(friends.count()).toBe(10); expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); expect(element(by.binding('friends.length')).getText()) .toMatch("I have 10 friends. They are:"); }); it('should update repeater when filter predicate changes', function() { expect(friends.count()).toBe(10); element(by.model('q')).sendKeys('ma'); expect(friends.count()).toBe(2); expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); });
          */ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { var NG_REMOVED = '$$NG_REMOVED'; var ngRepeatMinErr = minErr('ngRepeat'); var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { // TODO(perf): generate setters to shave off ~40ms or 1-1.5% scope[valueIdentifier] = value; if (keyIdentifier) scope[keyIdentifier] = key; scope.$index = index; scope.$first = (index === 0); scope.$last = (index === (arrayLength - 1)); scope.$middle = !(scope.$first || scope.$last); // eslint-disable-next-line no-bitwise scope.$odd = !(scope.$even = (index & 1) === 0); }; var getBlockStart = function(block) { return block.clone[0]; }; var getBlockEnd = function(block) { return block.clone[block.clone.length - 1]; }; return { restrict: 'A', multiElement: true, transclude: 'element', priority: 1000, terminal: true, $$tlb: true, compile: function ngRepeatCompile($element, $attr) { var expression = $attr.ngRepeat; var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); if (!match) { throw ngRepeatMinErr('iexp', 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got \'{0}\'.', expression); } var lhs = match[1]; var rhs = match[2]; var aliasAs = match[3]; var trackByExp = match[4]; match = lhs.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/); if (!match) { throw ngRepeatMinErr('iidexp', '\'_item_\' in \'_item_ in _collection_\' should be an identifier or \'(_key_, _value_)\' expression, but got \'{0}\'.', lhs); } var valueIdentifier = match[3] || match[1]; var keyIdentifier = match[2]; if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { throw ngRepeatMinErr('badident', 'alias \'{0}\' is invalid --- must be a valid JS identifier which is not a reserved name.', aliasAs); } var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; var hashFnLocals = {$id: hashKey}; if (trackByExp) { trackByExpGetter = $parse(trackByExp); } else { trackByIdArrayFn = function(key, value) { return hashKey(value); }; trackByIdObjFn = function(key) { return key; }; } return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { if (trackByExpGetter) { trackByIdExpFn = function(key, value, index) { // assign key, value, and $index to the locals so that they can be used in hash functions if (keyIdentifier) hashFnLocals[keyIdentifier] = key; hashFnLocals[valueIdentifier] = value; hashFnLocals.$index = index; return trackByExpGetter($scope, hashFnLocals); }; } // Store a list of elements from previous run. This is a hash where key is the item from the // iterator, and the value is objects with following properties. // - scope: bound scope // - element: previous element. // - index: position // // We are using no-proto object so that we don't need to guard against inherited props via // hasOwnProperty. var lastBlockMap = createMap(); //watch props $scope.$watchCollection(rhs, function ngRepeatAction(collection) { var index, length, previousNode = $element[0], // node that cloned nodes should be inserted after // initialized to the comment node anchor nextNode, // Same as lastBlockMap but it has the current state. It will become the // lastBlockMap on the next iteration. nextBlockMap = createMap(), collectionLength, key, value, // key/value of iteration trackById, trackByIdFn, collectionKeys, block, // last object information {scope, element, id} nextBlockOrder, elementsToRemove; if (aliasAs) { $scope[aliasAs] = collection; } if (isArrayLike(collection)) { collectionKeys = collection; trackByIdFn = trackByIdExpFn || trackByIdArrayFn; } else { trackByIdFn = trackByIdExpFn || trackByIdObjFn; // if object, extract keys, in enumeration order, unsorted collectionKeys = []; for (var itemKey in collection) { if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { collectionKeys.push(itemKey); } } } collectionLength = collectionKeys.length; nextBlockOrder = new Array(collectionLength); // locate existing items for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; trackById = trackByIdFn(key, value, index); if (lastBlockMap[trackById]) { // found previously seen block block = lastBlockMap[trackById]; delete lastBlockMap[trackById]; nextBlockMap[trackById] = block; nextBlockOrder[index] = block; } else if (nextBlockMap[trackById]) { // if collision detected. restore lastBlockMap and throw an error forEach(nextBlockOrder, function(block) { if (block && block.scope) lastBlockMap[block.id] = block; }); throw ngRepeatMinErr('dupes', 'Duplicates in a repeater are not allowed. Use \'track by\' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}', expression, trackById, value); } else { // new never before seen block nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; nextBlockMap[trackById] = true; } } // remove leftover items for (var blockKey in lastBlockMap) { block = lastBlockMap[blockKey]; elementsToRemove = getBlockNodes(block.clone); $animate.leave(elementsToRemove); if (elementsToRemove[0].parentNode) { // if the element was not removed yet because of pending animation, mark it as deleted // so that we can ignore it later for (index = 0, length = elementsToRemove.length; index < length; index++) { elementsToRemove[index][NG_REMOVED] = true; } } block.scope.$destroy(); } // we are not using forEach for perf reasons (trying to avoid #call) for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; block = nextBlockOrder[index]; if (block.scope) { // if we have already seen this object, then we need to reuse the // associated scope/element nextNode = previousNode; // skip nodes that are already pending removal via leave animation do { nextNode = nextNode.nextSibling; } while (nextNode && nextNode[NG_REMOVED]); if (getBlockStart(block) !== nextNode) { // existing item which got moved $animate.move(getBlockNodes(block.clone), null, previousNode); } previousNode = getBlockEnd(block); updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); } else { // new item which we don't know about $transclude(function ngRepeatTransclude(clone, scope) { block.scope = scope; // http://jsperf.com/clone-vs-createcomment var endNode = ngRepeatEndComment.cloneNode(false); clone[clone.length++] = endNode; $animate.enter(clone, null, previousNode); previousNode = endNode; // Note: We only need the first/last node of the cloned nodes. // However, we need to keep the reference to the jqlite wrapper as it might be changed later // by a directive with templateUrl when its template arrives. block.clone = clone; nextBlockMap[block.id] = block; updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); }); } } lastBlockMap = nextBlockMap; }); }; } }; }]; var NG_HIDE_CLASS = 'ng-hide'; var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; /** * @ngdoc directive * @name ngShow * @multiElement * * @description * The `ngShow` directive shows or hides the given HTML element based on the expression provided to * the `ngShow` attribute. * * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see * {@link ng.directive:ngCsp ngCsp}). * * ```html * *
          * * *
          * ``` * * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added * to the class attribute on the element causing it to become hidden. When truthy, the `.ng-hide` * CSS class is removed from the element causing the element not to appear hidden. * * ## Why is `!important` used? * * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as * simple as changing the display style on a HTML list item would make hidden elements appear * visible. This also becomes a bigger issue when dealing with CSS frameworks. * * By using `!important`, the show and hide behavior will work as expected despite any clash between * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a * developer chooses to override the styling to change how to hide an element then it is just a * matter of using `!important` in their own CSS code. * * ### Overriding `.ng-hide` * * By default, the `.ng-hide` class will style the element with `display: none !important`. If you * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. * * ```css * .ng-hide:not(.ng-hide-animate) { * /* These are just alternative ways of hiding an element */ * display: block!important; * position: absolute; * top: -9999px; * left: -9999px; * } * ``` * * By default you don't need to override anything in CSS and the animations will work around the * display style. * * ## A note about animations with `ngShow` * * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the * directive expression is true and false. This system works like the animation system present with * `ngClass` except that you must also include the `!important` flag to override the display * property so that the elements are not actually hidden during the animation. * * ```css * /* A working example can be found at the bottom of this page. */ * .my-element.ng-hide-add, .my-element.ng-hide-remove { * transition: all 0.5s linear; * } * * .my-element.ng-hide-add { ... } * .my-element.ng-hide-add.ng-hide-add-active { ... } * .my-element.ng-hide-remove { ... } * .my-element.ng-hide-remove.ng-hide-remove-active { ... } * ``` * * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property * to block during animation states - ngAnimate will automatically handle the style toggling for you. * * @animations * | Animation | Occurs | * |-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------| * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden. | * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngShow` expression evaluates to a truthy value and just before contents are set to visible. | * * @element ANY * @param {expression} ngShow If the {@link guide/expression expression} is truthy/falsy then the * element is shown/hidden respectively. * * @example * A simple example, animating the element's opacity: * Show:
          I show up when your checkbox is checked.
          .animate-show-hide.ng-hide { opacity: 0; } .animate-show-hide.ng-hide-add, .animate-show-hide.ng-hide-remove { transition: all linear 0.5s; } .check-element { border: 1px solid black; opacity: 1; padding: 10px; } it('should check ngShow', function() { var checkbox = element(by.model('checked')); var checkElem = element(by.css('.check-element')); expect(checkElem.isDisplayed()).toBe(false); checkbox.click(); expect(checkElem.isDisplayed()).toBe(true); });
          * *
          * @example * A more complex example, featuring different show/hide animations: * Show:
          I show up when your checkbox is checked.
          body { overflow: hidden; perspective: 1000px; } .funky-show-hide.ng-hide-add { transform: rotateZ(0); transform-origin: right; transition: all 0.5s ease-in-out; } .funky-show-hide.ng-hide-add.ng-hide-add-active { transform: rotateZ(-135deg); } .funky-show-hide.ng-hide-remove { transform: rotateY(90deg); transform-origin: left; transition: all 0.5s ease; } .funky-show-hide.ng-hide-remove.ng-hide-remove-active { transform: rotateY(0); } .check-element { border: 1px solid black; opacity: 1; padding: 10px; } it('should check ngShow', function() { var checkbox = element(by.model('checked')); var checkElem = element(by.css('.check-element')); expect(checkElem.isDisplayed()).toBe(false); checkbox.click(); expect(checkElem.isDisplayed()).toBe(true); });
          */ var ngShowDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngShow, function ngShowWatchAction(value) { // we're adding a temporary, animation-specific class for ng-hide since this way // we can control when the element is actually displayed on screen without having // to have a global/greedy CSS selector that breaks when other animations are run. // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }]; /** * @ngdoc directive * @name ngHide * @multiElement * * @description * The `ngHide` directive shows or hides the given HTML element based on the expression provided to * the `ngHide` attribute. * * The element is shown or hidden by removing or adding the `.ng-hide` CSS class onto the element. * The `.ng-hide` CSS class is predefined in AngularJS and sets the display style to none (using an * `!important` flag). For CSP mode please add `angular-csp.css` to your HTML file (see * {@link ng.directive:ngCsp ngCsp}). * * ```html * *
          * * *
          * ``` * * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added * to the class attribute on the element causing it to become hidden. When falsy, the `.ng-hide` * CSS class is removed from the element causing the element not to appear hidden. * * ## Why is `!important` used? * * You may be wondering why `!important` is used for the `.ng-hide` CSS class. This is because the * `.ng-hide` selector can be easily overridden by heavier selectors. For example, something as * simple as changing the display style on a HTML list item would make hidden elements appear * visible. This also becomes a bigger issue when dealing with CSS frameworks. * * By using `!important`, the show and hide behavior will work as expected despite any clash between * CSS selector specificity (when `!important` isn't used with any conflicting styles). If a * developer chooses to override the styling to change how to hide an element then it is just a * matter of using `!important` in their own CSS code. * * ### Overriding `.ng-hide` * * By default, the `.ng-hide` class will style the element with `display: none !important`. If you * wish to change the hide behavior with `ngShow`/`ngHide`, you can simply overwrite the styles for * the `.ng-hide` CSS class. Note that the selector that needs to be used is actually * `.ng-hide:not(.ng-hide-animate)` to cope with extra animation classes that can be added. * * ```css * .ng-hide:not(.ng-hide-animate) { * /* These are just alternative ways of hiding an element */ * display: block!important; * position: absolute; * top: -9999px; * left: -9999px; * } * ``` * * By default you don't need to override in CSS anything and the animations will work around the * display style. * * ## A note about animations with `ngHide` * * Animations in `ngShow`/`ngHide` work with the show and hide events that are triggered when the * directive expression is true and false. This system works like the animation system present with * `ngClass` except that you must also include the `!important` flag to override the display * property so that the elements are not actually hidden during the animation. * * ```css * /* A working example can be found at the bottom of this page. */ * .my-element.ng-hide-add, .my-element.ng-hide-remove { * transition: all 0.5s linear; * } * * .my-element.ng-hide-add { ... } * .my-element.ng-hide-add.ng-hide-add-active { ... } * .my-element.ng-hide-remove { ... } * .my-element.ng-hide-remove.ng-hide-remove-active { ... } * ``` * * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display property * to block during animation states - ngAnimate will automatically handle the style toggling for you. * * @animations * | Animation | Occurs | * |-----------------------------------------------------|------------------------------------------------------------------------------------------------------------| * | {@link $animate#addClass addClass} `.ng-hide` | After the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden. | * | {@link $animate#removeClass removeClass} `.ng-hide` | After the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible. | * * * @element ANY * @param {expression} ngHide If the {@link guide/expression expression} is truthy/falsy then the * element is hidden/shown respectively. * * @example * A simple example, animating the element's opacity: * Hide:
          I hide when your checkbox is checked.
          .animate-show-hide.ng-hide { opacity: 0; } .animate-show-hide.ng-hide-add, .animate-show-hide.ng-hide-remove { transition: all linear 0.5s; } .check-element { border: 1px solid black; opacity: 1; padding: 10px; } it('should check ngHide', function() { var checkbox = element(by.model('checked')); var checkElem = element(by.css('.check-element')); expect(checkElem.isDisplayed()).toBe(true); checkbox.click(); expect(checkElem.isDisplayed()).toBe(false); });
          * *
          * @example * A more complex example, featuring different show/hide animations: * Hide:
          I hide when your checkbox is checked.
          body { overflow: hidden; perspective: 1000px; } .funky-show-hide.ng-hide-add { transform: rotateZ(0); transform-origin: right; transition: all 0.5s ease-in-out; } .funky-show-hide.ng-hide-add.ng-hide-add-active { transform: rotateZ(-135deg); } .funky-show-hide.ng-hide-remove { transform: rotateY(90deg); transform-origin: left; transition: all 0.5s ease; } .funky-show-hide.ng-hide-remove.ng-hide-remove-active { transform: rotateY(0); } .check-element { border: 1px solid black; opacity: 1; padding: 10px; } it('should check ngHide', function() { var checkbox = element(by.model('checked')); var checkElem = element(by.css('.check-element')); expect(checkElem.isDisplayed()).toBe(true); checkbox.click(); expect(checkElem.isDisplayed()).toBe(false); });
          */ var ngHideDirective = ['$animate', function($animate) { return { restrict: 'A', multiElement: true, link: function(scope, element, attr) { scope.$watch(attr.ngHide, function ngHideWatchAction(value) { // The comment inside of the ngShowDirective explains why we add and // remove a temporary class for the show/hide animation $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { tempClasses: NG_HIDE_IN_PROGRESS_CLASS }); }); } }; }]; /** * @ngdoc directive * @name ngStyle * @restrict AC * * @description * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. * * @knownIssue * You should not use {@link guide/interpolation interpolation} in the value of the `style` * attribute, when using the `ngStyle` directive on the same element. * See {@link guide/interpolation#known-issues here} for more info. * * @element ANY * @param {expression} ngStyle * * {@link guide/expression Expression} which evals to an * object whose keys are CSS style names and values are corresponding values for those CSS * keys. * * Since some CSS style names are not valid keys for an object, they must be quoted. * See the 'background-color' style in the example below. * * @example
          Sample Text
          myStyle={{myStyle}}
          span { color: black; } var colorSpan = element(by.css('span')); it('should check ng-style', function() { expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); element(by.css('input[value=\'set color\']')).click(); expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); element(by.css('input[value=clear]')).click(); expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); });
          */ var ngStyleDirective = ngDirective(function(scope, element, attr) { scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { if (oldStyles && (newStyles !== oldStyles)) { forEach(oldStyles, function(val, style) { element.css(style, '');}); } if (newStyles) element.css(newStyles); }, true); }); /** * @ngdoc directive * @name ngSwitch * @restrict EA * * @description * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location * as specified in the template. * * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element * matches the value obtained from the evaluated expression. In other words, you define a container element * (where you place the directive), place an expression on the **`on="..."` attribute** * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default * attribute is displayed. * *
          * Be aware that the attribute values to match against cannot be expressions. They are interpreted * as literal string values to match against. * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the * value of the expression `$scope.someVal`. *
          * @animations * | Animation | Occurs | * |----------------------------------|-------------------------------------| * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | * * @usage * * ``` * * ... * ... * ... * * ``` * * * @scope * @priority 1200 * @param {*} ngSwitch|on expression to match against ng-switch-when. * On child elements add: * * * `ngSwitchWhen`: the case statement to match against. If match then this * case will be displayed. If the same match appears multiple times, all the * elements will be displayed. It is possible to associate multiple values to * the same `ngSwitchWhen` by defining the optional attribute * `ngSwitchWhenSeparator`. The separator will be used to split the value of * the `ngSwitchWhen` attribute into multiple tokens, and the element will show * if any of the `ngSwitch` evaluates to any of these tokens. * * `ngSwitchDefault`: the default case when no other case match. If there * are multiple default cases, all of them will be displayed when no other * case match. * * * @example
          selection={{selection}}
          Settings Div
          Home Span
          default
          angular.module('switchExample', ['ngAnimate']) .controller('ExampleController', ['$scope', function($scope) { $scope.items = ['settings', 'home', 'options', 'other']; $scope.selection = $scope.items[0]; }]); .animate-switch-container { position:relative; background:white; border:1px solid black; height:40px; overflow:hidden; } .animate-switch { padding:10px; } .animate-switch.ng-animate { transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; position:absolute; top:0; left:0; right:0; bottom:0; } .animate-switch.ng-leave.ng-leave-active, .animate-switch.ng-enter { top:-50px; } .animate-switch.ng-leave, .animate-switch.ng-enter.ng-enter-active { top:0; } var switchElem = element(by.css('[ng-switch]')); var select = element(by.model('selection')); it('should start in settings', function() { expect(switchElem.getText()).toMatch(/Settings Div/); }); it('should change to home', function() { select.all(by.css('option')).get(1).click(); expect(switchElem.getText()).toMatch(/Home Span/); }); it('should change to settings via "options"', function() { select.all(by.css('option')).get(2).click(); expect(switchElem.getText()).toMatch(/Settings Div/); }); it('should select default', function() { select.all(by.css('option')).get(3).click(); expect(switchElem.getText()).toMatch(/default/); });
          */ var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { return { require: 'ngSwitch', // asks for $scope to fool the BC controller module controller: ['$scope', function NgSwitchController() { this.cases = {}; }], link: function(scope, element, attr, ngSwitchController) { var watchExpr = attr.ngSwitch || attr.on, selectedTranscludes = [], selectedElements = [], previousLeaveAnimations = [], selectedScopes = []; var spliceFactory = function(array, index) { return function(response) { if (response !== false) array.splice(index, 1); }; }; scope.$watch(watchExpr, function ngSwitchWatchAction(value) { var i, ii; // Start with the last, in case the array is modified during the loop while (previousLeaveAnimations.length) { $animate.cancel(previousLeaveAnimations.pop()); } for (i = 0, ii = selectedScopes.length; i < ii; ++i) { var selected = getBlockNodes(selectedElements[i].clone); selectedScopes[i].$destroy(); var runner = previousLeaveAnimations[i] = $animate.leave(selected); runner.done(spliceFactory(previousLeaveAnimations, i)); } selectedElements.length = 0; selectedScopes.length = 0; if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { forEach(selectedTranscludes, function(selectedTransclude) { selectedTransclude.transclude(function(caseElement, selectedScope) { selectedScopes.push(selectedScope); var anchor = selectedTransclude.element; caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); var block = { clone: caseElement }; selectedElements.push(block); $animate.enter(caseElement, anchor.parent(), anchor); }); }); } }); } }; }]; var ngSwitchWhenDirective = ngDirective({ transclude: 'element', priority: 1200, require: '^ngSwitch', multiElement: true, link: function(scope, element, attrs, ctrl, $transclude) { var cases = attrs.ngSwitchWhen.split(attrs.ngSwitchWhenSeparator).sort().filter( // Filter duplicate cases function(element, index, array) { return array[index - 1] !== element; } ); forEach(cases, function(whenCase) { ctrl.cases['!' + whenCase] = (ctrl.cases['!' + whenCase] || []); ctrl.cases['!' + whenCase].push({ transclude: $transclude, element: element }); }); } }); var ngSwitchDefaultDirective = ngDirective({ transclude: 'element', priority: 1200, require: '^ngSwitch', multiElement: true, link: function(scope, element, attr, ctrl, $transclude) { ctrl.cases['?'] = (ctrl.cases['?'] || []); ctrl.cases['?'].push({ transclude: $transclude, element: element }); } }); /** * @ngdoc directive * @name ngTransclude * @restrict EAC * * @description * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. * * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. * * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing * content of this element will be removed before the transcluded content is inserted. * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case * that no transcluded content is provided. * * @element ANY * * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty * or its value is the same as the name of the attribute then the default slot is used. * * @example * ### Basic transclusion * This example demonstrates basic transclusion of content into a component directive. * * * *
          *
          *
          * {{text}} *
          *
          * * it('should have transcluded', function() { * var titleElement = element(by.model('title')); * titleElement.clear(); * titleElement.sendKeys('TITLE'); * var textElement = element(by.model('text')); * textElement.clear(); * textElement.sendKeys('TEXT'); * expect(element(by.binding('title')).getText()).toEqual('TITLE'); * expect(element(by.binding('text')).getText()).toEqual('TEXT'); * }); * *
          * * @example * ### Transclude fallback content * This example shows how to use `NgTransclude` with fallback content, that * is displayed if no transcluded content is provided. * * * * * * * * * Button2 * * * * it('should have different transclude element content', function() { * expect(element(by.id('fallback')).getText()).toBe('Button1'); * expect(element(by.id('modified')).getText()).toBe('Button2'); * }); * * * * @example * ### Multi-slot transclusion * This example demonstrates using multi-slot transclusion in a component directive. * * * *
          *
          *
          * * {{title}} *

          {{text}}

          *
          *
          *
          * * angular.module('multiSlotTranscludeExample', []) * .directive('pane', function() { * return { * restrict: 'E', * transclude: { * 'title': '?paneTitle', * 'body': 'paneBody', * 'footer': '?paneFooter' * }, * template: '
          ' + * '
          Fallback Title
          ' + * '
          ' + * '
          Fallback Footer
          ' + * '
          ' * }; * }) * .controller('ExampleController', ['$scope', function($scope) { * $scope.title = 'Lorem Ipsum'; * $scope.link = 'https://google.com'; * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; * }]); *
          * * it('should have transcluded the title and the body', function() { * var titleElement = element(by.model('title')); * titleElement.clear(); * titleElement.sendKeys('TITLE'); * var textElement = element(by.model('text')); * textElement.clear(); * textElement.sendKeys('TEXT'); * expect(element(by.css('.title')).getText()).toEqual('TITLE'); * expect(element(by.binding('text')).getText()).toEqual('TEXT'); * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); * }); * *
          */ var ngTranscludeMinErr = minErr('ngTransclude'); var ngTranscludeDirective = ['$compile', function($compile) { return { restrict: 'EAC', terminal: true, compile: function ngTranscludeCompile(tElement) { // Remove and cache any original content to act as a fallback var fallbackLinkFn = $compile(tElement.contents()); tElement.empty(); return function ngTranscludePostLink($scope, $element, $attrs, controller, $transclude) { if (!$transclude) { throw ngTranscludeMinErr('orphan', 'Illegal use of ngTransclude directive in the template! ' + 'No parent directive that requires a transclusion found. ' + 'Element: {0}', startingTag($element)); } // If the attribute is of the form: `ng-transclude="ng-transclude"` then treat it like the default if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { $attrs.ngTransclude = ''; } var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; // If the slot is required and no transclusion content is provided then this call will throw an error $transclude(ngTranscludeCloneAttachFn, null, slotName); // If the slot is optional and no transclusion content is provided then use the fallback content if (slotName && !$transclude.isSlotFilled(slotName)) { useFallbackContent(); } function ngTranscludeCloneAttachFn(clone, transcludedScope) { if (clone.length) { $element.append(clone); } else { useFallbackContent(); // There is nothing linked against the transcluded scope since no content was available, // so it should be safe to clean up the generated scope. transcludedScope.$destroy(); } } function useFallbackContent() { // Since this is the fallback content rather than the transcluded content, // we link against the scope of this directive rather than the transcluded scope fallbackLinkFn($scope, function(clone) { $element.append(clone); }); } }; } }; }]; /** * @ngdoc directive * @name script * @restrict E * * @description * Load the content of a ` Load inlined template
          it('should load template defined inside script tag', function() { element(by.css('#tpl-link')).click(); expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); }); */ var scriptDirective = ['$templateCache', function($templateCache) { return { restrict: 'E', terminal: true, compile: function(element, attr) { if (attr.type === 'text/ng-template') { var templateUrl = attr.id, text = element[0].text; $templateCache.put(templateUrl, text); } } }; }]; /* exported selectDirective, optionDirective */ var noopNgModelController = { $setViewValue: noop, $render: noop }; function chromeHack(optionElement) { // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 // Adding an

          documentation:liferay_1.png

          liferay_1.png

          liferay_1.png

          Date:
          2016/07/19 12:15
          Filename:
          liferay_1.png
          Format:
          PNG
          Size:
          185KB
          Width:
          1239
          Height:
          574


          Back to documentation:1.9:applications:liferay

          documentation:lemonldap-ng-password-expiration-warning.png

          lemonldap-ng-password-expiration-warning.png

          lemonldap-ng-password-expiration-warning.png

          Date:
          2016/07/19 12:14
          Filename:
          lemonldap-ng-password-expiration-warning.png
          Format:
          PNG
          Size:
          50KB
          Width:
          1591
          Height:
          778


          Back to documentation:1.9:authldap