Browse Source

added remove duplicates modules

Bachir Soussi Chiadmi 8 years ago
parent
commit
c9b16289ef

+ 339 - 0
sites/all/modules/contrib/admin/remove_duplicates/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.

+ 95 - 0
sites/all/modules/contrib/admin/remove_duplicates/README.txt

@@ -0,0 +1,95 @@
+CONTENTS OF THIS FILE
+---------------------
+
+ * Introduction
+ * Installing
+ * Uninstalling
+ * Frequently Asked Questions (FAQ)
+ * Known Issues
+ * More Information
+ * How Can You Contribute?
+
+
+INTRODUCTION
+------------
+
+Current Maintainer: Sami Radi a.k.a VirtuoWorks <http://drupal.org/user/656630>
+
+Remove Duplicates removes duplicate nodes
+according to any selected content field.
+
+This can be very useful with Drupal feeds module :
+- http://drupal.org/project/feeds
+
+This module was inspired by these threads : 
+- http://drupal.org/node/720190 : Remove Duplicate Nodes based on title
+- http://drupal.org/node/1211922 : remove duplicated node base on title problem
+
+THANKS
+------
+
+Thanks to dman <https://drupal.org/user/33240> for endorsing this project.
+
+He wrote a sandbox module "Deduplicate Nodes" for D6 intended to deduplicate
+nodes according to titles only : https://drupal.org/sandbox/dman/1422586
+
+INSTALLING
+----------
+
+See http://drupal.org/getting-started/install-contrib
+for instructions on how to install or update Drupal modules.
+
+Once Remove Duplicates is installed and enabled,
+you can use it at admin/config/content/remove_duplicates.
+
+It is highly recommended that you backup your database before using this module.
+
+UNINSTALLING
+------------
+
+Because Drupal does not uninstall modules in reverse 
+order of their dependencies, if you want to uninstall
+all the Remove Duplicates modules, be sure to disable 
+and uninstall all the sub-modules before the base Remove 
+Duplicates module.
+
+To help fix this bug in Drupal core, visit http://drupal.org/node/151452.
+
+
+FREQUENTLY ASKED QUESTIONS (FAQ)
+--------------------------------
+
+- There are no frequently asked questions at this time.
+
+
+KNOWN ISSUES
+------------
+
+- See http://drupal.org/project/issues/2005296 
+for a list of the current known issues.
+
+
+MORE INFORMATION
+----------------
+
+- To issue any bug reports, feature or support requests
+see the module issue queue at 
+http://drupal.org/project/issues/2005296
+
+- For additional documentation, see the online module handbook at
+https://drupal.org/project/remove_duplicates
+
+
+HOW CAN YOU CONTRIBUTE?
+-----------------------
+
+- Report any bugs, feature requests, etc. in the issue tracker.
+http://drupal.org/node/add/project-issue/2005296
+
+- Help translate this module.
+
+- Write a review for this module at drupalmodules.com.
+http://drupalmodules.com/
+
+- Help keep development active by donating to the developers.
+http://www.virtuoworks.com/

+ 12 - 0
sites/all/modules/contrib/admin/remove_duplicates/remove_duplicates.info

@@ -0,0 +1,12 @@
+name = Remove Duplicates
+description = "Remove duplicate nodes according to node fields or CCK fields."
+core = 7.x
+package = Content management
+configure = admin/config/content/remove_duplicates
+
+; Information added by Drupal.org packaging script on 2015-04-13
+version = "7.x-1.4"
+core = "7.x"
+project = "remove_duplicates"
+datestamp = "1428944507"
+

+ 1897 - 0
sites/all/modules/contrib/admin/remove_duplicates/remove_duplicates.module

@@ -0,0 +1,1897 @@
+<?php
+
+/**
+ * @file
+ * Remove duplicate nodes according to node fields or Custom fields.
+ *
+ * Author : Sami Radi - VirtuoWorks.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function remove_duplicates_permission() {
+  return array(
+    'administer remove_duplicates' => array(
+      'title' => t('Use Remove Duplicates'),
+      'restrict access' => TRUE,
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function remove_duplicates_menu() {
+
+  // Titles and Descriptions should no longer be wrapped in t().
+  // See : https://drupal.org/node/140311
+  $items['admin/config/content/remove_duplicates'] = array(
+    'title' => 'Remove Duplicates',
+    'description' => 'Delete Duplicate Nodes',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('remove_duplicates_settings_form'),
+    'access arguments' => array('administer remove_duplicates'),
+    'type' => MENU_NORMAL_ITEM,
+  );
+  return $items;
+}
+
+/**
+ * Form constructor for the module.
+ *
+ * Form constructor for the module settings page (step 1/2)
+ * and confirm settings page (step 2/2).
+ *
+ * @see remove_duplicates_settings_submit()
+ *
+ * @ingroup forms.
+ */
+function remove_duplicates_settings_form($form, &$form_state) {
+
+  $remove_duplicates_message = t('Be careful, you might be losing data! I recommend doing a backup before removing duplicates.');
+  drupal_set_message($remove_duplicates_message, 'warning', FALSE);
+
+  if (!empty($form_state['storage']['confirm'])) {
+    // Form constructor for the module settings confirmation page (Step 2/2).
+    $form = remove_duplicates_build_confirm_settings_form($form, $form_state);
+  }
+  else {
+    // Form constructor for the module settings page (Step 1/2).
+    $form = remove_duplicates_build_settings_form($form, $form_state);
+  }
+
+  return $form;
+}
+
+/**
+ * Implements hook_form_submit().
+ *
+ * Processing the settings form or the settings
+ * confirmation form according to confirm state.
+ */
+function remove_duplicates_settings_form_submit($form, &$form_state) {
+
+  if (!empty($form_state['storage']['confirm'])) {
+    // Processing the module settings confirmation form
+    // (Processing form from step 2/2).
+    remove_duplicates_confirm_settings_form_submit_process($form, $form_state);
+  }
+  else {
+    // Processing the module settings form
+    // (Processing form from step 1/2).
+    remove_duplicates_settings_form_submit_process($form, $form_state);
+  }
+
+}
+
+/**
+ * Form constructor for the module settings page (Step 1/2).
+ *
+ * @see remove_duplicates_settings_form_submit()
+ *
+ * @ingroup forms.
+ */
+function remove_duplicates_build_settings_form($form, &$form_state) {
+
+  $message = t('All actions are logged in <b>Reports</b> >> <b>Recent log messages</b>.');
+
+  $form['message'] = array(
+    '#type'   => 'item',
+    '#markup'  => '<div class="description"><p>' . $message . '</p></div>',
+  );
+
+  $node_types = node_type_get_names();
+  $node_types_fields = _remove_duplicates_get_node_types_fields();
+
+  $form['remove_duplicates_node_types'] = array(
+    '#type' => 'select',
+    '#title' => t('Select a node type.'),
+    '#options' => $node_types,
+    '#default_value' => variable_get('remove_duplicates_node_types', array('page')),
+    '#description' => t('Select the node type from which duplicates are going to be found.'),
+  );
+
+  foreach ($node_types_fields as $machine_name => $node_type_fields) {
+
+    $form[$machine_name . '_node_fields'] = array(
+      '#type' => 'select',
+      '#title' => t('Select a field from this node type.'),
+      '#options' => $node_type_fields,
+      '#states' => array(
+        'visible' => array(
+          ':input[name="remove_duplicates_node_types"]' => array('value' => $machine_name),
+        ),
+      ),
+      '#description' => t('Select the field which is going to be used to find duplicates for this node type .'),
+    );
+
+  }
+
+  $options = array(
+    0 => t('Display results as a list (with duplicates to remove autoselection)'),
+    1 => t('Display results as a table (with duplicates to remove autoselection)'),
+    2 => t('Display results as a tableselect (with duplicates to remove manual selection)'),
+  );
+
+  $form['remove_duplicates_select_results_layout'] = array(
+    '#type' => 'radios',
+    '#title' => t('Results layout'),
+    '#default_value' => 2,
+    '#options' => $options,
+    '#description' => t('You can choose between three layouts to display found duplicates.'),
+  );
+
+  $form['remove_duplicates_case_sensitive'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('The search for duplicate nodes IS <b>case sensitive</b>.'),
+    '#default_value' => TRUE,
+    '#description' => t('If checked, duplicates search will be case sensitive.'),
+  );
+
+  $form['remove_duplicates_prioritize_published'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Keep at least one published node.'),
+    '#default_value' => TRUE,
+    '#states' => array(
+      'visible' => array(
+        ':input[name="remove_duplicates_select_results_layout"]' => array('!value' => 2),
+      ),
+      'invisible' => array(
+        ':input[name="remove_duplicates_select_results_layout"]' => array('value' => 2),
+      ),
+    ),
+    '#description' => t('At least one published node among the duplicates found will be kept. If unchecked, there will be no status check among duplicates.'),
+  );
+
+  $warning = t('PHP settings limit the maximum post size. If you have 1000+ duplicates found, <b>increase</b> your max post size setting to insure that the <b>whole</b> duplicate selection will be sent to the batch.');
+
+  $form['warning'] = array(
+    '#type'   => 'item',
+    '#markup'  => '<div class="description"><p>' . $warning . '</p></div>',
+    '#states' => array(
+      'visible' => array(
+        ':input[name="remove_duplicates_select_results_layout"]' => array('value' => 2),
+      ),
+    ),
+  );
+
+  $form['message'] = array(
+    '#type'   => 'item',
+    '#markup'  => '<div class="description"><p>' . $message . '</p></div>',
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Find Duplicates'),
+  );
+
+  return $form;
+}
+
+/**
+ * Processing the settings form (Processing step 1/2).
+ *
+ * If the settings form has not been confirmed,
+ * the confirm form is set to be built.
+ *
+ * @see remove_duplicates_settings_form()
+ */
+function remove_duplicates_settings_form_submit_process($form, &$form_state) {
+
+  if (empty($form_state['storage']['confirm'])) {
+
+    $form_state['rebuild'] = TRUE;
+    $form_state['storage']['confirm'] = TRUE;
+
+  }
+
+}
+
+/**
+ * Form constructor for the module settings confirmation page (Step 2/2).
+ *
+ * @see remove_duplicates_confirm_settings_form_submit()
+ *
+ * @ingroup forms.
+ */
+function remove_duplicates_build_confirm_settings_form($form, &$form_state) {
+
+  if (isset($form_state['values']['remove_duplicates_node_types'])) {
+
+    // Set node type hidden field.
+    $node_type_machine_name = $form_state['values']['remove_duplicates_node_types'];
+
+    $form['remove_duplicates_node_types'] = array(
+      '#type' => 'hidden',
+      '#value' => $node_type_machine_name,
+    );
+
+    if (isset($form_state['values'][$node_type_machine_name . '_node_fields'])) {
+
+      $form[$node_type_machine_name . '_node_fields'] = array(
+        '#type' => 'hidden',
+        '#value' => $form_state['values'][$node_type_machine_name . '_node_fields'],
+      );
+
+      $node_field_machine_name = $form_state['values'][$node_type_machine_name . '_node_fields'];
+      $prioritize_published_nodes = $form_state['values']['remove_duplicates_prioritize_published'];
+      $case_sensitive = $form_state['values']['remove_duplicates_case_sensitive'];
+
+      // Display found duplicates.
+      switch ($form_state['values']['remove_duplicates_select_results_layout']) {
+        case 1:
+          $output = _remove_duplicates_get_table_output($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive);
+          break;
+
+        case 2:
+          $output = _remove_duplicates_get_tableselect_output($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive);
+          break;
+
+        default:
+          $output = _remove_duplicates_get_list_output($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive);
+      }
+
+      // The field name is not very explicit but short to insure
+      // that the post size won't be too big even with a large
+      // number of duplicates selected. Previously named :
+      // remove_duplicates_duplicates_to_remove
+      $form['r'] = $output['#element'];
+
+      // End Of Display.
+      if (empty($output['#proceed'])) {
+
+        $remove_duplicates_message = t('Everything went fine. No duplicates were found.');
+        drupal_set_message($remove_duplicates_message);
+
+        $form_state['storage']['confirm'] = FALSE;
+
+      }
+
+      if (isset($form_state['values']['remove_duplicates_prioritize_published'])) {
+
+        $form['remove_duplicates_prioritize_published'] = array(
+          '#type' => 'hidden',
+          '#value' => $form_state['values']['remove_duplicates_prioritize_published'],
+        );
+
+      }
+      else {
+        $remove_duplicates_message = t('Priority not set. No duplicates were deleted.');
+        drupal_set_message($remove_duplicates_message, 'error');
+        $form_state['storage']['confirm'] = FALSE;
+      }
+
+      if (isset($form_state['values']['remove_duplicates_case_sensitive'])) {
+
+        $form['remove_duplicates_case_sensitive'] = array(
+          '#type' => 'hidden',
+          '#value' => $form_state['values']['remove_duplicates_case_sensitive'],
+        );
+
+      }
+      else {
+        $remove_duplicates_message = t('Case sensitivity not set. No duplicates were deleted.');
+        drupal_set_message($remove_duplicates_message, 'error');
+        $form_state['storage']['confirm'] = FALSE;
+      }
+
+    }
+    else {
+      $remove_duplicates_message = t('Node field not set. No duplicates were deleted.');
+      drupal_set_message($remove_duplicates_message, 'error');
+      $form_state['storage']['confirm'] = FALSE;
+    }
+  }
+  else {
+    $remove_duplicates_message = t('Node type not set. No duplicates were deleted.');
+    drupal_set_message($remove_duplicates_message, 'error');
+    $form_state['storage']['confirm'] = FALSE;
+  }
+
+  if (!empty($form_state['storage']['confirm'])) {
+    $form = confirm_form(
+        $form,
+        t('Are you sure you want to remove duplicates ?'),
+        'admin/config/content/remove_duplicates',
+        t('Found duplicates are going to be permanently removed.'),
+        t('Remove Duplicates'),
+        t('Cancel')
+      );
+  }
+  else {
+
+    $form['actions']['cancel'] = array(
+      '#type'   => 'item',
+      '#markup'  => l(t('Cancel'), 'admin/config/content/remove_duplicates'),
+    );
+  }
+
+  return $form;
+}
+
+/**
+ * Processing the settings form (Processing step 2/2).
+ *
+ * If the settings form has been confirmed, the batch is started.
+ */
+function remove_duplicates_confirm_settings_form_submit_process($form, &$form_state) {
+
+  if (!empty($form_state['storage']['confirm'])) {
+
+    if (isset($form_state['values']['remove_duplicates_node_types'])) {
+
+      $node_types = node_type_get_names();
+      $node_type_machine_name = $form_state['values']['remove_duplicates_node_types'];
+
+      if (isset($node_types[$node_type_machine_name])) {
+        if (isset($form_state['values'][$node_type_machine_name . '_node_fields'])) {
+
+          $node_field_info = field_info_instances('node', $node_type_machine_name);
+          $node_field_machine_name = $form_state['values'][$node_type_machine_name . '_node_fields'];
+
+          if ((is_array($node_field_info) && isset($node_field_info[$node_field_machine_name])) || ($node_field_machine_name == 'title')) {
+
+            $prioritize_published_nodes = ((empty($form_state['values']['remove_duplicates_prioritize_published'])) ? FALSE : TRUE);
+
+            $case_sensitive = ((empty($form_state['values']['remove_duplicates_case_sensitive'])) ? FALSE : TRUE);
+
+            $nodes_marked_as_removable = ((empty($form_state['values']['r'])) ? array() : $form_state['values']['r']);
+
+            $batch = array(
+              'title' => t('Searching for Duplicates'),
+              'operations' => array(
+                array(
+                  'remove_duplicates_batch_operation', array(
+                    $node_type_machine_name,
+                    $node_field_machine_name,
+                    $prioritize_published_nodes,
+                    $case_sensitive,
+                    $nodes_marked_as_removable,
+                  ),
+                ),
+              ),
+              'progress_message' => t('Work In Progress...'),
+              'error_message' => t('An Error Has Occured'),
+              'finished' => 'remove_duplicates_batch_finished',
+            );
+
+            // Watchdog message should not be wrapped in t().
+            // See : https://api.drupal.org/comment/33838#comment-33838
+            watchdog('remove_duplicates', 'Batch - Remove Duplicates batch start', array(), WATCHDOG_INFO);
+            batch_set($batch);
+          }
+          else {
+            $remove_duplicates_message = t('Node field selected not found. No duplicates were deleted');
+            drupal_set_message($remove_duplicates_message, 'error');
+          }
+        }
+        else {
+          $remove_duplicates_message = t('No field selected. No duplicates were deleted');
+          drupal_set_message($remove_duplicates_message, 'error');
+        }
+      }
+      else {
+        $remove_duplicates_message = t('Node type selected not found. No duplicates were deleted');
+        drupal_set_message($remove_duplicates_message, 'error');
+      }
+    }
+    else {
+      $remove_duplicates_message = t('No node type selected. No duplicates were deleted');
+      drupal_set_message($remove_duplicates_message, 'error');
+    }
+
+  }
+
+}
+
+/**
+ * Operation for batch_set().
+ *
+ * @param string $node_type_machine_name
+ *   The node type to fetch.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to group nodes and therefore create sets
+ *   of duplicate nodes.
+ *
+ * @param bool $prioritize_published_nodes
+ *   If TRUE, the last published node in a set of duplicate nodes will be kept.
+ *   Otherwise, the first node in a set of duplicate nodes will be kept.
+ *
+ * @param bool $case_sensitive
+ *   If TRUE, duplicates detection is case sensitive
+ *   Otherwise, duplicates detection is case insensitive.
+ *
+ * @param array $nodes_marked_as_removable
+ *   [Optional] An array of nids to remove.
+ *   Provided when using custom tableselect output.
+ */
+function remove_duplicates_batch_operation($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive, $nodes_marked_as_removable, &$context) {
+
+  if (isset($context['sandbox']) && isset($context['sandbox']['nodes_to_remove'])) {
+    $nodes_to_remove = $context['sandbox']['nodes_to_remove'];
+  }
+  else {
+    $nodes_to_remove = _remove_duplicates_get_nodes_ids_to_remove($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive, $nodes_marked_as_removable);
+  }
+
+  if (empty($context['sandbox'])) {
+    $context['sandbox']['current'] = 0;
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['max'] = count($nodes_to_remove);
+    $context['sandbox']['nodes_to_remove'] = $nodes_to_remove;
+  }
+
+  if (empty($nodes_to_remove)) {
+    $context['finished'] = 1;
+    if (isset($context['sandbox']) && isset($context['sandbox']['nodes_to_remove'])) {
+      unset($context['sandbox']['nodes_to_remove']);
+    }
+  }
+  else {
+    $limit = 5;
+    if (count($nodes_to_remove) < $limit) {
+      $limit = count($nodes_to_remove);
+    }
+
+    $preserve_keys = TRUE;
+    $nodes_to_remove = array_slice($nodes_to_remove, 0, $limit, $preserve_keys);
+
+    if (count($nodes_to_remove)) {
+      $nodes_to_remove_nids = array();
+      foreach ($nodes_to_remove as $node_to_remove) {
+        if (is_object($node_to_remove)) {
+          if ($node_to_remove->nid) {
+            $nodes_to_remove_nids[$node_to_remove->nid] = $node_to_remove->nid;
+          }
+
+          watchdog('remove_duplicates', 'Batch - Duplicate node @nid deleted : @title | updated on @changed | created on @created.', array(
+            '@nid' => (isset($node_to_remove->nid)) ? $node_to_remove->nid : NULL,
+            '@title' => (isset($node_to_remove->title)) ? $node_to_remove->title : NULL,
+            '@changed' => (isset($node_to_remove->changed)) ? format_date($node_to_remove->changed) : NULL,
+            '@created' => (isset($node_to_remove->created)) ? format_date($node_to_remove->created) : NULL,
+          ), WATCHDOG_DEBUG);
+        }
+      }
+
+      node_delete_multiple($nodes_to_remove_nids);
+
+      foreach ($nodes_to_remove_nids as $nid) {
+        if (isset($context['sandbox']['nodes_to_remove']) && isset($context['sandbox']['nodes_to_remove'][$nid])) {
+          unset($context['sandbox']['nodes_to_remove'][$nid]);
+        }
+      }
+    }
+
+    $context['sandbox']['progress'] = $context['sandbox']['progress'] + $limit;
+    if ($context['sandbox']['progress'] != $context['sandbox']['max']  &&  $context['sandbox']['max'] != 0) {
+      $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+    }
+    else {
+      $context['finished'] = 0.99;
+    }
+  }
+
+  $context['results']['processed'] = $context['sandbox']['progress'];
+}
+
+/**
+ * Callback for batch_set().
+ *
+ * @param bool $success
+ *   A boolean indicating whether the batch operation successfully concluded.
+ * @param int $results
+ *   The results from the batch process.
+ * @param array $operations
+ *   The batch operations that remained unprocessed. Only relevant if $success
+ *   is FALSE.
+ *
+ * @ingroup callbacks
+ */
+function remove_duplicates_batch_finished($success, $results, $operations) {
+  if (empty($success)) {
+    $remove_duplicates_message = t('Everything went fine. No duplicates deleted');
+    watchdog('remove_duplicates', 'Batch - Remove Duplicates batch end. No duplicates deleted.', array(), WATCHDOG_INFO);
+  }
+  else {
+    $remove_duplicates_message = t('@processed duplicates deleted.', array('@processed' => ((string) $results['processed'])));
+    watchdog('remove_duplicates', 'Batch - Remove Duplicates batch end. @processed duplicates deleted.', array('@processed' => ((string) $results['processed'])), WATCHDOG_INFO);
+  }
+  drupal_set_message($remove_duplicates_message);
+}
+
+/**
+ * Get all duplicate nodes ids to remove.
+ *
+ * @param string $node_type_machine_name
+ *   The node type to fetch.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to group nodes and therefore create sets of
+ *   duplicate nodes.
+ *
+ * @param bool $prioritize_published_nodes
+ *   If TRUE, the last published node in a set of duplicate nodes will be kept.
+ *   Otherwise, the first node in a set of duplicate nodes will be kept.
+ *
+ * @param bool $case_sensitive
+ *   If TRUE, duplicates detection is case sensitive
+ *   Otherwise, duplicates detection is case insensitive.
+ *
+ * @param array $nodes_marked_as_removable
+ *   [Optional] An array of nids to remove.
+ *   Provided when using custom tableselect output.
+ *
+ * @return array
+ *   An array of node nids
+ */
+function _remove_duplicates_get_nodes_ids_to_remove($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive, $nodes_marked_as_removable = array()) {
+
+  $duplicate_node_groups = _remove_duplicates_get_duplicate_node_groups($node_type_machine_name, $node_field_machine_name, $case_sensitive);
+
+  if (is_array($duplicate_node_groups)) {
+
+    if (isset($duplicate_node_groups['count']) && is_array($duplicate_node_groups['count'])) {
+      $count_nodes = (array_key_exists('nodes', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['nodes'] : 0;
+      $count_node_groups = (array_key_exists('node_groups', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['node_groups'] : 0;
+
+      watchdog('remove_duplicates', 'Batch - Found duplicate nodes : [@count_nodes] | node groups : [@count_node_groups]', array(
+        '@count_nodes' => $count_nodes,
+        '@count_node_groups' => $count_node_groups,
+      ), WATCHDOG_INFO);
+      // If duplicate nodes to remove were not manually selected.
+      if (empty($nodes_marked_as_removable)) {
+        watchdog('remove_duplicates', 'Batch - Duplicate nodes to remove estimate before filtering : [@nodes_to_remove_estimate]', array(
+          '@nodes_to_remove_estimate' => $count_nodes - $count_node_groups,
+        ), WATCHDOG_INFO);
+      }
+      else {
+        if (count($nodes_marked_as_removable) < $count_nodes) {
+          $remove_duplicates_message = t('All the data from table selection was not recovered. Check out yout PHP settings to increase maximum post size or re-run search and remove operations to delete remaining duplicates.');
+          drupal_set_message($remove_duplicates_message, 'error');
+
+          watchdog('remove_duplicates', 'Batch - All the data from table selection was not recovered. Check out yout PHP settings to increase maximum post size.', array(), WATCHDOG_ERROR);
+        }
+        $nodes_marked_as_removable_count = count(array_filter($nodes_marked_as_removable));
+
+        watchdog('remove_duplicates', 'Batch - Duplicate nodes to remove estimate before filtering : [@nodes_to_remove_estimate]', array(
+          '@nodes_to_remove_estimate' => $nodes_marked_as_removable_count,
+        ), WATCHDOG_INFO);
+      }
+    }
+
+    if (isset($duplicate_node_groups['data']) && is_array($duplicate_node_groups['data']) && count($duplicate_node_groups['data'])) {
+      $nodes_to_remove = array();
+      foreach ($duplicate_node_groups['data'] as $duplicate_node_group) {
+        if (is_array($duplicate_node_group) && count($duplicate_node_group) > 1) {
+
+          if (empty($nodes_marked_as_removable)) {
+            // Preserving the first node in the current duplicate node group.
+            $node_to_keep = array_shift($duplicate_node_group);
+          }
+          // Filling the array with the rest of the nodes in the group.
+          foreach ($duplicate_node_group as $duplicate_node) {
+            if (is_object($duplicate_node) && isset($duplicate_node->nid) && isset($duplicate_node->status)) {
+              if (!empty($nodes_marked_as_removable)) {
+                if (!empty($nodes_marked_as_removable[$duplicate_node->nid])) {
+                  $nodes_to_remove[$duplicate_node->nid] = $duplicate_node;
+                }
+              }
+              else {
+                if ($prioritize_published_nodes) {
+                  if ($duplicate_node->status == 1 && $node_to_keep->status != 1) {
+                    $nodes_to_remove[$node_to_keep->nid] = $node_to_keep;
+                  }
+                  else {
+                    $nodes_to_remove[$duplicate_node->nid] = $duplicate_node;
+                  }
+                }
+                else {
+                  $nodes_to_remove[$duplicate_node->nid] = $duplicate_node;
+                }
+              }
+
+            }
+          }
+          unset($node_to_keep);
+        }
+      }
+      if (is_array($nodes_to_remove) && ($nodes_count = count($nodes_to_remove))) {
+        watchdog('remove_duplicates', 'Batch - Duplicate nodes to remove after filtering : [@count_nodes]', array(
+          '@count_nodes' => $nodes_count,
+        ), WATCHDOG_INFO);
+        return $nodes_to_remove;
+      }
+      else {
+        return array();
+      }
+    }
+  }
+
+  return array();
+}
+
+/**
+ * Get the field common name used in node objects.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to group nodes and therefore create sets of
+ *   duplicate nodes.
+ *
+ * @return string
+ *   A field name to use with extracted db records
+ */
+function _remove_duplicates_get_field_common_name($node_field_machine_name) {
+
+  if (in_array(strtolower($node_field_machine_name), array('title'))) {
+    // Basic field.
+    $field = $node_field_machine_name;
+  }
+  else {
+    // Custom field.
+    $field_info = field_info_field($node_field_machine_name);
+
+    if (!empty($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) {
+      $table = key($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT']);
+      $field = current($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT'][$table]);
+
+      if (!(db_table_exists($table) && db_field_exists($table, $field))) {
+        return NULL;
+      }
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  return $field;
+}
+
+/**
+ * Get all duplicate node grouped according to selected field.
+ *
+ * @param string $node_type_machine_name
+ *   The node type to fetch.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to group nodes and therefore create sets of
+ *   duplicate nodes.
+ *
+ * @param bool $case_sensitive
+ *   If TRUE, duplicates detection is case sensitive
+ *   Otherwise, duplicates detection is case insensitive.
+ *
+ * @return array
+ *   An array of duplicate nodes groups
+ */
+function _remove_duplicates_get_duplicate_node_groups($node_type_machine_name, $node_field_machine_name, $case_sensitive) {
+
+  $field = _remove_duplicates_get_field_common_name($node_field_machine_name);
+
+  if (empty($field)) {
+    return array();
+  }
+  else {
+    $records = _remove_duplicates_get_nodes($node_type_machine_name, $node_field_machine_name, $case_sensitive);
+
+    if (is_array($records) && count($records)) {
+      // Creating node groups.
+      $node_groups = array();
+      foreach ($records as $record) {
+        if (is_object($record) && isset($record->$field)) {
+          // MD5 unicity magic to group nodes according to field content.
+          if ($case_sensitive) {
+            $node_groups[md5($record->$field)][] = $record;
+          }
+          else {
+            $node_groups[md5(strtolower($record->$field))][] = $record;
+          }
+        }
+      }
+
+      // Keeping only node groups with duplicates.
+      $duplicate_node_groups = array();
+      $node_groups_count = $nodes_count = 0;
+      foreach ($node_groups as $md5key => $node_group) {
+        if (is_array($node_group) && ($node_group_count = count($node_group)) > 1) {
+          $nodes_count += $node_group_count;
+          $duplicate_node_groups[$md5key] = $node_group;
+        }
+      }
+      $node_groups_count = count($duplicate_node_groups);
+      return array(
+        'count' => array(
+          'nodes' => $nodes_count,
+          'node_groups' => $node_groups_count,
+        ),
+        'data' => $duplicate_node_groups,
+      );
+    }
+    else {
+      return array(
+        'count' => array(
+          'nodes' => 0,
+          'node_groups' => 0,
+        ),
+        'data' => array(),
+      );
+    }
+  }
+}
+
+/**
+ * Get all nodes with selected type / field.
+ *
+ * @param string $node_type_machine_name
+ *   The node type to fetch.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} to join with the fetched node type.
+ *
+ * @param bool $case_sensitive
+ *   If TRUE, duplicates detection is case sensitive
+ *   Otherwise, duplicates detection is case insensitive.
+ *
+ * @return array
+ *   An array of node objects with 3 properties : nid, status, {field}.
+ */
+function _remove_duplicates_get_nodes($node_type_machine_name = NULL, $node_field_machine_name = NULL, $case_sensitive = TRUE) {
+
+  $records = array();
+
+  // EntityFieldQuery does not support GROUP BY nor DISTINCT.
+  // See : https://drupal.org/node/1565708
+  // Using raw database calls instead.
+  if (in_array(strtolower($node_field_machine_name), array('title'))) {
+    // For basic title field.
+    $field = $node_field_machine_name;
+
+    if (Database::getConnection()->databaseType() == 'pgsql') {
+
+      // In PostgreSQL string comparisons are case sensitive by default.
+
+      // Nested Query pattern (case sensitive) :
+      // @code
+      //  SELECT s.{field} AS {field},
+      //  COUNT( * ) AS duplicate
+      //  FROM node s
+      //  WHERE (
+      //    s.type = {node_type_machine_name}
+      //  )
+      //  GROUP BY {$field}
+      // @endcode
+
+      // Nested Query pattern (case insensitive) :
+      // @code
+      //  SELECT s.lowered as {field},
+      //  SUM(s.duplicate) AS duplicate
+      //  FROM (
+      //    SELECT s.{field} AS {field}, lower(s.{field}) AS lowered,
+      //    COUNT( * ) AS duplicate
+      //    FROM node s
+      //    WHERE (
+      //      s.type = {node_type_machine_name}
+      //    )
+      //    GROUP BY lowered, {field}
+      //  ) s
+      //  GROUP BY lowered
+      // @endcode
+
+      $nested_query = db_select('node', 's');
+      $nested_query->fields('s', array($field));
+      $nested_query->addExpression('COUNT(*)', 'duplicate');
+      $nested_query->condition('s.type', $node_type_machine_name, '=');
+      if ($case_sensitive) {
+        $nested_query->groupBy($field);
+      }
+      else {
+        $nested_query->addExpression('lower(s.' . $field . ')', 'lowered');
+        $nested_query->groupBy('lowered');
+        $nested_query->groupBy($field);
+        $nested_query = db_select($nested_query, 's');
+        $nested_query->addExpression('s.lowered', $field);
+        $nested_query->addExpression('SUM(s.duplicate)', 'duplicate');
+        $nested_query->groupBy('lowered');
+      }
+
+      // Sub Query pattern (both cases) :
+      // @code
+      //  SELECT n.{field} AS {field}
+      //  FROM {$nested_query} n
+      //  WHERE duplicates > 1
+      // @endcode
+      $sub_query = db_select($nested_query, 'n');
+      $sub_query->fields('n', array($field));
+      $sub_query->condition('duplicate', 1, '>');
+    }
+    else {
+
+      // In MySQL nonbinary string comparisons are case insensitive by default.
+      // See : https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
+
+      // Sub Query pattern (case sensitive) :
+      // @code
+      //  SELECT n.{field} AS {field}, md5(n.{field}) AS checksum,
+      //  COUNT( * ) AS duplicate
+      //  FROM node n
+      //  WHERE (
+      //    n.type = {node_type_machine_name}
+      //  )
+      //  GROUP BY checksum
+      //  HAVING count( duplicate ) >1
+      // @endcode
+
+      // Sub Query pattern (case insensitive) :
+      // @code
+      //  SELECT n.{field} AS {field},
+      //  COUNT( * ) AS duplicate
+      //  FROM node n
+      //  WHERE (
+      //    n.type = {node_type_machine_name}
+      //  )
+      //  GROUP BY {field}
+      //  HAVING count( duplicate ) > 1
+      // @endcode
+      $sub_query = db_select('node', 'n');
+      $sub_query->fields('n', array($field));
+      $sub_query->addExpression('COUNT(*)', 'duplicate');
+      if ($case_sensitive) {
+        $sub_query->addExpression('md5(n.' . $field . ')', 'checksum');
+        $sub_query->groupBy('checksum');
+      }
+      else {
+        $sub_query->groupBy($field);
+      }
+      $sub_query->condition('n.type', $node_type_machine_name, '=');
+      $sub_query->havingCondition('duplicate', 1, '>');
+    }
+
+    // Main Query pattern (case sensitive) :
+    // @code
+    //  SELECT n.nid AS nid, n.uid AS uid, n.status AS status,
+    //  n.created AS created, n.changed AS changed,
+    //  n.{field} AS {field}, u.name AS name
+    //  FROM node n
+    //  INNER JOIN users u ON n.uid = u.uid
+    //  INNER JOIN (
+    //    {sub_query}
+    //  ) nn ON md5(n.{field}) = md5(nn.{field})
+    //  WHERE (
+    //    n.type = {node_type_machine_name}
+    //  )
+    //  ORDER BY {field} ASC
+    // @endcode
+
+    // Main Query pattern (case insensitive) :
+    // @code
+    //  SELECT n.nid AS nid, n.uid AS uid, n.status AS status,
+    //  n.created AS created, n.changed AS changed,
+    //  n.{field} AS {field}, u.name AS name
+    //  FROM node n
+    //  INNER JOIN users u ON n.uid = u.uid
+    //  INNER JOIN (
+    //    {sub_query}
+    //  ) nn ON n.{field} = nn.{field}
+    //  WHERE (
+    //    n.type = {node_type_machine_name}
+    //  )
+    //  ORDER BY {field} ASC
+    // @endcode
+    $main_query = db_select('node', 'n');
+    $main_query->fields('n', array(
+      'nid',
+      'uid',
+      'status',
+      'created',
+      'changed',
+      $field,
+    ));
+    $main_query->fields('u', array('name'));
+    $main_query->join('users', 'u', 'n.uid = u.uid');
+    if ($case_sensitive) {
+      $main_query->join($sub_query, 'nn', 'md5(n.' . $field . ') = md5(nn.' . $field . ')');
+    }
+    else {
+      if (Database::getConnection()->databaseType() == 'pgsql') {
+        $main_query->join($sub_query, 'nn', 'lower(n.' . $field . ') = lower(nn.' . $field . ')');
+      }
+      else {
+        $main_query->join($sub_query, 'nn', 'n.' . $field . ' = nn.' . $field);
+      }
+    }
+    $main_query->condition('n.type', $node_type_machine_name, '=');
+    $main_query->orderBy($field, 'ASC');
+
+    $records = $main_query->execute()->fetchall();
+  }
+  else {
+    // For Custom Fields.
+    $field_info = field_info_field($node_field_machine_name);
+
+    if (!empty($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT'])) {
+      $table = key($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT']);
+      $field = current($field_info['storage']['details']['sql']['FIELD_LOAD_CURRENT'][$table]);
+
+      if (db_table_exists($table) && db_field_exists($table, $field)) {
+
+        if (Database::getConnection()->databaseType() == 'pgsql') {
+          // Nested Query pattern (case sensitive) :
+          // @code
+          //  SELECT cf.{field} AS {field},
+          //  COUNT( * ) AS duplicate
+          //  FROM node s
+          //  INNER JOIN {table} cf ON cf.bundle = {node_type_machine_name}
+          //  AND cf.entity_type = 'node' AND cf.entity_id = s.nid
+          //  WHERE (
+          //    s.type = {node_type_machine_name}
+          //  )
+          //  GROUP BY {$field}
+          // @endcode
+
+          // Nested Query pattern (case insensitive) :
+          // @code
+          //  SELECT s.lowered as {field},
+          //  SUM(s.duplicate) AS duplicate
+          //  FROM (
+          //    SELECT cf.{field} AS {field}, lower(cf.{field}) AS lowered,
+          //    COUNT( * ) AS duplicate
+          //    FROM node s
+          //    INNER JOIN {table} cf ON cf.bundle = {node_type_machine_name}
+          //    AND cf.entity_type = 'node' AND cf.entity_id = s.nid
+          //    WHERE (
+          //      s.type = {node_type_machine_name}
+          //    )
+          //    GROUP BY lowered, {field}
+          //  ) s
+          //  GROUP BY lowered
+          // @endcode
+
+          $nested_query = db_select('node', 's');
+          $nested_query->addJoin('INNER', $table, 'cf', 'cf.bundle = :bundle AND cf.entity_type = :entity_type AND s.nid = cf.entity_id ', array(
+              ':bundle' => $node_type_machine_name,
+              ':entity_type' => 'node',
+            ));
+          $nested_query->fields('cf', array($field));
+          $nested_query->addExpression('COUNT(*)', 'duplicate');
+          $nested_query->condition('s.type', $node_type_machine_name, '=');
+          if ($case_sensitive) {
+            $nested_query->groupBy($field);
+          }
+          else {
+            $nested_query->addExpression('lower(cf.' . $field . ')', 'lowered');
+            $nested_query->groupBy('lowered');
+            $nested_query->groupBy($field);
+            $nested_query = db_select($nested_query, 's');
+            $nested_query->addExpression('s.lowered', $field);
+            $nested_query->addExpression('SUM(s.duplicate)', 'duplicate');
+            $nested_query->groupBy('lowered');
+          }
+
+          // Sub Query pattern (both cases) :
+          // @code
+          //  SELECT cf.{field} AS {field}
+          //  FROM {$nested_query} cf
+          //  WHERE duplicates > 1
+          // @endcode
+          $sub_query = db_select($nested_query, 'cf');
+          $sub_query->fields('cf', array($field));
+          $sub_query->condition('duplicate', 1, '>');
+        }
+        else {
+          // Sub Query pattern (case sensitive) :
+          // @code
+          //  SELECT cf.{field} AS {field}, md5(cf.{field}) AS checksum,
+          //  COUNT( * ) AS duplicate
+          //  FROM node n
+          //  INNER JOIN {table} cf ON cf.bundle = {node_type_machine_name}
+          //  AND cf.entity_type = 'node' AND cf.entity_id = n.nid
+          //  WHERE (
+          //    n.type = {node_type_machine_name}
+          //  )
+          //  GROUP BY checksum
+          //  HAVING count( duplicate ) > 1
+          // @endcode
+
+          // Sub Query pattern (case insensitive) :
+          // @code
+          //  SELECT cf.{field} AS {field},
+          //  COUNT( * ) AS duplicate
+          //  FROM node n
+          //  INNER JOIN {table} cf ON cf.bundle = {node_type_machine_name}
+          //  AND cf.entity_type = 'node' AND cf.entity_id = n.nid
+          //  WHERE (
+          //    n.type = {node_type_machine_name}
+          //  )
+          //  GROUP BY {field}
+          //  HAVING count( duplicate ) > 1
+          // @endcode
+          $sub_query = db_select('node', 'n');
+          $sub_query->addJoin('INNER', $table, 'cf', 'cf.bundle = :bundle AND cf.entity_type = :entity_type AND n.nid = cf.entity_id ', array(
+              ':bundle' => $node_type_machine_name,
+              ':entity_type' => 'node',
+            ));
+          $sub_query->condition('n.type', $node_type_machine_name, '=');
+          $sub_query->fields('cf', array($field));
+          $sub_query->addExpression('COUNT(*)', 'duplicate');
+          if ($case_sensitive) {
+            $sub_query->addExpression('md5(cf.' . $field . ')', 'checksum');
+            $sub_query->groupBy('checksum');
+          }
+          else {
+            $sub_query->groupBy($field);
+          }
+          $sub_query->havingCondition('duplicate', 1, '>');
+        }
+
+        // Main Query pattern (case sensitive) :
+        // @code
+        //  SELECT n.nid AS nid, n.uid AS uid, n.status AS status,
+        //  n.created AS created, n.changed AS changed, n.title AS title,
+        //  u.name AS name, cf.{field} AS {field}
+        //  FROM node n
+        //  INNER JOIN users u ON n.uid = u.uid
+        //  INNER JOIN {table} cf ON cf.bundle = {node_type_machine_name}
+        //  AND cf.entity_type = 'node' AND cf.entity_id = n.nid
+        //  INNER JOIN (
+        //    {sub_query}
+        //  ) nn ON md5(cf.{field}) = md5(nn.{field})
+        //  WHERE (
+        //    n.type = {node_type_machine_name}
+        //  )
+        //  ORDER BY {field} ASC
+        // @endcode
+
+        // Main Query pattern (case insensitive) :
+        // @code
+        //  SELECT n.nid AS nid, n.uid AS uid, n.status AS status,
+        //  n.created AS created, n.changed AS changed, n.title AS title,
+        //  u.name AS name, cf.{field} AS {field}
+        //  FROM node n
+        //  INNER JOIN users u ON n.uid = u.uid
+        //  INNER JOIN {table} cf ON cf.bundle = {node_type_machine_name}
+        //  AND cf.entity_type = 'node' AND cf.entity_id = n.nid
+        //  INNER JOIN (
+        //    {sub_query}
+        //  ) nn ON cf.{field} = nn.{field}
+        //  WHERE (
+        //    n.type = {node_type_machine_name}
+        //  )
+        //  ORDER BY {field} ASC
+        // @endcode
+        $main_query = db_select('node', 'n');
+        $main_query->fields('n', array(
+          'nid',
+          'uid',
+          'status',
+          'created',
+          'changed',
+          'title',
+        ));
+        $main_query->fields('u', array('name'));
+        $main_query->fields('cf', array($field));
+        $main_query->addJoin('INNER', $table, 'cf', 'cf.bundle = :bundle AND cf.entity_type = :entity_type AND n.nid = cf.entity_id ', array(
+            ':bundle' => $node_type_machine_name,
+            ':entity_type' => 'node',
+          ));
+        $main_query->join('users', 'u', 'n.uid = u.uid');
+        if ($case_sensitive) {
+          $main_query->join($sub_query, 'nn', 'md5(cf.' . $field . ') = md5(nn.' . $field . ')');
+        }
+        else {
+          if (Database::getConnection()->databaseType() == 'pgsql') {
+            $main_query->join($sub_query, 'nn', 'lower(cf.' . $field . ') = lower(nn.' . $field . ')');
+          }
+          else {
+            $main_query->join($sub_query, 'nn', 'cf.' . $field . ' = nn.' . $field);
+          }
+        }
+        $main_query->condition('n.type', $node_type_machine_name, '=');
+        $main_query->orderBy($field, 'ASC');
+
+        $records = $main_query->execute()->fetchall();
+      }
+    }
+  }
+
+  return $records;
+}
+
+/**
+ * Get all available fields for each node types.
+ *
+ * @return array
+ *   An array of node types fields
+ */
+function _remove_duplicates_get_node_types_fields() {
+
+  $node_types_fields = array();
+  $node_types = node_type_get_names();
+
+  foreach ($node_types as $machine_name => $human_readable_name) {
+
+    $node_types_fields[$machine_name] = array();
+    $node_types_fields[$machine_name]['title'] = t('Title');
+
+    $field_info = field_info_instances('node', $machine_name);
+    if (is_array($field_info)) {
+
+      foreach ($field_info as $field) {
+        if (isset($field['label']) && isset($field['field_name'])) {
+          $node_types_fields[$machine_name][$field['field_name']] = $field['label'];
+        }
+      }
+
+    }
+
+  }
+
+  return $node_types_fields;
+}
+
+/**
+ * Get a list themed output (Legacy output).
+ *
+ * @param string $node_type_machine_name
+ *   The fetched node type.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to find duplicates.
+ *
+ * @param bool $prioritize_published_nodes
+ *   If TRUE, the last published node in a set of duplicate nodes will be kept.
+ *   Otherwise, the first node in a set of duplicate nodes will be kept.
+ *
+ * @return array
+ *   An array with 2 keys :
+ *    #markup  An HTML string representing the list themed output.
+ *    #proceed A boolean indicating whether or not duplicates were found.
+ */
+function _remove_duplicates_get_list_output($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive) {
+
+  $node_types = node_type_get_names();
+  $node_types_fields = _remove_duplicates_get_node_types_fields();
+
+  $duplicate_node_groups = _remove_duplicates_get_duplicate_node_groups($node_type_machine_name, $node_field_machine_name, $case_sensitive);
+
+  if (is_array($duplicate_node_groups)) {
+
+    if (isset($duplicate_node_groups['count']) && is_array($duplicate_node_groups['count'])) {
+      $count_nodes = (array_key_exists('nodes', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['nodes'] : 0;
+      $count_node_groups = (array_key_exists('node_groups', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['node_groups'] : 0;
+
+      watchdog('remove_duplicates', 'Search - Found duplicate nodes : [@count_nodes] | node groups : [@count_node_groups]', array(
+        '@count_nodes' => $count_nodes,
+        '@count_node_groups' => $count_node_groups,
+      ), WATCHDOG_INFO);
+
+      watchdog('remove_duplicates', 'Search - Duplicate nodes to remove estimate : [@nodes_to_remove_estimate]', array(
+        '@nodes_to_remove_estimate' => $count_nodes - $count_node_groups,
+      ), WATCHDOG_INFO);
+    }
+
+    if (isset($duplicate_node_groups['data']) && is_array($duplicate_node_groups['data']) && count($duplicate_node_groups['data'])) {
+      // Outputs an HTML list of duplicates found.
+      $list_output = array();
+      foreach ($duplicate_node_groups['data'] as $duplicate_node_group) {
+        $items = array();
+        $node_group_title = NULL;
+        $nodetypefieldvalue = array();
+        $keep_node_nid = $keep_node_status = NULL;
+        $field = _remove_duplicates_get_field_common_name($node_field_machine_name);
+        foreach ($duplicate_node_group as $duplicate_node) {
+          // Defining the group title.
+          $nodetypefieldvalue[$duplicate_node->$field] = $duplicate_node->$field;
+          // Defaults to keeping the first node in the group.
+          if (empty($keep_node_nid)) {
+            $keep_node_status = $duplicate_node->status;
+            $keep_node_nid = $duplicate_node->nid;
+          }
+          // Defining the node which is going to be kept among duplicates.
+          if (isset($keep_node_nid)) {
+            // If "Keep at least one published node." is checked
+            // keeping the first published node in the group.
+            if ($prioritize_published_nodes) {
+              if ($duplicate_node->status && !$keep_node_status) {
+                $keep_node_nid = $duplicate_node->nid;
+                $keep_node_status = $duplicate_node->status;
+              }
+            }
+          }
+        }
+        // Defining the group title.
+        if (empty($node_group_title) && is_array($nodetypefieldvalue)) {
+          $node_group_title = t('Duplicates where "@nodetypefield" = @nodetypefieldvalue', array(
+            '@nodetypefieldvalue' => '"' . implode('" ' . t('or') . ' "', $nodetypefieldvalue) . '"',
+            '@nodetypefield' => ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]),
+          ));
+        }
+        reset($duplicate_node_group);
+        foreach ($duplicate_node_group as $duplicate_node) {
+          $node_title = l($duplicate_node->title, 'node/' . $duplicate_node->nid, array('attributes' => array('onclick' => 'window.open(this.href,parseInt(Math.random()*1000));return false;')));
+
+          $node_status = array();
+          if ($duplicate_node->status) {
+            $node_status[] = '<span style="color:black;">' . t('Published') . '</span>';
+          }
+          else {
+            $node_status[] = '<span style="color:gray;">' . t('Unpublished') . '</span>';
+          }
+          if (isset($keep_node_nid) && $keep_node_nid == $duplicate_node->nid) {
+            $node_status[] = '<span style="color:green;">' . t('Will be kept.') . '</span>';
+          }
+          else {
+            $node_status[] = '<span style="color:red;">' . t('Will be deleted.') . '</span>';
+          }
+
+          $items[] = theme('item_list', array(
+              'items' => $node_status,
+              'title' => $node_title,
+              'type' => 'ul',
+              'attributes' => array(),
+            ));
+        }
+
+        $list_output[] = theme('item_list', array(
+            'items' => $items,
+            'title' => $node_group_title,
+            'type' => 'ul',
+            'attributes' => array(),
+          ));
+      }
+
+      $main_title = t('Duplicates found for node type "@nodetype" according to field "@nodetypefield" :', array(
+        '@nodetype' => ((string) $node_types[$node_type_machine_name]),
+        '@nodetypefield' => ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]),
+      ));
+
+      $list_output = theme('item_list', array(
+          'items' => $list_output,
+          'title' => $main_title,
+          'type' => 'ul',
+          'attributes' => array(
+            'style' => 'font-size:0.9em;',
+          ),
+        ));
+
+      $output = array(
+        '#proceed' => TRUE,
+        '#element' => array(
+          '#type' => 'item',
+          '#title' => t('Results'),
+          '#markup' => $list_output,
+        ),
+      );
+
+      return $output;
+    }
+  }
+
+  watchdog('remove_duplicates', 'Search - No duplicates found for node [@node_type_machine_name] according to field [@node_field_machine_name]', array(
+    '@node_type_machine_name' => $node_type_machine_name,
+    '@node_field_machine_name' => $node_field_machine_name,
+  ), WATCHDOG_INFO);
+
+  // Outputs a no duplicates found message.
+  $text_output = t('No duplicates for node type "@nodetype" according to field "@nodetypefield".', array(
+    '@nodetype' => ((string) $node_types[$node_type_machine_name]),
+    '@nodetypefield' => ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]),
+  ));
+
+  $html_output = '<div class="description"><p><span style="font-weight:bold;color:red;">' . $text_output . '</span></p></div>';
+
+  $output = array(
+    '#proceed' => FALSE,
+    '#element' => array(
+      '#type' => 'item',
+      '#title' => t('Results'),
+      '#markup' => $html_output,
+    ),
+  );
+
+  return $output;
+}
+
+/**
+ * Get a table themed output (Legacy output).
+ *
+ * @param string $node_type_machine_name
+ *   The fetched node type.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to find duplicates.
+ *
+ * @param bool $prioritize_published_nodes
+ *   If TRUE, the last published node in a set of duplicate nodes will be kept.
+ *   Otherwise, the first node in a set of duplicate nodes will be kept.
+ *
+ * @param bool $case_sensitive
+ *   If TRUE, duplicates detection is case sensitive
+ *   Otherwise, duplicates detection is case insensitive.
+ *
+ * @return array
+ *   An array with 2 keys :
+ *    #markup  An HTML string representing the table themed output.
+ *    #proceed A boolean indicating whether or not duplicates were found.
+ */
+function _remove_duplicates_get_table_output($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive) {
+
+  $node_types = node_type_get_names();
+  $node_types_fields = _remove_duplicates_get_node_types_fields();
+
+  $duplicate_node_groups = _remove_duplicates_get_duplicate_node_groups($node_type_machine_name, $node_field_machine_name, $case_sensitive);
+
+  if (is_array($duplicate_node_groups)) {
+
+    if (isset($duplicate_node_groups['count']) && is_array($duplicate_node_groups['count'])) {
+      $count_nodes = (array_key_exists('nodes', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['nodes'] : 0;
+      $count_node_groups = (array_key_exists('node_groups', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['node_groups'] : 0;
+
+      watchdog('remove_duplicates', 'Search - Found duplicate nodes : [@count_nodes] | node groups : [@count_node_groups]', array(
+        '@count_nodes' => $count_nodes,
+        '@count_node_groups' => $count_node_groups,
+      ), WATCHDOG_INFO);
+
+      watchdog('remove_duplicates', 'Search - Duplicate nodes to remove estimate : [@nodes_to_remove_estimate]', array(
+        '@nodes_to_remove_estimate' => $count_nodes - $count_node_groups,
+      ), WATCHDOG_INFO);
+    }
+
+    if (isset($duplicate_node_groups['data']) && is_array($duplicate_node_groups['data']) && count($duplicate_node_groups['data'])) {
+      // Outputs an table of duplicates found.
+      $table_output = NULL;
+      // Construction of duplicate node group tables.
+      $duplicate_node_group_table_rows = array();
+      $duplicate_node_group_table_header = array(
+        array(
+          'header' => TRUE,
+          'data' => t('remove'),
+        ),
+        array(
+          'header' => TRUE,
+          'data' => t('title'),
+        ),
+        array(
+          'header' => TRUE,
+          'data' => t('author'),
+        ),
+        array(
+          'header' => TRUE,
+          'data' => t('published'),
+        ),
+        array(
+          'header' => TRUE,
+          'data' => t('updated'),
+        ),
+        array(
+          'header' => TRUE,
+          'data' => t('created'),
+        ),
+      );
+
+      $duplicate_node_group_table_rows[] = array(
+        array(
+          'header' => TRUE,
+          'data' => t('Found Duplicates'),
+          'colspan' => count($duplicate_node_group_table_header),
+        ),
+      );
+
+      $duplicate_node_group_table_rows[] = array(
+        array(
+          'header' => TRUE,
+          'data' => t('For node type'),
+        ),
+        array(
+          'data' => ((string) $node_types[$node_type_machine_name]),
+        ),
+        array(
+          'header' => TRUE,
+          'data' => t('With field name'),
+        ),
+        array(
+          'colspan' => count($duplicate_node_group_table_header) - 3,
+          'data' => ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]),
+        ),
+      );
+
+      foreach ($duplicate_node_groups['data'] as $duplicate_node_group) {
+
+        // Defining the default duplicate group title.
+        $first_duplicate_node = NULL;
+        $node_group_title = $node_group_field_name = $node_group_field_value = NULL;
+        if (is_array($duplicate_node_group) && count($duplicate_node_group)) {
+          $field_common_name = _remove_duplicates_get_field_common_name($node_field_machine_name);
+          $node_group_field_value = array();
+          foreach ($duplicate_node_group as $duplicate_node) {
+            if (is_object($duplicate_node) && !empty($duplicate_node->$field_common_name)) {
+              $node_group_field_value[((string) $duplicate_node->$field_common_name)] = ((string) $duplicate_node->$field_common_name);
+            }
+          }
+          $node_group_field_name = ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]);
+          $node_group_field_value = '"' . implode('" ' . t('or') . ' "', $node_group_field_value) . '"';
+          $node_group_title = t('Where "@nodetypefield" is', array(
+            '@nodetypefield' => $node_group_field_name,
+          ));
+        }
+
+        // Defining the default kept node among duplicates.
+        $keep_node_nid = $keep_node_status = NULL;
+        foreach ($duplicate_node_group as $duplicate_node) {
+          // Defaults to keeping the first node in the group.
+          if (empty($keep_node_nid)) {
+            $keep_node_nid = $duplicate_node->nid;
+            $keep_node_status = $duplicate_node->status;
+          }
+          // Defining the node which is going to be kept among duplicates.
+          if (isset($keep_node_nid)) {
+            // If "Keep at least one published node." is checked
+            // keeping the first published node in the group.
+            if ($prioritize_published_nodes) {
+              if ($duplicate_node->status && !$keep_node_status) {
+                $keep_node_nid = $duplicate_node->nid;
+                $keep_node_status = $duplicate_node->status;
+              }
+            }
+          }
+        }
+        reset($duplicate_node_group);
+        // Defining the group table first rows.
+        $duplicate_node_group_table_rows[] = array(
+          array(
+            'header' => TRUE,
+            'data' => $node_group_title,
+          ),
+          array(
+            'data' => $node_group_field_value,
+            'colspan' => count($duplicate_node_group_table_header) - 1,
+          ),
+        );
+        $duplicate_node_group_table_rows[] = $duplicate_node_group_table_header;
+        // Construction of duplicate node group table.
+        foreach ($duplicate_node_group as $duplicate_node) {
+          $duplicate_node_group_table_row = array();
+          // Data for 'remove' column :
+          if (isset($keep_node_nid) && $keep_node_nid == $duplicate_node->nid) {
+            $duplicate_node_group_table_row[] = array(
+              'data' => '<span style="color:red;">' . t('No') . '</span>',
+            );
+          }
+          else {
+            $duplicate_node_group_table_row[] = array(
+              'data' => '<span style="color:green;">' . t('Yes') . '</span>',
+            );
+          }
+          // Data for 'title' column :
+          $duplicate_node_group_table_row[] = array(
+            'data' => l($duplicate_node->title, 'node/' . $duplicate_node->nid, array('attributes' => array('onclick' => 'window.open(this.href,parseInt(Math.random()*1000));return false;'))),
+          );
+          // Data for 'author' column :
+          $duplicate_node_group_table_row[] = array(
+            'data' => $duplicate_node->name,
+          );
+          // Data for 'status' column :
+          if ($duplicate_node->status) {
+            $duplicate_node_group_table_row[] = array(
+              'data' => '<span style="color:black;">' . t('Yes') . '</span>',
+            );
+          }
+          else {
+            $duplicate_node_group_table_row[] = array(
+              'data' => '<span style="color:gray;">' . t('No') . '</span>',
+            );
+          }
+          // Data for 'updated' column :
+          $duplicate_node_group_table_row[] = array(
+            'data' => format_date($duplicate_node->changed),
+          );
+          // Data for 'created' column :
+          $duplicate_node_group_table_row[] = array(
+            'data' => format_date($duplicate_node->created),
+          );
+          // Adding new row to rows array.
+          $duplicate_node_group_table_rows[] = $duplicate_node_group_table_row;
+        }
+        // End of duplicate node group table construction.
+      }
+
+      $table_output = theme('table', array(
+        'rows' => $duplicate_node_group_table_rows,
+      ));
+
+      $output = array(
+        '#proceed' => TRUE,
+        '#element' => array(
+          '#type' => 'item',
+          '#title' => t('Results'),
+          '#markup' => $table_output,
+        ),
+      );
+
+      return $output;
+    }
+  }
+
+  watchdog('remove_duplicates', 'Search - No duplicates found for node [@node_type_machine_name] according to field [@node_field_machine_name]', array(
+    '@node_type_machine_name' => $node_type_machine_name,
+    '@node_field_machine_name' => $node_field_machine_name,
+  ), WATCHDOG_INFO);
+
+  // Outputs a no duplicates found message.
+  $text_output = t('No duplicates for node type "@nodetype" according to field "@nodetypefield".', array(
+    '@nodetype' => ((string) $node_types[$node_type_machine_name]),
+    '@nodetypefield' => ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]),
+  ));
+
+  $html_output = '<div class="description"><p><span style="font-weight:bold;color:red;">' . $text_output . '</span></p></div>';
+
+  $output = array(
+    '#proceed' => FALSE,
+    '#element' => array(
+      '#type' => 'item',
+      '#title' => t('Results'),
+      '#markup' => $html_output,
+    ),
+  );
+
+  return $output;
+}
+
+/**
+ * Get a tableselect data output.
+ *
+ * @param string $node_type_machine_name
+ *   The fetched node type.
+ *
+ * @param string $node_field_machine_name
+ *   The {field} used to find duplicates.
+ *
+ * @param bool $prioritize_published_nodes
+ *   If TRUE, the last published node in a set of duplicate nodes will be kept.
+ *   Otherwise, the first node in a set of duplicate nodes will be kept.
+ *
+ * @param bool $case_sensitive
+ *   If TRUE, duplicates detection is case sensitive
+ *   Otherwise, duplicates detection is case insensitive.
+ *
+ * @return array
+ *   An array with 2 keys :
+ *    #element A datastructure to use with custom tableselect form element.
+ *    #proceed A boolean indicating whether or not duplicates were found.
+ */
+function _remove_duplicates_get_tableselect_output($node_type_machine_name, $node_field_machine_name, $prioritize_published_nodes, $case_sensitive) {
+
+  $node_types = node_type_get_names();
+  $node_types_fields = _remove_duplicates_get_node_types_fields();
+
+  $duplicate_node_groups = _remove_duplicates_get_duplicate_node_groups($node_type_machine_name, $node_field_machine_name, $case_sensitive);
+
+  if (is_array($duplicate_node_groups)) {
+
+    if (isset($duplicate_node_groups['count']) && is_array($duplicate_node_groups['count'])) {
+      $count_nodes = (array_key_exists('nodes', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['nodes'] : 0;
+      $count_node_groups = (array_key_exists('node_groups', $duplicate_node_groups['count'])) ? $duplicate_node_groups['count']['node_groups'] : 0;
+
+      watchdog('remove_duplicates', 'Search - Found duplicate nodes : [@count_nodes] | node groups : [@count_node_groups]', array(
+        '@count_nodes' => $count_nodes,
+        '@count_node_groups' => $count_node_groups,
+      ), WATCHDOG_INFO);
+
+      watchdog('remove_duplicates', 'Search - Duplicate nodes to remove estimate : [@nodes_to_remove_estimate]', array(
+        '@nodes_to_remove_estimate' => $count_nodes - $count_node_groups,
+      ), WATCHDOG_INFO);
+    }
+
+    if (isset($duplicate_node_groups['data']) && is_array($duplicate_node_groups['data']) && count($duplicate_node_groups['data'])) {
+      // Outputs a data structure to use with
+      // Remove Duplicates specific tableselect.
+      // Default duplicate node group table header.
+      $duplicate_node_group_table_header = array(
+        'remove' => t('remove'),
+        'title' => t('title'),
+        'author' => t('author'),
+        'status' => t('published'),
+        'updated' => t('updated'),
+        'created' => t('created'),
+      );
+      // Construction of duplicate node group tables.
+      $duplicate_node_group_tables = array();
+      // Construction of duplicate node group tables title.
+      $duplicate_node_group_tables['#header'] = array(
+        array(
+          'header' => TRUE,
+          'data' => t('Found Duplicates'),
+          'colspan' => count($duplicate_node_group_table_header),
+        ),
+      );
+      // Construction of duplicate node group tables.
+      $duplicate_node_group_tables['#tables'] = array();
+      foreach ($duplicate_node_groups['data'] as $duplicate_node_group) {
+        // Construction of duplicate node group table.
+        $duplicate_node_group_table = array();
+        // Construction of duplicate node group table header.
+        $duplicate_node_group_table['#header'] = array();
+        // Defining the duplicate group header prefix.
+        $node_group_title = $node_group_field_name = $node_group_field_value = NULL;
+        if (is_array($duplicate_node_group) && count($duplicate_node_group)) {
+          $field_common_name = _remove_duplicates_get_field_common_name($node_field_machine_name);
+          $node_group_field_value = array();
+          foreach ($duplicate_node_group as $duplicate_node) {
+            if (is_object($duplicate_node) && !empty($duplicate_node->$field_common_name)) {
+              $node_group_field_value[((string) $duplicate_node->$field_common_name)] = ((string) $duplicate_node->$field_common_name);
+            }
+          }
+          $node_group_field_name = ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]);
+          $node_group_field_value = '"' . implode('" ' . t('or') . ' "', $node_group_field_value) . '"';
+          $node_group_title = t('Where "@nodetypefield" is', array(
+            '@nodetypefield' => $node_group_field_name,
+          ));
+        }
+
+        $duplicate_node_group_table['#header']['#prefix'] = array(
+          array(
+            'header' => TRUE,
+            'data' => $node_group_title,
+          ),
+          array(
+            'data' => $node_group_field_value,
+            'colspan' => count($duplicate_node_group_table_header) - 1,
+          ),
+        );
+        // Defining the duplicate group header root.
+        $duplicate_node_group_table['#header']['#root'] = $duplicate_node_group_table_header;
+        // Defining the default kept node among duplicates.
+        $keep_node_nid = $keep_node_status = NULL;
+        foreach ($duplicate_node_group as $duplicate_node) {
+          // Defaults to keeping the first node in the group.
+          if (empty($keep_node_nid)) {
+            $keep_node_nid = $duplicate_node->nid;
+            $keep_node_status = $duplicate_node->status;
+          }
+          // Defining the node which is going to be kept among duplicates.
+          if (isset($keep_node_nid)) {
+            // If "Keep at least one published node." is checked
+            // keeping the first published node in the group.
+            if ($prioritize_published_nodes) {
+              if ($duplicate_node->status && !$keep_node_status) {
+                $keep_node_nid = $duplicate_node->nid;
+                $keep_node_status = $duplicate_node->status;
+              }
+            }
+          }
+        }
+        reset($duplicate_node_group);
+        // Defining the duplicate group options.
+        $duplicate_node_group_table['#value'] = array();
+        $duplicate_node_group_table['#options'] = array();
+        $duplicate_node_group_table['#default_value'] = array();
+        foreach ($duplicate_node_group as $duplicate_node) {
+          $duplicate_node_group_table['#options'][$duplicate_node->nid] = array();
+          // Data for 'remove' column :
+          $duplicate_node_group_table['#value'][$duplicate_node->nid] = $duplicate_node->nid;
+          if (isset($keep_node_nid) && $keep_node_nid == $duplicate_node->nid) {
+            $duplicate_node_group_table['#options'][$duplicate_node->nid]['remove'] = '<span style="color:red;">' . t('No') . '</span>';
+          }
+          else {
+            $duplicate_node_group_table['#default_value'][$duplicate_node->nid] = $duplicate_node->nid;
+            $duplicate_node_group_table['#options'][$duplicate_node->nid]['remove'] = '<span style="color:green;">' . t('Yes') . '</span>';
+          }
+          // Data for 'title' column :
+          $duplicate_node_group_table['#options'][$duplicate_node->nid]['title'] = l($duplicate_node->title, 'node/' . $duplicate_node->nid, array('attributes' => array('onclick' => 'window.open(this.href,parseInt(Math.random()*1000));return false;')));
+          // Data for 'author' column :
+          $duplicate_node_group_table['#options'][$duplicate_node->nid]['author'] = $duplicate_node->name;
+          // Data for 'status' column :
+          if ($duplicate_node->status) {
+            $duplicate_node_group_table['#options'][$duplicate_node->nid]['status'] = '<span style="color:black;">' . t('Yes') . '</span>';
+          }
+          else {
+            $duplicate_node_group_table['#options'][$duplicate_node->nid]['status'] = '<span style="color:gray;">' . t('No') . '</span>';
+          }
+          // Data for 'updated' column :
+          $duplicate_node_group_table['#options'][$duplicate_node->nid]['updated'] = format_date($duplicate_node->changed);
+          // Data for 'created' column :
+          $duplicate_node_group_table['#options'][$duplicate_node->nid]['created'] = format_date($duplicate_node->created);
+          // End of duplicate node group options construction.
+        }
+        $duplicate_node_group_tables['#tables'][] = $duplicate_node_group_table;
+      }
+
+      // Custom table select element output to use with forms.
+      $output = array(
+        '#proceed' => TRUE,
+        '#element' => array(
+          '#title' => t('Results'),
+          '#type' => 'remove_duplicates_tableselect',
+          '#options' => $duplicate_node_group_tables,
+        ),
+      );
+
+      return $output;
+    }
+  }
+
+  watchdog('remove_duplicates', 'Search - No duplicates found for node [@node_type_machine_name] according to field [@node_field_machine_name]', array(
+    '@node_type_machine_name' => $node_type_machine_name,
+    '@node_field_machine_name' => $node_field_machine_name,
+  ), WATCHDOG_INFO);
+
+  // Outputs a no duplicates found message.
+  $text_output = t('No duplicates for node type "@nodetype" according to field "@nodetypefield".', array(
+    '@nodetype' => ((string) $node_types[$node_type_machine_name]),
+    '@nodetypefield' => ((string) $node_types_fields[$node_type_machine_name][$node_field_machine_name]),
+  ));
+
+  $html_output = '<div class="description"><p><span style="font-weight:bold;color:red;">' . $text_output . '</span></p></div>';
+
+  $output = array(
+    '#proceed' => FALSE,
+    '#element' => array(
+      '#type' => 'item',
+      '#title' => t('Results'),
+      '#markup' => $html_output,
+    ),
+  );
+
+  return $output;
+}
+
+/**
+ * Implements hook_element_info().
+ */
+function remove_duplicates_element_info() {
+  return array(
+    'remove_duplicates_tableselect' => array(
+      '#tree' => TRUE,
+      '#input' => TRUE,
+      '#options' => array(),
+      '#process' => array(
+        'remove_duplicates_form_process_tableselect',
+      ),
+      '#theme' => 'remove_duplicates_tableselect',
+    ));
+}
+
+/**
+ * Process function for Remove Duplicates tableselect.
+ */
+function remove_duplicates_form_process_tableselect($element, $form_state, $complete_form) {
+  if (!empty($element['#options']) && (isset($element['#options']['#tables']) && is_array($element['#options']['#tables']) && count($element['#options']['#tables']))) {
+    $element['#value'] = (isset($element['#value']) && is_array($element['#value'])) ? $element['#value'] : array();
+    foreach ($element['#options']['#tables'] as &$sub_element) {
+      $sub_element['#tree'] = TRUE;
+      $value = (isset($sub_element['#value']) && is_array($sub_element['#value'])) ? $sub_element['#value'] : array();
+      $element['#value'] = array_unique(array_merge($element['#value'], $value));
+      if (isset($sub_element['#options']) && is_array($sub_element['#options']) && count($sub_element['#options'])) {
+        if (!isset($sub_element['#default_value']) || $sub_element['#default_value'] === 0) {
+          $sub_element['#default_value'] = array();
+        }
+        // Create a checkbox for each item in #options in such a way that the
+        // value of the tableselect element behaves as if it had been of type
+        // checkboxes.
+        foreach ($sub_element['#options'] as $key => $choice) {
+          // Do not overwrite manually created children.
+          if (!isset($element[$key])) {
+            $title = '';
+            if (!empty($sub_element['#options'][$key]['title']['data']['#title'])) {
+              $title = t('Update @title', array(
+                '@title' => $sub_element['#options'][$key]['title']['data']['#title'],
+              ));
+            }
+            $checked = array();
+            if (isset($value[$key]) && isset($sub_element['#default_value'][$key])) {
+              $checked = array('checked' => 'checked');
+            }
+            $element[$key] = array(
+              '#type' => 'checkbox',
+              '#title' => $title,
+              '#title_display' => 'invisible',
+              '#return_value' => $key,
+              '#default_value' => (!empty($checked) && isset($value[$key])) ? $key : NULL,
+              '#attributes' => (isset($sub_element['#attributes']) ? $sub_element['#attributes'] : array()) + $checked,
+            );
+            $element['#options'][$key] = TRUE;
+            if (isset($sub_element['#options'][$key]['#weight'])) {
+              $element[$key]['#weight'] = $sub_element['#options'][$key]['#weight'];
+            }
+          }
+        }
+      }
+      else {
+        $sub_element['#value'] = array();
+      }
+    }
+  }
+  return $element;
+}
+
+/**
+ * Implements hook_theme().
+ *
+ * Remove Duplicates tableselect theme function registration.
+ */
+function remove_duplicates_theme($existing, $type, $theme, $path) {
+  return array(
+    'remove_duplicates_tableselect' => array(
+      'render element' => 'element',
+    ),
+  );
+}
+
+/**
+ * Implements hook_theme().
+ *
+ * Remove Duplicates tableselect theme function implementation.
+ */
+function theme_remove_duplicates_tableselect($variables) {
+  $element = $variables['element'];
+  if (isset($element['#options']) && is_array($element['#options']['#tables'])) {
+    $rows = array();
+    if (!empty($element['#options']['#header']) && is_array($element['#options']['#header'])) {
+      // Add an empty header to provide room for
+      // the checkboxes in the first table column.
+      array_unshift($element['#options']['#header'], array('header' => TRUE, 'data' => array()));
+      $rows[] = $element['#options']['#header'];
+    }
+    foreach ($element['#options']['#tables'] as $sub_element) {
+      // Add an empty header to provide room for
+      // the checkboxes in the first table column.
+      if (!empty($sub_element['#header']) && is_array($sub_element['#header'])) {
+        if (!empty($sub_element['#header']['#prefix']) && is_array($sub_element['#header']['#prefix'])) {
+          array_unshift($sub_element['#header']['#prefix'], array('header' => TRUE, 'data' => array()));
+          $rows[] = $sub_element['#header']['#prefix'];
+        }
+        if (!empty($sub_element['#header']['#root']) && is_array($sub_element['#header']['#root'])) {
+          $header = $sub_element['#header']['#root'];
+          foreach ($sub_element['#header']['#root'] as $fieldname => $title) {
+            $sub_element['#header']['#root'][$fieldname] = array(
+              'header' => TRUE,
+              'data' => $title,
+            );
+          }
+          array_unshift($sub_element['#header']['#root'], array('header' => TRUE, 'data' => array()));
+          $rows[] = $sub_element['#header']['#root'];
+        }
+      }
+      if (!empty($sub_element['#options']) && is_array($sub_element['#options'])) {
+        // Generate a table row for each selectable item in #options.
+        foreach (element_children($sub_element['#options']) as $key) {
+          $row = array('data' => array());
+          if (isset($sub_element['#options'][$key]['#attributes'])) {
+            $row += $sub_element['#options'][$key]['#attributes'];
+          }
+          // Render the checkbox / radio element.
+          $row['data'][] = drupal_render($element[$key]);
+          // As theme_table only maps header and row columns by order,
+          // create the correct order by iterating over the header fields.
+          if (!empty($header)) {
+            foreach ($header as $fieldname => $title) {
+              $row['data'][] = $sub_element['#options'][$key][$fieldname];
+            }
+          }
+          $rows[] = $row;
+        }
+      }
+    }
+    return theme('table', array('rows' => $rows, 'attributes' => $element['#attributes']));
+  }
+}

+ 1 - 1
sites/all/modules/features/materio_administration/materio_administration.default_elysia_cron_rules.inc

@@ -18,7 +18,7 @@ function materio_administration_default_elysia_cron_rules() {
   $cron_rule->rule = NULL;
   $cron_rule->weight = NULL;
   $cron_rule->context = NULL;
-  $cron_rules['workflow_cron'] = $cron_rule;
+  $cron_rules[''] = $cron_rule;
 
   return $cron_rules;
 

+ 9 - 0
sites/all/modules/features/materio_administration/materio_administration.features.user_permission.inc

@@ -38,6 +38,15 @@ function materio_administration_user_default_permissions() {
     'module' => 'entity_translation',
   );
 
+  // Exported permission: 'administer remove_duplicates'.
+  $permissions['administer remove_duplicates'] = array(
+    'name' => 'administer remove_duplicates',
+    'roles' => array(
+      'root' => 'root',
+    ),
+    'module' => 'remove_duplicates',
+  );
+
   // Exported permission: 'administer workflow'.
   $permissions['administer workflow'] = array(
     'name' => 'administer workflow',

+ 2 - 0
sites/all/modules/features/materio_administration/materio_administration.info

@@ -17,6 +17,7 @@ dependencies[] = logintoboggan_rules
 dependencies[] = logintoboggan_variable
 dependencies[] = materio_user
 dependencies[] = menu
+dependencies[] = remove_duplicates
 dependencies[] = role_delegation
 dependencies[] = simplenews
 dependencies[] = strongarm
@@ -62,6 +63,7 @@ features[menu_links][] = navigation_users:admin/users
 features[user_permission][] = access administration menu
 features[user_permission][] = access workflow summary views
 features[user_permission][] = administer entity translation
+features[user_permission][] = administer remove_duplicates
 features[user_permission][] = administer workflow
 features[user_permission][] = assign Adhérent role
 features[user_permission][] = assign Contact opérationnel role

+ 4 - 4
sites/all/modules/features/materio_administration/materio_administration.views_default.inc

@@ -1946,7 +1946,7 @@ function materio_administration_views_default_views() {
   $handler->display->display_options['fields']['uid']['exclude'] = TRUE;
   $handler->display->display_options['fields']['uid']['element_label_colon'] = FALSE;
   $handler->display->display_options['fields']['uid']['link_to_user'] = FALSE;
-  /* Field: Profile: Titre */
+  /* Field: Profile: Title */
   $handler->display->display_options['fields']['field_private_name_title']['id'] = 'field_private_name_title';
   $handler->display->display_options['fields']['field_private_name_title']['table'] = 'field_data_field_private_name_title';
   $handler->display->display_options['fields']['field_private_name_title']['field'] = 'field_private_name_title';
@@ -2008,7 +2008,7 @@ function materio_administration_views_default_views() {
   $handler->display->display_options['fields']['field_service']['alter']['text'] = 'service : [field_service]';
   $handler->display->display_options['fields']['field_service']['hide_empty'] = TRUE;
   $handler->display->display_options['fields']['field_service']['empty_zero'] = TRUE;
-  /* Field: Profile: Qualité */
+  /* Field: Profile: Quality */
   $handler->display->display_options['fields']['field_private_quality']['id'] = 'field_private_quality';
   $handler->display->display_options['fields']['field_private_quality']['table'] = 'field_data_field_private_quality';
   $handler->display->display_options['fields']['field_private_quality']['field'] = 'field_private_quality';
@@ -2333,7 +2333,7 @@ print user_pass_reset_url($account);
     t('Profile'),
     t('Utilisateur'),
     t('- Choose an operation -'),
-    t('Titre'),
+    t('Title'),
     t('Prénom'),
     t('Nom'),
     t('[field_first_name] [field_name]'),
@@ -2346,7 +2346,7 @@ print user_pass_reset_url($account);
     t('Organisation'),
     t('service'),
     t('service : [field_service]'),
-    t('Qualité'),
+    t('Quality'),
     t('quality : [field_private_quality]'),
     t('SIRET'),
     t('siret : [field_siret]'),