diff --git a/ckanext-d4science_theme/.project b/ckanext-d4science_theme/.project
new file mode 100644
index 0000000..2c4c958
--- /dev/null
+++ b/ckanext-d4science_theme/.project
@@ -0,0 +1,17 @@
+
+
+ ckanext-d4science_theme
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/ckanext-d4science_theme/.settings/org.eclipse.core.resources.prefs b/ckanext-d4science_theme/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..77f221a
--- /dev/null
+++ b/ckanext-d4science_theme/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/setup.py=utf-8
diff --git a/ckanext-d4science_theme/LICENSE b/ckanext-d4science_theme/LICENSE
new file mode 100644
index 0000000..3ffc567
--- /dev/null
+++ b/ckanext-d4science_theme/LICENSE
@@ -0,0 +1,661 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
\ No newline at end of file
diff --git a/ckanext-d4science_theme/MANIFEST.in b/ckanext-d4science_theme/MANIFEST.in
new file mode 100644
index 0000000..06e4a8a
--- /dev/null
+++ b/ckanext-d4science_theme/MANIFEST.in
@@ -0,0 +1,2 @@
+include README.rst
+recursive-include ckanext/d4science_theme *.html *.json *.js *.less *.css
\ No newline at end of file
diff --git a/ckanext-d4science_theme/README.md b/ckanext-d4science_theme/README.md
new file mode 100644
index 0000000..8d71cb2
--- /dev/null
+++ b/ckanext-d4science_theme/README.md
@@ -0,0 +1,3 @@
+# ckanext-d4science_theme
+
+si parte dal plugin presente su gitlab
diff --git a/ckanext-d4science_theme/README.rst b/ckanext-d4science_theme/README.rst
new file mode 100644
index 0000000..ebc903a
--- /dev/null
+++ b/ckanext-d4science_theme/README.rst
@@ -0,0 +1,129 @@
+
+============
+ckanext-d4science_theme
+============
+
+The CKAN extension that implements the D4Science theme template used by D4Science Catalogues
+
+
+------------
+Requirements
+------------
+
+None
+
+------------
+Installation
+------------
+
+To install ckanext-d4science_theme:
+
+1. Activate your CKAN virtual environment, for example::
+
+ . /usr/lib/ckan/default/bin/activate
+
+2. Install the ckanext-d4science_theme Python package into your virtual environment::
+
+ pip install ckanext-d4science_theme
+
+3. Add ``d4science_theme`` to the ``ckan.plugins`` setting in your CKAN
+ config file (by default the config file is located at
+ ``/etc/ckan/default/production.ini``).
+
+4. Restart CKAN. For example if you've deployed CKAN with Apache on Ubuntu::
+
+ sudo service apache2 reload
+
+
+---------------
+Config Settings
+---------------
+
+Document any optional config settings here. For example::
+
+ # The minimum number of hours to wait before re-checking a resource
+ # (optional, default: 24).
+ ckanext.d4science_theme.some_setting = some_default_value
+
+
+------------------------
+Development Installation
+------------------------
+
+To install ckanext-d4science_theme for development, activate your CKAN virtualenv and
+do::
+
+ git clone https://code-repo.d4science.org/CKAN-Extensions/ckanext-d4science_theme.git
+ cd ckanext-d4science_theme
+ python setup.py develop
+ pip install -r dev-requirements.txt
+
+
+-----------------
+Running the Tests
+-----------------
+
+To run the tests, do::
+
+ nosetests --nologcapture --with-pylons=test.ini
+
+To run the tests and produce a coverage report, first make sure you have
+coverage installed in your virtualenv (``pip install coverage``) then run::
+
+ nosetests --nologcapture --with-pylons=test.ini --with-coverage --cover-package=ckanext.d4science_theme --cover-inclusive --cover-erase --cover-tests
+
+
+---------------------------------
+Registering ckanext-d4science_theme on PyPI
+---------------------------------
+
+ckanext-d4science_theme should be availabe on PyPI as
+https://pypi.python.org/pypi/ckanext-d4science_theme. If that link doesn't work, then
+you can register the project on PyPI for the first time by following these
+steps:
+
+1. Create a source distribution of the project::
+
+ python setup.py sdist
+
+2. Register the project::
+
+ python setup.py register
+
+3. Upload the source distribution to PyPI::
+
+ python setup.py sdist upload
+
+4. Tag the first release of the project on GitHub with the version number from
+ the ``setup.py`` file. For example if the version number in ``setup.py`` is
+ 0.0.1 then do::
+
+ git tag 0.0.1
+ git push --tags
+
+
+----------------------------------------
+Releasing a New Version of ckanext-d4science_theme
+----------------------------------------
+
+ckanext-d4science_theme is availabe on PyPI as https://pypi.python.org/pypi/ckanext-d4science_theme.
+To publish a new version to PyPI follow these steps:
+
+1. Update the version number in the ``setup.py`` file.
+ See `PEP 440 `_
+ for how to choose version numbers.
+
+2. Create a source distribution of the new version::
+
+ python setup.py sdist
+
+3. Upload the source distribution to PyPI::
+
+ python setup.py sdist upload
+
+4. Tag the new release of the project on GitHub with the version number from
+ the ``setup.py`` file. For example if the version number in ``setup.py`` is
+ 0.0.2 then do::
+
+ git tag 0.0.2
+ git push --tags
diff --git a/ckanext-d4science_theme/ckanext/__init__.py b/ckanext-d4science_theme/ckanext/__init__.py
new file mode 100644
index 0000000..2e2033b
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/__init__.py
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+ import pkg_resources
+ pkg_resources.declare_namespace(__name__)
+except ImportError:
+ import pkgutil
+ __path__ = pkgutil.extend_path(__path__, __name__)
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/__init__.py b/ckanext-d4science_theme/ckanext/d4science_theme/__init__.py
new file mode 100644
index 0000000..6cca9de
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/__init__.py
@@ -0,0 +1,19 @@
+# from flask import Flask, g
+# from ckan.lib.app_globals import app_globals
+# import ckan.plugins.toolkit as toolkit
+
+# app = Flask("d4Science")
+
+# def base_context():
+# return {
+# 'helpers': toolkit.h,
+# }
+
+# @app.context_processor
+# def inject_base_context():
+# return base_context()
+
+# # Aggiungi main_css al contesto globale di Flask
+# # @app.before_request
+# # def before_request():
+# # g.main_css = app_globals.main_css
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/assets/css/d4science_theme.css b/ckanext-d4science_theme/ckanext/d4science_theme/assets/css/d4science_theme.css
new file mode 100644
index 0000000..9e416b1
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/assets/css/d4science_theme.css
@@ -0,0 +1,844 @@
+/* =====================================================
+ The "account masthead" bar across the top of the site
+ ===================================================== */
+
+.account-masthead {
+ background-color: #ccc;
+}
+/* The "bubble" containing the number of new notifications. */
+.account-masthead .account .notifications a span {
+ background-color: #9fa0a2;
+}
+/* The text and icons in the user account info. */
+.account-masthead .account ul li a {
+ color: rgba(255, 255, 255, 0.6);
+}
+/* The user account info text and icons, when the user's pointer is hovering
+ over them. */
+.account-masthead .account ul li a:hover {
+ color: rgba(255, 255, 255, 0.7);
+/* background-color: black;*/
+}
+
+
+/* ========================================================================
+ The main masthead bar that contains the site logo, nav links, and search
+ ======================================================================== */
+
+.masthead {
+ background: #eee url("/bg-noise.png") repeat scroll 0 0;
+ border-top: 1px solid #555;
+ padding-top: 5px;
+ padding-bottom: 15px !important;
+ border-bottom: 1px solid #999;
+/* background-image: url("/bg-pattern.min.svg") !important; */
+}
+
+.masthead .navigation .nav-pills li a{
+ color: #187794;
+}
+
+a.logo > img{
+ margin-bottom: 5px;
+}
+
+/* The "navigation pills" in the masthead (the links to Datasets,
+ Organizations, etc) when the user's pointer hovers over them. */
+.masthead .navigation .nav-pills li a:hover {
+/* background-color: rgb(48, 48, 48);*/
+ color: white;
+}
+/* The "active" navigation pill (for example, when you're on the /dataset page
+ the "Datasets" link is active). */
+.masthead .navigation .nav-pills li.active a {
+ background-color: #d2d2d5;
+}
+/* The "box shadow" effect that appears around the search box when it
+ has the keyboard cursor's focus. */
+.masthead input[type="text"]:focus {
+ -webkit-box-shadow: inset 0px 0px 2px 0px rgba(0, 0, 0, 0.7);
+ box-shadow: inset 0px 0px 2px 0px rgba(0, 0, 0, 0.7);
+}
+
+
+/* ===========================================
+ The content in the middle of the front page
+ =========================================== */
+
+/* Remove the "box shadow" effect around various boxes on the page. */
+.box {
+ box-shadow: none;
+}
+.hero {
+ background: #FEFEFE repeat scroll 0 0 !important;
+}
+/* Remove the borders around the "Welcome to CKAN" and "Search Your Data"
+ boxes. */
+.hero .box {
+ /*border: none;*/
+ margin-top: 10px !important;
+}
+/* Change the colors of the "Search Your Data" box. */
+.homepage .module-search .module-content {
+ color: rgb(68, 68, 68);
+ background-color: white;
+}
+/* Change the background color of the "Popular Tags" box. */
+.homepage .module-search .tags {
+ background-color: #fcfcfc;
+}
+
+.homepage-title{
+ font-size: 20px;
+ font-weight: bold;
+ color: #202020;
+ margin-bottom: 20px;
+}
+
+/* Change the background color of the "Popular Tags" box. */
+.homepage .module-search h3{
+ color: #444;
+}
+
+/* Remove some padding. This makes the bottom edges of the "Welcome to CKAN"
+ and "Search Your Data" boxes line up. */
+.module-content:last-child {
+ /*padding-bottom: 0px;*/
+}
+.homepage .module-search {
+ padding: 0px;
+}
+/* Add a border line between the top and bottom halves of the front page. */
+.homepage [role="main"] {
+ border-bottom: 1px solid #bbb;
+ padding: 10px 0;
+}
+
+.homepage .stats ul li a b{
+ font-size: 30px !important;
+}
+
+[role="main"], .main {
+/* background: #f5f6fa url("/bg-pattern.min.svg") repeat; scroll 0 0;*/
+ /*background: #fafafa url("/bg-pattern.svg") repeat; scroll 0 0;*/
+ background: #fdfdfd none repeat scroll 0 0;
+ min-height: 0px !important;
+}
+
+.media-item-homepage {
+ background-color: white;
+ border-radius: 3px;
+ float: left;
+ margin: 15px 0 0 15px;
+ overflow: hidden;
+ padding-left: 10px;
+ padding-right: 10px;
+ position: relative;
+ text-align: center;
+ width: 150px;
+}
+
+.media-heading-homepage {
+ font-size: 16px;
+ hyphens: auto;
+ line-height: 1.3;
+ margin: 5px 0;
+}
+
+.media-grid-homepage {
+ -moz-border-bottom-colors: none;
+ -moz-border-left-colors: none;
+ -moz-border-right-colors: none;
+ -moz-border-top-colors: none;
+/* background: #fbfbfb url("../../../base/images/bg.png") repeat scroll 0 0;
+ border-color: #dddddd;
+ border-image: none;
+ border-style: solid;
+ border-width: 1px 0;*/
+ list-style: outside none none;
+ margin: 0 -10px;
+ padding-bottom: 15px;
+}
+.media-grid-homepage::before, .media-grid::after {
+ content: "";
+ display: table;
+ line-height: 0;
+}
+.media-grid-homepage::after {
+ clear: both;
+}
+
+.background-circle{
+ padding: 10px 10px;
+ display: inline-block !important;
+ -webkit-border-radius: 90px;
+ -moz-border-radius: 90px;
+ border-radius: 90px;
+ background-color: #4679b2;
+ text-decoration: none !important;
+}
+
+.color-white{
+ color: white !important;
+}
+
+.badge-circle {
+ border-radius: 50% 50% 50% 50% !important;
+ height: 60px;
+ text-align: center;
+ vertical-align: middle;
+ width: 65px;
+ background-color: #4679b2;
+ display: inline-block !important;
+ padding-top: 5px;
+ text-decoration: none !important;
+}
+
+/* ====================================
+ The footer at the bottom of the site
+ ==================================== */
+
+.site-footer, body {
+ background-color: #bbb;
+ font-family: "Lato","Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 16px;
+}
+/* The text in the footer. */
+.site-footer,
+.site-footer label,
+.site-footer small {
+ color: rgba(255, 255, 255, 0.6);
+}
+/* The link texts in the footer. */
+.site-footer a {
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.site-footer-internal{
+ min-height: 10px;
+ padding: 2px 0;
+ font-size: 12px;
+}
+
+.site-footer-internal {
+ /*background-color: rgba(255, 255, 255, 0.6);*/
+ text-align: center;
+ /*display: inline-block;*/
+}
+
+.site-footer-internal,
+.site-footer-internal label,
+.site-footer-internal small {
+
+}
+
+.site-footer-internal a {
+ display: inline-block;
+}
+
+.d4s-hide-text {
+ background-color: transparent;
+ border: 0 none;
+ color: transparent;
+ font: 0px/0 a;
+ text-shadow: none;
+}
+
+.d4science-footer-logo {
+ background: url("/gCube_70.png") no-repeat scroll left top rgba(0, 0, 0, 0);
+ height: 32px;
+ margin-top: 2px;
+ text-indent: -900em;
+ width: 75px;
+}
+
+.d4s-ckan-footer-logo {
+ background: rgba(0, 0, 0, 0) url("/ckan-logo-footer.png") no-repeat scroll left top;
+ height: 21px;
+ margin-top: 2px;
+ text-indent: -900em;
+ width: 69px;
+}
+
+.site-footer-d4science {
+ font-size: 14px;
+ color: #f5f5f5;
+ text-align: center;
+ height: 25px;
+ padding-top: 5px;
+ background-color: #7F7F7F;
+}
+
+.site-footer-d4science a {
+ font-weight: bold;
+ text-decoration: none;
+ color: white;
+}
+
+
+/* ====================================
+ Base elements of the site
+ ==================================== */
+
+div .principaltitle {
+ color: inherit;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 1.2;
+ margin: 15px 0;
+ text-rendering: optimizelegibility;
+ word-break: break-all;
+ padding-bottom: 10px;
+ padding-top: 5px;
+ border-bottom: 1px solid #eee;
+}
+
+div .notes {
+ color: #444444;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 14px;
+ line-height: 1.3;
+ text-align: justify;
+ word-break: break-all;
+}
+
+div .infotitle {
+ font-size: 15px;
+ hyphens: auto;
+ line-height: 1.3;
+ word-break: break-all;
+ font-weight: bold;
+}
+
+.toolbar .breadcrumb{
+ font-size: 16px !important;
+}
+
+.box{
+ border: 0px !important;
+}
+
+div .sectiontitle{
+ color: #9F9F9F;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 17px;
+ font-weight: bold;
+ margin: 20px 0;
+ margin-top: 20px;
+ margin-bottom: 10px;
+ text-rendering: optimizelegibility;
+}
+
+section .well {
+ background-color: #fdfdfd !important;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ box-shadow: none !important;
+ margin-bottom: 20px;
+ min-height: 20px;
+
+}
+
+.page-heading {
+ font-size: 18px;
+ line-height: 1.2;
+ margin-top: 20px;
+ margin-bottom: 0px;
+}
+
+#dataset-resources .resource-list{
+ background-color: #fdfdfd !important;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ box-shadow: none !important;
+ margin: -1px 0 !important;
+}
+
+.wrapper{
+ border: 1px solid #d0d0d0;
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
+ border-radius: 3px
+}
+
+.home-popular{
+ padding-top: 25px;
+}
+
+.logo-homepage{
+ max-height: 60px;
+}
+
+.statistics-show{
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ color: #444444;
+ text-decoration: none;
+}
+
+.d4s-center-cropped{
+ text-align: center;
+ background-color: #eee;
+ border: 1px solid #ddd;
+ padding-bottom: 10px;
+ padding-top: 10px;
+}
+
+.tag-list {
+ font-size: 14px;
+}
+
+
+/* ====================================
+ Acquired Dataset
+ ==================================== */
+.label-acquired {
+ background-color: #55a1ce;
+}
+
+.label-owner {
+ background-color: #e0051e;
+}
+
+.divider {
+ margin-left:10px;
+ height:auto;
+ display:inline-block;
+}
+
+/* ====================================
+ List Dataset
+ ==================================== */
+
+/*LEFT
+.show_meatadatatype {
+ color: white;
+ display: inline-block; // Inline elements with width and height. TL;DR they make the icon buttons stack from left-to-right instead of top-to-bottom
+ position: relative; // All 'absolute'ly positioned elements are relative to this one
+ margin-bottom: 20px;
+ margin-left: 25px;
+}
+*/
+
+/*RIGHT*/
+.show_meatadatatype {
+ color: white;
+ display: inline-block;
+ float: right;
+ margin-right: 2px;
+ margin-top: -20px;
+ position: relative;
+}
+
+
+
+/* LEFT
+ * Position the badge within the relatively positioned button
+.button__badge {
+ background-color: #fa3e3e;
+ border-radius: 2px;
+ color: white;
+
+ padding: 1px 6px;
+ font-size: 10px;
+
+ position: absolute;
+ top: 0;
+ right: 0;
+}*/
+
+
+
+/* RIGTH */
+.button__badge {
+ color: #808080;
+ padding: 0px 2px;
+ font-size: 10px;
+ top: 0;
+ right: 0;
+ font-family: sans-serif, times, georgia;
+}
+
+/* ====================================
+ Modal Popup
+ ==================================== */
+
+/* Popup container - can be anything you want */
+/* The Modal (background) */
+.d4s_modal {
+ display: none; /* Hidden by default */
+ position: fixed; /* Stay in place */
+ z-index: 10001; /* Sit on top (NB. At 1000 there is the zoom in/out of the Map Widget)*/
+ /*padding-top: 100px;*/ /* Location of the box */
+ left: 0;
+ top: 0;
+ width: 100%; /* Full width */
+ height: 100%; /* Full height */
+ overflow: auto; /* Enable scroll if needed */
+ background-color: rgb(0,0,0); /* Fallback color */
+ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
+}
+
+/* Modal Content */
+.d4s_modal-content {
+ background-color: #fefefe;
+ /*margin: auto;*/
+ padding: 20px;
+ border: 1px solid #888;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ position: absolute;
+ left: 50%;
+ margin-left: -225px;
+ width: 450px;
+}
+
+/* The Close Button */
+.d4s_close {
+ color: #aaaaaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+ padding-left: 20px;
+}
+
+.d4s_close:hover,
+.d4s_close:focus {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.d4s_div_clickable{
+ cursor: pointer;
+}
+
+/*====================================
+D4S POPUP
+======================================*/
+
+/* Popup container - can be anything you want */
+.popupD4SNoArrow {
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+/* The actual popup */
+.popupD4SNoArrow .popuptext {
+ visibility: hidden;
+ width: 300px;
+ background-color: #555;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 8px;
+ position: absolute;
+ z-index: 1;
+ bottom: 125%;
+ left: 50%;
+ margin-left: -150px;
+}
+
+/* Toggle this class - hide and show the popup */
+.popupD4SNoArrow .show {
+ visibility: visible;
+ -webkit-animation: fadeIn 1s;
+ animation: fadeIn 1s;
+}
+
+
+/* Popup container - can be anything you want */
+.popupD4S {
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+/* The actual popup */
+.popupD4S .popuptext {
+ visibility: hidden;
+ width: 300px;
+ background-color: #555;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 8px;
+ position: absolute;
+ z-index: 1;
+ bottom: 125%;
+ left: 50%;
+ margin-left: -150px;
+}
+
+/* Popup arrow */
+.popupD4S .popuptext::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: #555 transparent transparent transparent;
+}
+
+/* Toggle this class - hide and show the popup */
+.popupD4S .show {
+ visibility: visible;
+ -webkit-animation: fadeIn 1s;
+ animation: fadeIn 1s;
+}
+
+/* Add animation (fade in the popup) */
+@-webkit-keyframes fadeIn {
+ from {opacity: 0;}
+ to {opacity: 1;}
+}
+
+@keyframes fadeIn {
+ from {opacity: 0;}
+ to {opacity:1 ;}
+}
+
+/*====================================
+D4S PACKAGE
+======================================*/
+
+.graphic-preview-style {
+ text-align: center;
+ border-top: 1px dotted #DDD;
+ padding-top: 10px;
+ padding-bottom: 0px;
+ margin-top: 15px;
+}
+
+.graphic-preview-style a{
+ font-size: 13px;
+}
+
+.graphic-preview-style img{
+ max-width: 100% !important;
+ height: auto;
+
+}
+
+.graphic-preview-style #graphic-title{
+ font-size: 13px;
+
+}
+
+.nav-item{
+ word-wrap:break-word;
+ }
+
+/*====================================
+RESOURCE_LIST RESOURCE_ITEM INTO PACKAGE
+======================================*/
+
+.required-access {
+ font-style: italic;
+ font-weight: bold;
+ padding: 5px;
+}
+
+/*====================================
+LINK TO RESOURCES FROM PACKAGE LIST
+======================================*/
+
+.dataset-resources li a {
+ background-color: #187794;
+}
+
+.label[data-format="csw"], .label[data-format*="csw"] {
+ background-color: #e6b800;
+}
+
+/*====================================
+CSS APPLIED TO Similar GRSF Records
+======================================*/
+
+.my-grsf-table{
+ word-break: break-all;
+}
+
+.my-grsf-table tr td{
+ width: inherit;
+}
+
+.my-grsf-table tr td:first-child{
+ width: 82px !important;
+}
+
+/*====================================
+CSS APPLIED in base.html
+======================================*/
+
+#ckan-page-loading {
+ display: none;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ margin-top: -130px;
+ margin-left: -130px;
+ width: 260px;
+ height: 260px;
+ z-index: 100000;
+ background-image: url("/pageloading.gif");
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+/*====================================
+CSS APPLIED in search_for_location.html
+======================================*/
+
+div#search-for-location {
+
+}
+
+div#search-for-location #dataset-map {
+ position: relative !important;
+ top: +0px !important;
+}
+
+div#search-for-location #dataset-map-container {
+ height: 300px;
+}
+
+
+div#search-for-location .module-heading {
+ display: none;
+}
+
+div#search-for-extent{
+ padding-top: 10px;
+}
+
+/*====================================
+CSS APPLIED in additional_info.html
+======================================*/
+.qr-code-table {
+ width: 100%;
+}
+
+.qr-code-table td {
+ width: 85%;
+ border: 1px solid #e3e3e3;
+}
+
+.qr-code-table td:first-child {
+ padding-left: 10px;
+ border-right-style: none;
+
+}
+
+.qr-code-table td:last-child {
+ width: 105px;
+ text-align: center;
+ border-left-style: none;
+
+}
+
+/* MAX-WITH APPIED TO QR_CODE */
+.qr-code-table img {
+ max-width: 100px;
+ height: auto;
+}
+
+
+/*====================================
+CSS APPLIED FROM JSON TO HTML TABLE
+======================================*/
+
+.json-to-html-table-column{
+ word-break: break-all;
+}
+
+.json-to-html-table-column tr td{
+ width: inherit;
+}
+
+.json-to-html-table-column tr td:first-child{
+ font-weight: bold;
+ color: #5a5a5a;
+}
+
+/*====================================
+CSS APPLIED into custom_form_fields
+======================================*/
+.disabled-div{
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+.disabled-div input[type="text"]{
+ background: #f1f1f1;
+}
+
+/*====================================
+CSS APPLIED into extra_table.html
+======================================*/
+
+.read-more-state {
+ display: none;
+}
+
+.read-more-target {
+ opacity: 0;
+ max-height: 0;
+ font-size: 0;
+ transition: .25s ease;
+}
+
+.read-more-state:checked ~ .read-more-wrap .read-more-target {
+ opacity: 1;
+ font-size: inherit;
+ max-height: 999em;
+ content: "";
+}
+
+.read-more-state ~ .read-more-trigger:before {
+ content: 'Show more';
+}
+
+.read-more-state:checked ~ .read-more-trigger:before {
+ content: 'Show less';
+}
+
+.read-more-state:checked ~ .read-more-wrap::after {
+ content: "";
+}
+
+.read-more-trigger {
+ cursor: pointer;
+ display: inline-block;
+ padding: 0 .5em;
+ color: #187794;
+ font-size: .9em;
+ line-height: 2;
+ border: 1px solid #ddd;
+ border-radius: .25em;
+ font-weight: normal;
+}
+
+.read-more-trigger::after {
+ content: "";
+}
+
+.read-more-wrap {
+ margin-bottom: 2px;
+}
+
+.read-more-wrap::after{
+ content: " ...";
+
+}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/assets/js/d4science_scripts.js b/ckanext-d4science_theme/ckanext/d4science_theme/assets/js/d4science_scripts.js
new file mode 100644
index 0000000..36b79e3
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/assets/js/d4science_scripts.js
@@ -0,0 +1,305 @@
+/* =====================================================
+JavaScript used by CKAN plugin: 'd4science_theme'
+Created by Francesco Mangiacrapa ISTI-CNR Pisa, Italy
+===================================================== */
+
+
+//old script starts
+CKAN_D4S_Breadcrumb_Manager = {
+
+ breadcrumbShow : function (show) {
+
+ var breadcrumb = document.getElementById('ckan-breadcrumb');
+ console.log('breadcrumb is '+breadcrumb)
+ if(breadcrumb){
+ if(show){
+ breadcrumb.style.display = 'block';
+ this.organizationTreeShow(true);
+ } else{
+ breadcrumb.style.display = 'none';
+ this.organizationTreeShow(false);
+ }
+ }
+
+ //var elements = document.getElementsByTagName('a');
+ //for(var i = 0, len = elements.length; i < len; i++) {
+ // elements[i].onclick = function () {
+ // //alert("You clicked an external link to: " + this.href);
+ // //window.parent.add_hide_breadcrumb_to_dom(false);
+ // this.add_hide_breadcrumb_to_dom(false);
+ // }
+ //}
+ },
+
+ organizationTreeShow : function (show) {
+ var trees = document.getElementsByClassName("hierarchy-tree-top");
+
+ if (trees){
+ for (i = 0; i < trees.length; i++) {
+ if(show){
+ trees[i].style.display = 'block';
+ } else{
+ trees[i].style.display = 'none';
+ }
+ }
+ }
+ },
+
+ checkBreadcrumbShow : function () {
+
+ var showBdc = this.getSessionStorageItem("showbreadcrumb")
+ //console.log("showBdc is: "+showBdc)
+ if(showBdc != undefined && showBdc=="false"){
+ console.log("Show breadcrumb false");
+ this.breadcrumbShow(false);
+ }else{
+ console.log("Show breadcrumb true");
+ this.breadcrumbShow(true);
+ }
+ },
+
+
+ setSessionStorageItem : function (item_key, item_value) {
+
+ // Check browser support
+ if (typeof(Storage) !== "undefined") {
+ // Store
+ sessionStorage.setItem(item_key, item_value);
+ return true;
+ } else {
+ console.log("Sorry, your browser does not support Web Storage...");
+ return false;
+ }
+ },
+
+
+ getSessionStorageItem : function (item_key) {
+
+ // Check browser support
+ if (typeof(Storage) !== "undefined") {
+ // Store
+ return sessionStorage.getItem(item_key);
+ } else {
+ console.log("Sorry, your browser does not support Web Storage...");
+ return undefined;
+ }
+ }
+
+}
+
+
+CKAN_D4S_Functions_Util = {
+
+ getPosition : function(canvas, event){
+ var x = new Number();
+ var y = new Number();
+ try {
+ if (event.clientX != undefined && event.clientY != undefined)
+ {
+
+ x = event.clientX;
+ y = event.clientY;
+ }
+ else // Firefox method to get the position
+ {
+ x = event.clientX + document.body.scrollLeft +
+ document.documentElement.scrollLeft;
+ y = event.clientY + document.body.scrollTop +
+ document.documentElement.scrollTop;
+ }
+ x -= canvas.offsetLeft;
+ y -= canvas.offsetTop;
+ }catch (err) {
+ //silent error
+ }
+ return '{"posX": "'+x+'", "posY": "'+y+'"}';
+ },
+
+ // When the user clicks on div, open the popup
+ showPopupD4S : function(event, my_div, my_popup_left_position) {
+ var popup = document.getElementById(my_div);
+ var clickPosition = this.getPosition(my_div, event)
+ var myPosition = JSON.parse(clickPosition);
+ this.closePopups(my_div);
+ // When the user clicks anywhere outside of the modal, close it
+ /*window.onclick = function(event) {
+ if (event.target != popup) {
+ popup.style.display = "none";
+ }
+ }*/
+ popup.classList.toggle("show");
+
+ if(my_popup_left_position){
+ popup.style.left = my_popup_left_position;
+ }
+ else if (myPosition.posX){
+ popup.style.left = myPosition.posX + "px";
+ }
+ },
+
+ closePopups : function ($target) {
+ var popups = document.getElementsByClassName('popuptext');
+ for (i = 0; i < popups.length; i++) {
+ if (popups[i].getAttribute('id') != $target) {
+ popups[i].classList.remove('show');
+ }
+ }
+ },
+
+ checkURL : function (url) {
+ //console.log('checking url: '+url)
+ var regex = new RegExp('^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/|www\.|ftp:\/\/)+[^ "]+$');
+ if (regex.test(url)) {
+ return true;
+ }
+ return false;
+ }
+
+}
+
+CKAN_D4S_HTMLMessage_Util = {
+
+postHeightToPortlet : function (selectedProduct, product) {
+ var h = document.body.scrollHeight + "px";
+ var p = "";
+ var msg = "";
+ //WORK AROUND IF TWO MESSAGES ARE SENT FROM A PAGE OF A PRODUCT
+ //THE MESSAGE WITH 'NULL' PRODUCT IS SKIPPED
+ //console.log("window.location.pathname? "+window.location.pathname);
+ var pathArray = window.location.pathname.split('/');
+ var productContext = "dataset";
+ if(pathArray.length>1){
+ //console.log("pathArray is: "+pathArray);
+ var secondLevelLocation = pathArray[1]; //it is the second level location
+ //console.log("secondLevelLocation is: "+secondLevelLocation);
+ //console.log("h is: "+h);
+ if(secondLevelLocation == productContext){ //is it product context?
+ if(product !== 'undefined' && product !== null){
+ p = product;
+ //console.log("product selected is: "+p);
+ }else{
+ //console.log("product is null or undefined, passing only height");
+ msg = "{\"height\": \""+h+"\"}";
+ //window.postMessage(msg,'*');
+ this.postMessageToParentWindow(msg);
+ return;
+ }
+ }
+ }
+
+ //msg = "{'height': '"+h+"', 'product': '"+p+"'}";
+ msg = "{\"height\": \""+h+"\", \"product\": \""+p+"\"}";
+ //window.postMessage(msg,'*');
+ //console.log("posting message in the window: "+msg);
+ this.postMessageToParentWindow(msg);
+ },
+
+ postMessageToParentWindow : function (msg) {
+ //window.postMessage(msg,'*');
+ //console.log("posting message in the window: "+msg);
+ if(window.parent!=null){
+ console.log("posting message in the parent window: "+msg);
+ window.parent.postMessage(msg,'*');
+ }
+ return;
+ }
+
+}
+
+CKAN_D4S_JSON_Util = {
+
+
+//ADDED by Francesco Mangiacrapa
+appendHTMLToElement : function(containerID, elementHTML){
+
+ var divContainer = document.getElementById(containerID);
+ divContainer.innerHTML = "";
+ divContainer.appendChild(elementHTML);
+},
+
+//ADDED by Francesco Mangiacrapa
+jsonToHTML : function(containerID, cssClassToTable) {
+
+ try
+ {
+ var jsonTxt = document.getElementById(containerID).innerHTML;
+ var jsonObj = JSON.parse(jsonTxt);
+ //console.log(jsonObj.length)
+
+ if(jsonObj.length==undefined)
+ jsonObj = [jsonObj]
+ //console.log(jsonObj.length)
+
+ // EXTRACT VALUE FOR HTML HEADER.
+ var col = [];
+ for (var i = 0; i < jsonObj.length; i++) {
+ for (var key in jsonObj[i]) {
+ //console.log('key json' +key)
+ if (col.indexOf(key) === -1) {
+ col.push(key);
+ }
+ }
+ }
+
+ // CREATE DYNAMIC TABLE.
+ var table = document.createElement("table");
+ var addDefaultCss = "json-to-html-table-column";
+ if(cssClassToTable){
+ addDefaultCss = cssClassToTable;
+ }
+ try{
+ table.classList.add(addDefaultCss);
+ }catch(e){
+ console.log('invalid css add', e);
+ }
+
+ // ADD JSON DATA TO THE TABLE AS ROWS.
+ for (var i = 0; i < col.length; i++) {
+
+ tr = table.insertRow(-1);
+ var firstCell = tr.insertCell(-1);
+ //firstCell.style.cssText="font-weight: bold; text-align: center; vertical-align: middle;";
+ firstCell.innerHTML = col[i];
+ for (var j = 0; j < jsonObj.length; j++) {
+ var tabCell = tr.insertCell(-1);
+ var theValue = jsonObj[j][col[i]];
+ /* console.log(theValue + ' is url? '+isUrl);*/
+ if(CKAN_D4S_Functions_Util.checkURL(theValue)){
+ theValue = ''+theValue+'';
+ }
+
+ tabCell.innerHTML = theValue;
+ }
+ }
+
+ // FINALLY ADD THE NEWLY CREATED TABLE WITH JSON DATA TO A CONTAINER.
+ this.appendHTMLToElement(containerID, table);
+
+ }
+ catch(e){
+ console.log('invalid json', e);
+ }
+}
+}
+
+
+//Task #8032
+window.addEventListener("message",
+function (e) {
+
+ var curr_loc = window.location.toString()
+ var orgin = e.origin.toString()
+ if(curr_loc.startsWith(orgin)){
+ //alert("ignoring message from myself");
+ return;
+ }
+ //console.log("origin: "+e.data)
+ if(e.data == null)
+ return;
+
+ var pMess = JSON.parse(e.data)
+ //console.log(pMess.explore_vres_landing_page)
+ window.linktogateway = pMess.explore_vres_landing_page;
+},false);
+//old script ends
+
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/assets/webassets.yml b/ckanext-d4science_theme/ckanext/d4science_theme/assets/webassets.yml
new file mode 100644
index 0000000..5b697a1
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/assets/webassets.yml
@@ -0,0 +1,15 @@
+d4science-js:
+ filter: rjsmin
+ output: ckanext-d4science_theme/ckanext/d4science_theme/assets/js/d4science_scripts.js
+ contents:
+ - js/d4science_scripts.js
+ extra:
+ preload:
+ - base/main
+
+d4science-css:
+ filter: cssrewrite
+ output: ckanext-d4science_theme/ckanext/d4science_theme/assets/css/d4science_theme.css
+ contents:
+ - css/d4science_theme.css
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/controllers/__init__.py b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/__init__.py
new file mode 100644
index 0000000..68f2e01
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/__init__.py
@@ -0,0 +1,4 @@
+#The __init__.py files are required to make Python
+#treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path.
+#See: https://docs.python.org/3/tutorial/modules.html#packages
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/controllers/home.py b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/home.py
new file mode 100644
index 0000000..c5746d5
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/home.py
@@ -0,0 +1,83 @@
+import logging
+# from ckan.controllers.home import HomeController
+import ckan.plugins as p
+from ckan.common import _, g, c
+from collections import OrderedDict
+
+import ckan.lib.search as search
+import ckan.model as model
+import ckan.logic as logic
+import ckan.lib.maintain as maintain
+import ckan.lib.base as base
+import ckan.lib.helpers as h
+
+from flask import render_template
+
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+class d4SHomeController():
+
+ #Overriding controllers.HomeController.index method
+ def index(self):
+ try:
+ # package search
+ context = {'model': model, 'session': model.Session,'user': c.user, 'auth_user_obj': c.userobj}
+
+ facets = OrderedDict()
+
+ default_facet_titles = {
+ 'organization': _('Organizations'),
+ 'groups': _('Groups'),
+ 'types':_('Types'),
+ 'tags': _('Tags'),
+ 'res_format': _('Formats'),
+ 'license_id': _('Licenses'),
+ }
+
+ for facet in g.facets:
+ if facet in default_facet_titles:
+ facets[facet] = default_facet_titles[facet]
+ else:
+ facets[facet] = facet
+
+ # Facet titles
+ for plugin in p.PluginImplementations(p.IFacets):
+ facets = plugin.dataset_facets(facets, 'dataset')
+
+ c.facet_titles = facets
+
+ data_dict = {
+ 'q': '*:*',
+ 'facet.field': list(facets.keys()),
+ 'rows': 4,
+ 'start': 0,
+ 'sort': 'views_recent desc',
+ 'fq': 'capacity:"public"'
+ }
+ query = logic.get_action('package_search')(context, data_dict)
+ c.search_facets = query['search_facets']
+ c.package_count = query['count']
+ c.datasets = query['results']
+
+ #print "c.search_facets: "
+ #print " ".join(c.search_facets)
+
+ except search.SearchError:
+ c.package_count = 0
+
+ if c.userobj and not c.userobj.email:
+ url = h.url_for(controller='user', action='edit')
+ msg = _('Please update your profile'
+ ' and add your email address. ') % url + \
+ _('%s uses your email address'
+ ' if you need to reset your password.') \
+ % g.site_title
+ h.flash_notice(msg, allow_html=True)
+
+ #return base.render('home/index.html', cache_force=True)
+ #ckan 2.10 :
+ return render_template('home/index.html')
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/controllers/organization.py b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/organization.py
new file mode 100644
index 0000000..90b85cc
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/organization.py
@@ -0,0 +1,134 @@
+# encoding: utf-8
+
+import re
+
+#import ckan.controllers.group as group
+import ckan.plugins as plugins
+import logging
+import datetime
+from urllib.parse import urlencode
+
+import ckan.plugins.toolkit as toolkit
+import ckan.lib.base as base
+import ckan.lib.helpers as h
+import ckan.lib.maintain as maintain
+import ckan.lib.navl.dictization_functions as dict_fns
+import ckan.logic as logic
+import ckan.lib.search as search
+import ckan.model as model
+import ckan.authz as authz
+import ckan.lib.plugins
+import ckan.plugins as plugins
+from ckan.common import c, request, _
+
+
+'''
+Created by Francesco Mangiacrapa, see: #8964
+'''
+class OrganizationVREController(plugins.toolkit.DefaultOrganizationForm): #changed for 2.10 : GroupController -> defaultOrganizationForm
+ ''' The organization controller is for Organizations, which are implemented
+ as Groups with is_organization=True and group_type='organization'. It works
+ the same as the group controller apart from:
+ * templates and logic action/auth functions are sometimes customized
+ (switched using _replace_group_org)
+ * 'bulk_process' action only works for organizations
+
+ Nearly all the code for both is in the GroupController (for historical
+ reasons).
+ '''
+
+ group_types = ['organization']
+
+ def _guess_group_type(self, expecting_name=False):
+ return 'organization'
+
+ def _replace_group_org(self, string):
+ ''' substitute organization for group if this is an org'''
+ return re.sub('^group', 'organization', string)
+
+ def _update_facet_titles(self, facets, group_type):
+ for plugin in plugins.PluginImplementations(plugins.IFacets):
+ facets = plugin.organization_facets(
+ facets, group_type, None)
+
+ def index(self):
+ group_type = self._guess_group_type()
+
+ page = h.get_page_number(request.args) or 1
+ items_per_page = 21
+
+ context = {'model': model, 'session': model.Session,
+ 'user': c.user, 'for_view': True,
+ 'with_private': False}
+
+ q = c.q = request.args.get('q', '')
+ sort_by = c.sort_by_selected = request.args.get('sort')
+ try:
+ logic.check_access('site_read', context)
+ logic.check_access('group_list', context)
+ except ckan.plugins.toolkit.NotAuthorized:
+ abort(403, _('Not authorized to see this page'))
+
+ # pass user info to context as needed to view private datasets of
+ # orgs correctly
+ if c.userobj:
+ context['user_id'] = c.userobj.id
+ context['user_is_admin'] = c.userobj.sysadmin
+
+ data_dict_global_results = {
+ 'all_fields': False,
+ 'q': q,
+ 'sort': sort_by,
+ 'type': group_type or 'group',
+ }
+ global_results = toolkit.get_action('group_list')(context,
+ data_dict_global_results)
+
+ data_dict_page_results = {
+ 'all_fields': True,
+ 'q': q,
+ 'sort': sort_by,
+ 'type': group_type or 'group',
+ 'limit': items_per_page,
+ 'offset': items_per_page * (page - 1),
+ }
+ page_results = toolkit.get_action('group_list')(context,
+ data_dict_page_results)
+
+ c.page = h.Page(
+ collection=global_results,
+ page=page,
+ url=h.pager_url,
+ items_per_page=items_per_page,
+ )
+
+ c.page.items = page_results
+ return base.render('organization_vre/index.html',
+ extra_vars={'group_type': group_type})
+
+
+ def read(self, id, limit=20):
+ group_type = self._ensure_controller_matches_group_type(
+ id.split('@')[0])
+
+ context = {'model': model, 'session': model.Session,
+ 'user': c.user,
+ 'schema': self._db_to_form_schema(group_type=group_type),
+ 'for_view': True}
+ data_dict = {'id': id, 'type': group_type}
+
+ # unicode format (decoded from utf8)
+ c.q = request.args.get('q', '')
+
+ try:
+ # Do not query for the group datasets when dictizing, as they will
+ # be ignored and get requested on the controller anyway
+ data_dict['include_datasets'] = False
+ c.group_dict = toolkit.get_action('group_show')(context, data_dict)
+ c.group = context['group']
+ except (NotFound, NotAuthorized, ckan.logic.NotFound):
+ abort(404, _('Group not found'))
+
+ self._read(id, limit, group_type)
+ return base.render('organization_vre/read.html',
+ extra_vars={'group_type': group_type})
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/controllers/systemtype.py b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/systemtype.py
new file mode 100644
index 0000000..8d94e5b
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/controllers/systemtype.py
@@ -0,0 +1,96 @@
+import logging
+import ckan.plugins as p
+from ckan.common import _, g, c
+import ckan.lib.search as search
+import ckan.model as model
+import ckan.logic as logic
+import ckan.lib.maintain as maintain
+import ckan.lib.base as base
+import ckan.lib.helpers as h
+
+from urllib.parse import urlencode
+
+
+import ckan.lib.base as base
+import ckan.lib.helpers as h
+import ckan.lib.maintain as maintain
+import ckan.lib.navl.dictization_functions as dict_fns
+import ckan.logic as logic
+import ckan.lib.search as search
+import ckan.model as model
+import ckan.authz as authz
+import ckan.lib.plugins
+import ckan.plugins as plugins
+from ckan.common import c, g, request, _
+
+from collections import OrderedDict
+from flask import render_template
+
+
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+class d4STypeController():
+
+ #Overriding controllers.HomeController.index method
+ def index(self):
+ try:
+ # package search
+ context = {'model': model, 'session': model.Session,'user': c.user, 'auth_user_obj': c.userobj}
+
+ facets = OrderedDict()
+
+ default_facet_titles = {
+ 'organization': _('Organizations'),
+ 'groups': _('Groups'),
+ 'types':_('Types'),
+ 'tags': _('Tags'),
+ 'res_format': _('Formats'),
+ 'license_id': _('Licenses'),
+ }
+
+ for facet in g.facets:
+ if facet in default_facet_titles:
+ facets[facet] = default_facet_titles[facet]
+ else:
+ facets[facet] = facet
+
+ # Facet titles
+ for plugin in p.PluginImplementations(p.IFacets):
+ facets = plugin.dataset_facets(facets, 'dataset')
+
+ c.facet_titles = facets
+
+ data_dict = {
+ 'q': '*:*',
+ 'facet.field': list(facets.keys()),
+ 'rows': 4,
+ 'start': 0,
+ 'sort': 'views_recent desc',
+ 'fq': 'capacity:"public"'
+ }
+ query = logic.get_action('package_search')(context, data_dict)
+ c.search_facets = query['search_facets']
+ c.package_count = query['count']
+ c.datasets = query['results']
+
+ #print "c.search_facets: "
+ #print " ".join(c.search_facets)
+
+ except search.SearchError:
+ c.package_count = 0
+
+ if c.userobj and not c.userobj.email:
+ url = h.url_for(controller='user', action='edit')
+ msg = _('Please update your profile'
+ ' and add your email address. ') % url + \
+ _('%s uses your email address'
+ ' if you need to reset your password.') \
+ % g.site_title
+ h.flash_notice(msg, allow_html=True)
+
+ # return base.render('type/index.html', cache_force=True)
+ return render_template('type/index.html')
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/__init__.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_cache_controller.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_cache_controller.py
new file mode 100644
index 0000000..0462d7d
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_cache_controller.py
@@ -0,0 +1,106 @@
+import datetime
+import logging
+import os
+import tempfile
+import csv
+
+from .icproxycontroller import NAMESPACE_ID_LABEL
+
+log = logging.getLogger(__name__)
+
+CATALINA_HOME = 'CATALINA_HOME'
+temp_dir = None
+namespaces_dir = None
+NAMESPACES_DIR_NAME = "namespaces_for_catalogue"
+NAMESPACES_CACHE_FILENAME = "Namespaces_Catalogue_Categories.csv"
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+
+# D4S_Cache_Controller
+class D4S_Cache_Controller():
+ namespaces_cache_path = None
+ __scheduler = None
+
+ def __init__(self):
+ """ Virtually private constructor. """
+ log.debug("__init__ D4S_Cache_Controller")
+ self._check_cache()
+
+ def _check_cache(self):
+
+ if self.namespaces_cache_path is None:
+ self.init_temp_dir()
+ self.namespaces_cache_path = os.path.join(namespaces_dir, NAMESPACES_CACHE_FILENAME)
+ log.info("The namespaces cache is located at: %s" % self.namespaces_cache_path)
+
+ if not os.path.exists(self.namespaces_cache_path):
+ log.info("File does not exists creating it")
+ try:
+ with open(self.namespaces_cache_path, mode='w') as namespaces_file:
+ csv.writer(namespaces_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ log.info("Cache created at %s" % self.namespaces_cache_path)
+ except Exception as e:
+ print(e)
+
+ ''' Write the list of dictionary with namespaces'''
+ def write_namespaces(self, namespace_list_of_dict):
+ # Insert Data
+ with open(self.namespaces_cache_path, 'w') as namespaces_file:
+ writer = csv.writer(namespaces_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
+ writer.writerow([NAMESPACE_ID_LABEL, 'name', 'title', 'description'])
+ for namespace_dict in namespace_list_of_dict:
+ #print("namespace %s" % namespace_dict)
+ writer.writerow([namespace_dict[NAMESPACE_ID_LABEL], namespace_dict['name'], namespace_dict['title'], namespace_dict['description']])
+
+ log.info("Inserted %d namespaces in the Cache" % len(namespace_list_of_dict))
+
+ '''Returns the list of dictionary with namespaces'''
+ def read_namespaces(self):
+ # Read Data
+ namespace_list_of_dict = []
+ try:
+ with open(self.namespaces_cache_path, 'r') as namespaces_file:
+ reader = csv.DictReader(namespaces_file)
+ for row in reader:
+ #print("read namespace %s" % row)
+ namespace_list_of_dict.append(dict(row))
+
+ log.debug("from Cache returning namespace_list_of_dict %s: " % namespace_list_of_dict)
+ log.info("from Cache read namespace_list_of_dict with %d item/s " % len(namespace_list_of_dict))
+ return namespace_list_of_dict
+ except Exception as e:
+ print(e)
+
+ log.info("no namespace in the Cache returning empty list of dict")
+ return namespace_list_of_dict
+
+ @property
+ def get_namespaces_cache_path(self):
+ return self.namespaces_cache_path
+
+ @classmethod
+ def init_temp_dir(cls):
+ global temp_dir
+ global NAMESPACES_DIR_NAME
+ global namespaces_dir
+ try:
+ temp_dir = str(os.environ[CATALINA_HOME])
+ temp_dir = os.path.join(temp_dir, "temp")
+ except KeyError as error:
+ log.error("No environment variable for: %s" % CATALINA_HOME)
+
+ if temp_dir is None:
+ temp_dir = tempfile.gettempdir() # using system tmp dir
+
+ log.debug("Temp dir is: %s" % temp_dir)
+
+ namespaces_dir = os.path.join(temp_dir, NAMESPACES_DIR_NAME)
+
+ if not os.path.exists(namespaces_dir):
+ os.makedirs(namespaces_dir)
+
+
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_extras.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_extras.py
new file mode 100644
index 0000000..8a62764
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_extras.py
@@ -0,0 +1,30 @@
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+import logging
+log = logging.getLogger(__name__)
+
+class D4S_Extras():
+
+ def __init__(self, category_dict={}, extras=[]):
+ self._category = category_dict
+ self._extras = extras
+
+ def append_extra(self, k, v):
+ #print ("self._extras: %s" %self._extras)
+ if k is not None:
+ self._extras.append({k:v})
+
+ @property
+ def category(self):
+ return self._category
+
+ @property
+ def extras(self):
+ return self._extras
+
+ def __repr__(self):
+ return 'category: %s'%self._category+' ' \
+ 'extras: %s'%self._extras
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces.py
new file mode 100644
index 0000000..54f1e90
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces.py
@@ -0,0 +1,39 @@
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+#OrderedDict([(u'@id', u'extra_information'), (u'name', u'Extra Information'), (u'title', u'Extras'), (u'description', u'This section is about Extra(s)')]), u'contact': OrderedDict([(u'@id', u'contact'), (u'name', u'Contact'), (u'title', u'Contact Title'), (u'description', u'This section is about Contact(s)')]), u'developer_information': OrderedDict([(u'@id', u'developer_information'), (u'name', u'Developer'), (u'title', u'Developer Information'), (u'description', u'This section is about Developer(s)')])}
+
+import logging
+log = logging.getLogger(__name__)
+
+class D4S_Namespaces():
+
+ def __init__(self, id=None, name=None, title=None, description=None):
+ self._id = id
+ self._name = name
+ self._title = title
+ self._description = description
+
+ @property
+ def id(self):
+ return self._id
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def title(self):
+ return self._title
+
+
+ @property
+ def description(self):
+ return self._description
+
+ def __repr__(self):
+ return '{id: %s'%self.id+', ' \
+ 'name: %s'%self.name+ ', ' \
+ 'title: %s'%self.title+ ', ' \
+ 'description: %s'%self.description+ '}'
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces_controller.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces_controller.py
new file mode 100644
index 0000000..afb88df
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces_controller.py
@@ -0,0 +1,130 @@
+import logging
+import time
+
+from .d4s_cache_controller import D4S_Cache_Controller
+from .icproxycontroller import D4S_IS_DiscoveryCatalogueNamespaces
+from threading import Event, Thread
+
+CATEGORY = 'category'
+NOCATEOGORY = 'nocategory'
+
+log = logging.getLogger(__name__)
+
+cancel_future_calls = None
+
+# Refreshing time for namespaces cache in secs.
+NAMESPACES_CACHE_REFRESHING_TIME = 60 * 60
+
+
+# Funtion to call repeatedly another function
+def call_repeatedly(interval, func, *args):
+ log.info("call_repeatedly called on func '{}' with interval {} sec".format(func.__name__, interval))
+ stopped = Event()
+
+ def loop():
+ while not stopped.wait(interval): # the first call is in `interval` secs
+ func(*args)
+
+ th = Thread(name='daemon_caching_namespaces', target=loop)
+ th.setDaemon(True)
+ th.start()
+ return stopped.set
+
+
+def reload_namespaces_from_IS(urlICProxy, resourceID, gcubeToken):
+ log.info("_reload_namespaces_from_IS called")
+ try:
+ discovery_ctg_namespaces = D4S_IS_DiscoveryCatalogueNamespaces(urlICProxy, resourceID, gcubeToken)
+ namespaces_list_of_dict = discovery_ctg_namespaces.getNamespacesDictFromResource()
+
+ if namespaces_list_of_dict is not None and len(namespaces_list_of_dict) > 0:
+ log.debug("namespaces read from IS are: %s" % namespaces_list_of_dict)
+ D4S_Cache_Controller().write_namespaces(namespaces_list_of_dict)
+ else:
+ log.info("namespaces list read from IS is empty. Skipping caching update")
+
+ except Exception as e:
+ print("Error occurred on reading namespaces from IS and refilling the cache!")
+ print(e)
+
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+
+# D4S_IS_DiscoveryCatalogueNamespacesController is used to discovery namespaces for Catalogue Categories (implemented as a Singleton)
+# @param: urlICProxy is the URI of IC proxy rest-full service provided by IS
+# @param: resourceID is the resource ID of the Generic Resource: "Namespaces Catalogue Categories"
+# @param: gcubeToken the gcube token used to contact the IC proxy
+class D4S_Namespaces_Controller():
+ __instance = None
+
+ @staticmethod
+ def getInstance():
+ """ Static access method. """
+ if D4S_Namespaces_Controller.__instance is None:
+ D4S_Namespaces_Controller()
+
+ return D4S_Namespaces_Controller.__instance
+
+ def __init__(self):
+ """ Virtually private constructor. """
+ log.debug("__init__ D4S_Namespaces_Controller")
+
+ if D4S_Namespaces_Controller.__instance is not None:
+ raise Exception("This class is a singleton!")
+ else:
+ D4S_Namespaces_Controller.__instance = self
+
+ self._d4s_cache_controller = D4S_Cache_Controller()
+ self._urlICProxy = None
+ self._resourceID = None
+ self._gcubeToken = None
+
+ def load_namespaces(self, urlICProxy, resourceID, gcubeToken):
+ log.debug("readNamespaces called")
+ self._urlICProxy = urlICProxy
+ self._resourceID = resourceID
+ self._gcubeToken = gcubeToken
+ return self._check_namespaces()
+
+ def _read_namespaces(self):
+ return self._d4s_cache_controller.read_namespaces()
+
+ def _check_namespaces(self):
+ log.debug("_check_namespaces called")
+
+ if self._d4s_cache_controller is None:
+ self._d4s_cache_controller = D4S_Cache_Controller()
+
+ namespace_list = self._read_namespaces()
+
+ # when the Cache is empty
+ if namespace_list is None or not namespace_list:
+ # reading namespaces from IS and filling the DB
+ log.info("The Cache is empty. Reading the namespace from IS and filling the Cache")
+ reload_namespaces_from_IS(self._urlICProxy, self._resourceID, self._gcubeToken)
+ # reloading the namespaces from the cache
+ namespace_list = self._read_namespaces()
+
+ # starting Thread daemon for refreshing the namespaces Cache
+ global cancel_future_calls
+ if cancel_future_calls is None:
+ cancel_future_calls = call_repeatedly(NAMESPACES_CACHE_REFRESHING_TIME, reload_namespaces_from_IS,
+ self._urlICProxy,
+ self._resourceID,
+ self._gcubeToken)
+
+ return namespace_list
+
+ def get_dict_ctg_namespaces(self):
+ log.debug("get_dict_ctg_namespaces called")
+ namespace_list_of_dict = self._check_namespaces()
+ return self.convert_namespaces_to_d4s_namespacedict(namespace_list_of_dict)
+
+ # Private method
+ @staticmethod
+ def convert_namespaces_to_d4s_namespacedict(namespace_list_of_dict):
+ log.debug("convert_namespaces_to_d4s_namespacedict called on %s" % namespace_list_of_dict)
+ return D4S_IS_DiscoveryCatalogueNamespaces.to_namespaces_dict_index_for_id(namespace_list_of_dict)
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces_extras_util.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces_extras_util.py
new file mode 100644
index 0000000..edb0bbf
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/d4s_namespaces_extras_util.py
@@ -0,0 +1,89 @@
+import logging
+import collections
+from .d4s_extras import D4S_Extras
+
+CATEGORY = 'category'
+NOCATEOGORY = 'nocategory'
+
+log = logging.getLogger(__name__)
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+
+# D4S_Namespaces_Extra_Util is used to get the extra fields indexed for D4Science namespaces
+# @param: namespace_dict is the namespace dict of D4Science namespaces (defined in the Generic Resource: "Namespaces Catalogue Categories")
+# @param: extras is the dictionary of extra fields for a certain item
+class D4S_Namespaces_Extra_Util():
+
+ def get_extras_indexed_for_namespaces(self, namespace_dict, extras):
+ extras_for_categories = collections.OrderedDict()
+
+ # ADDING ALL EXTRAS WITH NAMESPACE
+ for namespaceid in list(namespace_dict.keys()):
+ dict_extras = None
+ nms = namespaceid + ":"
+ #has_namespace_ref = None
+ for key, value in extras:
+ k = key
+ v = value
+ # print "key: " + k
+ # print "value: " + v
+ if k.startswith(nms):
+
+ if namespaceid not in extras_for_categories:
+ extras_for_categories[namespaceid] = collections.OrderedDict()
+
+ dict_extras = extras_for_categories[namespaceid]
+ log.debug("dict_extras %s "%dict_extras)
+
+ if (dict_extras is None) or (not dict_extras):
+ dict_extras = D4S_Extras(namespace_dict.get(namespaceid), [])
+ log.debug("dict_extras after init %s " % dict_extras)
+
+ #print ("dict_extras after init %s " % dict_extras)
+ log.debug("replacing namespace into key %s " % k +" with empty string")
+ nms = namespaceid + ":"
+ k = k.replace(nms, "")
+ dict_extras.append_extra(k, v)
+ extras_for_categories[namespaceid] = dict_extras
+ log.debug("adding d4s_extra: %s " % dict_extras+ " - to namespace id: %s" %namespaceid)
+ #has_namespace_ref = True
+ #break
+
+ #ADDING ALL EXTRAS WITHOUT NAMESPACE
+ for key, value in extras:
+ k = key
+ v = value
+
+ has_namespace_ref = None
+ for namespaceid in list(namespace_dict.keys()):
+ nms = namespaceid + ":"
+ #IF KEY NOT STARTING WITH NAMESPACE
+ if k.startswith(nms):
+ has_namespace_ref = True
+ log.debug("key: %s " % k + " - have namespace: %s" % nms)
+ break
+
+ if has_namespace_ref is None:
+ log.debug("key: %s " % k + " - have not namespace")
+ if NOCATEOGORY not in extras_for_categories:
+ extras_for_categories[NOCATEOGORY] = collections.OrderedDict()
+
+ dict_extras_no_cat = extras_for_categories[NOCATEOGORY]
+ #print ("dict_extras_no_cat %s " % dict_extras_no_cat)
+
+ if (dict_extras_no_cat is None) or (not dict_extras_no_cat):
+ dict_extras_no_cat = D4S_Extras(NOCATEOGORY, [])
+
+ #print ("adding key: %s "%k+" - value: %s"%v)
+ log.debug("NOCATEOGORY adding key: %s " % k + " - value: %s" % v)
+
+ dict_extras_no_cat.append_extra(k, v)
+ log.debug("dict_extras_no_cat %s " % dict_extras_no_cat)
+ extras_for_categories[NOCATEOGORY] = dict_extras_no_cat
+ log.debug("extras_for_categories NOCATEOGORY %s " % extras_for_categories)
+
+ return extras_for_categories
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/icproxycontroller.py b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/icproxycontroller.py
new file mode 100644
index 0000000..f1c03ce
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/d4sdiscovery/icproxycontroller.py
@@ -0,0 +1,110 @@
+import logging
+import urllib.request, urllib.error, urllib.parse
+from lxml import etree
+
+import xmltodict
+import collections
+
+from .d4s_namespaces import D4S_Namespaces
+
+XPATH_NAMESPACES = "/Resource/Profile/Body/namespaces"
+gcubeTokenParam = "gcube-token"
+NAMESPACE_ID_LABEL = '@id'
+
+log = logging.getLogger(__name__)
+
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+def getResponseBody(uri):
+ req = urllib.request.Request(uri)
+ try:
+ resp = urllib.request.urlopen(req, timeout=20)
+ except urllib.error.HTTPError as e:
+ log.error("Error on contacting URI: %s" % uri)
+ log.error("HTTPError: %d" % e.code)
+ return None
+ except urllib.error.URLError as e:
+ # Not an HTTP-specific error (e.g. connection refused)
+ log.error("URLError - Input URI: %s " % uri + " is not valid!!")
+ return None
+ else:
+ # 200
+ body = resp.read()
+ return body
+
+
+# D4S_IS_DiscoveryCatalogueNamespaces is used to discovery namespaces for Catalogue Categories.
+# @param: urlICProxy is the URI of IC proxy rest-full service provided by IS
+# @param: resourceID is the resource ID of the Generic Resource: "Namespaces Catalogue Categories"
+# @param: gcubeToken the gcube token used to contact the IC proxy
+class D4S_IS_DiscoveryCatalogueNamespaces():
+
+ def __init__(self, urlICProxy, resourceID, gcubeToken):
+ self.urlICProxy = urlICProxy
+ self.resourceID = resourceID
+ self.gcubeToken = gcubeToken
+
+ def getNamespacesDictFromResource(self):
+
+ doc = {}
+ namespace_list = []
+
+ try:
+ # print("proxy: "+self.urlICProxy)
+ # print("resourceID: " + self.resourceID)
+ # print("gcubeTokenParam: " + gcubeTokenParam)
+ # print("gcubeToken: " + self.gcubeToken)
+
+ uri = self.urlICProxy + "/" + self.resourceID + "?" + gcubeTokenParam + "=" + self.gcubeToken
+ log.info("Contacting URL: %s" % uri)
+ theResource = getResponseBody(uri)
+ log.debug("Resource returned %s " % theResource)
+ theResourceXML = etree.XML(theResource)
+ theNamespaces = theResourceXML.xpath(XPATH_NAMESPACES)
+ log.debug("The body %s" % etree.tostring(theNamespaces[0]))
+
+ if theNamespaces is not None and theNamespaces[0] is not None:
+ bodyToString = etree.tostring(theNamespaces[0])
+ doc = xmltodict.parse(bodyToString)
+ else:
+ log.warn("No Namespace for Catalogue Categories found, returning None")
+ except Exception as inst:
+ log.error("Error on getting catalogue namespaces: " + str(inst))
+ log.info("Returning empty list of namespaces")
+ return namespace_list
+
+ log.debug("IS namespaces resource to dict is: %s" % doc)
+
+
+ if ('namespaces' in doc):
+ # log.debug('Namespaces obj %s:' % doc['namespaces'])
+ namespaces = doc['namespaces']
+ if doc is not None and 'namespace' in namespaces:
+ namespace_list = namespaces['namespace']
+
+ log.info("Loaded %d namespaces from IS resource" % len(namespace_list))
+ return namespace_list
+
+ @staticmethod
+ def to_namespaces_dict_index_for_id(namespace_list):
+ namespace_dict = collections.OrderedDict()
+ log.debug("namespaces to dict: %s" % namespace_list)
+ try:
+ if namespace_list is not None and len(namespace_list) > 0:
+ for namespace in namespace_list:
+ try:
+ if NAMESPACE_ID_LABEL in namespace:
+ namespace_dict[namespace[NAMESPACE_ID_LABEL]] = D4S_Namespaces(
+ namespace[NAMESPACE_ID_LABEL],
+ namespace['name'],
+ namespace['title'],
+ namespace['description'])
+ except Exception as inst:
+ log.error("Error on converting catalogue namespaces: " + str(inst))
+ except Exception as inst:
+ log.error("Error on checking namespace_list: " + str(inst))
+ # print "namespace_dict to Nam: %s"%namespace_dict
+ return namespace_dict
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/.gitignore b/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/d4science_scripts.js b/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/d4science_scripts.js
new file mode 100644
index 0000000..642cdc5
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/d4science_scripts.js
@@ -0,0 +1,303 @@
+/* =====================================================
+ JavaScript used by CKAN plugin: 'd4science_theme'
+ Created by Francesco Mangiacrapa ISTI-CNR Pisa, Italy
+ ===================================================== */
+
+
+CKAN_D4S_Breadcrumb_Manager = {
+
+ breadcrumbShow : function (show) {
+
+ var breadcrumb = document.getElementById('ckan-breadcrumb');
+ console.log('breadcrumb is '+breadcrumb)
+ if(breadcrumb){
+ if(show){
+ breadcrumb.style.display = 'block';
+ this.organizationTreeShow(true);
+ } else{
+ breadcrumb.style.display = 'none';
+ this.organizationTreeShow(false);
+ }
+ }
+
+ //var elements = document.getElementsByTagName('a');
+ //for(var i = 0, len = elements.length; i < len; i++) {
+ // elements[i].onclick = function () {
+ // //alert("You clicked an external link to: " + this.href);
+ // //window.parent.add_hide_breadcrumb_to_dom(false);
+ // this.add_hide_breadcrumb_to_dom(false);
+ // }
+ //}
+ },
+
+ organizationTreeShow : function (show) {
+ var trees = document.getElementsByClassName("hierarchy-tree-top");
+
+ if (trees){
+ for (i = 0; i < trees.length; i++) {
+ if(show){
+ trees[i].style.display = 'block';
+ } else{
+ trees[i].style.display = 'none';
+ }
+ }
+ }
+ },
+
+ checkBreadcrumbShow : function () {
+
+ var showBdc = this.getSessionStorageItem("showbreadcrumb")
+ //console.log("showBdc is: "+showBdc)
+ if(showBdc != undefined && showBdc=="false"){
+ console.log("Show breadcrumb false");
+ this.breadcrumbShow(false);
+ }else{
+ console.log("Show breadcrumb true");
+ this.breadcrumbShow(true);
+ }
+ },
+
+
+ setSessionStorageItem : function (item_key, item_value) {
+
+ // Check browser support
+ if (typeof(Storage) !== "undefined") {
+ // Store
+ sessionStorage.setItem(item_key, item_value);
+ return true;
+ } else {
+ console.log("Sorry, your browser does not support Web Storage...");
+ return false;
+ }
+ },
+
+
+ getSessionStorageItem : function (item_key) {
+
+ // Check browser support
+ if (typeof(Storage) !== "undefined") {
+ // Store
+ return sessionStorage.getItem(item_key);
+ } else {
+ console.log("Sorry, your browser does not support Web Storage...");
+ return undefined;
+ }
+ }
+
+}
+
+
+CKAN_D4S_Functions_Util = {
+
+ getPosition : function(canvas, event){
+ var x = new Number();
+ var y = new Number();
+ try {
+ if (event.clientX != undefined && event.clientY != undefined)
+ {
+
+ x = event.clientX;
+ y = event.clientY;
+ }
+ else // Firefox method to get the position
+ {
+ x = event.clientX + document.body.scrollLeft +
+ document.documentElement.scrollLeft;
+ y = event.clientY + document.body.scrollTop +
+ document.documentElement.scrollTop;
+ }
+ x -= canvas.offsetLeft;
+ y -= canvas.offsetTop;
+ }catch (err) {
+ //silent error
+ }
+ return '{"posX": "'+x+'", "posY": "'+y+'"}';
+ },
+
+ // When the user clicks on div, open the popup
+ showPopupD4S : function(event, my_div, my_popup_left_position) {
+ var popup = document.getElementById(my_div);
+ var clickPosition = this.getPosition(my_div, event)
+ var myPosition = JSON.parse(clickPosition);
+ this.closePopups(my_div);
+ // When the user clicks anywhere outside of the modal, close it
+ /*window.onclick = function(event) {
+ if (event.target != popup) {
+ popup.style.display = "none";
+ }
+ }*/
+ popup.classList.toggle("show");
+
+ if(my_popup_left_position){
+ popup.style.left = my_popup_left_position;
+ }
+ else if (myPosition.posX){
+ popup.style.left = myPosition.posX + "px";
+ }
+ },
+
+ closePopups : function ($target) {
+ var popups = document.getElementsByClassName('popuptext');
+ for (i = 0; i < popups.length; i++) {
+ if (popups[i].getAttribute('id') != $target) {
+ popups[i].classList.remove('show');
+ }
+ }
+ },
+
+ checkURL : function (url) {
+ //console.log('checking url: '+url)
+ var regex = new RegExp('^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/|www\.|ftp:\/\/)+[^ "]+$');
+ if (regex.test(url)) {
+ return true;
+ }
+ return false;
+ }
+
+}
+
+CKAN_D4S_HTMLMessage_Util = {
+
+ postHeightToPortlet : function (selectedProduct, product) {
+ var h = document.body.scrollHeight + "px";
+ var p = "";
+ var msg = "";
+ //WORK AROUND IF TWO MESSAGES ARE SENT FROM A PAGE OF A PRODUCT
+ //THE MESSAGE WITH 'NULL' PRODUCT IS SKIPPED
+ //console.log("window.location.pathname? "+window.location.pathname);
+ var pathArray = window.location.pathname.split('/');
+ var productContext = "dataset";
+ if(pathArray.length>1){
+ //console.log("pathArray is: "+pathArray);
+ var secondLevelLocation = pathArray[1]; //it is the second level location
+ //console.log("secondLevelLocation is: "+secondLevelLocation);
+ //console.log("h is: "+h);
+ if(secondLevelLocation == productContext){ //is it product context?
+ if(product !== 'undefined' && product !== null){
+ p = product;
+ //console.log("product selected is: "+p);
+ }else{
+ //console.log("product is null or undefined, passing only height");
+ msg = "{\"height\": \""+h+"\"}";
+ //window.postMessage(msg,'*');
+ this.postMessageToParentWindow(msg);
+ return;
+ }
+ }
+ }
+
+ //msg = "{'height': '"+h+"', 'product': '"+p+"'}";
+ msg = "{\"height\": \""+h+"\", \"product\": \""+p+"\"}";
+ //window.postMessage(msg,'*');
+ //console.log("posting message in the window: "+msg);
+ this.postMessageToParentWindow(msg);
+ },
+
+ postMessageToParentWindow : function (msg) {
+ //window.postMessage(msg,'*');
+ //console.log("posting message in the window: "+msg);
+ if(window.parent!=null){
+ console.log("posting message in the parent window: "+msg);
+ window.parent.postMessage(msg,'*');
+ }
+ return;
+ }
+
+}
+
+CKAN_D4S_JSON_Util = {
+
+
+ //ADDED by Francesco Mangiacrapa
+ appendHTMLToElement : function(containerID, elementHTML){
+
+ var divContainer = document.getElementById(containerID);
+ divContainer.innerHTML = "";
+ divContainer.appendChild(elementHTML);
+ },
+
+ //ADDED by Francesco Mangiacrapa
+ jsonToHTML : function(containerID, cssClassToTable) {
+
+ try
+ {
+ var jsonTxt = document.getElementById(containerID).innerHTML;
+ var jsonObj = JSON.parse(jsonTxt);
+ //console.log(jsonObj.length)
+
+ if(jsonObj.length==undefined)
+ jsonObj = [jsonObj]
+ //console.log(jsonObj.length)
+
+ // EXTRACT VALUE FOR HTML HEADER.
+ var col = [];
+ for (var i = 0; i < jsonObj.length; i++) {
+ for (var key in jsonObj[i]) {
+ //console.log('key json' +key)
+ if (col.indexOf(key) === -1) {
+ col.push(key);
+ }
+ }
+ }
+
+ // CREATE DYNAMIC TABLE.
+ var table = document.createElement("table");
+ var addDefaultCss = "json-to-html-table-column";
+ if(cssClassToTable){
+ addDefaultCss = cssClassToTable;
+ }
+ try{
+ table.classList.add(addDefaultCss);
+ }catch(e){
+ console.log('invalid css add', e);
+ }
+
+ // ADD JSON DATA TO THE TABLE AS ROWS.
+ for (var i = 0; i < col.length; i++) {
+
+ tr = table.insertRow(-1);
+ var firstCell = tr.insertCell(-1);
+ //firstCell.style.cssText="font-weight: bold; text-align: center; vertical-align: middle;";
+ firstCell.innerHTML = col[i];
+ for (var j = 0; j < jsonObj.length; j++) {
+ var tabCell = tr.insertCell(-1);
+ var theValue = jsonObj[j][col[i]];
+ /* console.log(theValue + ' is url? '+isUrl);*/
+ if(CKAN_D4S_Functions_Util.checkURL(theValue)){
+ theValue = ''+theValue+'';
+ }
+
+ tabCell.innerHTML = theValue;
+ }
+ }
+
+ // FINALLY ADD THE NEWLY CREATED TABLE WITH JSON DATA TO A CONTAINER.
+ this.appendHTMLToElement(containerID, table);
+
+ }
+ catch(e){
+ console.log('invalid json', e);
+ }
+ }
+}
+
+
+//Task #8032
+window.addEventListener("message",
+ function (e) {
+
+ var curr_loc = window.location.toString()
+ var orgin = e.origin.toString()
+ if(curr_loc.startsWith(orgin)){
+ //alert("ignoring message from myself");
+ return;
+ }
+ //console.log("origin: "+e.data)
+ if(e.data == null)
+ return;
+
+ var pMess = JSON.parse(e.data)
+ //console.log(pMess.explore_vres_landing_page)
+ window.linktogateway = pMess.explore_vres_landing_page;
+ },false);
+
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/d4science_theme.css b/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/d4science_theme.css
new file mode 100644
index 0000000..9e416b1
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/fanstatic/d4science_theme.css
@@ -0,0 +1,844 @@
+/* =====================================================
+ The "account masthead" bar across the top of the site
+ ===================================================== */
+
+.account-masthead {
+ background-color: #ccc;
+}
+/* The "bubble" containing the number of new notifications. */
+.account-masthead .account .notifications a span {
+ background-color: #9fa0a2;
+}
+/* The text and icons in the user account info. */
+.account-masthead .account ul li a {
+ color: rgba(255, 255, 255, 0.6);
+}
+/* The user account info text and icons, when the user's pointer is hovering
+ over them. */
+.account-masthead .account ul li a:hover {
+ color: rgba(255, 255, 255, 0.7);
+/* background-color: black;*/
+}
+
+
+/* ========================================================================
+ The main masthead bar that contains the site logo, nav links, and search
+ ======================================================================== */
+
+.masthead {
+ background: #eee url("/bg-noise.png") repeat scroll 0 0;
+ border-top: 1px solid #555;
+ padding-top: 5px;
+ padding-bottom: 15px !important;
+ border-bottom: 1px solid #999;
+/* background-image: url("/bg-pattern.min.svg") !important; */
+}
+
+.masthead .navigation .nav-pills li a{
+ color: #187794;
+}
+
+a.logo > img{
+ margin-bottom: 5px;
+}
+
+/* The "navigation pills" in the masthead (the links to Datasets,
+ Organizations, etc) when the user's pointer hovers over them. */
+.masthead .navigation .nav-pills li a:hover {
+/* background-color: rgb(48, 48, 48);*/
+ color: white;
+}
+/* The "active" navigation pill (for example, when you're on the /dataset page
+ the "Datasets" link is active). */
+.masthead .navigation .nav-pills li.active a {
+ background-color: #d2d2d5;
+}
+/* The "box shadow" effect that appears around the search box when it
+ has the keyboard cursor's focus. */
+.masthead input[type="text"]:focus {
+ -webkit-box-shadow: inset 0px 0px 2px 0px rgba(0, 0, 0, 0.7);
+ box-shadow: inset 0px 0px 2px 0px rgba(0, 0, 0, 0.7);
+}
+
+
+/* ===========================================
+ The content in the middle of the front page
+ =========================================== */
+
+/* Remove the "box shadow" effect around various boxes on the page. */
+.box {
+ box-shadow: none;
+}
+.hero {
+ background: #FEFEFE repeat scroll 0 0 !important;
+}
+/* Remove the borders around the "Welcome to CKAN" and "Search Your Data"
+ boxes. */
+.hero .box {
+ /*border: none;*/
+ margin-top: 10px !important;
+}
+/* Change the colors of the "Search Your Data" box. */
+.homepage .module-search .module-content {
+ color: rgb(68, 68, 68);
+ background-color: white;
+}
+/* Change the background color of the "Popular Tags" box. */
+.homepage .module-search .tags {
+ background-color: #fcfcfc;
+}
+
+.homepage-title{
+ font-size: 20px;
+ font-weight: bold;
+ color: #202020;
+ margin-bottom: 20px;
+}
+
+/* Change the background color of the "Popular Tags" box. */
+.homepage .module-search h3{
+ color: #444;
+}
+
+/* Remove some padding. This makes the bottom edges of the "Welcome to CKAN"
+ and "Search Your Data" boxes line up. */
+.module-content:last-child {
+ /*padding-bottom: 0px;*/
+}
+.homepage .module-search {
+ padding: 0px;
+}
+/* Add a border line between the top and bottom halves of the front page. */
+.homepage [role="main"] {
+ border-bottom: 1px solid #bbb;
+ padding: 10px 0;
+}
+
+.homepage .stats ul li a b{
+ font-size: 30px !important;
+}
+
+[role="main"], .main {
+/* background: #f5f6fa url("/bg-pattern.min.svg") repeat; scroll 0 0;*/
+ /*background: #fafafa url("/bg-pattern.svg") repeat; scroll 0 0;*/
+ background: #fdfdfd none repeat scroll 0 0;
+ min-height: 0px !important;
+}
+
+.media-item-homepage {
+ background-color: white;
+ border-radius: 3px;
+ float: left;
+ margin: 15px 0 0 15px;
+ overflow: hidden;
+ padding-left: 10px;
+ padding-right: 10px;
+ position: relative;
+ text-align: center;
+ width: 150px;
+}
+
+.media-heading-homepage {
+ font-size: 16px;
+ hyphens: auto;
+ line-height: 1.3;
+ margin: 5px 0;
+}
+
+.media-grid-homepage {
+ -moz-border-bottom-colors: none;
+ -moz-border-left-colors: none;
+ -moz-border-right-colors: none;
+ -moz-border-top-colors: none;
+/* background: #fbfbfb url("../../../base/images/bg.png") repeat scroll 0 0;
+ border-color: #dddddd;
+ border-image: none;
+ border-style: solid;
+ border-width: 1px 0;*/
+ list-style: outside none none;
+ margin: 0 -10px;
+ padding-bottom: 15px;
+}
+.media-grid-homepage::before, .media-grid::after {
+ content: "";
+ display: table;
+ line-height: 0;
+}
+.media-grid-homepage::after {
+ clear: both;
+}
+
+.background-circle{
+ padding: 10px 10px;
+ display: inline-block !important;
+ -webkit-border-radius: 90px;
+ -moz-border-radius: 90px;
+ border-radius: 90px;
+ background-color: #4679b2;
+ text-decoration: none !important;
+}
+
+.color-white{
+ color: white !important;
+}
+
+.badge-circle {
+ border-radius: 50% 50% 50% 50% !important;
+ height: 60px;
+ text-align: center;
+ vertical-align: middle;
+ width: 65px;
+ background-color: #4679b2;
+ display: inline-block !important;
+ padding-top: 5px;
+ text-decoration: none !important;
+}
+
+/* ====================================
+ The footer at the bottom of the site
+ ==================================== */
+
+.site-footer, body {
+ background-color: #bbb;
+ font-family: "Lato","Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 16px;
+}
+/* The text in the footer. */
+.site-footer,
+.site-footer label,
+.site-footer small {
+ color: rgba(255, 255, 255, 0.6);
+}
+/* The link texts in the footer. */
+.site-footer a {
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.site-footer-internal{
+ min-height: 10px;
+ padding: 2px 0;
+ font-size: 12px;
+}
+
+.site-footer-internal {
+ /*background-color: rgba(255, 255, 255, 0.6);*/
+ text-align: center;
+ /*display: inline-block;*/
+}
+
+.site-footer-internal,
+.site-footer-internal label,
+.site-footer-internal small {
+
+}
+
+.site-footer-internal a {
+ display: inline-block;
+}
+
+.d4s-hide-text {
+ background-color: transparent;
+ border: 0 none;
+ color: transparent;
+ font: 0px/0 a;
+ text-shadow: none;
+}
+
+.d4science-footer-logo {
+ background: url("/gCube_70.png") no-repeat scroll left top rgba(0, 0, 0, 0);
+ height: 32px;
+ margin-top: 2px;
+ text-indent: -900em;
+ width: 75px;
+}
+
+.d4s-ckan-footer-logo {
+ background: rgba(0, 0, 0, 0) url("/ckan-logo-footer.png") no-repeat scroll left top;
+ height: 21px;
+ margin-top: 2px;
+ text-indent: -900em;
+ width: 69px;
+}
+
+.site-footer-d4science {
+ font-size: 14px;
+ color: #f5f5f5;
+ text-align: center;
+ height: 25px;
+ padding-top: 5px;
+ background-color: #7F7F7F;
+}
+
+.site-footer-d4science a {
+ font-weight: bold;
+ text-decoration: none;
+ color: white;
+}
+
+
+/* ====================================
+ Base elements of the site
+ ==================================== */
+
+div .principaltitle {
+ color: inherit;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 1.2;
+ margin: 15px 0;
+ text-rendering: optimizelegibility;
+ word-break: break-all;
+ padding-bottom: 10px;
+ padding-top: 5px;
+ border-bottom: 1px solid #eee;
+}
+
+div .notes {
+ color: #444444;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 14px;
+ line-height: 1.3;
+ text-align: justify;
+ word-break: break-all;
+}
+
+div .infotitle {
+ font-size: 15px;
+ hyphens: auto;
+ line-height: 1.3;
+ word-break: break-all;
+ font-weight: bold;
+}
+
+.toolbar .breadcrumb{
+ font-size: 16px !important;
+}
+
+.box{
+ border: 0px !important;
+}
+
+div .sectiontitle{
+ color: #9F9F9F;
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ font-size: 17px;
+ font-weight: bold;
+ margin: 20px 0;
+ margin-top: 20px;
+ margin-bottom: 10px;
+ text-rendering: optimizelegibility;
+}
+
+section .well {
+ background-color: #fdfdfd !important;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ box-shadow: none !important;
+ margin-bottom: 20px;
+ min-height: 20px;
+
+}
+
+.page-heading {
+ font-size: 18px;
+ line-height: 1.2;
+ margin-top: 20px;
+ margin-bottom: 0px;
+}
+
+#dataset-resources .resource-list{
+ background-color: #fdfdfd !important;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ box-shadow: none !important;
+ margin: -1px 0 !important;
+}
+
+.wrapper{
+ border: 1px solid #d0d0d0;
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
+ border-radius: 3px
+}
+
+.home-popular{
+ padding-top: 25px;
+}
+
+.logo-homepage{
+ max-height: 60px;
+}
+
+.statistics-show{
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ color: #444444;
+ text-decoration: none;
+}
+
+.d4s-center-cropped{
+ text-align: center;
+ background-color: #eee;
+ border: 1px solid #ddd;
+ padding-bottom: 10px;
+ padding-top: 10px;
+}
+
+.tag-list {
+ font-size: 14px;
+}
+
+
+/* ====================================
+ Acquired Dataset
+ ==================================== */
+.label-acquired {
+ background-color: #55a1ce;
+}
+
+.label-owner {
+ background-color: #e0051e;
+}
+
+.divider {
+ margin-left:10px;
+ height:auto;
+ display:inline-block;
+}
+
+/* ====================================
+ List Dataset
+ ==================================== */
+
+/*LEFT
+.show_meatadatatype {
+ color: white;
+ display: inline-block; // Inline elements with width and height. TL;DR they make the icon buttons stack from left-to-right instead of top-to-bottom
+ position: relative; // All 'absolute'ly positioned elements are relative to this one
+ margin-bottom: 20px;
+ margin-left: 25px;
+}
+*/
+
+/*RIGHT*/
+.show_meatadatatype {
+ color: white;
+ display: inline-block;
+ float: right;
+ margin-right: 2px;
+ margin-top: -20px;
+ position: relative;
+}
+
+
+
+/* LEFT
+ * Position the badge within the relatively positioned button
+.button__badge {
+ background-color: #fa3e3e;
+ border-radius: 2px;
+ color: white;
+
+ padding: 1px 6px;
+ font-size: 10px;
+
+ position: absolute;
+ top: 0;
+ right: 0;
+}*/
+
+
+
+/* RIGTH */
+.button__badge {
+ color: #808080;
+ padding: 0px 2px;
+ font-size: 10px;
+ top: 0;
+ right: 0;
+ font-family: sans-serif, times, georgia;
+}
+
+/* ====================================
+ Modal Popup
+ ==================================== */
+
+/* Popup container - can be anything you want */
+/* The Modal (background) */
+.d4s_modal {
+ display: none; /* Hidden by default */
+ position: fixed; /* Stay in place */
+ z-index: 10001; /* Sit on top (NB. At 1000 there is the zoom in/out of the Map Widget)*/
+ /*padding-top: 100px;*/ /* Location of the box */
+ left: 0;
+ top: 0;
+ width: 100%; /* Full width */
+ height: 100%; /* Full height */
+ overflow: auto; /* Enable scroll if needed */
+ background-color: rgb(0,0,0); /* Fallback color */
+ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
+}
+
+/* Modal Content */
+.d4s_modal-content {
+ background-color: #fefefe;
+ /*margin: auto;*/
+ padding: 20px;
+ border: 1px solid #888;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ position: absolute;
+ left: 50%;
+ margin-left: -225px;
+ width: 450px;
+}
+
+/* The Close Button */
+.d4s_close {
+ color: #aaaaaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+ padding-left: 20px;
+}
+
+.d4s_close:hover,
+.d4s_close:focus {
+ color: #000;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.d4s_div_clickable{
+ cursor: pointer;
+}
+
+/*====================================
+D4S POPUP
+======================================*/
+
+/* Popup container - can be anything you want */
+.popupD4SNoArrow {
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+/* The actual popup */
+.popupD4SNoArrow .popuptext {
+ visibility: hidden;
+ width: 300px;
+ background-color: #555;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 8px;
+ position: absolute;
+ z-index: 1;
+ bottom: 125%;
+ left: 50%;
+ margin-left: -150px;
+}
+
+/* Toggle this class - hide and show the popup */
+.popupD4SNoArrow .show {
+ visibility: visible;
+ -webkit-animation: fadeIn 1s;
+ animation: fadeIn 1s;
+}
+
+
+/* Popup container - can be anything you want */
+.popupD4S {
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+/* The actual popup */
+.popupD4S .popuptext {
+ visibility: hidden;
+ width: 300px;
+ background-color: #555;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 8px;
+ position: absolute;
+ z-index: 1;
+ bottom: 125%;
+ left: 50%;
+ margin-left: -150px;
+}
+
+/* Popup arrow */
+.popupD4S .popuptext::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: #555 transparent transparent transparent;
+}
+
+/* Toggle this class - hide and show the popup */
+.popupD4S .show {
+ visibility: visible;
+ -webkit-animation: fadeIn 1s;
+ animation: fadeIn 1s;
+}
+
+/* Add animation (fade in the popup) */
+@-webkit-keyframes fadeIn {
+ from {opacity: 0;}
+ to {opacity: 1;}
+}
+
+@keyframes fadeIn {
+ from {opacity: 0;}
+ to {opacity:1 ;}
+}
+
+/*====================================
+D4S PACKAGE
+======================================*/
+
+.graphic-preview-style {
+ text-align: center;
+ border-top: 1px dotted #DDD;
+ padding-top: 10px;
+ padding-bottom: 0px;
+ margin-top: 15px;
+}
+
+.graphic-preview-style a{
+ font-size: 13px;
+}
+
+.graphic-preview-style img{
+ max-width: 100% !important;
+ height: auto;
+
+}
+
+.graphic-preview-style #graphic-title{
+ font-size: 13px;
+
+}
+
+.nav-item{
+ word-wrap:break-word;
+ }
+
+/*====================================
+RESOURCE_LIST RESOURCE_ITEM INTO PACKAGE
+======================================*/
+
+.required-access {
+ font-style: italic;
+ font-weight: bold;
+ padding: 5px;
+}
+
+/*====================================
+LINK TO RESOURCES FROM PACKAGE LIST
+======================================*/
+
+.dataset-resources li a {
+ background-color: #187794;
+}
+
+.label[data-format="csw"], .label[data-format*="csw"] {
+ background-color: #e6b800;
+}
+
+/*====================================
+CSS APPLIED TO Similar GRSF Records
+======================================*/
+
+.my-grsf-table{
+ word-break: break-all;
+}
+
+.my-grsf-table tr td{
+ width: inherit;
+}
+
+.my-grsf-table tr td:first-child{
+ width: 82px !important;
+}
+
+/*====================================
+CSS APPLIED in base.html
+======================================*/
+
+#ckan-page-loading {
+ display: none;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ margin-top: -130px;
+ margin-left: -130px;
+ width: 260px;
+ height: 260px;
+ z-index: 100000;
+ background-image: url("/pageloading.gif");
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+/*====================================
+CSS APPLIED in search_for_location.html
+======================================*/
+
+div#search-for-location {
+
+}
+
+div#search-for-location #dataset-map {
+ position: relative !important;
+ top: +0px !important;
+}
+
+div#search-for-location #dataset-map-container {
+ height: 300px;
+}
+
+
+div#search-for-location .module-heading {
+ display: none;
+}
+
+div#search-for-extent{
+ padding-top: 10px;
+}
+
+/*====================================
+CSS APPLIED in additional_info.html
+======================================*/
+.qr-code-table {
+ width: 100%;
+}
+
+.qr-code-table td {
+ width: 85%;
+ border: 1px solid #e3e3e3;
+}
+
+.qr-code-table td:first-child {
+ padding-left: 10px;
+ border-right-style: none;
+
+}
+
+.qr-code-table td:last-child {
+ width: 105px;
+ text-align: center;
+ border-left-style: none;
+
+}
+
+/* MAX-WITH APPIED TO QR_CODE */
+.qr-code-table img {
+ max-width: 100px;
+ height: auto;
+}
+
+
+/*====================================
+CSS APPLIED FROM JSON TO HTML TABLE
+======================================*/
+
+.json-to-html-table-column{
+ word-break: break-all;
+}
+
+.json-to-html-table-column tr td{
+ width: inherit;
+}
+
+.json-to-html-table-column tr td:first-child{
+ font-weight: bold;
+ color: #5a5a5a;
+}
+
+/*====================================
+CSS APPLIED into custom_form_fields
+======================================*/
+.disabled-div{
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+.disabled-div input[type="text"]{
+ background: #f1f1f1;
+}
+
+/*====================================
+CSS APPLIED into extra_table.html
+======================================*/
+
+.read-more-state {
+ display: none;
+}
+
+.read-more-target {
+ opacity: 0;
+ max-height: 0;
+ font-size: 0;
+ transition: .25s ease;
+}
+
+.read-more-state:checked ~ .read-more-wrap .read-more-target {
+ opacity: 1;
+ font-size: inherit;
+ max-height: 999em;
+ content: "";
+}
+
+.read-more-state ~ .read-more-trigger:before {
+ content: 'Show more';
+}
+
+.read-more-state:checked ~ .read-more-trigger:before {
+ content: 'Show less';
+}
+
+.read-more-state:checked ~ .read-more-wrap::after {
+ content: "";
+}
+
+.read-more-trigger {
+ cursor: pointer;
+ display: inline-block;
+ padding: 0 .5em;
+ color: #187794;
+ font-size: .9em;
+ line-height: 2;
+ border: 1px solid #ddd;
+ border-radius: .25em;
+ font-weight: normal;
+}
+
+.read-more-trigger::after {
+ content: "";
+}
+
+.read-more-wrap {
+ margin-bottom: 2px;
+}
+
+.read-more-wrap::after{
+ content: " ...";
+
+}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/helpers.py b/ckanext-d4science_theme/ckanext/d4science_theme/helpers.py
new file mode 100644
index 0000000..82221e8
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/helpers.py
@@ -0,0 +1,732 @@
+from multiprocessing import context
+import ckan.authz as authz
+import ckan.model as model
+from webhelpers2.html import literal
+from webhelpers2.text import truncate
+import ckan.lib.helpers as h
+import ckan.logic as logic
+from ckan.common import config
+from ckanext.d4science_theme.d4sdiscovery.d4s_namespaces_controller import D4S_Namespaces_Controller
+from ckanext.d4science_theme.d4sdiscovery.d4s_namespaces_extras_util import D4S_Namespaces_Extra_Util
+from ckanext.d4science_theme.qrcodelink.generate_qrcode import D4S_QrCode
+import urllib.request, urllib.error, urllib.parse
+
+from ckan.common import (
+ _, g, c, request, session
+)
+
+import random
+from operator import itemgetter
+from logging import getLogger
+import base64
+import sys, os, re
+import configparser
+import collections
+import ckan.plugins.toolkit as tk
+import ckan.logic as logic
+
+log = getLogger(__name__)
+
+systemtype_field = 'systemtypefield'
+systemtype_field_default_value = 'system:type'
+ic_proxy_url_field = 'ic_proxy_url'
+ic_proxy_url_field_default_value = "https://registry.d4science.org/icproxy/gcube/service"
+application_token_field = 'application_token'
+namespaces_generic_resource_id_default_value = "23d827cd-ba8e-4d8c-9ab4-6303bdb7d1db"
+namespaces_gr_id_fieldname = "namespaces_generic_resource_id"
+namespaceseparator_field = 'namespace_separator'
+namespaceseparator_field_default_value = ':'
+systemtype_rgb_colors = ['#c0392b ', '#585858', '#04407C', '#9b59b6', '#2ecc71', '#16a085', '#7f8c8d ', '#2ecc71',
+ '#FA8072', '#00FFFF', '#C76611', '#f39c12', '#800000']
+systemtype_field_colors = 'systemtype_field_colors'
+
+systemtype_cms_fields_placeholders = {'prefix': 'system:cm_', 'item_status': 'system:cm_item_status'}
+
+NOCATEOGORY = 'nocategory'
+TRANSLATE_OF_ = 'translate_of_'
+
+ctg_namespace_ctrl = None
+
+
+# def get_tag_list()
+# return logic.get_action('tag_list')
+
+# ADDED BY FRANCESCO.MANGIACRAPA, related to Task #5196
+def get_user_role_for_group_or_org(group_id, user_name):
+ ''' Returns the user's role for the group. (Ignores privileges that cascade
+ in a group hierarchy.)'''
+ return authz.users_role_for_group_or_org(group_id, user_name)
+
+
+# ADDED BY FRANCESCO.MANGIACRAPA, related to breadcrumb for Group
+def get_parents_for_group(group_name_or_id):
+ ''' Returns the user's role for the group. (Ignores privileges that cascade
+ in a group hierarchy.)'''
+ group = model.Group.get(group_name_or_id)
+ if group:
+ return model.Group.get_parent_group_hierarchy(group)
+ else:
+ return None
+
+
+# ADDED BY FRANCESCO.MANGIACRAPA
+def get_header_param(parameter_name, default=None):
+ ''' This function allows templates to access header string parameters
+ from the request. '''
+ return request.headers.get(parameter_name, default)
+
+
+# ADDED BY FRANCESCO.MANGIACRAPA
+def get_request_param(parameter_name, default=None):
+ ''' This function allows templates to access query string parameters
+ from the request. '''
+ return request.args.get(parameter_name, default)
+
+
+# ADDED BY FRANCESCO.MANGIACRAPA
+def get_cookie_value(cookie_name, default=None):
+ ''' This function allows templates to access cookie by cookie_name parameter
+ from the request. '''
+
+ value = request.cookies.get(cookie_name)
+
+ if value is None:
+ print(('cookie: ' + cookie_name + ', has value None'))
+ else:
+ print(('cookie: ' + cookie_name + ', has value ' + value))
+
+ return value
+
+
+# Updated BY FRANCESCO.MANGIACRAPA, added allow_html
+def markdown_extract_html(text, extract_length=190, allow_html=False):
+ ''' Returns the plain text representation of markdown encoded text. That
+ is the texted without any html tags. If extract_length is 0 then it
+ will not be truncated.'''
+ if not text:
+ return ''
+ if allow_html:
+ plain = h.markdown(text.strip())
+ else:
+ plain = h.RE_MD_HTML_TAGS.sub('', h.markdown(text))
+
+ if not extract_length or len(plain) < extract_length:
+ return literal(plain)
+ return literal(str(truncate(plain, length=extract_length, indicator='...', whole_word=True)))
+
+
+def get_systemtype_field_dict_from_session():
+ '''Return the value of 'ckan.d4science_theme.metadatatypefield'
+ read from production.ini'''
+
+ systemtype_fieldname = session.get(systemtype_field)
+
+ if systemtype_fieldname is None:
+ log.info(systemtype_field + " not found in session, loading from config")
+ else:
+ log.debug(systemtype_field + " found in session having value: %s" % systemtype_fieldname)
+ return systemtype_fieldname
+
+ systemtype_fieldname = config.get('ckan.d4science_theme.' + systemtype_field)
+
+ if systemtype_fieldname is None:
+ log.info(
+ systemtype_field + " field does not exist in production.ini, returning default value %s" % systemtype_field_default_value)
+ systemtype_fieldname = systemtype_field_default_value
+
+ separator = get_namespace_separator_from_session()
+ log.debug("Replacing %s" % separator + " with empty string for key %s" % systemtype_field)
+ systemtype_fieldname_name = systemtype_fieldname.replace(separator, "")
+ purgedfieldname = purge_namespace_to_fieldname(systemtype_fieldname)
+ log.debug("Setting %s" % systemtype_fieldname + " in session for key %s" % systemtype_field)
+ session[systemtype_field] = {'id': systemtype_fieldname, 'name': systemtype_fieldname_name,
+ 'title': purgedfieldname}
+ session.save()
+ return session[systemtype_field]
+
+
+def get_d4s_namespace_controller():
+ '''Instance the D4S_Namespaces_Controller and check that the namespaces are not empty reading it from IS and/or using a Caching system.
+ The ic-proxy-url is built by reading the configurations from production.ini'''
+
+ d4s_extras_controller = D4S_Namespaces_Controller.getInstance()
+ global ctg_namespace_ctrl
+
+ if ctg_namespace_ctrl is not None:
+ log.info("ctg_namespace_ctrl with configurations is NOT None")
+ the_namespaces = d4s_extras_controller.load_namespaces(ctg_namespace_ctrl['ic_proxy_url'],
+ ctg_namespace_ctrl['resource_id'],
+ ctg_namespace_ctrl['application_token'])
+ log.debug("the_namespaces are %s" % the_namespaces)
+
+ if the_namespaces is None or len(the_namespaces) == 0:
+ log.info("D4S_Namespaces_Controller obj with none or empty namespaces, going to read them")
+ else:
+ log.info(
+ "d4s_namespaces_controller found and the namespaces property is not empty: %s" % d4s_extras_controller)
+ return d4s_extras_controller
+ else:
+ log.info("ctg_namespace_ctrl with configurations is None, instancing it")
+
+ ic_proxy_url_value = config.get('ckan.d4science_theme.' + ic_proxy_url_field)
+
+ if ic_proxy_url_value is None:
+ log.info(
+ "ckan.d4science_theme." + ic_proxy_url_field + " field does not exist in production.ini, returning default value %s" % ic_proxy_url_field_default_value)
+ ic_proxy_url_value = ic_proxy_url_field_default_value
+
+ application_token_fieldname = config.get('ckan.d4science_theme.' + application_token_field)
+
+ if application_token_fieldname is None:
+ log.error("ckan.d4science_theme." + application_token_field + " field does not exist in production.ini!!!")
+ application_token_fieldname = None
+
+ namespaces_gr_id_fieldname_value = config.get('ckan.d4science_theme.' + namespaces_gr_id_fieldname)
+
+ if namespaces_gr_id_fieldname_value is None:
+ log.error("ckan.d4science_theme." + application_token_field + " field does not exist in production.ini!!!")
+ namespaces_gr_id_fieldname_value = namespaces_generic_resource_id_default_value
+
+ # filling the ctg_namespace_ctrl with IS configurations to perform the query for loading the namespaces from IS
+ ctg_namespace_ctrl = {'ic_proxy_url': ic_proxy_url_value,
+ 'application_token': application_token_fieldname,
+ 'resource_id': namespaces_gr_id_fieldname_value}
+
+ d4s_extras_controller.load_namespaces(ctg_namespace_ctrl['ic_proxy_url'], ctg_namespace_ctrl['resource_id'],
+ ctg_namespace_ctrl['application_token'])
+
+ return d4s_extras_controller
+
+
+def get_extras_indexed_for_namespaces(extras):
+ namespace_dict = get_namespaces_dict()
+ # log.info("my_namespace_dict %s" % namespace_dict)
+ my_extra = get_extras(extras)
+ # log.info("my_extra is %s" % my_extra)
+ # d4s_extras_controller = D4S_Namespaces_Controller.getInstance()
+ # extras_indexed_for_categories = d4s_extras_controller.get_extras_indexed_for_namespaces(namespace_dict, my_extra)
+
+ extras_indexed_for_categories = D4S_Namespaces_Extra_Util().get_extras_indexed_for_namespaces(namespace_dict,
+ my_extra)
+ return extras_indexed_for_categories
+
+
+def get_namespaces_dict():
+ d4s_extras_controller = get_d4s_namespace_controller()
+
+ if d4s_extras_controller is not None:
+ return d4s_extras_controller.get_dict_ctg_namespaces()
+ else:
+ log.info("local_extras_controller is null, returning empty dictionary for namespaces")
+ return {}
+
+
+def get_extra_for_category(extras_indexed_for_categories, key_category):
+ if key_category in extras_indexed_for_categories:
+ catalogue_namespace = extras_indexed_for_categories[key_category]
+ return catalogue_namespace.extras
+
+ return []
+
+
+def get_systemtype_value_from_extras(package, extras=None):
+ '''Returns the value of metadata fied read from key 'metadatatype'
+ stored into extra fields if it exists, 'No Type' otherwise'''
+ systemtype_dict = get_systemtype_field_dict_from_session()
+
+ no_type = 'No Type'
+
+ if extras is None:
+ return no_type
+
+ for extra in extras:
+ k, v = extra['key'], extra['value']
+ log.debug("key is %s" % k)
+ log.debug("value is %s" % v)
+ if k == str(systemtype_dict['id']):
+ return v
+
+ return no_type
+
+
+def get_namespace_separator_from_session():
+ '''Returns the character used to separate namespace from fieldname'''
+
+ separator = session.get(namespaceseparator_field)
+
+ if separator is None:
+ log.info(namespaceseparator_field + " not found in session, loading from config")
+ else:
+ log.debug(namespaceseparator_field + " found in session: %s" % separator)
+ return separator
+
+ namespace_sep = config.get('ckan.d4science_theme.' + namespaceseparator_field)
+
+ if namespace_sep is None:
+ log.info(
+ namespaceseparator_field + " field does not exist in production.ini, returning default value %s" % namespaceseparator_field_default_value)
+ namespace_sep = namespaceseparator_field_default_value
+
+ log.debug("Setting %s" % namespace_sep + " in session for key %s" % namespaceseparator_field)
+ session[namespaceseparator_field] = namespace_sep
+ return namespace_sep
+
+
+def get_extras(package_extras, auto_clean=False, subs=None, exclude=None):
+ ''' Used for outputting package extras
+
+ :param package_extras: the package extras
+ :type package_extras: dict
+ :param auto_clean: If true capitalize and replace -_ with spaces
+ :type auto_clean: bool
+ :param subs: substitutes to use instead of given keys
+ :type subs: dict {'key': 'replacement'}
+ :param exclude: keys to exclude
+ :type exclude: list of strings
+ '''
+
+ # If exclude is not supplied use values defined in the config
+ if not exclude:
+ exclude = g.package_hide_extras
+ output = []
+ for extra in package_extras:
+ if extra.get('state') == 'deleted':
+ continue
+ k, v = extra['key'], extra['value']
+ if k in exclude:
+ continue
+ if subs and k in subs:
+ k = subs[k]
+ elif auto_clean:
+ k = k.replace('_', ' ').replace('-', ' ').title()
+ if isinstance(v, (list, tuple)):
+ v = ", ".join(map(str, v))
+ output.append((k, v))
+ return output
+
+
+def purge_namespace_to_fieldname(fieldname):
+ separator = get_namespace_separator_from_session()
+
+ if fieldname is None:
+ return ""
+
+ if separator not in fieldname:
+ return fieldname
+
+ end = fieldname.index(separator) + 1
+ max_l = len(fieldname)
+ if end < max_l:
+ return fieldname[end:max_l]
+ return fieldname
+
+
+def purge_namespace_to_string(facet):
+ if not c.search_facets or \
+ not c.search_facets.get(facet) or \
+ not c.search_facets.get(facet).get('items'):
+ return ""
+
+ facet_name = c.search_facets.get(facet)
+ print(("facet_name " + str(facet_name)))
+ end = str(facet_name).index(":")
+ if end <= len(facet_name):
+ return facet_name[:end]
+ return facet_name
+
+
+def count_facet_items_dict(facet, limit=None, exclude_active=False):
+ if not c.search_facets or \
+ not c.search_facets.get(facet) or \
+ not c.search_facets.get(facet).get('items'):
+ return 0
+ facets = []
+ for facet_item in c.search_facets.get(facet)['items']:
+ if not len(facet_item['name'].strip()):
+ continue
+ if not (facet, facet_item['name']) in list(request.args.items()):
+ facets.append(dict(active=False, **facet_item))
+ elif not exclude_active:
+ facets.append(dict(active=True, **facet_item))
+
+ # for count,
+ # print "facets " + str(facets)
+ total = len(facets)
+ log.debug("total facet: %s" % facet + " are %d" % total)
+ return total
+
+
+def random_color():
+ rgbl = [255, 0, 0]
+ random.shuffle(rgbl)
+ return tuple(rgbl)
+
+
+def check_url(the_url):
+ try:
+ urllib.request.urlopen(the_url)
+ return True
+ except urllib.error.HTTPError as e:
+ # print(e.code)
+ return False
+ except urllib.error.URLError as e:
+ # print(e.args)
+ return False
+ except Exception as error:
+ # print(error)
+ return False
+
+
+def get_color_for_type(systemtype_field_value):
+ '''Return a color assigned to a system type'''
+
+ systemtypecolors = session.get(systemtype_field_colors)
+ # log.info("color: getting color for type: %s" %systemtype_field_value)
+
+ if systemtypecolors is None:
+ log.info("color: " + systemtype_field_colors + " not found in session, creating new one")
+ systemtypecolors = {}
+ session[systemtype_field_colors] = systemtypecolors
+ else:
+ log.debug("color: " + systemtype_field_colors + " found in session having value: %s" % systemtypecolors)
+
+ e_color = systemtypecolors.get(systemtype_field_value)
+
+ if e_color is None:
+ usedcolorsLen = len(systemtypecolors)
+ colorsLen = len(systemtype_rgb_colors)
+ index = usedcolorsLen if usedcolorsLen < colorsLen else random.randint(0, colorsLen - 1)
+ e_color = systemtype_rgb_colors[index]
+ # log.debug("color: adding color %s" %e_color +" index is: "+str(index))
+ systemtypecolors[systemtype_field_value] = e_color
+ session[systemtype_field_colors] = systemtypecolors
+
+ session.save()
+ # log.debug("color: returning color %s" %e_color +" for type: "+systemtype_field_value)
+ return e_color
+
+
+def ordered_dictionary(list_to_be_sorted, property='name', ordering="asc"):
+ # print ("dict %s" %list_to_be_sorted)
+
+ ord = False if ordering == "asc" else True
+
+ if list_to_be_sorted:
+ return sorted(list_to_be_sorted, key=itemgetter(property), reverse=ord)
+
+ return list_to_be_sorted
+
+
+def qrcode_for_url(url):
+ if url:
+ try:
+ qr_code = D4S_QrCode(url)
+ image_path = qr_code.get_qrcode_path()
+ with open(image_path, "rb") as image_file:
+ return base64.b64encode(image_file.read())
+ return ""
+ except Exception as error:
+ log.error("Error on getting qrcode for url: " + url + "error: %s" % error)
+
+ return ""
+
+
+def get_list_of_organizations(limit=10, sort='packages'):
+ to_browse_organizations = []
+ try:
+ data = {}
+
+ if sort:
+ data['sort'] = sort
+
+ data['limit'] = limit
+ data['all_fields'] = True
+ ordered_organizations = []
+ ordered_organizations = logic.get_action('organization_list')({}, data)
+
+ for organization in ordered_organizations:
+ try:
+ to_browse_obj = {}
+
+ if not organization['name']:
+ continue
+
+ to_browse_obj['name'] = organization['name']
+
+ if 'package_count' in organization:
+ to_browse_obj['package_count'] = organization['package_count']
+
+ if 'display_name' in organization:
+ to_browse_obj['display_name'] = organization['display_name']
+
+ image_url = get_url_to_icon_for_ckan_entity(organization['name'], 'organization', False)
+
+ # Using ICON as first option
+ if image_url:
+ to_browse_obj['url'] = image_url
+ # Using object image_url as second one
+ elif 'image_url' in organization and organization['image_url']:
+ to_browse_obj['url'] = organization['image_url']
+ # Default placeholder
+ else:
+ to_browse_obj['url'] = h.url_for_static('/images/organisations/icon/placeholder-organization.png')
+
+ to_browse_organizations.append(to_browse_obj)
+ except (logic.NotFound, logic.ValidationError, logic.NotAuthorized) as error:
+ # SILENT
+ log.warn("Error on putting organization: %s" % error)
+
+ log.info("browse %d" % len(ordered_organizations) + " organisation/s")
+ except (logic.NotFound, logic.ValidationError, logic.NotAuthorized) as error:
+ log.error("Error on getting organizations: %s" % error)
+ return []
+
+ return to_browse_organizations
+
+
+def get_list_of_groups(limit=10, sort='package_count'):
+ to_browse_groups = []
+ try:
+ data = {}
+ if sort:
+ data['sort'] = sort
+
+ data['limit'] = limit
+ data['all_fields'] = True
+ ordered_groups = []
+ ordered_groups = logic.get_action('group_list')({}, data)
+
+ log.debug("group_list: " + ", ".join(ordered_groups))
+
+ for group in ordered_groups:
+ # print "\n\ngroup %s" %group
+ try:
+ to_browse_obj = {}
+
+ if not group['name']:
+ continue
+
+ to_browse_obj['name'] = group['name']
+
+ if 'package_count' in group:
+ to_browse_obj['package_count'] = group['package_count']
+
+ if 'display_name' in group:
+ to_browse_obj['display_name'] = group['display_name']
+
+ if 'image_url' in group and group['image_url']:
+ to_browse_obj['url'] = group['image_url']
+ else:
+ to_browse_obj['url'] = get_url_to_icon_for_ckan_entity(group['name'], 'group')
+
+ to_browse_groups.append(to_browse_obj)
+ except (logic.NotFound, logic.ValidationError, logic.NotAuthorized) as error:
+ # SILENT
+ log.warn("Error on putting group: %s" % error)
+
+ log.info("browse %d" % len(ordered_groups) + " organisation/s")
+ except (logic.NotFound, logic.ValidationError, logic.NotAuthorized) as error:
+ log.error("Error on getting group: %s" % error)
+ return []
+
+ return to_browse_groups
+
+
+def get_browse_info_for_organisations_or_groups(type='organization', limit=10, sort_field=None):
+ sort = None
+ if sort_field:
+ sort = sort_field
+
+ if type == 'organization':
+ if sort:
+ return get_list_of_organizations(limit, sort)
+ else:
+ return get_list_of_organizations(limit)
+
+ elif type == 'group':
+ if sort:
+ return get_list_of_groups(limit, sort)
+ else:
+ return get_list_of_groups(limit)
+
+ return []
+
+
+def get_image_display_for_group(item_id):
+ if item_id:
+ try:
+ item_obj = model.Group.get(item_id)
+
+ if item_obj and item_obj.image_url:
+ return item_obj.image_url
+ else:
+ return h.url_for_static('/images/groups/icon/placeholder-group.png')
+
+ except Exception as error:
+ log.error("Error on getting item obj: %s" % item_id + "error: %s" % error)
+
+
+def get_application_path():
+ if getattr(sys, 'frozen', False):
+ # If the application is run as a bundle, the pyInstaller bootloader
+ # extends the sys module by a flag frozen=True and sets the app
+ # path into variable _MEIPASS'.
+ application_path = sys._MEIPASS
+ else:
+ application_path = os.path.dirname(os.path.abspath(__file__))
+
+ return application_path
+
+
+'''
+Get icon url for input entity type
+@:param default_placeholder if True returns the URL of default image, otherwise None.
+'''
+
+
+def get_url_to_icon_for_ckan_entity(item_name, entity_type=None, default_placeholder=True):
+ if not entity_type or not item_name:
+ return None
+
+ dir_images_full_path = get_application_path() + "/public/images"
+ dir_images_relative_path = "/images"
+
+ if entity_type == 'group':
+ dir_images_full_path += "/groups"
+ dir_images_relative_path += "/groups"
+ placeholder_icon = "placeholder-group.png"
+ elif entity_type == 'organization':
+ dir_images_full_path += "/organisations"
+ dir_images_relative_path += "/organisations"
+ placeholder_icon = "placeholder-organization.png"
+ elif entity_type == 'type':
+ dir_images_full_path += "/types"
+ dir_images_relative_path += "/types"
+ placeholder_icon = "placeholder-type.png"
+ else:
+ return None
+
+ icon_path = dir_images_full_path + "/icon/" + item_name.lower() + ".png"
+ if os.path.isfile(icon_path):
+ return h.url_for_static(dir_images_relative_path + "/icon/" + item_name.lower() + ".png")
+ elif default_placeholder:
+ return h.url_for_static(dir_images_relative_path + "/icon/" + placeholder_icon)
+
+ return None
+
+
+def get_user_info(user_id_or_name):
+ if user_id_or_name:
+ try:
+
+ item_obj = model.User.get(user_id_or_name)
+
+ if item_obj:
+ return item_obj
+
+ return None
+ except Exception as error:
+ log.error("Error on getting item obj: %s" % user_id_or_name + "error: %s" % error)
+
+ return None
+
+
+'''
+Search the value of my_search_string into input file {ckan_po_file} or the default file ckan.po provided as CKAN language
+and returns its translate
+'''
+
+
+def get_ckan_translate_for(ckan_po_file, my_search_string):
+ my_translate = session.get(TRANSLATE_OF_ + my_search_string)
+
+ if not my_search_string:
+ return ""
+
+ if my_translate:
+ log.info("Translate of '%s' " % my_search_string + " found in session as: %s" % my_translate)
+ return my_translate
+
+ if not ckan_po_file:
+ ckan_po_file = "/usr/lib/ckan/default/src/ckan/ckan/i18n/en_GB/LC_MESSAGES/ckan.po"
+
+ numlines = 0
+ numfound = 0
+ found = 0
+ line_text = ""
+
+ try:
+ infile = open(ckan_po_file, "r")
+
+ for line in infile:
+ numlines += 1
+ if found > 0:
+ numfound += 1
+ line_text += str(line)
+ found = 0 # reset found
+
+ # found += line.upper().count(my_search_string.upper())
+ found += line.count(my_search_string)
+
+ if found > 0:
+ log.debug("The search string '%s'" % my_search_string + " was found. Read the line: %s" % str(line))
+
+ infile.close()
+
+ except Exception as e:
+ print(("Exception during parsing the file %s" % ckan_po_file, e))
+
+ log.info("Recap: '%s' was found" % my_search_string + " %i times " % numfound + "in %i lines" % numlines)
+ log.debug("Line text is: %s" % line_text)
+
+ pattern = '"([A-Za-z0-9_ \./\\-]*)"'
+ m = re.search(pattern, line_text)
+
+ try:
+ my_translate = m.group()
+ except Exception as e:
+ print(("Pattern %s" % my_search_string + " not found ", e))
+
+ if my_translate:
+ log.debug("Replacing quotas...")
+ my_translate = my_translate.replace("\"", "")
+
+ log.info("Found the string '%s'" % my_translate + " that translating '%s'" % my_search_string)
+
+ session[TRANSLATE_OF_ + my_search_string] = my_translate
+ session.save()
+
+ return my_translate
+
+
+def get_location_to_bboxes():
+ config = configparser.ConfigParser()
+ config.optionxform = str
+ location_to_bboxes = {}
+ try:
+ bboxes_file = get_application_path() + "/public/location_to_bboxes.ini"
+ log.debug("bboxes_file is: '%s'" % bboxes_file)
+ config.read(bboxes_file)
+ for section_name in config.sections():
+ log.debug('Location to bboxes Section: ' + section_name)
+ # print ' Options:', parser.options(section_name)
+ for name, value in config.items(section_name):
+ location_to_bboxes[name] = value.replace(",", "%2C")
+
+ ordDictBboxes = collections.OrderedDict(sorted(location_to_bboxes.items()))
+ log.debug("Ordered 'bboxes_file' dict: '%s'" % ordDictBboxes)
+ return ordDictBboxes
+ except Exception as error:
+ log.error("Error on reading file: %s" % bboxes_file + "error: %s" % error)
+
+
+def get_content_moderator_system_placeholder():
+ return systemtype_cms_fields_placeholders
+
+
+def get_site_statistics() -> dict[str, int]:
+ return {
+ 'dataset_count': logic.get_action('package_search')({}, {"rows": 1})['count'],
+ 'group_count': len(logic.get_action('group_list')({}, {})),
+ 'organization_count': len(logic.get_action('organization_list')({}, {}))
+ }
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/plugin.py b/ckanext-d4science_theme/ckanext/d4science_theme/plugin.py
new file mode 100644
index 0000000..5769285
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/plugin.py
@@ -0,0 +1,440 @@
+# encoding: utf-8
+from logging import getLogger
+
+import ckan.plugins as plugins
+
+from ckanext.d4science_theme import helpers
+import ckan.plugins.toolkit as toolkit
+import ckan.model as model
+from ckanext.d4science_theme.controllers.home import d4SHomeController
+from ckanext.d4science_theme.controllers.systemtype import d4STypeController
+from ckanext.d4science_theme.controllers.organization import OrganizationVREController
+#from ckan.controllers.home import HomeController
+#from ckan.plugins import IRoutes
+from flask import Blueprint, render_template
+
+from ckan.common import (
+ g
+)
+from flask import Flask, g
+from ckan.lib.app_globals import app_globals
+import ckan.plugins.toolkit as toolkit
+
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+log = getLogger(__name__)
+
+d4s_ctg_namespaces_controller = None
+
+def remove_check_replicated_custom_key(schema):
+ if schema is not None:
+ schema.pop('__before', None)
+
+ return schema
+
+#CREATED BY FRANCESCO MANGIACRAPA FOR OVERRIDING THE package_extras_save FROM dictization.model_save.py
+# Is this needed?
+def _package_extras_save(extra_dicts, obj, context):
+ ''' It can save repeated extras as key-value '''
+ allow_partial_update = context.get("allow_partial_update", False)
+ if extra_dicts is None and allow_partial_update:
+ return
+
+ model = context["model"]
+ session = context["session"]
+
+ #ADDED BY FRANCESCO MANGIACRAPA
+ log.debug("extra_dicts: "+ str(extra_dicts))
+ #print "extra_dicts: "+str(extra_dicts)
+
+ extras_list = obj.extras_list
+ #extras = dict((extra.key, extra) for extra in extras_list)
+ old_extras = {}
+ extras = {}
+ for extra in extras_list or []:
+ old_extras.setdefault(extra.key, []).append(extra.value)
+ extras.setdefault(extra.key, []).append(extra)
+
+ #ADDED BY FRANCESCO MANGIACRAPA
+ #print "old_extras: "+str(old_extras)
+
+ new_extras = {}
+ for extra_dict in extra_dicts or []:
+ #print 'extra_dict key: '+extra_dict["key"] + ', value: '+extra_dict["value"]
+ #new_extras.setdefault(extra_dict["key"], []).append(extra_dict["value"])
+ if extra_dict.get("deleted"):
+ log.debug("extra_dict deleted: "+str(extra_dict["key"]))
+ #print 'extra_dict deleted: '+extra_dict["key"]
+ continue
+
+ #if extra_dict['value'] is not None and not extra_dict["value"] == "":
+ if extra_dict['value'] is not None:
+ new_extras.setdefault(extra_dict["key"], []).append(extra_dict["value"])
+
+ #ADDED BY FRANCESCO MANGIACRAPA
+ log.debug("new_extras: "+str(new_extras))
+ #print "new_extras: "+str(new_extras)
+
+ #new
+ for key in set(new_extras.keys()) - set(old_extras.keys()):
+ state = 'active'
+ log.debug("adding key: "+str(key))
+ #print "adding key: "+str(key)
+ extra_lst = new_extras[key]
+ for extra in extra_lst:
+ extra = model.PackageExtra(state=state, key=key, value=extra)
+ session.add(extra)
+ extras_list.append(extra)
+
+ #deleted
+ for key in set(old_extras.keys()) - set(new_extras.keys()):
+ log.debug("deleting key: "+str(key))
+ #print "deleting key: "+str(key)
+ extra_lst = extras[key]
+ for extra in extra_lst:
+ state = 'deleted'
+ extra.state = state
+ extras_list.remove(extra)
+
+ #changed
+ for key in set(new_extras.keys()) & set(old_extras.keys()):
+ #for each value of new list
+ for value in new_extras[key]:
+ old_occur = old_extras[key].count(value)
+ new_occur = new_extras[key].count(value)
+ log.debug("value: "+str(value) + ", new_occur: "+str(new_occur)+ ", old_occur: "+str(old_occur))
+ #print "value: "+str(value) + ", new_occur: "+str(new_occur) + ", old_occur: "+str(old_occur)
+ # it is an old value deleted or not
+ if value in old_extras[key]:
+ if old_occur == new_occur:
+ #print "extra - occurrences of: "+str(value) +", are equal into both list"
+ log.debug("extra - occurrences of: "+str(value) +", are equal into both list")
+ #there is a little bug, this code return always the first element, so I'm fixing with #FIX-STATUS
+ extra_values = get_package_for_value(extras[key], value)
+ #extras_list.append(extra)
+ for extra in extra_values:
+ state = 'active'
+ extra.state = state
+ session.add(extra)
+ #print "extra updated: "+str(extra)
+ log.debug("extra updated: "+str(extra))
+
+ elif new_occur > old_occur:
+ #print "extra - a new occurrence of: "+str(value) +", is present into new list, adding it to old list"
+ log.debug("extra - a new occurrence of: "+str(value) +", is present into new list, adding it to old list")
+ state = 'active'
+ extra = model.PackageExtra(state=state, key=key, value=value)
+ extra.state = state
+ session.add(extra)
+ extras_list.append(extra)
+ old_extras[key].append(value)
+ log.debug("old extra values updated: "+str(old_extras[key]))
+ #print "old extra values updated: "+str(old_extras[key])
+
+ else:
+ #remove all occurrences deleted - this code could be optimized, it is run several times but could be performed one shot
+ countDelete = old_occur-new_occur
+ log.debug("extra - occurrence of: "+str(value) +", is not present into new list, removing "+str(countDelete) + " occurrence/s from old list")
+ #print "extra - occurrence of: "+str(value) +", is not present into new list, removing "+str(countDelete)+" occurrence/s from old list"
+ extra_values = get_package_for_value(extras[key], value)
+ for idx, extra in enumerate(extra_values):
+ if idx < countDelete:
+ #print "extra - occurrence of: "+str(value) +", is not present into new list, removing it from old list"
+ log.debug("pkg extra deleting: "+str(extra.value))
+ #print "pkg extra deleting: "+str(extra.value)
+ state = 'deleted'
+ extra.state = state
+
+ else:
+ #print "pkg extra reactivating: "+str(extra.value)
+ log.debug("pkg extra reactivating: "+str(extra.value))
+ state = 'active'
+ extra.state = state
+ session.add(extra)
+
+ else:
+ #print "extra new value: "+str(value)
+ log.debug("extra new value: "+str(value))
+ state = 'active'
+ extra = model.PackageExtra(state=state, key=key, value=value)
+ extra.state = state
+ session.add(extra)
+ extras_list.append(extra)
+
+
+ #for each value of old list
+ for value in old_extras[key]:
+ #if value is not present in new list
+ if value not in new_extras[key]:
+ extra_values = get_package_for_value(extras[key], value)
+ for extra in extra_values:
+ #print "not present extra deleting: "+str(extra)
+ log.debug("not present extra deleting: "+str(extra))
+ state = 'deleted'
+ extra.state = state
+
+
+#ADDED BY FRANCESCO MANGIACRAPA
+def get_package_for_value(list_package, value):
+ ''' Returns a list of packages containing the value passed in input
+ '''
+ lst = []
+ for x in list_package:
+ if x.value == value:
+ lst.append(x)
+ else:
+ return lst
+
+ return lst
+
+
+class D4Science_ThemePlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm):
+ plugins.implements(plugins.IConfigurer)
+ plugins.implements(plugins.IDatasetForm)
+ plugins.implements(plugins.ITemplateHelpers)
+ plugins.implements(plugins.IFacets)
+ #plugins.implements(IRoutes, inherit=True)
+
+ #ckan 2.10
+ plugins.implements(plugins.IBlueprint)
+
+ # IConfigurer
+ def update_config(self, config_):
+ # Add this plugin's templates dir to CKAN's extra_template_paths, so
+ # that CKAN will use this plugin's custom templates.
+ toolkit.add_template_directory(config_, 'templates')
+
+ # Add this plugin's public dir to CKAN's extra_public_paths, so
+ # that CKAN will use this plugin's custom static files.
+ toolkit.add_public_directory(config_, 'public')
+
+ # Register this plugin's fanstatic directory with CKAN.
+ # Here, 'fanstatic' is the path to the fanstatic directory
+ # (relative to this plugin.py file), and 'example_theme' is the name
+ # that we'll use to refer to this fanstatic directory from CKAN
+ # templates.
+ toolkit.add_resource('assets', 'd4science_theme')
+ # toolkit.add_resource('assets', 'd4science_scripts')
+
+ #IDatasetForm
+ def create_package_schema(self):
+ # let's grab the default schema in our plugin
+ schema = super(D4Science_ThemePlugin, self).create_package_schema()
+ schema = remove_check_replicated_custom_key(schema)
+ #d.package_dict_save = _package_dict_save
+ return schema
+
+ #IDatasetForm
+ def update_package_schema(self):
+ schema = super(D4Science_ThemePlugin, self).update_package_schema()
+ schema = remove_check_replicated_custom_key(schema)
+ return schema
+
+ #IDatasetForm
+ def show_package_schema(self):
+ schema = super(D4Science_ThemePlugin, self).show_package_schema()
+ schema = remove_check_replicated_custom_key(schema)
+ return schema
+
+ #IDatasetForm
+ def is_fallback(self):
+ # Return True to register this plugin as the default handler for package types not handled by any other IDatasetForm plugin
+ return False
+
+ #IDatasetForm
+ def package_types(self):
+ # This plugin doesn't handle any special package types, it just
+ # registers itself as the default (above).
+ return []
+
+
+ #ITemplateHelpers
+ def get_helpers(self):
+ log.info("get_helpers called...")
+ '''Register functions as a template
+ helper function.
+ '''
+ # Template helper function names should begin with the name of the
+ # extension they belong to, to avoid clashing with functions from
+ # other extensions.
+ return {
+ 'd4science_theme_get_user_role_for_group_or_org': helpers.get_user_role_for_group_or_org,
+ 'd4science_theme_get_parents_for_group': helpers.get_parents_for_group,
+ 'get_header_param': helpers.get_header_param,
+ 'get_request_param': helpers.get_request_param,
+ 'get_cookie_value': helpers.get_cookie_value,
+ 'd4science_theme_markdown_extract_html' : helpers.markdown_extract_html,
+ 'd4science_theme_get_systemtype_value_from_extras' : helpers.get_systemtype_value_from_extras,
+ 'd4science_theme_get_systemtype_field_dict_from_session' : helpers.get_systemtype_field_dict_from_session,
+ 'd4science_theme_get_namespace_separator_from_session' : helpers.get_namespace_separator_from_session,
+ 'd4science_theme_get_extras' : helpers.get_extras,
+ 'd4science_theme_count_facet_items_dict' : helpers.count_facet_items_dict,
+ 'd4science_theme_purge_namespace_to_facet': helpers.purge_namespace_to_fieldname,
+ 'd4science_get_color_for_type': helpers.get_color_for_type,
+ 'd4science_get_d4s_namespace_controller': helpers.get_d4s_namespace_controller,
+ 'd4science_get_extras_indexed_for_namespaces': helpers.get_extras_indexed_for_namespaces,
+ 'd4science_get_namespaces_dict': helpers.get_namespaces_dict,
+ 'd4science_get_extra_for_category' : helpers.get_extra_for_category,
+ 'd4science_get_ordered_dictionary': helpers.ordered_dictionary,
+ 'd4science_get_qrcode_for_url': helpers.qrcode_for_url,
+ 'd4science_get_list_of_organizations': helpers.get_list_of_organizations,
+ 'd4science_get_image_display_for_group': helpers.get_image_display_for_group,
+ 'd4science_get_list_of_groups': helpers.get_list_of_groups,
+ 'd4science_get_browse_info_for_organisations_or_groups': helpers.get_browse_info_for_organisations_or_groups,
+ 'd4science_get_user_info': helpers.get_user_info,
+ 'd4science_get_url_to_icon_for_ckan_entity' : helpers.get_url_to_icon_for_ckan_entity,
+ 'd4science_get_ckan_translate_for' : helpers.get_ckan_translate_for,
+ 'd4science_get_location_to_bboxes' : helpers.get_location_to_bboxes,
+ 'd4science_get_content_moderator_system_placeholder': helpers.get_content_moderator_system_placeholder,
+ 'd4science_get_site_statistics': helpers.get_site_statistics,
+ }
+
+ #Overriding package_extras_save method
+ # Is this needed?
+ # model_save.package_extras_save = _package_extras_save
+
+ #Overriding index home controller - rimosso in ckan 2.10
+ #d4sHC = d4SHomeController()
+ # HomeController.index = d4sHC.index
+
+ global d4s_ctg_namespaces_controller
+
+ #if d4s_ctg_namespaces_controller is None:
+ # log.info("d4s_ctg_namespaces_controller instancing...")
+ # d4s_ctg_namespaces_controller = helpers.get_d4s_namespace_controller()
+ # log.info("d4s_ctg_namespaces_controller instancied %s" % d4s_ctg_namespaces_controller)
+
+
+ #IFacets
+ def dataset_facets(self, facets_dict, package_type):
+ facets_dict = self._update_facets(facets_dict)
+ return facets_dict
+
+ def group_facets(self, facets_dict, group_type, package_type):
+ # facets_dict = self._update_facets(facets_dict)
+ return facets_dict
+
+ def organization_facets(self, facets_dict, organization_type, package_type):
+ # facets_dict = self._update_facets(facets_dict)
+ return facets_dict
+
+ def _update_facets(self, facets_dict):
+ '''Add 'metadatatype' to facets if not already present.'''
+
+ log.debug("facets_dict: ")
+ log.debug(', '.join(facets_dict))
+
+ metadatatype = helpers.get_systemtype_field_dict_from_session()
+
+ '''Adding system:type'''
+ facet_title = helpers.purge_namespace_to_fieldname(str(metadatatype['id']))
+ facet_title = plugins.toolkit._(facet_title.capitalize() + 's')
+ facets_dict = self._add_or_update_facet(metadatatype['name'],facet_title, facets_dict)
+
+ log.info("facets dict after update: " + ', '.join(facets_dict))
+ log.info("site_url is: " + g.site_url)
+
+ #ADD IT IN THE CUSTOMIZATION?
+ if g.site_url:
+
+ dev_sites = ['https://ckan-d-d4s.d4science.org']
+ #GRSF Catalogues. 'Status of the Record' must be distributed everywhere, see #23398
+ grsf_sites = ['https://ckan-grsf-admin2.d4science.org', 'https://ckan-grsf.pre.d4science.org', 'https://ckan-grsf.d4science.org', 'https://ckan-grsf-pre.d4science.org']
+ sbd_sites = ['https://ckan.sobigdata.d4science.net', 'https://ckan-sobigdata.d4science.org',
+ 'https://ckan-sobigdata2.d4science.org']
+
+ if g.site_url in dev_sites:
+ '''Adding Status of the GRSF record'''
+ facets_dict = self._add_or_update_facet("StatusoftheRecord", "Status of the Record", facets_dict,
+ display_after_facet='groups')
+
+ facets_dict = self._add_or_update_facet("Anno", "Anno", facets_dict,
+ display_after_facet='groups')
+
+ elif g.site_url in grsf_sites:
+ '''Adding Status of the GRSF record'''
+ # facets_dict = self._add_or_update_facet("StatusoftheGRSFrecord", "Status of the GRSF record", facets_dict, display_after_facet='groups')
+ # Fixing #23348
+ facets_dict = self._add_or_update_facet("StatusoftheRecord", "Status of the Record", facets_dict,
+ display_after_facet='groups')
+ elif g.site_url in sbd_sites:
+ '''Adding the field Availability '''
+ facets_dict = self._add_or_update_facet("Availability", "Availability", facets_dict,
+ display_after_facet='groups')
+
+ return facets_dict
+
+ def init_template_globals(app):
+ from ckan.lib.app_globals import app_globals
+ app.jinja_env.globals.update(g=app_globals)
+
+ #changed to migrate to ckan 2.10:
+ def get_blueprint(self):
+ d4sHC = d4SHomeController()
+ d4sTC = d4STypeController()
+ d4sOC = OrganizationVREController()
+
+ blueprint = Blueprint('d4s', self.__module__)
+
+ def tags():
+ return render_template('tag/index.html')
+
+ def groups():
+ return render_template('group/index.html')
+
+ rules = [
+ ('/', 'index', d4sHC.index),
+ ('/types', 'types', d4sTC.index),
+ ('/organization_vre', 'organization_vre', d4sOC.index),
+ ('/tags', 'tags', tags),
+ ('/groups', 'groups', groups),
+ ]
+ for rule in rules:
+ blueprint.add_url_rule(*rule)
+
+ return blueprint
+
+ # def before_map(self, map):
+ # """This IRoutes implementation overrides the standard
+ # ``/user/register`` behaviour with a custom controller. You
+ # might instead use it to provide a completely new page, for
+ # example.
+ # Note that we have also provided a custom register form
+ # template at ``theme/templates/user/register.html``.
+ # """
+ # # Hook in our custom user controller at the points of creation
+ # # and edition.
+ # #
+ # #map.connect('/type', controller='ckanext.d4science_theme.controllers.type::d4STypeController', action='index')
+ # map.connect('/type', controller='ckanext.d4science_theme.controllers.systemtype:d4STypeController', action='index')
+ # ''' Added by Francesco Mangiacrapa, see: #8964 '''
+ # organization_vre = OrganizationVREController()
+ # map.connect('/organization_vre', controller='ckanext.d4science_theme.controllers.organization:OrganizationVREController', action='index')
+ # map.connect('/organization_vre/{id}', controller='ckanext.d4science_theme.controllers.organization:OrganizationVREController', action='read')
+ # map.redirect('/types', "/type")
+ # return map
+
+
+ def _add_or_update_facet(self, facet_key, facet_value, facets_dict, display_after_facet='organization'):
+
+ #Updating ordering of facets_dict OrderedDict
+ if str(facet_key) not in facets_dict:
+
+ new_orderded_facets_dict=facets_dict.__class__()
+ for key, value in list(facets_dict.items()):
+ new_orderded_facets_dict[key]=value
+ # #the field 'metadatatype' will be inserted after following key
+ if key==display_after_facet:
+ new_orderded_facets_dict[facet_key]=facet_value
+
+ facets_dict.clear()
+ facets_dict.update(new_orderded_facets_dict)
+ log.debug("facets_dict ordered: ")
+ log.debug(', '.join(facets_dict))
+
+ return facets_dict
+
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/.gitignore b/ckanext-d4science_theme/ckanext/d4science_theme/public/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/D4ScienceDataCataloguelogo.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/D4ScienceDataCataloguelogo.png
new file mode 100644
index 0000000..27dce14
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/D4ScienceDataCataloguelogo.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/D4ScienceDataCataloguelogo2.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/D4ScienceDataCataloguelogo2.png
new file mode 100644
index 0000000..1c378c5
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/D4ScienceDataCataloguelogo2.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/bg-noise.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/bg-noise.png
new file mode 100644
index 0000000..40828c6
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/bg-noise.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/bg-pattern.svg b/ckanext-d4science_theme/ckanext/d4science_theme/public/bg-pattern.svg
new file mode 100644
index 0000000..0a3e7c8
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/public/bg-pattern.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/ckan-logo-footer.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/ckan-logo-footer.png
new file mode 100644
index 0000000..09aa4ca
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/ckan-logo-footer.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/d4ScienceDataCatalogue.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4ScienceDataCatalogue.png
new file mode 100644
index 0000000..8b7d380
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4ScienceDataCatalogue.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/d4s_tagcloud.js b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4s_tagcloud.js
new file mode 100644
index 0000000..aa3c487
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4s_tagcloud.js
@@ -0,0 +1,58 @@
+/*!
+ * d4s_tagcloud.js
+ * D4science Tag Cloud which using Tag Cloud Plugin for JQuery
+ *
+ * jquery.tagcloud.js
+ * created by Francesco Mangiacrapa francesco.mangiacrapa@isti.cnr.it
+ */
+loadTagCloudJS = function (elementIDtoCloud, rgb_start, rgb_end) {
+
+ //console.log('start: '+rgb_start)
+ //console.log('end: '+rgb_end)
+
+ if(!rgb_start)
+ rgb_start = '#C0C0C0';
+
+ if(!rgb_end)
+ rgb_end = '#000066';
+
+ var script = document.createElement('script');
+ script.onload = function() {
+ /*console.log("TagCloud json loaded and ready");*/
+ $.fn.tagcloud.defaults = {
+ size: {start: 13, end: 20, unit: 'px'},
+ color: {start: rgb_start, end: rgb_end}
+ }
+ $('#'+elementIDtoCloud +' a').tagcloud();
+ };
+
+ script.type = "text/javascript";
+ script.src = "jquery.tagcloud.js";
+ document.getElementsByTagName('head')[0].appendChild(script);
+}
+
+loadCloud = function (elementIDtoCloud, rgb_start, rgb_end) {
+
+ if(!window.jQuery){
+ var script = document.createElement('script');
+ script.onload = function() {
+ /*console.log("JQuery loaded and ready");*/
+ loadTagCloudJS(elementIDtoCloud,rgb_start,rgb_end);
+ };
+
+ script.type = "text/javascript";
+ script.src = "https://code.jquery.com/jquery-1.11.0.min.js";
+ document.getElementsByTagName('head')[0].appendChild(script);
+
+ }else {
+ loadTagCloudJS(elementIDtoCloud,rgb_start,rgb_end);
+ }
+
+ /*SHUFFLE TAGS*/
+ var cloud = document.querySelector('#'+elementIDtoCloud);
+ if (cloud == null)
+ return;
+ for (var i = cloud.children.length; i >= 0; i--) {
+ cloud.appendChild(cloud.children[Math.random() * i | 0]);
+ }
+}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/d4science.ico b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4science.ico
new file mode 100644
index 0000000..718bf5d
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4science.ico differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/d4science_logo.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4science_logo.png
new file mode 100644
index 0000000..3b87305
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/d4science_logo.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/favicon.ico b/ckanext-d4science_theme/ckanext/d4science_theme/public/favicon.ico
new file mode 100644
index 0000000..718bf5d
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/favicon.ico differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/gCube_70.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/gCube_70.png
new file mode 100644
index 0000000..018d52c
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/gCube_70.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/gcubedatacataloguelogo.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/gcubedatacataloguelogo.png
new file mode 100644
index 0000000..1b4bf52
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/gcubedatacataloguelogo.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/grsf/GRSF_for_admins_logo.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/grsf/GRSF_for_admins_logo.png
new file mode 100644
index 0000000..dbf83de
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/grsf/GRSF_for_admins_logo.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/groups/icon/placeholder-group.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/groups/icon/placeholder-group.png
new file mode 100644
index 0000000..1ece150
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/groups/icon/placeholder-group.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/d4sciencelabs.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/d4sciencelabs.png
new file mode 100644
index 0000000..7d1a75e
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/d4sciencelabs.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/emodnet.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/emodnet.png
new file mode 100644
index 0000000..0724a33
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/emodnet.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/fao.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/fao.png
new file mode 100644
index 0000000..13d5ef0
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/fao.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/grsf.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/grsf.png
new file mode 100644
index 0000000..91ccbbb
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/grsf.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/d4sciencelabs.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/d4sciencelabs.png
new file mode 100644
index 0000000..e0b8d80
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/d4sciencelabs.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/emodnet.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/emodnet.png
new file mode 100644
index 0000000..4eb8091
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/emodnet.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/fao.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/fao.png
new file mode 100644
index 0000000..aa815b4
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/fao.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/grsf.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/grsf.png
new file mode 100644
index 0000000..4343813
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/grsf.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/imarine.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/imarine.png
new file mode 100644
index 0000000..9d7a311
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/imarine.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/placeholder-organization.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/placeholder-organization.png
new file mode 100644
index 0000000..30616f2
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/placeholder-organization.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/rprototypinglab.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/rprototypinglab.png
new file mode 100644
index 0000000..743f19c
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/icon/rprototypinglab.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/imarine.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/imarine.png
new file mode 100644
index 0000000..875348a
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/imarine.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/rprototypinglab.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/rprototypinglab.png
new file mode 100644
index 0000000..472294b
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/organisations/rprototypinglab.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/codelist.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/codelist.png
new file mode 100644
index 0000000..5cb017a
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/codelist.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/dataset.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/dataset.png
new file mode 100644
index 0000000..8ded500
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/dataset.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/dsd.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/dsd.png
new file mode 100644
index 0000000..101975b
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/dsd.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/metadata.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/metadata.png
new file mode 100644
index 0000000..84d395e
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/metadata.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/placeholder-type.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/placeholder-type.png
new file mode 100644
index 0000000..1ca5afb
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/placeholder-type.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/researchobject.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/researchobject.png
new file mode 100644
index 0000000..cab2b45
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/researchobject.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/series.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/series.png
new file mode 100644
index 0000000..573d43f
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/series.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/service.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/service.png
new file mode 100644
index 0000000..54dd849
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/service.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/trainingmaterial.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/trainingmaterial.png
new file mode 100644
index 0000000..38ea713
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/trainingmaterial.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/virtualresearchenvironment.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/virtualresearchenvironment.png
new file mode 100644
index 0000000..91a8a97
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/icon/virtualresearchenvironment.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/placeholder-type.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/placeholder-type.png
new file mode 100644
index 0000000..d09e87e
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/images/types/placeholder-type.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/jquery.tagcloud.js b/ckanext-d4science_theme/ckanext/d4science_theme/public/jquery.tagcloud.js
new file mode 100644
index 0000000..4e5d5a3
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/public/jquery.tagcloud.js
@@ -0,0 +1,92 @@
+/*!
+ * jquery.tagcloud.js
+ * A Simple Tag Cloud Plugin for JQuery
+ *
+ * https://github.com/addywaddy/jquery.tagcloud.js
+ * created by Adam Groves
+ */
+(function($) {
+
+ /*global jQuery*/
+ "use strict";
+
+ var compareWeights = function(a, b)
+ {
+ return a - b;
+ };
+
+ // Converts hex to an RGB array
+ var toRGB = function(code) {
+ if (code.length === 4) {
+ code = code.replace(/(\w)(\w)(\w)/gi, "\$1\$1\$2\$2\$3\$3");
+ }
+ var hex = /(\w{2})(\w{2})(\w{2})/.exec(code);
+ return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];
+ };
+
+ // Converts an RGB array to hex
+ var toHex = function(ary) {
+ return "#" + jQuery.map(ary, function(i) {
+ var hex = i.toString(16);
+ hex = (hex.length === 1) ? "0" + hex : hex;
+ return hex;
+ }).join("");
+ };
+
+ var colorIncrement = function(color, range) {
+ return jQuery.map(toRGB(color.end), function(n, i) {
+ return (n - toRGB(color.start)[i])/range;
+ });
+ };
+
+ var tagColor = function(color, increment, weighting) {
+ var rgb = jQuery.map(toRGB(color.start), function(n, i) {
+ var ref = Math.round(n + (increment[i] * weighting));
+ if (ref > 255) {
+ ref = 255;
+ } else {
+ if (ref < 0) {
+ ref = 0;
+ }
+ }
+ return ref;
+ });
+ return toHex(rgb);
+ };
+
+ $.fn.tagcloud = function(options) {
+
+ var opts = $.extend({}, $.fn.tagcloud.defaults, options);
+ var tagWeights = this.map(function(){
+ return $(this).attr("rel");
+ });
+ tagWeights = jQuery.makeArray(tagWeights).sort(compareWeights);
+ var lowest = tagWeights[0];
+ var highest = tagWeights.pop();
+ var range = highest - lowest;
+ if(range === 0) {range = 1;}
+ // Sizes
+ var fontIncr, colorIncr;
+ if (opts.size) {
+ fontIncr = (opts.size.end - opts.size.start)/range;
+ }
+ // Colors
+ if (opts.color) {
+ colorIncr = colorIncrement (opts.color, range);
+ }
+ return this.each(function() {
+ var weighting = $(this).attr("rel") - lowest;
+ if (opts.size) {
+ $(this).css({"font-size": opts.size.start + (weighting * fontIncr) + opts.size.unit});
+ }
+ if (opts.color) {
+ $(this).css({"color": tagColor(opts.color, colorIncr, weighting)});
+ }
+ });
+ };
+
+ $.fn.tagcloud.defaults = {
+ size: {start: 14, end: 18, unit: "pt"}
+ };
+
+})(jQuery);
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/location_to_bboxes.ini b/ckanext-d4science_theme/ckanext/d4science_theme/public/location_to_bboxes.ini
new file mode 100644
index 0000000..e7f14c2
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/public/location_to_bboxes.ini
@@ -0,0 +1,16 @@
+[location_to_bboxes_epsg_4326]
+Antarctica=-180,-90,180,-63.2706604895
+Brazil=-73.9872354804,-33.7683777809,-34.7299934555,5.24448639569
+France=-54.5247541978,2.05338918702,9.56001631027,51.1485061713
+Greece=20.1500159034,34.9199876979,26.6041955909,41.8269046087
+Italy=6.7499552751,36.619987291,18.4802470232,47.1153931748
+Ivory Coast=-8.60288021487,4.33828847902,-2.56218950033,10.5240607772
+Mexico=-117.12776,14.5388286402,-86.811982388,32.72083
+Nigeria=2.69170169436,4.24059418377,14.5771777686,13.8659239771
+Philippines=117.17427453,5.58100332277,126.537423944,18.5052273625
+Portugal=-9.52657060387,36.838268541,-6.3890876937,42.280468655
+Puerto Rico=-67.2424275377,17.946553453,-65.5910037909,18.5206011011
+Slovenia=13.6981099789,45.4523163926,16.5648083839,46.8523859727
+Thailand=97.3758964376,5.69138418215,105.589038527,20.4178496363
+United Kingdom=-7.57216793459,49.959999905,1.68153079591,58.6350001085
+United States=-171.791110603,18.91619,-66.96466,71.3577635769
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/oai2_style.xsl b/ckanext-d4science_theme/ckanext/d4science_theme/public/oai2_style.xsl
new file mode 100644
index 0000000..2a41373
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/public/oai2_style.xsl
@@ -0,0 +1,690 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+td.value {
+ vertical-align: top;
+ padding-left: 1em;
+ padding: 3px;
+}
+td.key {
+ background-color: #e0e0ff;
+ padding: 3px;
+ text-align: right;
+ border: 1px solid #c0c0c0;
+ white-space: nowrap;
+ font-weight: bold;
+ vertical-align: top;
+}
+.dcdata td.key {
+ background-color: #ffffe0;
+}
+body {
+ margin: 1em 2em 1em 2em;
+}
+h1, h2, h3 {
+ font-family: sans-serif;
+ clear: left;
+}
+h1 {
+ padding-bottom: 2px;
+ margin-bottom: 0px;
+}
+h2 {
+ margin-bottom: 0.5em;
+}
+h3 {
+ margin-bottom: 0.3em;
+ font-size: medium;
+}
+
+h5 {
+ color: gray;
+ font-size: 12px;
+ margin: 0;
+ padding-top: 5px;
+}
+
+.about-xsl {
+ font-size: 12px;
+ font-family: "Arial";
+ color: gray;
+}
+
+.link {
+ border: 1px outset #88f;
+ background-color: #c0c0ff;
+ padding: 1px 4px 1px 4px;
+ font-size: 80%;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ color: black;
+}
+.link:hover {
+ color: red;
+}
+.link:active {
+ color: red;
+ border: 1px inset #88f;
+ background-color: #a0a0df;
+}
+.oaiRecord, .oaiRecordTitle {
+ background-color: #f0f0ff;
+ border-style: solid;
+ border-color: #d0d0d0;
+}
+h2.oaiRecordTitle {
+ background-color: #e0e0ff;
+ font-size: medium;
+ font-weight: bold;
+ padding: 10px;
+ border-width: 2px 2px 0px 2px;
+ margin: 0px;
+}
+.oaiRecord {
+ margin-bottom: 3em;
+ border-width: 2px;
+ padding: 10px;
+}
+
+.results {
+ margin-bottom: 1.5em;
+}
+ul.quicklinks {
+ margin-top: 3px;
+ padding: 4px;
+ text-align: left;
+ border-bottom: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ clear: left;
+ list-style-type: none;
+}
+ul.quicklinks li {
+ font-size: 80%;
+ #display: inline;
+ list-stlye: none;
+ font-family: sans-serif;
+ padding-left: 12px;
+}
+
+.oai-footer{
+ border-top: 1px solid #ccc;
+}
+
+p.intro {
+ font-size: 12px;
+ font-family: "Arial";
+ color: gray;
+}
+
+
+
+
+
+
+
+
+ OAI 2.0 Request Results
+
+
+
+
OAI 2.0 Request Results
+
You are viewing an HTML version of the XML OAI response. More information about this XSLT. Return to OAI-PMH 2.0 Home for this Repository
+
+
+
+
+ URL
+ URL not shown as it is very long.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Coverage
+
+
+
Rights Management
+
+
+
+
+
+ <></>
+
+
+
+
+
+
+ =""
+
+
+
+.xmlSource {
+ font-size: 70%;
+ border: solid #c0c0a0 1px;
+ background-color: #ffffe0;
+ padding: 2em 2em 2em 0em;
+}
+.xmlBlock {
+ padding-left: 2em;
+}
+.xmlTagName {
+ color: #800000;
+ font-weight: bold;
+}
+.xmlAttrName {
+ font-weight: bold;
+}
+.xmlAttrValue {
+ color: #0000c0;
+}
+
+
+
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/pageloading.gif b/ckanext-d4science_theme/ckanext/d4science_theme/public/pageloading.gif
new file mode 100644
index 0000000..ea92a4c
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/pageloading.gif differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/parthenos/logo-parthenos.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/parthenos/logo-parthenos.png
new file mode 100644
index 0000000..7fd8e9b
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/parthenos/logo-parthenos.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/public/placeholder_types.png b/ckanext-d4science_theme/ckanext/d4science_theme/public/placeholder_types.png
new file mode 100644
index 0000000..cc33636
Binary files /dev/null and b/ckanext-d4science_theme/ckanext/d4science_theme/public/placeholder_types.png differ
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/qrcodelink/__init__.py b/ckanext-d4science_theme/ckanext/d4science_theme/qrcodelink/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/qrcodelink/generate_qrcode.py b/ckanext-d4science_theme/ckanext/d4science_theme/qrcodelink/generate_qrcode.py
new file mode 100644
index 0000000..1909226
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/qrcodelink/generate_qrcode.py
@@ -0,0 +1,80 @@
+import os
+import tempfile
+import logging
+import pyqrcode
+import time
+
+# Created by Francesco Mangiacrapa
+# francesco.mangiacrapa@isti.cnr.it
+# ISTI-CNR Pisa (ITALY)
+
+CATALINA_HOME = 'CATALINA_HOME'
+log = logging.getLogger(__name__)
+
+temp_dir = None
+qr_code_dir = None
+qr_code_dir_name = "qr_code_for_catalogue"
+
+
+class D4S_QrCode():
+ def __init__(self, qr_code_url=None):
+ self._qr_code_url = qr_code_url
+ global temp_dir
+ if temp_dir is None:
+ D4S_QrCode.init_temp_dir()
+ if temp_dir is None:
+ raise Exception('No temp directory found!')
+
+ def get_qrcode_path(self):
+ image_name = self._qr_code_url.rsplit('/', 1)[-1]
+ image_name+=".svg"
+ image_path = os.path.join(qr_code_dir, image_name)
+ # ONLY IF QRCODE DOES NOT EXIST THEN IT WILL BE CREATED
+ if not os.path.isfile(image_path):
+ url = pyqrcode.create(self._qr_code_url)
+ url.svg(image_path, scale=3)
+ log.debug("Created QRCode image: " + image_name)
+
+ attempt = 0
+ while not os.path.exists(image_path) and attempt < 3:
+ time.sleep(1)
+ attempt += 1
+
+ if os.path.isfile(image_path):
+ log.info("QRcode image exists at: " + image_path)
+ else:
+ log.error("%s isn't a file!" % image_path)
+
+ return image_path
+
+ @classmethod
+ def init_temp_dir(cls):
+ global temp_dir
+ global qr_code_dir_name
+ global qr_code_dir
+ try:
+ temp_dir = str(os.environ[CATALINA_HOME])
+ temp_dir = os.path.join(temp_dir, "temp")
+ except KeyError as error:
+ log.error("No environment variable for: %s" % CATALINA_HOME)
+
+ if temp_dir is None:
+ temp_dir = tempfile.gettempdir() # using system tmp dir
+
+ log.debug("Temp dir is: %s" % temp_dir)
+
+ qr_code_dir = os.path.join(temp_dir, qr_code_dir_name)
+
+ if not os.path.exists(qr_code_dir):
+ os.makedirs(qr_code_dir)
+
+ def get_temp_directory(self):
+ return temp_dir
+
+ def get_qr_code_dir(self):
+ return qr_code_dir
+
+
+# D4S_QrCode.init_temp_dir()
+#qr_code = D4S_QrCode("http://data.d4science.org/ctlg/BiodiversityLab/distribution_of_the_giant_squid_architeuthis")
+#print qr_code.get_qrcode_path()
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/activity_streams/activity_stream_email_notifications.text b/ckanext-d4science_theme/ckanext/d4science_theme/templates/activity_streams/activity_stream_email_notifications.text
new file mode 100644
index 0000000..505710b
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/activity_streams/activity_stream_email_notifications.text
@@ -0,0 +1,7 @@
+{% set num = activities|length %}{{ ngettext("You have {num} new activity on your {site_title} dashboard", "You have {num} new activities on your {site_title} dashboard", num).format(site_title=g.site_title if g else site_title, num=num) }} {{ _('To view your dashboard, click on this link:') }}
+
+{% url_for 'dashboard.index', _external=True %}
+
+{{ _('You can turn off these email notifications in your {site_title} preferences. To change your preferences, click on this link:').format(site_title=g.site_title if g else site_title) }}
+
+{% url_for 'user.edit', _external=True %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/admin/base.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/admin/base.html
new file mode 100644
index 0000000..806d8c9
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/admin/base.html
@@ -0,0 +1,12 @@
+{% extends "page.html" %}
+
+{% block subtitle %}{{ _('Administration') }}{% endblock %}
+
+{% block breadcrumb_content %}{% endblock %}
+
+{% block content_primary_nav %}
+ {{ h.build_nav_icon('admin.index', _('Sysadmins'), icon='gavel') }}
+ {{ h.build_nav_icon('admin.config', _('Config'), icon='gear') }}
+ {{ h.build_nav_icon('admin.trash', _('Trash'), icon='trash') }}
+ {{ h.build_extra_admin_nav() }}
+{% endblock %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/admin/config.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/admin/config.html
new file mode 100644
index 0000000..0c29625
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/admin/config.html
@@ -0,0 +1,72 @@
+{% import 'macros/autoform.html' as autoform %}
+
+{% extends "admin/base.html" %}
+
+{% import 'macros/form.html' as form %}
+
+{% block primary_content_inner %}
+
+ {{ form.errors(error_summary) }}
+
+
+{% endblock %}
+
+{% block secondary_content %}
+
+
+
+ {{ _('CKAN config options') }}
+
+
+ {% block admin_form_help %}
+ {% set about_url = h.url_for('home.about') %}
+ {% set home_url = h.url_for('home.index') %}
+ {% set docs_url = "http://docs.ckan.org/en/{0}/theming".format(g.ckan_doc_version) %}
+ {% trans %}
+
Site Title: This is the title of this CKAN instance
+ It appears in various places throughout CKAN.
+
Custom Stylesheet: Define an alternative main CSS file.
+
Site Tag Logo: This is the logo that appears in the
+ header of all the CKAN instance templates.
+
About: This text will appear on this CKAN instances
+ about page.
+
Intro Text: This text will appear on this CKAN instances
+ home page as a welcome to visitors.
+
Custom CSS: This is a block of CSS that appears in
+ <head> tag of every page. If you wish to customize
+ the templates more fully we recommend
+ reading the documentation.
+
Homepage: This is for choosing a predefined layout for
+ the modules that appear on your homepage.
+ {% trans %}
+ Purge deleted datasets, organizations or groups forever and irreversibly.
+ {% endtrans %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/ajax_snippets/custom_fields.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/ajax_snippets/custom_fields.html
new file mode 100644
index 0000000..90f7344
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/ajax_snippets/custom_fields.html
@@ -0,0 +1,4 @@
+{# Snippet for unit testing custom-fields.js #}
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/ajax_snippets/follow_button.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/ajax_snippets/follow_button.html
new file mode 100644
index 0000000..9dc09c9
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/ajax_snippets/follow_button.html
@@ -0,0 +1 @@
+{{ h.follow_button(type, id) }}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/base.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/base.html
new file mode 100644
index 0000000..192d8e3
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/base.html
@@ -0,0 +1,130 @@
+{# Allows the DOCTYPE to be set on a page by page basis #}
+{%- block doctype %}{% endblock -%}
+
+{# Allows custom attributes to be added to the tag #}
+{%- block htmltag -%}
+{% set lang = h.lang() %}
+
+
+{%- endblock -%}
+
+ {# Allows custom attributes to be added to the tag #}
+
+ {#
+ Add custom meta tags to the page. Call super() to get the default tags
+ such as charset, viewport and generator.
+
+ Example:
+
+ {% block meta %}
+ {{ super() }}
+
+ {% endblock %}
+
+ #}
+ {%- block meta -%}
+
+
+
+
+ {% block meta_generator %}{% endblock %}
+ {% block meta_viewport %}{% endblock %}
+ {%- endblock -%}
+
+ {#
+ Add a custom title to the page by extending the title block. Call super()
+ to get the default page title.
+
+ Example:
+
+ {% block title %}My Subtitle - {{ super() }}{% endblock %}
+
+ #}
+
+ {%- block title -%}
+ {%- block subtitle %}{% endblock -%}
+ {%- if self.subtitle()|trim %} {{ g.template_title_delimiter }} {% endif -%}
+ {{ g.site_title }}
+ {%- endblock -%}
+
+
+ {#
+ The links block allows you to add additonal content before the stylesheets
+ such as rss feeds and favicons in the same way as the meta block.
+ #}
+ {% block links -%}
+
+ {% endblock -%}
+
+ {#
+ The styles block allows you to add additonal stylesheets to the page in
+ the same way as the meta block. Use super() to include the default
+ stylesheets before or after your own.
+
+ Example:
+
+ {% block styles %}
+ {{ super() }}
+
+ {% endblock %}
+ #}
+ {%- block styles %}
+ {# TODO: store just name of asset instead of path to it. #}
+ {% set theme = h.get_rtl_theme() if h.is_rtl_language() else g.theme %}
+ {% asset theme %}
+ {% asset 'd4science_theme/d4science-js' %}
+ {% asset 'd4science_theme/d4science-css' %}
+ {% endblock %}
+
+ {% block head_extras %}
+ {# defined in the config.ini under "ckan.template_head_end" #}
+ {{ g.template_head_end | safe }}
+ {% endblock %}
+
+ {# render all assets included in styles block #}
+ {{ h.render_assets('style') }}
+ {%- block custom_styles %}
+ {%- if g.site_custom_css -%}
+
+ {%- endif %}
+ {% endblock %}
+
+
+ {# Allows custom attributes to be added to the tag #}
+
+
+ {#
+ The page block allows you to add content to the page. Most of the time it is
+ recommended that you extend one of the page.html templates in order to get
+ the site header and footer. If you need a clean page then this is the
+ block to use.
+
+ Example:
+
+ {% block page %}
+
Some other page content
+ {% endblock %}
+ #}
+ {%- block page %}{% endblock -%}
+
+ {#
+ DO NOT USE THIS BLOCK FOR ADDING SCRIPTS
+ Scripts should be loaded by the {% assets %} tag except in very special
+ circumstances
+ #}
+ {%- block scripts %}
+ {% endblock -%}
+
+ {% block body_extras -%}
+ {# defined in the config.ini under "ckan.template_footer_end" #}
+ {{ g.template_footer_end | safe }}
+ {%- endblock %}
+
+ {# render all assets included in scripts block and everywhere else #}
+ {# make sure there are no calls to `asset` tag after this point #}
+ {{ h.render_assets('style') }}
+ {{ h.render_assets('script') }}
+
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/dataviewer/base.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/dataviewer/base.html
new file mode 100644
index 0000000..9f3d714
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/dataviewer/base.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block subtitle %}{{ h.dataset_display_name(package) }} {{ g.template_title_delimiter }} {{h.resource_display_name(resource) }}{% endblock %}
+
+{# remove any scripts #}
+{% block scripts %}
+
+{% endblock %}
+
+{# remove any ckan styles #}
+{% block styles %}{% endblock %}
+
+{% block custom_styles %}{% endblock %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/development/primer.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/development/primer.html
new file mode 100644
index 0000000..fe4ddc8
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/development/primer.html
@@ -0,0 +1,100 @@
+{% extends "page.html" %}
+
+{% block toolbar %}
+{% snippet 'development/snippets/breadcrumb.html', stage=1 %}
+{% snippet 'development/snippets/breadcrumb.html', stage=2 %}
+{% snippet 'development/snippets/breadcrumb.html', stage=3 %}
+{% endblock %}
+
+{% block actions_content %}
+{% snippet 'development/snippets/actions.html' %}
+{% endblock %}
+
+{% block secondary_content %}
+{% snippet 'development/snippets/context.html' %}
+
+
+ {{ h.humanize_entity_type('group', group_type, 'no any objects') or _('There are currently no groups for this site') }}.
+ {% if h.check_access('group_create') %}
+ {% link_for _('How about creating one?'), named_route=group_type+'.new' %}.
+ {% endif %}
+
+ {% endif %}
+ {% endblock %}
+ {% block page_pagination %}
+ {{ page.pager(q=q or '', sort=sort_by_selected or '') }}
+ {% endblock %}
+{% endblock %}
+
+{% block secondary_content %}
+ {% snippet "group/snippets/helper.html", group_type=group_type %}
+{% endblock %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/member_new.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/member_new.html
new file mode 100644
index 0000000..3234f71
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/member_new.html
@@ -0,0 +1,101 @@
+{% extends "group/edit_base.html" %}
+
+{% import 'macros/form.html' as form %}
+
+{% set user = user_dict %}
+
+{% block primary_content_inner %}
+ {% link_for _('Back to all members'), named_route=group_type+'.members', id=group.name, class_='btn btn-default pull-right', icon='arrow-left' %}
+
+ {% block page_heading %}{{ _('Edit Member') if user else _('Add Member') }}{% endblock %}
+
+{% endblock %}
+{% if position is divisibleby 3 %}
+
+{% endif %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/group_list.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/group_list.html
new file mode 100644
index 0000000..d171296
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/group_list.html
@@ -0,0 +1,19 @@
+{#
+Display a grid of group items.
+
+groups - A list of groups.
+
+Example:
+
+ {% snippet "group/snippets/group_list.html" %}
+
+#}
+{% block group_list %}
+
+ {% block group_list_inner %}
+ {% for group in groups %}
+ {% snippet "group/snippets/group_item.html", group=group, position=loop.index %}
+ {% endfor %}
+ {% endblock %}
+
+{% endblock %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/helper.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/helper.html
new file mode 100644
index 0000000..7061646
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/helper.html
@@ -0,0 +1,27 @@
+{#
+ Displays a sidebard module with information about group.
+
+ group_type - The type of group.
+
+ Example:
+
+ {% snippet "group/snippets/helper.html", group_type=group_type %}
+
+ #}
+
+
+
+
+ {{ _('What are Groups?') }}
+
+
+
+ {% trans %}
+ You can use CKAN Groups to create and manage collections of datasets.
+ This could be to catalogue datasets for a particular project or team,
+ or on a particular theme, or as a very simple way to help people find
+ and search your own published datasets.
+ {% endtrans %}
+
+
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/info.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/info.html
new file mode 100644
index 0000000..5916124
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/group/snippets/info.html
@@ -0,0 +1,54 @@
+{% set dataset_type = h.default_package_type() %}
+
+{% block info %}
+
CKAN is the world’s leading open-source data portal platform.
+
+
CKAN is a complete out-of-the-box software solution that makes data
+accessible and usable – by providing tools to streamline publishing, sharing,
+finding and using data (including storage of data and provision of robust data
+APIs). CKAN is aimed at data publishers (national and regional governments,
+companies and organizations) wanting to make their data open and available.
+
+
CKAN is used by governments and user groups worldwide and powers a variety
+of official and community data portals including portals for local, national
+and international government, such as the UK’s data.gov.uk and the
+United States catalog.data.gov, the Brazilian dados.gov.br, Dutch and
+Netherland government portals, as well as city and municipal sites in the US,
+UK, Argentina, Finland and elsewhere.
+{% endtrans %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/featured_group.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/featured_group.html
new file mode 100644
index 0000000..f411c1e
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/featured_group.html
@@ -0,0 +1,7 @@
+{% set groups = h.get_featured_groups() %}
+
+{% for group in groups %}
+
All the products are accompanied with rich descriptions capturing general attributes, e.g. title and creator(s), as well as usage policies and licences.
+
+ {% endif %}
+
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search.html
new file mode 100644
index 0000000..bb27776
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search.html
@@ -0,0 +1,22 @@
+{% set tags = h.get_facet_items_dict('tags', search_facets, limit=3) %}
+{% set placeholder = _('E.g. environment') %}
+{% set dataset_type = h.default_package_type() %}
+
+
+{% endif %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search_for_location.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search_for_location.html
new file mode 100644
index 0000000..4b88bd2
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search_for_location.html
@@ -0,0 +1,44 @@
+{% set alternative_url_dataset = h.url_for(controller='dataset', action='search') %}
+{% set locations = h.d4science_get_location_to_bboxes() %}
+
+
+
Browse by Location Extent
+
+
+ {% set offset = 5 %}
+ {% for key, value in locations.items() %}
+ {% set index = loop.index0 %}
+ {% if index % offset == 0 %}
+
+ {% endif %}
+
+ {% set loc = key %}
+ {% set bbox = value %}
+
+ {{ loc }}
+
+ {% if (index+offset+1) % offset == 0 %}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+ {# ADDED BY FRANCESCO MANGIACRAPA SEE #12651 #}
+ {% snippet 'spatial/snippets/spatial_query.html', alternative_url=alternative_url_dataset %}
+ {# END #}
+
+
+
+
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search_for_organisations.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search_for_organisations.html
new file mode 100644
index 0000000..65b8acb
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/home/snippets/search_for_organisations.html
@@ -0,0 +1,33 @@
+{% set organizations = h.d4science_get_browse_info_for_organisations_or_groups(type='organization', limit=10) %}
+{% set type = 'organization' %}
+{% if organizations|length > 1 %}
+
+
+
Browse by {{_('Organizations')}}
+
+
+ {% for index in range(0,organizations|length) %}
+ {% set offset = 5 %}
+ {% set organization = organizations[index] %}
+ {% if index % offset == 0 %}
+
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/autoform.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/autoform.html
new file mode 100644
index 0000000..c469b81
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/autoform.html
@@ -0,0 +1,70 @@
+{#
+Builds a form from the supplied form_info list/tuple. All form info dicts
+can also contain an "extra_info" key which will add some help text after the
+input element.
+
+form_info - A list of dicts describing the form field to build.
+data - The form data object.
+errors - The form errors object.
+error_summary - A list of errors to display above the fields.
+
+Example
+
+ {% set form_info = [
+ {'name': 'ckan.site_title', 'control': 'input', 'label': _('Site Title'), 'placeholder': ''},
+ {'name': 'ckan.theme', 'control': 'select', 'options': styles, 'label': _('Style'), 'placeholder': ''},
+ {'name': 'ckan.site_description', 'control': 'input', 'label': _('Site Tag Line'), 'placeholder': ''},
+ {'name': 'ckan.site_logo', 'control': 'input', 'label': _('Site Tag Logo'), 'placeholder': ''},
+ {'name': 'ckan.site_about', 'control': 'markdown', 'label': _('About'), 'placeholder': _('About page text')},
+ {'name': 'ckan.site_intro_text', 'control': 'markdown', 'label': _('Intro Text'), 'placeholder': _('Text on home page')},
+ {'name': 'ckan.site_custom_css', 'control': 'textarea', 'label': _('Custom CSS'), 'placeholder': _('Customisable css inserted into the page header')},
+ ] %}
+
+ {% import 'macros/autoform.html' as autoform %}
+ {{ autoform.generate(form_info, data, errors) }}
+
+#}
+{% import 'macros/form.html' as form %}
+{%- macro generate(form_info=[], data={}, errors={}, error_summary=[]) -%}
+ {{ form.errors(error_summary) if error_summary }}
+
+ {% for item in form_info %}
+ {% set name = item.name %}
+ {% set value = data.get(name) %}
+ {% set error = errors.get(name) %}
+ {% set id = 'field-%s' % (name|lower|replace('_', '-')|replace('.', '-')) %}
+
+ {% set control = item.control or 'input' %}
+ {% set label = item.label %}
+ {% set placeholder = item.placeholder %}
+
+ {% set classes = item.classes or [] %}
+ {% set classes = ['control-medium'] if not classes and control == 'input' %}
+
+ {% if control == 'select' %}
+ {% call form.select(name, id=id, label=label, options=item.options, selected=value, error=error) %}
+ {% if item.extra_info %}{{ form.info(item.extra_info) }}{% endif %}
+ {% endcall %}
+ {% elif control == 'html' %}
+
+
+ {{ item.html }}
+
+
+ {% elif control == 'image_upload' %}
+ {% set field_url = item.field_url or 'image_url' %}
+ {% set is_upload = data[field_url] and not data[field_url].startswith('http') %}
+ {% set is_url = data[field_url] and data[field_url].startswith('http') %}
+
+ {% set field_upload = item.field_upload or 'image_upload' %}
+ {% set field_clear = item.field_clear or 'clear_upload' %}
+
+ {{ form.image_upload(data, errors, is_upload_enabled=item.upload_enabled, is_url=is_url, is_upload=is_upload, upload_label = _('Site logo'), url_label=_('Site logo'),
+ field_url=field_url, field_upload=field_upload, field_clear=field_clear)}}
+ {% else %}
+ {% call form[control](name, id=id, label=label, placeholder=placeholder, value=value, error=error, classes=classes) %}
+ {% if item.extra_info %}{{ form.info(item.extra_info) }}{% endif %}
+ {% endcall %}
+ {% endif %}
+ {% endfor %}
+{%- endmacro -%}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form.html
new file mode 100644
index 0000000..f33b7ab
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form.html
@@ -0,0 +1,36 @@
+{#
+All macros were split into their own template file in `templates/macros/form/`)
+and here, we are importing them all to maintain backward compatibility.
+#}
+
+{% from 'macros/form/input.html' import input %}
+{% from "macros/form/input_block.html" import input_block %}
+{% from 'macros/form/checkbox.html' import checkbox %}
+{% from 'macros/form/select.html' import select %}
+{% from "macros/form/attributes.html" import attributes %}
+{% from "macros/form/markdown.html" import markdown %}
+{% from "macros/form/textarea.html" import textarea %}s
+{% from "macros/form/prepend.html" import prepend %}
+{% from "macros/form/custom.html" import custom %}
+{% from "macros/form/errors.html" import errors %}
+{% from "macros/form/info.html" import info %}
+{% from "macros/form/hidden.html" import hidden %}
+{% from "macros/form/hidden_from_list.html" import hidden_from_list %}
+{% from "macros/form/required_message.html" import required_message %}
+{% from "macros/form/image_upload.html" import image_upload %}
+
+{% set input = input %}
+{% set input_block = input_block %}
+{% set checkbox = checkbox %}
+{% set select = select %}
+{% set attributes = attributes %}
+{% set markdown = markdown %}
+{% set textarea = textarea %}
+{% set prepend = prepend %}
+{% set custom = custom %}
+{% set errors = errors %}
+{% set info = info %}
+{% set hidden = hidden %}
+{% set hidden_from_list = hidden_from_list %}
+{% set required_message = required_message %}
+{% set image_upload = image_upload %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/attributes.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/attributes.html
new file mode 100644
index 0000000..47359a6
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/attributes.html
@@ -0,0 +1,17 @@
+{#
+Builds a space seperated list of html attributes from a dict of key/value pairs.
+Generally only used internally by macros.
+
+attrs - A dict of attribute/value pairs
+
+Example
+
+{% import 'macros/form.html' as form %}
+{{ form.attributes({}) }}
+
+#}
+{%- macro attributes(attrs={}) -%}
+{%- for key, value in attrs.items() -%}
+{{ " " }}{{ key }}{% if value != "" %}="{{ value }}"{% endif %}
+{%- endfor -%}
+{%- endmacro -%}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/checkbox.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/checkbox.html
new file mode 100644
index 0000000..130720f
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/checkbox.html
@@ -0,0 +1,34 @@
+{% from "macros/form/attributes.html" import attributes %}
+
+{#
+Builds a single checkbox input.
+
+name - The name of the form parameter.
+id - The id to use on the input and label. Convention is to prefix with 'field-'.
+label - The human readable label.
+value - The value of the input.
+checked - If true the checkbox will be checked
+error - An error string for the field or just true to highlight the field.
+classes - An array of classes to apply to the form-group.
+is_required - Boolean of whether this input is requred for the form to validate
+
+Example:
+
+{% import 'macros/form.html' as form %}
+{{ form.checkbox('remember', checked=true) }}
+
+#}
+{% macro checkbox(name, id='', label='', value='', checked=false, placeholder='', error="", classes=[], attrs={}, is_required=false) %}
+{%- set extra_html = caller() if caller -%}
+
+
+
+ {{ extra_html }}
+
+
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/custom.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/custom.html
new file mode 100644
index 0000000..4503b05
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/custom.html
@@ -0,0 +1,61 @@
+{% from "macros/form/input_block.html" import input_block %}
+{% from "macros/form/attributes.html" import attributes %}
+
+{#
+Creates all the markup required for an custom key/value input. These are usually
+used to let the user provide custom meta data. Each "field" has three inputs
+one for the key, one for the value and a checkbox to remove it. So the arguments
+for this macro are nearly all tuples containing values for the
+(key, value, delete) fields respectively.
+
+name - A tuple of names for the three fields.
+id - An id string to be used for each input.
+label - The human readable label for the main label.
+values - A tuple of values for the (key, value, delete) fields. If delete
+is truthy the checkbox will be checked.
+placeholder - A tuple of placeholder text for the (key, value) fields.
+error - A list of error strings for the field or just true to highlight the field.
+classes - An array of classes to apply to the form-group.
+is_required - Boolean of whether this input is requred for the form to validate
+
+Examples:
+
+{% import 'macros/form.html' as form %}
+{{ form.custom(
+names=('custom_key', 'custom_value', 'custom_deleted'),
+id='field-custom',
+label=_('Custom Field'),
+values=(extra.key, extra.value, extra.deleted),
+error=''
+) }}
+#}
+{% macro custom(names=(), id="", label="", values=(), placeholders=(), error="", classes=[], attrs={}, is_required=false, key_values=()) %}
+{%- set classes = (classes|list) -%}
+{%- set label_id = (id or names[0]) ~ "-key" -%}
+{%- set extra_html = caller() if caller -%}
+{%- do classes.append('control-custom') -%}
+
+{% call input_block(label_id, label or name, error, classes, control_classes=["editor"], extra_html=extra_html, is_required=is_required) %}
+
+
+
+
+
+
+
+
+ {% if values[0] or values[1] or error %}
+
+ {% endif %}
+
+
+
+
+
+
+
+{% endcall %}
+{% endmacro %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/errors.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/errors.html
new file mode 100644
index 0000000..9f55bad
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/errors.html
@@ -0,0 +1,26 @@
+{#
+Builds a list of errors for the current form.
+
+errors - A dict of field/message pairs.
+type - The alert-* class that should be applied (default: "error")
+classes - A list of classes to apply to the wrapper (default: [])
+
+Example:
+
+{% import 'macros/form.html' as form %}
+{{ form.errors(error_summary, type="warning") }}
+
+#}
+
+{% macro errors(errors={}, type="error", classes=[]) %}
+{% if errors %}
+
+
{{ _('The form contains invalid entries:') }}
+
+ {% for key, error in errors.items() %}
+
{% if key %}{{ key }}: {% endif %}{{ error }}
+ {% endfor %}
+
+
+{% endif %}
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/hidden.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/hidden.html
new file mode 100644
index 0000000..ad46447
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/hidden.html
@@ -0,0 +1,14 @@
+{#
+Builds a single hidden input.
+
+name - name of the hidden input
+value - value of the hidden input
+
+Example
+{% import 'macros/form.html' as form %}
+{{ form.hidden('name', 'value') }}
+
+#}
+{% macro hidden(name, value) %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/hidden_from_list.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/hidden_from_list.html
new file mode 100644
index 0000000..60e0d16
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/hidden_from_list.html
@@ -0,0 +1,28 @@
+{% from "macros/form/hidden.html" import hidden %}
+
+{#
+Contructs hidden inputs for each name-value pair.
+
+fields - [('name1', 'value1'), ('name2', 'value2'), ...]
+
+Two parameter for excluding several names or name-value pairs.
+
+except_names - list of names to be excluded
+except - list of name-value pairs to be excluded
+
+
+Example:
+{% import 'macros/form.html' as form %}
+{% form.hidden_from_list(fields=c.fields, except=[('topic', 'xyz')]) %}
+{% form.hidden_from_list(fields=c.fields, except_names=['time_min', 'time_max']) %}
+#}
+{% macro hidden_from_list(fields, except_names=None, except=None) %}
+{% set except_names = except_names or [] %}
+{% set except = except or [] %}
+
+{% for name, value in fields %}
+{% if name and value and name not in except_names and (name, value) not in except %}
+{{ hidden(name, value) }}
+{% endif %}
+{% endfor %}
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/image_upload.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/image_upload.html
new file mode 100644
index 0000000..1f35a0b
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/image_upload.html
@@ -0,0 +1,59 @@
+{% from 'macros/form/input.html' import input %}
+{% from 'macros/form/checkbox.html' import checkbox %}
+
+{#
+Builds a file upload for input
+
+Example
+{% import 'macros/form.html' as form %}
+{{ form.image_upload(data, errors, is_upload_enabled=true) }}
+
+#}
+{% macro image_upload(data, errors, field_url='image_url', field_upload='image_upload', field_clear='clear_upload',
+ is_url=false, is_upload=false, is_upload_enabled=false, placeholder=false,
+ url_label='', upload_label='', field_name='image_url') %}
+{% set placeholder = placeholder if placeholder else _('http://example.com/my-image.jpg') %}
+{% set url_label = url_label or _('Image URL') %}
+{% set upload_label = upload_label or _('Image') %}
+{% set previous_upload = data['previous_upload'] %}
+
+{% if field_url == 'url' and field_upload == 'upload' %}
+ {# backwards compatibility for old resource forms that still call the `forms.image_upload()` macro, eg ckanext-scheming #}
+ {% snippet 'package/snippets/resource_upload_field.html',
+ data=data,
+ errors=errors,
+ is_url=is_url,
+ is_upload=is_upload,
+ is_upload_enabled=is_upload_enabled,
+ url_label=url_label,
+ upload_label=upload_label,
+ placeholder=placeholder %}
+{% else %}
+ {% if is_upload_enabled %}
+
{% endif %}
+{% endif %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/info.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/info.html
new file mode 100644
index 0000000..54ecc84
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/info.html
@@ -0,0 +1,24 @@
+{#
+Renders an info box with a description. This will usually be used with in a
+call block when creating an input element.
+
+text - The text to include in the box.
+inline - If true displays the info box inline with the input.
+classes - A list of classes to add to the info box.
+
+Example
+
+{% import 'macros/form.html' as form %}
+{% call form.input('name') %}
+{{ form.info(_('My useful help text')) }}
+{% endcall %}
+
+#}
+{% macro info(text='', inline=false, classes=[]) %}
+{%- if text -%}
+
+
+{{ text }}
+
+{%- endif -%}
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/input.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/input.html
new file mode 100644
index 0000000..d7f7c29
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/input.html
@@ -0,0 +1,30 @@
+{% from "macros/form/input_block.html" import input_block %}
+{% from "macros/form/attributes.html" import attributes %}
+
+{#
+Creates all the markup required for an input element. Handles matching labels to
+inputs, error messages and other useful elements.
+
+name - The name of the form parameter.
+id - The id to use on the input and label. Convention is to prefix with 'field-'.
+label - The human readable label.
+value - The value of the input.
+placeholder - Some placeholder text.
+type - The type of input eg. email, url, date (default: text).
+error - A list of error strings for the field or just true to highlight the field.
+classes - An array of classes to apply to the form-group.
+is_required - Boolean of whether this input is requred for the form to validate
+
+Examples:
+
+{% import 'macros/form.html' as form %}
+{{ form.input('title', label=_('Title'), value=data.title, error=errors.title) }}
+
+#}
+{% macro input(name, id='', label='', value='', placeholder='', type='text', error="", classes=[], attrs={'class': 'form-control'}, is_required=false) %}
+{%- set extra_html = caller() if caller -%}
+
+{% call input_block(id or name, label or name, error, classes, extra_html=extra_html, is_required=is_required) %}
+
+{% endcall %}
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/input_block.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/input_block.html
new file mode 100644
index 0000000..6f7e691
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/input_block.html
@@ -0,0 +1,32 @@
+{#
+A generic input_block for providing the default markup for CKAN form elements.
+It is expected to be called using a {% call %} block, the contents of which
+will be inserted into the .controls element.
+
+for - The id for the input that the label should match.
+label - A human readable label.
+error - A list of error strings for the field or just true.
+classes - An array of custom classes for the outer element.
+control_classes - An array of custom classes for the .control wrapper.
+extra_html - An html string to be inserted after the errors eg. info text.
+is_required - Boolean of whether this input is requred for the form to validate
+
+Example:
+
+{% import 'macros/form.html' as form %}
+{% call form.input_block("field", "My Field") %}
+
+{% endcall %}
+
+#}
+
+{% macro input_block(for, label="", error="", classes=[], control_classes=[], extra_html="", is_required=false) %}
+
+
+
+{{ caller() }}
+{% if error and error is iterable %}{{ error|join(', ') }}{% endif %}
+{{ extra_html }}
+
+
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/markdown.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/markdown.html
new file mode 100644
index 0000000..59b4a1d
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/markdown.html
@@ -0,0 +1,33 @@
+{% from "macros/form/input_block.html" import input_block %}
+{% from "macros/form/attributes.html" import attributes %}
+
+{#
+Creates all the markup required for a Markdown textarea element. Handles
+matching labels to inputs, selected item and error messages.
+
+name - The name of the form parameter.
+id - The id to use on the input and label. Convention is to prefix with 'field-'.
+label - The human readable label.
+value - The value of the input.
+placeholder - Some placeholder text.
+error - A list of error strings for the field or just true to highlight the field.
+classes - An array of classes to apply to the form-group.
+is_required - Boolean of whether this input is requred for the form to validate
+
+Examples:
+
+{% import 'macros/form.html' as form %}
+{{ form.markdown('desc', id='field-description', label=_('Description'), value=data.desc, error=errors.desc) }}
+
+#}
+{% macro markdown(name, id='', label='', value='', placeholder='', error="", classes=[], attrs={'class': 'form-control'}, is_required=false) %}
+{% set classes = (classes|list) %}
+{% do classes.append('control-full') %}
+{% set markdown_tooltip = "
Please note: HTML tags are stripped out for security reasons
" %}
+
+{%- set extra_html = caller() if caller -%}
+{% call input_block(id or name, label or name, error, classes, control_classes=["editor"], extra_html=extra_html, is_required=is_required) %}
+
+{% trans %}You can use Markdown formatting here{% endtrans %}
+{% endcall %}
+{% endmacro %}
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/prepend.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/prepend.html
new file mode 100644
index 0000000..1441343
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/prepend.html
@@ -0,0 +1,38 @@
+{% from "macros/form/input_block.html" import input_block %}
+{% from "macros/form/attributes.html" import attributes %}
+
+{#
+Creates all the markup required for an input element with a prefixed segment.
+These are useful for showing url slugs and other fields where the input
+information forms only part of the saved data.
+
+name - The name of the form parameter.
+id - The id to use on the input and label. Convention is to prefix with 'field-'.
+label - The human readable label.
+prepend - The text that will be prepended before the input.
+value - The value of the input.
+which will use the name key as the value.
+placeholder - Some placeholder text.
+error - A list of error strings for the field or just true to highlight the field.
+classes - An array of classes to apply to the form-group.
+is_required - Boolean of whether this input is requred for the form to validate
+
+Examples:
+
+{% import 'macros/form.html' as form %}
+{{ form.prepend('slug', id='field-slug', prepend='/dataset/', label=_('Slug'), value=data.slug, error=errors.slug) }}
+
+#}
+{% macro prepend(name, id='', label='', prepend='', value='', placeholder='', type='text', error="", classes=[], attrs={'class': 'form-control'}, is_required=false) %}
+{# We manually append the error here as it needs to be inside the .input-group block #}
+{% set classes = (classes|list) %}
+{% do classes.append('error') if error %}
+{%- set extra_html = caller() if caller -%}
+{% call input_block(id or name, label or name, error='', classes=classes, extra_html=extra_html, is_required=is_required) %}
+
+ {% if prepend %}{%- endif -%}
+
+ {% if error and error is iterable %}{{ error|join(', ') }}{% endif %}
+
+{% endcall %}
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/required_message.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/required_message.html
new file mode 100644
index 0000000..f5a4cc7
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/required_message.html
@@ -0,0 +1,13 @@
+{#
+Outputs the "* Required field" message for the bottom of formss
+
+Example
+{% import 'macros/form.html' as form %}
+{{ form.required_message() }}
+
+#}
+{% macro required_message() %}
+
+ * {{ _("Required field") }}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/select.html b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/select.html
new file mode 100644
index 0000000..15a1025
--- /dev/null
+++ b/ckanext-d4science_theme/ckanext/d4science_theme/templates/macros/form/select.html
@@ -0,0 +1,50 @@
+{% from "macros/form/input_block.html" import input_block %}
+{% from "macros/form/attributes.html" import attributes %}
+
+{#
+Creates all the markup required for an select element. Handles matching labels to
+inputs and error messages.
+
+A field should be a dict with a "value" key and an optional "text" key which
+will be displayed to the user. We use a dict to easily allow extension in
+future should extra options be required.
+
+name - The name of the form parameter.
+id - The id to use on the input and label. Convention is to prefix with 'field-'.
+label - The human readable label.
+options - A list/tuple of fields to be used as .
+ selected - The value of the selected