From 3c2bb788b44411345268ba279af00af45aea037e Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Wed, 26 Jul 2017 11:36:38 +0200 Subject: [PATCH] New module 'Nodecomment' --- modules/nodecomment/LICENSE.txt | 339 +++++++++ modules/nodecomment/README.txt | 97 +++ .../includes/nodecomment.admin.inc | 102 +++ .../includes/nodecomment.common.inc | 666 ++++++++++++++++++ .../includes/nodecomment.forms.inc | 326 +++++++++ .../includes/nodecomment.theme.inc | 111 +++ modules/nodecomment/nodecomment.api.php | 46 ++ modules/nodecomment/nodecomment.info | 12 + modules/nodecomment/nodecomment.install | 223 ++++++ modules/nodecomment/nodecomment.module | 467 ++++++++++++ .../content_types/nodecomment_form.inc | 83 +++ .../nodecomment/views/nodecomment.views.inc | 260 +++++++ .../views/nodecomment.views_default.inc | 144 ++++ ...ment_handler_argument_comment_user_uid.inc | 18 + .../nodecomment_handler_field_author.inc | 48 ++ .../views/nodecomment_handler_field_link.inc | 69 ++ ...omment_handler_field_node_new_comments.inc | 67 ++ ...mment_handler_relationship_nid_or_self.inc | 59 ++ .../nodecomment_plugin_cache_comments.inc | 94 +++ .../nodecomment_plugin_display_comments.inc | 96 +++ .../nodecomment_plugin_style_threaded.inc | 36 + .../views_handler_field_comment_depth.inc | 17 + .../views_handler_field_comment_link.inc | 39 + ...views_handler_field_comment_link_reply.inc | 21 + .../views_handler_field_username_comment.inc | 50 ++ .../views_handler_sort_comment_thread.inc | 23 + 26 files changed, 3513 insertions(+) create mode 100644 modules/nodecomment/LICENSE.txt create mode 100644 modules/nodecomment/README.txt create mode 100644 modules/nodecomment/includes/nodecomment.admin.inc create mode 100644 modules/nodecomment/includes/nodecomment.common.inc create mode 100644 modules/nodecomment/includes/nodecomment.forms.inc create mode 100644 modules/nodecomment/includes/nodecomment.theme.inc create mode 100644 modules/nodecomment/nodecomment.api.php create mode 100644 modules/nodecomment/nodecomment.info create mode 100644 modules/nodecomment/nodecomment.install create mode 100644 modules/nodecomment/nodecomment.module create mode 100644 modules/nodecomment/plugins/content_types/nodecomment_form.inc create mode 100644 modules/nodecomment/views/nodecomment.views.inc create mode 100644 modules/nodecomment/views/nodecomment.views_default.inc create mode 100644 modules/nodecomment/views/nodecomment_handler_argument_comment_user_uid.inc create mode 100644 modules/nodecomment/views/nodecomment_handler_field_author.inc create mode 100644 modules/nodecomment/views/nodecomment_handler_field_link.inc create mode 100644 modules/nodecomment/views/nodecomment_handler_field_node_new_comments.inc create mode 100644 modules/nodecomment/views/nodecomment_handler_relationship_nid_or_self.inc create mode 100644 modules/nodecomment/views/nodecomment_plugin_cache_comments.inc create mode 100644 modules/nodecomment/views/nodecomment_plugin_display_comments.inc create mode 100644 modules/nodecomment/views/nodecomment_plugin_style_threaded.inc create mode 100644 modules/nodecomment/views/views_handler_field_comment_depth.inc create mode 100644 modules/nodecomment/views/views_handler_field_comment_link.inc create mode 100644 modules/nodecomment/views/views_handler_field_comment_link_reply.inc create mode 100644 modules/nodecomment/views/views_handler_field_username_comment.inc create mode 100644 modules/nodecomment/views/views_handler_sort_comment_thread.inc diff --git a/modules/nodecomment/LICENSE.txt b/modules/nodecomment/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/modules/nodecomment/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/modules/nodecomment/README.txt b/modules/nodecomment/README.txt new file mode 100644 index 0000000..cbdd9b3 --- /dev/null +++ b/modules/nodecomment/README.txt @@ -0,0 +1,97 @@ +ABOUT THE NODE COMMENTS +======================= + +With this module comments can be full nodes. For every content type you can +choose to use different content type as comment, or to continue using Drupal +core comments. +Thanks to this module, comments can have fields, revisions, taxonomy, +uploaded files, access control and anything else that comes from the goodness +of nodeapi. + +Current maintainer: Andrey Tretyakov http://drupal.org/user/169459 + +REQUIREMENTS +============ + +Views module. + +USAGE AND TECHNICAL DETAILS +=========================== + +Node Comments module has settings page at admin/settings/nodecomment. There you +can get overview of your content types and their Node Comment settings. +The module also uses Comment module settings, configured per content type. + +Comment types can have own comments. For example, a site can have +products, commented by reviews, commented by comments. + +Comment types can be enabled to work as "content", meaning they will have own +pages, full node titles (independently from the comment settings) and generally +behave as content. The only requirement currently is they must be added in +comment context, i.e. via "Add new " link for their target node. + +Node Comments displays comments using "nodecomments" view. You can configure +the view to tweak some options, but "Nodecomments" (second) display is always +used, and you can switch display style (e.g. set it to "table") only when node +comments are configured as flat in content type configuration. In threaded mode +Node Comments module forces usage of own display style. + +When deleting a node with node comments, or a node comment, child node comments +will be automatically deleted even if the user performing the delete doesn't +have delete permission for them. This may be surprise for you if you previously +used 2.x branch. This is needed not to leave orphan nodes in the database and is +generally in line with the core Comment module logics. +This behavior can be changed programmatically (see Node Comments API). + +To theme the node comments, use general node templates. There are some tricks, +in terms of theming, though. +First, because we generally repeat comment module stuff, we use "comment-123" +anchors (where 123 is node id). You must add these anchors to your nodecomment +templates for consistent scrolling. +When previewing a nodecomment on the same page with the thread, the module +will try to scroll to preview zone using "#preview" anchor. This +anchor is missing from the original theme_node_preview() function so you need +to add it using theme override. + +KNOWN ISSUES +============ + +This module may consume more resources and run slower than the core Comment +module. To increase performance there is special Views caching plugin which +caches the view output (a list of node comments). It can cause problems with +modules expecting to modify node output in the list dynamically. +There are 2 solutions to this problem: +- the caching can be disabled via Views UI +- using hook_views_post_render() one can insert dynamic values in the cached + output. This is similar to what Node Comments module itself does. + +To render node comments, the module overrides node view menu callback. This can +conflict with other modules doing the same. This is a limitation of Drupal: only +one function can process menu item. To deal with this problem, you can use +Panels module and output combined data from both Node Comments and conflicting +module. For more information, see http://drupal.org/node/477518. + +Unlike 2.x branch, 3.x doesn't have comment conversion tool anymore. It had too +many bugs and could lose data. Current maintainer is not comfortable supporting +it. It can be revived as a separate module anytime, if someone is willing to do +that. +This means that changing comment type when comments of that type already exist +can turn them into orphans, and break the database consistency. You are STRONGLY +recommended to configure comment types only once. + +For efficiency and reliability the module uses MySQL-only upsert (merge) +queries. If you want to make the module work with Postgres, submit a patch. + +COMPATIBILITY WITH COMMENT MODULE +================================= + +Previous branches of Node Comments had various issues with Comment module: +- 1.x branch was not compatible at all +- 2.x branch had many hacks, resulting in lots of bugs and compatibility issues + with other contributed modules + +3.x branch has exactly one hack: when loading a node with node comments enabled, +it moves "commentability" setting to own variable, so other modules +think the node can't be commented using Drupal core comments. When saving +the node, Node Comments restores that setting. Thus we fully re-use Comment +module default "commentability" setting and per-node "commentability" setting. diff --git a/modules/nodecomment/includes/nodecomment.admin.inc b/modules/nodecomment/includes/nodecomment.admin.inc new file mode 100644 index 0000000..bf01401 --- /dev/null +++ b/modules/nodecomment/includes/nodecomment.admin.inc @@ -0,0 +1,102 @@ +' . t('Content type') . '' . ': ' . + t('If set to "Drupal comments" the normal Drupal comment system will be used. Otherwise, set this to a node type for the node comment system to be used.') . + t("It's strongly recommended that you configure the setting once and then don't touch it."); + $help_items[] = '' . t('Is content') . '' . ': ' . + t("If enabled, the content type will work as full featured content type, even if it's set as comment.") . ' ' . t('Only applicable for node comments.'); + $help_items[] = '' . t('Comment view') . '' . ': ' . + t('The view to use when displaying comments for this node type.') . ' ' . t('Only applicable for node comments.'); + $help_items[] = '' . t('Plural form of comment type') . '' . + ': ' . t('The plural form of the comment node-type name, like comments or replies. The singular form is taken from the node type selected above. Only applicable for node comments.'); + $help = theme('item_list', $help_items); + + $warning = t("Changing comment type when comments of that type already exist +can turn them into orphans, and break the database consistency. You have been +warned!"); + + $form['top']['#value'] = + '
+
' . $help . '
+
' . $warning . '
+
'; + + $view_options = array('' => t('Disabled')); + $default_views = views_get_all_views(); + if (is_array($default_views)) { + foreach ($default_views as $key => $view) { + if (isset($view->display['nodecomment_comments_1'])) { + $view_options[$key] = $view->name; + } + } + } + foreach ($names as $type => $name) { + $comment_type = nodecomment_get_comment_type($type); + $form['#header'] = array(t('Content type'), t('Is content'), t('Comment type'), t('Comment view'), t('Plural form of comment type')); + $type_edit_path = "admin/content/node-type/" . str_replace('_', '-', $type); + $type_edit_link = l($name, $type_edit_path, array('fragment' => 'comment')); + $form['rows'][$type]['name']['#value'] = $type_edit_link; + $form['rows'][$type]['is_content']['node_comment_is_content_' . $type] = array( + '#type' => 'checkbox', + '#default_value' => nodecomment_is_content($type) + ); + $form['rows'][$type]['select']['node_comment_type_' . $type] = array( + '#type' => 'select', + '#options' => array('' => t('Drupal comments')) + $names, + '#default_value' => $comment_type + ); + $form['rows'][$type]['view']['node_comment_view_' . $type] = array( + '#type' => 'select', + '#options' => $view_options, + '#default_value' => $comment_type ? variable_get('node_comment_view_'. $type, 'nodecomments') : '', + ); + // TODO: find a better way to deal with these strings. + $form['rows'][$type]['plural']['node_comment_plural_' . $type] = array( + '#type' => 'textfield', + '#size' => 20, + '#default_value' => variable_get('node_comment_plural_'. $type, 'comments'), + ); + } + $form['bottom']['#value'] = ''; + $form = system_settings_form($form); + $form['#theme'] = 'nodecomment_admin_settings_form'; + return $form; +} + +/** + * Validate the nodecomment settings form. + */ +function nodecomment_admin_settings_form_validate($form, &$form_state) { + foreach(node_get_types('names') as $type => $blank) { + if ($form_state['values']['node_comment_type_' . $type]) { + // Node comments are enabled for the type. + $id = 'node_comment_view_' . $type; + if (!$form_state['values'][$id]) { + form_set_error($id, t("You must choose a comment view.")); + } + } + } +} + +/** + * Submit callback for the nodecomment settings form. + */ +function nodecomment_admin_settings_form_submit($form, &$form_state) { + // Rebuild menu so that our menu access callbacks work properly. + // TODO: track configuration changes and rebuild only when needed. + menu_router_build(1); +} diff --git a/modules/nodecomment/includes/nodecomment.common.inc b/modules/nodecomment/includes/nodecomment.common.inc new file mode 100644 index 0000000..da4b8a1 --- /dev/null +++ b/modules/nodecomment/includes/nodecomment.common.inc @@ -0,0 +1,666 @@ +thread)) { + $node->thread = nodecomment_get_thread($node); + } + + // MySQL-only upsert. Sorry Postgres guys. + // When updating, do not change the original submitted IP Address. + db_query( + "INSERT INTO {node_comments} + (cid, nid, pid, hostname, thread, name, uid, mail, homepage) + VALUES (%d, %d, %d, '%s', '%s', '%s', %d, '%s', '%s') + ON DUPLICATE KEY UPDATE + nid=VALUES(nid), pid=VALUES(pid), thread=VALUES(thread), name=VALUES(name), + uid=VALUES(uid), mail=VALUES(mail), homepage=VALUES(homepage)", + $node->nid, $node->comment_target_nid, $node->comment_target_cid, + ip_address(), $node->thread, $node->name, $node->uid, $node->mail, + $node->homepage + ); + + _nodecomment_update_node_statistics($node->comment_target_nid); + + // Explain the approval queue if necessary, and then + // redirect the user to the node he's commenting on. + if ($node->moderate == 1) { + drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.')); + } + return $node->nid; +} + + +/** + * Set the breadcrumb trail to include another node. + * + * This is used when viewing or adding a comment so that the parent node's + * breadcrumb trail is used instead of the normal breadcrumb paths. + * + * @param $node + * The node to use. + */ +function nodecomment_set_breadcrumb($node) { + // If the node had any breadcrumb changes, they will be made via nodeapi('view') + // as a general rule, so this will make them happen. + node_invoke_nodeapi($node, 'view', FALSE, TRUE); + + // Then add the parent node to the trail. + $breadcrumb = drupal_get_breadcrumb(); + $breadcrumb[] = l($node->title, "node/$node->nid"); + drupal_set_breadcrumb($breadcrumb); +} + +/** + * Get number of new comments for current user and specified node + * + * @param $nid node-id to count comments for + * @param $timestamp time to count from (defaults to time of last user access + * to node) + */ +function nodecomment_num_new($nid, $timestamp = 0) { + global $user; + + if ($user->uid) { + // Retrieve the timestamp at which the current user last viewed the + // specified node. + if (!$timestamp) { + $timestamp = node_last_viewed($nid); + } + $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT); + + // Use the timestamp to retrieve the number of new comments. + $result = db_result(db_query( + "SELECT COUNT(cn.nid) FROM {node} n + INNER JOIN {node_comments} c ON n.nid = c.nid + INNER JOIN {node} cn ON c.cid = cn.nid + WHERE n.nid = %d AND (cn.created > %d OR cn.changed > %d) AND cn.status = %d", + $nid, $timestamp, $timestamp, 1 + )); + + return $result; + } + else { + return 0; + } +} + +/** + * Calculate page number for any given comment. + * + * @param $comment + * The comment. + * @return + * The page number. + */ +function nodecomment_page_count($comment, $node = NULL) { + if (!$node) { + if (empty($comment->comment_target_nid)) { + return ''; + } + + $node = node_load($comment->comment_target_nid); + if (!nodecomment_get_comment_type($node->type)) { + return ''; + } + } + + $comments_per_page = _comment_get_display_setting('comments_per_page', $node); + $mode = _comment_get_display_setting('mode', $node); + $order = _comment_get_display_setting('sort', $node); + + $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED)); + + if ($flat) { + $field = 'n.nid'; + $value = '%d'; + $arg = $comment->nid; + } + else { + $field = 'nc.thread'; + $value = "'%s'"; + $arg = $comment->thread; + } + + if ($order == COMMENT_ORDER_NEWEST_FIRST) { + $op = ' >= '; + } + else { + $op = ' <= '; + } + + $query = "SELECT COUNT(*) FROM {node_comments} nc + INNER JOIN {node} n ON n.nid = nc.cid + WHERE $field $op $value AND n.status <> 0 AND n.nid != %d AND nc.nid = %d"; + $count = db_result(db_query($query, $arg, $comment->nid, $node->nid)); + $pageno = intval($count / $comments_per_page); + + return $pageno; +} + +/** + * Calculate page number for first new comment. + * + * This works for both comments and nodecomments. + * + * @param $num_comments + * Number of comments. + * @param $new_replies + * Number of new replies. + * @param $node + * The first new comment node. + * @return + * "page=X" if the page number is greater than zero; empty string otherwise. + */ +function nodecomment_new_page_count($num_comments, $new_replies, $node) { + // Default to normal comments so this function works either way. + if (!nodecomment_get_comment_type($node->type)) { + return comment_new_page_count($num_comments, $new_replies, $node); + } + + $comments_per_page = _comment_get_display_setting('comments_per_page', $node); + $mode = _comment_get_display_setting('mode', $node); + $order = _comment_get_display_setting('sort', $node); + $pagenum = NULL; + $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED)); + if ($num_comments <= $comments_per_page || ($flat && $order == COMMENT_ORDER_NEWEST_FIRST)) { + // Only one page of comments or flat forum and newest first. + // First new comment will always be on first page. + $pageno = 0; + } + else { + if ($flat) { + // Flat comments and oldest first. + $count = $num_comments - $new_replies; + } + else { + // Threaded comments. See the documentation for comment_render(). + if ($order == COMMENT_ORDER_NEWEST_FIRST) { + // Newest first: find the last thread with new comment + $thread = db_result(db_query( + "(SELECT thread FROM {node_comments} nc + INNER JOIN {node} n ON n.nid = nc.cid + WHERE nc.nid = %d AND n.status <> 0 + ORDER BY n.created DESC LIMIT %d) + ORDER BY thread DESC LIMIT 1", + $node->nid, $new_replies + )); + $result_count = db_query( + "SELECT COUNT(*) FROM {node_comments} nc + INNER JOIN {node} n ON n.nid = nc.cid + WHERE nc.nid = %d AND n.status <> 0 AND nc.thread > '%s'", + $node->nid, $thread + ); + } + else { + // Oldest first: find the first thread with new comment + $result = db_query( + '(SELECT thread FROM {node_comments} nc + INNER JOIN {node} n ON n.nid = nc.cid + WHERE nc.nid = %d AND n.status <> 0 + ORDER BY n.created DESC LIMIT %d) + ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1)) LIMIT 1', + $node->nid, $new_replies + ); + $thread = substr(db_result($result), 0, -1); + $result_count = db_query( + "SELECT COUNT(*) FROM {comments} nc + INNER JOIN {node} n ON n.nid = nc.cid + WHERE nc.nid = %d AND n.status <> 0 AND SUBSTRING(nc.thread, 1, (LENGTH(nc.thread) - 1)) < '%s'", + $node->nid, $thread + ); + } + $count = db_result($result_count); + } + $pageno = $count / $comments_per_page; + } + if ($pageno >= 1) { + $pagenum = "page=". intval($pageno); + } + return $pagenum; +} + +function nodecomment_get_thread($node) { + // Here we are building the thread field. See the documentation for + // comment_render(). + if (empty($node->comment_target_cid)) { + // This is a comment with no parent comment (depth 0): we start + // by retrieving the maximum thread level. + $max = db_result(db_query( + "SELECT MAX(thread) FROM {node_comments} WHERE nid = %d", + $node->comment_target_nid + )); + + // Strip the "/" from the end of the thread. + $max = rtrim($max, '/'); + + // Finally, build the thread field for this new comment. + $thread = int2vancode(vancode2int($max) + 1) .'/'; + } + else { + // This is comment with a parent comment: we increase + // the part of the thread value at the proper depth. + + // Get the parent comment: + $parent = node_load($node->comment_target_cid); + + // Strip the "/" from the end of the parent thread. + $parent->thread = (string) rtrim((string) $parent->thread, '/'); + + // Get the max value in _this_ thread. + $max = db_result(db_query( + "SELECT MAX(thread) FROM {node_comments} + WHERE thread LIKE '%s.%%' AND nid = %d", + $parent->thread, $node->comment_target_nid + )); + + if ($max == '') { + // First child of this parent. + $thread = $parent->thread .'.'. int2vancode(0) .'/'; + } + else { + // Strip the "/" at the end of the thread. + $max = rtrim($max, '/'); + + // We need to get the value at the correct depth. + $parts = explode('.', $max); + $parent_depth = count(explode('.', $parent->thread)); + $last = $parts[$parent_depth]; + + // Finally, build the thread field for this new comment. + $thread = $parent->thread .'.'. int2vancode(vancode2int($last) + 1) .'/'; + } + } + return $thread; +} + +/** + * Delete children comments from the same thread as comment. + */ +function _nodecomment_thread_delete_children($cid, $nid) { + // Delete the comment's replies. + // We have to be careful here to not delete comments from a separate thread + // started by this node, if it has own comments. + // To be sure about this, also check node id. + $result = db_query("SELECT cid FROM {node_comments} WHERE pid = %d AND nid = %d", $cid, $nid); + $delete = array(); + while ($row = db_fetch_object($result)) { + $delete[] = $row->cid; + } + foreach ($delete as $cid) { + _nodecomment_delete_comment($cid); + } +} + +/** + * Delete node comments of a node. + */ +function _nodecomment_delete_comments($nid) { + $result = db_query('SELECT cid FROM {node_comments} WHERE nid = %d', $nid); + $delete = array(); + while ($row = db_fetch_object($result)) { + $delete[] = $row->cid; + } + foreach ($delete as $cid) { + _nodecomment_delete_comment($cid); + } +} + +/** + * Delete a nodecomment. + * + * Based on code from node_delete() function but has different logics. + * + * Previously, the module used node_delete() which checks permissions. + * If the user didn't have the permission to delete the comment, the comment + * was left in the database as orphan. + * + * Having own function is better: + * 1) We don't check permissions, so we don't create orphans. Usually only + * moderators have the permission to delete other user's comments, so this + * is essential for any community site where users delete own comments. + * This also follows core comment behavior. + * 2) We warn other modules about the delete event, if nodeapi delete + * event is too late for them to react. They can also veto the deletion, + * provided they allow alternate action. + * 3) We don't print messages on screen. + */ +function _nodecomment_delete_comment($cid) { + // Clear the cache before the load, so if multiple nodes are deleted, the + // memory will not fill up with nodes (possibly) already removed. + // This also allows to find out easily if the node is already deleted. + $node = node_load($cid, NULL, TRUE); + + // The node might not exist already for a number of reasons. This is + // generally ok. + if (!$node) { + return; + } + + // Warn other modules about deletion and let them veto it. + $delete = TRUE; + $votes = nodecomment_invoke($node, 'delete_vote'); + foreach ($votes as $vote) { + if ($vote === FALSE) { + $delete = FALSE; + break; + } + } + + if ($delete) { + // Let the modules prepare for deleting. + nodecomment_invoke($node, 'delete'); + + db_query('DELETE FROM {node} WHERE nid = %d', $node->nid); + db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid); + db_query('DELETE FROM {node_access} WHERE nid = %d', $node->nid); + + // Call the node-specific callback (if any): + node_invoke($node, 'delete'); + node_invoke_nodeapi($node, 'delete'); + + // Clear the page and block caches. + cache_clear_all(); + + // Remove this node from the search index if needed. + if (function_exists('search_wipe')) { + search_wipe($node->nid, 'node'); + } + + // Log the event, but don't print messages on screen. + watchdog('content', '@type: deleted %title.', array('@type' => $node->type, '%title' => $node->title)); + } +} + +/** + * Updates the comment statistics for a given node. This should be called any + * time a comment is added, deleted, or updated. + * + * The following fields are contained in the node_comment_statistics table: + * + * - last_comment_timestamp: the timestamp of the last comment for this node + * or the node create stamp if no comments exist for the node. + * + * - last_comment_name: the name of the anonymous poster for the last comment. + * + * - last_comment_uid: the uid of the poster for the last comment for this node + * or the node authors uid if no comments exists for the node. + * + * - comment_count: the total number of approved/published comments on this node. + */ +function _nodecomment_update_node_statistics($nid) { + $count = db_result(db_query( + 'SELECT COUNT(*) FROM {node_comments} nc + INNER JOIN {node} n ON n.nid = nc.cid + WHERE nc.nid = %d AND n.status = 1', + $nid + )); + + // Comments exist. + if ($count > 0) { + $last_reply = db_fetch_object(db_query_range( + 'SELECT nc.cid, nc.name, n.created, n.changed, n.uid + FROM {node} n LEFT JOIN {node_comments} nc ON n.nid = nc.cid + WHERE nc.nid = %d AND n.status = 1 + ORDER BY cid DESC', + $nid, 0, 1 + )); + $timestamp = max($last_reply->created, $last_reply->changed); + $name = $last_reply->uid ? '' : $last_reply->name; + db_query( + "UPDATE {node_comment_statistics} + SET comment_count = %d, last_comment_timestamp = %d, last_comment_name = '%s', last_comment_uid = %d + WHERE nid = %d", + $count, $timestamp, $name, $last_reply->uid, $nid + ); + } + // No comments. + else { + // The node might not exist if called from hook_nodeapi($op = 'delete'). + if ($node = db_fetch_object(db_query("SELECT uid, created FROM {node} WHERE nid = %d", $nid))) { + db_query( + "UPDATE {node_comment_statistics} + SET comment_count = 0, last_comment_timestamp = %d, last_comment_name = '', last_comment_uid = %d + WHERE nid = %d", + $node->created, $node->uid, $nid + ); + } + else { + db_query("DELETE FROM {node_comment_statistics} WHERE nid = %d", $nid); + } + } +} + +function nodecomment_form($node) { + $comment_type = nodecomment_get_comment_type($node->type); + if ($comment_type) { + global $user; + $new_node = array( + 'uid' => $user->uid, + 'name' => $user->name, + 'type' => $comment_type, + 'comment_target_nid' => $node->nid, + 'comment_target_cid' => 0, + ); + module_load_include('inc', 'node', 'node.pages'); + return drupal_get_form($comment_type .'_node_form', $new_node); + } +} + +/** + * Menu callback; view a single node. + */ +function nodecomment_node_view($node, $cid = NULL) { + drupal_set_title(check_plain($node->title)); + $output = node_view($node, FALSE, TRUE); + + if (nodecomment_get_commentable($node)) { + if ($node->comment_type) { + $output .= nodecomment_render($node, $cid); + } + else { + $output .= comment_render($node, $cid); + } + } + + // Update the history table, stating that this user viewed this node. + node_tag_new($node->nid); + + return $output; +} + +/** + * Node comment's version of comment_render, to render all comments on a node. + */ +function nodecomment_render($node, $cid = 0) { + global $user; + + $output = ''; + + if (user_access('access comments')) { + // Pre-process variables. + $nid = $node->nid; + if (empty($nid)) { + $nid = 0; + } + + // Render nothing if there are no comments to render. + if (!empty($node->comment_count)) { + if ($cid && is_numeric($cid)) { + // Single comment view. + if ($comment = node_load($cid)) { + $output = theme('node', $comment, TRUE, TRUE); + } + } + else { + $view_name = variable_get('node_comment_view_'. $node->type, 'nodecomments'); + if ($view_name) { + $output = views_embed_view($view_name, 'nodecomment_comments_1', $nid); + } + } + } + + // If enabled, show new comment form. + $comment_type = nodecomment_get_comment_type($node->type); + if (user_access("create $comment_type content") && nodecomment_is_readwrite($node) && (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_BELOW)) { + // There is likely a cleaner way to do this, but for now it will have to do. -- JE + $friendly_name = node_get_types('name', $comment_type); + $output .= nodecomment_form_box($node, t('Post new !type', array('!type' => $friendly_name))); + } + + if ($output) { + $output = theme('comment_wrapper', $output, $node); + } + } + + return $output; +} + +function nodecomment_form_box($node, $title = NULL) { + return theme('box', $title, nodecomment_form($node)); +} + +/** + * Public API function to retrieve the comment type for a node type. + * + * @param $node_type + * The name of the node type for which the comment type will be retreived. + * @return + * Returns a string containing the node type which will be used for comments. + * If node comments are not used for the passed in type, returns empty string. + */ +function nodecomment_get_comment_type($node_type) { + return variable_get('node_comment_type_'. $node_type, ''); +} + +/** + * Return array of content types which serve as nodecomments. + */ +function nodecomment_get_comment_types() { + $comment_types = array(); + foreach (node_get_types('names') as $type => $blank) { + $comment_type = nodecomment_get_comment_type($type); + if ($comment_type) { + $comment_types[$comment_type] = $comment_type; + } + } + return $comment_types; +} + +/** + * Redirect to target node page containing a comment. Supports comment threads + * spanning multiple pages. + * + * @param $node + * node comment object + */ +function _nodecomment_target_node_redirect($node) { + $pagenum = nodecomment_page_count($node); + $query = NULL; + if ($pagenum) { + $query = array('page' => $pagenum); + } + $fragment = 'comment-' . $node->nid; + drupal_goto("node/" . $node->comment_target_nid, $query, $fragment); +} + +/** + * Check if the content type should be treated as full featured content type, + * even if it works as comment too. + */ +function nodecomment_is_content($type) { + // Types which are not comments are always treated as content. + if (!in_array($type, nodecomment_get_comment_types())) { + return TRUE; + } + return variable_get('node_comment_is_content_' . $type, FALSE); +} + +/** + * Check if the node is commentable (read/write). + */ +function nodecomment_is_readwrite(&$node) { + $commentable = nodecomment_get_commentable($node); + return ($commentable == COMMENT_NODE_READ_WRITE); +} + +/** + * Get "commentable" setting. + */ +function nodecomment_get_commentable(&$node) { + static $settings = array(); + if (!isset($settings[$node->nid])) { + $commentable = isset($node->node_comment) ? $node->node_comment : $node->comment; + // Let modules override commentability dynamically. + drupal_alter('nodecomment_commentable', $commentable, $node); + $settings[$node->nid] = $commentable; + } + return $settings[$node->nid]; +} + + +/** + * Return list of Node Comments module variables. + * + * @param $type + * content type + */ +function _nodecomment_vars() { + $vars = _nodecomment_type_vars(); + $vars[] = 'node_comment_node_redirect'; + return $vars; +} + +/** + * Return list of Node Comments module variables, associated with a content type. + * + * @param $type + * Content type. If not provided, return variables for all content types. + */ +function _nodecomment_type_vars($type) { + if ($type) { + $types = array($type); + } + else { + $types = array_keys(node_get_types('names')); + } + $vars = array(); + foreach ($types as $type) { + $vars[] = 'node_comment_type_' . $type; + $vars[] = 'node_comment_view_' . $type; + $vars[] = 'node_comment_plural_' . $type; + $vars[] = 'node_comment_is_content_' . $type; + } + return $vars; +} + +function nodecomment_invoke($node, $op) { + $results = array(); + $hook = 'nodecomment_' . $op; + foreach (module_implements($hook) as $module) { + $function = $module . '_' . $hook; + $results[] = $function($node); + } + return $results; +} + +function nodecomment_include($inc) { + module_load_include('inc', 'nodecomment', "includes/nodecomment.$inc"); +} + diff --git a/modules/nodecomment/includes/nodecomment.forms.inc b/modules/nodecomment/includes/nodecomment.forms.inc new file mode 100644 index 0000000..ec9d578 --- /dev/null +++ b/modules/nodecomment/includes/nodecomment.forms.inc @@ -0,0 +1,326 @@ +comment_target_nid = $node_context; + // If the parrent comment context is not set, use 0 (thread root). + $node->comment_target_cid = is_numeric($comment_context) ? $comment_context : 0; + + $target_node = node_load($node_context); + $target_comment = node_load(is_numeric($comment_context) ? $comment_context : $node_context); + + // Show the node which this comment is replying to. + if (!isset($form['#prefix'])) { + $form['#prefix'] = ''; + } + // In flat mode we use target comment context to store the data, but + // still show the target node. + $form['#prefix'] .= node_view($flat ? $target_node : $target_comment); + } + + if (isset($node->comment_target_nid)) { + // We're altering a nodecomment form. + _nodecomment_alter_nodecomment_form($form, $node, $target_node, $target_comment); + } + + if (nodecomment_get_comment_type($node->type)) { + // This is node edit form for a content type with nodecomments. + + // Make sure that node_comment property is set up, if the node wasn't + // loaded using node_load(), e.g. if it was passed from API call. + if (!isset($node->node_comment) && isset($node->comment)) { + $node->node_comment = $node->comment; + } + + // Load real value because it's the form to change it. + $form['comment_settings']['comment']['#default_value'] = $node->node_comment; + + // Add workaround for fake forms: some modules (for example, Node Gallery) + // use node form id in their special forms. They don't always copy comment + // setting form element from the real node form, which can lead to breakage + // of our logics. + // To protect against these fake forms, we insert special marker + // into form which will continue its life in node object. Fake forms won't + // have the marker, which will allow us to detect them in + // hook_nodeapi('presave') and to react properly. + // This is not bullet-proof though, cause if the faked form decided to + // change the setting, we would overwrite it's values in 'presave'. + // This will stay for now, until better solution replaces it. + $form['comment_settings']['nodecomment_real_node_form'] = array( + '#type' => 'value', + '#value' => 1 + ); + } +} + +function _nodecomment_alter_nodecomment_form(&$form, $node, $target_node = NULL, $target_comment = NULL) { + // Store Node Comments additional properties in the form. Otherwise + // they won't be passed by nodeapi. + $form['comment_target_nid'] = array( + '#type' => 'value', + '#value' => $node->comment_target_nid, + ); + $form['comment_target_cid'] = array( + '#type' => 'value', + '#value' => $node->comment_target_cid, + ); + $form['thread'] = array( + '#type' => 'value', + '#value' => $node->thread, + ); + + // Load our nodes. It's possible they may have been loaded during the + // node/add discovery above. + if (!isset($target_node)) { + $target_node = node_load($node->comment_target_nid); + } + if (!isset($target_comment)) { + $target_comment = node_load(!empty($node->comment_target_cid) ? $node->comment_target_cid : $node->comment_target_nid); + } + + // Process breadcrumbs for node pages. + if (arg(1) == 'add' || arg(1) == 'edit') { + // Reset the breadcrumb trail to get rid of the 'create content' stuff. + drupal_set_breadcrumb(array(l(t('Home'), NULL))); + + // Then add the target node. + nodecomment_set_breadcrumb($target_node); + + if (!empty($node->nid)) { + // And then add the current node: + $breadcrumb = drupal_get_breadcrumb(); + $breadcrumb[] = l($node->title, "node/$node->nid"); + drupal_set_breadcrumb($breadcrumb); + } + } + + // Add fields for anonymous commenting. + // Use settings of target node, not target comment. + _nodecomment_add_anon_contact_fields($form, $node, $target_node); + + // Does this nodecomment work as content ? + if (nodecomment_is_content($node->type)) { + // This type is full content type. It has title, own page, etc. + // Do something useful here. + } + else { + // This is a pure comment. + + // Remove settings that have no meaning on comments. + $form['menu']['#access'] = FALSE; + $form['path']['#access'] = FALSE; + $form['comment_settings']['#access'] = FALSE; + + // Remove the teaser splitter if body field is present. + if (isset($form['body_field'])) { + $teaser_js_build = array_search('node_teaser_js', $form['body_field']['#after_build']); + unset($form['body_field']['#after_build'][$teaser_js_build]); + $form['body_field']['teaser_js']['#access'] = FALSE; + $form['body_field']['teaser_include']['#access'] = FALSE; + } + + // Set up an automatic title in case it's new nodecomment. + // Use target comment title, not target node. + if (empty($node->nid)) { + $re = t('Re: '); + $re_len = drupal_strlen($re); + if (drupal_substr($target_comment->title, 0, $re_len) == $re) { + $form['title']['#default_value'] = $target_comment->title; + } + else { + $form['title']['#default_value'] = $re . $target_comment->title; + } + } + + // Make the title not required: + $form['title']['#required'] = FALSE; + if (variable_get('comment_subject_field_'. $target_node->type, 1) != 1) { + $form['title']['#access'] = FALSE; + } + } + + // File attachments dropdown should remain open: + $form['attachments']['#collapsed'] = FALSE; + + // If nodecomments are language enabled (but not translation enabled) + // set the language to that of the parent node. + if (variable_get('language_content_type_' . $node->type, 1)) { + $form['language'] = array( + '#type' => 'value', + '#value' => $target_node->language + ); + } + + // When previewing nodecomment, scroll to preview. + // Note that we add anchor for blank node so that scrolling works on first + // preview too. + if ($node->build_mode === NODE_BUILD_PREVIEW || !isset($node->nid)) { + $form['#action'] .= '#preview'; + } + + $form['buttons']['submit']['#submit'][] = 'nodecomment_node_form_submit'; +} + +function _nodecomment_add_anon_contact_fields(&$form, $node, $target_node) { + global $user; + $anon = variable_get('comment_anonymous_'. $target_node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT); + if ($user->uid == 0 && + ($anon == COMMENT_ANONYMOUS_MAY_CONTACT || $anon == COMMENT_ANONYMOUS_MUST_CONTACT)) { + + $form['comment_info'] = array('#weight' => -10); + $form['comment_info']['name'] = array( + '#type' => 'textfield', + '#title' => t('Your name'), + '#maxlength' => 60, + '#size' => 30, + '#default_value' => $node->name ? $node->name : variable_get('anonymous', t('Anonymous')), + '#required' => ($anon == COMMENT_ANONYMOUS_MUST_CONTACT) + ); + + $form['comment_info']['mail'] = array( + '#type' => 'textfield', + '#title' => t('E-mail'), + '#maxlength' => 64, + '#size' => 30, + '#default_value' => $node->mail, + '#description' => t('The content of this field is kept private and will not be shown publicly.'), + '#required' => ($anon == COMMENT_ANONYMOUS_MUST_CONTACT) + ); + + $form['comment_info']['homepage'] = array( + '#type' => 'textfield', + '#title' => t('Homepage'), + '#maxlength' => 255, + '#size' => 30, + '#default_value' => $node->homepage, + ); + + // Store target type in the form for the validate callback. + $form['comment_info']['target_node_type'] = array( + '#type' => 'value', + '#value' => $target_node->type, + ); + // Attach anonymous info validation. + $form['#validate'][] = 'nodecomment_node_form_validate'; + } + else { + $form['comment_info']['mail'] = array( + '#type' => 'value', + '#value' => '', + ); + $form['comment_info']['homepage'] = array( + '#type' => 'value', + '#value' => '', + ); + } +} + +/** + * Validate anonymous info (mail, homepage etc). + */ +function nodecomment_node_form_validate(&$form, &$form_state) { + $target_node_type = $form['comment_info']['target_node_type']['#value']; + $requirement = variable_get('comment_anonymous_'. $target_node_type, COMMENT_ANONYMOUS_MAYNOT_CONTACT); + + if ($form_state['values']['name']) { + $taken = db_result(db_query("SELECT COUNT(uid) FROM {users} WHERE LOWER(name) = '%s'", $form_state['values']['name'])); + if ($taken != 0) { + form_set_error('name', t('The name you used belongs to a registered user.')); + } + } + else if ($requirement == COMMENT_ANONYMOUS_MUST_CONTACT) { + form_set_error('name', t('You have to leave your name.')); + } + + if ($form_state['values']['mail']) { + if (!valid_email_address($form_state['values']['mail'])) { + form_set_error('mail', t('The e-mail address you specified is not valid.')); + } + } + else if ($requirement == COMMENT_ANONYMOUS_MUST_CONTACT) { + form_set_error('mail', t('You have to leave an e-mail address.')); + } + + if ($form_state['values']['homepage']) { + if (!valid_url($form_state['values']['homepage'], TRUE)) { + form_set_error('homepage', t('The URL of your homepage is not valid. Remember that it must be fully qualified, i.e. of the form http://example.com/directory.')); + } + } +} + +/** + * Redirect the node form to the right place. + */ +function nodecomment_node_form_submit(&$form, &$form_state) { + $node = $form['#node']; + $nid = $form_state['nid']; + if (empty($node->nid)) { + $node->nid = $nid; + } + + if (nodecomment_is_content($node->type)) { + // This is a full content type. Do something useful here. + } + else { + // This is a pure comment. Redirect to the target node page which contains + // the comment. + _nodecomment_target_node_redirect($node); + } +} + +/** + * Implementation of hook_form_FORMID_alter(). + */ +function nodecomment_form_node_delete_confirm_alter(&$form, &$form_state) { + // Node delete form. + if (!isset($_GET['destination'])) { + $node = node_load($form['nid']['#value']); + if (isset($node->comment_target_nid)) { + // Change the redirect and cancel link to the parent node page. + $form['destination'] = array( + '#type' => 'hidden', + '#value' => 'node/'. $node->comment_target_nid, + ); + $form['actions']['cancel']['#value'] = l(t('Cancel'), 'node/'. $node->comment_target_nid); + } + } +} diff --git a/modules/nodecomment/includes/nodecomment.theme.inc b/modules/nodecomment/includes/nodecomment.theme.inc new file mode 100644 index 0000000..6d9947c --- /dev/null +++ b/modules/nodecomment/includes/nodecomment.theme.inc @@ -0,0 +1,111 @@ + array('convert_counts' => NULL, 'form' => NULL), + 'file' => 'includes/nodecomment.convert.inc', + ); + $items['nodecomment_comment_count'] = array( + 'arguments' => array('count' => NULL, 'type' => NULL), + 'file' => 'includes/nodecomment.theme.inc', + ); + $items['nodecomment_new_comment_count'] = array( + 'arguments' => array('count' => NULL, 'type' => NULL), + 'file' => 'includes/nodecomment.theme.inc', + ); + $items['nodecomment_admin_settings_form'] = array( + 'arguments' => array('form' => NULL) + ); + + return $items; +} + +/** + * Add some additional suggestions for comment node templates. + */ +function nodecomment_preprocess_node(&$vars) { + // Test to see if it's a comment. + if (isset($vars['node']->comment_target_nid)) { + $node = &$vars['node']; + + // First comment checking. + static $first_new = TRUE; + + $vars['new'] = ''; + $vars['new_class'] = ''; + $vars['new_output'] = ''; + $vars['first_new'] = ''; + + $node->new = node_mark($node->comment_target_nid, $node->created); + if ($node->new) { + $vars['new'] = t('new'); + $vars['new_class'] = 'comment-new'; + $vars['classes'] = (isset($vars['classes']) ? $vars['classes'] . ' ' : '') . 'comment-new'; + $vars['new_output'] ='' . $vars['new'] . ''; + if ($first_new) { + $vars['first_new'] = "\n"; + $first_new = FALSE; + } + } + + $query = NULL; + if ($vars['page']) { + $pagenum = nodecomment_page_count($node); + } + else { + $pagenum = !empty($_GET['page']) ? $_GET['page'] : 0; + } + if ($pagenum) { + $query = array('page' => $pagenum); + } + $vars['comment_link'] = l($node->title, 'node/'. $node->comment_target_nid, array('query' => $query, 'fragment' => 'comment-' . $node->nid)); + $vars['signature'] = !empty($node->signature) ? theme('user_signature', $node->signature) : ''; + } +} + +/** + * Return plural form of number of comments. + * + * Use $type to provide different strings per nodetype. + */ +function theme_nodecomment_comment_count($count, $type) { + return format_plural($count, '1 comment', '@count comments'); +} + +/** + * Return plural form of number of new comments. + * + * Use $type to provide different strings per nodetype. + */ +function theme_nodecomment_new_comment_count($count, $type) { + return format_plural($count, '1 new comment', '@count new comments'); +} + +/** + * Theme relationships table + */ +function theme_nodecomment_admin_settings_form($form) { + $rows = array(); + foreach (element_children($form['rows']) as $type) { + $cells = $form['rows'][$type]; + foreach (element_children($cells) as $col) { + $rows[$type][$col] = drupal_render($cells[$col]); + } + } + unset($form['rows']); + $header = $form['#header']; + unset($form['#header']); + $attributes = array( + 'id' => 'nodecomment-admin-settings-table', + ); + $output = drupal_render($form['top']); + $output .= theme('table', $header, $rows, $attributes); + $output .= drupal_render($form['bottom']); + return $output . drupal_render($form); +} + diff --git a/modules/nodecomment/nodecomment.api.php b/modules/nodecomment/nodecomment.api.php new file mode 100644 index 0000000..5c08995 --- /dev/null +++ b/modules/nodecomment/nodecomment.api.php @@ -0,0 +1,46 @@ +comment) dynamically + * for the Node Comment module. The setting only defines Node Comment behavior + * and doesn't change the $node->comment flag in the database. + * + * @param int $commentable + * one of the following: + * COMMENT_NODE_DISABLED, COMMENT_NODE_READ_ONLY, COMMENT_NODE_READ_WRITE + * @param $node + * node object being commented on + */ +function hook_nodecomment_commentable_alter(&$commentable, $node) { + +} diff --git a/modules/nodecomment/nodecomment.info b/modules/nodecomment/nodecomment.info new file mode 100644 index 0000000..f62a0c3 --- /dev/null +++ b/modules/nodecomment/nodecomment.info @@ -0,0 +1,12 @@ +name = Node Comment +description = Allows users to comment on and discuss published content using nodes. +package = Core - optional +dependencies[] = views +dependencies[] = comment +core = 6.x +; Information added by drupal.org packaging script on 2012-11-16 +version = "6.x-3.0-beta1" +core = "6.x" +project = "nodecomment" +datestamp = "1353097067" + diff --git a/modules/nodecomment/nodecomment.install b/modules/nodecomment/nodecomment.install new file mode 100644 index 0000000..ba7d543 --- /dev/null +++ b/modules/nodecomment/nodecomment.install @@ -0,0 +1,223 @@ + 'comment', + 'name' => t('Comment'), + 'module' => 'node', + 'description' => t('A comment for use with the nodecomment module.'), + 'title_label' => t('Subject'), + 'body_label' => t('Body'), + 'custom' => TRUE, + 'modified' => TRUE, + 'locked' => FALSE, + ); + $nodecomment_node_type = (object)_node_type_set_defaults($nodecomment_node_type); + node_type_save($nodecomment_node_type); + + // Default to not promoted. + variable_set('node_options_comment', array('status')); + + // Default to not allowing comments. + variable_set('comment_comment', 0); + + cache_clear_all(); + module_load_include('inc', 'system', 'system.admin'); + system_modules(); + menu_rebuild(); + node_types_rebuild(); + } +} + +function nodecomment_schema() { + $schema['node_comments'] = array( + 'fields' => array( + 'cid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'disp-width' => '11'), + 'pid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'disp-width' => '11'), + 'nid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'disp-width' => '11'), + 'hostname' => array('type' => 'varchar', 'length' => '128', 'not null' => TRUE, 'default' => ''), + 'thread' => array('type' => 'varchar', 'length' => '255', 'not null' => TRUE), + 'name' => array('type' => 'varchar', 'length' => '60', 'not null' => FALSE), + 'uid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'disp-width' => '11'), + 'mail' => array('type' => 'varchar', 'length' => '64', 'not null' => FALSE), + 'homepage' => array('type' => 'varchar', 'length' => '255', 'not null' => FALSE) + ), + 'primary key' => array('cid'), + 'indexes' => array( + 'lid' => array('nid'), + // For quickly finding user comments. + 'nid_uid' => array('nid', 'uid') + ), + ); + return $schema; +} + +function nodecomment_uninstall() { + drupal_uninstall_schema('nodecomment'); + foreach (_nodecomment_vars() as $var) { + variable_del($var); + } +} + +function nodecomment_update_6000() { + // adding uid to schema. this is needed to avoid the (not verified) that theme_user now implements + $ret = array(); + db_add_field($ret, 'node_comments', 'uid', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'disp-width' => '11')); + return $ret; +} + +function nodecomment_update_6001() { + // the node_comments view changed to nodecomments. update any node types that + // use this view for comments. + foreach(node_get_types() as $type) { + if(variable_get('comment_view_'. $type->type, '') == 'node_comments') { + variable_set('comment_view_'. $type->type, 'nodecomments'); + } + } + return array(); +} + +/** + * Move comment settings into per content-type settings. + */ +function nodecomment_update_6002() { + $ret = array(); + + $comment_anonymous = variable_get('comment_anonymous', NULL); + $comment_preview = variable_get('comment_preview', NULL); + $comment_form_location = variable_get('comment_form_location', NULL); + + foreach(node_get_types() as $type) { + if (!is_null($comment_anonymous)) { + variable_set('comment_anonymous_'. $type->type, $comment_anonymous); + } + if (!is_null($comment_preview)) { + variable_set('comment_preview_'. $type->type, $comment_preview); + } + if (!is_null($comment_form_location)) { + variable_set('comment_form_location_'. $type->type, $comment_preview); + } + $ret[] = array('success' => TRUE, 'query' => t('Updated comment settings for %node_type.', array('%node_type' => $type->name))); + } + + variable_del('comment_anonymous'); + variable_del('comment_preview'); + variable_del('comment_form_location'); + + return $ret; +} + +/** + * Update 6001 added a uid column but didn't populate it. + */ +function nodecomment_update_6003() { + $ret = array(); + $ret[] = update_sql('UPDATE {node_comments} c INNER JOIN {node} n ON c.cid = n.nid SET c.uid = n.uid'); + return $ret; +} + +// update 6200 needs to update settings from the old nodecomment style to the +// new style so that comment.module is not confused. + +/** + * Update our variables to proper namespace. + */ +function nodecomment_update_6201() { + $ret = array(); + variable_del('default_comment_type'); + $types = node_get_types(); + foreach ($types as $type => $info) { + foreach (array('comment_type', 'comment_view', 'comment_plural') as $var) { + $value = variable_get($var . '_' . $type, NULL); + if (isset($value)) { + variable_set('node_' . $var . '_' . $type, $value); + } + variable_del($var . '_' . $type); + } + } + + drupal_install_modules(array('comment')); + + return $ret; +} + +/** + * Get rid of redundant variables that only create confusion. + */ +function nodecomment_update_6301() { + $ret = array(); + $types = node_get_types(); + foreach ($types as $type => $info) { + $oldvar = 'node_comment_' . $type; + $newvar = 'comment_' . $type; + $value = variable_get($oldvar, NULL); + if (isset($value)) { + variable_set($newvar, $value); + } + variable_del($oldvar); + } + return $ret; +} + +/** + * Add an index to find user comments for a node. + */ +function nodecomment_update_6302() { + $ret = array(); + db_add_index($ret, 'node_comments', 'nid_uid', array('nid', 'uid')); + return $ret; +} + +/** + * Delete unused variables. + */ +function nodecomment_update_6303() { + $ret = array(); + foreach (node_get_types() as $type => $info) { + variable_del('node_comment_topic_review_' . $type); + } + return $ret; +} + +/** + * Disable commenting for node comments. + */ +function nodecomment_update_6304() { + $ret = array(); + + // Disable comment status for existing nodecomments. This is needed because + // we now allow comments for nodecomments in a separate thread, and it was + // impossible in 2.x. We need to be sure it's not a surprise but a conscious + // decision to not confuse users. + $comment_types = nodecomment_get_comment_types(); + db_query( + "UPDATE {node} SET comment = 0 + WHERE type IN (" . db_placeholders($comment_types, 'varchar') . ")", + $comment_types + ); + // Also set default commenting to disabled. + foreach ($comment_types as $type) { + variable_set("comment_$type", 0); + } + + return $ret; +} diff --git a/modules/nodecomment/nodecomment.module b/modules/nodecomment/nodecomment.module new file mode 100644 index 0000000..44cf117 --- /dev/null +++ b/modules/nodecomment/nodecomment.module @@ -0,0 +1,467 @@ +node_comment)) { + // Allow changing of comment setting during node form submission. + if (!isset($node->nodecomment_real_node_form)) { + $node->comment = $node->node_comment; + } + } + break; + + case 'insert': + case 'update': + // If this is a comment, save comment data. + if (isset($node->comment_target_nid)) { + nodecomment_save($node); + } + break; + + case 'delete': + // If this node has own comments, delete them. + // For increased durability, don't add any checks here: it should help to + // deal with the orphan problem. + _nodecomment_delete_comments($node->nid); + + // If this is a comment, delete it and it's children comments from the thread. + if (isset($node->comment_target_nid)) { + _nodecomment_thread_delete_children($node->nid, $node->comment_target_nid); + + // For increased durability, delete node_comments entries one by one, + // together with their nodes, even when mass deleting. + db_query('DELETE FROM {node_comments} WHERE cid = %d', $node->nid); + + _nodecomment_update_node_statistics($node->comment_target_nid); + } + break; + + case 'view': + // If this is a comment. + if ($page && isset($node->comment_target_nid)) { + // Redirect to target node, if needed. + // We could do it inside hook_init() but then we would add 1 query for + // every node view, which is a tax it's better not to pay. + if (variable_get('node_comment_node_redirect', TRUE)) { + if (!nodecomment_is_content($node->type)) { + // This is a pure comment. Pure comments don't have own pages. + // Redirect this to the node page the comment is displayed on. + _nodecomment_target_node_redirect($node); + } + } + // Adjust the breadcrumb trail to include target node. + $target_node = node_load($node->comment_target_nid); + nodecomment_set_breadcrumb($target_node); + } + break; + } +} + +function _nodecomment_nodeapi_load($node, $op, $arg, $page) { + // We want to process 3 cases: + // - node which is a node comment + // - node which has node comments + // - both + $comment_types = nodecomment_get_comment_types(); + $node->comment_type = nodecomment_get_comment_type($node->type); + $comment_data = array(); + + // Is this a comment type ? + if (in_array($node->type, $comment_types)) { + $query = "SELECT nc.nid AS comment_target_nid, nc.pid AS comment_target_cid, + nc.hostname, nc.thread, nc.name, nc.uid, nc.mail, nc.homepage, + u.signature, u.signature_format + FROM {node_comments} nc + INNER JOIN {users} u ON nc.uid = u.uid + WHERE nc.cid = %d"; + $comment_data = db_fetch_array(db_query($query, $node->nid)); + if ($comment_data) { + // It's a node comment! Populate commenty stuff. + // Don't let the "name" field in the comment overwrite a username. + if (!empty($node->uid)) { + unset($comment_data['name']); + } + // Add the user signature like comment.module does + if (variable_get('user_signatures', 0) && !empty($comment_data['signature'])) { + $comment_data['signature'] = check_markup($comment_data['signature'], $comment_data['signature_format'], FALSE); + } + else { + $comment_data['signature'] = ''; + } + } + else { + // Node type is a comment, but node_comments table entry is missing. + watchdog( + 'nodecomment', + 'Node @nid is configured as comment, but the comment data is missing.', + array('@nid' => $node->nid), + WATCHDOG_ERROR + ); + } + } + + // Does this node have node comments ? + if ($node->comment_type) { + // Move $node->comment to $node->node_comment and set $node->comment + // to disabled to prevent core comment module messing with the node. + // In presave nodeapi operation restore this setting. + // In 3.x branch this is the only hack we do with core comment module. + $node->node_comment = $node->comment; + $node->comment = COMMENT_NODE_DISABLED; + } + + return $comment_data; +} + +/** + * Implementation of hook_user(). + */ +function nodecomment_user($type, $edit, &$user, $category = NULL) { + if ($type == 'delete') { + // 'node_comment_statistics' table is updated by comment module. + db_query('UPDATE {node_comments} SET uid = 0 WHERE uid = %d', $user->uid); + } +} + +/** + * Implementation of hook_link(). + */ +function nodecomment_link($type, $node = NULL, $teaser = FALSE) { + global $user; + $links = array(); + + if ($type != 'node') { + return; + } + + if (isset($node->comment_target_nid)) { + // This node is a comment to a parent node. + _nodecomment_comment_links($links, $node, $teaser); + } + + if (!empty($node->comment_type)) { + // This node can have node comments, read only or writable. + _nodecomment_node_links($links, $node, $teaser); + } + + return $links; +} + +function _nodecomment_comment_links(&$links, &$node, $teaser) { + // This really looks like a "security by obscurity" anti-pattern: user with + // edit permission can still edit comment even while the link is hidden. + // But the core comment does the same. + // Fixing this properly will require an advanced node access module. + $target_node = node_load($node->comment_target_nid); + if ($target_node && nodecomment_is_readwrite($target_node)) { + if (node_access('update', $node)) { + $links['comment_edit'] = array( + 'title' => t('edit'), + 'href' => 'node/'. $node->nid .'/edit', + 'query' => drupal_get_destination(), + ); + } + if (node_access('delete', $node)) { + $links['comment_delete'] = array( + 'title' => t('delete'), + 'href' => 'node/'. $node->nid .'/delete', + 'query' => drupal_get_destination(), + ); + } + // Show comment reply links in threaded mode. In flat mode we only + // hide the link: separate comment reply pages are always accessible. + $mode = _comment_get_display_setting('mode', $node); + $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED)); + if (!$flat && node_access('create', $node)) { + $links['comment_reply'] = array( + 'title' => t('reply'), + 'href' => 'node/add/'. str_replace('_', '-', $node->type) .'/'. $node->comment_target_nid .'/'. $node->nid, + ); + } + } + + return $links; +} + +function _nodecomment_node_links(&$links, &$node, $teaser) { + global $user; + + $comment_read = nodecomment_get_commentable($node); + if ($comment_read && $teaser && user_access('access comments')) { + // Main page: display the number of comments that have been posted. + $all = comment_num_all($node->nid); + if ($all) { + $links['comment_comments'] = array( + 'title' => theme('nodecomment_comment_count', $all, $node->comment_type), + 'href' => "node/$node->nid", + 'attributes' => array('title' => t('Jump to the first comment of this posting.')), + 'fragment' => 'comments' + ); + + $new = nodecomment_num_new($node->nid); + if ($new) { + $links['comment_new_comments'] = array( + 'title' => theme('nodecomment_new_comment_count', $new, $node->comment_type), + 'href' => "node/$node->nid", + 'query' => nodecomment_new_page_count($all, $new, $node), + 'attributes' => array('title' => t('Jump to the first new comment of this posting.')), + 'fragment' => 'new' + ); + } + } + } + + // This node needs an Add new comment link. + // Note that the add comment link is shown on full node view no matter + // where the comment form is located, which differs from core's + // comment.module. See http://drupal.org/node/480282. + if (nodecomment_is_readwrite($node)) { + $comment_type = nodecomment_get_comment_type($node->type); + // Can the current user create the node comment ? + if (user_access("create $comment_type content")) { + // Is num of comments link present ? + if (!isset($links['comment_comments'])) { + $links['comment_add'] = array( + 'title' => t('Add new @comment_type', array('@comment_type' => node_get_types('name', $comment_type))), + 'attributes' => array('title' => t('Add a new comment to this page.')), + ); + if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == 1) { + $links['comment_add']['href'] = "node/$node->nid"; + $links['comment_add']['fragment'] = 'node-form'; + } + else { + $links['comment_add']['href'] = "node/add/". str_replace('_', '-', $comment_type) ."/". $node->nid; + } + } + } + // The user can't create the comment nodetype. + elseif ($user->uid == 0) { + // Show anonymous users the chance to login or register + // We cannot use drupal_get_destination() because these links sometimes + // appear on /node and taxonomy listing pages. + if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) { + $destination = 'destination='. drupal_urlencode('node/add/'. str_replace('_', '-', $comment_type) .'/'. $node->nid); + } + else { + $destination = 'destination='. drupal_urlencode('node/'. $node->nid .'#nodecomment_form'); + } + + $links['login_register']['html'] = TRUE; + if (variable_get('user_register', 1)) { + $links['login_register']['title'] = t( + 'Login or register to post @comments', + array( + '@login' => url('user/login', array('query' => $destination)), + '@register' => url('user/register', array('query' => $destination)), + '@comments' => variable_get('node_comment_plural_'. $node->type, 'comments') + ) + ); + } + else { + $links['login_register']['title'] = t( + 'Login to post @comments', + array( + '@login' => url('user/login', array('query' => $destination)), + '@comments' => variable_get('node_comment_plural_'. $node->type, 'comments') + ) + ); + } + } + } +} + +/** + * Implementation of hook_link_alter(). + */ +function nodecomment_link_alter(&$links, $node) { + // Ensure comment links go before "read more" one. Other modules can still + // add links and break the order, but its the best we can do. + if (isset($links['node_read_more'])) { + $readmore = $links['node_read_more']; + unset($links['node_read_more']); + $links['node_read_more'] = $readmore; + } +} + +/** + * Implementation of hook_menu(). + */ +function nodecomment_menu() { + $items = array(); + $items['admin/settings/nodecomment'] = array( + 'title' => 'Node Comments', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('nodecomment_admin_settings_form'), + 'access arguments' => array('administer content types'), + 'file' => 'includes/nodecomment.admin.inc', + ); + return $items; +} + +/** + * Implementation of hook_menu_alter() + * + * Alter the node view page to come to us instead. Don't do this if + * another module has already done so, or if delegator module is enabled. + */ +function nodecomment_menu_alter(&$items) { + // Override the node view handler for our purpose. + if ((!module_exists('page_manager') || + variable_get('page_manager_node_view_disabled', TRUE)) && + $items['node/%node']['page callback'] == 'node_page_view') { + + $items['node/%node']['page callback'] = 'nodecomment_node_view'; + } + + $comment_types = nodecomment_get_comment_types(); + foreach ($comment_types as $type) { + $path = "node/add/". str_replace('_', '-', $type); + if (isset($items[$path])) { + // Attach custom access callback that ensures proper access denied page + // when needed. + $items[$path]['access callback'] = '_nodecomment_node_add_access'; + // Make all our comment types not visible to node/add. + $items[$path]['type'] = MENU_CALLBACK; + } + } +} + +/** + * Menu access callback for node/add pages of node comment types. + */ +function _nodecomment_node_add_access($op, $type) { + // Note: menu callbacks receive proper type with underscores instead of + // hyphens because Node module creates menu items with predefined + // callback arguments rather than lets callback fetch argument from the url. + + // Don't allow to add nodecomments without comment context. + // + // TODO: we may want to allow this later, if we want to add comments + // that are "content" by our terms without comment context. + // But before doing so, we need to be sure that our nodeapi & nodecomment + // logics can handle that. + if (!is_numeric(arg(3))) { + return FALSE; + } + + // Check basic create permission before performing more heavy checks. + if (!node_access('create', $type)) { + return FALSE; + } + + $target_node = node_load(arg(3)); + + // Verify that target node being commented on exists and can be commented. + if (!$target_node || !nodecomment_is_readwrite($target_node)) { + return FALSE; + } + + // Check that we are trying to add a proper node comment type. + $comment_type = nodecomment_get_comment_type($target_node->type); + if (!$comment_type || $type != $comment_type) { + return FALSE; + } + + // If we are replying to a nodecomment, check it's validity. + if (is_numeric(arg(4))) { + $target_comment = node_load(arg(4)); + // Target node should be a comment and should belong to the same thread. + if (!$target_comment || + !isset($target_comment->comment_target_nid) || + $target_comment->comment_target_nid != $target_node->nid) { + + return FALSE; + } + } + + return TRUE; +} + +/** + * Ask Page Manager to use our version of the node page view instead of + * Drupal's when falling back. + */ +function nodecomment_page_manager_override($type) { + if ($type == 'node_view') { + return 'nodecomment_node_view'; + } +} + +/** + * Implementation of hook_views_pre_build() + */ +function nodecomment_views_pre_build(&$view) { + if ($view->display[$view->current_display]->display_plugin == 'nodecomment_comments') { + $view->display_handler->pre_build(); + } +} + +/** + * Implementation of hook_node_type(). + * + * Update nodecomment variables when node type information changes. + */ +function nodecomment_node_type($op, $info) { + if ($op == 'delete') { + $vars = _nodecomment_type_vars($info->type); + foreach ($vars as $var) { + variable_del($var); + } + } + else if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) { + $vars = _nodecomment_type_vars($info->old_type); + foreach ($vars as $var) { + variable_del($var); + } + } +} + +/** + * Implementation of hook_features_pipe_COMPONENT_alter(). + */ +function nodecomment_features_pipe_node_alter(&$pipe, $data, $export) { + if (!empty($data)) { + foreach ($data as $node_type) { + $variables = _nodecomment_type_vars($node_type); + foreach ($variables as $variable_name) { + $pipe['variable'][] = $variable_name; + } + } + } +} + +/** +* Implementation of hook_ctools_plugin_directory(). +*/ +function nodecomment_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools') { + return 'plugins/' . $plugin; + } +} + +/** + * Implementation of hook_views_api(). + */ +function nodecomment_views_api() { + return array( + 'api' => 2, + 'path' => drupal_get_path('module', 'nodecomment') .'/views', + ); +} diff --git a/modules/nodecomment/plugins/content_types/nodecomment_form.inc b/modules/nodecomment/plugins/content_types/nodecomment_form.inc new file mode 100644 index 0000000..7929d09 --- /dev/null +++ b/modules/nodecomment/plugins/content_types/nodecomment_form.inc @@ -0,0 +1,83 @@ + TRUE, + 'title' => t('Nodecomment form'), + 'icon' => 'icon_node.png', + 'description' => t('Form to add a new comment (using nodecomment or comment depending on the content type configuration).'), + 'required context' => new ctools_context_required(t('Node'), 'node'), + 'category' => t('Node'), +); + +function nodecomment_nodecomment_form_content_type_render($subtype, $conf, $panel_args, $context) { + $node = isset($context->data) ? drupal_clone($context->data) : NULL; + $output = ''; + $block = new stdClass(); + $comment_type = nodecomment_get_comment_type($node->type); + + // Use comment.module with node_comment_form.inc or nodecomment with custom code depending on what the node requires + if (module_exists('comment') && !$comment_type) { + ctools_get_content_type('node_comment_form'); + return ctools_node_comment_form_content_type_render($subtype, $conf, $panel_args, $context); + } + else if (module_exists('nodecomment') && $comment_type) { + $comment_type_info = node_get_types('type', $comment_type); + $block->title = t('Add @type', array('@type' => $comment_type_info->name)); + if (empty($node)) { + $output .= t('@type form here.', array('@type' => $comment_type_info->name)); + } + else { + if (user_access("create $comment_type content") && nodecomment_is_readwrite($node)) { + ctools_include('form'); + global $user; + // create basic comment node + $comment = array( + 'uid' => $user->uid, + 'name' => $user->name, + 'type' => $comment_type, + 'comment_target_nid' => $node->nid, + ); + $form_state = array( + 'ctools comment alter' => 'nodecomment', + 'args' => array($comment), + ); + // make sure node.pages.inc is loaded + module_load_include('inc', 'node', 'node.pages'); + $output .= ctools_build_form("{$comment_type}_node_form", $form_state); + } + } + } + + $block->module = 'nodecomment'; + $block->delta = $node->nid; + $block->content = $output; + + return $block; +} + +function nodecomment_nodecomment_form_content_type_admin_title($subtype, $conf, $context) { + return t('"@s" comment form', array('@s' => $context->identifier)); +} + +function nodecomment_nodecomment_form_content_type_edit_form(&$form, &$form_state) { + // provide a blank form so we have a place to have context setting. +} + +/** + * Alter the comment form to get a little more control over it. + */ +function nodecomment_nodecomment_form_form_alter(&$form, &$form_state) { + if ($form_state['ctools comment alter'] == 'nodecomment') { + // force the form to post back to wherever we are. + $url = parse_url($_GET['q']); + $form['#submit'][] = 'nodecomment_nodecomment_form_submit'; + } + else if (!empty($form_state['ctools comment alter'])){ + ctools_get_content_type('node_comment_form'); + ctools_form_comment_form_alter($form, $form_state); + } +} + +function nodecomment_nodecomment_form_submit(&$form, &$form_state) { + $form_state['redirect'][0] = $_GET['q']; +} diff --git a/modules/nodecomment/views/nodecomment.views.inc b/modules/nodecomment/views/nodecomment.views.inc new file mode 100644 index 0000000..a10f962 --- /dev/null +++ b/modules/nodecomment/views/nodecomment.views.inc @@ -0,0 +1,260 @@ + array( + 'left_field' => 'nid', + 'field' => 'cid', + ), + ); + + // ---------------------------------------------------------------- + // node_comments table -- fields + + $data['node_comments']['cid'] = array( + 'title' => t('Nid'), + 'help' => t('The node ID of the comment.'), + 'argument' => array( + 'handler' => 'views_handler_argument_node_nid', + 'name field' => 'title', + 'numeric' => TRUE, + 'validate type' => 'nid', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ); + + $data['node_comments']['nid'] = array( + 'title' => t('Original Parent Nid'), + 'help' => t('The original node the comment is a reply to.'), + 'relationship' => array( + 'base' => 'node', + 'field' => 'nid', + 'handler' => 'views_handler_relationship', + 'label' => t('Node'), + ), + ); + + $data['node_comments']['nid_or_self'] = array( + 'title' => t('Original Parent Nid or Self'), + 'help' => t('The original node the comment is a reply to, or the original node. This allows searching across nodes that may or may not be comments.'), + 'relationship' => array( + 'base' => 'node', + 'field' => 'nid', + 'real field' => 'nid', + 'handler' => 'nodecomment_handler_relationship_nid_or_self', + 'label' => t('Node'), + ), + ); + + $data['node_comments']['pid'] = array( + 'title' => t('Parent Nid'), + 'help' => t('The node of the parent comment. Could be another comment.'), + 'field' => array( + 'handler' => 'views_handler_field', + ), + 'relationship' => array( + 'title' => t('Parent comment'), + 'help' => t('The parent comment.'), + 'base' => 'comments', + 'field' => 'cid', + 'handler' => 'views_handler_relationship', + 'label' => t('Parent comment'), + ), + ); + + $data['node_comments']['name'] = array( + 'title' => t('Author'), + 'help' => t('The name of the poster. This does not query the user table, it includes only data in the node_comment table.'), + 'field' => array( + 'handler' => 'views_handler_field_username_comment', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['node_comments']['homepage'] = array( + 'title' => t("Author's website"), + 'help' => t("The website address of the comment's author. Can be a link. The homepage can also be linked with the Name field. Will be empty if posted by a registered user."), + 'field' => array( + 'handler' => 'views_handler_field_url', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + $data['node_comments']['thread'] = array( + 'field' => array( + 'title' => t('Depth'), + 'help' => t('Display the depth of the comment if it is threaded.'), + 'handler' => 'views_handler_field_comment_depth', + ), + 'sort' => array( + 'title' => t('Thread'), + 'help' => t('Sort by the threaded order. This will keep child comments together with their parents.'), + 'handler' => 'views_handler_sort_comment_thread', + ), + ); + + // link to reply to comment + $data['node_comments']['replyto_comment'] = array( + 'field' => array( + 'title' => t('Reply-to link'), + 'help' => t('Provide a simple link to reply to the comment.'), + 'handler' => 'views_handler_field_comment_link_reply', + ), + ); + + return $data; +} + +/** + * Implementation of hook_views_data() + */ +function nodecomment_views_data_alter(&$data) { + $data['node']['nodecomment_link'] = array( + 'field' => array( + 'title' => t('Node comment link'), + 'help' => t('If the node is a comment, provide a link to the parent node at the right location. Otherwise provide a normal link to the node.'), + 'handler' => 'nodecomment_handler_field_link', + ), + ); + $data['node']['nodecomment_author'] = array( + 'field' => array( + 'title' => t('Node comment author'), + 'help' => t('Provide a link to the author of the node. This works properly with node comment where the author may not be an actual user.'), + 'handler' => 'nodecomment_handler_field_author', + ), + ); + + // Replace comment.module's "new comments" handler with ours. + $data['node']['new_comments']['field']['handler'] = 'nodecomment_handler_field_node_new_comments'; + + // Replace comment module "user created or commented" argument handler. + $data['node']['uid_touch']['argument']['handler'] = 'nodecomment_handler_argument_comment_user_uid'; +} + +/** + * Implementation of hook_views_plugins + */ +function nodecomment_views_plugins() { + return array( + 'display' => array( + 'nodecomment_comments' => array( + 'title' => t('Nodecomments'), + 'handler' => 'nodecomment_plugin_display_comments', + 'path' => drupal_get_path('module', 'nodecomment') .'/views', + 'theme' => 'views_view', + 'theme path' => drupal_get_path('module', 'views') .'/theme', + 'help' => t('Display a view in the "comments" location'), + 'use ajax' => TRUE, + 'use pager' => TRUE, + 'accept attachments' => TRUE, + 'admin' => t('Nodecomments'), + 'help topic' => 'display-nodecomments', + ), + ), + 'style' => array( + 'nodecomment_threaded' => array( + 'path' => drupal_get_path('module', 'nodecomment') .'/views', + 'theme path' => drupal_get_path('module', 'nodecomment') .'/views', + 'title' => t('Node comments threaded'), + 'help' => t('Display the comment with a threaded comment view.'), + 'handler' => 'nodecomment_plugin_style_threaded', + 'theme' => 'nodecomment_threaded', + 'uses row plugin' => TRUE, + 'type' => 'normal', + ), + ), + 'cache' => array( + 'nodecomment_comments' => array( + 'path' => drupal_get_path('module', 'nodecomment') .'/views', + 'title' => t('Nodecomment thread'), + 'help' => t('Cache a single thread of nodecomments based on a nid.'), + 'handler' => 'nodecomment_plugin_cache_comments', + 'uses options' => TRUE, + 'help topic' => 'cache-nodecomment', + ), + ), + ); +} + +/** + * Register handlers + */ +function nodecomment_views_handlers() { + return array( + 'info' => array( + 'path' => drupal_get_path('module', 'nodecomment') .'/views', + ), + 'handlers' => array( + 'views_handler_field_username_comment' => array( + 'parent' => 'views_handler_field', + ), + 'views_handler_field_comment_depth' => array( + 'parent' => 'views_handler_field' + ), + 'views_handler_field_username_comment' => array( + 'parent' => 'views_handler_field' + ), + 'views_handler_field_comment_link' => array( + 'parent' => 'views_handler_field' + ), + 'views_handler_field_comment_link_reply' => array( + 'parent' => 'views_handler_field_comment_link' + ), + 'views_handler_sort_comment_thread' => array( + 'parent' => 'views_handler_sort' + ), + 'nodecomment_handler_relationship_nid_or_self' => array( + 'parent' => 'views_handler_relationship' + ), + 'nodecomment_handler_field_link' => array( + 'parent' => 'views_handler_field_node_link' + ), + 'nodecomment_handler_field_author' => array( + 'parent' => 'views_handler_field_user_name' + ), + 'nodecomment_handler_field_node_new_comments' => array( + 'parent' => 'views_handler_field_node_new_comments' + ), + 'nodecomment_handler_argument_comment_user_uid' => array( + 'parent' => 'views_handler_argument_comment_user_uid' + ), + ), + ); +} diff --git a/modules/nodecomment/views/nodecomment.views_default.inc b/modules/nodecomment/views/nodecomment.views_default.inc new file mode 100644 index 0000000..7ecdc4d --- /dev/null +++ b/modules/nodecomment/views/nodecomment.views_default.inc @@ -0,0 +1,144 @@ +name = 'nodecomments'; + $view->description = 'Node comments flat'; + $view->tag = ''; + $view->view_php = ''; + $view->base_table = 'node'; + $view->is_cacheable = FALSE; + $view->api_version = 2; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + $handler = $view->new_display('default', 'Defaults', 'default'); + $handler->override_option('relationships', array( + 'nid' => array( + 'label' => 'Node', + 'required' => 0, + 'id' => 'nid', + 'table' => 'node_comments', + 'field' => 'nid', + 'relationship' => 'none', + ), + )); + $handler->override_option('fields', array( + 'title' => array( + 'label' => 'Title', + 'link_to_node' => 0, + 'exclude' => 0, + 'id' => 'title', + 'table' => 'node', + 'field' => 'title', + 'relationship' => 'none', + ), + 'name' => array( + 'label' => 'Author', + 'link_to_user' => 1, + 'exclude' => 0, + 'id' => 'name', + 'table' => 'node_comments', + 'field' => 'name', + 'relationship' => 'none', + ), + 'thread' => array( + 'label' => 'Depth', + 'exclude' => 0, + 'id' => 'thread', + 'table' => 'node_comments', + 'field' => 'thread', + 'relationship' => 'none', + ), + )); + $handler->override_option('arguments', array( + 'nid' => array( + 'default_action' => 'not found', + 'style_plugin' => 'default_summary', + 'style_options' => array(), + 'wildcard' => 'all', + 'wildcard_substitution' => 'All', + 'title' => '', + 'default_argument_type' => 'fixed', + 'default_argument' => '', + 'validate_type' => 'none', + 'validate_fail' => 'not found', + 'break_phrase' => 0, + 'not' => 0, + 'id' => 'nid', + 'table' => 'node', + 'field' => 'nid', + 'relationship' => 'nid', + 'default_options_div_prefix' => '', + 'default_argument_user' => 0, + 'default_argument_fixed' => '', + 'default_argument_php' => '', + 'validate_argument_node_type' => array( + 'comment' => 0, + 'page' => 0, + 'story' => 0, + ), + 'validate_argument_node_access' => 0, + 'validate_argument_nid_type' => 'nid', + 'validate_argument_vocabulary' => array(), + 'validate_argument_type' => 'tid', + 'validate_argument_php' => '', + ), + )); + $handler->override_option('filters', array( + 'status' => array( + 'operator' => '=', + 'value' => 1, + 'group' => '0', + 'exposed' => FALSE, + 'expose' => array( + 'operator' => FALSE, + 'label' => '', + ), + 'id' => 'status', + 'table' => 'node', + 'field' => 'status', + 'relationship' => 'none', + ), + )); + $handler->override_option('access', array( + 'type' => 'none', + 'role' => array(), + 'perm' => '', + )); + $handler->override_option('cache', array( + 'type' => 'nodecomment_comments', + 'argument' => 'nid', + )); + $handler->override_option('use_pager', '1'); + $handler->override_option('style_options', array( + 'grouping' => '', + )); + $handler->override_option('row_plugin', 'node'); + $handler->override_option('row_options', array( + 'teaser' => 0, + 'links' => 1, + )); + $handler = $view->new_display('nodecomment_comments', 'Nodecomments', 'nodecomment_comments_1'); + $handler = $view->new_display('nodecomment_comments', 'Nodecomments Topic Review', 'nodecomment_comments_2'); + $handler->override_option('sorts', array( + 'nid' => array( + 'order' => 'DESC', + 'id' => 'nid', + 'table' => 'node', + 'field' => 'nid', + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + ), + )); + $views[$view->name] = $view; + + return $views; +} diff --git a/modules/nodecomment/views/nodecomment_handler_argument_comment_user_uid.inc b/modules/nodecomment/views/nodecomment_handler_argument_comment_user_uid.inc new file mode 100644 index 0000000..5b894f7 --- /dev/null +++ b/modules/nodecomment/views/nodecomment_handler_argument_comment_user_uid.inc @@ -0,0 +1,18 @@ +ensure_my_table(); + // This copies comment handler query. It could be rewritten in various + // different ways, but all variants would suck in terms of performance. + $where = "$this->table_alias.uid = %d OR + ((SELECT COUNT(*) FROM {comments} c WHERE c.uid = %d AND c.nid = $this->table_alias.nid) > 0) OR + ((SELECT COUNT(*) FROM {node_comments} nc WHERE nc.uid = %d AND nc.nid = $this->table_alias.nid) > 0)"; + $this->query->add_where(0, $where, $this->argument, $this->argument, $this->argument); + } +} diff --git a/modules/nodecomment/views/nodecomment_handler_field_author.inc b/modules/nodecomment/views/nodecomment_handler_field_author.inc new file mode 100644 index 0000000..05ab1a7 --- /dev/null +++ b/modules/nodecomment/views/nodecomment_handler_field_author.inc @@ -0,0 +1,48 @@ +additional_fields['uid'] = array('table' => 'users', 'field' => 'uid'); + $this->additional_fields['user_name'] = array('table' => 'users', 'field' => 'name'); + $this->additional_fields['name'] = array('table' => 'node_comments', 'field' => 'name'); + $this->additional_fields['homepage'] = array('table' => 'node_comments', 'field' => 'homepage'); + } + + function option_definition() { + $options = parent::option_definition(); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + } + + function query() { + $this->add_additional_fields(); + $this->field_alias = $this->aliases['user_name']; + } + + function render_link($data, $values) { + // The parent will handle anything that isn't a node comment just fine. + if (!empty($values->{$this->aliases['uid']}) || empty($values->{$this->aliases['name']})) { + return parent::render_link($data, $values); + } + + if (!empty($this->options['link_to_user'])) { + $account->uid = 0; + $account->name = $values->{$this->aliases['name']}; + $account->homepage = $values->{$this->aliases['homepage']}; + + return theme('username', $account); + } + else { + return $data; + } + } +} diff --git a/modules/nodecomment/views/nodecomment_handler_field_link.inc b/modules/nodecomment/views/nodecomment_handler_field_link.inc new file mode 100644 index 0000000..2cb9f16 --- /dev/null +++ b/modules/nodecomment/views/nodecomment_handler_field_link.inc @@ -0,0 +1,69 @@ +additional_fields['parent_nid'] = array('table' => 'node_comments', 'field' => 'nid'); + $this->additional_fields['thread'] = array('table' => 'node_comments', 'field' => 'thread'); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + + $node_comments = $this->query->ensure_table('node_comments', $this->relationship); + + $def = array( + 'table' => 'node', + 'field' => 'nid', + 'left_table' => $node_comments, + 'left_field' => 'nid', + ); + + $join = new views_join(); + + $join->definition = $def; + $join->construct(); + $join->adjusted = TRUE; + + // Add more info to figure out what page the comment is on. + $this->parent_node = $this->query->add_table('node', $this->relationship, $join); + $this->aliases['parent_type'] = $this->query->add_field($this->parent_node, 'type'); + } + + function render($values) { + $text = !empty($this->options['text']) ? $this->options['text'] : t('view'); + $nid = $values->{$this->aliases['nid']}; + $this->options['alter']['make_link'] = TRUE; + + if (!empty($values->{$this->aliases['parent_nid']})) { + // Fake up two nodes so we can get the target page: + $comment = new stdClass(); + $comment->nid = $values->{$this->aliases['nid']}; + $comment->thread = $values->{$this->aliases['thread']}; + + $parent = new stdClass(); + $parent->nid = $values->{$this->aliases['parent_nid']}; + $parent->type = $values->{$this->aliases['parent_type']}; + + if ($pageno = nodecomment_page_count($comment, $parent)) { + $this->options['alter']['query'] = 'page=' . $pageno; + } + + $this->options['alter']['path'] = "node/$parent->nid"; + $this->options['alter']['fragment'] = 'comment-' . $nid; + } + else { + $this->options['alter']['path'] = "node/$nid"; + $this->options['alter']['fragment'] = ''; + } + + $this->options['alter']['alter_text'] = TRUE; + if (empty($this->options['alter']['text'])) { + $this->options['alter']['text'] = $text; + } + return $text; + } +} diff --git a/modules/nodecomment/views/nodecomment_handler_field_node_new_comments.inc b/modules/nodecomment/views/nodecomment_handler_field_node_new_comments.inc new file mode 100644 index 0000000..bdcbc89 --- /dev/null +++ b/modules/nodecomment/views/nodecomment_handler_field_node_new_comments.inc @@ -0,0 +1,67 @@ +uid || empty($values)) { + return; + } + + $nids = array(); // for comments + $nc_nids = array(); // for node_comments + $ids = array(); + foreach ($values as $id => $result) { + if (nodecomment_get_comment_type($result->{$this->aliases['type']})) { + $nc_nids[] = $result->{$this->aliases['nid']}; + } + else { + $nids[] = $result->{$this->aliases['nid']}; + } + $values[$id]->{$this->field_alias} = 0; + // Create a reference so we can find this record in the values again. + if (empty($ids[$result->{$this->aliases['nid']}])) { + $ids[$result->{$this->aliases['nid']}] = array(); + } + $ids[$result->{$this->aliases['nid']}][] = $id; + } + + if ($nids) { + $result = db_query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {comments} c ON n.nid = c.nid LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = %d WHERE n.nid IN (" . implode(', ', $nids) . ") AND c.timestamp > GREATEST(COALESCE(h.timestamp, %d), %d) AND c.status = %d GROUP BY n.nid ", $user->uid, NODE_NEW_LIMIT, NODE_NEW_LIMIT, COMMENT_PUBLISHED); + + while ($node = db_fetch_object($result)) { + foreach ($ids[$node->nid] as $id) { + $values[$id]->{$this->field_alias} = $node->num_comments; + } + } + } + + if ($nc_nids) { + $result = db_query("SELECT n.nid, COUNT(c.cid) as num_comments FROM {node} n INNER JOIN {node_comments} c ON n.nid = c.nid INNER JOIN {node} nc ON c.cid = nc.nid LEFT JOIN {history} h ON h.nid = n.nid AND h.uid = %d WHERE n.nid IN (" . implode(', ', $nc_nids) . ") AND nc.created > GREATEST(COALESCE(h.timestamp, %d), %d) AND nc.status <> 0 GROUP BY n.nid ", $user->uid, NODE_NEW_LIMIT, NODE_NEW_LIMIT); + + while ($node = db_fetch_object($result)) { + foreach ($ids[$node->nid] as $id) { + $values[$id]->{$this->field_alias} = $node->num_comments; + } + } + + } + + } + + function render_link($data, $values) { + if (!empty($this->options['link_to_comment']) && $data !== NULL && $data !== '') { + $node = new stdClass(); + $node->nid = $values->{$this->aliases['nid']}; + $node->type = $values->{$this->aliases['type']}; + $this->options['alter']['make_link'] = TRUE; + $this->options['alter']['path'] = 'node/' . $node->nid; + $this->options['alter']['query'] = nodecomment_new_page_count($values->node_comment_statistics_comment_count, $values->{$this->field_alias}, $node); + $this->options['alter']['fragment'] = 'new'; + } + + return $data; + } +} diff --git a/modules/nodecomment/views/nodecomment_handler_relationship_nid_or_self.inc b/modules/nodecomment/views/nodecomment_handler_relationship_nid_or_self.inc new file mode 100644 index 0000000..8e88157 --- /dev/null +++ b/modules/nodecomment/views/nodecomment_handler_relationship_nid_or_self.inc @@ -0,0 +1,59 @@ +definition['base']); + $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field']; + + $this->ensure_my_table(); + + // We need to figure out the alias of our base table based upon our relationship: + if ($this->relationship) { + $base_table = $this->relationship; + } + else { + $base_table = $this->query->base_table; + } + + $def = $this->definition; + $def['table'] = $this->definition['base']; + $def['field'] = $base_field; + $def['left_table'] = ''; + $def['left_field'] = "COALESCE($this->table.$this->real_field, $base_table.$this->real_field)"; + $def['type'] = 'INNER'; + + $join = new views_join(); + + $join->definition = $def; + $join->construct(); + $join->adjusted = TRUE; + + // use a short alias for this: + $alias = $def['table'] . '_' . $this->table; + + $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship); + } + +} diff --git a/modules/nodecomment/views/nodecomment_plugin_cache_comments.inc b/modules/nodecomment/views/nodecomment_plugin_cache_comments.inc new file mode 100644 index 0000000..19e206b --- /dev/null +++ b/modules/nodecomment/views/nodecomment_plugin_cache_comments.inc @@ -0,0 +1,94 @@ +view->display_handler->get_handlers('argument'); + foreach ($arguments as $id => $argument) { + $options[$id] = $argument->ui_name(); + } + + $form['argument'] = array( + '#type' => 'select', + '#title' => t('Argument to use'), + '#description' => t('The argument that contains the node ID. This content will be re-cached whenever that node is updated. If there is no node ID then the cache expiration will default to 1 hour.'), + '#options' => $options, + '#default_value' => $this->options['argument'], + ); + } + + function summary_title() { + return t('Node comment thread'); + } + + function cache_expire($type) { + // extract the node ID. + if (isset($this->view->argument[$this->options['argument']])) { + $nid = $this->view->argument[$this->options['argument']]->argument;; + $node = node_load($nid); + if ($node) { + return max($node->changed, $node->last_comment_timestamp); + } + } + + // default to 1 hour. + return time() - 3600; + } + + /** + * Post process cached output for new strings. + * + * The template preprocess will use placeholders for any 'new' output, so + * that the post process can replace it. This postprocess runs despite caching, + * so the freshness of comments can always be checked accurately for the + * logged in user. Without this, the "new" values are incorrect. This can + * be extended by modules that utlize other values that need to be + * freshened very easily with hook_views_post_render. + */ + function post_render(&$output) { + $tokens = array(); + // First comment checking. + static $first_new = TRUE; + if (!isset($this->view->argument[$this->options['argument']])) { + return; + } + + $nid = $this->view->argument[$this->options['argument']]->argument;; + $node = node_load($nid); + if (!$node) { + return; + } + + // Set up tokens for each row. + foreach ($this->view->result as $id => $row) { + // we probably shouldn't use node_created directly here, but the display + // doesn't use any relationship so the chances of this alias failing is + // much slimmer than other weird things going wrong. + $new_output = $first = $new_class = ''; + + $new = node_mark($node->nid, $row->node_changed); + if ($new) { + $new_output = $new ? '' . t('new') . '' : ''; + $new_class = 'comment-new'; + if ($first_new) { + $first = ""; + $first_new = FALSE; + } + } + + $tokens[""] = $first; + $tokens[""] = $new_output; + $tokens[""] = $new_class; + } + + // Replace + $output = strtr($output, $tokens); + } +} diff --git a/modules/nodecomment/views/nodecomment_plugin_display_comments.inc b/modules/nodecomment/views/nodecomment_plugin_display_comments.inc new file mode 100644 index 0000000..c29a019 --- /dev/null +++ b/modules/nodecomment/views/nodecomment_plugin_display_comments.inc @@ -0,0 +1,96 @@ +use_pager() ? t('Items per page') : t('Items to display'); + + $form['items_per_page'] = array( + '#type' => 'value', + '#value' => 10, + ); + $form['markup'] = array( + '#value' => t('The number of items to display will be taken from the node type settings.'), + ); + $form['offset'] = array( + '#type' => 'textfield', + '#title' => t('Offset'), + '#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed. Offset can not be used if items to display is 0; instead use a very large number there.'), + '#default_value' => intval($this->get_option('offset')), + ); + return; + } + + // otherwise do the default + parent::options_form($form, $form_state); + + if ($form_state['section'] == 'items_per_page') { + $form['style_plugin']['#description'] .= ' ' . t('Important note: the style will be overridden to the nodecomment threaded style if threading is enabled for the node type.'); + } + } + + function query() { + // Get the node from the argument handler + if (empty($this->node)) { + return; + } + + // Override our sorting options if necessary + $node_comments = $this->view->query->ensure_table('node_comments'); + $mode = _comment_get_display_setting('mode', $this->node); + $order = _comment_get_display_setting('sort', $this->node); + + if ($order == COMMENT_ORDER_NEWEST_FIRST) { + $sort = 'DESC'; + if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) { + $table = 'node'; + $field = 'nid'; + } + else { + $table = $node_comments; + $field = 'thread'; + } + } + else if ($order == COMMENT_ORDER_OLDEST_FIRST) { + $sort = 'ASC'; + if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) { + $table = 'node'; + $field = 'nid'; + } + else { + // See comment above. Analysis reveals that this doesn't cost too + // much. It scales much much better than having the whole comment + // structure. + $table = NULL; + $field = "SUBSTRING($node_comments.thread, 1, (LENGTH($node_comments.thread) - 1))"; + } + } + $this->view->query->add_orderby($table, $field, $sort, 'nodecomment_sort'); + $this->view->query->add_field('node', 'changed'); + + parent::query(); + } + + /** + * Called by an implementation of hook_views_pre_build() to let us + * check the argument and make changes based upon global settings. + */ + function pre_build() { + if (!empty($this->view->args[0]) && $node = node_load($this->view->args[0])) { + $this->node = $node; + // Change items per page. + $this->view->set_items_per_page(_comment_get_display_setting('comments_per_page', $node)); + $mode = _comment_get_display_setting('mode', $node); + if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) { + $this->set_option('style_plugin', 'nodecomment_threaded'); + } + } + } +} diff --git a/modules/nodecomment/views/nodecomment_plugin_style_threaded.inc b/modules/nodecomment/views/nodecomment_plugin_style_threaded.inc new file mode 100644 index 0000000..40b7bdf --- /dev/null +++ b/modules/nodecomment/views/nodecomment_plugin_style_threaded.inc @@ -0,0 +1,36 @@ +view->result as $n) { + $node = node_load($n->nid); + $node->view = &$this->view; + $node->depth = count(explode('.', $node->thread)) - 1; + + if ($node->depth > $last_depth) { + $divs++; + $output .= '
'; + $last_depth++; + } + else { + while ($node->depth < $last_depth) { + $divs--; + $output .= '
'; + $last_depth--; + } + } + $output .= node_view($node); + } + + for ($i = 0; $i < $divs; $i++) { + $output .= ''; + } + + return $output; + } +} diff --git a/modules/nodecomment/views/views_handler_field_comment_depth.inc b/modules/nodecomment/views/views_handler_field_comment_depth.inc new file mode 100644 index 0000000..2c5cf9b --- /dev/null +++ b/modules/nodecomment/views/views_handler_field_comment_depth.inc @@ -0,0 +1,17 @@ +additional_fields['thread'] = 'thread'; + } + /** + * Work out the depth of this comment + */ + function render($values) { + return count(explode('.', $values->{$this->aliases['thread']})) - 1; + } +} diff --git a/modules/nodecomment/views/views_handler_field_comment_link.inc b/modules/nodecomment/views/views_handler_field_comment_link.inc new file mode 100644 index 0000000..39b41a8 --- /dev/null +++ b/modules/nodecomment/views/views_handler_field_comment_link.inc @@ -0,0 +1,39 @@ +additional_fields['cid'] = 'cid'; + $this->additional_fields['nid'] = 'nid'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['text'] = array('default' => '', 'translatable' => TRUE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display'), + '#default_value' => $this->options['text'], + ); + } + + function query() { + $this->ensure_my_table(); + $this->add_additional_fields(); + } + + function render($values) { + $text = !empty($this->options['text']) ? $this->options['text'] : t('view'); + return l($text, "node/" . $values->{$this->aliases['nid']}, array('html' => TRUE, 'fragment' => "comment-" . $values->{$this->aliases['cid']})); + } +} + +?> \ No newline at end of file diff --git a/modules/nodecomment/views/views_handler_field_comment_link_reply.inc b/modules/nodecomment/views/views_handler_field_comment_link_reply.inc new file mode 100644 index 0000000..90cf937 --- /dev/null +++ b/modules/nodecomment/views/views_handler_field_comment_link_reply.inc @@ -0,0 +1,21 @@ +additional_fields['type'] = array('table' => 'node', 'field' => 'type'); + } + + function render($values) { + //check for permission to reply to comments + if (!user_access('post comments')) { + return; + } + $comment_type = str_replace('_', '-', $values->{$this->aliases['type']}); + $text = !empty($this->options['text']) ? $this->options['text'] : t('reply'); + return l($text, 'node/add/'. $comment_type .'/' . $values->{$this->aliases['nid']} . '/' . $values->{$this->aliases['cid']}); + } +} diff --git a/modules/nodecomment/views/views_handler_field_username_comment.inc b/modules/nodecomment/views/views_handler_field_username_comment.inc new file mode 100644 index 0000000..9aae32e --- /dev/null +++ b/modules/nodecomment/views/views_handler_field_username_comment.inc @@ -0,0 +1,50 @@ +additional_fields['uid'] = 'uid'; + $this->additional_fields['homepage'] = 'homepage'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['link_to_user'] = array('default' => TRUE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['link_to_user'] = array( + '#title' => t("Link this field to its user or an author's homepage"), + '#type' => 'checkbox', + '#default_value' => $this->options['link_to_user'], + ); + } + + function render_link($data, $values) { + $account->uid = $values->{$this->aliases['uid']}; + $account->name = $values->{$this->field_alias}; + $account->homepage = $values->{$this->aliases['homepage']}; + + if (!empty($this->options['link_to_user'])) { + return theme('username', $account); + } + else { + return $data; + } + } + + function render($values) { + return $this->render_link(check_plain($values->{$this->field_alias}), $values); + } + +} + +?> \ No newline at end of file diff --git a/modules/nodecomment/views/views_handler_sort_comment_thread.inc b/modules/nodecomment/views/views_handler_sort_comment_thread.inc new file mode 100644 index 0000000..d18684d --- /dev/null +++ b/modules/nodecomment/views/views_handler_sort_comment_thread.inc @@ -0,0 +1,23 @@ +ensure_my_table(); + + //Read comment_render() in comment.module for an explanation of the + //thinking behind this sort. + if ($this->options['order'] == 'DESC') { + $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']); + } + else { + $alias = $this->table_alias . '_' . $this->real_field . 'asc'; + //@todo is this secure? + $this->query->add_orderby(NULL, "SUBSTRING({$this->table_alias}.{$this->real_field}, 1, (LENGTH({$this->table_alias}.{$this->real_field}) - 1))", $this->options['order'], $alias); + } + } +} + +?> \ No newline at end of file