diff --git a/sites/all/modules/contrib/fields/cer/LICENSE.txt b/sites/all/modules/contrib/fields/cer/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/fields/cer/README.txt b/sites/all/modules/contrib/fields/cer/README.txt new file mode 100644 index 00000000..bc985ffa --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/README.txt @@ -0,0 +1,58 @@ +ACKNOWLEDGEMENTS + +As this is the next evolution of Corresponding Node References, +I would like to say thanks for all the work done over on Corresponding Node +References. + +DESCRIPTION + +It syncs the entity reference between two entity types which have an entity +reference to each other, so double editing entities is no longer needed. If one +entity has a reference, the other entity also receives a reference to the saved +entity if it is referenced in that entity. + +DEPENDENCIES + +7.x: Entity Reference + +EXAMPLE + +Entity type A has an entity reference to entity type B and entity type B has an +entity reference to entity type A. When you create entity X of type A and +reference it to entity Y of type B entity Y will also receive an update in its +entity reference field pointing to entity X. + +KNOWN ISSUES + +- Support for entity reference fields in field collections is still a work in progress. + CER has no native support for entities that are wrapped by other entities (i.e., + field collections), and implementing this properly will require extensive changes + to many parts of CER. For this reason, field collection support is on hold until + a few other major issues in the queue are sorted out. The thread for field collection + support is http://drupal.org/node/1729666. + +- Support for multi-language entities is, at the time of this writing, flaky at best. + There is a patch to implement better multi-language support, available at + http://drupal.org/node/1961026. If this patch works well for you, PLEASE post + in that issue to say that it worked so that the patch can be reviewed by + the community before being committed into CER. + +- If you're updating CER from 1.x to 2.x, you should rebuild your theme registry. + This is because the reference labels on CER's admin page were made themeable + in 2.x, so you'll need to make Drupal recognize the new theme hook. + +INSTALL + +- To install enable the module at admin/build/modules +- Create entity type A +- Create entity type B +- Create a entity reference field on entity type A pointing to entity B +- Create a entity reference field on entity type B pointing to entity A +- Go to the settings page at admin/config/system/cer. + Select to enable the corresponding referencing for these node types pointing + to each other. +- Create some entities and reference them to each other + +MAINTAINER + +Questions, comments, etc. should be directed to phenaproxima (djphenaproxima@gmail.com). \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/cer/cer.admin.inc b/sites/all/modules/contrib/fields/cer/cer.admin.inc new file mode 100644 index 00000000..e4f0fdf9 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/cer.admin.inc @@ -0,0 +1,253 @@ + $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $field['field_name'], $bundle); + $channels = array_merge($channels, _cer_find_channels($instance)); + } + } + } + + if (empty($channels)) { + drupal_set_message(t('There are no entity reference fields that can correspond.'), 'warning'); + } + else { + $mapping = array(); + foreach ($channels as $count => $key) { + $formatted_key = str_replace(' ', '*', $key); + + $mapping[$count] = $formatted_key; + + $form['values']["enabled_{$count}"] = array( + '#type' => 'checkbox', + '#default_value' => cer_preset_enabled($formatted_key), + '#title' => theme('cer_label', array('key' => $key)), + ); + } + + // We are using a hidden field to submit the configuration because on + // some systems the checkbox name length is limited, and using + // the key would cause the length to be too long. (Issue #558612) + $form['mapping'] = array( + '#type' => 'hidden', + '#value' => serialize($mapping), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + } + + + return $form; +} + +/** + * Submit function for settings form. + */ +function cer_settings_form_submit($form, $form_state) { + ctools_include('export'); + $query_values = array(); + + $mapping = unserialize($form_state['values']['mapping']); + + foreach ($form_state['values'] as $key => $value) { + $keys = explode('_', $key); + if ($keys[0] == 'enabled') { + $query_values[$mapping[$keys[1]]] = $value; + } + } + + // load all existing presets + $presets = ctools_export_crud_load_all('cer'); + + foreach ($query_values as $key => $value) { + // get preset object (create new one if it doesn't exist already). + $preset = empty($presets[$key]) ? ctools_export_crud_new('cer') : $presets[$key]; + + // set and save value + if (empty($preset->entity_types_content_fields)) { + $preset->entity_types_content_fields = $key; + } + $preset->enabled = $value; + ctools_export_crud_save('cer', $preset); + + // remove from list of presets, so we know which ones are still used + if (isset($presets[$key])) { + unset($presets[$key]); + } + } + + drupal_set_message(t('The configuration has been saved.')); +} + +/** + * Allows batch updating of existing entities. + */ +function cer_update_form($form = array(), &$form_state) { + $form['type'] = array( + '#type' => 'select', + '#title' => t('Entity type'), + '#required' => TRUE, + '#options' => array(), + '#description' => t('Select the entity type that you want to update.'), + ); + foreach (entity_get_info() as $type => $class) { + $form['type']['#options'][$type] = $class['label']; + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + ); + + return $form; +} + +/** + * The update form. + * Allows updating of current entitys. + */ +function cer_update_form_submit($form, &$form_state) { + $batch = array( + 'finished' => 'cer_batch_update_existing_finished', + 'title' => t('Processing'), + 'init_message' => t('Preparing to update corresponding entity references for existing entities...'), + 'progress_message' => t('Processing entities...'), + 'error_message' => t('Corresponding entity references - existing entity update has encountered an error.'), + ); + + $entities = entity_load($form_state['values']['type'], FALSE); + foreach ($entities as $entity) { + $batch['operations'][] = array('cer_processing_entity', array('update', $entity, $form_state['values']['type'])); + } + batch_set($batch); +} + +/** + * The purpose of this function is to answer this question: I am a field instance. Which other + * fields reference the entity that owns me? And of those instances, which ones can I reference? + * The answer is returned as an array of CER keys: "entity1 bundle1 field1 entity2 bundle2 field2". + * + * @param array $instance + * Field instance info, as returned by field_info_instance(). + * + * @return array + */ +function _cer_find_channels($instance) { + $channels = array(); + + $my_id = $instance['entity_type'] . ' ' . $instance['bundle'] . ' ' . $instance['field_name']; + $my_info = field_info_field($instance['field_name']); + $my_targets = _cer_get_target_bundles($my_info); + $my_target_type = $my_info['settings']['target_type']; + + $referrers = _cer_find_referrers($instance['entity_type'], $instance['bundle']); + foreach ($referrers as $referrer) { + if (isset($referrer['bundles'][$my_target_type])) { + if (empty($my_targets)) { + $bundles = $referrer['bundles'][$my_target_type]; + } + else { + $bundles = array_intersect($referrer['bundles'][$my_target_type], $my_targets); + } + + foreach ($bundles as $bundle) { + $channels[] = "{$my_id} {$my_target_type} {$bundle} " . $referrer['field_name']; + } + } + } + + return $channels; +} + +/** + * Find all fields that can reference the given entity type and bundle. + * + * @param $entity_type + * The entity type that can be referenced. + * @param $bundle + * The bundle that can be referenced. + * + * @return array + */ +function _cer_find_referrers($entity_type, $bundle) { + $referrers = array(); + foreach (_cer_get_fields() as $field) { + if ($field['settings']['target_type'] == $entity_type) { + $target_bundles = _cer_get_target_bundles($field); + if (empty($target_bundles) || in_array($bundle, $target_bundles)) { + $referrers[] = $field; + } + } + } + + return $referrers; +} + +/** + * Find all bundles reference-able by a given field. If all bundles are reference-able, + * an empty array is returned. + * + * @param $field + * Field info array as returned by field_info_field(). + * + * @return array + */ +function _cer_get_target_bundles($field) { + $target_bundles = array(); + + // If the reference field is using a view, load the view and see if it's filtering by the entity + // type's bundle filter. If it is, the filter values are the target bundles. Otherwise, + // assume that all bundles can be referenced. + // + // @todo Support contextual filters? + // + // NOTE: Selection handlers (i.e., $field['settings']['handler']) are plugins owned by + // Entity Reference. There is no method defined to get an array of referenceable + // bundles, but hopefully, if CER gains enough traction in the community, such a + // method can be added to the EntityReference_SelectionHandler interface. This + // function could then be deprecated, which would be a more flexible, future-proof + // method of finding a field's target bundles. + // + if ($field['settings']['handler'] == 'views') { + $view = views_get_view($field['settings']['handler_settings']['view']['view_name']); + $view->set_display($field['settings']['handler_settings']['view']['display_name']); + + $info = entity_get_info($field['settings']['target_type']); + if ($info['entity keys']['bundle'] && $handler = $view->display_handler->get_handler('filter', $info['entity keys']['bundle'])) { + $target_bundles = $handler->value; + } + } + else { + $target_bundles = $field['settings']['handler_settings']['target_bundles']; + } + + return $target_bundles; +} + +/** + * Get the Field API definitions for all entity reference fields. + * + * @return array + */ +function _cer_get_fields() { + static $fields; + if (!isset($fields)) { + $fields = array_map('field_info_field', db_select('field_config', 'fc')->fields('fc', array('field_name'))->condition('type', 'entityreference')->execute()->fetchCol()); + } + return $fields; +} \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/cer/cer.info b/sites/all/modules/contrib/fields/cer/cer.info new file mode 100644 index 00000000..ea06b5a2 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/cer.info @@ -0,0 +1,16 @@ +name = Corresponding Entity References +description = Syncs the entity references between entity types which have an entityreference to each other. +core = 7.x +package = Fields +dependencies[] = entityreference +dependencies[] = ctools + +files[] = handler.inc + +configure = admin/config/system/cer +; Information added by drupal.org packaging script on 2013-05-01 +version = "7.x-2.x-dev" +core = "7.x" +project = "cer" +datestamp = "1367412087" + diff --git a/sites/all/modules/contrib/fields/cer/cer.install b/sites/all/modules/contrib/fields/cer/cer.install new file mode 100644 index 00000000..4e7173f1 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/cer.install @@ -0,0 +1,41 @@ + t('Saves the content types and entity reference fields for which the corresponding entity reference is enabled'), + 'fields' => array( + 'entity_types_content_fields' => array('type' => 'varchar', 'length' => 200, 'not null' => TRUE, 'default' => ''), + 'enabled' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0), + ), + 'primary key' => array('entity_types_content_fields'), + 'export' => array( + 'key' => 'entity_types_content_fields', + 'status' => 'enabled', + 'primary key' => 'entity_types_content_fields', + 'key name' => 'Corresponding entity reference', + 'identifier' => 'cnr_obj', + 'api' => array( + 'api' => 'default_cer_presets', + 'owner' => 'cer', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + ); + return $schema; +} + +/** + * Rename table to shorten module name. + */ +function cer_update_7001() { + db_rename_table('corresponding_entity_references', 'cer'); +} \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/cer/cer.module b/sites/all/modules/contrib/fields/cer/cer.module new file mode 100644 index 00000000..34952253 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/cer.module @@ -0,0 +1,300 @@ + 'Corresponding entity references', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cer_settings_form'), + 'access arguments' => array('administer cer settings'), + 'file' => 'cer.admin.inc', + 'type' => MENU_NORMAL_ITEM, + ); + $items['admin/config/system/cer/references'] = array( + 'title' => 'References', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cer_settings_form'), + 'access arguments' => array('administer cer settings'), + 'file' => 'cer.admin.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['admin/config/system/cer/update'] = array( + 'title' => 'Update existing entities', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('cer_update_form'), + 'access arguments' => array('administer cer settings'), + 'file' => 'cer.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + + return $items; +} + +/** + * Implements hook_permission(). + */ +function cer_permission() { + return array( + 'administer cer settings' => array( + 'title' => t('Administer corresponding entity reference settings'), + ) + ); +} + +/** + * Implements hook_help(). + */ +function cer_help($path, $arg) { + $output = ''; + if ($path == 'admin/config/system/cer') { + $output .= t('Check which entity references should listen to each other. When checking a check box a reference on entity type A to entity B will automatically update the entity reference field on entity B adding an entry which points to entity A.'); + } + elseif ($path == 'admin/config/system/cer/update') { + $output .= t('This will update all the existing entities for the selected content types so that their entity reference fields are in sync.'); + $output .= '
'; + $output .= t('This process may take a long time depending on the number of entities you are updating.'); + $output .= '

'; + $output .= t('When the process is finished you will see a count of the number of entities that were updated.'); + } + return $output; +} + +/** + * Implements hook_field_delete_instance(). + */ +function cer_field_delete_instance($instance) { + foreach (cer_preset_load_enabled() as $row) { + $keys = explode('*', $row->entity_types_content_fields); + + if (($keys[0] == $instance['entity_type'] && $keys[1] == $instance['bundle'] && $keys[2] == $instance['field_name']) || ($keys[3] == $instance['entity_type'] && $keys[4] == $instance['bundle'] && $keys[5] == $instance['field_name'])) { + cer_preset_delete($row->entity_types_content_fields); + } + } +} + +/** + * Implements hook_field_delete_field(). + */ +function cer_field_delete_field($field) { + foreach (cer_preset_load_enabled() as $row) { + $keys = explode('*', $row->entity_types_content_fields); + + if ($keys[2] == $field['field_name'] || $keys[5] == $field['field_name']) { + cer_preset_delete($row->entity_types_content_fields); + } + } +} + +/** + * Implements hook_theme(). + */ +function cer_theme() { + return array( + 'cer_label' => array( + 'variables' => array('key' => ''), + ), + ); +} + +function theme_cer_label($variables) { + $key = explode(' ', $variables['key']); + + $local = field_info_instance($key[0], $key[2], $key[1]); + $remote = field_info_instance($key[3], $key[5], $key[4]); + + $message = 'Correspond %local_label on !local_entity(s) of type %local_bundle with %remote_label on !remote_entity(s) of type %remote_bundle.'; + + $variables = array( + '%local_label' => $local['label'], + '!local_field' => $local['field_name'], + '!local_entity' => $local['entity_type'], + '%local_bundle' => $local['bundle'], + '%remote_label' => $remote['label'], + '!remote_field' => $remote['field_name'], + '!remote_entity' => $remote['entity_type'], + '%remote_bundle' => $remote['bundle'], + ); + + return t($message, $variables); +} + +/** + * Implements hook_entity_insert(). + */ +function cer_entity_insert($entity, $type) { + cer_processing_entity('insert', $entity, $type); +} + +/** + * Implements hook_entity_update(). + */ +function cer_entity_update($entity, $type) { + cer_processing_entity('update', $entity, $type); +} + +/** + * Implements hook_entity_delete(). + */ +function cer_entity_delete($entity, $type) { + cer_processing_entity('delete', $entity, $type); +} + +/** + * Load enabled CER presets. + */ +function cer_preset_load_enabled() { + ctools_include('export'); + return ctools_export_load_object('cer', 'conditions', array('enabled' => 1)); +} + +/** + * Return CER preset by key. + */ +function cer_preset_load($key) { + ctools_include('export'); + return ctools_export_crud_load('cer', $key); +} + +/** + * Return 1 if CER preset specified by given key is enabled. + */ +function cer_preset_enabled($key) { + $preset = cer_preset_load($key); + return empty($preset) ? 0 : $preset->enabled; +} + +/** + * Deletes or disables a given CER preset. + */ +function cer_preset_delete($key) { + ctools_include('export'); + + ctools_export_crud_delete('cer', $key); + ctools_export_crud_disable('cer', $key); + + ctools_export_load_object_reset('cer'); +} + +/** + * Process a entity's corresponding entity references. + * + * @param string $op + * The operation being performed on the entity (insert, update, or delete). + * + * @param object $entity + * The entity. + * + * @param string $entity_type + * The entity type. + * + * @param array $context + * Either the Batch API context (since this is the callback function used + * during bulk update) or NULL if we're not in a batch job. + */ +function cer_processing_entity($op, $entity, $entity_type, &$context = NULL) { + // If the entity is of the wrong type, entity_extract_IDs() will throw + // EntityMalformedException and rightfully bail out here. + list (, , $bundle) = entity_extract_IDs($entity_type, $entity); + + $result = cer_preset_load_enabled(); + + foreach ($result as $row) { + $keys = explode('*', $row->entity_types_content_fields); + + if ($keys[0] == $entity_type && $keys[1] == $bundle) { + try { + $handler = new CerHandler($row->entity_types_content_fields, $entity); + call_user_func(array($handler, $op)); + } + catch (CerException $e) { + if (isset($context)) { + $context['results']['errors'][] = $e; + } + else { + throw $e; + } + } + } + + if ($keys[3] == $entity_type && $keys[4] == $bundle) { + $preset = implode('*', array($keys[3], $keys[4], $keys[5], $keys[0], $keys[1], $keys[2])); + + try { + $handler = new CerHandler($preset, $entity); + call_user_func(array($handler, $op)); + } + catch (CerException $e) { + if (isset($context)) { + $context['results']['errors'][] = $e; + } + else { + throw $e; + } + } + } + } + + if (isset($context)) { + $context['results']['count']++; + } +} + +/** + * Batch 'finished' callback. + */ +function cer_batch_update_existing_finished($success, $results, $operations) { + if ($success) { + $message = format_plural($results['count'], '1 entity processed.', '@count entities processed.'); + + if (isset($results['errors'])) { + $type = 'warning'; + foreach ($results['errors'] as $e) { + drupal_set_message($e->getMessage(), 'error'); + } + } + else { + $type = 'status'; + } + drupal_set_message($message, $type); + } + else { + // An error occurred. $operations contains the operations that remained unprocessed. + $error_operation = reset($operations); + $message = 'An error occurred while processing ' . $error_operation[0] . ' with arguments:' . print_r($error_operation[0], TRUE); + drupal_set_message($message, 'error'); + } +} + +/** + * Implements hook_ctools_plugin_api(). + */ +function cer_ctools_plugin_api($owner, $api) { + if ($owner == 'cer' && $api == 'default_cer_presets') { + return array('version' => 1); + } +} + +/** + * Update field data. + * + * @param $node the referenced node to be updated. + */ +function _cer_update($entity_type, $entity) { + $entity->original = isset($entity->original) ? $entity->original : NULL; + + field_attach_presave($entity_type, $entity); + field_attach_update($entity_type, $entity); + + $extract_ids = entity_extract_IDs($entity_type, $entity); + $id = array_shift($extract_ids); + entity_get_controller($entity_type)->resetCache(array($id)); +} diff --git a/sites/all/modules/contrib/fields/cer/handler.inc b/sites/all/modules/contrib/fields/cer/handler.inc new file mode 100644 index 00000000..57c4f908 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/handler.inc @@ -0,0 +1,437 @@ + $preset))); + } + + $this->local = field_info_instance($keys[0], $keys[2], $keys[1]); + if ($this->local) { + $this->local['field'] = field_info_field($keys[2]); + } + else { + throw new CerException(t('Local field instance does not exist.')); + } + + $this->remote = field_info_instance($keys[3], $keys[5], $keys[4]); + if ($this->remote) { + $this->remote['field'] = field_info_field($keys[5]); + } + else { + throw new CerException(t('Remote field instance does not exist.')); + } + } + +} + +/** + * @class + * Generic CER handler with rudimentary language handling. + */ +class CerHandler extends CerHandlerBase implements CerHandlerInterface { + + /** + * The local (home) entity. + */ + protected $entity; + + /** + * The local entity's ID. + */ + protected $id; + + /** + * Implements CerHandlerInterface::__construct(). + */ + public function __construct($preset, $entity) { + parent::__construct($preset); + + // If $entity is of the wrong type, entity_extract_IDs() + // will throw EntityMalformedException here. + $extract_ids = entity_extract_IDs($this->local['entity_type'], $entity); + $this->id = array_shift($extract_ids); + + $this->entity = $entity; + } + + /** + * Implements CerHandlerInterface::insert(). + */ + public function insert() { + foreach ($this->getReferencedEntities() as $referenced_entity) { + $this->reference($referenced_entity); + _cer_update($this->remote['entity_type'], $referenced_entity); + } + } + + /** + * Implements CerHandlerInterface::update(). + */ + public function update() { + $original = isset($this->entity->original) ? $this->entity->original : $this->entity; + + $deleted = array_diff($this->getReferenceIDs($original, $this->local), $this->getLocalReferenceIDs()); + if ($deleted) { + $entities = entity_load($this->remote['entity_type'], $deleted); + foreach ($entities as $referenced_entity) { + $this->dereference($referenced_entity); + _cer_update($this->remote['entity_type'], $referenced_entity); + } + } + + $this->insert(); + } + + /** + * Implements CerHandlerInterface::delete(). + */ + public function delete() { + foreach ($this->getReferencedEntities() as $referenced_entity) { + $this->dereference($referenced_entity); + _cer_update($this->remote['entity_type'], $referenced_entity); + } + } + + /** + * Implements CerHandlerInterface::references(). + */ + public function references($entity) { + return in_array($this->getRemoteEntityID($entity), $this->getLocalReferenceIDs()); + } + + /** + * Implements CerHandlerInterface::referencedBy(). + */ + public function referencedBy($entity) { + return in_array($this->id, $this->getRemoteReferenceIDs($entity)); + } + + /** + * Implements CerHandlerInterface::referenceable(). + */ + public function referenceable($entity) { + $id = $this->getRemoteEntityID($entity); + + $allowed = array( + entityreference_get_selection_handler( + $this->local['field'], + $this->local, + $this->local['entity_type'], + $this->entity + ) + ->validateReferencableEntities(array($id)), + entityreference_get_selection_handler( + $this->remote['field'], + $this->remote, + $this->remote['entity_type'], + $entity + ) + ->validateReferencableEntities(array($this->id)), + ); + + return in_array($id, $allowed[0]) && in_array($this->id, $allowed[1]); + } + + /** + * Implements CerHandlerInterface::reference(). + */ + public function reference($entity) { + if ($this->referenceable($entity)) { + try { + $this->addReferenceTo($entity); + } + catch (CerException $e) { + // Fail silently + } + + try { + $this->addReferenceFrom($entity); + } + catch (CerException $e) { + // Fail silently + } + } + else { + throw new CerException(t('Cannot create invalid reference to remote entity.')); + } + } + + /** + * Implements CerHandlerInterface::dereference(). + */ + public function dereference($entity) { + if ($this->references($entity)) { + $id = $this->getRemoteEntityID($entity); + + foreach ($this->entity->{$this->local['field_name']} as $language => $references) { + foreach ($references as $delta => $reference) { + if ($reference['target_id'] == $id) { + unset($this->entity->{$this->local['field_name']}[$language][$delta]); + } + } + } + } + + if ($this->referencedBy($entity)) { + foreach ($entity->{$this->remote['field_name']} as $language => $references) { + foreach ($references as $delta => $reference) { + if ($reference['target_id'] == $this->id) { + unset($entity->{$this->remote['field_name']}[$language][$delta]); + } + } + } + } + } + + /** + * Creates a reference to the local entity on the remote entity. Throws CerException + * if the local entity is already referenced by the remote entity, or if the remote + * field cannot hold any more values. + * + * @param object $entity + * The remote entity. + */ + protected function addReferenceFrom($entity) { + if ($this->referencedBy($entity)) { + throw new CerException(t('Cannot create duplicate reference from remote entity.')); + } + elseif ($this->filled($this->getRemoteReferenceIDs($entity), $this->remote['field'])) { + throw new CerException(t('Remote field cannot support any more references.')); + } + else { + $languages = field_available_languages($this->remote['entity_type'], $this->remote['field']); + foreach ($languages as $language) { + $entity->{$this->remote['field_name']}[$language][] = array('target_id' => $this->id); + } + } + } + + /** + * Creates a reference to the remote entity on the local entity. Throws CerException + * if the local entity already references the remote entity, or if the field cannot + * hold any more values. + * + * @param object $entity + * The remote entity. + */ + protected function addReferenceTo($entity) { + $id = $this->getRemoteEntityID($entity); + + if ($this->references($entity)) { + throw new CerException(t('Cannot create duplicate reference to remote entity.')); + } + elseif ($this->filled($this->getLocalReferenceIDs(), $this->local['field'])) { + throw new CerException(t('Local field cannot support any more references.')); + } + else { + $languages = field_available_languages($this->local['entity_type'], $this->local['field']); + foreach ($languages as $language) { + $this->entity->{$this->local['field_name']}[$language][] = array('target_id' => $id); + } + } + } + + /** + * Get the ID of the remote entity. If the entity is of the wrong type, + * EntityMalformedException will be thrown. + * + * @param object $entity + * The remote entity. + * + * @return mixed + * The remote entity ID. + */ + protected function getRemoteEntityID($entity) { + $extract_ids = entity_extract_IDs($this->remote['entity_type'], $entity); + return array_shift($extract_ids); + } + + /** + * Gets all the entities referenced by the local entity. + * + * @return array + * Array of fully loaded referenced entities keyed by ID, or empty + * array if nothing has been referenced. + */ + protected function getReferencedEntities() { + $IDs = $this->getLocalReferenceIDs(); + return $IDs ? entity_load($this->remote['entity_type'], $IDs) : array(); + } + + /** + * Gets the IDs of the entities referenced by the local entity. + * + * @return array + * Array of entity IDs, empty array if there are no references. + */ + protected function getLocalReferenceIDs() { + return $this->getReferenceIDs($this->entity, $this->local); + } + + /** + * Gets the IDs of the entities referenced by $entity. + * + * @param object $entity + * The remote entity. + * + * @return array + * Array of entity IDs, empty array if there are no references. + */ + protected function getRemoteReferenceIDs($entity) { + return $this->getReferenceIDs($entity, $this->remote); + } + + /** + * Check if a field can support any more values. Formerly known as + * "reference overloading". + * + * @param array $references + * The values in the field. + * + * @param $field + * Field definition (i.e., from field_info_field). + * + * @return boolean + */ + private function filled($references, $field) { + return $field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && sizeof($references) >= $field['cardinality']; + } + + /** + * Gets all the referenced entity IDs from a specific field on $entity. + * + * @param object $entity + * The entity to scan for references. + * + * @param array $field + * Field or instance definition. + * + * @return array + * Array of unique IDs, empty if there are no references or the field + * does not exist on $entity. + */ + private function getReferenceIDs($entity, $field) { + $IDs = array(); + if (isset($entity->{$field['field_name']})) { + foreach ($entity->{$field['field_name']} as $references) { + foreach ($references as $reference) { + $IDs[] = $reference['target_id']; + } + } + } + return array_unique(array_filter($IDs)); + } + +} \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/cer/tests/cer_tests.info b/sites/all/modules/contrib/fields/cer/tests/cer_tests.info new file mode 100644 index 00000000..d7cac36d --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/tests/cer_tests.info @@ -0,0 +1,16 @@ +name = "CER Tests" +core = "7.x" +description = "Automated tests for CER." +hidden = TRUE + +dependencies[] = simpletest +dependencies[] = cer + +files[] = crud.test +files[] = fields.test +; Information added by drupal.org packaging script on 2013-05-01 +version = "7.x-2.x-dev" +core = "7.x" +project = "cer" +datestamp = "1367412087" + diff --git a/sites/all/modules/contrib/fields/cer/tests/cer_tests.module b/sites/all/modules/contrib/fields/cer/tests/cer_tests.module new file mode 100644 index 00000000..b3d9bbc7 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/tests/cer_tests.module @@ -0,0 +1 @@ + 'CRUD', + 'group' => 'Corresponding Entity Reference', + 'description' => 'Tests basic CER functionality.', + ); + } + + public function setUp() { + parent::setUp('field', 'field_sql_storage', 'ctools', 'entityreference', 'cer'); + + field_create_field(array( + 'field_name' => 'field_user', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'user', + ), + )); + field_create_field(array( + 'field_name' => 'field_node', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'node', + ), + )); + + field_create_instance(array( + 'field_name' => 'field_user', + 'entity_type' => 'node', + 'bundle' => 'page', + )); + field_create_instance(array( + 'field_name' => 'field_node', + 'entity_type' => 'user', + 'bundle' => 'user', + )); + + db_insert('cer')->fields(array( + 'entity_types_content_fields' => 'node*page*field_user*user*user*field_node', + 'enabled' => TRUE, + ))->execute(); + } + + public function testImplicitReferenceCreation() { + $uid = $this->drupalCreateUser()->uid; + + $referrers = array(); + for ($i = 0; $i < 5; $i++) { + $referrers[] = $this->drupalCreateNode(array( + 'type' => 'page', + 'field_user' => array( + 'und' => array( + array('target_id' => $uid), + ), + ), + ))->nid; + } + + $references = array(); + foreach (user_load($uid, TRUE)->field_node['und'] as $reference) { + $references[] = $reference['target_id']; + } + $this->assertFalse(array_diff($referrers, $references), 'Creating 5 referrers to a single entity creates 5 corresponding references on that entity.', 'CER'); + } + + public function testDuplicateReferencePrevention() { + $uid = $this->drupalCreateUser()->uid; + + $this->drupalCreateNode(array( + 'type' => 'page', + 'field_user' => array( + 'und' => array( + array('target_id' => $uid), + array('target_id' => $uid), + ), + ), + )); + + $account = user_load($uid, TRUE); + $this->assertEqual(sizeof($account->field_node['und']), 1, 'Creating two references to an entity from a single referrer creates one corresponding reference.', 'CER'); + } + + public function testExplicitReferenceCreation() { + $uid = $this->drupalCreateNode()->uid; + + $node = $this->drupalCreateNode(array('type' => 'page')); + $node->field_user['und'][0]['target_id'] = $uid; + node_save($node); + + $account = user_load($uid, TRUE); + $this->assertEqual($account->field_node['und'][0]['target_id'], $node->nid, 'Creating an explicit reference between to unrelated entities creates a corresponding reference.', 'CER'); + } + + public function testExplicitDereference() { + $uid = $this->drupalCreateUser()->uid; + + $nid = $this->drupalCreateNode(array( + 'type' => 'page', + 'field_user' => array( + 'und' => array( + array('target_id' => $uid), + ), + ), + ))->nid; + + $account = user_load($uid, TRUE); + $account->field_node = array(); + user_save($account); + + $node = node_load($nid, NULL, TRUE); + $this->assertFalse($node->field_user, 'Explicitly clearing a reference from the referenced entity clears the corresponding reference on the referrer.', 'CER'); + } + + public function testReferrerDeletion() { + $uid = $this->drupalCreateUser()->uid; + + $referrers = array(); + for ($i = 0; $i < 5; $i++) { + $referrers[] = $this->drupalCreateNode(array( + 'type' => 'page', + 'field_user' => array( + 'und' => array( + array('target_id' => $uid), + ), + ), + ))->nid; + } + + node_delete($referrers[0]); + + $references = array(); + foreach (user_load($uid, TRUE)->field_node['und'] as $reference) { + $references[] = $reference['target_id']; + } + $this->assertFalse(in_array($referrers[0], $references), 'Deleting a referrer clears corresponding reference on the referenced entity.', 'CER'); + } + + public function testReferencedEntityDeletion() { + $uid = $this->drupalCreateUser()->uid; + + $referrers = array(); + for ($i = 0; $i < 5; $i++) { + $referrers[] = $this->drupalCreateNode(array( + 'type' => 'page', + 'field_user' => array( + 'und' => array( + array('target_id' => $uid), + ), + ), + ))->nid; + } + user_delete($uid); + + $cleared = 0; + foreach ($referrers as $nid) { + $node = node_load($nid, NULL, TRUE); + $cleared += (int) empty($node->field_user); + } + $this->assertEqual($cleared, sizeof($referrers), 'Deleting a referenced entity clears all references to it.', 'CER'); + } + +} \ No newline at end of file diff --git a/sites/all/modules/contrib/fields/cer/tests/fields.test b/sites/all/modules/contrib/fields/cer/tests/fields.test new file mode 100644 index 00000000..e117b819 --- /dev/null +++ b/sites/all/modules/contrib/fields/cer/tests/fields.test @@ -0,0 +1,67 @@ + 'Fields', + 'group' => 'Corresponding Entity Reference', + 'description' => 'Tests integration with the Field API.', + ); + } + + public function setUp() { + parent::setUp('field', 'field_sql_storage', 'ctools', 'entityreference', 'cer'); + + field_create_field(array( + 'field_name' => 'field_user', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'user', + ), + )); + field_create_field(array( + 'field_name' => 'field_node', + 'type' => 'entityreference', + 'cardinality' => -1, + 'settings' => array( + 'target_type' => 'node', + ), + )); + + field_create_instance(array( + 'field_name' => 'field_user', + 'entity_type' => 'node', + 'bundle' => 'page', + )); + field_create_instance(array( + 'field_name' => 'field_node', + 'entity_type' => 'user', + 'bundle' => 'user', + )); + + ctools_include('export'); + + $preset = ctools_export_crud_new('cer'); + $preset->entity_types_content_fields = 'node*page*field_user*user*user*field_node'; + $preset->enabled = TRUE; + + ctools_export_crud_save('cer', $preset); + } + + public function testFieldInstanceDelete() { + field_delete_instance(field_info_instance('user', 'field_node', 'user')); + + $preset = cer_preset_load('node*page*field_user*user*user*field_node'); + $this->assertNull($preset, 'Deleting a field instance clears CER presets for that instance.'); + } + + public function testFieldDelete() { + field_delete_field('field_user'); + + $preset = cer_preset_load('node*page*field_user*user*user*field_node'); + $this->assertNull($preset, 'Deleting a field clears CER presets for that field.'); + } + +} \ No newline at end of file