New module 'Nodecomment'

This commit is contained in:
Manuel Cillero 2017-07-26 11:36:38 +02:00
parent 09b74574f1
commit 3c2bb788b4
26 changed files with 3513 additions and 0 deletions

View file

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

View file

@ -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 <type>" 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.

View file

@ -0,0 +1,102 @@
<?php
/**
* @file
* Admin UI of the nodecomment module.
*/
function nodecomment_admin_settings_form() {
$form = array();
$names = node_get_types('names');
if (!$names) {
// No content types at all.
$form['notice']['#value'] = t("You don't have content types.");
return $form;
}
$help_items[] = '<strong>' . t('Content type') . '</strong>' . ': ' .
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[] = '<strong>' . t('Is content') . '</strong>' . ': ' .
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[] = '<strong>' . t('Comment view') . '</strong>' . ': ' .
t('The view to use when displaying comments for this node type.') . ' ' . t('Only applicable for node comments.');
$help_items[] = '<strong>' . t('Plural form of comment type') . '</strong>' .
': ' . t('The plural form of the comment node-type name, like <em>comments</em> or <em>replies</em>. 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'] =
'<div id="nodecomment-admin-settings-help">
<div class="status">' . $help . '</div>
<div class="warning"><strong>' . $warning . '</strong></div>
</div>';
$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);
}

View file

@ -0,0 +1,666 @@
<?php
/**
* @file
* Helper functions of the Node Comments module.
*/
/**
* Accepts a submission of new or changed comment content.
*
* @param $node
* The node that is serving as a comment to another node.
*
* @return
* If the comment is successfully saved the node ID of the comment is returned. If the comment
* is not saved, FALSE is returned.
*/
function nodecomment_save($node) {
global $user;
if (!isset($node->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");
}

View file

@ -0,0 +1,326 @@
<?php
/**
* Node Comment terminology:
*
* "target node"
* node which is being commented on
*
* "target comment"
* nodecomment which is being replied to
*/
/**
* Implementation of hook_form_alter().
*/
function nodecomment_form_alter(&$form, &$form_state, $form_id) {
global $user;
// Make sure we alter node form.
if (!isset($form['type']) ||
!isset($form['type']['#value']) ||
$form['type']['#value'] .'_node_form' != $form_id) {
return;
}
$node = &$form['#node'];
$mode = _comment_get_display_setting('mode', $node);
$flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
// Convert dashes to underscores as we get type from menu path.
$type = str_replace('-', '_', arg(2));
// Prepare nodecomment data for a newly created node.
if (arg(0) == 'node'
&& arg(1) == 'add'
&& is_numeric(arg(3))
&& !empty($type)
&& in_array($type, nodecomment_get_comment_types())
) {
$node_context = arg(3);
$comment_context = arg(4);
$node->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 <code>http://example.com/directory</code>.'));
}
}
}
/**
* 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);
}
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* Implementation of hook_theme().
*/
function nodecomment_theme() {
$items = array();
$items['nodecomment_convert_page'] = array(
'arguments' => 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'] ='<span class="new">' . $vars['new'] . '</span>';
if ($first_new) {
$vars['first_new'] = "<a id=\"new\"></a>\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);
}

View file

@ -0,0 +1,46 @@
<?php
/**
* Allows modules to vote on deleting child comments. This hook is called for
* every child nodecomment when parent node is already deleted, and
* Node Comments needs to decide what to do with children comments (own comments
* of the node and child nodecomments in the same thread).
* A module returning FALSE will prevent node deletion.
*
* @param object $node
* node comment being deleted
*/
function hook_nodecomment_delete_vote(&$node) {
}
/**
* Allows modules to prepare for deleting child comments. This hook is called
* for every child nodecomment when parent node is already deleted, and
* Node Comments is going to delete it's children comments (own comments of
* the node and child nodecomments in the same thread).
*
* This can be handy if nodeapi delete operation is too late for a module to
* react.
*
* @param object $node
* node comment being deleted
*/
function hook_nodecomment_delete(&$node) {
}
/**
* Change "commentability" setting (similar to $node->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) {
}

View file

@ -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"

View file

@ -0,0 +1,223 @@
<?php
/**
* @file
* Install file for nodecomment
*/
function nodecomment_install() {
drupal_install_schema('nodecomment');
_nodecomment_install_type_create();
}
function _nodecomment_install_type_create() {
// During install profiles, node and user modules aren't yet loaded.
// Ensure they're loaded before calling node_get_types().
drupal_load('module', 'node');
drupal_load('module', 'user');
$types = node_get_types();
$types = array_change_key_case($types, CASE_LOWER);
if (!in_array('comment', array_keys($types))) {
// Create the comment content type.
$nodecomment_node_type = array(
'type' => '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;
}

View file

@ -0,0 +1,467 @@
<?php
/**
* @file
* Enables comments to be nodes if needed.
*/
require_once(dirname(__FILE__) . '/includes/nodecomment.common.inc');
require_once(dirname(__FILE__) . '/includes/nodecomment.forms.inc');
require_once(dirname(__FILE__) . '/includes/nodecomment.theme.inc');
/**
* Implementation of hook_nodeapi().
*/
function nodecomment_nodeapi(&$node, $op, $arg = 0, $page = 0) {
switch ($op) {
case 'load':
return _nodecomment_nodeapi_load($node, $op, $arg, $page);
case 'presave':
// Restore comment setting so that we don't disable commenting
// accidentally.
if (isset($node->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(
'<a href="@login">Login</a> or <a href="@register">register</a> 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(
'<a href="@login">Login</a> 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',
);
}

View file

@ -0,0 +1,83 @@
<?php
$plugin = array(
'single' => 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'];
}

View file

@ -0,0 +1,260 @@
<?php
/**
* @file
* Provide views data and handlers for nodecomment.module
*/
/**
* Implementation of hook_views_data()
*/
function nodecomment_views_data() {
// since comments are nodes we keep with the Node group
$data['node_comments']['table']['group'] = t('Node comment');
// ----------------------------------------------------------------
// node table -- basic table information.
$data['node_comments']['table']['join'] = array(
// node_comments links to node
'node' => 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'
),
),
);
}

View file

@ -0,0 +1,144 @@
<?php
/**
* @file
* Contains default views on behalf of the nodecomment module.
*/
/**
* Implementation of hook_default_view_views().
*/
function nodecomment_views_default_views() {
$view = new view;
$view->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;
}

View file

@ -0,0 +1,18 @@
<?php
/**
* Argument handler to accept a user id to check for nodes that
* user posted or commented on (using Nodecomment).
*
* Based on original handler from the Views module.
*/
class nodecomment_handler_argument_comment_user_uid extends views_handler_argument_comment_user_uid {
function query() {
$this->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);
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* Field handler to allow linking to a user account or homepage
*/
class nodecomment_handler_field_author extends views_handler_field_user_name {
/**
* Override init function to add uid and homepage fields.
*/
function init(&$view, &$data) {
parent::init($view, $data);
$this->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;
}
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* Field handler to present a link to the node comment
*/
class nodecomment_handler_field_link extends views_handler_field_node_link {
function construct() {
parent::construct();
$this->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;
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* Field handler to display the number of new comments
*/
class nodecomment_handler_field_node_new_comments extends views_handler_field_node_new_comments {
function pre_render(&$values) {
global $user;
if (!$user->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;
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Field handler to display the depth of a comment
*/
class nodecomment_handler_relationship_nid_or_self extends views_handler_relationship {
function option_definition() {
$options = parent::option_definition();
unset($options['required']);
return $options;
}
/**
* Default options form that provides the label widget that all fields
* should have.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
unset($form['required']);
}
/**
* Called to implement a relationship in a query.
*/
function query() {
// Figure out what base table this relationship brings to the party.
$table_data = views_fetch_data($this->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);
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* Cache comment threads in Views.
*/
class nodecomment_plugin_cache_comments extends views_plugin_cache {
function option_defaults(&$options) {
$options['argument'] = '';
}
function options_form(&$form, &$form_state) {
$options = array();
$arguments = $this->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 ? '<span class="new">' . t('new') . '</span>' : '';
$new_class = 'comment-new';
if ($first_new) {
$first = "<a id=\"new\"></a>";
$first_new = FALSE;
}
}
$tokens["<!--post:first-new-$row->nid-->"] = $first;
$tokens["<!--post:new-$row->nid-->"] = $new_output;
$tokens["<!--post:new-class-$row->nid-->"] = $new_class;
}
// Replace
$output = strtr($output, $tokens);
}
}

View file

@ -0,0 +1,96 @@
<?php
class nodecomment_plugin_display_comments extends views_plugin_display {
function options_summary(&$categories, &$options) {
parent::options_summary($categories, $options);
$options['items_per_page']['value'] = t('From node type settings');
}
function options_form(&$form, &$form_state) {
// specifically override these options
if ($form_state['section'] == 'items_per_page') {
$form['#title'] .= $this->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');
}
}
}
}

View file

@ -0,0 +1,36 @@
<?php
class nodecomment_plugin_style_threaded extends views_plugin_style {
function render() {
$divs = 0;
$last_depth = 0;
$output = '';
drupal_add_css(drupal_get_path('module', 'comment') .'/comment.css');
foreach ($this->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 .= '<div class="indented">';
$last_depth++;
}
else {
while ($node->depth < $last_depth) {
$divs--;
$output .= '</div>';
$last_depth--;
}
}
$output .= node_view($node);
}
for ($i = 0; $i < $divs; $i++) {
$output .= '</div>';
}
return $output;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* Field handler to display the depth of a comment
*/
class views_handler_field_comment_depth extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['thread'] = 'thread';
}
/**
* Work out the depth of this comment
*/
function render($values) {
return count(explode('.', $values->{$this->aliases['thread']})) - 1;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* Base field handler to present a link.
*/
class views_handler_field_comment_link extends views_handler_field {
function construct() {
parent::construct();
$this->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']}));
}
}
?>

View file

@ -0,0 +1,21 @@
<?php
/**
* Field handler to present a link for replying to a nodecomment.
*/
class views_handler_field_comment_link_reply extends views_handler_field_comment_link {
function construct() {
parent::construct();
$this->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']});
}
}

View file

@ -0,0 +1,50 @@
<?php
/**
* Field handler to allow linking to a user account or homepage
*/
class views_handler_field_username_comment extends views_handler_field {
/**
* Override init function to add uid and homepage fields.
*/
function init(&$view, &$data) {
parent::init($view, $data);
$this->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);
}
}
?>

View file

@ -0,0 +1,23 @@
<?php
/**
* Sort handler for ordering by thread
*/
class views_handler_sort_comment_thread extends views_handler_sort {
function query() {
$this->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);
}
}
}
?>