Browse Source

merged field_group submodule. TODO test new version of field_group, IDs seem to be removed ...

Bachir Soussi Chiadmi 9 years ago
parent
commit
f0531608b7
24 changed files with 5982 additions and 0 deletions
  1. 40 0
      sites/all/modules/contrib/fields/field_group/CHANGELOG.txt
  2. 339 0
      sites/all/modules/contrib/fields/field_group/LICENSE.txt
  3. 47 0
      sites/all/modules/contrib/fields/field_group/README.txt
  4. 7 0
      sites/all/modules/contrib/fields/field_group/field_group-rtl.css
  5. 460 0
      sites/all/modules/contrib/fields/field_group/field_group.api.php
  6. 23 0
      sites/all/modules/contrib/fields/field_group/field_group.css
  7. 70 0
      sites/all/modules/contrib/fields/field_group/field_group.features.inc
  8. 14 0
      sites/all/modules/contrib/fields/field_group/field_group.field_ui.css
  9. 854 0
      sites/all/modules/contrib/fields/field_group/field_group.field_ui.inc
  10. 137 0
      sites/all/modules/contrib/fields/field_group/field_group.field_ui.js
  11. 19 0
      sites/all/modules/contrib/fields/field_group/field_group.info
  12. 327 0
      sites/all/modules/contrib/fields/field_group/field_group.install
  13. 224 0
      sites/all/modules/contrib/fields/field_group/field_group.js
  14. 2139 0
      sites/all/modules/contrib/fields/field_group/field_group.module
  15. 15 0
      sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs-rtl.css
  16. 101 0
      sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.css
  17. 203 0
      sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.js
  18. 13 0
      sites/all/modules/contrib/fields/field_group/multipage/multipage-rtl.css
  19. 128 0
      sites/all/modules/contrib/fields/field_group/multipage/multipage.css
  20. 268 0
      sites/all/modules/contrib/fields/field_group/multipage/multipage.js
  21. 416 0
      sites/all/modules/contrib/fields/field_group/tests/field_group.display.test
  22. 109 0
      sites/all/modules/contrib/fields/field_group/tests/field_group.ui.test
  23. 12 0
      sites/all/modules/contrib/fields/field_group/tests/field_group_test.info
  24. 17 0
      sites/all/modules/contrib/fields/field_group/tests/field_group_test.module

+ 40 - 0
sites/all/modules/contrib/fields/field_group/CHANGELOG.txt

@@ -0,0 +1,40 @@
+/* $Id*/
+CHANGELOG for field_group for Drupal 7
+
+Field_group 7.x-1.x-dev
+    o Issue #1095316: Field Groups disappear when Content Type is renamed.
+    o Issue #1095316 by swentel: Support for Entity API.
+    o Issue #1095002 by animelion: Upgrading removes all existing field groups.
+    o Issue #1095130 by willvincent: Features export not working with rc2.
+    
+Field_group 7.x-1.0-rc2
+    o Ran through coder, minor.
+    o Issue #1033036 by Stalski, swentel: Create a field_group.api.php.
+    o Made the summary descriptions more human readable.
+    o Issue #1086450: Cannot see red star on some field groups even required fields are set to 1.
+    o #1072292 by shadow_jh, stalski: Using on user settings page but need to hid on registration page.
+    o #1092360 by dww: Move field_group_update_7000 functionality to hook_install().
+    o #1061228 Rewrite the field_group_field_group_is_empty function.
+    o Added ID's to fieldgroups.
+    o Removed unused field_group.admin.inc + menu item. Required asterix moving to field_group setting.
+    o #1045526 by stalski: Make formatter options more user-friendly and logical.
+    o #1041880 by robertgarrigos: duplicated entries in field_group table.
+    o #1043834 by amsri: Field Group module just does not work with profiles 2.
+    
+Field_group 7.x-1.0-rc1
+    o #1006464 Change #groups to #fieldgroups because of name collapsing with form_process_fieldset
+    o #1024184 fix collapsible when mode is set to open
+    o #1020278 by mori: Update fails.
+    o #1020116 by mikegfx: Confusing verbage across group types.
+    o #1018012 by mikegfx: Adding required asterisk to group tabs that have required fields.
+    o #960916 fixed reference warnings.
+    o No label anymore with div>open.
+    o #969258 Added check for fields and extra_fields.
+    o #960916 Fixed notice on for reference on group in field_group_settings.
+    o #961106 Fixed notice on entity type and bundle check.
+    o #962072 by mori: Improve CSS for horizontal tabs & accordion.
+    o Changed Fieldgroup API: defaults and instance_settings are now merged.
+    o Changed save action so everything is gathered during form_state to 
+      postpone saving until the save button is hit.
+    o Changed some important variable name, so it makes more sense and easier to read.
+    o Add basic crud functions.

+ 339 - 0
sites/all/modules/contrib/fields/field_group/LICENSE.txt

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 47 - 0
sites/all/modules/contrib/fields/field_group/README.txt

@@ -0,0 +1,47 @@
+
+History:
+  Field_group was written for Drupal 7. For drupal 6, the module is
+  located in the CCK module (http://drupal.org/project/cck).
+  As drupal core has a fields API drupal > 6, the field_group module
+  is considered a contribution.
+
+Description:
+  field_group is a module that will group a set of fields. In Drupal7,
+  with fields, one means all fields that come from fieldable entities.
+  You can add fieldgroups in several types with their own format settings.
+  field_group has API functions to add your own formatter and rendering for
+  it.
+  One of the biggest improvements to previous versions, is that fieldgroups
+  have unlimited nesting, better display control.
+  Note that field_group will only group fields, it can not be used to hide
+  certain fields since this a permission matter.
+
+Module project page:
+  http://drupal.org/project/field_group
+
+Documentation page:
+  http://drupal.org/node/1017838
+  http://drupal.org/node/1017962
+
+Available group types:
+  - Fieldsets
+  - Horizontal tabs
+  - Vertical tabs
+  - Accordions
+  - Divs
+  - Multipage steps: <strong>Note: This is only client side.
+  - HTML5 group type
+  - Html element
+
+To submit bug reports and feature suggestions, or to track changes:
+  http://drupal.org/project/issues/field_group
+
+-- MAINTAINERS --
+
+stalski - http://drupal.org/user/322618
+swentel - http://drupal.org/user/107403
+zuuperman - http://drupal.org/user/361625
+
+-- INSPIRATORS --
+
+yched - http://drupal.org/user/39567

+ 7 - 0
sites/all/modules/contrib/fields/field_group/field_group-rtl.css

@@ -0,0 +1,7 @@
+/**
+ * Override the accordion default style for view_modes.
+ */
+form .ui-accordion h3, form .ui-accordion h3.ui-state-active {
+  padding-left: 0;
+  padding-right: 2em;
+}

+ 460 - 0
sites/all/modules/contrib/fields/field_group/field_group.api.php

@@ -0,0 +1,460 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Field group module.
+ *
+ * Fieldgroup is a module that will wrap fields and other fieldgroups. Nothing more, nothing less.
+ * For this there are formatters we can create on forms and view modes.
+ *
+ * Some of the elements defined in fieldgroup will be ported to the elements module.
+ *
+ * DEVELOPERS NOTES
+ *
+ * - Fieldgroup uses a ''#fieldgroups' property to know what fieldgroups are to be pre_rendered and
+ *   rendered by the field_group module. This means we need to be sure our groups are in #fieldgroups.
+ *   #fieldgroups is later merged with the normal #groups that can be used by any other module.
+ *   This is done to be sure fieldgroup is not taking fieldsets from profile2, commerce line items,
+ *   commerce user profiles, ... .
+ *   When trying to merge a programmatically created field wrapper (div, markup, fieldset, ...) into
+ *   groups, you might consider adding it in #field_groups as well if you want the element processed
+ *   by fieldgroup.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+
+/**
+ * Javascript hooks
+ *
+ * Drupal.FieldGroup.Effects.processHook.execute()
+ * See field_group.js for the examples for all implemented formatters.
+ */
+
+
+/**
+ * Implements hook_field_group_formatter_info().
+ *
+ * Define the information on formatters. The formatters are
+ * separated by view mode type. We have "form" for all form elements
+ * and "display" will be the real view modes (full, teaser, sticky, ...)
+ *
+ * structure:
+ * @code
+ * array(
+ *   'form' => array(
+ *     'fieldset' => array(
+ *       // required, String with the name of the formatter type.
+ *       'label' => t('Fieldset'),
+ *       // optional, String description of the formatter type.
+ *       'description' => t('This is field group that ...'),
+ *       // required, Array of available formatter options.
+ *       'format_types' => array('open', 'collapsible', 'collapsed'),
+ *       // required, String with default value of the style.
+        'default_formatter' => 'collapsible',
+ *       // optional, Array with key => default_value pairs.
+ *       'instance_settings' => array('key' => 'value'),
+ *     ),
+ *   ),
+ *   'display' => array(
+ *     'fieldset' => array(
+ *       // required, String with the name of the formatter type.
+ *       'label' => t('Fieldset'),
+ *       // optional, String description of the formatter type.
+ *       'description' => t('This is field group that ...'),
+ *       // required, Array of available formatter options.
+ *       'format_types' => array('open', 'collapsible', 'collapsed'),
+ *       // required, String with default value of the style.
+        'default_formatter' => 'collapsible',
+ *       // optional, Array with key => default_value pairs.
+ *       'instance_settings' => array('key' => 'value'),
+ *     ),
+ *   ),
+ * ),
+ * @endcode
+ *
+ * @return $formatters
+ *   A collection of available formatting html controls for form
+ *   and display overview type.
+ *
+ * @see field_group_field_group_formatter_info()
+ */
+function hook_field_group_formatter_info() {
+  return array(
+    'form' => array(
+      'fieldset' => array(
+        'label' => t('Fieldset'),
+        'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'),
+        'format_types' => array('open', 'collapsible', 'collapsed'),
+        'instance_settings' => array('classes' => ''),
+        'default_formatter' => 'collapsible',
+      ),
+    ),
+    'display' => array(
+      'div' => array(
+        'label' => t('Div'),
+        'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'),
+        'format_types' => array('open', 'collapsible', 'collapsed'),
+        'instance_settings' => array('effect' => 'none', 'speed' => 'fast', 'classes' => ''),
+        'default_formatter' => 'collapsible',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_group_format_settings().
+ *
+ * Defines configuration widget for the settings on a field group
+ * formatter. Eache formatter can have different elements and storage.
+ *
+ * @params Object $group The group object.
+ * @return Array $form The form element for the format settings.
+ */
+function hook_field_group_format_settings($group) {
+  // Add a wrapper for extra settings to use by others.
+  $form = array(
+    'instance_settings' => array(
+      '#tree' => TRUE,
+      '#weight' => 2,
+    ),
+  );
+
+  $field_group_types = field_group_formatter_info();
+  $mode = $group->mode == 'form' ? 'form' : 'display';
+  $formatter = $field_group_types[$mode][$group->format_type];
+
+  // Add the required formatter type selector.
+  if (isset($formatter['format_types'])) {
+    $form['formatter'] = array(
+      '#title' => t('Fieldgroup settings'),
+      '#type' => 'select',
+      '#options' => drupal_map_assoc($formatter['format_types']),
+      '#default_value' => isset($group->format_settings['formatter']) ? $group->format_settings['formatter'] : $formatter['default_formatter'],
+      '#weight' => 1,
+    );
+  }
+  if ($mode == 'form') {
+    $form['instance_settings']['required_fields'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Mark group for required fields.'),
+      '#default_value' => isset($group->format_settings['instance_settings']['required_fields']) ? $group->format_settings['instance_settings']['required_fields'] : (isset($formatter['instance_settings']['required_fields']) ? $formatter['instance_settings']['required_fields'] : ''),
+      '#weight' => 2,
+    );
+  }
+  $form['instance_settings']['classes'] = array(
+    '#title' => t('Extra CSS classes'),
+    '#type' => 'textfield',
+    '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''),
+    '#weight' => 3,
+    '#element_validate' => array('field_group_validate_css_class'),
+  );
+  $form['instance_settings']['description'] = array(
+    '#title' => t('Description'),
+    '#type' => 'textarea',
+    '#default_value' => isset($group->format_settings['instance_settings']['description']) ? $group->format_settings['instance_settings']['description'] : (isset($formatter['instance_settings']['description']) ? $formatter['instance_settings']['description'] : ''),
+    '#weight' => 0,
+  );
+
+  // Add optional instance_settings.
+  switch ($group->format_type) {
+    case 'div':
+      $form['instance_settings']['effect'] = array(
+        '#title' => t('Effect'),
+        '#type' => 'select',
+        '#options' => array('none' => t('None'), 'blind' => t('Blind')),
+        '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'],
+        '#weight' => 2,
+      );
+      $form['instance_settings']['speed'] = array(
+        '#title' => t('Speed'),
+        '#type' => 'select',
+        '#options' => array('none' => t('None'), 'slow' => t('Slow'), 'fast' => t('Fast')),
+        '#default_value' => isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : $formatter['instance_settings']['speed'],
+        '#weight' => 3,
+      );
+      break;
+    case 'fieldset':
+      $form['instance_settings']['classes'] = array(
+        '#title' => t('Extra CSS classes'),
+        '#type' => 'textfield',
+        '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : $formatter['instance_settings']['classes'],
+        '#weight' => 3,
+        '#element_validate' => array('field_group_validate_css_class'),
+      );
+      break;
+    case 'tabs':
+    case 'htabs':
+    case 'accordion':
+      unset($form['instance_settings']['description']);
+      if (isset($form['instance_settings']['required_fields'])) {
+        unset($form['instance_settings']['required_fields']);
+      }
+      break;
+    case 'tab':
+    case 'htab':
+    case 'accordion-item':
+    default:
+  }
+
+  return $form;
+}
+
+/**
+ * Implements hook_field_group_pre_render().
+ *
+ * This function gives you the oppertunity to create the given
+ * wrapper element that can contain the fields.
+ * In the example beneath, some variables are prepared and used when building the
+ * actual wrapper element. All elements in drupal fapi can be used.
+ *
+ * Note that at this point, the field group has no notion of the fields in it.
+ *
+ * There is also an alternative way of handling this. The default implementation
+ * within field_group calls "field_group_pre_render_<format_type>".
+ * @see field_group_pre_render_fieldset.
+ *
+ * @param Array $elements by address.
+ * @param Object $group The Field group info.
+ */
+function hook_field_group_pre_render(& $element, $group, & $form) {
+
+  // You can prepare some variables to use in the logic.
+  $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form';
+  $id = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $view_mode . '_' . $group->group_name;
+
+  // Each formatter type can have whole different set of element properties.
+  switch ($group->format_type) {
+
+    // Normal or collapsible div.
+    case 'div':
+      $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none';
+      $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none';
+      $add = array(
+        '#type' => 'markup',
+        '#weight' => $group->weight,
+        '#id' => $id,
+      );
+      $classes .= " speed-$speed effect-$effect";
+      if ($group->format_settings['formatter'] != 'open') {
+        $add['#prefix'] = '<div class="field-group-format ' . $classes . '">
+          <span class="field-group-format-toggler">' . check_plain(t($group->label)) . '</span>
+          <div class="field-group-format-wrapper" style="display: none;">';
+        $add['#suffix'] = '</div></div>';
+      }
+      else {
+        $add['#prefix'] = '<div class="field-group-format ' . $group->group_name . ' ' . $classes . '">';
+        $add['#suffix'] = '</div>';
+      }
+      if (!empty($description)) {
+        $add['#prefix'] .= '<div class="description">' . $description . '</div>';
+      }
+      $element += $add;
+
+      if ($effect == 'blind') {
+        drupal_add_library('system', 'effects.blind');
+      }
+
+      break;
+    break;
+  }
+}
+
+/**
+ * Implements hook_field_group_pre_render().
+ *
+ * Function that fungates as last resort to alter the pre_render build.
+ */
+function hook_field_group_pre_render_alter(&$element, $group, & $form) {
+
+  if ($group->format_type == 'htab') {
+    $element['#theme_wrappers'] = array('my_horizontal_tab');
+  }
+
+}
+
+/**
+ * Implements hook_field_group_build_pre_render_alter().
+ *
+ * Function that fungates as last resort where you can alter things. It is
+ * expected that when you need this function, you have most likely a very custom
+ * case or it is a fix that can be put in field_group core.
+ *
+ * @param Array $elements by address.
+ */
+function hook_field_group_build_pre_render_alter(& $element) {
+
+  // Prepare variables.
+  $display = isset($element['#view_mode']);
+  $groups = array_keys($element['#groups']);
+
+  // Example from field_group itself to unset empty elements.
+  if ($display) {
+    foreach (element_children($element) as $name) {
+      if (in_array($name, $groups)) {
+        if (field_group_field_group_is_empty($element[$name], $groups)) {
+          unset($element[$name]);
+        }
+      }
+    }
+  }
+
+  // You might include additional javascript files and stylesheets.
+  $element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js';
+  $element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css';
+
+}
+
+/**
+ * Implements hook_field_group_format_summary().
+ *
+ * Place to override or change default summary behavior. In most
+ * cases the implementation of field group itself will be enough.
+ *
+ * TODO It might be better to change this hook with already created summaries,
+ * giving the ability to alter or add it later on.
+ */
+function hook_field_group_format_summary($group) {
+  $output = '';
+  // Create additional summary or change the default setting.
+  return $output;
+}
+
+/**
+ * Implement hook_ctools_plugin_api().
+ * This hook is needed to let ctools know about exportables.
+ * If you create field groups by using hook_field_group_info, you
+ * will need to include the ctools api hook as well.
+ */
+function hook_ctools_plugin_api($module, $api) {
+  if ($module == 'field_group' && $api == 'field_group') {
+    return array('version' => 1);
+  }
+}
+
+/**
+ * Implements hook_field_group_info().
+ * Don't forget to include the ctools hook to notify that
+ * your modules has field group exports.
+ * @see hook_ctools_plugin_api.
+ */
+function hook_field_group_info() {
+
+}
+
+/**
+ * Alter the field group definitions provided by other modules.
+ *
+ * @param array $groups
+ *   Reference to an array of field group definition objects.
+ */
+function hook_field_group_info_alter(&$groups) {
+  if (!empty($groups['group_issue_metadata|node|project_issue|form'])) {
+    $groups['group_issue_metadata|node|project_issue|form']->data['children'][] = 'taxonomy_vocabulary_9';
+  }
+}
+
+/**
+ * Implements hook_field_group_update_field_group().
+ *
+ * This hook is invoked by ctools export API.
+ * Note that this is used by ctools and the group could occasional be
+ * the group ID.
+ *
+ * @param $object $group
+ *   The FieldGroup object.
+ */
+function hook_field_group_update_field_group($group) {
+  // Delete extra data depending on the group.
+}
+
+/**
+ * Implements hook_field_group_delete_field_group().
+ *
+ * This hook is invoked by ctools export API.
+ *
+ * @param $object $group
+ *   The FieldGroup object.
+ */
+function hook_field_group_delete_field_group($group) {
+  // Delete extra data depending on the group.
+}
+
+/**
+ * Implements hook_field_group_create_field_group().
+ *
+ * This hook is invoked by ctools export API.
+ *
+ * @param $object $group
+ *   The FieldGroup object.
+ */
+function hook_field_group_create_field_group($group) {
+  // Create extra data depending on the group.
+}
+
+
+
+/**
+ * @} End of "addtogroup hooks".
+ */
+
+
+
+/**
+ * @addtogroup utility functions
+ * @{
+ */
+
+/**
+ * Get the groups for a given entity type, bundle and view mode.
+ *
+ * @param String $entity_type
+ *   The Entity type where field groups are requested.
+ * @param String $bundle
+ *   The entity bundle for the field groups.
+ * @param String $view_mode
+ *   The view mode scope for the field groups.
+ *
+ * @see field_group_read_groups()
+ * @see ctools_export_crud_load()
+ * @see ctools_export_crud_load_all()
+ * @see ctools_export_crud_delete()
+ * @see ctools_export_crud_save()
+ */
+function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode = NULL, $reset = FALSE) {
+  // This function caches the result and delegates to field_group_read_groups.
+}
+
+/**
+ * Get the groups for the given parameters, uncached.
+ *
+ * @param Array $params
+ *   The Entity type where field groups are requested.
+ * @param $enabled
+ *   Return enabled or disabled groups.*
+ *
+ * @see field_group_info_groups()
+ * @see ctools_export_load_object()
+ */
+function field_group_read_groups($conditions = array(), $enabled = TRUE) {
+  // This function loads the requested groups through ctools export api.
+}
+
+/**
+ * Hides field groups including children in a render array.
+ *
+ * @param array $element
+ *   A render array. Can be a form, node, user, ...
+ * @param array $group_names
+ *   An array of field group names that should be hidden.
+ */
+function field_group_hide_field_groups(&$element, $group_names) {
+}
+
+/**
+ * @} End of "addtogroup utility functions".
+ */
+

+ 23 - 0
sites/all/modules/contrib/fields/field_group/field_group.css

@@ -0,0 +1,23 @@
+/* $Id: field_group.css,v 1.1.2.12 2010/12/22 22:22:35 stalski Exp $ */
+
+/**
+ * Fix for fieldsets in vertical tabs.
+ * Note that this can only be hardcoded to the Seven theme
+ * where people who override this, are in trouble.
+ * This can be removed in next d7 release.
+ */
+.vertical-tabs fieldset.default-fallback,
+div.field-group-tabs-wrapper div.field-type-image fieldset,
+div.field-group-tabs-wrapper div.field-type-file fieldset,
+div.field-group-tabs-wrapper div.field-type-datetime fieldset {
+   border: 1px solid #CCCCCC;
+   margin: 1em 0;
+   padding: 2.5em 0 0;
+   position: relative;
+}
+
+div.field-group-tabs-wrapper div.field-type-image legend,
+div.field-group-tabs-wrapper div.field-type-file legend,
+div.field-group-tabs-wrapper div.field-type-datetime legend {
+  display: block;
+}

+ 70 - 0
sites/all/modules/contrib/fields/field_group/field_group.features.inc

@@ -0,0 +1,70 @@
+<?php
+
+
+/**
+ * Implements hook_features_export_alter().
+ *
+ * For a given feature, add field groups that contain any fields that
+ * are a part of this feature.  Also, add parent groups of any groups
+ * that are a part of this feature.
+ */
+function field_group_features_export_alter(&$export, $module_name) {
+  // Make sure we have fresh data by loading directly.
+  ctools_include('export');
+  $field_groups = ctools_export_load_object('field_group');
+
+  // Support the separate field base -vs- field instance structure that was
+  // added in Features v7.x-2.0-beta2.
+  if (function_exists('field_instance_features_export')) {
+    $export_var = 'field_instance';
+  }
+  else {
+    $export_var = 'field';
+  }
+
+  // Add fieldgroups based on the fields that are present.
+  if (!empty($export['features'][$export_var])) {
+    if (!isset($export['features']['field_group'])) {
+      $export['features']['field_group'] = array();
+    }
+    foreach ($export['features'][$export_var] as $field) {
+      list($entity_type, $bundle, $field_name) = explode('-', $field);
+
+      foreach ($field_groups as $group_id => $group) {
+
+        if ($group->entity_type == $entity_type && $group->bundle == $bundle && in_array($field_name, $group->data['children']) && !in_array($group->identifier, $export['features']['field_group'])) {
+          if (isset($group->export_module) && $group->export_module != $module_name) {
+            $export['dependencies'][$group->export_module] = $group->export_module;
+          }
+          else {
+            $export['features']['field_group'][$group_id] = $group_id;
+          }
+        }
+      }
+    }
+  }
+
+  // Add any parent field groups that haven't been selected.
+  if (!empty($export['features']['field_group'])) {
+    foreach ($export['features']['field_group'] as $id) {
+      $group = isset($field_groups[$id]) ? $field_groups[$id] : FALSE;
+
+      if ($group && !empty($group->parent_name)) {
+        $parent_id = $group->parent_name . '|' . $group->entity_type . '|' . $group->bundle . '|' . $group->mode;
+        $parent_group = isset($field_groups[$parent_id]) ? $field_groups[$parent_id] : FALSE;
+
+        if ($parent_group && !isset($export['features']['field_group'][$parent_id])) {
+          if (isset($parent_group->export_module) && $parent_group->export_module != $module_name) {
+            $export['dependencies'][$parent_group->export_module] = $parent_group->export_module;
+          }
+          else {
+            $export['features']['field_group'][$parent_id] = $parent_id;
+          }
+        }
+      }
+    }
+    if(empty($export['dependencies']['field_group'])) {
+      $export['dependencies']['field_group'] = 'field_group';
+    }
+  }
+}

+ 14 - 0
sites/all/modules/contrib/fields/field_group/field_group.field_ui.css

@@ -0,0 +1,14 @@
+
+#field-overview tr.field-group .group-label,
+#field-display-overview tr.field-group .group-label {
+  font-weight: bold;
+}
+
+#field-overview tr.static-region,
+#field-display-overview tr.static-region {
+	background-color: #ddd;
+}
+
+#edit-refresh {
+  display:none;
+}

File diff suppressed because it is too large
+ 854 - 0
sites/all/modules/contrib/fields/field_group/field_group.field_ui.inc


+ 137 - 0
sites/all/modules/contrib/fields/field_group/field_group.field_ui.js

@@ -0,0 +1,137 @@
+
+(function($) {
+
+Drupal.behaviors.fieldUIFieldsOverview = {
+  attach: function (context, settings) {
+    $('table#field-overview', context).once('field-field-overview', function() {
+      Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIFieldOverview);
+    });
+  }
+};
+  
+/**
+ * Row handlers for the 'Manage fields' screen.
+ */
+Drupal.fieldUIFieldOverview = Drupal.fieldUIFieldOverview || {};
+
+Drupal.fieldUIFieldOverview.group = function(row, data) {
+  this.row = row;
+  this.name = data.name;
+  this.region = data.region;
+  this.tableDrag = data.tableDrag;
+
+  // Attach change listener to the 'group format' select.
+  this.$formatSelect = $('select.field-group-type', row);
+  this.$formatSelect.change(Drupal.fieldUIOverview.onChange);
+
+  return this;
+};
+
+Drupal.fieldUIFieldOverview.group.prototype = {
+  getRegion: function () {
+    return 'main';
+  },
+  
+  regionChange: function (region, recurse) {
+    return {};
+  },
+
+  regionChangeFields: function (region, element, refreshRows) {
+
+    // Create a new tabledrag rowObject, that will compute the group's child
+    // rows for us.
+    var tableDrag = element.tableDrag;
+    rowObject = new tableDrag.row(element.row, 'mouse', true);
+    // Skip the main row, we handled it above.
+    rowObject.group.shift();
+
+    // Let child rows handlers deal with the region change - without recursing
+    // on nested group rows, we are handling them all here.
+    $.each(rowObject.group, function() {
+      var childRow = this;
+      var childRowHandler = $(childRow).data('fieldUIRowHandler');
+      $.extend(refreshRows, childRowHandler.regionChange(region, false));
+    });
+  }  
+};
+  
+  
+/**
+ * Row handlers for the 'Manage display' screen.
+ */
+Drupal.fieldUIDisplayOverview = Drupal.fieldUIDisplayOverview || {};
+
+Drupal.fieldUIDisplayOverview.group = function(row, data) {
+  this.row = row;
+  this.name = data.name;
+  this.region = data.region;
+  this.tableDrag = data.tableDrag;
+
+  // Attach change listener to the 'group format' select.
+  this.$formatSelect = $('select.field-group-type', row);
+  this.$formatSelect.change(Drupal.fieldUIOverview.onChange);
+
+  return this;
+};
+
+Drupal.fieldUIDisplayOverview.group.prototype = {
+  getRegion: function () {
+    return (this.$formatSelect.val() == 'hidden') ? 'hidden' : 'visible';
+  },
+
+  regionChange: function (region, recurse) {
+
+    // Default recurse to true.
+    recurse = (recurse == undefined) || recurse;
+
+    // When triggered by a row drag, the 'format' select needs to be adjusted to
+    // the new region.
+    var currentValue = this.$formatSelect.val();
+    switch (region) {
+      case 'visible':
+        if (currentValue == 'hidden') {
+          // Restore the group format back to 'fieldset'.
+          var value = 'fieldset';
+        }
+        break;
+
+      default:
+        var value = 'hidden';
+        break;
+    }
+    if (value != undefined) {
+      this.$formatSelect.val(value);
+    }
+
+    var refreshRows = {};
+    refreshRows[this.name] = this.$formatSelect.get(0);
+
+    if (recurse) {
+      this.regionChangeFields(region, this, refreshRows);
+    }
+
+    return refreshRows;
+  },
+
+  regionChangeFields: function (region, element, refreshRows) {
+
+    // Create a new tabledrag rowObject, that will compute the group's child
+    // rows for us.
+    var tableDrag = element.tableDrag;
+    rowObject = new tableDrag.row(element.row, 'mouse', true);
+    // Skip the main row, we handled it above.
+    rowObject.group.shift();
+
+    // Let child rows handlers deal with the region change - without recursing
+    // on nested group rows, we are handling them all here.
+    $.each(rowObject.group, function() {
+      var childRow = this;
+      var childRowHandler = $(childRow).data('fieldUIRowHandler');
+      $.extend(refreshRows, childRowHandler.regionChange(region, false));
+    });
+    
+  }
+  
+};
+
+})(jQuery);

+ 19 - 0
sites/all/modules/contrib/fields/field_group/field_group.info

@@ -0,0 +1,19 @@
+name = Fieldgroup
+description = Fieldgroup
+package = Fields
+dependencies[] = field
+dependencies[] = ctools
+core = 7.x
+files[] = field_group.install
+files[] = field_group.module
+files[] = field_group.field_ui.inc
+files[] = field_group.form.inc
+files[] = field_group.features.inc
+files[] = tests/field_group.ui.test
+files[] = tests/field_group.display.test
+; Information added by drupal.org packaging script on 2013-09-25
+version = "7.x-1.3"
+core = "7.x"
+project = "field_group"
+datestamp = "1380124361"
+

+ 327 - 0
sites/all/modules/contrib/fields/field_group/field_group.install

@@ -0,0 +1,327 @@
+<?php
+
+/**
+ * @file
+ * Fieldgroup module install file.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function field_group_schema() {
+  $schema['field_group'] = array(
+    'description' => t('Table that contains field group entries and settings.'),
+
+    // CTools export definitions.
+    'export' => array(
+      'key' => 'identifier',
+      'identifier' => 'field_group',
+      'default hook' => 'field_group_info',
+      'save callback' => 'field_group_group_save',
+      'delete callback' => 'field_group_group_export_delete',
+      'can disable' => TRUE,
+      'api' => array(
+        'owner' => 'field_group',
+        'api' => 'field_group',
+        'minimum_version' => 1,
+        'current_version' => 1,
+      ),
+    ),
+
+    'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'The primary identifier for a group',
+        'no export' => TRUE,
+      ),
+      'identifier' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The unique string identifier for a group.',
+      ),
+      'group_name' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The name of this group.',
+      ),
+      'entity_type' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'bundle' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''
+        ),
+      'mode' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''
+      ),
+      // @todo 'parent_name' is redundant with the data in the 'children'
+      // entry, brings a risk of inconsistent data. This should be removed from
+      // the schema and pre-computed it if needed in field_group_get_groups().
+      'parent_name' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The parent name for a group',
+      ),
+      'data' => array(
+        'type' => 'blob',
+        'size' => 'big',
+        'not null' => TRUE,
+        'serialize' => TRUE,
+        'description' => 'Serialized data containing the group properties that do not warrant a dedicated column.',
+      ),
+    ),
+    'primary key' => array('id'),
+    'indexes' => array(
+      'group_name' => array('group_name'),
+    ),
+    'unique keys' => array(
+      'identifier' => array('identifier'),
+    ),
+  );
+  return $schema;
+}
+
+/**
+ * Utility function: fetch all the field_group definitions from the database.
+ */
+function _field_group_install_read_groups() {
+  $groups = array();
+  if (db_table_exists('content_group')) {
+    $query = db_select('content_group', 'cg', array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('cg')
+      // We only want non-multigroups.
+      ->condition('group_type', 'standard');
+    foreach ($query->execute() as $record) {
+      $record['settings'] = unserialize($record['settings']);
+      $groups[$record['group_name'] . '-' . $record['type_name']] = $record;
+    }
+    foreach ($groups as $key => $group) {
+      $query2 = db_select('content_group_fields', 'cgf', array('fetch' => PDO::FETCH_ASSOC))
+        ->fields('cgf')
+        ->condition('group_name', $group['group_name']);
+      foreach ($query2->execute() as $field) {
+        $groups[$field['group_name'] . '-' . $field['type_name']]['children'][] = $field;
+      }
+    }
+  }
+  return $groups;
+}
+
+/**
+ * Implements of hook_install().
+ *
+ * Because this is a new module in D7, hook_update_N() doesn't help D6
+ * users who upgrade to run the migration path. So, we try that here as
+ * the module is being installed.
+ */
+function field_group_install() {
+
+  $groups = _field_group_install_read_groups();
+  module_load_include('module', 'field_group');
+
+  if (!empty($groups)) {
+
+    module_load_include('module', 'ctools');
+    ctools_include('export');
+
+    foreach ($groups as $group) {
+
+      $group = (object) $group;
+
+      $new = new stdClass();
+      $new->group_name = $group->group_name;
+      $new->entity_type = 'node';
+      $new->bundle = $group->type_name;
+      $new->label = $group->label;
+      $new->parent_name = '';
+      $new->children = array();
+      foreach ($group->children as $child) {
+        $new->children[] = $child['field_name'];
+      }
+
+      // The form.
+      $new->id = NULL;
+      $new->weight = $group->weight;
+      $new->mode = 'form';
+      $new->format_type = 'fieldset';
+      $new->format_settings = array(
+        'formatter' => preg_match("/fieldset/", $group->settings['form']['style']) ? 'collapsible' : 'collapsed',
+        'instance_settings' => array(),
+      );
+      $new->identifier = $new->group_name . '|' . $new->entity_type . '|' . $new->bundle . '|' . $new->mode;
+      ctools_export_crud_save('field_group', $new);
+
+      // The full node.
+      $new->id = NULL;
+      $new->weight = $group->weight;
+      $new->mode = 'default';
+      $new->format_type = $group->settings['display']['full']['format'];
+      $new->format_settings = array(
+        'formatter' => 'collapsible',
+        'instance_settings' => array(),
+      );
+      $new->identifier = $new->group_name . '|' . $new->entity_type . '|' . $new->bundle . '|' . $new->mode;
+      ctools_export_crud_save('field_group', $new);
+
+      // The teaser node.
+      $new->id = NULL;
+      $new->weight = $group->weight;
+      $new->mode = 'teaser';
+      $new->format_type = $group->settings['display']['teaser']['format'];
+      $new->format_settings = array(
+        'formatter' => 'collapsible',
+        'instance_settings' => array(),
+      );
+      $new->identifier = $new->group_name . '|' . $new->entity_type . '|' . $new->bundle . '|' . $new->mode;
+      ctools_export_crud_save('field_group', $new);
+
+    }
+
+  }
+
+  // Set weight to 1.
+  db_update('system')
+    ->fields(array('weight' => 1))
+    ->condition('name', 'field_group')
+    ->execute();
+
+}
+
+/**
+ * Update hook on the field_group table to add an unique identifier.
+ */
+function field_group_update_7001() {
+
+  if (!db_field_exists('field_group', 'identifier')) {
+    // Add the new string identifier field for ctools.
+    db_add_field('field_group', 'identifier', array(
+      'type' => 'varchar',
+      'length' => 255,
+      'not null' => TRUE,
+      'default' => '',
+      'description' => 'The unique string identifier for a group.',
+    ));
+    // Force drupal's schema to be rebuilt
+    drupal_get_schema('field_group', TRUE);
+
+    module_load_include('module', 'field_group');
+    _field_group_recreate_identifiers();
+
+  }
+
+  db_update('system')
+    ->fields(array('weight' => 1))
+    ->condition('name', 'field_group')
+    ->execute();
+
+}
+
+/**
+ * Update hook to clear cache for new changes to take effect.
+ */
+function field_group_update_7002() {
+
+  module_load_include('module', 'field_group');
+
+  // This hook is called to satify people with older version of field_group.
+  // This will recreate all identifiers for the field_groups known in database.
+  // At the moment, we only trigger field_groups that are stored in the database, where
+  // we should maybe get all field_groups as ctools has registered them.
+  // See http://drupal.org/node/1169146.
+  // See http://drupal.org/node/1018550.
+  _field_group_recreate_identifiers();
+
+}
+
+/**
+ * Update hook to recreate identifiers.
+ * @see function field_group_update_7002.
+ */
+function field_group_update_7003() {
+
+  module_load_include('module', 'field_group');
+  _field_group_recreate_identifiers();
+
+}
+
+/**
+ * Update hook to make sure identifier is set as unique key.
+ */
+function field_group_update_7004() {
+  db_drop_unique_key('field_group', 'identifier');
+  db_add_unique_key('field_group', 'identifier', array('identifier'));
+}
+
+/**
+ * Checks all existing groups and removes optional HTML classes
+ * while adding them as extra classes.
+ */
+function field_group_update_7005() {
+
+  // Migrate the field groups so they have a unique identifier.
+  $result = db_select('field_group', 'fg')
+    ->fields('fg')
+    ->execute();
+  $rows = array();
+  foreach($result as $row) {
+    //$row->identifier = $row->group_name . '|' . $row->entity_type . '|' . $row->bundle . '|' . $row->mode;
+    $row->data = unserialize($row->data);
+    $classes = explode(" ", $row->data['format_settings']['instance_settings']['classes']);
+    $optional_classes = array(str_replace("_", "-", $row->group_name), 'field-group-' . $row->data['format_type']);
+    foreach ($optional_classes as $optional_class) {
+      if (!in_array($optional_class, $classes)) {
+        $classes[] = $optional_class;
+      }
+    }
+    $row->data['format_settings']['instance_settings']['classes'] = implode(" ", $classes);
+    $rows[] = $row;
+  }
+  foreach ($rows as $row) {
+    drupal_write_record('field_group', $row, array('id'));
+  }
+
+}
+
+/**
+ * Save all optional HTML classes for fieldgroups located in features.
+ * If you need the optional classes, recreate feature after this update.
+ * If not, you can revert it.
+ */
+function field_group_update_7006() {
+  ctools_include("export");
+  // Migrate the field groups so they have a unique identifier.
+  $field_groups = ctools_export_load_object("field_group");
+  foreach ($field_groups as $row) {
+
+    // Only update feature field_groups this time.
+    // Don't touch the fieldgroups in db.
+    if ($row->export_type == EXPORT_IN_CODE) {
+      $classes = explode(" ", $row->data['format_settings']['instance_settings']['classes']);
+      $optional_classes = array(str_replace("_", "-", $row->group_name), 'field-group-' . $row->data['format_type']);
+      foreach ($optional_classes as $optional_class) {
+        if (!in_array($optional_class, $classes)) {
+          $classes[] = $optional_class;
+        }
+      }
+      $row->data['format_settings']['instance_settings']['classes'] = implode(" ", $classes);
+      unset($row->id);
+      drupal_write_record('field_group', $row);
+    }
+  }
+
+}

+ 224 - 0
sites/all/modules/contrib/fields/field_group/field_group.js

@@ -0,0 +1,224 @@
+
+(function($) {
+
+/**
+ * Drupal FieldGroup object.
+ */
+Drupal.FieldGroup = Drupal.FieldGroup || {};
+Drupal.FieldGroup.Effects = Drupal.FieldGroup.Effects || {};
+Drupal.FieldGroup.groupWithfocus = null;
+
+Drupal.FieldGroup.setGroupWithfocus = function(element) {
+  element.css({display: 'block'});
+  Drupal.FieldGroup.groupWithfocus = element;
+}
+
+/**
+ * Implements Drupal.FieldGroup.processHook().
+ */
+Drupal.FieldGroup.Effects.processFieldset = {
+  execute: function (context, settings, type) {
+    if (type == 'form') {
+      // Add required fields mark to any fieldsets containing required fields
+      $('fieldset.fieldset', context).once('fieldgroup-effects', function(i) {
+        if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) {
+          $('legend span.fieldset-legend', $(this)).eq(0).append(' ').append($('.form-required').eq(0).clone());
+        }
+        if ($('.error', $(this)).length) {
+          $('legend span.fieldset-legend', $(this)).eq(0).addClass('error');
+          Drupal.FieldGroup.setGroupWithfocus($(this));
+        }
+      });
+    }
+  }
+}
+
+/**
+ * Implements Drupal.FieldGroup.processHook().
+ */
+Drupal.FieldGroup.Effects.processAccordion = {
+  execute: function (context, settings, type) {
+    $('div.field-group-accordion-wrapper', context).once('fieldgroup-effects', function () {
+      var wrapper = $(this);
+
+      wrapper.accordion({
+        autoHeight: false,
+        active: '.field-group-accordion-active',
+        collapsible: true,
+        changestart: function(event, ui) {
+          if ($(this).hasClass('effect-none')) {
+            ui.options.animated = false;
+          }
+          else {
+            ui.options.animated = 'slide';
+          }
+        }
+      });
+
+      if (type == 'form') {
+
+        var $firstErrorItem = false;
+
+        // Add required fields mark to any element containing required fields
+        wrapper.find('div.field-group-accordion-item').each(function(i) {
+
+          if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) {
+            $('h3.ui-accordion-header a').eq(i).append(' ').append($('.form-required').eq(0).clone());
+          }
+          if ($('.error', $(this)).length) {
+            // Save first error item, for focussing it.
+            if (!$firstErrorItem) {
+              $firstErrorItem = $(this).parent().accordion("activate" , i);
+            }
+            $('h3.ui-accordion-header').eq(i).addClass('error');
+          }
+        });
+
+        // Save first error item, for focussing it.
+        if (!$firstErrorItem) {
+          $('.ui-accordion-content-active', $firstErrorItem).css({height: 'auto', width: 'auto', display: 'block'});
+        }
+
+      }
+    });
+  }
+}
+
+/**
+ * Implements Drupal.FieldGroup.processHook().
+ */
+Drupal.FieldGroup.Effects.processHtabs = {
+  execute: function (context, settings, type) {
+    if (type == 'form') {
+      // Add required fields mark to any element containing required fields
+      $('fieldset.horizontal-tabs-pane', context).once('fieldgroup-effects', function(i) {
+        if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) {
+          $(this).data('horizontalTab').link.find('strong:first').after($('.form-required').eq(0).clone()).after(' ');
+        }
+        if ($('.error', $(this)).length) {
+          $(this).data('horizontalTab').link.parent().addClass('error');
+          Drupal.FieldGroup.setGroupWithfocus($(this));
+          $(this).data('horizontalTab').focus();
+        }
+      });
+    }
+  }
+}
+
+/**
+ * Implements Drupal.FieldGroup.processHook().
+ */
+Drupal.FieldGroup.Effects.processTabs = {
+  execute: function (context, settings, type) {
+    if (type == 'form') {
+      // Add required fields mark to any fieldsets containing required fields
+      $('fieldset.vertical-tabs-pane', context).once('fieldgroup-effects', function(i) {
+        if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) {
+          $(this).data('verticalTab').link.find('strong:first').after($('.form-required').eq(0).clone()).after(' ');
+        }
+        if ($('.error', $(this)).length) {
+          $(this).data('verticalTab').link.parent().addClass('error');
+          Drupal.FieldGroup.setGroupWithfocus($(this));
+          $(this).data('verticalTab').focus();
+        }
+      });
+    }
+  }
+}
+
+/**
+ * Implements Drupal.FieldGroup.processHook().
+ *
+ * TODO clean this up meaning check if this is really
+ *      necessary.
+ */
+Drupal.FieldGroup.Effects.processDiv = {
+  execute: function (context, settings, type) {
+
+    $('div.collapsible', context).once('fieldgroup-effects', function() {
+      var $wrapper = $(this);
+
+      // Turn the legend into a clickable link, but retain span.field-group-format-toggler
+      // for CSS positioning.
+
+      var $toggler = $('span.field-group-format-toggler:first', $wrapper);
+      var $link = $('<a class="field-group-format-title" href="#"></a>');
+      $link.prepend($toggler.contents());
+
+      // Add required field markers if needed
+      if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) {
+        $link.append(' ').append($('.form-required').eq(0).clone());
+      }
+
+      $link.appendTo($toggler);
+
+      // .wrapInner() does not retain bound events.
+      $link.click(function () {
+        var wrapper = $wrapper.get(0);
+        // Don't animate multiple times.
+        if (!wrapper.animating) {
+          wrapper.animating = true;
+          var speed = $wrapper.hasClass('speed-fast') ? 300 : 1000;
+          if ($wrapper.hasClass('effect-none') && $wrapper.hasClass('speed-none')) {
+            $('> .field-group-format-wrapper', wrapper).toggle();
+          }
+          else if ($wrapper.hasClass('effect-blind')) {
+            $('> .field-group-format-wrapper', wrapper).toggle('blind', {}, speed);
+          }
+          else {
+            $('> .field-group-format-wrapper', wrapper).toggle(speed);
+          }
+          wrapper.animating = false;
+        }
+        $wrapper.toggleClass('collapsed');
+        return false;
+      });
+
+    });
+  }
+};
+
+/**
+ * Behaviors.
+ */
+Drupal.behaviors.fieldGroup = {
+  attach: function (context, settings) {
+    if (settings.field_group == undefined) {
+      return;
+    }
+
+    // Execute all of them.
+    $.each(Drupal.FieldGroup.Effects, function (func) {
+      // We check for a wrapper function in Drupal.field_group as
+      // alternative for dynamic string function calls.
+      var type = func.toLowerCase().replace("process", "");
+      if (settings.field_group[type] != undefined && $.isFunction(this.execute)) {
+        this.execute(context, settings, settings.field_group[type]);
+      }
+    });
+
+    // Fixes css for fieldgroups under vertical tabs.
+    $('.fieldset-wrapper .fieldset > legend').css({display: 'block'});
+    $('.vertical-tabs fieldset.fieldset').addClass('default-fallback');
+
+
+    // Add a new ID to each fieldset.
+    $('.group-wrapper fieldset').each(function() {
+      // Tats bad, but we have to keep the actual id to prevent layouts to break.
+      var fieldgorupID = 'field_group-' + $(this).attr('id') + ' ' + $(this).attr('id');
+      $(this).attr('id', fieldgorupID);
+    })
+    // Set the hash in url to remember last userselection.
+    $('.group-wrapper ul li').each(function() {
+      var fieldGroupNavigationListIndex = $(this).index();
+      $(this).children('a').click(function() {
+        var fieldset = $('.group-wrapper fieldset').get(fieldGroupNavigationListIndex);
+        // Grab the first id, holding the wanted hashurl.
+        var hashUrl = $(fieldset).attr('id').replace(/^field_group-/, '').split(' ')[0];
+        window.location.hash = hashUrl;
+      });
+    });
+  }
+};
+
+})(jQuery);

+ 2139 - 0
sites/all/modules/contrib/fields/field_group/field_group.module

@@ -0,0 +1,2139 @@
+<?php
+
+/**
+ * @file
+ * Fieldgroup module.
+ *
+ * For an overview of all php and javascript hooks, see field_group.api.php.
+ *
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function field_group_menu() {
+  $items = array();
+
+  // Ensure the following is not executed until field_bundles is working and
+  // tables are updated. Needed to avoid errors on initial installation.
+  if (defined('MAINTENANCE_MODE')) {
+    return $items;
+  }
+
+  // Create tabs for all possible bundles.
+  foreach (entity_get_info() as $entity_type => $entity_info) {
+    if (isset($entity_info['fieldable']) && $entity_info['fieldable']) {
+      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
+        if (isset($bundle_info['admin'])) {
+          // Extract path information from the bundle.
+          $path = $bundle_info['admin']['path'];
+          // Different bundles can appear on the same path (e.g. %node_type and
+          // %comment_node_type). To allow field_group_menu_load() to extract the
+          // actual bundle object from the translated menu router path
+          // arguments, we need to identify the argument position of the bundle
+          // name string ('bundle argument') and pass that position to the menu
+          // loader. The position needs to be casted into a string; otherwise it
+          // would be replaced with the bundle name string.
+          if (isset($bundle_info['admin']['bundle argument'])) {
+            $bundle_arg = $bundle_info['admin']['bundle argument'];
+            $bundle_pos = (string) $bundle_arg;
+          }
+          else {
+            $bundle_arg = $bundle_name;
+            $bundle_pos = '0';
+          }
+
+          // This is the position of the %field_group_menu placeholder in the
+          // items below.
+          $group_position = count(explode('/', $path)) + 1;
+
+          // Extract access information, providing defaults.
+          $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
+          $access += array(
+            'access callback' => 'user_access',
+            'access arguments' => array('administer site configuration'),
+          );
+
+          $items["$path/groups/%field_group_menu/delete"] = array(
+            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
+            'title' => 'Delete',
+            'page callback' => 'drupal_get_form',
+            'page arguments' => array('field_group_delete_form', $group_position),
+            'type' => MENU_CALLBACK,
+            'file' => 'field_group.field_ui.inc',
+          ) + $access;
+
+          $items["$path/groups/%field_group_menu/enable"] = array(
+            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
+            'title' => 'Enable',
+            'page callback' => 'drupal_get_form',
+            'page arguments' => array('field_group_enable_form', $group_position),
+            'type' => MENU_CALLBACK,
+            'file' => 'field_group.field_ui.inc',
+          ) + $access;
+
+        }
+      }
+    }
+  }
+
+  return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function field_group_permission() {
+  return array(
+    'administer fieldgroups' => array(
+      'title' => t('Administer fieldgroups'),
+      'description' => t('Display the administration for fieldgroups.'),
+    ),
+  );
+}
+
+/**
+ * Menu Wildcard loader function to load group definitions.
+ *
+ * @param $group_name
+ *   The name of the group, as contained in the path.
+ * @param $entity_type
+ *   The name of the entity.
+ * @param $bundle_name
+ *   The name of the bundle, as contained in the path.
+ * @param $bundle_pos
+ *   The position of $bundle_name in $map.
+ * @param $map
+ *   The translated menu router path argument map.
+ */
+function field_group_menu_load($group_name, $entity_type, $bundle_name, $bundle_pos, $map) {
+
+  if ($bundle_pos > 0) {
+    $bundle = $map[$bundle_pos];
+    $bundle_name = field_extract_bundle($entity_type, $bundle);
+  }
+
+  $args = func_get_args();
+  $args_pop = array_pop($args);
+  $mode = array_pop($args_pop);
+
+  $group = field_group_load_field_group($group_name, $entity_type, $bundle_name, $mode);
+
+  return empty($group) ? FALSE : $group;
+}
+
+/**
+ * Loads a group definition.
+ *
+ * @param $group_name
+ *   The name of the group.
+ * @param $entity_type
+ *   The name of the entity.
+ * @param $bundle_name
+ *   The name of the bundle.
+ * @param $mode
+ *   The view mode to load.
+ */
+function field_group_load_field_group($group_name, $entity_type, $bundle_name, $mode) {
+
+  ctools_include('export');
+  $objects = ctools_export_load_object('field_group', 'conditions', array(
+    'group_name' => $group_name,
+    'entity_type' => $entity_type,
+    'bundle' => $bundle_name,
+    'mode' => $mode,
+  ));
+  $object = array_shift($objects);
+
+  if ($object && isset($object->data)) {
+    return field_group_unpack($object);
+  }
+
+  return $object;
+}
+
+/**
+ * Implements hook_ctools_plugin_api().
+ */
+function field_group_ctools_plugin_api($owner, $api) {
+  if ($owner == 'field_group' && $api == 'field_group') {
+    return array('version' => 1);
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function field_group_theme() {
+  return array(
+    'horizontal_tabs' => array(
+      'render element' => 'element',
+    ),
+    'multipage' => array(
+      'render element' => 'element',
+    ),
+    'multipage_pane' => array(
+      'render element' => 'element',
+    ),
+  );
+}
+
+/**
+ * Implements hook_theme_registry_alter().
+ */
+function field_group_theme_registry_alter(&$theme_registry) {
+
+  // Inject field_group_build_entity_groups in all entity theming functions.
+  $entity_info = entity_get_info();
+  $entities = array();
+  foreach ($entity_info as $entity => $info) {
+    if (isset($entity_info[$entity]['fieldable']) && $entity_info[$entity]['fieldable']) {
+      // User uses user_profile for theming.
+      if ($entity == 'user') $entity = 'user_profile';
+      $entities[] = $entity;
+    }
+  }
+
+  // Support for File Entity.
+  if (isset($theme_registry['file_entity'])) {
+    $entities[] = 'file_entity';
+  }
+
+  // Support for Entity API.
+  if (isset($theme_registry['entity'])) {
+    $entities[] = 'entity';
+  }
+
+  foreach ($entities as $entity) {
+    if (isset($theme_registry[$entity])) {
+      $theme_registry[$entity]['preprocess functions'][] = 'field_group_build_entity_groups';
+      // DS support, make sure it comes after field_group.
+      if ($key = array_search('ds_entity_variables', $theme_registry[$entity]['preprocess functions'])) {
+        unset($theme_registry[$entity]['preprocess functions'][$key]);
+        $theme_registry[$entity]['preprocess functions'][] = 'ds_entity_variables';
+      }
+    }
+  }
+
+}
+
+/**
+ * Implements hook_field_attach_delete_bundle().
+ *
+ * @param String $entity_type
+ * @param String $bundle
+ */
+function field_group_field_attach_delete_bundle($entity_type, $bundle) {
+
+  ctools_include('export');
+  $list = field_group_read_groups(array('bundle' => $bundle, 'entity_type' => $entity_type));
+
+  // Delete the entity's entry from field_group of all entities.
+  // We fetch the field groups first to assign the removal task to ctools.
+  if (isset($list[$entity_type], $list[$entity_type][$bundle])) {
+    foreach ($list[$entity_type][$bundle] as $group_mode => $groups) {
+      foreach ($groups as $group) {
+        ctools_export_crud_delete('field_group', $group);
+      }
+    }
+  }
+
+}
+
+/**
+ * Implements hook_field_attach_form().
+ */
+function field_group_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
+  $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css';
+  field_group_attach_groups($form, 'form', $form_state);
+  $form['#pre_render'][] = 'field_group_form_pre_render';
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ * Using hook_form_field_ui_field_overview_form_alter.
+ */
+function field_group_form_field_ui_field_overview_form_alter(&$form, &$form_state) {
+  form_load_include($form_state, 'inc', 'field_group', 'field_group.field_ui');
+  field_group_field_ui_overview_form_alter($form, $form_state);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ * Using hook_form_field_ui_display_overview_form_alter.
+ */
+function field_group_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
+  form_load_include($form_state, 'inc', 'field_group', 'field_group.field_ui');
+  field_group_field_ui_overview_form_alter($form, $form_state, TRUE);
+}
+
+/**
+ * Implements hook_field_attach_view_alter().
+ */
+function field_group_field_attach_view_alter(&$element, $context) {
+  // Check whether the view mode uses custom display settings or the 'default' mode.
+  $actual_mode = 'default';
+  if (isset($element['#entity_type']) && isset($element['#bundle'])) {
+    $view_mode_settings = field_view_mode_settings($element['#entity_type'], $element['#bundle']);
+    $view_mode = $context['view_mode'];
+    $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default');
+    field_group_attach_groups($element, $actual_mode);
+  }
+}
+
+/**
+ * Implements hook_field_group_formatter_info().
+ */
+function field_group_field_group_formatter_info() {
+
+  return array(
+    'form' => array(
+      'html-element' => array(
+        'label' => t('HTML element'),
+        'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'),
+        'instance_settings' => array('element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1),
+      ),
+      'div' => array(
+        'label' => t('Div'),
+        'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'),
+        'format_types' => array('open', 'collapsible', 'collapsed'),
+        'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => '', 'required_fields' => 1, 'id' => ''),
+        'default_formatter' => 'open',
+      ),
+      'html5' => array(
+        'label' => t('HTML5'),
+        'description' => t('This fieldgroup renders the inner content in a semantic HTML5 wrapper'),
+        'instance_settings' => array('wrapper' => '', 'classes' => ''),
+      ),
+      'fieldset' => array(
+        'label' => t('Fieldset'),
+        'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'),
+        'format_types' => array('open', 'collapsible', 'collapsed'),
+        'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1),
+        'default_formatter' => 'collapsible',
+      ),
+      'tabs' => array(
+        'label' => t('Vertical tabs group'),
+        'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'),
+        'instance_settings' => array('classes' => ''),
+      ),
+      'tab' => array(
+        'label' => t('Vertical tab'),
+        'description' => t('This fieldgroup renders the content in a fieldset, part of vertical tabs group.'),
+        'format_types' => array('open', 'closed'),
+        'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1),
+        'default_formatter' => 'closed',
+      ),
+      'htabs' => array(
+        'label' => t('Horizontal tabs group'),
+        'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'),
+        'instance_settings' => array('classes' => ''),
+      ),
+      'htab' => array(
+        'label' => t('Horizontal tab'),
+        'format_types' => array('open', 'closed'),
+        'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'),
+        'default_formatter' => 'closed',
+        'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1, 'id' => ''),
+      ),
+      'multipage-group' => array(
+        'label' => t('Multipage group'),
+        'description' => t('This fieldgroup renders groups on separate pages.'),
+        'instance_settings' => array('classes' => '', 'page_header' => 3, 'move_additional' => 1, 'page_counter' => 1, 'move_button' => 0),
+      ),
+      'multipage' => array(
+        'label' => t('Multipage'),
+        'format_types' => array('start', 'no-start'),
+        'description' => t('This fieldgroup renders the content in a page.'),
+        'default_formatter' => 'no-start',
+        'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1),
+      ),
+      'accordion' => array(
+        'label' => t('Accordion group'),
+        'description' => t('This fieldgroup renders child groups as jQuery accordion.'),
+        'instance_settings' => array('effect' => 'none', 'classes' => ''),
+      ),
+      'accordion-item' => array(
+        'label' => t('Accordion item'),
+        'format_types' => array('open', 'closed'),
+        'description' => t('This fieldgroup renders the content in a div, part of accordion group.'),
+        'default_formatter' => 'closed',
+        'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1),
+      ),
+    ),
+    'display' => array(
+      'html-element' => array(
+        'label' => t('HTML element'),
+        'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'),
+        'instance_settings' => array('element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1),
+      ),
+      'div' => array(
+        'label' => t('Div'),
+        'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'),
+        'format_types' => array('open', 'collapsible', 'collapsed'),
+        'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => ''),
+        'default_formatter' => 'collapsible',
+      ),
+      'html5' => array(
+        'label' => t('HTML5'),
+        'description' => t('This fieldgroup renders the inner content in a semantic HTML5 wrapper'),
+        'instance_settings' => array('wrapper' => '', 'classes' => ''),
+      ),
+      'fieldset' => array(
+        'label' => t('Fieldset'),
+        'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'),
+        'format_types' => array('open', 'collapsible', 'collapsed'),
+        'instance_settings' => array('description' => '', 'classes' => ''),
+        'default_formatter' => 'collapsible',
+      ),
+      'tabs' => array(
+        'label' => t('Vertical tabs group'),
+        'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'),
+        'instance_settings' => array('classes' => ''),
+      ),
+      'tab' => array(
+        'label' => t('Vertical tab'),
+        'description' => t('This fieldgroup renders the content in a fieldset, part of vertical tabs group.'),
+        'format_types' => array('open', 'closed'),
+        'instance_settings' => array('description' => '', 'classes' => ''),
+        'default_formatter' => 'closed',
+      ),
+      'htabs' => array(
+        'label' => t('Horizontal tabs group'),
+        'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'),
+        'instance_settings' => array('classes' => ''),
+      ),
+      'htab' => array(
+        'label' => t('Horizontal tab item'),
+        'format_types' => array('open', 'closed'),
+        'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'),
+        'instance_settings' => array('description' => '', 'classes' => '', 'id' => ''),
+        'default_formatter' => 'closed',
+      ),
+      'accordion' => array(
+        'label' => t('Accordion group'),
+        'description' => t('This fieldgroup renders child groups as jQuery accordion.'),
+        'instance_settings' => array('description' => '', 'classes' => '', 'effect' => 'bounceslide'),
+      ),
+      'accordion-item' => array(
+        'label' => t('Accordion item'),
+        'format_types' => array('open', 'closed'),
+        'description' => t('This fieldgroup renders the content in a div, part of accordion group.'),
+        'instance_settings' => array('classes' => ''),
+        'default_formatter' => 'closed',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements hook_field_group_format_settings().
+ * If the group has no format settings, default ones will be added.
+ * @params Object $group The group object.
+ * @return Array $form The form element for the format settings.
+ */
+function field_group_field_group_format_settings($group) {
+  // Add a wrapper for extra settings to use by others.
+  $form = array(
+    'instance_settings' => array(
+      '#tree' => TRUE,
+      '#weight' => 2,
+    ),
+  );
+
+  $field_group_types = field_group_formatter_info();
+  $mode = $group->mode == 'form' ? 'form' : 'display';
+  $formatter = $field_group_types[$mode][$group->format_type];
+
+  // Add the required formatter type selector.
+  if (isset($formatter['format_types'])) {
+    $form['formatter'] = array(
+      '#title' => t('Fieldgroup settings'),
+      '#type' => 'select',
+      '#options' => drupal_map_assoc($formatter['format_types']),
+      '#default_value' => isset($group->format_settings['formatter']) ? $group->format_settings['formatter'] : $formatter['default_formatter'],
+      '#weight' => 1,
+    );
+  }
+
+  if (isset($formatter['instance_settings']['required_fields']) && $mode == 'form') {
+    $form['instance_settings']['required_fields'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Mark group as required if it contains required fields.'),
+      '#default_value' => isset($group->format_settings['instance_settings']['required_fields']) ? $group->format_settings['instance_settings']['required_fields'] : (isset($formatter['instance_settings']['required_fields']) ? $formatter['instance_settings']['required_fields'] : ''),
+      '#weight' => 2,
+    );
+  }
+
+  if (isset($formatter['instance_settings']['id'])) {
+    $form['instance_settings']['id'] = array(
+      '#title' => t('ID'),
+      '#type' => 'textfield',
+      '#default_value' => isset($group->format_settings['instance_settings']['id']) ? $group->format_settings['instance_settings']['id'] : (isset($formatter['instance_settings']['id']) ? $formatter['instance_settings']['id'] : ''),
+      '#weight' => 3,
+      '#element_validate' => array('field_group_validate_id'),
+    );
+  }
+  if (isset($formatter['instance_settings']['classes'])) {
+    $form['instance_settings']['classes'] = array(
+      '#title' => t('Extra CSS classes'),
+      '#type' => 'textfield',
+      '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''),
+      '#weight' => 4,
+      '#element_validate' => array('field_group_validate_css_class'),
+    );
+  }
+
+  if (isset($formatter['instance_settings']['description'])) {
+    $form['instance_settings']['description'] = array(
+      '#title' => t('Description'),
+      '#type' => 'textarea',
+      '#default_value' => isset($group->format_settings['instance_settings']['description']) ? $group->format_settings['instance_settings']['description'] : (isset($formatter['instance_settings']['description']) ? $formatter['instance_settings']['description'] : ''),
+      '#weight' => 0,
+    );
+  }
+
+  // Add optional instance_settings.
+  switch ($group->format_type) {
+    case 'html-element':
+      $form['instance_settings']['element'] = array(
+        '#title' => t('Element'),
+        '#type' => 'textfield',
+        '#default_value' => isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : $formatter['instance_settings']['element'],
+        '#description' => t('E.g. div, section, aside etc.'),
+        '#weight' => 2,
+      );
+
+      $form['instance_settings']['attributes'] = array(
+        '#title' => t('Attributes'),
+        '#type' => 'textfield',
+        '#default_value' => isset($group->format_settings['instance_settings']['attributes']) ? $group->format_settings['instance_settings']['attributes'] : $formatter['instance_settings']['attributes'],
+        '#description' => t('E.g. name="anchor"'),
+        '#weight' => 4,
+      );
+      break;
+    case 'div':
+      $form['label']['#description'] = t('Please enter a label for collapsible elements');
+      $form['instance_settings']['show_label'] = array(
+        '#title' => t('Show label'),
+        '#type' => 'select',
+        '#options' => array(0 => t('No'), 1 => t('Yes')),
+        '#default_value' => isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : $formatter['instance_settings']['show_label'],
+        '#weight' => 2,
+      );
+      $form['instance_settings']['label_element'] = array(
+        '#title' => t('Label element'),
+        '#type' => 'select',
+        '#options' => array('h2' => t('Header 2'), 'h3' => t('Header 3')),
+        '#default_value' => isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : $formatter['instance_settings']['label_element'],
+        '#weight' => 2,
+      );
+      $form['instance_settings']['effect'] = array(
+        '#title' => t('Effect'),
+        '#type' => 'select',
+        '#options' => array('none' => t('None'), 'blind' => t('Blind')),
+        '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'],
+        '#weight' => 2,
+      );
+      $form['instance_settings']['speed'] = array(
+        '#title' => t('Speed'),
+        '#type' => 'select',
+        '#options' => array('none' => t('None'), 'slow' => t('Slow'), 'fast' => t('Fast')),
+        '#default_value' => isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : $formatter['instance_settings']['speed'],
+        '#weight' => 3,
+      );
+      break;
+    case 'html5':
+      $form['instance_settings']['wrapper'] = array(
+        '#title' => t('HTML5 wrapper'),
+        '#type' => 'select',
+        '#options' => array('section' => t('Section'), 'article' => t('Article'), 'header' => t('Header'), 'footer' => t('Footer'), 'aside' => t('Aside')),
+        '#default_value' => isset($group->format_settings['instance_settings']['wrapper']) ? $group->format_settings['instance_settings']['wrapper'] : 'section',
+      );
+      break;
+    case 'fieldset':
+      $form['label']['#description'] = t('Please enter a label for collapsible elements');
+      break;
+    case 'multipage-group':
+      $form['instance_settings']['page_header'] = array(
+        '#title' => t('Format page title'),
+        '#type' => 'select',
+        '#options' => array(0 => t('None'), 1 => t('Label only'), 2 => t('Step 1 of 10'), 3 => t('Step 1 of 10 [Label]'),),
+        '#default_value' => isset($group->format_settings['instance_settings']['page_header']) ? $group->format_settings['instance_settings']['page_header'] : $formatter['instance_settings']['page_header'],
+        '#weight' => 1,
+      );
+      $form['instance_settings']['page_counter'] = array(
+        '#title' => t('Add a page counter at the bottom'),
+        '#type' => 'select',
+        '#options' => array(0 => t('No'), 1 => t('Format 1 / 10'), 2 => t('The count number only')),
+        '#default_value' => isset($group->format_settings['instance_settings']['page_counter']) ? $group->format_settings['instance_settings']['page_counter'] : $formatter['instance_settings']['page_counter'],
+        '#weight' => 2,
+      );
+      $form['instance_settings']['move_button'] = array(
+        '#title' => t('Move submit button to last multipage'),
+        '#type' => 'select',
+        '#options' => array(0 => t('No'), 1 => t('Yes')),
+        '#default_value' => isset($group->format_settings['instance_settings']['move_button']) ? $group->format_settings['instance_settings']['move_button'] : $formatter['instance_settings']['move_button'],
+        '#weight' => 3,
+      );
+      $form['instance_settings']['move_additional'] = array(
+        '#title' => t('Move additional settings to last multipage (if available)'),
+        '#type' => 'select',
+        '#options' => array(0 => t('No'), 1 => t('Yes')),
+        '#default_value' => isset($group->format_settings['instance_settings']['move_additional']) ? $group->format_settings['instance_settings']['move_additional'] : $formatter['instance_settings']['move_additional'],
+        '#weight' => 4,
+      );
+    case 'tabs':
+    case 'htabs':
+      break;
+    case 'accordion':
+      $form['instance_settings']['effect'] = array(
+        '#title' => t('Effect'),
+        '#type' => 'select',
+        '#options' => array('none' => t('None'), 'bounceslide' => t('Bounce slide')),
+        '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'],
+        '#weight' => 2,
+      );
+      break;
+    case 'multipage':
+      break;
+    case 'tab':
+    case 'htab':
+    case 'accordion-item':
+    default:
+  }
+
+  return $form;
+}
+
+/**
+ * Helper function to prepare basic variables needed for most formatters.
+ *
+ * Called in field_group_field_group_pre_render(), but can also be called in
+ * other implementations of hook_field_group_pre_render().
+ */
+function field_group_pre_render_prepare(&$group) {
+
+  $classes = _field_group_get_html_classes($group);
+
+  $group->classes = implode(' ', $classes->required);
+  $group->description = isset($group->format_settings['instance_settings']['description']) ? filter_xss_admin(t($group->format_settings['instance_settings']['description'])) : '';
+
+}
+
+/**
+ * Implements hook_field_group_pre_render().
+ *
+ * @param Array $elements by address.
+ * @param Object $group The Field group info.
+ */
+function field_group_field_group_pre_render(& $element, &$group, & $form) {
+
+  field_group_pre_render_prepare($group);
+
+  $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form';
+
+  // Add all field_group format types to the js settings.
+  $form['#attached']['js'][] = array(
+    'data' => array('field_group' => array($group->format_type => $view_mode)),
+    'type' => 'setting',
+  );
+
+  if (isset($group->format_settings['instance_settings']['id']) && !empty($group->format_settings['instance_settings']['id'])) {
+    $element['#id'] = $group->format_settings['instance_settings']['id'];
+  }
+  else {
+    $element['#id'] = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $view_mode . '_' . $group->group_name;
+  }
+
+  $element['#weight'] = $group->weight;
+
+  // Call the pre render function for the format type.
+  $function = "field_group_pre_render_" . str_replace("-", "_", $group->format_type);
+  if (function_exists($function)) {
+    $function($element, $group, $form);
+  }
+
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Fieldset.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_fieldset(&$element, $group, &$form) {
+
+  $element += array(
+    '#type' => 'fieldset',
+    '#title' => check_plain(t($group->label)),
+    '#collapsible' => $group->collapsible,
+    '#collapsed' => $group->collapsed,
+    '#pre_render' => array(),
+    '#attributes' => array('class' => explode(' ', $group->classes)),
+    '#description' => $group->description,
+  );
+
+  if ($group->collapsible || $group->collapsed) {
+    $element['#attached']['library'][] = array('system', 'drupal.collapse');
+  }
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: HTML element.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_html_element(&$element, $group, &$form) {
+  $html_element = isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : 'div';
+  $attributes = isset($group->format_settings['instance_settings']['attributes']) ? ' ' . $group->format_settings['instance_settings']['attributes'] : '';
+  $group->classes = trim($group->classes);
+
+  // This regex split the attributes string so that we can pass that
+  // later to drupal_attributes().
+  preg_match_all('/([^\s=]+)="([^"]+)"/', $attributes, $matches);
+
+  $element_attributes = array();
+  // Put the attribute and the value together.
+  foreach ($matches[1] as $key => $attribute) {
+    $element_attributes[$attribute] = $matches[2][$key];
+  }
+
+  // Add the classes to the attributes array.
+  if (!isset($element_attributes['class']) && $group->classes) {
+    $element_attributes['class'] = $group->classes;
+  }
+  elseif (isset($element_attributes['class']) && $group->classes) {
+    $element_attributes['class'] .= ' ' . $group->classes;
+  }
+
+  $attributes = drupal_attributes($element_attributes);
+
+  $element['#prefix'] = '<' . $html_element . $attributes . '>';
+  $element['#suffix'] = '</' . $html_element . '>';
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Div.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_div(&$element, $group, &$form) {
+
+  $show_label = isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : 0;
+  $label_element = isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : 'h2';
+  $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none';
+
+  $element['#type'] = 'markup';
+  if ($group->format_settings['formatter'] != 'open') {
+    $element['#prefix'] = '<div class="' . $group->classes . '">
+      <' . $label_element . '><span class="field-group-format-toggler">' . check_plain(t($group->label)) . '</span></' . $label_element . '>
+      <div class="field-group-format-wrapper" style="display: ' . (!empty($group->collapsed) ? 'none' : 'block') . ';">';
+    $element['#suffix'] = '</div></div>';
+  }
+  else {
+    $class_attribute = '';
+    if (!empty($group->classes)) {
+      $class_attribute = 'class = "' . $group->classes . '"';
+    }
+    $element['#prefix'] = '<div id="' . $element['#id'] . '"' . $class_attribute . '>';
+    if ($show_label) {
+      $element['#prefix'] .= '<' . $label_element . '><span>' . check_plain(t($group->label)) . '</span></' . $label_element . '>';
+    }
+    $element['#suffix'] = '</div>';
+  }
+  if (!empty($group->description)) {
+    $element['#prefix'] .= '<div class="description">' . $group->description . '</div>';
+  }
+
+  if ($effect == 'blind') {
+    $element['#attached']['library'][] = array('system', 'effects.blind');
+  }
+
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: HTML5.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_html5(&$element, $group, &$form) {
+  $element += array(
+    '#type' => 'markup',
+    '#prefix' => '<' . $group->format_settings['instance_settings']['wrapper'] . ' id="' . $element['#id'] . '" class="' . $group->classes . '">',
+    '#suffix' => '</' . $group->format_settings['instance_settings']['wrapper'] . '>',
+  );
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Accordion.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_accordion(&$element, $group, &$form) {
+
+  // Add the jQuery UI accordion.
+  $element['#attached']['library'][] = array('system', 'ui.accordion');
+
+  $element += array(
+    '#type' => 'markup',
+    '#prefix' => '<div class="' . $group->classes . '">',
+    '#suffix' => '</div>',
+  );
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Accordion item.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_accordion_item(&$element, $group, &$form) {
+
+  $element += array(
+    '#type' => 'markup',
+    '#prefix' => '<h3 class="field-group-format-toggler ' . $group->format_type . ($group->collapsed ? '' : ' field-group-accordion-active') . '"><a href="#">' . check_plain(t($group->label)) . '</a></h3>
+    <div class="field-group-format-wrapper ' . $group->classes . '">',
+    '#suffix' => '</div>',
+    //'#attributes' => array('class' => array($group->format_type)),
+  );
+  if (!empty($group->description)) {
+    $element['#prefix'] .= '<div class="description">' . $group->description . '</div>';
+  }
+
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Horizontal tabs group.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_htabs(&$element, $group, &$form) {
+
+  $element += array(
+    '#type' => 'horizontal_tabs',
+    '#title' => check_plain(t($group->label)),
+    '#theme_wrappers' => array('horizontal_tabs'),
+    '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $group->classes . '">',
+    '#suffix' => '</div>',
+  );
+
+  // By default vertical_tabs don't have titles but you can override it in the theme.
+  if (!empty($group->label)) {
+    $element['#title'] = check_plain($group->label);
+  }
+
+  // Only add form.js on forms.
+  if (!empty($form['#type']) && $form['#type'] == 'form') {
+    $element['#attached']['js'][] = 'misc/form.js';
+  }
+
+  $element['#attached']['library'][] = array('field_group', 'horizontal-tabs');
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Horizontal tab.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_htab(&$element, $group, &$form) {
+
+  $element += array(
+    '#type' => 'fieldset',
+    '#title' => check_plain(t($group->label)),
+    '#collapsible' => $group->collapsible,
+    '#collapsed' => $group->collapsed,
+    '#attributes' => array('class' => explode(" ", $group->classes)),
+    '#group' => $group->parent_name,
+    // very important. Cannot be added on the form!
+    '#parents' => array($group->parent_name),
+    '#description' => $group->description,
+  );
+
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Multipage group.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_multipage_group(&$element, &$group, &$form) {
+
+  $multipage_element = array(
+    '#type' => 'multipage',
+    '#theme_wrappers' => array('multipage'),
+    '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $group->classes . '">',
+    '#suffix' => '</div>',
+  );
+
+  $element += $multipage_element;
+
+  $move_additional = isset($group->format_settings['instance_settings']['move_additional']) ? ($group->format_settings['instance_settings']['move_additional'] && isset($form['additional_settings'])) : isset($form['additional_settings']);
+  $move_button = isset($group->format_settings['instance_settings']['move_button']) ? $group->format_settings['instance_settings']['move_button'] : 0;
+
+  drupal_add_js(array(
+    'field_group' => array(
+      'multipage_move_submit' => (bool) $move_button,
+      'multipage_move_additional' => (bool) $move_additional
+    )
+  ), 'setting');
+
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Multipage.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_multipage(&$element, $group, &$form) {
+
+  $group->classes .= $group->format_settings['formatter'] == 'start' ? ' multipage-open' : ' multipage-closed';
+  $element += array(
+    '#type' => 'multipage_pane',
+    '#title' => check_plain(t($group->label)),
+    '#collapsible' => $group->collapsible,
+    '#collapsed' => $group->collapsed,
+    '#attributes' => array('class' => explode(" ", $group->classes)),
+    '#group' => $group->parent_name,
+    '#group_object' => $group,
+    '#parent_group_object' => $form['#groups'][$group->parent_name],
+    // very important. Cannot be added on the form!
+    '#parents' => array($group->parent_name),
+    '#description' => $group->description,
+  );
+
+  $element['#attached']['library'][] = array('field_group', 'multipage');
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Vertical tabs wrapper.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_tabs(&$element, $group, &$form) {
+
+  $element += array(
+    '#type' => 'vertical_tabs',
+    '#theme_wrappers' => array('vertical_tabs'),
+    '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper ' . $group->classes . '">',
+    '#suffix' => '</div>',
+  );
+
+  // By default vertical_tabs don't have titles but you can override it in the theme.
+  if (!empty($group->label)) {
+    $element['#title'] = check_plain($group->label);
+  }
+
+  $element[$group->group_name . '__active_tab'] = array(
+    '#type' => 'hidden',
+    '#default_value' => '',
+    '#attributes' => array('class' => array('vertical-tabs-active-tab')),
+  );
+
+  $element['#attached']['library'][] = array('system', 'drupal.collapse');
+}
+
+/**
+ * Implements field_group_pre_render_<format-type>.
+ * Format type: Vertical tab.
+ *
+ * @param $element The field group form element.
+ * @param $group The Field group object prepared for pre_render.
+ * @param $form The root element or form.
+ */
+function field_group_pre_render_tab(&$element, $group, &$form) {
+
+  $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form';
+
+  // Could be it never runs through htab.
+  $form['#attached']['js'][] = array(
+    'data' => array('field_group' => array('tabs' => $view_mode)),
+    'type' => 'setting',
+  );
+
+  $add = array(
+    '#type' => 'fieldset',
+    '#id' => 'edit-' . $group->group_name,
+    '#title' => check_plain(t($group->label)),
+    '#collapsible' => $group->collapsible,
+    '#collapsed' => $group->collapsed,
+    '#attributes' => array('class' => explode(" ", $group->classes)),
+    '#description' => $group->description,
+  );
+
+  // Front-end and back-end on configuration will lead
+  // to vertical tabs nested in a separate vertical group.
+  if ($view_mode != 'form') {
+    $add['#group'] = empty($group->parent_name) ? 'additional_settings' : $group->parent_name;
+    $add['#parents'] = array($add['#group']);
+    $element += $add;
+  }
+  // Form fieldgroups which are nested into a vertical tab group
+  // are handled a little different.
+  elseif (!empty($group->parent_name)) {
+    $add['#group'] = $group->parent_name;
+    $element += $add;
+  }
+  // Forms "can" have additional settins. We'll try to locate it first, if not
+  // exists, field_group will create a parallel additional settings entry.
+  else {
+    // Create the fieldgroup element.
+    $add['#parents'] = array('additional_settings');
+    $add['#group'] = 'additional_settings';
+    $add['#weight'] = -30 + $group->weight; // hardcoded to bring our extra additional vtabs on top.
+
+    // Check if the additional_settings exist for this type of form.
+    if (isset($form['additional_settings']['group']['#groups']['additional_settings'])) {
+
+      // Merge fieldgroups with the core additional settings.
+      $form['additional_settings']['group']['#groups']['additional_settings'][$group->group_name] = $add;
+      $form['additional_settings']['group']['#groups'][$group->group_name] = array('#group_exists' => TRUE);
+      // Nest the fields inside the appropriate structure.
+      foreach (element_children($element) as $fieldname) {
+        $form['additional_settings']['group']['#groups']['additional_settings'][$group->group_name][$fieldname] = &$element[$fieldname];
+        unset($element[$fieldname]);
+      }
+    }
+    // Assumption the wrapper is in the root. This could be done by field_group itself
+    // in previous loop of tabs in same wrapper or even some other contrib / custom module.
+    else {
+      if (!isset($form['additional_settings']['#type'])) {
+        $form['additional_settings'] = array(
+          '#type' => 'vertical_tabs',
+          '#weight' => $group->weight,
+          '#theme_wrappers' => array('vertical_tabs'),
+          '#prefix' => '<div class="field-group-' . $group->format_type . '-wrapper">',
+          '#suffix' => '</div>',
+        );
+        $form['#attached']['library'][] = array('system', 'drupal.collapse');
+      }
+      $form['additional_settings'][$group->group_name] = $add;
+      // Nest the fields inside the appropriate structure.
+      foreach (element_children($element) as $fieldname) {
+        $form['additional_settings'][$group->group_name][$fieldname] = &$element[$fieldname];
+        unset($element[$fieldname]);
+      }
+    }
+  }
+
+}
+
+/**
+ * Implements hook_field_group_build_pre_render_alter().
+ * @param Array $elements by address.
+ */
+function field_group_field_group_build_pre_render_alter(& $element) {
+
+  // Someone is doing a node view, in a node view. Reset content.
+  // TODO Check if this breaks something else.
+  if (isset($element['#node']->content) && count($element['#node']->content) > 0) {
+    $element['#node']->content = array();
+  }
+
+  $display = isset($element['#view_mode']);
+  $groups = array_keys($element['#groups']);
+
+  // Dish the fieldgroups with no fields for non-forms.
+  if ($display) {
+    field_group_remove_empty_display_groups($element, $groups);
+  }
+  else {
+    // Fix the problem on forms with additional settings.
+    field_group_remove_empty_form_groups('form', $element, $groups, $element['#groups'], $element['#entity_type']);
+  }
+
+  // Add the default field_group javascript and stylesheet.
+  $element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js';
+  $element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css';
+
+  // Move additional settings to the last multipage pane if configured that way.
+  // Note that multipages MUST be in the root of the form.
+  foreach (element_children($element) as $name) {
+    if (isset($element[$name]['#type']) && $element[$name]['#type'] == 'multipage' && isset($element['additional_settings'])) {
+      $parent_group = $element['#groups'][$name];
+      $move_additional = isset($parent_group->format_settings['instance_settings']['move_additional']) ? $parent_group->format_settings['instance_settings']['move_additional'] : 1;
+      $last_pane = NULL;
+      foreach (element_children($element[$name], TRUE) as $pane) {
+        $last_pane = $pane;
+      }
+      $element[$name][$last_pane]['additional_settings'] = $element['additional_settings'];
+      unset($element['additional_settings']);
+    }
+  }
+
+}
+
+/**
+ * Remove empty groups on forms.
+ *
+ * @param String $parent_name
+ *   The name of the element.
+ * @param array $element
+ *   The element to check the empty state.
+ * @param array $groups
+ *   Array of group objects.
+ */
+function field_group_remove_empty_form_groups($name, & $element, $groups, &$form_groups, $entity) {
+
+  $exceptions = array('user__account', 'comment__author');
+
+  $children = element_children($element);
+
+  $hasChildren = FALSE;
+  if (count($children)) {
+    foreach ($children as $childname) {
+      if (in_array($childname, $groups)) {
+        field_group_remove_empty_form_groups($childname, $element[$childname], $groups, $form_groups, $entity);
+      }
+      $exception = $entity . '__' . $childname;
+      $hasChildren = $hasChildren ? TRUE : (isset($element[$childname]['#type']) || in_array($exception, $exceptions));
+    }
+  }
+
+  if (!$hasChildren) {
+
+    // Remove empty elements from the #groups.
+    if (empty($element) && isset($form_groups[$name]) && !is_array($form_groups[$name])) {
+      foreach ($form_groups as $group_name => $group) {
+        if (isset($group->children)) {
+          $group_children = array_flip($group->children);
+          if (isset($group_children[$name])) {
+            unset($form_groups[$group_name]->children[$group_children[$name]]);
+          }
+        }
+      }
+    }
+
+    $element['#access'] = FALSE;
+
+  }
+
+}
+
+/**
+ * Remove empty groups on entity display.
+ * @param array $element
+ *   The element to check the empty state.
+ * @param array $groups
+ *   Array of group objects.
+ */
+function field_group_remove_empty_display_groups(& $element, $groups) {
+
+  $empty_child = TRUE;
+  $empty_group = TRUE;
+
+  // Loop through the children for current element.
+  foreach (element_children($element) as $name) {
+
+    // Descend if the child is a group.
+    if (in_array($name, $groups)) {
+      $empty_child = field_group_remove_empty_display_groups($element[$name], $groups);
+      if (!$empty_child) {
+        $empty_group = FALSE;
+      }
+    }
+    // Child is a field, the element is not empty and access is set to true (or empty).
+    elseif (!empty($element[$name]) && (!isset($element[$name]['#access']) || $element[$name]['#access'])) {
+      $empty_group = FALSE;
+    }
+
+  }
+
+  // Reset an empty group.
+  if ($empty_group) {
+    $element = NULL;
+  }
+
+  return $empty_group;
+
+}
+
+/**
+ * Implements hook_field_group_format_summary().
+ */
+function field_group_field_group_format_summary($group) {
+
+  $group_form = module_invoke_all('field_group_format_settings', $group);
+
+  $output = '';
+  if (isset($group->format_settings['formatter'])) {
+    $output .= '<strong>' . $group->format_type . '</strong> ' . $group->format_settings['formatter'] . '';
+  }
+  if (isset($group->format_settings['instance_settings'])) {
+    $last = end($group->format_settings['instance_settings']);
+    $output .= '<br />';
+    foreach ($group->format_settings['instance_settings'] as $key => $value) {
+      if (empty($value)) {
+        continue;
+      }
+
+      $output .= '<strong>' . $key . '</strong> ';
+
+      if (isset($group_form['instance_settings'], $group_form['instance_settings'][$key]['#options'])) {
+        if (is_array($value)) {
+          $value = implode(array_filter($value), ', ');
+        }
+        else {
+          $value = $group_form['instance_settings'][$key]['#options'][$value];
+        }
+      }
+
+      // Shorten the string.
+      if (drupal_strlen($value) > 38) {
+        $value = truncate_utf8($value, 50, TRUE, TRUE);
+      }
+      // If still numeric, handle it as yes or no.
+      elseif (is_numeric($value)) {
+        $value = $value == '1' ? t('yes') : t('no');
+      }
+      $output .= check_plain($value);
+      $output .= $last == $value ? ' ' : '<br />';
+    }
+  }
+  return $output;
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function field_group_element_info() {
+  $types['horizontal_tabs'] = array(
+    '#theme_wrappers' => array('horizontal_tabs'),
+    '#default_tab' => '',
+    '#process' => array('form_process_horizontal_tabs'),
+  );
+  $types['multipage'] = array(
+    '#theme_wrappers' => array('multipage'),
+    '#default_tab' => '',
+    '#process' => array('form_process_multipage'),
+  );
+  $types['multipage_pane'] = array(
+    '#value' => NULL,
+    '#process' => array('form_process_fieldset', 'ajax_process_form'),
+    '#pre_render' => array('form_pre_render_fieldset'),
+    '#theme_wrappers' => array('multipage_pane'),
+  );
+  return $types;
+}
+
+/**
+ * Implements hook_library().
+ */
+function field_group_library() {
+
+  $path = drupal_get_path('module', 'field_group');
+  // Horizontal Tabs.
+  $libraries['horizontal-tabs'] = array(
+    'title' => 'Horizontal Tabs',
+    'website' => 'http://drupal.org/node/323112',
+    'version' => '1.0',
+    'js' => array(
+      $path . '/horizontal-tabs/horizontal-tabs.js' => array(),
+    ),
+    'css' => array(
+      $path . '/horizontal-tabs/horizontal-tabs.css' => array(),
+    ),
+  );
+  // Multipage Tabs.
+  $libraries['multipage'] = array(
+    'title' => 'Multipage',
+    'website' => 'http://drupal.org/node/323112',
+    'version' => '1.0',
+    'js' => array(
+      $path . '/multipage/multipage.js' => array(),
+    ),
+    'css' => array(
+      $path . '/multipage/multipage.css' => array(),
+    ),
+  );
+
+  return $libraries;
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function field_group_field_extra_fields() {
+  $extra = array();
+
+  $extra['user']['user'] = array('form' => array());
+
+  // User picture field to integrate with user module.
+  if (variable_get('user_pictures', 0)) {
+    $extra['user']['user']['form']['picture'] = array(
+      'label' => t('Picture'),
+      'description' => t('User picture'),
+      'weight' => 5,
+    );
+  }
+
+  // Field to itegrate with overlay module.
+  if (module_exists('overlay')) {
+    $extra['user']['user']['form']['overlay_control'] = array(
+      'label' => t('Administrative overlay'),
+      'description' => t('Administrative overlay'),
+      'weight' => 5,
+    );
+  }
+
+  // Field to itegrate with contact module.
+  if (module_exists('contact')) {
+    $extra['user']['user']['form']['contact'] = array(
+      'label' => t('Contact'),
+      'description' => t('Contact user element'),
+     'weight' => 5,
+    );
+  }
+
+  // Field to integrate with the locale module.
+  if (module_exists('locale')) {
+    $extra['user']['user']['form']['locale'] = array(
+      'label' => t('Language settings'),
+      'description' => t('Language settings for the user account.'),
+      'weight' => 5,
+    );
+  }
+
+  // Field to integrate with the wysiwyg module on user settings.
+  if (module_exists('wysiwyg')) {
+    $extra['user']['user']['form']['wysiwyg'] = array(
+      'label' => t('Wysiwyg status'),
+      'description' => t('Text formats enabled for rich-text editing'),
+      'weight' => 5,
+    );
+  }
+
+  return $extra;
+}
+
+/**
+ * Implements hook_field_attach_rename_bundle().
+ */
+function field_group_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) {
+  db_query('UPDATE {field_group} SET bundle = :bundle WHERE bundle = :old_bundle AND entity_type = :entity_type', array(
+    ':bundle' => $bundle_new,
+    ':old_bundle' => $bundle_old,
+    ':entity_type' => $entity_type,
+  ));
+}
+
+/**
+ * Creates a group formatted as horizontal tabs.
+ * This function will never be callable from within field_group rendering. Other
+ * modules using #type horizontal_tabs will have the benefit of this processor.
+ *
+ * @param $element
+ *   An associative array containing the properties and children of the
+ *   fieldset.
+ * @param $form_state
+ *   The $form_state array for the form this horizontal tab widget belongs to.
+ * @return
+ *   The processed element.
+ */
+function form_process_horizontal_tabs($element, &$form_state) {
+  // Inject a new fieldset as child, so that form_process_fieldset() processes
+  // this fieldset like any other fieldset.
+  $element['group'] = array(
+    '#type' => 'fieldset',
+    '#theme_wrappers' => array(),
+    '#parents' => $element['#parents'],
+  );
+
+  // The JavaScript stores the currently selected tab in this hidden
+  // field so that the active tab can be restored the next time the
+  // form is rendered, e.g. on preview pages or when form validation
+  // fails.
+  $name = implode('__', $element['#parents']);
+  if (isset($form_state['values'][$name . '__active_tab'])) {
+    $element['#default_tab'] = $form_state['values'][$name . '__active_tab'];
+  }
+  $element[$name . '__active_tab'] = array(
+    '#type' => 'hidden',
+    '#default_value' => $element['#default_tab'],
+    '#attributes' => array('class' => array('horizontal-tabs-active-tab')),
+  );
+
+  return $element;
+}
+
+/**
+ * Returns HTML for an element's children fieldsets as horizontal tabs.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties and children of the
+ *     fieldset. Properties used: #children.
+ *
+ * @ingroup themeable
+ */
+function theme_horizontal_tabs($variables) {
+  $element = $variables['element'];
+  // Add required JavaScript and Stylesheet.
+  $element['#attached']['library'][] = array('field_group', 'horizontal-tabs');
+
+  $output = '<h2 class="element-invisible">' . (!empty($element['#title']) ? $element['#title'] : t('Horizontal Tabs')) . '</h2>';
+  $output .= '<div class="horizontal-tabs-panes">' . $element['#children'] . '</div>';
+
+  return $output;
+}
+
+/**
+ * Creates a group formatted as multipage.
+ * This function will never be callable from within field_group rendering. Other
+ * modules using #type multipage will have the benefit of this processor.
+ *
+ * @param $element
+ *   An associative array containing the properties and children of the
+ *   fieldset.
+ * @param $form_state
+ *   The $form_state array for the form this multipage tab widget belongs to.
+ * @return
+ *   The processed element.
+ */
+function form_process_multipage($element, &$form_state) {
+  // Inject a new fieldset as child, so that form_process_fieldset() processes
+  // this fieldset like any other fieldset.
+  $element['group'] = array(
+    '#type' => 'fieldset',
+    '#theme_wrappers' => array(),
+    '#parents' => $element['#parents'],
+  );
+
+  // The JavaScript stores the currently selected tab in this hidden
+  // field so that the active control can be restored the next time the
+  // form is rendered, e.g. on preview pages or when form validation
+  // fails.
+  $name = implode('__', $element['#parents']);
+  if (isset($form_state['values'][$name . '__active_control'])) {
+    $element['#default_tab'] = $form_state['values'][$name . '__active_control'];
+  }
+  $element[$name . '__active_control'] = array(
+    '#type' => 'hidden',
+    '#default_value' => $element['#default_control'],
+    '#attributes' => array('class' => array('multipage-active-control')),
+  );
+  
+  return $element;
+}
+
+/**
+ * Returns HTML for an element's children fieldsets as multipage.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties and children of the
+ *     fieldset. Properties used: #children.
+ *
+ * @ingroup themeable
+ */
+function theme_multipage($variables) {
+  $element = $variables['element'];
+  // Add required JavaScript and Stylesheet.
+  $element['#attached']['library'][] = array('field_group', 'multipage');
+
+  $output = '<h2 class="element-invisible">' . (!empty($element['#title']) ? $element['#title'] : t('Multipage')) . '</h2>';
+
+  $output .= '<div class="multipage-panes">';
+  $output .= $element['#children'];
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Returns HTML for multipage pane.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties and children of the
+ *     fieldset. Properties used: #children.
+ *
+ * @ingroup themeable
+ */
+function theme_multipage_pane($variables) {
+
+  $element = $variables['element'];
+  $group = $variables['element']['#group_object'];
+  $parent_group = $variables['element']['#parent_group_object'];
+
+  static $multipages;
+  if (!isset($multipages[$group->parent_name])) {
+    $multipages = array($group->parent_name => 0);
+  }
+  $multipages[$parent_group->group_name]++;
+
+  // Create a page title from the label.
+  $page_header = isset($parent_group->format_settings['instance_settings']['page_header']) ? $parent_group->format_settings['instance_settings']['page_header'] : 3;
+  switch ($page_header) {
+    case 1:
+      $title = $element['#title'];
+      break;
+    case 2:
+      $title = t('Step %count of %total', array('%count' => $multipages[$parent_group->group_name], '%total' => count($parent_group->children)));
+      break;
+    case 3:
+      $title = t('Step %count of %total !label', array('%count' => $multipages[$parent_group->group_name], '%total' => count($parent_group->children), '!label' => $element['#title']));
+      break;
+    case 0:
+    default:
+      $title = '';
+      break;
+  }
+
+  element_set_attributes($element, array('id'));
+  _form_set_class($element, array('form-wrapper'));
+
+  $output = '<div' . drupal_attributes($element['#attributes']) . '>';
+  if (!empty($element['#title'])) {
+    // Always wrap fieldset legends in a SPAN for CSS positioning.
+    $output .= '<h2 class="multipage-pane-title"><span>' . $title . '</span></h2>';
+  }
+  $output .= '<div class="fieldset-wrapper multipage-pane-wrapper">';
+  if (!empty($element['#description'])) {
+    $output .= '<div class="fieldset-description">' . $element['#description'] . '</div>';
+  }
+  $output .= $element['#children'];
+  if (isset($element['#value'])) {
+    $output .= $element['#value'];
+  }
+
+  // Add a page counter if needed.
+  // counter array(0 => t('No'), 1 => t('Format 1 / 10'), 2 => t('The count number only'));
+  $page_counter_format = isset($parent_group->format_settings['instance_settings']['page_counter']) ? $parent_group->format_settings['instance_settings']['page_counter'] : 1;
+  $multipage_element['#page_counter_rendered'] = '';
+  if ($page_counter_format == 1) {
+    $output .= t('<span class="multipage-counter">%count / %total</span>', array('%count' => $multipages[$parent_group->group_name], '%total' => count($parent_group->children)));
+  }
+  elseif ($page_counter_format == 2) {
+    $output .=  t('<span class="multipage-counter">%count</span>', array('%count' => $multipages[$parent_group->group_name]));
+  }
+
+  $output .= '</div>';
+  $output .= "</div>\n";
+
+  return $output;
+
+}
+
+/**
+ * Get all groups.
+ *
+ * @param $entity_type
+ *   The name of the entity.
+ * @param $bundle
+ *   The name of the bundle.
+ * @param $view_mode
+ *   The view mode.
+ * @param $reset.
+ *   Whether to reset the cache or not.
+ */
+function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode = NULL, $reset = FALSE) {
+  static $groups = FALSE;
+
+  if (!$groups || $reset) {
+    if (!$reset && $cached = cache_get('field_groups', 'cache_field')) {
+      $groups = $cached->data;
+    }
+    else {
+      drupal_static_reset('ctools_export_load_object');
+      drupal_static_reset('ctools_export_load_object_all');
+      $groups = field_group_read_groups();
+      cache_set('field_groups', $groups, 'cache_field');
+    }
+  }
+
+  if (!isset($entity_type)) {
+    return $groups;
+  }
+  elseif (!isset($bundle) && isset($groups[$entity_type])) {
+    return $groups[$entity_type];
+  }
+  elseif (!isset($view_mode) && isset($groups[$entity_type][$bundle])) {
+    return $groups[$entity_type][$bundle];
+  }
+  elseif (isset($groups[$entity_type][$bundle][$view_mode])) {
+    return $groups[$entity_type][$bundle][$view_mode];
+  }
+  return array();
+}
+
+/**
+ * Read all groups.
+ *
+ * @param array $conditions
+ *   Parameters for the query, as elements of the $conditions array.
+ *   'entity_type' The name of the entity type.
+ *   'bundle' The name of the bundle.
+ *   'mode' The view mode.
+ *
+ * @param boolean $enabled
+ *   Return enabled or disabled groups.
+ *
+ * @return array
+ *   Array of groups.
+ */
+function field_group_read_groups($conditions = array(), $enabled = TRUE) {
+
+  $groups = array();
+  ctools_include('export');
+
+  if (empty($conditions)) {
+    $records = ctools_export_load_object('field_group');
+  }
+  else {
+    $records = ctools_export_load_object('field_group', 'conditions', $conditions);
+  }
+
+  foreach ($records as $group) {
+
+    // Return only enabled groups.
+    if ($enabled && isset($group->disabled) && $group->disabled) {
+      continue;
+    }
+    // Return only disabled groups.
+    elseif (!$enabled && (!isset($group->disabled) || !$group->disabled)) {
+      continue;
+    }
+
+    $groups[$group->entity_type][$group->bundle][$group->mode][$group->group_name] = field_group_unpack($group);
+
+  }
+  drupal_alter('field_group_info', $groups);
+  return $groups;
+
+}
+
+/**
+ * Utility function to recreate identifiers.
+ */
+function _field_group_recreate_identifiers() {
+
+  // Migrate the field groups so they have a unique identifier.
+  $result = db_select('field_group', 'fg')
+    ->fields('fg')
+    ->execute();
+  $rows = array();
+  foreach($result as $row) {
+    $row->identifier = $row->group_name . '|' . $row->entity_type . '|' . $row->bundle . '|' . $row->mode;
+    $row->data = unserialize($row->data);
+    $rows[] = $row;
+  }
+  foreach ($rows as $row) {
+    drupal_write_record('field_group', $row, array('id'));
+  }
+
+}
+
+/**
+ * Checks if a field_group exists in required context.
+ *
+ * @param String $group_name
+ *   The name of the group.
+ * @param String $entity_type
+ *   The name of the entity.
+ * @param String $bundle
+ *   The bundle for the entity.
+ * @param String $mode
+ *   The view mode context the group will be rendered.
+ */
+function field_group_exists($group_name, $entity_type, $bundle, $mode) {
+  $groups = field_group_read_groups();
+  return !empty($groups[$entity_type][$bundle][$mode][$group_name]);
+}
+
+/**
+ * Unpacks a database row in a FieldGroup object.
+ * @param $packed_group
+ *   Database result object with stored group data.
+ * @return $group
+ *   Field group object.
+ */
+function field_group_unpack($packed_group) {
+  if (!isset($packed_group->data)) {
+    return $packed_group;
+  }
+
+  // Extract unserialized data.
+  $group = clone $packed_group;
+  $data = $group->data;
+  unset($group->data);
+  $group->label = isset($data['label']) ? $data['label'] : '';
+  $group->weight = isset($data['weight']) ? $data['weight'] : '';
+  $group->children = isset($data['children']) ? $data['children'] : '';
+  $group->format_type = !empty($data['format_type']) ? $data['format_type'] : 'fieldset';
+  if (isset($data['format_settings'])) {
+    $group->format_settings = $data['format_settings'];
+  }
+
+  return $group;
+}
+
+/**
+ * Packs a FieldGroup object into a database row.
+ * @param $group
+ *   FieldGroup object.
+ * @return $record
+ *   Database row object, ready to be inserted/update
+ */
+function field_group_pack($group) {
+
+  $record = clone $group;
+  $record->data = array(
+    'label' => $record->label,
+    'weight' => $record->weight,
+    'children' => $record->children,
+    'format_type' => !empty($record->format_type) ? $record->format_type : 'fieldset',
+  );
+  if (isset($record->format_settings)) {
+    $record->data['format_settings'] = $record->format_settings;
+  }
+  return $record;
+}
+
+/**
+ * Delete a field group.
+ * This function is also called by ctools export when calls are
+ * made through ctools_export_crud_delete().
+ *
+ * @param $group
+ *   A group definition.
+ * @param $ctools_crud
+ *  Is this function called by the ctools crud delete.
+ */
+function field_group_group_export_delete($group, $ctools_crud = TRUE) {
+
+  $query = db_delete('field_group');
+
+  if (isset($group->identifier)) {
+    $query->condition('identifier', $group->identifier);
+    if (!$ctools_crud) {
+      ctools_export_crud_disable('field_group', $group->identifier);
+    }
+  }
+  elseif (isset($group->id)) {
+    $query->condition('id', $group->id);
+  }
+
+  if (!empty($group->mode)) {
+    $query->condition('mode', $group->mode);
+  }
+
+  $query->execute();
+
+  cache_clear_all('field_groups', 'cache_field');
+  module_invoke_all('field_group_delete_field_group', $group);
+
+}
+
+/**
+ * field_group_group_save().
+ *
+ * Saves a group definition.
+ * This function is called by ctools export when calls are made
+ * through ctools_export_crud_save().
+ *
+ * @param $group
+ *   A group definition.
+ */
+function field_group_group_save(& $group) {
+
+  // Prepare the record.
+  $object = field_group_pack($group);
+
+  if (isset($object->export_type) && $object->export_type & EXPORT_IN_DATABASE) {
+    // Existing record.
+    $update = array('id');
+    module_invoke_all('field_group_update_field_group', $object);
+  }
+  else {
+    // New record.
+    $update = array();
+    $object->export_type = EXPORT_IN_DATABASE;
+    module_invoke_all('field_group_create_field_group', $object);
+  }
+
+  return drupal_write_record('field_group', $object, $update);
+
+}
+
+/**
+ * Function to retrieve all format possibilities for the fieldgroups.
+ */
+function field_group_formatter_info($display_overview = FALSE) {
+  $cache = &drupal_static(__FUNCTION__, array());
+  if (empty($cache)) {
+    if ($cached = cache_get('field_group_formatter_info', 'cache_field')) {
+      $formatters = $cached->data;
+    }
+    else {
+      $formatters = array();
+      $formatters += module_invoke_all('field_group_formatter_info');
+      $hidden_region = array(
+        'label' => '<' . t('Hidden') . '>',
+        'description' => '',
+        'format_types' => array(),
+        'instance_settings' => array(),
+        'default_formatter' => '',
+      );
+      //$formatters['form']['hidden'] = $hidden_region;
+      $formatters['display']['hidden'] = $hidden_region;
+      cache_set('field_group_formatter_info', $formatters, 'cache_field');
+    }
+    $cache = $formatters;
+  }
+  return $cache;
+}
+
+/**
+ * Attach groups to the (form) build.
+ *
+ * @param Array $element
+ *   The part of the form.
+ * @param String $view_mode
+ *   The mode for the build.
+ * @param Array $form_state
+ *   The optional form state when in view_mode = form context.
+ */
+function field_group_attach_groups(&$element, $view_mode, $form_state = array()) {
+
+  $entity_type = $element['#entity_type'];
+  $bundle = $element['#bundle'];
+
+  $element['#groups'] = field_group_info_groups($entity_type, $bundle, $view_mode);
+  $element['#fieldgroups'] = $element['#groups'];
+
+  // Create a lookup array.
+  $group_children = array();
+  foreach ($element['#groups'] as $group_name => $group) {
+    foreach ($group->children as $child) {
+      $group_children[$child] = $group_name;
+    }
+  }
+  $element['#group_children'] = $group_children;
+
+}
+
+/**
+ * Pre render callback for rendering groups.
+ * @see field_group_field_attach_form
+ * @param $element Form that is beïng rendered.
+ */
+function field_group_form_pre_render(&$element) {
+  return field_group_build_entity_groups($element, 'form');
+}
+
+/**
+ * Preprocess/ Pre-render callback.
+ *
+ * @see field_group_form_pre_render()
+ * @see field_group_theme_registry_alter
+ * @see field_group_fields_nest()
+ * @param $vars preprocess vars or form element
+ * @param $type The type of object beïng rendered
+ * @return $element Array with re-arranged fields in forms.
+ */
+function field_group_build_entity_groups(&$vars, $type) {
+
+  if ($type == 'form') {
+    $element = &$vars;
+    $nest_vars = NULL;
+  }
+  else {
+    $element = &$vars['elements'];
+    $nest_vars = &$vars;
+  }
+
+  // No groups on the entity.
+  if (empty($element['#fieldgroups'])) {
+    return $element;
+  }
+
+  // Nest the fields in the corresponding field groups.
+  field_group_fields_nest($element, $nest_vars);
+
+  // Allow others to alter the pre_rendered build.
+  drupal_alter('field_group_build_pre_render', $element);
+
+  // Return the element on forms.
+  if ($type == 'form') {
+    return $element;
+  }
+
+  // Put groups inside content if we are rendering an entity_view.
+  foreach ($element['#groups'] as $group) {
+    if (!empty($element[$group->group_name]) && $type != 'user_profile') {
+      $vars['content'][$group->group_name] = $element[$group->group_name];
+    }
+    elseif (!empty($element[$group->group_name])) {
+      $vars['user_profile'][$group->group_name] = $element[$group->group_name];
+    }
+  }
+
+  // New css / js can be attached.
+  drupal_process_attached($element);
+}
+
+/**
+ * Recursive function to nest fields in the field groups.
+ *
+ * This function will take out all the elements in the form and
+ * place them in the correct container element, a fieldgroup.
+ * The current group element in the loop is passed recursively so we can
+ * stash fields and groups in it while we go deeper in the array.
+ * @param Array $element
+ *   The current element to analyse for grouping.
+ * @param Array $vars
+ *   Rendering vars from the entity beïng viewed.
+ */
+function field_group_fields_nest(&$element, &$vars = NULL) {
+
+  // Create all groups and keep a flat list of references to these groups.
+  $group_references = array();
+  foreach ($element['#fieldgroups'] as $group_name => $group) {
+    // Construct own weight, as some fields (for example preprocess fields) don't have weight set.
+    $element[$group_name] = array();
+    $group_references[$group_name] = &$element[$group_name];
+  }
+
+  // Loop through all form children looking for those that are supposed to be
+  // in groups, and insert placeholder element for the new group field in the
+  // correct location within the form structure.
+  $element_clone = array();
+  foreach (element_children($element) as $child_name) {
+    $element_clone[$child_name] = $element[$child_name];
+    // If this element is in a group, create the placeholder element.
+    if (isset($element['#group_children'][$child_name])) {
+      $element_clone[$element['#group_children'][$child_name]] = array();
+    }
+  }
+  $element = array_merge($element_clone, $element);
+
+  // Move all children to their parents. Use the flat list of references for
+  // direct access as we don't know where in the root_element hierarchy the
+  // parent currently is situated.
+  foreach ($element['#group_children'] as $child_name => $parent_name) {
+
+    // Entity being viewed
+    if ($vars) {
+      // If not a group, check vars['content'] for empty field.
+      if (!isset($element['#groups'][$child_name]) && isset($vars['content'][$child_name])) {
+        $group_references[$parent_name][$child_name] = $vars['content'][$child_name];
+        unset($vars['content'][$child_name]);
+      }
+      elseif (!isset($element['#groups'][$child_name]) && isset($vars['user_profile'][$child_name])) {
+        $group_references[$parent_name][$child_name] = $vars['user_profile'][$child_name];
+        unset($vars['user_profile'][$child_name]);
+      }
+      // If this is a group, we have to use a reference to keep the reference
+      // list intact (but if it is a field we don't mind).
+      else {
+        $group_references[$parent_name][$child_name] = &$element[$child_name];
+        unset($element[$child_name]);
+      }
+    }
+    // Form being viewed
+    else {
+
+      // Block denied fields (#access) before they are put in groups.
+      // Fields (not groups) that don't have children (like field_permissions) are removed
+      // in field_group_field_group_build_pre_render_alter.
+      if (isset($element[$child_name]) && (!isset($element[$child_name]['#access']) || $element[$child_name]['#access'])) {
+        // If this is a group, we have to use a reference to keep the reference
+        // list intact (but if it is a field we don't mind).
+        $group_references[$parent_name][$child_name] = &$element[$child_name];
+        $group_references[$parent_name]['#weight'] = $element['#fieldgroups'][$parent_name]->weight;
+      }
+
+      // The child has been copied to its parent: remove it from the root element.
+      unset($element[$child_name]);
+    }
+
+  }
+
+  // Bring extra element wrappers to achieve a grouping of fields.
+  // This will mainly be prefix and suffix altering.
+  foreach ($element['#fieldgroups'] as $group_name => $group) {
+    field_group_pre_render($group_references[$group_name], $group, $element);
+  }
+
+}
+
+/**
+ * Function to pre render the field group element.
+ *
+ * @see field_group_fields_nest()
+ *
+ * @param $element Array of group element that needs to be created!
+ * @param $group Object with the group information.
+ * @param $form The form object itself.
+ */
+function field_group_pre_render(& $element, $group, & $form) {
+
+  // Only run the pre_render function if the group has elements.
+  // $group->group_name
+  if ($element == array()) {
+    return;
+  }
+
+  // Let modules define their wrapping element.
+  // Note that the group element has no properties, only elements.
+  foreach (module_implements('field_group_pre_render') as $module) {
+    $function = $module . '_field_group_pre_render';
+    if (function_exists($function)) {
+      // The intention here is to have the opportunity to alter the
+      // elements, as defined in hook_field_group_formatter_info.
+      // Note, implement $element by reference!
+      $function($element, $group, $form);
+    }
+  }
+
+  // Allow others to alter the pre_render.
+  drupal_alter('field_group_pre_render', $element, $group, $form);
+
+}
+
+/**
+ * Hides field groups including children in a render array.
+ *
+ * @param array $element
+ *   A render array. Can be a form, node, user, ...
+ * @param array $group_names
+ *   An array of field group names that should be hidden.
+ */
+function field_group_hide_field_groups(&$element, $group_names) {
+  foreach ($group_names as $group_name) {
+    if (isset($element['#fieldgroups'][$group_name]) && isset($element['#group_children'])) {
+      // Hide the field group.
+      $element['#fieldgroups'][$group_name]->format_type = 'hidden';
+      // Hide the elements inside the field group.
+      $sub_groups = array();
+      foreach (array_keys($element['#group_children'], $group_name) as $field_name) {
+        if (isset($element['#fieldgroups'][$field_name])) {
+          $sub_groups[] = $field_name;
+        } else {
+          $element[$field_name]['#access'] = FALSE;
+        }
+      }
+      field_group_hide_field_groups($element, $sub_groups);
+    }
+  }
+}
+
+/**
+ * Calculates html classes for a group.
+ */
+function _field_group_get_html_classes(&$group) {
+
+  if (isset($group->format_settings['formatter'])) {
+    $group->collapsible = in_array($group->format_settings['formatter'], array('collapsible', 'collapsed'));
+    // Open or closed horizontal or vertical tabs will be collapsible by default.
+    if ($group->format_type == 'tab' || $group->format_type == 'htab') {
+      $group->collapsible = TRUE;
+    }
+    $group->collapsed = in_array($group->format_settings['formatter'], array('collapsed', 'closed'));
+  }
+
+  $classes = new stdClass();
+
+  // Prepare extra classes, required and optional ones.
+  $optional = array(str_replace('_', '-', $group->group_name));
+  $required = array();
+  if ($group->format_type == 'multipage') {
+    $required[] = 'field-group-' . $group->format_type;
+  }
+  else {
+    $optional[] = 'field-group-' . $group->format_type;
+  }
+
+  if (isset($group->format_settings['formatter']) && $group->collapsible) {
+    $required[] = 'collapsible';
+    if ($group->collapsed) {
+      $required[] = 'collapsed';
+    }
+  }
+
+  if (isset($group->format_settings['instance_settings'])) {
+
+    // Add a required-fields class to trigger the js.
+    if (!empty($group->format_settings['instance_settings']['required_fields'])) {
+      $required[] = 'required-fields';
+    }
+
+    // Add user selected classes.
+    if (!empty($group->format_settings['instance_settings']['classes'])) {
+      $required[] = check_plain($group->format_settings['instance_settings']['classes']);
+    }
+
+    // Extra required classes for div.
+    if ($group->format_type == 'div') {
+      if ($group->format_settings['formatter'] != 'open') {
+
+        $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none';
+        $required[] = 'speed-' . $speed;
+
+        $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none';
+        $required[] = 'effect-' . $effect;
+      }
+    }
+
+    // Extra required classes for accordions.
+    elseif ($group->format_type == 'accordion') {
+      $required[] = 'field-group-' . $group->format_type . '-wrapper';
+      $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none';
+      $required[] = 'effect-' . $effect;
+    }
+
+  }
+
+  $classes->required = $required;
+  $classes->optional = $optional;
+
+  return $classes;
+}
+
+/**
+ * Get the default formatter settings for a given formatter and a mode.
+ */
+function _field_group_get_default_formatter_settings($format_type, $mode) {
+
+  $field_group_types = field_group_formatter_info();
+  $display_mode = $mode == 'form' ? 'form' : 'display';
+  $formatter = $field_group_types[$display_mode][$format_type];
+
+  return array(
+    'formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : '',
+    'instance_settings' => $formatter['instance_settings']
+  );
+}
+

+ 15 - 0
sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs-rtl.css

@@ -0,0 +1,15 @@
+div.horizontal-tabs {
+  margin: 0 0 1em 0;
+}
+
+.horizontal-tabs ul.horizontal-tabs-list {
+  border-right: 0;
+  border-left: 1px solid #dedede;
+}
+
+/* Layout of each tab */
+.horizontal-tabs ul.horizontal-tabs-list li {
+  border-right: 0;
+  border-left: 1px solid #ccc;
+  float: right;
+}

+ 101 - 0
sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.css

@@ -0,0 +1,101 @@
+div.horizontal-tabs {
+  margin: 0 0 1em 0; /* LTR */
+  padding: 0;
+  border: 1px solid #ccc;
+  position: relative; /* IE6/7 */
+}
+
+.horizontal-tabs ul.horizontal-tabs-list {
+  display: inline-block;
+  margin: 0;
+  border: 0;
+  padding: 0px;
+  position: relative; /* IE6 */
+  list-style: none;
+  list-style-image: none; /* IE6 */
+  background-color: #dedede;
+  border-right: 1px solid #dedede; /* LTR */
+  width: 100%;
+  height: auto;
+  clear: both;
+}
+
+.horizontal-tabs fieldset.horizontal-tabs-pane {
+  padding: 0 1em;
+  border: 0;
+}
+
+fieldset.horizontal-tabs-pane > legend,
+fieldset.vertical-tabs-pane fieldset.horizontal-tabs-pane > legend {
+  display: none;
+}
+
+/* Layout of each tab */
+.horizontal-tabs ul.horizontal-tabs-list li {
+  background: #eee;
+  border-right: 1px solid #ccc; /* LTR */
+  padding: 1px;
+  padding-top: 0;
+  margin: 0;
+  min-width: 5em; /* IE7 */
+  float: left; /* LTR */
+}
+.horizontal-tabs ul.horizontal-tabs-list li.selected {
+  background-color: #fff;
+  padding: 0 0 1px 0;
+}
+.horizontal-tabs ul.horizontal-tabs-list li a {
+  display: block;
+  text-decoration: none;
+  padding: 0.5em 0.6em;
+}
+.horizontal-tabs ul.horizontal-tabs-list li a:hover {
+  outline: none;
+  background-color: #ededdd;
+}
+.horizontal-tabs ul.horizontal-tabs-list li:hover,
+.horizontal-tabs ul.horizontal-tabs-list li:focus {
+  background-color: #ddd;
+}
+.horizontal-tabs ul.horizontal-tabs-list :focus {
+  outline: none;
+}
+.horizontal-tabs ul.horizontal-tabs-list li a:focus strong,
+.horizontal-tabs ul.horizontal-tabs-list li a:active strong,
+.horizontal-tabs ul.horizontal-tabs-list li a:hover strong {
+  text-decoration: none;
+  outline: none;
+}
+.horizontal-tabs ul.horizontal-tabs-list li a,
+.horizontal-tabs ul.horizontal-tabs-list li.selected a {
+  display: block;
+  text-decoration: none;
+  padding: 0.5em 0.6em 0.3em 0.6em;
+  position:relative;
+  top: 0px;
+}
+.horizontal-tabs ul.horizontal-tabs-list .selected strong {
+  color: #000;
+}
+.horizontal-tabs ul.horizontal-tabs-list .summary {
+  display: block;
+}
+.horizontal-tabs ul.horizontal-tabs ul.horizontal-tabs-list .summary {
+  line-height: normal;
+  margin-bottom: 0;
+}
+
+/**
+ * tab content
+ */
+div.field-group-htabs-wrapper .field-group-format-wrapper {
+  clear: both;
+  padding: 0 0 0.6em;
+}
+/*hide*/
+.horizontal-tabs .horizontal-tab-hidden {
+  display: block;
+  position: absolute;
+  top: -100000px;
+  width: 100%;
+}

+ 203 - 0
sites/all/modules/contrib/fields/field_group/horizontal-tabs/horizontal-tabs.js

@@ -0,0 +1,203 @@
+(function ($) {
+
+/**
+ * This script transforms a set of fieldsets into a stack of horizontal
+ * tabs. Another tab pane can be selected by clicking on the respective
+ * tab.
+ *
+ * Each tab may have a summary which can be updated by another
+ * script. For that to work, each fieldset has an associated
+ * 'horizontalTabCallback' (with jQuery.data() attached to the fieldset),
+ * which is called every time the user performs an update to a form
+ * element inside the tab pane.
+ */
+Drupal.behaviors.horizontalTabs = {
+  attach: function (context) {
+    $('.horizontal-tabs-panes', context).once('horizontal-tabs', function () {
+      var focusID = $(':hidden.horizontal-tabs-active-tab', this).val();
+      var tab_focus;
+
+      // Check if there are some fieldsets that can be converted to horizontal-tabs
+      var $fieldsets = $('> fieldset', this);
+      if ($fieldsets.length == 0) {
+        return;
+      }
+
+      // Create the tab column.
+      var tab_list = $('<ul class="horizontal-tabs-list"></ul>');
+      $(this).wrap('<div class="horizontal-tabs clearfix"></div>').before(tab_list);
+
+      // Transform each fieldset into a tab.
+      $fieldsets.each(function (i) {
+        var horizontal_tab = new Drupal.horizontalTab({
+          title: $('> legend', this).text(),
+          fieldset: $(this)
+        });
+        horizontal_tab.item.addClass('horizontal-tab-button-' + i);
+        tab_list.append(horizontal_tab.item);
+        $(this)
+          .removeClass('collapsible collapsed')
+          .addClass('horizontal-tabs-pane')
+          .data('horizontalTab', horizontal_tab);
+        if (this.id == focusID) {
+          tab_focus = $(this);
+        }
+      });
+
+      $('> li:first', tab_list).addClass('first');
+      $('> li:last', tab_list).addClass('last');
+
+      if (!tab_focus) {
+        // If the current URL has a fragment and one of the tabs contains an
+        // element that matches the URL fragment, activate that tab.
+        if (window.location.hash && window.location.hash !== '#' && $(window.location.hash, this).length) {
+          tab_focus = $(window.location.hash, this).closest('.horizontal-tabs-pane');
+        }
+        else {
+          tab_focus = $('> .horizontal-tabs-pane:first', this);
+        }
+      }
+      if (tab_focus.length) {
+        tab_focus.data('horizontalTab').focus();
+      }
+    });
+  }
+};
+
+/**
+ * The horizontal tab object represents a single tab within a tab group.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - title: The name of the tab.
+ *   - fieldset: The jQuery object of the fieldset that is the tab pane.
+ */
+Drupal.horizontalTab = function (settings) {
+  var self = this;
+  $.extend(this, settings, Drupal.theme('horizontalTab', settings));
+
+  this.link.click(function () {
+    self.focus();
+    return false;
+  });
+
+  // Keyboard events added:
+  // Pressing the Enter key will open the tab pane.
+  this.link.keydown(function(event) {
+    if (event.keyCode == 13) {
+      self.focus();
+      // Set focus on the first input field of the visible fieldset/tab pane.
+      $("fieldset.horizontal-tabs-pane :input:visible:enabled:first").focus();
+      return false;
+    }
+  });
+
+  // Only bind update summary on forms.
+  if (this.fieldset.drupalGetSummary) {
+    this.fieldset.bind('summaryUpdated', function() {
+      self.updateSummary();
+    }).trigger('summaryUpdated');
+  }
+
+};
+
+Drupal.horizontalTab.prototype = {
+  /**
+   * Displays the tab's content pane.
+   */
+  focus: function () {
+    this.fieldset
+      .removeClass('horizontal-tab-hidden')
+      .siblings('fieldset.horizontal-tabs-pane')
+        .each(function () {
+          var tab = $(this).data('horizontalTab');
+          tab.fieldset.addClass('horizontal-tab-hidden');
+          tab.item.removeClass('selected');
+        })
+        .end()
+      .siblings(':hidden.horizontal-tabs-active-tab')
+        .val(this.fieldset.attr('id'));
+    this.item.addClass('selected');
+    // Mark the active tab for screen readers.
+    $('#active-horizontal-tab').remove();
+    this.link.append('<span id="active-horizontal-tab" class="element-invisible">' + Drupal.t('(active tab)') + '</span>');
+  },
+
+  /**
+   * Updates the tab's summary.
+   */
+  updateSummary: function () {
+    this.summary.html(this.fieldset.drupalGetSummary());
+  },
+
+  /**
+   * Shows a horizontal tab pane.
+   */
+  tabShow: function () {
+    // Display the tab.
+    this.item.removeClass('horizontal-tab-hidden');
+    // Update .first marker for items. We need recurse from parent to retain the
+    // actual DOM element order as jQuery implements sortOrder, but not as public
+    // method.
+    this.item.parent().children('.horizontal-tab-button').removeClass('first')
+      .filter(':visible:first').addClass('first');
+    // Display the fieldset.
+    this.fieldset.removeClass('horizontal-tab-hidden');
+    // Focus this tab.
+    this.focus();
+    return this;
+  },
+
+  /**
+   * Hides a horizontal tab pane.
+   */
+  tabHide: function () {
+    // Hide this tab.
+    this.item.addClass('horizontal-tab-hidden');
+    // Update .first marker for items. We need recurse from parent to retain the
+    // actual DOM element order as jQuery implements sortOrder, but not as public
+    // method.
+    this.item.parent().children('.horizontal-tab-button').removeClass('first')
+      .filter(':visible:first').addClass('first');
+    // Hide the fieldset.
+    this.fieldset.addClass('horizontal-tab-hidden');
+    // Focus the first visible tab (if there is one).
+    var $firstTab = this.fieldset.siblings('.horizontal-tabs-pane:not(.horizontal-tab-hidden):first');
+    if ($firstTab.length) {
+      $firstTab.data('horizontalTab').focus();
+    }
+    return this;
+  }
+};
+
+/**
+ * Theme function for a horizontal tab.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - title: The name of the tab.
+ * @return
+ *   This function has to return an object with at least these keys:
+ *   - item: The root tab jQuery element
+ *   - link: The anchor tag that acts as the clickable area of the tab
+ *       (jQuery version)
+ *   - summary: The jQuery element that contains the tab summary
+ */
+Drupal.theme.prototype.horizontalTab = function (settings) {
+  var tab = {};
+  var idAttr = settings.fieldset.attr('id');
+
+  tab.item = $('<li class="horizontal-tab-button" tabindex="-1"></li>')
+    .append(tab.link = $('<a href="#' + idAttr + '"></a>')
+    .append(tab.title = $('<strong></strong>').text(settings.title))
+    );
+
+  // No need to add summary on frontend.
+  if (settings.fieldset.drupalGetSummary) {
+    tab.link.append(tab.summary = $('<span class="summary"></span>'))
+    }
+
+  return tab;
+};
+
+})(jQuery);

+ 13 - 0
sites/all/modules/contrib/fields/field_group/multipage/multipage-rtl.css

@@ -0,0 +1,13 @@
+.multipage-controls-list #edit-actions {
+  float: right !important;
+}
+
+.multipage-button {    
+  float: right !important;
+}
+
+.multipage-counter{
+  float: left !important;
+  margin-right: 0 !important;
+  margin-left: 5px !important;
+}

+ 128 - 0
sites/all/modules/contrib/fields/field_group/multipage/multipage.css

@@ -0,0 +1,128 @@
+.multipage-controls-list #edit-actions {
+  float: left; /* LTR */
+}
+
+.multipage-button {    
+  margin-bottom: 1em;
+  margin-top: 0;
+  float: left; /* LTR */
+  line-height: 36px;
+}
+
+.multipage-button a {    
+  padding-top: 10px;
+}
+
+.multipage-counter {
+  float: right; /* LTR */
+  margin-right: 5px; /* LTR */
+  height: 0;
+  position: relative;
+  top: 1.8em;
+  line-height: 30px;
+  font: 12px arial,sans-serif;
+  font-weight: bold;
+  color:#666;
+}
+
+a.multipage-link-previous {
+  font: 12px arial,sans-serif;
+  font-weight: bold;
+  color:#666;
+  -webkit-transition: color 218ms;
+  -moz-transition: color 218ms;
+  -o-transition: color 218ms;
+  transition: color 218ms;
+}
+
+a.multipage-link-previous:hover {
+  text-decoration:none;
+  color: #333;
+}
+
+.multipage-controls-list input.form-submit {
+  background:none;
+  border: none;
+  border-radius: 2px;
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  font: 12px arial,sans-serif;
+  font-weight: bold;
+  color: #666;
+  text-shadow: 0 1px 0 white;
+  padding: 7px 12px;
+  background: -webkit-gradient(linear,0% 40%,0% 70%,from(whiteSmoke),to(#F1F1F1));
+  -o-transition: border-top-color 0.218s,border-right-color 0.218s,border-bottom-color 0.218s,border-left-color .218s;
+  -webkit-transition: border-color .218s;
+}
+
+.multipage-controls-list input.form-submit:hover {
+  color:#333;
+  box-shadow: 0 1px 1px rgba(0,0,0,0.1);
+  -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1);
+  border-color: #939393;
+}
+
+.multipage-controls-list input.form-submit:active {
+  background: -webkit-gradient(linear,0% 40%,0% 70%,from(#F1F1F1),to(whiteSmoke));
+}
+
+.multipage-controls-list input#edit-submit {
+  background: #4D90FE; /* for non-css3 browsers */
+  background-image: #4D90FE; /* for non-css3 browsers */
+  background-image: -o-linear-gradient(top,#4d90fe,#4787ed);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4D90FE', endColorstr='#4787ED'); /* for IE */
+  background: -webkit-gradient(linear, center top, center bottom, from(#4D90FE), to(#4787ED)); /* for webkit browsers */
+  background: -moz-linear-gradient(center top,  #4D90FE,  #4787ED); /* for firefox 3.6+ */ 
+  color: white;
+  text-shadow: none;
+  text-transform: uppercase;
+  min-width: 79px;
+}
+
+.multipage-controls-list input#edit-submit:hover {
+  background-image: -moz-linear-gradient(top,#4d90fe,#357ae8);
+  background-image: -o-linear-gradient(top,#4d90fe,#357ae8);
+  background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#357ae8));
+  color: white;
+  text-shadow: none;
+  box-shadow: 0 1px 1px rgba(0,0,0,0.2);
+  -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.2);
+}
+
+.multipage-controls-list input#edit-submit:active {
+  background: #4D90FE;
+  border-color: #2F5BB7;
+}
+
+.multipage-controls-list input#edit-delete {
+  background-image: -moz-linear-gradient(top,#dd4b39,#d14836);
+  background-image: -o-linear-gradient(top,#dd4b39,#d14836);
+  background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836));
+  text-shadow: 0 1px rgba(0, 0, 0, 0.1);
+  border: 1px solid transparent;
+  color: white;
+  text-shadow: none;
+
+}
+
+.multipage-controls-list input#edit-delete:hover {
+  background-image: -moz-linear-gradient(top,#dd4b39,#c53727);
+  background-image: -o-linear-gradient(top,#dd4b39,#c53727);
+  background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#c53727));
+  border: 1px solid #B0281A!important;
+  border-bottom: 1px solid #AF301F!important;
+  box-shadow: 0 1px 1px rgba(0,0,0,0.2);
+  -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.2);
+  color: white;
+}
+
+.multipage-controls-list input#edit-delete:active {
+  background-image: -moz-linear-gradient(top,#dd4b39,#b0281a);
+  background-image: -o-linear-gradient(top,#dd4b39,#b0281a);
+  background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#b0281a));
+  border: 1px solid #992A1b!important;
+  box-shadow: 0 1px 2px rgba(0,0,0,0.3);
+  -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.3);
+}

+ 268 - 0
sites/all/modules/contrib/fields/field_group/multipage/multipage.js

@@ -0,0 +1,268 @@
+(function ($) {
+
+/**
+ * This script transforms a set of wrappers into a stack of multipage pages. 
+ * Another pane can be entered by clicking next/previous.
+ *
+ */
+Drupal.behaviors.MultiPage = {
+  attach: function (context) {
+    $('.multipage-panes', context).once('multipage', function () {
+
+      var focusID = $(':hidden.multipage-active-control', this).val();
+      var paneWithFocus;
+
+      // Check if there are some wrappers that can be converted to multipages.
+      var $panes = $('> div.field-group-multipage', this);
+      var $form = $panes.parents('form');
+      if ($panes.length == 0) {
+        return;
+      }
+
+      // Create the next/previous controls.
+      var $controls;
+
+      // Transform each div.multipage-pane into a multipage with controls.
+      $panes.each(function () {
+        
+        $controls = $('<div class="multipage-controls-list clearfix"></div>');
+        $(this).append($controls);
+        
+        // Check if the submit button needs to move to the latest pane.
+        if (Drupal.settings.field_group.multipage_move_submit && $('.form-actions').length) {
+          $('.form-actions', $form).remove().appendTo($($controls, $panes.last()));
+        }
+        
+        var multipageControl = new Drupal.multipageControl({
+          title: $('> .multipage-pane-title', this).text(),
+          wrapper: $(this),
+          has_next: $(this).next().length,
+          has_previous: $(this).prev().length
+        });
+        
+        $controls.append(multipageControl.item);
+        $(this)
+          .addClass('multipage-pane')
+          .data('multipageControl', multipageControl);
+
+        if (this.id == focusID) {
+          paneWithFocus = $(this);
+        }
+        
+      });
+
+      if (paneWithFocus === undefined) {
+        // If the current URL has a fragment and one of the tabs contains an
+        // element that matches the URL fragment, activate that tab.
+        if (window.location.hash && window.location.hash !== '#' && $(window.location.hash, this).length) {
+          paneWithFocus = $(window.location.hash, this).closest('.multipage-pane');
+        }
+        else {
+          paneWithFocus = $('multipage-open', this).length ? $('multipage-open', this) : $('> .multipage-pane:first', this);
+        }
+      }
+      if (paneWithFocus !== undefined) {
+        paneWithFocus.data('multipageControl').focus();
+      }
+    });
+  }
+};
+
+/**
+ * The multipagePane object represents a single div as a page.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - title: The name of the tab.
+ *   - wrapper: The jQuery object of the <div> that is the tab pane.
+ */
+Drupal.multipageControl = function (settings) {
+  var self = this;
+  var controls = Drupal.theme('multipage', settings);
+  $.extend(self, settings, controls);
+
+  this.nextLink.click(function () {
+    self.nextPage();
+    return false;
+  });
+  
+  this.previousLink.click(function () {
+    self.previousPage();
+    return false;
+  });
+  
+/*
+  // Keyboard events added:
+  // Pressing the Enter key will open the tab pane.
+  this.nextLink.keydown(function(event) {
+    if (event.keyCode == 13) {
+      self.focus();
+      // Set focus on the first input field of the visible wrapper/tab pane.
+      $("div.multipage-pane :input:visible:enabled:first").focus();
+      return false;
+    }
+  });
+
+  // Pressing the Enter key lets you leave the tab again.
+  this.wrapper.keydown(function(event) {
+    // Enter key should not trigger inside <textarea> to allow for multi-line entries.
+    if (event.keyCode == 13 && event.target.nodeName != "TEXTAREA") {
+      // Set focus on the selected tab button again.
+      $(".multipage-tab-button.selected a").focus();
+      return false;
+    }
+  });
+*/
+};
+
+Drupal.multipageControl.prototype = {
+    
+  /**
+   * Displays the tab's content pane.
+   */
+  focus: function () {
+    this.wrapper
+      .show()
+      .siblings('div.multipage-pane')
+        .each(function () {
+          var tab = $(this).data('multipageControl');
+          tab.wrapper.hide();
+        })
+        .end()
+      .siblings(':hidden.multipage-active-control')
+        .val(this.wrapper.attr('id'));
+    // Mark the active control for screen readers.
+    $('#active-multipage-control').remove();
+    this.nextLink.after('<span id="active-multipage-control" class="element-invisible">' + Drupal.t('(active page)') + '</span>');
+  },
+  
+  /**
+   * Continues to the next page or step in the form.
+   */
+  nextPage: function () {
+    this.wrapper.next().data('multipageControl').focus();
+    $('html, body').scrollTop(this.wrapper.parents('.field-group-multipage-group-wrapper').offset().top);
+  },
+  
+  /**
+   * Returns to the previous page or step in the form.
+   */
+  previousPage: function () {
+    this.wrapper.prev().data('multipageControl').focus();
+    $('html, body').scrollTop(this.wrapper.parents('.field-group-multipage-group-wrapper').offset().top);
+  },
+
+  /**
+   * Shows a horizontal tab pane.
+   */
+  tabShow: function () {
+    // Display the tab.
+    this.item.show();
+    // Update .first marker for items. We need recurse from parent to retain the
+    // actual DOM element order as jQuery implements sortOrder, but not as public
+    // method.
+    this.item.parent().children('.multipage-control').removeClass('first')
+      .filter(':visible:first').addClass('first');
+    // Display the wrapper.
+    this.wrapper.removeClass('multipage-control-hidden').show();
+    // Focus this tab.
+    this.focus();
+    return this;
+  },
+
+  /**
+   * Hides a horizontal tab pane.
+   */
+  tabHide: function () {
+    // Hide this tab.
+    this.item.hide();
+    // Update .first marker for items. We need recurse from parent to retain the
+    // actual DOM element order as jQuery implements sortOrder, but not as public
+    // method.
+    this.item.parent().children('.multipage-control').removeClass('first')
+      .filter(':visible:first').addClass('first');
+    // Hide the wrapper.
+    this.wrapper.addClass('horizontal-tab-hidden').hide();
+    // Focus the first visible tab (if there is one).
+    var $firstTab = this.wrapper.siblings('.multipage-pane:not(.multipage-control-hidden):first');
+    if ($firstTab.length) {
+      $firstTab.data('multipageControl').focus();
+    }
+    return this;
+  }
+};
+
+/**
+ * Theme function for a multipage control.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - title: The name of the tab.
+ * @return
+ *   This function has to return an object with at least these keys:
+ *   - item: The root tab jQuery element
+ *   - nextLink: The anchor tag that acts as the clickable area of the control
+ *   - nextTitle: The jQuery element that contains the group title
+ *   - previousLink: The anchor tag that acts as the clickable area of the control
+ *   - previousTitle: The jQuery element that contains the group title
+ */
+Drupal.theme.prototype.multipage = function (settings) {
+
+  var controls = {};
+  controls.item = $('<span class="multipage-button"></span>');
+  
+  controls.previousLink = $('<input type="button" class="form-submit multipage-link-previous" value="" />');
+  controls.previousTitle = Drupal.t('Previous page');
+  controls.item.append(controls.previousLink.val(controls.previousTitle));  
+  
+  controls.nextLink = $('<input type="button" class="form-submit multipage-link-next" value="" />');
+  controls.nextTitle = Drupal.t('Next page');
+  controls.item.append(controls.nextLink.val(controls.nextTitle));
+  
+  if (!settings.has_next) {
+    controls.nextLink.hide();
+  }
+  if (!settings.has_previous) {
+    controls.previousLink.hide();
+  }
+  
+  return controls;
+};
+
+
+Drupal.FieldGroup = Drupal.FieldGroup || {};
+Drupal.FieldGroup.Effects = Drupal.FieldGroup.Effects || {};
+
+/**
+ * Implements Drupal.FieldGroup.processHook().
+ */
+Drupal.FieldGroup.Effects.processMultipage = {
+  execute: function (context, settings, type) {
+    if (type == 'form') {
+      
+      var $firstErrorItem = false;
+      
+      // Add required fields mark to any element containing required fields
+      $('div.multipage-pane').each(function(i){
+        if ($('.error', $(this)).length) {
+          
+          // Save first error item, for focussing it.
+          if (!$firstErrorItem) {
+            $firstErrorItem = $(this).data('multipageControl');
+          }          
+          
+          Drupal.FieldGroup.setGroupWithfocus($(this));
+          $(this).data('multipageControl').focus();
+        }
+      });
+
+      // Focus on first multipage that has an error.
+      if ($firstErrorItem) {
+        $firstErrorItem.focus();
+      }
+      
+    }
+  }
+}
+
+})(jQuery);

+ 416 - 0
sites/all/modules/contrib/fields/field_group/tests/field_group.display.test

@@ -0,0 +1,416 @@
+<?php
+
+/**
+ * @file
+ * Test file for fieldgroup display.
+ */
+
+/**
+ * Group display tests
+ */
+class GroupDisplayTestCase extends DrupalWebTestCase {
+
+  protected $node;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Display tests',
+      'description' => 'Test the field group display.',
+      'group' => 'Field group',
+    );
+  }
+
+  function setUp() {
+
+    parent::setUp('field_test', 'field_group', 'field_group_test');
+
+    $node = new stdClass();
+    $node->type = 'article';
+    $node->title = $this->randomName();
+    $node->status = 1;
+
+    // Create test fields.
+    $test_fields = array('field_test', 'field_test_2', 'field_no_access');
+    foreach ($test_fields as $field_name) {
+
+      $field = array(
+        'field_name' => $field_name,
+        'type' => 'test_field',
+        'cardinality' => 1,
+      );
+      $instance = array(
+        'field_name' => $field_name,
+        'entity_type' => 'node',
+        'bundle' => 'article',
+        'label' => $this->randomName(),
+        'display' => array(
+          'default' => array(
+            'type' => 'field_test_default',
+            'settings' => array(
+              'test_formatter_setting' => $this->randomName(),
+            ),
+          ),
+          'teaser' => array(
+            'type' => 'field_test_default',
+            'settings' => array(
+              'test_formatter_setting' => $this->randomName(),
+            ),
+          ),
+        ),
+      );
+      field_create_field($field);
+      field_create_instance($instance);
+
+      $node->{$field_name}[LANGUAGE_NONE][0]['value'] = mt_rand(1, 127);
+    }
+
+    node_save($node);
+    $this->node = $node;
+  }
+
+  /**
+   * Create a new group.
+   * @param array $data
+   *   Data for the field group.
+   */
+  function createGroup($mode, array $data) {
+
+    $group_name = 'group_' . drupal_strtolower($this->randomName(8));
+    $identifier = $group_name . '|node|article|' . $mode;
+
+    $field_group = new stdClass;
+    $field_group->disabled = FALSE;
+    $field_group->api_version = 1;
+    $field_group->identifier = $identifier;
+    $field_group->group_name = $group_name;
+    $field_group->entity_type = 'node';
+    $field_group->bundle = 'article';
+    $field_group->mode = $mode;
+    $field_group->parent_name = '';
+    $field_group->children = $data['children'];
+    $field_group->data = $data;
+    drupal_write_record('field_group', $field_group);
+    ctools_export_crud_enable('field_group', $field_group->identifier);
+
+    return $field_group;
+  }
+
+  /**
+   * Test if an empty  formatter.
+   */
+  function testFieldAccess() {
+
+    $data = array(
+      'label' => 'Wrapper',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_no_access',
+      ),
+      'format_type' => 'div',
+      'format_settings' => array(
+        'label' => 'Link',
+        'instance_settings' => array(
+          'required_fields' => 0,
+          'id' => 'wrapper-id',
+          'classes' => 'test-class',
+          'description' => '',
+          'show_label' => FALSE,
+          'label_element' => 'h3',
+          'effect' => 'blink',
+          'speed' => 'fast',
+        ),
+        'formatter' => 'open',
+      ),
+    );
+    $group = $this->createGroup('default', $data);
+
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+    $this->drupalGet('node/' . $this->node->nid);
+
+    // Test if group is not shown.
+    $this->assertNoFieldByXPath("//div[contains(@id, 'wrapper-id')]", NULL, t('Div that contains fields with no access is not shown.'));
+  }
+
+  /**
+   * Test the div formatter.
+   */
+  function testDiv() {
+
+    $data = array(
+      'label' => 'Wrapper',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test',
+      ),
+      'format_type' => 'div',
+      'format_settings' => array(
+        'label' => 'Link',
+        'instance_settings' => array(
+          'required_fields' => 0,
+          'id' => 'wrapper-id',
+          'classes' => 'test-class',
+          'description' => '',
+          'show_label' => FALSE,
+          'label_element' => 'h3',
+          'effect' => 'blink',
+          'speed' => 'fast',
+        ),
+        'formatter' => 'open',
+      ),
+    );
+    $group = $this->createGroup('default', $data);
+
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+    $this->drupalGet('node/' . $this->node->nid);
+
+    // Test group ids and classes.
+    $this->assertFieldByXPath("//div[contains(@id, 'wrapper-id')]", NULL, t('Wrapper id set on wrapper div'));
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Test class set on wrapper div') . 'class="' . $group->group_name . ' test-class');
+
+    // Test group label.
+    $this->assertNoRaw('<h3><span>' . $data['label'] . '</span></h3>', t('Label is not shown'));
+
+    // Set show label to true.
+    $group->data['format_settings']['instance_settings']['show_label'] = TRUE;
+
+    drupal_write_record('field_group', $group, array('identifier'));
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+    $this->drupalGet('node/' . $this->node->nid);
+    $this->assertRaw('<h3><span>' . $data['label'] . '</span></h3>', t('Label is shown'));
+
+    // Change to collapsible
+    $group->data['format_settings']['formatter'] = 'collapsible';
+    drupal_write_record('field_group', $group, array('identifier'));
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+    $this->drupalGet('node/' . $this->node->nid);
+    $this->assertFieldByXPath("//div[contains(@class, 'speed-fast')]", NULL, t('Speed class is set'));
+    $this->assertFieldByXPath("//div[contains(@class, 'effect-blink')]", NULL, t('Effect class is set'));
+  }
+
+  /**
+   * Test the horizontal tabs formatter.
+   */
+  function testHorizontalTabs() {
+
+    $data = array(
+      'label' => 'Tab 1',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test',
+      ),
+      'format_type' => 'htab',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class',
+          'description' => '',
+        ),
+        'formatter' => 'open',
+      ),
+    );
+    $first_tab = $this->createGroup('default', $data);
+    $first_tab_id = 'node_article_full_' . $first_tab->group_name;
+
+    $data = array(
+      'label' => 'Tab 2',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test_2',
+      ),
+      'format_type' => 'htab',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class-2',
+          'description' => 'description of second tab',
+        ),
+        'formatter' => 'closed',
+      ),
+    );
+    $second_tab = $this->createGroup('default', $data);
+    $second_tab_id = 'node_article_full_' . $first_tab->group_name;
+
+    $data = array(
+      'label' => 'Tabs',
+      'weight' => '1',
+      'children' => array(
+        0 => $first_tab->group_name,
+        1 => $second_tab->group_name,
+      ),
+      'format_type' => 'htabs',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class-wrapper',
+        ),
+      ),
+    );
+    $tabs = $this->createGroup('default', $data);
+
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+
+    $this->drupalGet('node/' . $this->node->nid);
+
+    // Test properties.
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper'));
+    $this->assertFieldByXPath("//fieldset[contains(@class, 'test-class-2')]", NULL, t('Test class set on second tab'));
+    $this->assertRaw('<div class="fieldset-description">description of second tab</div>', t('Description of tab is shown'));
+    $this->assertRaw('class="collapsible collapsed test-class-2', t('Second tab is default collapsed'));
+
+    // Test if correctly nested
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$first_tab_id')]", NULL, 'First tab is displayed as child of the wrapper.');
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$second_tab_id')]", NULL, 'Second tab is displayed as child of the wrapper.');
+
+  }
+
+  /**
+   * Test the vertical tabs formatter.
+   */
+  function testVerticalTabs() {
+
+    $data = array(
+      'label' => 'Tab 1',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test',
+      ),
+      'format_type' => 'tab',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class',
+          'description' => '',
+        ),
+        'formatter' => 'open',
+      ),
+    );
+    $first_tab = $this->createGroup('default', $data);
+    $first_tab_id = 'node_article_full_' . $first_tab->group_name;
+
+    $data = array(
+      'label' => 'Tab 2',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test_2',
+      ),
+      'format_type' => 'tab',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class-2',
+          'description' => 'description of second tab',
+        ),
+        'formatter' => 'closed',
+      ),
+    );
+    $second_tab = $this->createGroup('default', $data);
+    $second_tab_id = 'node_article_full_' . $first_tab->group_name;
+
+    $data = array(
+      'label' => 'Tabs',
+      'weight' => '1',
+      'children' => array(
+        0 => $first_tab->group_name,
+        1 => $second_tab->group_name,
+      ),
+      'format_type' => 'tabs',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class-wrapper',
+        ),
+      ),
+    );
+    $tabs = $this->createGroup('default', $data);
+
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+
+    $this->drupalGet('node/' . $this->node->nid);
+
+    // Test properties.
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper'));
+    $this->assertFieldByXPath("//fieldset[contains(@class, 'test-class-2')]", NULL, t('Test class set on second tab'));
+    $this->assertRaw('<div class="fieldset-description">description of second tab</div>', t('Description of tab is shown'));
+    $this->assertRaw('class="collapsible collapsed test-class-2', t('Second tab is default collapsed'));
+
+    // Test if correctly nested
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$first_tab_id')]", NULL, 'First tab is displayed as child of the wrapper.');
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$second_tab_id')]", NULL, 'Second tab is displayed as child of the wrapper.');
+  }
+
+  /**
+   * Test the accordion formatter.
+   */
+  function testAccordion() {
+
+    $data = array(
+      'label' => 'Accordion item 1',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test',
+      ),
+      'format_type' => 'accordion-item',
+      'format_settings' => array(
+        'label' => 'Accordion item 1',
+        'instance_settings' => array(
+          'classes' => 'test-class',
+        ),
+        'formatter' => 'closed',
+      ),
+    );
+    $first_item = $this->createGroup('default', $data);
+    $first_item_id = 'node_article_full_' . $first_item->group_name;
+
+    $data = array(
+      'label' => 'Accordion item 2',
+      'weight' => '1',
+      'children' => array(
+        0 => 'field_test_2',
+      ),
+      'format_type' => 'accordion-item',
+      'format_settings' => array(
+        'label' => 'Tab 2',
+        'instance_settings' => array(
+          'classes' => 'test-class-2',
+        ),
+        'formatter' => 'open',
+      ),
+    );
+    $second_item = $this->createGroup('default', $data);
+    $second_item_id = 'node_article_full_' . $second_item->group_name;
+
+    $data = array(
+      'label' => 'Accordion',
+      'weight' => '1',
+      'children' => array(
+        0 => $first_item->group_name,
+        1 => $second_item->group_name,
+      ),
+      'format_type' => 'accordion',
+      'format_settings' => array(
+        'label' => 'Tab 1',
+        'instance_settings' => array(
+          'classes' => 'test-class-wrapper',
+          'effect' => 'bounceslide'
+        ),
+      ),
+    );
+    $accordion = $this->createGroup('default', $data);
+
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+
+    $this->drupalGet('node/' . $this->node->nid);
+
+    // Test properties.
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper'));
+    $this->assertFieldByXPath("//div[contains(@class, 'effect-bounceslide')]", NULL, t('Correct effect is set on the accordion'));
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Accordion item with test-class is shown'));
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-2')]", NULL, t('Accordion item with test-class-2 is shown'));
+    $this->assertFieldByXPath("//h3[contains(@class, 'field-group-accordion-active')]", NULL, t('Accordion item 2 was set active'));
+
+    // Test if correctly nested
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class')]", NULL, 'First item is displayed as child of the wrapper.');
+    $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class-2')]", NULL, 'Second item is displayed as child of the wrapper.');
+  }
+
+}

+ 109 - 0
sites/all/modules/contrib/fields/field_group/tests/field_group.ui.test

@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Test file for fieldgroup UI.
+ */
+
+/**
+ * Group UI tests.
+ */
+class GroupUITestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'UI tests',
+      'description' => 'Test the field group UI.',
+      'group' => 'Field group',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_test', 'field_group', 'field_group_test');
+
+    // Create test user.
+    $admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'access administration pages', 'bypass node access'));
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Test the creation a group on the article content type.
+   */
+  function createGroup() {
+
+    // Create random group name.
+    $this->group_label = $this->randomName(8);
+    $this->group_name_input = drupal_strtolower($this->randomName(8));
+    $this->group_name = 'group_' . $this->group_name_input;
+
+    // Setup new group.
+    $group = array(
+      'fields[_add_new_group][label]' => $this->group_label,
+      'fields[_add_new_group][group_name]' => $this->group_name_input,
+    );
+
+    // Add new group on the 'Manage fields' page.
+    $this->drupalPost('admin/structure/types/manage/article/fields', $group, t('Save'));
+
+    $this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.'));
+
+    // Test if group is in the $groups array.
+    $groups = field_group_info_groups('node', 'article', 'form', TRUE);
+    $this->assertTrue(array_key_exists($this->group_name, $groups), t('Group found in groups array'));
+
+    // Add new group on the 'Manage display' page.
+    $this->drupalPost('admin/structure/types/manage/article/display', $group, t('Save'));
+    $this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.'));
+
+    // Test if group is in the $groups array.
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+    $this->assertTrue(array_key_exists($this->group_name, $groups), t('Group found in groups array'));
+  }
+
+  /**
+   * Delete a group.
+   */
+  function deleteGroup() {
+
+    $this->drupalPost('admin/structure/types/manage/article/groups/' . $this->group_name . '/delete/form', array(), t('Delete'));
+    $this->assertRaw(t('The group %label has been deleted from the %article content type.', array('%label' => $this->group_label, '%article' => 'Article')), t('Group removal message displayed on screen.'));
+
+    // Test that group is not in the $groups array.
+    $groups = field_group_info_groups('node', 'article', 'form', TRUE);
+    $this->assertFalse(array_key_exists($this->group_name, $groups), t('Group not found in groups array while deleting'));
+
+    $this->drupalPost('admin/structure/types/manage/article/groups/' . $this->group_name . '/delete/default', array(), t('Delete'));
+    $this->assertRaw(t('The group %label has been deleted from the %article content type.', array('%label' => $this->group_label, '%article' => 'Article')), t('Group removal message displayed on screen.'));
+
+    // Test that group is not in the $groups array.
+    $groups = field_group_info_groups('node', 'article', 'default', TRUE);
+    $this->assertFalse(array_key_exists($this->group_name, $groups), t('Group not found in groups array while deleting'));
+  }
+
+  /**
+   * General CRUD.
+   */
+  function testCRUDGroup() {
+    $this->createGroup();
+    $this->deleteGroup();
+  }
+
+  /**
+   * Nest a field underneath a group.
+   */
+  function testNestField() {
+
+    $this->createGroup();
+
+    $edit = array(
+      'fields[field_image][parent]' => $this->group_name,
+    );
+    $this->drupalPost('admin/structure/types/manage/article/fields', $edit, t('Save'));
+    $this->assertRaw(t('Your settings have been saved.'), t('Settings saved'));
+
+    $groups = field_group_info_groups('node', 'article', 'form', TRUE);
+    $this->assertTrue(in_array('field_image', $groups[$this->group_name]->children), t('Image is a child of %group', array('%group' => $this->group_name)));
+  }
+
+}
+

+ 12 - 0
sites/all/modules/contrib/fields/field_group/tests/field_group_test.info

@@ -0,0 +1,12 @@
+name = "Fieldgroup Test"
+description = "Test module for fieldgroup"
+core = "7.x"
+package = "Fieldgroup"
+
+
+; Information added by drupal.org packaging script on 2013-09-25
+version = "7.x-1.3"
+core = "7.x"
+project = "field_group"
+datestamp = "1380124361"
+

+ 17 - 0
sites/all/modules/contrib/fields/field_group/tests/field_group_test.module

@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Fieldgroup test module.
+ */
+
+/**
+ * Implements hook_field_access().
+ */
+function field_group_test_field_access($op, $field, $entity_type, $entity, $account) {
+  // Set access to false for field_no_access.
+  if ($op == 'view' && $field['field_name'] == 'field_no_access') {
+    return FALSE;
+  }
+}
+

Some files were not shown because too many files changed in this diff