FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 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,100 @@
You may want to visit the handbook of this module, at:
http://drupal.org/handbook/modules/flag
The Flag module is a flexible flagging system whose primary goal is
to give all the control to the administrator. Using this module, the
site administrator can provide an arbitrary number of 'flags'.
A flag is really just a boolean toggle that is set on a node, comment,
or user. Flags may be per-user, meaning that each user can flag an item
individually, or global, meaning that the item is either flagged or it
is not flagged, and any user who changes that changes it for everyone.
In this way, additional flags (similar to 'published' and 'sticky') can
be put on nodes, or other items, and dealt with by the system however
the administration likes.
Each flag allows the administrator to choose the 'flag this' text, and
the place where the user interface for flagging the item will appear
(For example: for nodes, whether a flagging link appears on the node
teaser as well on the full node view).
Each flag can be restricted to use only by certain roles. Each
flag provides data to the Views module, and provides a default
view to list 'My bookmarks'. These default views are somewhat crude,
but are easily tailored to whatever the system administrator would like
it to do.
Each flag also provides an 'argument' to the Views module that can be
used to allow a user to view other people's flagged content. This isn't
turned on by default anywhere, though, and the administrator will need
to construct a view in order to take advantage of it.
The module will come installed with a simple flag called "bookmarks" and
a simple view for 'My bookmarks'. This is a default view provided by the
Flag module, but can be customized to fit the needs of your site. To
customize this view, go to admin/structure/views and find the
'flags_bookmarks' view. Click the 'Add' action to customize the view.
Once saved, the new version of the view will be used rather than the one
provided by Flag.
Besides editing the default view that comes with the module, Flag
provides many views filters, fields, and sort criteria to make all sorts of
displays possible relating to the number of times an item has been flagged.
This module was formerly known as Views Bookmark, which was originally was
written by Earl Miles. Flag was written by Nathan Haug and mystery man Mooffie.
This module built by robots: http://www.lullabot.com
Recommended Modules
-------------------
- Views
- Session API
Installation
------------
1) Copy the flag directory to the modules folder in your installation.
2) Enable the module using Administer -> Modules (/admin/modules)
Optional Installation
---------------------
1) The ability for anonymous users to flag content is provided by the Session
API module, available at http://drupal.org/project/session_api.
Configuration
-------------
The configuration for Flag is spread between Views configuration
and the Flag site building page. To configure:
1) Configure the flags for your site at
Administer -> Structure -> Flags (/admin/structure/flags)
You can create and edit flags on this page. Descriptions for the various
options are provided below each field on the flag edit form.
2) Go to the Views building pages at
Administer -> Site Building -> Views (/admin/structure/views)
A default view is provided to get you started organizing your flags. You
can override the view or use it as a template to control the display of your
flags.
Support
-------
If you experience a problem with flag or have a problem, file a
request or issue on the flag queue at
http://drupal.org/project/issues/flag. DO NOT POST IN THE FORUMS.
Posting in the issue queues is a direct line of communication with the module
authors.
No guarantee is provided with this software, no matter how critical your
information, module authors are not responsible for damage caused by this
software or obligated in any way to correct problems you may experience.
Licensed under the GPL 2.0.
http://www.gnu.org/licenses/gpl-2.0.txt

View File

@@ -0,0 +1,374 @@
<?php
/**
* @file
* Hooks provided by the Flag module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Define one or more flag types.
*
* This hook may be placed in a $module.flag.inc file.
*
* @return
* An array whose keys are flag type names and whose values are properties of
* the flag type.
* When a flag type is for an entity, the flag type name must match the entity
* type.
* Properties for flag types are as follows:
* - 'title': The main label of the flag type.
* - 'description': A longer description shown in the UI when creating a new
* flag.
* - 'handler': The name of the class implementing this flag type.
* - 'module': (optional) The name of the module that should be registered as a
* dependency for this flag type.
*
* @see flag_fetch_definition()
*/
function hook_flag_type_info() {
return array(
'node' => array(
'title' => t('Nodes'),
'description' => t("Nodes are a Drupal site's primary content."),
'handler' => 'flag_node',
'module' => 'node',
),
);
}
/**
* Alter flag type definitions provided by other modules.
*
* This hook may be placed in a $module.flag.inc file.
*
* @param $definitions
* An array of flag definitions returned by hook_flag_type_info().
*/
function hook_flag_type_info_alter(&$definitions) {
}
/**
* Define default flags.
*/
function hook_flag_default_flags() {
$flags = array();
$flags['bookmarks'] = array (
'entity_type' => 'node',
'title' => 'Bookmarks',
'global' => FALSE,
'types' => array (
0 => 'article',
1 => 'blog',
),
'flag_short' => 'Bookmark this',
'flag_long' => 'Add this post to your bookmarks',
'flag_message' => 'This post has been added to your bookmarks',
'unflag_short' => 'Unbookmark this',
'unflag_long' => 'Remove this post from your bookmarks',
'unflag_message' => 'This post has been removed from your bookmarks',
'unflag_denied_text' => '',
'link_type' => 'toggle',
'weight' => 0,
'show_in_links' => array (
'full' => TRUE,
'token' => FALSE,
),
'show_as_field' => FALSE,
'show_on_form' => FALSE,
'access_author' => '',
'show_contextual_link' => TRUE,
'show_on_profile' => FALSE,
'access_uid' => '',
'api_version' => 3,
);
return $flags;
}
/**
* Allow modules to alter a flag when it is initially loaded.
*
* @see flag_get_flags().
*/
function hook_flag_alter(&$flag) {
}
/**
* Alter a flag's default options.
*
* Modules that wish to extend flags and provide additional options must declare
* them here so that their additions to the flag admin form are saved into the
* flag object.
*
* @param $options
* The array of default options for the flag type, with the options for the
* flag's link type merged in.
* @param $flag
* The flag object.
*
* @see flag_flag::options()
*/
function hook_flag_options_alter(&$options, $flag) {
}
/**
* Act on an object being flagged.
*
* @param $flag
* The flag object.
* @param $entity_id
* The id of the entity the flag is on.
* @param $account
* The user account performing the action.
* @param $flagging_id
* The flagging entity.
*/
function hook_flag_flag($flag, $entity_id, $account, $flagging) {
}
/**
* Act on an object being unflagged.
*
* This is invoked after the flag count has been decreased, but before the
* flagging entity has been deleted.
*
* @param $flag
* The flag object.
* @param $entity_id
* The id of the entity the flag is on.
* @param $account
* The user account performing the action.
* @param $flagging
* The flagging entity.
*/
function hook_flag_unflag($flag, $entity_id, $account, $flagging) {
}
/**
* Perform custom validation on a flag before flagging/unflagging.
*
* @param $action
* The action about to be carried out. Either 'flag' or 'unflag'.
* @param $flag
* The flag object.
* @param $entity_id
* The id of the entity the user is trying to flag or unflag.
* @param $account
* The user account performing the action.
* @param $flagging
* The flagging entity.
*
* @return
* Optional array: textual error with the error-name as the key.
* If the error name is 'access-denied' and javascript is disabled,
* drupal_access_denied will be called and a 403 will be returned.
* If validation is successful, do not return a value.
*/
function hook_flag_validate($action, $flag, $entity_id, $account, $skip_permission_check, $flagging) {
// We're only operating on the "test" flag, and users may always unflag.
if ($flag->name == 'test' && $action == 'flag') {
// Get all flags set by the current user.
$flags = flag_get_user_flags('node', NULL, $account->uid, $sid = NULL, $reset = FALSE);
// Check if this user has any flags of this type set.
if (isset($flags['test'])) {
$count = count($flags[$flag->name]);
if ($count >= 2) {
// Users may flag only 2 nodes with this flag.
return(array('access-denied' => t('You may only flag 2 nodes with the test flag.')));
}
}
}
}
/**
* Allow modules to allow or deny access to flagging for a single entity.
*
* Called when displaying a single entity view or edit page. For flag access
* checks from within Views, implement hook_flag_access_multiple().
*
* @param $flag
* The flag object.
* @param $entity_id
* The id of the entity in question.
* @param $action
* The action to test. Either 'flag' or 'unflag'.
* @param $account
* The user on whose behalf to test the flagging action.
*
* @return
* One of the following values:
* - TRUE: User has access to the flag.
* - FALSE: User does not have access to the flag.
* - NULL: This module does not perform checks on this flag/action.
*
* NOTE: Any module that returns FALSE will prevent the user from
* being able to use the flag.
*
* @see hook_flag_access_multiple()
* @see flag_flag:access()
*/
function hook_flag_access($flag, $entity_id, $action, $account) {
}
/**
* Allow modules to allow or deny access to flagging for multiple entities.
*
* Called when preparing a View or list of multiple flaggable entities.
* For flag access checks for individual entities, see hook_flag_access().
*
* @param $flag
* The flag object.
* @param $entity_ids
* An array of object ids to check access.
* @param $account
* The user on whose behalf to test the flagging action.
*
* @return
* An array whose keys are the object IDs and values are booleans indicating
* access.
*
* @see hook_flag_access()
* @see flag_flag:access_multiple()
*/
function hook_flag_access_multiple($flag, $entity_ids, $account) {
}
/**
* Define one or more flag link types.
*
* Link types defined here must be returned by this module's hook_flag_link().
*
* This hook may be placed in a $module.flag.inc file.
*
* @return
* An array of one or more types, keyed by the machine name of the type, and
* where each value is a link type definition as an array with the following
* properties:
* - 'title': The human-readable name of the type.
* - 'description': The description of the link type.
* - 'options': An array of extra options for the link type.
* - 'uses standard js': Boolean, indicates whether the link requires Flag
* module's own JS file for links.
* - 'uses standard css': Boolean, indicates whether the link requires Flag
* module's own CSS file for links.
* - 'provides form': (optional) Boolean indicating that this link type shows
* the user a flagging entity form. This property is used in the UI, eg to
* warn the admin user of link types that are not compatible with other
* flag options. Defaults to FALSE.
*
* @see flag_get_link_types()
* @see hook_flag_link_type_info_alter()
*/
function hook_flag_link_type_info() {
}
/**
* Alter other modules' definitions of flag link types.
*
* This hook may be placed in a $module.flag.inc file.
*
* @param $link_types
* An array of the link types defined by all modules.
*
* @see flag_get_link_types()
* @see hook_flag_link_type_info()
*/
function hook_flag_link_type_info_alter(&$link_types) {
}
/**
* Return the link for the link types this module defines.
*
* The type of link to be produced is given by $flag->link_type.
*
* When Flag uses a link type provided by this module, it will call this
* implementation of hook_flag_link(). This should return a single link's
* attributes, using the same structure as hook_link(). Note that "title" is
* provided by the Flag configuration if not specified here.
*
* @param $flag
* The full flag object for the flag link being generated.
* @param $action
* The action this link should perform. Either 'flag' or 'unflag'.
* @param $entity_id
* The ID of the node, comment, user, or other object being flagged. The type
* of the object can be deduced from the flag type.
*
* @return
* An array defining properties of the link.
*
* @see hook_flag_link_type_info()
* @see template_preprocess_flag()
*/
function hook_flag_link() {
}
/**
* Act on flag deletion.
*
* This is invoked after all the flag database tables have had their relevant
* entries deleted.
*
* @param $flag
* The flag object that has been deleted.
*/
function hook_flag_delete($flag) {
}
/**
* Act when a flag is reset.
*
* @param $flag
* The flag object.
* @param $entity_id
* The entity ID on which all flaggings are to be removed. May be NULL, in
* which case all of this flag's entities are to be unflagged.
* @param $rows
* Database rows from the {flagging} table.
*
* @see flag_reset_flag()
*/
function hook_flag_reset($flag, $entity_id, $rows) {
}
/**
* Alter the javascript structure that describes the flag operation.
*
* @param $flag
* The full flag object.
* @param $entity_id
* The ID of the node, comment, user or other object being flagged.
*
* @see flag_build_javascript_info()
*/
function hook_flag_javascript_info_alter() {
}
/**
* Alter a flag object that is being prepared for exporting.
*
* @param $flag
* The flag object.
*
* @see flag_export_flags()
*/
function hook_flag_export_alter($flag) {
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* @file
* Contains implementations of flag info hooks.
*/
/**
* Implements hook_flag_type_info().
*
* Defines the flag types this module implements.
*
* @return
* An "array of arrays", keyed by object type. The 'handler' slot
* should point to the PHP class implementing this flag.
*/
function flag_flag_type_info() {
// Entity types we specifically cater for.
$definitions = array(
'node' => array(
'title' => t('Nodes'),
'description' => t("Nodes are a Drupal site's primary content."),
'handler' => 'flag_node',
),
'user' => array(
'title' => t('Users'),
'description' => t('Users who have created accounts on your site.'),
'handler' => 'flag_user',
),
);
if (module_exists('comment')) {
$definitions['comment'] = array(
'title' => t('Comments'),
'description' => t('Comments are responses to node content.'),
'handler' => 'flag_comment',
'module' => 'comment',
);
}
if (module_exists('taxonomy')) {
$definitions['taxonomy_term'] = array(
'title' => t('Taxonomy Terms'),
'description' => t('Taxonomy terms are used to categorize content.'),
'handler' => 'flag_entity',
'module' => 'taxonomy',
);
}
return $definitions;
}
/**
* Implements hook_flag_type_info_alter().
*
* Step in and add flag types for any entities not yet catered for, using the
* basic flag_entity handler. This allows other modules to provide more
* specialized handlers for entities in hook_flag_type_info() as normal.
*/
function flag_flag_type_info_alter(&$definitions) {
foreach (entity_get_info() as $entity_type => $entity_info) {
// Only add flag support for entities that don't yet have them, and which
// are non-config entities.
if (!isset($definitions[$entity_type]) && empty($entity_info['configuration'])) {
// We deliberately exclude taxonomy vocabularies from the list of
// supported entity types because they aren't fieldable or directly
// viewable, which makes them impossible to flag.
if ($entity_type === 'taxonomy_vocabulary') {
continue;
}
$definitions[$entity_type] = array(
'title' => $entity_info['label'],
'description' => t('@entity-type entity', array('@entity-type' => $entity_info['label'])),
'handler' => 'flag_entity',
);
}
}
}
/**
* Implements hook_flag_link_type_info().
*/
function flag_flag_link_type_info() {
return array(
'toggle' => array(
'title' => t('JavaScript toggle'),
'description' => t('An AJAX request will be made and degrades to type "Normal link" if JavaScript is not available.'),
'uses standard js' => TRUE,
'uses standard css' => TRUE,
),
'normal' => array(
'title' => t('Normal link'),
'description' => t('A normal non-JavaScript request will be made and the current page will be reloaded.'),
'uses standard js' => FALSE,
'uses standard css' => FALSE,
),
'confirm' => array(
'title' => t('Confirmation form'),
'description' => t('The user will be taken to a confirmation form on a separate page to confirm the flag.'),
'options' => array(
'flag_confirmation' => '',
'unflag_confirmation' => '',
),
'uses standard js' => FALSE,
'uses standard css' => FALSE,
'provides form' => TRUE,
),
);
}

View File

@@ -0,0 +1,34 @@
name = Flag
description = Create customized flags that users can set on entities.
core = 7.x
package = Flags
configure = admin/structure/flags
; Files that contain classes.
; Flag classes
files[] = includes/flag/flag_flag.inc
files[] = includes/flag/flag_entity.inc
files[] = includes/flag/flag_node.inc
files[] = includes/flag/flag_comment.inc
files[] = includes/flag/flag_user.inc
files[] = includes/flag.cookie_storage.inc
files[] = includes/flag.entity.inc
files[] = flag.rules.inc
; Views
files[] = includes/views/flag_handler_argument_entity_id.inc
files[] = includes/views/flag_handler_field_ops.inc
files[] = includes/views/flag_handler_field_flagged.inc
files[] = includes/views/flag_handler_filter_flagged.inc
files[] = includes/views/flag_handler_sort_flagged.inc
files[] = includes/views/flag_handler_relationships.inc
files[] = includes/views/flag_plugin_argument_validate_flaggability.inc
files[] = tests/flag.test
; Information added by drupal.org packaging script on 2013-09-13
version = "7.x-3.2"
core = "7.x"
project = "flag"
datestamp = "1379063829"

View File

@@ -0,0 +1,98 @@
<?php
/**
* @file Contains Entity API property information.
*/
/**
* Implements hook_entity_property_info_alter().
*
* We add properties thus:
* - global flags:
* - entity->flag_FLAGNAME, boolean.
* - per-user flags:
* - entity->flag_FLAGNAME_users, list of users.
* - user->flag_FLAGNAME_flagged, list of user's flagged entities.
*/
function flag_entity_property_info_alter(&$info) {
foreach (flag_get_flags() as $flag) {
if ($flag->global) {
// Global flags.
// Boolean property on entity type.
// This can go on either the entity as a whole, or on bundles, depending
// on whether the flag is limited by bundle.
$property_definition = array(
'label' => t('Whether the entity is flagged with flag @flag', array(
'@flag' => $flag->name,
)),
'description' => t('Whether the entity is flagged with flag @flag.', array(
'@flag' => $flag->name,
)),
'type' => 'boolean',
'getter callback' => 'flag_properties_get_flagging_boolean',
'computed' => TRUE,
'flag_name' => $flag->name,
);
if (count($flag->types)) {
// Bundle specific property.
foreach ($flag->types as $type) {
$info[$flag->entity_type]['bundles'][$type]['properties']['flag_' . $flag->name] = $property_definition;
}
}
else {
// Generic property, applies for all bundles.
$info[$flag->entity_type]['properties']['flag_' . $flag->name] = $property_definition;
}
}
else {
// Per-user flags.
// User property: list of flagged entities by the user.
$info['user']['properties']['flag_' . $flag->name . '_flagged'] = array(
'label' => t('Flagged @entity-type with flag @flag', array(
'@entity-type' => $flag->entity_type,
'@flag' => $flag->name,
)),
'description' => t('Returns a list of entities a user flagged with flag @flag.', array(
'@flag' => $flag->name,
)),
'type' => 'list<' . $flag->entity_type . '>',
'getter callback' => 'flag_properties_get_flagged_entities',
'computed' => TRUE,
'flag_name' => $flag->name,
'flag_entity_type' => $flag->entity_type,
);
$info['user']['properties']['flag_sid'] = array(
'label' => t('Flag user session identifier'),
'description' => t('Returns the sessions id used to distinguish anonymous users from each other.'),
'type' => 'text',
'getter callback' => 'flag_properties_get_user_sid',
'computed' => TRUE,
);
// Entity property: list of users who have flagged this entity.
// This can go on either the entity as a whole, or on bundles, depending
// on whether the flag is limited by bundle.
$property_definition = array(
'label' => t('Users who flagged the entity with flag @flag', array(
'@flag' => $flag->name,
)),
'description' => t('Returns a list of users who flagged an entity with flag @flag.', array(
'@flag' => $flag->name,
)),
'type' => 'list<user>',
'getter callback' => 'flag_properties_get_flagging_users',
'computed' => TRUE,
'flag_name' => $flag->name,
);
if (count($flag->types)) {
// Bundle specific property.
foreach ($flag->types as $type) {
$info[$flag->entity_type]['bundles'][$type]['properties']['flag_' . $flag->name . '_user'] = $property_definition;
}
}
else {
// Generic property, applies for all bundles.
$info[$flag->entity_type]['properties']['flag_' . $flag->name . '_user'] = $property_definition;
}
}
}
}

View File

@@ -0,0 +1,687 @@
<?php
/**
* @file
* Flag module install/schema/update hooks.
*/
/**
* Implements hook_schema().
*/
function flag_schema() {
$schema = array();
$schema['flag'] = array(
'description' => 'All available flags in the system.',
'fields' => array(
'fid' => array(
'description' => 'The unique ID for this particular flag.',
'type' => 'serial',
'size' => 'small',
'unsigned' => TRUE,
'not null' => TRUE,
),
'entity_type' => array(
'description' => 'The flag type, such as one of "node", "comment", or "user".',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
'name' => array(
'description' => 'The machine-name for this flag.',
'type' => 'varchar',
'length' => '32',
'not null' => FALSE,
'default' => '',
),
'title' => array(
'description' => 'The human-readable title for this flag.',
'type' => 'varchar',
'length' => '255',
'not null' => FALSE,
'default' => '',
),
'global' => array(
'description' => 'Whether this flag state should act as a single toggle to all users across the site.',
'type' => 'int',
'size' => 'tiny',
'not null' => FALSE,
'default' => 0,
),
'options' => array(
'description' => 'The options and configuration of this flag.',
'type' => 'text',
'not null' => FALSE,
),
),
'primary key' => array('fid'),
'unique keys' => array(
'name' => array('name'),
),
);
$schema['flagging'] = array(
'description' => 'Objects that have been flagged.',
'fields' => array(
'flagging_id' => array(
'description' => 'The unique ID for this particular tag.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'fid' => array(
'description' => 'The unique flag ID this object has been flagged with, from {flag}.',
'type' => 'int',
'size' => 'small',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'entity_type' => array(
'description' => 'The flag type, eg "node", "comment", "user".',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
'entity_id' => array(
'description' => 'The unique ID of the object, such as either the {cid}, {uid}, or {nid}.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'uid' => array(
'description' => 'The user ID by whom this object was flagged.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'sid' => array(
'description' => "The user's session id as stored in the session table.",
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'timestamp' => array(
'description' => 'The UNIX time stamp representing when the flag was set.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-size' => 11,
)
),
'primary key' => array('flagging_id'),
'unique keys' => array(
'fid_entity_id_uid_sid' => array('fid', 'entity_id', 'uid', 'sid'),
),
'indexes' => array(
'entity_type_uid_sid' => array('entity_type', 'uid', 'sid'),
'entity_type_entity_id_uid_sid' => array('entity_type', 'entity_id', 'uid', 'sid'),
'entity_id_fid' => array('entity_id', 'fid'),
),
);
$schema['flag_types'] = array(
'description' => 'The entity bundles that are affected by a flag.',
'fields' => array(
'fid' => array(
'description' => 'The unqiue flag ID as defined for the flag in {flag}.',
'type' => 'int',
'size' => 'small',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'type' => array(
'description' => 'The entity bundles that can be flagged by this fid.',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
),
'indexes' => array(
'fid' => array('fid'),
),
);
$schema['flag_counts'] = array(
'description' => 'The number of times an item has been flagged.',
'fields' => array(
'fid' => array(
'type' => 'int',
'size' => 'small',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'entity_type' => array(
'description' => 'The flag type, usually one of "node", "comment", "user".',
'type' => 'varchar',
'length' => '128',
'not null' => TRUE,
'default' => '',
),
'entity_id' => array(
'description' => 'The unique ID of the content, usually either the {cid}, {uid}, or {nid}.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'count' => array(
'description' => 'The number of times this object has been flagged for this flag.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
'last_updated' => array(
'description' => 'The UNIX time stamp representing when the flag was last updated.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-size' => 11,
)
),
'primary key' => array('fid', 'entity_id'),
'indexes' => array(
'fid_entity_type' => array('fid', 'entity_type'),
'entity_type_entity_id' => array('entity_type', 'entity_id'),
'fid_count' => array('fid', 'count'),
'fid_last_updated' => array('fid', 'last_updated'),
),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function flag_uninstall() {
$result = db_select('variable', 'v')
->fields('v', array('name'))
->condition('name', 'flag_%', 'LIKE')
->execute();
foreach ($result as $row) {
variable_del($row->name);
}
drupal_set_message(t('Flag has been uninstalled.'));
}
/**
* Implements hook_requirements().
*/
function flag_requirements($phase) {
$requirements = array();
$t = get_t();
if ($phase == 'runtime') {
if (module_exists('translation') && !module_exists('translation_helpers')) {
$requirements['flag_translation'] = array(
'title' => $t('Flag'),
'severity' => REQUIREMENT_ERROR,
'description' => $t('To have the flag module work with translations, you need to install and enable the <a href="http://drupal.org/project/translation_helpers">Translation helpers</a> module.'),
'value' => $t('Translation helpers module not found.'),
);
}
if (module_exists('session_api')) {
if (file_exists('./robots.txt')) {
$flag_path = url('flag') . '/';
// We don't use url() because this may return an absolute URL when
// language negotiation is set to 'domain'.
$flag_path = parse_url($flag_path, PHP_URL_PATH);
$robots_string = 'Disallow: ' . $flag_path;
$contents = file_get_contents('./robots.txt');
if (strpos($contents, $robots_string) === FALSE) {
$requirements['flag_robots'] = array(
'title' => $t('Flag robots.txt problem'),
'severity' => REQUIREMENT_WARNING,
'description' => $t('Flag module may currently be used with anonymous users, however the robots.txt file does not exclude the "@flag-path" path, which may cause search engines to randomly flag and unflag content when they index the site. It is highly recommended to add "@robots-string" to your robots.txt file (located in the root of your Drupal installation).', array('@flag-path' => $flag_path, '@robots-string' => $robots_string)),
'value' => $t('Search engines flagging content'),
);
}
}
}
}
return $requirements;
}
function flag_update_last_removed() {
return 6004;
}
/**
* Convert role access to have separate "flag" and "unflag" permissions.
*/
function flag_update_6200() {
if (db_field_exists('flags', 'roles')) {
$result = db_select('flags', 'f')
->fields('f')
->execute();
foreach ($result as $flag) {
$roles = array_filter(explode(',', $flag->roles));
$options = unserialize($flag->options);
$options['roles'] = array(
'flag' => $roles,
'unflag' => $roles,
);
db_update('flags')
->fields(array(
'options' => serialize($options),
))
->condition('fid', $flag->fid)
->execute();
}
db_drop_field('flags', 'roles');
}
}
/**
* Refine the indexes.
*
* The content type inclusion actually slowed down on unique key. And a count
* index would be helpful for sorting by counts.
*/
function flag_update_6201() {
// Remove "content type" from one key, see http://drupal.org/node/612602.
db_drop_unique_key('flag_content', 'fid_content_type_content_id_uid');
db_add_unique_key('flag_content', 'fid_content_id_uid', array('fid', 'content_id', 'uid'));
// Add a count index, see http://drupal.org/node/489610.
db_add_index('flag_counts', 'count', array('count'));
}
/**
* Add the sid column and unique index on the flag_content table.
*/
function flag_update_6202() {
// Drop the keys affected by the addition of the SID column.
db_drop_unique_key('flag_content', 'fid_content_id_uid');
db_drop_index('flag_content', 'content_type_uid');
// Add the column.
db_add_field('flag_content', 'sid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0));
// Re-add the removed keys.
db_add_unique_key('flag_content', 'fid_content_id_uid_sid', array('fid', 'content_id', 'uid', 'sid'));
db_add_index('flag_content', 'content_type_uid_sid', array('content_type', 'uid', 'sid'));
}
/**
* Remove count = 0 rows from the count tables.
*/
function flag_update_6203() {
db_delete('flag_counts')
->condition('count', 0)
->execute();
}
/**
* Remove "content type" from the flag_counts primary key.
*/
function flag_update_6204() {
db_drop_primary_key('flag_counts');
db_add_primary_key('flag_counts', array('fid', 'content_id'));
}
/**
* Provide a better index on the flag_content table including 'uid' and 'sid'.
*/
function flag_update_6205() {
// This update has been removed and corrected in flag_update_6206.
// See http://drupal.org/node/1105490.
}
/**
* Correction to flag_update_6205(). Convert unique key to an index.
*/
function flag_update_6206() {
// Remove the old index that did not include UID or SID.
if (db_index_exists('flag_content', 'content_type_content_id')) {
db_drop_index('flag_content', 'content_type_content_id');
}
// Remove the erroneous unique key that was added in flag_update_6205().
if (db_index_exists('flag_content', 'content_type_content_id_uid_sid')) {
db_drop_unique_key('flag_content', 'content_type_content_id_uid_sid');
}
db_add_index('flag_content', 'content_type_content_id_uid_sid', array('content_type', 'content_id', 'uid', 'sid'));
}
/**
* Adds column last_updated to flag_counts table.
*/
function flag_update_6207() {
db_add_field('flag_counts', 'last_updated', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'disp-size' => 11), array('indexes' => array('last_updated' => array('last_updated'))));
}
/**
* Convert flag_count indexes to include FID for more efficient indexing.
*/
function flag_update_6208() {
db_drop_index('flag_counts', 'count');
db_drop_index('flag_counts', 'last_updated');
db_add_index('flag_counts', 'fid_count', array('fid', 'count'));
db_add_index('flag_counts', 'fid_last_updated', array('fid', 'last_updated'));
}
/**
* Clear caches.
*/
function flag_update_7201() {
// Do nothing. Update.php is going to clear caches for us.
}
/**
* Clean-up flag records for deleted nodes and comments.
*/
function flag_update_7202() {
// These queries can't use db_delete() because that doesn't support a
// subquery: see http://drupal.org/node/1267508.
// Clean-up for nodes.
db_query("DELETE FROM {flag_content} WHERE content_type = 'node' AND NOT EXISTS (SELECT 1 FROM {node} n WHERE content_id = n.nid)");
db_query("DELETE FROM {flag_counts} WHERE content_type = 'node' AND NOT EXISTS (SELECT 1 FROM {node} n WHERE content_id = n.nid)");
// Clean-up for comments. Do not use module_exists() because comment module
// could be disabled.
if (db_table_exists('comment')) {
db_query("DELETE FROM {flag_content} WHERE content_type = 'comment' AND NOT EXISTS (SELECT 1 FROM {comment} c WHERE content_id = c.cid)");
db_query("DELETE FROM {flag_counts} WHERE content_type = 'comment' AND NOT EXISTS (SELECT 1 FROM {comment} c WHERE content_id = c.cid)");
}
}
/**
* Add an index to help with view's flag_handler_relationship when not required.
*/
function flag_update_7203() {
// Skip if this index was also added by the 6.x-2.x branch.
if (!db_index_exists('flag_content', 'content_id_fid')) {
db_add_index('flag_content', 'content_id_fid', array('content_id', 'fid'));
}
}
/**
* Rebuild the class registry due to classes moving files.
*/
function flag_update_7300() {
registry_rebuild();
}
/**
* Rename {flag_content} table to {flagging} and {flags} table to {flag}.
*/
function flag_update_7301() {
db_rename_table('flag_content', 'flagging');
db_rename_table('flags', 'flag');
// A second cache clear appears to be required here...
cache_clear_all();
// ...which in fact isn't enough, as the schema cache appears to need explicit
// clearing to prevent the next updates failing to get the schema for the new
// table names.
drupal_get_schema(NULL, TRUE);
}
/**
* Rename database columns on the {flag} table.
*/
function flag_update_7302() {
// No keys or indexes are affected.
// Change field 'content_type' to 'entity_type'.
db_change_field('flag', 'content_type', 'entity_type',
// Spec of the field. Identical to our current hook_schema(): we're not
// changing anything except the name.
array(
'description' => 'The flag type, such as one of "node", "comment", or "user".',
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'default' => '',
)
// No keys to re-add.
);
}
/**
* Rename database columns on the {flagging} table.
*/
function flag_update_7303() {
// Drop affected keys and indexes.
db_drop_unique_key('flagging', 'fid_content_id_uid_sid');
db_drop_index('flagging', 'content_type_uid_sid');
db_drop_index('flagging', 'content_type_content_id_uid_sid');
db_drop_index('flagging', 'content_id_fid');
// Change field 'content_type' to 'entity_type'.
db_change_field('flagging', 'content_type', 'entity_type',
// Spec of the field. Identical to our current hook_schema(): we're not
// changing anything except the name.
array(
'description' => 'The flag type, eg "node", "comment", "user".',
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'default' => '',
),
// Keys spec. Some are short-lived, as they are about to be dropped again
// and have hybrid names that refer to 'content_id' still.
array(
'unique keys' => array(
'fid_content_id_uid_sid' => array('fid', 'content_id', 'uid', 'sid'),
),
'indexes' => array(
'entity_type_uid_sid' => array('entity_type', 'uid', 'sid'),
'entity_type_content_id_uid_sid' => array('entity_type', 'content_id', 'uid', 'sid'),
'content_id_fid' => array('content_id', 'fid'),
),
)
);
// Now we have to drop keys and indexes all over again!
db_drop_unique_key('flagging', 'fid_content_id_uid_sid');
db_drop_index('flagging', 'entity_type_content_id_uid_sid');
db_drop_index('flagging', 'content_id_fid');
// Change field 'content_id' to 'entity_id'.
db_change_field('flagging', 'content_id', 'entity_id',
// Spec of the field. Identical to our current hook_schema(): we're not
// changing anything except the name.
array(
'description' => 'The unique ID of the content, such as either the {cid}, {uid}, or {nid}.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
// Keys spec. Identical to current hook_schema().
array(
'unique keys' => array(
'fid_entity_id_uid_sid' => array('fid', 'entity_id', 'uid', 'sid'),
),
'indexes' => array(
'entity_type_entity_id_uid_sid' => array('entity_type', 'entity_id', 'uid', 'sid'),
'entity_id_fid' => array('entity_id', 'fid'),
),
)
);
// A serial field must be defined as a key, so make a temporary index on
// 'fcid' so we can safely drop the primary key.
// @see http://drupal.org/node/190027
db_add_index('flagging', 'temp', array('fcid'));
// Drop the primary key so we can rename the field.
db_drop_primary_key('flagging');
// Change field 'fcid' to 'flagging_id'.
db_change_field('flagging', 'fcid', 'flagging_id',
// Spec of the field. Identical to our current hook_schema(): we're not
// changing anything except the name.
array(
'description' => 'The unique ID for this particular tag.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
// Keys spec. Identical to current hook_schema().
array(
'primary key' => array('flagging_id'),
)
);
// Drop our temporary index.
db_drop_index('flagging', 'temp');
cache_clear_all();
}
/**
* Rename database columns on the {flag_counts} table.
*/
function flag_update_7304() {
// Drop keys and indexes using 'content_type'.
db_drop_index('flag_counts', 'fid_content_type');
db_drop_index('flag_counts', 'content_type_content_id');
// Change field 'content_type' to 'entity_type'.
db_change_field('flag_counts', 'content_type', 'entity_type',
// Spec of the field. Identical to our current hook_schema(): we're not
// changing anything except the name.
array(
'description' => 'The flag type, usually one of "node", "comment", "user".',
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'default' => '',
),
// Keys spec. Some are short-lived, as they are about to be dropped again.
// Note the hybrid names refer to 'content_id' still.
array(
'indexes' => array(
'fid_entity_type' => array('fid', 'entity_type'),
'entity_type_content_id' => array('entity_type', 'content_id'),
)
)
);
// Now drop keys and indexes using 'content_id'.
db_drop_primary_key('flag_counts');
db_drop_index('flag_counts', 'entity_type_content_id');
// Change field 'content_id' to 'entity_id'.
db_change_field('flag_counts', 'content_id', 'entity_id',
// Spec of the field. Identical to our current hook_schema(): we're not
// changing anything except the name.
array(
'description' => 'The unique ID of the content, usually either the {cid}, {uid}, or {nid}.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'disp-width' => '10',
),
// Keys spec. Identical to current hook_schema() now we're finished.
array(
'primary key' => array('fid', 'entity_id'),
'indexes' => array(
'entity_type_entity_id' => array('entity_type', 'entity_id'),
),
)
);
}
/**
* Convert flag roles to permissions.
*/
function flag_update_7305() {
// We can't use flag_get_flags() to get all flags to act on, because that
// now looks for user permissions and we want the old roles array to convert.
// Hence we need to get flags directly from the database.
// Flags defined in code are saved in the database by flag_get_flags(), so
// this will get them too, unless the module providing them was *only just*
// installed before update.php was run. This edge case is not covered.
$result = db_query("SELECT name, options FROM {flag}");
$flag_data = $result->fetchAllKeyed();
// Note we don't call hook_flag_alter() because we don't have a complete flag.
// If your custom module does something to flag roles, it is your
// responsibility to handle upgrading your extra role data.
foreach ($flag_data as $flag_name => $flag_options) {
$flag_options = unserialize($flag_options);
$flag_roles = $flag_options['roles'];
foreach ($flag_roles['flag'] as $rid) {
$permission = "flag $flag_name";
user_role_grant_permissions($rid, array($permission));
}
foreach ($flag_roles['unflag'] as $rid) {
$permission = "unflag $flag_name";
user_role_grant_permissions($rid, array($permission));
}
// Save the flag options with the roles array removed.
unset($flag_options['roles']);
db_update('flag')
->fields(array(
'options' => serialize($flag_options),
))
->condition('name', $flag_name)
->execute();
}
// Flags in code will now report as overridden because the roles option is no
// longer output. Notify the user that they should update them.
if (count(module_implements('flag_default_flags'))) {
drupal_set_message(t('Flags which are defined in code with hook_flag_default_flags() or Features need to be re-exported.'));
}
// Direct the user to read the change notice, which has more details of how
// access to flag objects has been affected.
return t('Flag roles have been converted to user permissions. Permissions have been granted to each flag based on flag roles. You should review the consequences of this in the <a href="!url">change record</a>.', array(
'!url' => 'http://drupal.org/node/1724256',
));
}
/**
* Convert flag view modes settings.
*/
function flag_update_7306() {
foreach (flag_get_flags() as $flag) {
// Update show_on_teaser property to use new view mode settings.
if (!empty($flag->show_on_teaser)) {
$flag->show_in_links['teaser'] = TRUE;
unset($flag->show_on_teaser);
}
// Update show_on_page property to use new view mode settings.
if (!empty($flag->show_on_page)) {
$flag->show_in_links['full'] = TRUE;
unset($flag->show_on_page);
}
// Update show_on_comment and show_on_entity properties to use new view
// mode settings. Since the old logic was to show on all view modes, do that.
if (!empty($flag->show_on_entity) || !empty($flag->show_on_comment)) {
if ($entity_info = entity_get_info($flag->entity_type)) {
foreach ($entity_info['view modes'] as $view_mode => $value) {
$flag->show_in_links[$view_mode] = TRUE;
}
}
unset($flag->show_on_entity, $flag->show_on_comment);
}
$flag->save();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,582 @@
<?php
/**
* @file
* Rules integration for the Flag module.
*/
/**
* Implements hook_rules_data_info().
* @ingroup rules
*/
function flag_rules_data_info() {
return array(
'flag' => array(
'label' => t('flag'),
'ui class' => 'FlagRulesUIClass',
'wrapper class' => 'FlagRulesDataWrapper',
'wrap' => TRUE,
),
'flagging' => array(
'label' => t('flagging'),
'parent' => 'entity',
'group' => t('flag'),
),
);
}
/**
* A custom wrapper class for flags to be used with Rules.
* @ingroup rules
*/
class FlagRulesDataWrapper extends RulesIdentifiableDataWrapper implements RulesDataWrapperSavableInterface {
protected function extractIdentifier($flag) {
return $flag->name;
}
protected function load($name) {
return flag_get_flag($name);
}
public function save() {
$flag = $this->value();
$flag->save();
}
public function validate($value) {
if (isset($value) && is_string($value)) {
return TRUE;
}
elseif (isset($value) && is_object($value) && $value instanceof flag_flag) {
return TRUE;
}
return parent::validate($value);
}
}
/**
* UI for inputing flags.
* @ingroup rules
*/
class FlagRulesUIClass extends RulesDataUI implements RulesDataDirectInputFormInterface {
public static function getDefaultMode() {
return 'input';
}
public static function inputForm($name, $info, $settings, RulesPlugin $element) {
$options = _flag_rules_flags_options(isset($info['flag_type']) ? $info['flag_type'] : NULL);
$header = array(
'title' => t('Flag:'),
'type' => t('The flag type'),
'global' => t('Is the flag global?'),
);
$settings += array($name => isset($info['default value']) ? $info['default value'] : '');
$form[$name] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#required' => empty($info['optional']),
'#multiple' => FALSE,
'#default_value' => $settings[$name],
'#empty' => t('There is no suiting flag available.')
);
return $form;
}
public static function render($value) {
$flag = flag_get_flag($value);
if ($flag === FALSE) {
return array();
}
return array(
'content' => array('#markup' => check_plain($flag->get_title())),
'#attributes' => array('class' => array('rules-parameter-flag')),
);
}
}
function _flag_rules_flags_options($flag_type = NULL) {
$flags = flag_get_flags();
$options = array();
foreach ($flags as $flag) {
if (!isset($flag_type) || $flag->entity_type == $flag_type) {
$options[$flag->name] = array(
'title' => $flag->get_title(),
'type' => $flag->entity_type,
'global' => $flag->global ? t('Yes') : t('No'),
);
}
}
return $options;
}
/**
* Implements hook_rules_event_info().
*/
function flag_rules_event_info() {
$items = array();
$flags = flag_get_flags();
foreach ($flags as $flag) {
// We only support flags on entities.
if ($info = entity_get_info($flag->entity_type)) {
$variables = array(
'flag' => array(
'type' => 'flag',
'label' => t('flag'),
'flag_type' => $flag->entity_type,
),
'flagged_' . $flag->entity_type => array(
'type' => $flag->entity_type,
'label' => $info['label'],
),
'flagging_user' => array(
'type' => 'user',
'label' => t('flagging user'),
),
'flagging' => array(
'type' => 'flagging',
'label' => t('flagging'),
),
);
// For each flag we define two events.
$items['flag_flagged_' . $flag->name] = array(
'group' => t('Flag'),
'label' => t('A @flag-type has been flagged, under "@flag-title"', array('@flag-title' => $flag->get_title(), '@flag-type' => t($flag->entity_type))),
'variables' => $variables,
'access callback' => 'flag_rules_integration_access',
);
$items['flag_unflagged_' . $flag->name] = array(
'group' => t('Flag'),
'label' => t('A @flag-type has been unflagged, under "@flag-title"', array('@flag-title' => $flag->get_title(), '@flag-type' => t($flag->entity_type))),
'variables' => $variables,
'access callback' => 'flag_rules_integration_access',
);
}
}
return $items;
}
/**
* Implements hook_rules_action_info().
*/
function flag_rules_action_info() {
$param_defaults = array(
'flagging_user' => array(
'type' => 'user',
'label' => t('User on whose behalf to flag'),
'description' => t('For non-global flags, this is the user on whose behalf to flag the object. In addition, if checked below, the access permissions to the flag are checked against this user.'),
),
'permission_check' => array(
'type' => 'boolean',
'label' => t('Skip permission check'),
'description' => t('Whether to ignore permissions of the user on whose behalf to flag.'),
'restriction' => 'input',
),
);
$items = array(
'flag_trim' => array(
'label' => t('Trim a flag'),
'base' => 'flag_rules_action_trim',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
),
'flagging_user' => array(
'type' => 'user',
'label' => t('User whose flag to trim'),
'description' => t('For non-global flags, this is the user whose flag to trim. (For global flags, this argument is ignored.)'),
),
'cutoff_size' => array(
'type' => 'integer',
'label' => t('Flag queue size'),
'description' => t('The maximum number of objects to keep in the queue. Newly flagged objects will be kept; older ones will be removed. Tip: by typing "1" here you implement a singleton.'),
),
'trim_newest' => array(
'type' => 'boolean',
'label' => t('Trim newest flags'),
'description' => t('Checking this will trim the newest flags. This will prevent new flags once a limit is reached.'),
),
'permission_check' => $param_defaults['permission_check'],
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
'fetch_overall_flag_count' => array(
'label' => t('Fetch overall flag count'),
'base' => 'flag_rules_action_fetch_overall_flag_count',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
),
),
'provides' => array(
'overall_flag_count' => array(
'label' => t('Overall flag count'),
'type' => 'integer',
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
'fetch_entity_flag_count' => array(
'label' => t('Fetch entity flag count'),
'base' => 'flag_rules_action_fetch_entity_flag_count',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
),
'entity_type' => array(
'type' => 'text',
'label' => t('Entity type'),
'options list' => 'flag_rules_get_flag_types',
'restriction' => 'input',
),
),
'provides' => array(
'entity_flag_count' => array(
'label' => t('Entity flag count'),
'type' => 'integer',
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
'fetch_user_flag_count' => array(
'label' => t('Fetch user flag count'),
'base' => 'flag_rules_action_fetch_user_flag_count',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
),
'user' => array(
'type' => 'user',
'label' => t('User'),
),
),
'provides' => array(
'user_flag_count' => array(
'label' => t('User flag count'),
'type' => 'integer',
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
);
foreach (flag_get_types() as $type) {
$entity_info = entity_get_info($type);
$label = $entity_info['label'];
$items += array(
'flag_fetch_' . $type . '_by_user' => array(
'label' => t('Fetch @label flagged by user', array('@label' => $label)),
'base' => 'flag_rules_action_fetch_entity_by_user',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
'flag_type' => $type,
'description' => t('The flag to check for.'),
),
'flagging_user' => array(
'type' => 'user',
'label' => t('User who flagged the @label', array('@label' => $label)),
'description' => t('For non-global flags, this is the user who flagged the @label. (For global flags, this argument is ignored.)', array('@label' => $label)),
),
),
'provides' => array(
'content_flagged_by_user' => array(
'label' => t('Content flagged by user'),
'type' => 'list<' . $type . '>',
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
'flag_flag' . $type => array(
'label' => t('Flag a @label', array('@label' => $label)),
'base' => 'flag_rules_action_flag',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
'flag_type' => $type,
'description' => t('The flag to check for.'),
),
$type => array(
'type' => $type,
'label' => $label,
),
) + $param_defaults,
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
'flag_unflag' . $type => array(
'label' => t('Unflag a @label', array('@label' => $label)),
'base' => 'flag_rules_action_unflag',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
'flag_type' => $type,
'description' => t('The flag to check for.'),
),
$type => array(
'type' => $type,
'label' => $label,
),
) + $param_defaults,
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
);
$items['flag_fetch_users_' . $type] = array(
'label' => t('Fetch users who have flagged a @label', array('@label' => $label)),
'base' => 'flag_rules_action_fetch_users',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
'flag_type' => $type,
'description' => t('Choose the flag for which to fetch the users.'),
),
$type => array(
'type' => $type,
'label' => $label,
),
),
'provides' => array(
'users' => array(
'label' => t('Users who flagged'),
'type' => 'list<user>',
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
);
}
// For backward compatibility sake. This was the original name of the 'fetch node by user'.
$items['flag_fetch_entity_by_user'] = $items['flag_fetch_node_by_user'];
$items['flag_fetch_entity_by_user']['label'] .= ' '. t('(Legacy)');
return $items;
}
/**
* Base action implementation: Flag.
*/
function flag_rules_action_flag($flag, $entity, $flagging_user, $permissions_check) {
$flag->flag('flag', $flag->get_entity_id($entity), $flagging_user, $permissions_check);
}
/**
* Base action implementation: Unflag.
*/
function flag_rules_action_unflag($flag, $entity, $flagging_user, $permissions_check) {
$flag->flag('unflag', $flag->get_entity_id($entity), $flagging_user, $permissions_check);
}
/**
* Base action implementation: Trim flag.
*/
function flag_rules_action_trim($flag, $flagging_user, $cutoff_size, $trim_newest, $permissions_check) {
// For some reason, when this action fires in response to a flagging event,
// as an anonymous user, then the $flagging_user is sent through as FALSE.
// Not sure why. This workaround fixes the problem in this specific case.
if ($flagging_user === FALSE) {
$flagging_user = $GLOBALS['user'];
}
flag_trim_flag($flag, $flagging_user, $cutoff_size, $trim_newest, $permissions_check);
}
/**
* Base action implementation: Fetch users who flagged an entity.
*/
function flag_rules_action_fetch_users($flag, $entity) {
$result = db_select('flagging', 'fc')
->fields('fc', array('uid'))
->condition('entity_type', $flag->entity_type)
->condition('entity_id', $flag->get_entity_id($entity))
->condition('fid', $flag->fid)
->execute();
$uids = $result->fetchCol();
// Filter out anonymous users.
return array('users' => array_filter($uids));
}
/**
* Base action implementation: Fetch entities who were flagged a user.
*/
function flag_rules_action_fetch_entity_by_user($flag, $entity) {
$user = entity_metadata_wrapper('user', $entity);
$sid = $user->flag_sid->value();
$query = db_select('flagging', 'fc')
->fields('fc', array('entity_id'))
->condition('entity_type', $flag->entity_type)
->condition('uid', $user->uid->value())
->condition('fid', $flag->fid);
// Filter out any bad session ids and any users that aren't anonymous.
if (!empty($sid) && $sid != -1) {
$query->condition('sid', $sid);
}
$result = $query->execute();
$flagged = $result->fetchCol();
return array('content_flagged_by_user' => $flagged);
}
/**
* Base action implementation: Fetch overall count for a particular flag.
*/
function flag_rules_action_fetch_overall_flag_count($flag) {
$count = flag_get_flag_counts($flag->name);
return array('overall_flag_count' => $count);
}
/**
* Helper function which will return all the available flag types.
*
* @return
* An array of flag type names keyed by the type name.
*/
function flag_rules_get_flag_types() {
$types = array();
foreach (flag_get_types() as $type) {
$types[$type] = $type;
}
return $types;
}
/**
* Base action implementation: Fetch count of flags for a particular entity.
*/
function flag_rules_action_fetch_entity_flag_count($flag, $entity_type) {
$count = flag_get_entity_flag_counts($flag, $entity_type);
return array('entity_flag_count' => $count);
}
/**
* Base action implementation: Fetch user's flag count.
*/
function flag_rules_action_fetch_user_flag_count($flag, $user) {
$count = flag_get_user_flag_counts($flag, $user);
return array('user_flag_count' => $count);
}
/**
* Implements hook_rules_condition_info().
*/
function flag_rules_condition_info() {
$items = array();
foreach (flag_get_types() as $type) {
$entity_info = entity_get_info($type);
$label = isset($entity_info[$type]['label']) ? $entity_info[$type]['label'] : $type;
$items += array(
'flag_threshold_' . $type => array(
'label' => drupal_ucfirst(t('@type has flagging count', array('@type' => $label))),
'base' => 'flag_rules_condition_threshold',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
'flag_type' => $type,
'description' => t('The flag to check for.')
),
$type => array(
'type' => $type,
'label' => $label,
),
'number' => array(
'type' => 'integer',
'label' => t('Number'),
'description' => t('The number against which to test the number of times the object is flagged. For example, if you type "3" here, and choose "Greater than" for the operator, then this condition will return TRUE if the object is flagged more than three times.'),
),
'operator' => array(
'type' => 'text',
'label' => t('Comparison operator'),
'options list' => 'flag_rules_condition_threshold_operator_options',
'restriction' => 'input',
'default value' => '=',
'optional' => TRUE,
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
'flag_flagged_' . $type => array(
'label' => drupal_ucfirst(t('@type is flagged', array('@type' => $label))),
'base' => 'flag_rules_condition_flagged',
'parameter' => array(
'flag' => array(
'type' => 'flag',
'label' => t('Flag'),
'flag_type' => $type,
'description' => t('The flag to check for.')
),
$type => array(
'type' => $type,
'label' => $label,
),
'flagging_user' => array(
'type' => 'user',
'label' => t('User on whose behalf to check'),
'description' => t('For non-global flags, this is the user on whose behalf the flag is checked.'),
),
),
'group' => t('Flag'),
'access callback' => 'flag_rules_integration_access',
),
);
}
return $items;
}
/**
* Options list callback for the operator parameter of the flagging threshold condition.
*/
function flag_rules_condition_threshold_operator_options() {
return array(
'>' => t('Greater than'),
'>=' => t('Greater than or equal'),
'=' => t('Equal to'),
'<=' => t('Less than or equal'),
'<' => t('Less than'),
);
}
/**
* Condition: Check flagging count.
*/
function flag_rules_condition_threshold($flag, $entity, $number, $operator = '=') {
$count = $flag->get_count($flag->get_entity_id($entity));
switch ($operator) {
case '>' : return $count > $number;
case '>=': return $count >= $number;
case '=' : return $count == $number;
case '<' : return $count < $number;
case '<=': return $count <= $number;
}
}
/**
* Condition: Flag is flagged.
*/
function flag_rules_condition_flagged($flag, $entity, $account) {
return $flag->is_flagged($flag->get_entity_id($entity), $account->uid);
}
/**
* Rules integration access callback.
*/
function flag_rules_integration_access($type, $name) {
return user_access('administer flags');
}

View File

@@ -0,0 +1,195 @@
<?php
/**
* @file
* Flag module tokens support.
*/
/**
* Implements of hook_token_info().
*/
function flag_token_info() {
$types = array();
$tokens = array();
// Flag tokens.
$types['flag'] = array(
'name' => t('Flags'),
'description' => t('Tokens related to flag data.'),
'needs-data' => 'flag',
);
$tokens['flag']['name'] = array(
'name' => t('Flag name'),
'description' => t('The flag machine-readable name.'),
);
$tokens['flag']['title'] = array(
'name' => t('Flag title'),
'description' => t('The human-readable flag title.'),
);
// Flagging tokens.
//
// Attached fields are exposed as tokens via some contrib module, but we
// need to expose other fields ourselves. Currently, 'date' is the only such
// field we expose.
$types['flagging'] = array(
'name' => t('Flaggings'),
'description' => t('Tokens related to flaggings.'),
'needs-data' => 'flagging',
);
$tokens['flagging']['date'] = array(
'name' => t('Flagging date'),
'description' => t('The date an item was flagged.'),
'type' => 'date',
);
// Flag action tokens.
$types['flag-action'] = array(
'name' => t('Flag actions'),
'description' => t('Tokens available in response to a flag action being executed by a user.'),
'needs-data' => 'flag-action',
);
$tokens['flag-action']['action'] = array(
'name' => t('Flag action'),
'description' => t('The flagging action taking place, either "flag" or "unflag".'),
);
$tokens['flag-action']['entity-url'] = array(
'name' => t('Flag entity URL'),
'description' => t('The URL of the entity being flagged.'),
);
$tokens['flag-action']['entity-title'] = array(
'name' => t('Flag entity title'),
'description' => t('The title of the entity being flagged.'),
);
$tokens['flag-action']['entity-type'] = array(
'name' => t('Flag entity type'),
'description' => t('The type of entity being flagged, such as <em>node</em> or <em>comment</em>.'),
);
$tokens['flag-action']['entity-id'] = array(
'name' => t('Flag entity ID'),
'description' => t('The ID of entity being flagged, such as a nid or cid.'),
);
$tokens['flag-action']['count'] = array(
'name' => t('Flag count'),
'description' => t('The current count total for this flag.'),
);
// Add tokens for the flag count available at the node/comment/user level.
foreach (flag_get_types() as $flag_type) {
$flags = flag_get_flags($flag_type);
foreach ($flags as $flag) {
$tokens[$flag_type]['flag-' . str_replace('_', '-', $flag->name) . '-count'] = array(
'name' => t('@flag flag count', array('@flag' => $flag->get_title())),
'description' => t('Total flag count for flag @flag', array('@flag' => $flag->get_title())),
);
$tokens[$flag_type]['flag-' . str_replace('_', '-', $flag->name) . '-link'] = array(
'name' => t('@flag flag link', array('@flag' => $flag->get_title())),
'description' => t('Flag/unflag link for @flag', array('@flag' => $flag->get_title())),
);
}
}
return array(
'types' => $types,
'tokens' => $tokens,
);
}
/**
* Implements hook_tokens().
*/
function flag_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']);
$langcode = isset($options['language']) ? $options['language']->language : NULL;
if ($type == 'flag' && !empty($data['flag'])) {
$flag = $data['flag'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'name':
$replacements[$original] = $sanitize ? check_plain($flag->name) : $flag->name;
break;
case 'title':
$replacements[$original] = $sanitize ? check_plain($flag->get_title()) : $flag->get_title();
break;
}
}
}
elseif ($type == 'flagging' && !empty($data['flagging'])) {
$flagging = $data['flagging'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'date':
$replacements[$original] = format_date($flagging->timestamp, 'medium', '', NULL, $langcode);
break;
}
}
if ($date_tokens = token_find_with_prefix($tokens, 'date')) {
$replacements += token_generate('date', $date_tokens, array('date' => $flagging->timestamp), $options);
}
}
elseif ($type == 'flag-action' && !empty($data['flag-action'])) {
$action = $data['flag-action'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'action':
$replacements[$original] = $action->action;
break;
case 'entity-url':
$replacements[$original] = $sanitize ? check_url($action->entity_url) : $action->entity_url;
break;
case 'entity-title':
$replacements[$original] = $sanitize ? check_plain($action->entity_title) : $action->entity_title;
break;
case 'entity-type':
$replacements[$original] = $action->entity_type;
break;
case 'entity-id':
$replacements[$original] = $action->entity_id;
break;
case 'count':
$replacements[$original] = $action->count;
break;
}
}
}
if (isset($data[$type]) && in_array($type, flag_get_types())) {
$flags = flag_get_flags($type);
$object = $data[$type];
foreach ($flags as $flag) {
foreach ($tokens as $name => $original) {
$flag_count_token = 'flag-' . str_replace('_', '-', $flag->name) . '-count';
$flag_link_token = 'flag-' . str_replace('_', '-', $flag->name) . '-link';
if ($name == $flag_count_token) {
$replacements[$original] = $flag->get_count($flag->get_entity_id($object));
}
elseif ($name == $flag_link_token) {
$replacements[$original] = flag_create_link($flag->name, $flag->get_entity_id($object));
}
}
}
}
return $replacements;
}
/**
* Returns HTML for a tokens browser.
*
* @param $variables
* An associative array containing:
* - types: An array naming the types of tokens to show.
* - global_types: Whether to show global tokens.
*/
function theme_flag_tokens_browser($variables) {
$types = $variables['types'];
$global_types = $variables['global_types'];
if (module_exists('token')) {
return theme('token_tree', array('token_types' => $types, 'global_types' => $global_types));
}
else {
return '<p><em>' . t("Note: You don't have the <a href='@token-url'>Token</a> module installed, so the list of available tokens isn't shown here. You don't have to install <a href='@token-url'>Token</a> to be able to use tokens, but if you have it installed, and enabled, you'll be able to enjoy an interactive tokens browser.", array('@token-url' => 'http://drupal.org/project/token')) . '</em></p>';
}
}

View File

@@ -0,0 +1,15 @@
name = Flag actions
description = Execute actions on Flag events.
core = 7.x
dependencies[] = flag
package = Flags
configure = admin/structure/flags/actions
files[] = flag.install
files[] = flag_actions.module
; Information added by drupal.org packaging script on 2013-09-13
version = "7.x-3.2"
core = "7.x"
project = "flag"
datestamp = "1379063829"

View File

@@ -0,0 +1,95 @@
<?php
/**
* @file
* Flag actions install file.
*/
/**
* Implements hook_uninstall().
*/
function flag_actions_uninstall() {
}
/**
* Implements hook_schema().
*/
function flag_actions_schema() {
$schema = array();
$schema['flag_actions'] = array(
'fields' => array(
'aid' => array(
'type' => 'serial',
'not null' => TRUE,
'disp-width' => '5',
),
'fid' => array(
'type' => 'int',
'size' => 'small',
'not null' => FALSE,
'disp-width' => '5',
),
'event' => array(
'type' => 'varchar',
'length' => '255',
'not null' => FALSE,
),
'threshold' => array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'disp-width' => '5',
),
'repeat_threshold' => array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'disp-width' => '5',
),
'callback' => array(
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
'default' => '',
),
'parameters' => array(
'type' => 'text',
'size' => 'big',
'not null' => TRUE,
),
),
'primary key' => array('aid'),
);
return $schema;
}
/**
* Add a "repeat_threshold" value to all existing Flag actions.
*/
function flag_actions_update_6200() {
// Add the new repeat_threshold column.
if (!db_field_exists('flag_actions', 'repeat_threshold')) {
$column = array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'disp-width' => '5',
);
db_add_field('flag_actions', 'repeat_threshold', $column);
}
// Update the normal threshold column to default to 0.
$column = array(
'type' => 'int',
'size' => 'small',
'not null' => TRUE,
'default' => 0,
'disp-width' => '5',
);
db_change_field('flag_actions', 'threshold', 'threshold', $column);
}

View File

@@ -0,0 +1,692 @@
<?php
/**
* @file
* Actions support for the Flag module.
*/
/**
* Implements hook_flag_flag(). Trigger actions if any are available.
*/
function flag_actions_flag_flag($flag, $entity_id, $account, $flagging) {
flag_actions_do('flag', $flag, $entity_id, $account);
}
/**
* Implements hook_flag_unflag(). Trigger actions if any are available.
*/
function flag_actions_flag_unflag($flag, $entity_id, $account, $flagging) {
flag_actions_do('unflag', $flag, $entity_id, $account);
}
/**
* Implements hook_menu().
*/
function flag_actions_menu() {
$items = array();
$items[FLAG_ADMIN_PATH . '/actions'] = array(
'title' => 'Actions',
'page callback' => 'flag_actions_page',
'access callback' => 'user_access',
'access arguments' => array('administer actions'),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
$items[FLAG_ADMIN_PATH . '/actions/add'] = array(
'title' => 'Add action',
'page callback' => 'drupal_get_form',
'page arguments' => array('flag_actions_form', NULL, 5),
'access callback' => 'user_access',
'access arguments' => array('administer actions'),
'type' => MENU_CALLBACK,
);
$items[FLAG_ADMIN_PATH . '/actions/delete'] = array(
'title' => 'Delete action',
'page callback' => 'drupal_get_form',
'page arguments' => array('flag_actions_delete_form', 5),
'access callback' => 'user_access',
'access arguments' => array('administer actions'),
'type' => MENU_CALLBACK,
);
$items[FLAG_ADMIN_PATH . '/actions/configure'] = array(
'title' => 'Edit action',
'page callback' => 'drupal_get_form',
'page arguments' => array('flag_actions_form', 5),
'access callback' => 'user_access',
'access arguments' => array('administer actions'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_theme().
*/
function flag_actions_theme() {
return array(
'flag_actions_page' => array(
'variables' => array('actions' => NULL, 'form' => NULL),
),
'flag_actions_add_form' => array(
'render element' => 'form',
),
'flag_actions_flag_form' => array(
'render element' => 'form',
),
);
}
function flag_actions_get_action($aid) {
$actions = flag_actions_get_actions();
return $actions[$aid];
}
function flag_actions_get_actions($flag_name = NULL, $reset = FALSE) {
$flag_actions = &drupal_static(__FUNCTION__);
module_load_include('inc', 'flag', 'includes/flag.actions');
// Get a list of all possible actions defined by modules.
$actions = module_invoke_all('action_info');
// Retrieve the list of user-defined flag actions.
if (!isset($flag_actions) || $reset) {
$flag_actions = array();
$query = db_select('flag_actions', 'a');
$query->innerJoin('flag', 'f', 'a.fid = f.fid');
$query->addField('f', 'name', 'flag');
$result = $query
->fields('a')
->execute();
foreach ($result as $action) {
if (!isset($actions[$action->callback])) {
$actions[$action->callback] = array(
'description' => t('Missing action "@action-callback". Module providing it was either uninstalled or disabled.', array('@action-callback' => $action->callback)),
'configurable' => FALSE,
'type' => 'node',
'missing' => TRUE,
);
}
$action->parameters = unserialize($action->parameters);
$action->label = $actions[$action->callback]['label'];
$action->configurable = $actions[$action->callback]['configurable'];
$action->behavior = isset($actions[$action->callback]['behavior']) ? $actions[$action->callback]['behavior'] : array();
$action->type = $actions[$action->callback]['type'];
$action->missing = !empty($actions[$action->callback]['missing']);
$flag_actions[$action->aid] = $action;
}
}
// Filter actions to a specified flag.
if (isset($flag_name)) {
$specific_flag_actions = array();
foreach ($flag_actions as $aid => $action) {
if ($action->flag == $flag_name) {
$specific_flag_actions[$aid] = $action;
}
}
return $specific_flag_actions;
}
return $flag_actions;
}
/**
* Insert a new flag action.
*
* @param $fid
* The flag object ID.
* @param $event
* The flag event, such as "flag" or "unflag".
* @param $threshold
* The flagging threshold at which this action will be executed.
* @param $repeat_threshold
* The number of additional flaggings after which the action will be repeated.
* @param $callback
* The action callback to be executed.
* @param $parameters
* The action parameters.
*/
function flag_actions_insert_action($fid, $event, $threshold, $repeat_threshold, $callback, $parameters) {
return db_insert('flag_actions')
->fields(array(
'fid' => $fid,
'event' => $event,
'threshold' => $threshold,
'repeat_threshold' => $repeat_threshold,
'callback' => $callback,
'parameters' => serialize($parameters),
))
->execute();
}
/**
* Update an existing flag action.
*
* @param $aid
* The flag action ID to update.
* @param $event
* The flag event, such as "flag" or "unflag".
* @param $threshold
* The flagging threshold at which this action will be executed.
* @param $repeat_threshold
* The number of additional flaggings after which the action will be repeated.
* @param $parameters
* The action parameters.
*/
function flag_actions_update_action($aid, $event, $threshold, $repeat_threshold, $parameters) {
return db_update('flag_actions')
->fields(array(
'event' => $event,
'threshold' => $threshold,
'repeat_threshold' => $repeat_threshold,
'parameters' => serialize($parameters),
))
->condition('aid', $aid)
->execute();
}
/**
* Delete a flag action.
*
* @param $aid
* The flag action ID to delete.
*/
function flag_actions_delete_action($aid) {
return db_delete('flag_actions', array('return' => Database::RETURN_AFFECTED))
->condition('aid', $aid)
->execute();
}
/**
* Perform flag actions.
*/
function flag_actions_do($event, $flag, $entity_id, $account) {
$actions = flag_actions_get_actions($flag->name);
if (!$actions) {
return;
}
$flag_action = $flag->get_flag_action($entity_id);
$flag_action->action = $event;
$flag_action->count = $count = $flag->get_count($entity_id);
$relevant_objects = $flag->get_relevant_action_objects($entity_id);
$object_changed = FALSE;
foreach ($actions as $aid => $action) {
if ($action->event == 'flag') {
$at_threshold = ($count == $action->threshold);
$repeat = $action->repeat_threshold ? (($count > $action->threshold) && (($count - $action->threshold) % $action->repeat_threshold == 0)) : FALSE;
}
elseif ($action->event == 'unflag') {
$at_threshold = ($count == $action->threshold - 1);
$repeat = $action->repeat_threshold ? (($count < $action->threshold - 1) && (($count - $action->threshold - 1) % $action->repeat_threshold == 0)) : FALSE;
}
if (($at_threshold || $repeat) && $action->event == $event && !$action->missing) {
$context = $action->parameters;
$context['callback'] = $action->callback;
// We're setting 'hook' to something, to prevent PHP warnings by actions
// who read it. Maybe we should set it to nodeapi/comment/user, depending
// on the flag, because these three are among the only hooks some actions
// in system.module "know" to work with.
$context['hook'] = 'flag';
$context['type'] = $action->type;
$context['account'] = $account;
$context['flag'] = $flag;
$context['flag-action'] = $flag_action;
// We add to the $context all the objects we know about:
$context = array_merge($relevant_objects, $context);
$callback = $action->callback;
if (isset($relevant_objects[$action->type])) {
$callback($relevant_objects[$action->type], $context);
}
else {
// What object shall we send as last resort? Let's send a node, or
// the flag's object.
if (isset($relevant_objects['node'])) {
$callback($relevant_objects['node'], $context);
}
else {
$callback($relevant_objects[$flag->entity_type], $context);
}
}
if (is_array($action->behavior) && in_array('changes_property', $action->behavior)) {
$object_changed = TRUE;
}
}
}
// Actions by default do not save elements unless the save action is
// explicitly added. We run it automatically upon flagging.
if ($object_changed) {
$save_action = $action->type . '_save_action';
if (function_exists($save_action)) {
$save_action($relevant_objects[$action->type]);
}
}
}
/**
* Menu callback for FLAG_ADMIN_PATH/actions.
*/
function flag_actions_page() {
$actions = flag_actions_get_actions();
$add_action_form = drupal_get_form('flag_actions_add_form');
return theme('flag_actions_page', array('actions' => $actions, 'form' => $add_action_form));
}
/**
* Theme the list of actions currently in place for flags.
*/
function theme_flag_actions_page($variables) {
$actions = $variables['actions'];
$add_action_form = $variables['form'];
$rows = array();
foreach ($actions as $action) {
$flag = flag_get_flag($action->flag);
// Build a sample string representing repeating actions.
if ($action->repeat_threshold) {
$repeat_count = 3;
$repeat_subtract = ($action->event == 'flag') ? 1 : -1;
$repeat_samples = array();
for ($n = 1; $n < $repeat_count + 2; $n++) {
$sample = $action->threshold + (($n * $action->repeat_threshold) * $repeat_subtract);
if ($sample > 0) {
$repeat_samples[] = $sample;
}
}
if (count($repeat_samples) > $repeat_count) {
$repeat_samples[$repeat_count] = '&hellip;';
}
$repeat_string = implode(', ', $repeat_samples);
}
else {
$repeat_string = '-';
}
$row = array();
$row[] = $flag->get_title();
$row[] = ($action->event == 'flag' ? '&ge; ' : '&lt; ') . $action->threshold;
$row[] = $repeat_string;
$row[] = empty($action->missing) ? $action->label : '<div class="error">' . $action->label . '</div>';
$row[] = l(t('edit'), FLAG_ADMIN_PATH . '/actions/configure/' . $action->aid);
$row[] = l(t('delete'), FLAG_ADMIN_PATH . '/actions/delete/' . $action->aid);
$rows[] = $row;
}
if (empty($rows)) {
$rows[] = array(array('data' => t('Currently no flag actions. Use the <em>Add new flag action</em> form to add an action.'), 'colspan' => 6));
}
$header = array(
t('Flag'),
t('Threshold'),
t('Repeats'),
t('Action'),
array('data' => t('Operations'), 'colspan' => 2),
);
$output = '';
$output .= theme('table', array('header' => $header, 'rows' => $rows));
$output .= drupal_render($add_action_form);
return $output;
}
/**
* Modified version of the Add action form that redirects back to the flag list.
*/
function flag_actions_add_form($form, &$form_state) {
$flags = flag_get_flags();
$options = array();
foreach ($flags as $flag) {
$options[$flag->name] = $flag->get_title();
}
if (empty($options)) {
$options[] = t('No flag available');
}
$form['flag'] = array(
'#type' => 'select',
'#options' => empty($options) ? array(t('No flag available')) : $options,
'#disabled' => empty($options),
'#title' => t('Select a flag'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Add action'),
);
return $form;
}
function flag_actions_add_form_submit($form, &$form_state) {
if ($form_state['values']['flag']) {
$form_state['redirect'] = array(FLAG_ADMIN_PATH . '/actions/add/' . $form_state['values']['flag']);
}
}
function theme_flag_actions_add_form($variables) {
$form = $variables['form'];
$fieldset = array(
'#type' => 'fieldset',
'#title' => t('Add a new flag action'),
'#children' => '<div class="container-inline">'. drupal_render($form['flag']) . drupal_render($form['submit']) .'</div>',
'#parents' => array('add_action'),
'#attributes' => array(),
'#groups' => array('add_action' => array()),
);
return drupal_render($fieldset) . drupal_render_children($form);
}
/**
* Generic configuration form for configuration of flag actions.
*
* @param $form_state
* The form state.
* @param $aid
* If editing an action, an action ID must be passed in.
* @param $flag_name
* If adding a new action to a flag, a flag name must be specified.
*
*/
function flag_actions_form($form, &$form_state, $aid = NULL, $flag_name = NULL) {
// This is a multistep form. Get the callback value if set and continue.
if (isset($form_state['storage']['callback'])) {
$callback = $form_state['storage']['callback'];
unset($form_state['storage']['callback']);
}
if (isset($aid)) {
$action = flag_actions_get_action($aid);
$callback = $action->callback;
$flag = flag_get_flag($action->flag);
drupal_set_title(t('Edit the "@action" action for the @title flag', array('@action' => $action->label, '@title' => $flag->get_title())));
}
elseif (isset($flag_name)) {
$flag = flag_get_flag($flag_name);
}
if (empty($flag)) {
drupal_not_found();
}
$form['new'] = array(
'#type' => 'value',
'#value' => isset($callback) ? FALSE: TRUE,
);
if (!isset($callback)) {
drupal_set_title(t('Add an action to the @title flag', array('@title' => $flag->get_title())));
$actions = $flag->get_valid_actions();
$options = array();
foreach($actions as $key => $action) {
$options[$key] = $action['label'];
}
$form['callback'] = array(
'#title' => t('Select an action'),
'#type' => 'select',
'#options' => $options,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Continue'),
);
return $form;
}
elseif (!isset($action)) {
$actions = $flag->get_valid_actions();
$action = (object)$actions[$callback];
$action->parameters = array();
$action->event = 'flag';
$action->threshold = 10;
$action->repeat_threshold = 0;
drupal_set_title(t('Add "@action" action to the @title flag', array('@action' => $action->label, '@title' => $flag->get_title())));
}
$form['flag'] = array(
'#tree' => TRUE,
'#weight' => -9,
'#theme' => 'flag_actions_flag_form',
'#action' => $action,
'#flag' => $flag,
);
$form['flag']['flag'] = array(
'#type' => 'value',
'#value' => $flag,
);
$form['flag']['callback'] = array(
'#type' => 'value',
'#value' => $callback,
);
$form['flag']['aid'] = array(
'#type' => 'value',
'#value' => $aid,
);
$form['flag']['event'] = array(
'#type' => 'select',
'#options' => array(
'flag' => t('reaches'),
'unflag' => t('falls below'),
),
'#default_value' => $action->event,
);
$form['flag']['threshold'] = array(
'#type' => 'textfield',
'#size' => 6,
'#maxlength' => 6,
'#default_value' => $action->threshold,
'#required' => TRUE,
);
$form['flag']['repeat_threshold'] = array(
'#type' => 'textfield',
'#size' => 6,
'#maxlength' => 6,
'#default_value' => $action->repeat_threshold,
);
if ($flag->global) {
$form['flag']['threshold']['#disabled'] = 1;
$form['flag']['threshold']['#value'] = 1;
$form['flag']['repeat_threshold']['#access'] = FALSE;
$form['flag']['repeat_threshold']['#value'] = 0;
}
// Merge in the standard flag action form.
$action_form = $callback .'_form';
$edit = array();
if (function_exists($action_form)) {
$edit += $action->parameters;
$edit['actions_label'] = $action->label;
$edit['actions_type'] = $action->type;
$edit['actions_flag'] = $flag->name;
$additions = flag_actions_form_additions($action_form, $edit);
$form = array_merge($form, $additions);
}
// Add a few customizations to existing flag actions.
$flag_actions_form = 'flag_actions_'. $callback .'_form';
if (function_exists($flag_actions_form)) {
$flag_actions_form($form, $flag, $edit);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Execute an action form callback to retrieve form additions.
*
* This function prevents the form callback from modifying local variables.
*/
function flag_actions_form_additions($callback, $edit) {
return $callback($edit);
}
/**
* Generic submit handler for validating flag actions.
*/
function flag_actions_form_validate($form, &$form_state) {
// Special validation handlers may be needed to save this form properly.
// Try to load the action's validation routine if needed.
if (isset($form_state['values']['flag']['callback'])) {
$callback = $form_state['values']['flag']['callback'];
$validate_function = $callback . '_validate';
if (function_exists($validate_function)) {
$validate_function($form, $form_state);
}
}
}
/**
* Generic submit handler for saving flag actions.
*/
function flag_actions_form_submit($form, &$form_state) {
// If simply gathering the callback, save it to form state storage and
// rebuild the form to gather the complete information.
if ($form_state['values']['new']) {
$form_state['storage']['callback'] = $form_state['values']['callback'];
$form_state['rebuild'] = TRUE;
return;
}
$aid = $form_state['values']['flag']['aid'];
$flag = $form_state['values']['flag']['flag'];
$event = $form_state['values']['flag']['event'];
$threshold = $form_state['values']['flag']['threshold'];
$repeat_threshold = $form_state['values']['flag']['repeat_threshold'];
$callback = $form_state['values']['flag']['callback'];
// Specialized forms may need to execute their own submit handlers on save.
$submit_function = $callback . '_submit';
$parameters = function_exists($submit_function) ? $submit_function($form, $form_state) : array();
if (empty($aid)) {
$aid = flag_actions_insert_action($flag->fid, $event, $threshold, $repeat_threshold, $callback, $parameters);
$form_state['values']['flag']['aid'] = $aid;
$form_state['values']['flag']['is_new'] = TRUE;
}
else {
flag_actions_update_action($aid, $event, $threshold, $repeat_threshold, $parameters);
}
$action = flag_actions_get_action($aid);
drupal_set_message(t('The "@action" action for the @title flag has been saved.', array('@action' => $action->label, '@title' => $flag->get_title())));
$form_state['redirect'] = FLAG_ADMIN_PATH . '/actions';
}
function theme_flag_actions_flag_form($variables) {
$form = $variables['form'];
$event = drupal_render($form['event']);
$threshold = drupal_render($form['threshold']);
$repeat_threshold = drupal_render($form['repeat_threshold']);
$action = $form['#action']->label;
$output = '';
$output .= '<div class="container-inline">';
$output .= t('Perform action when content !event !threshold flags', array('!event' => $event, '!threshold' => $threshold));
if ($form['#flag']->global) {
$output .= ' ' . t('(global flags always have a threshold of 1)');
}
$output .= '</div>';
$output .= '<div class="container-inline">';
if (!$form['#flag']->global) {
$output .= t('Repeat this action every !repeat_threshold additional flags after the threshold is reached', array('!repeat_threshold' => $repeat_threshold));
}
$output .= '</div>';
$element = array(
'#title' => t('Flagging threshold'),
'#required' => TRUE,
);
return $output . drupal_render_children($form);
}
function flag_actions_delete_form($form, &$form_state, $aid) {
$action = flag_actions_get_action($aid);
$flag = flag_get_flag($action->flag);
$form['action'] = array(
'#type' => 'value',
'#value' => $action,
);
$form['flag'] = array(
'#type' => 'value',
'#value' => $flag,
);
$question = t('Delete the "@action" action for the @title flag?', array('@action' => $action->label, '@title' => $flag->get_title()));
$path = FLAG_ADMIN_PATH . '/actions';
return confirm_form($form, $question, $path, NULL, t('Delete'));
}
function flag_actions_delete_form_submit(&$form, &$form_state) {
flag_actions_delete_action($form_state['values']['action']->aid);
drupal_set_message(t('The "@action" action for the @title flag has been deleted.', array('@action' => $form_state['values']['action']->label, '@title' => $form_state['values']['flag']->get_title())));
$form_state['redirect'] = FLAG_ADMIN_PATH . '/actions';
}
/**
* Make modifications to the "Send e-mail" action form.
*/
function flag_actions_system_send_email_action_form(&$form, &$flag, $context) {
if (!isset($context['recipient'])) {
$form['recipient']['#default_value'] = '[site:mail]';
}
if (!isset($context['subject'])) {
$form['subject']['#default_value'] = t('Content Flagged @flag_title', array('@flag_title' => $flag->get_title()));
}
if (!isset($context['message'])) {
$form['message']['#default_value'] = t("The @flag_entity_type [flag-action:content-title] has been flagged [flag-action:count] times with the @flag_title flag.\n\nView this @flag_entity_type at [flag-action:content-url].", array('@flag_entity_type' => $flag->entity_type, '@flag_title' => $flag->get_title()));
}
$form['help'] = array(
'#type' => 'fieldset',
'#title' => t('Tokens'),
'#description' => t('The following tokens can be used in the recipient, subject, or message.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['help']['basic'] = array(
'#markup' => theme('flag_tokens_browser', array('types' => array('flag', 'flag-action'))),
);
$form['help']['tokens'] = array(
'#type' => 'fieldset',
'#title' => t('More tokens'),
'#description' => t("Depending on the type of the content being flagged, the following tokens can be used in the recipients, subject, or message. For example, if the content being flagged is a node, you can use any of the node tokens --but you can't use the comment tokens: they won't be recognized. Similarly, if the content being flagged is a user, you can use only the user tokens."),
'#value' => theme('flag_tokens_browser', array('types' => $flag->get_labels_token_types(), 'global_types' => FALSE)),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
}

View File

@@ -0,0 +1,12 @@
name = Flag Bookmark
description = Provides an example bookmark flag and supporting views.
core = 7.x
dependencies[] = flag
package = Flags
; Information added by drupal.org packaging script on 2013-09-13
version = "7.x-3.2"
core = "7.x"
project = "flag"
datestamp = "1379063829"

View File

@@ -0,0 +1,67 @@
<?php
/**
* @file
* The Flag Bookmark module install hooks.
*/
/**
* Implements hook_enable().
*
* We create the demonstration flag on enable, so hook implementations in flag
* module will fire correctly, as the APIs are not available on install.
*/
function flag_bookmark_enable() {
// Load the flag API in case we want to use it when enabling.
include_once(drupal_get_path('module', 'flag') . '/flag.module');
if (!flag_get_flags()) {
// Install a demonstration flag only if no flag exists. This is to prevent
// a case where a disables and enables the module, and the demonstration
// flag is overwritten or re-created.
$flag = flag_flag::factory_by_entity_type('node');
$configuration = array(
'name' => 'bookmarks',
'global' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'show_on_form' => 1,
// The following UI labels aren't wrapped in t() because they are written
// to the DB in English. They are passed to t() later, thus allowing for
// multilingual sites.
'title' => 'Bookmarks',
'flag_short' => 'Bookmark this',
'flag_long' => 'Add this post to your bookmarks',
'flag_message' => 'This post has been added to your bookmarks',
'unflag_short' => 'Unbookmark this',
'unflag_long' => 'Remove this post from your bookmarks',
'unflag_message' => 'This post has been removed from your bookmarks',
'types' => _flag_bookmark_install_get_suggested_node_types(),
);
$flag->form_input($configuration);
$flag->save();
// Clear the flag cache so the new permission is seen by core.
drupal_static_reset('flag_get_flags');
// Grant permissions.
$permissions = array('flag bookmarks', 'unflag bookmarks');
user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, $permissions);
}
}
/**
* Returns some node types to which the demonstration 'bookmarks' flag will apply.
*/
function _flag_bookmark_install_get_suggested_node_types() {
$preferred = array('article', 'story', 'forum', 'blog');
$existing = array_intersect($preferred, array_keys(node_type_get_types()));
if (!$existing) {
// As a last resort, take the first preference.
return array($preferred[0]);
}
return $existing;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @file
* The Flag bookmark module.
*
* This module creates a default Flag and associated views when enabled. The
* content is intended to be an example and may be deleted afterward.
*/
/**
* Implements hook_views_api().
*/
function flag_bookmark_views_api() {
return array(
'api' => 2.0,
'path' => drupal_get_path('module', 'flag_bookmark') . '/includes',
);
}

View File

@@ -0,0 +1,284 @@
<?php
/**
* @file
* Provides support for the Views module.
*/
/**
* Implements hook_views_default_views().
*/
function flag_bookmark_views_default_views() {
// Only setup views for the "bookmarks" flag.
// If it's been deleted, don't create any views.
$flag = flag_get_flag('bookmarks');
if (!$flag) {
return array();
}
// Set up properties that are used for both views.
$fields = array(
'type' => array(
'id' => 'type',
'table' => 'node',
'field' => 'type',
'label' => 'Type',
),
'title' => array(
'id' => 'title',
'table' => 'node',
'field' => 'title',
'label' => 'Title',
'link_to_node' => 1,
),
'name' => array(
'label' => 'Author',
'link_to_user' => 1,
'id' => 'name',
'table' => 'users',
'field' => 'name',
'relationship' => 'uid_1',
),
);
$style_options = array(
'grouping' => '',
'override' => 0,
'sticky' => 1,
'columns' => array(),
'default' => 'title',
'order' => 'asc',
'columns' => array(
'type' => 'type',
'title' => 'title',
'name' => 'name',
),
'info' => array(
'type' => array(
'sortable' => TRUE,
),
'title' => array(
'sortable' => TRUE,
),
'name' => array(
'sortable' => TRUE,
),
),
'override' => FALSE,
'order' => 'asc',
);
$filters = array(
'status' => array(
'operator' => '=',
'value' => 1,
'group' => '0',
'exposed' => FALSE,
'expose' => array(
'operator' => FALSE,
'label' => '',
),
'id' => 'status',
'table' => 'node',
'field' => 'status',
'relationship' => 'none',
),
);
$relationships = array(
'flag_content_rel' => array(
'label' => 'bookmarks',
'required' => 1,
'flag' => 'bookmarks',
'user_scope' => 'current',
'id' => 'flag_content_rel',
'table' => 'node',
'field' => 'flag_content_rel',
'relationship' => 'none',
'override' => array(
'button' => 'Override',
),
),
'uid_1' => array(
'label' => 'author',
'required' => 0,
'id' => 'uid_1',
'table' => 'node',
'field' => 'uid',
),
);
$access = array(
'type' => 'perm',
'perm' => 'flag bookmarks',
);
// Additional fields and style options if comment exists.
if (module_exists('comment')) {
$fields += array(
'comment_count' => array(
'id' => 'comment_count',
'table' => 'node_comment_statistics',
'field' => 'comment_count',
'label' => 'Replies',
),
'last_comment_timestamp' => array(
'id' => 'last_comment_timestamp',
'table' => 'node_comment_statistics',
'field' => 'last_comment_timestamp',
'label' => 'Last Post',
),
);
$style_options['default'] = 'last_comment_timestamp';
$style_options['order'] = 'desc';
$style_options['info'] += array(
'comment_count' => array(
'sortable' => TRUE,
),
'last_comment_timestamp' => array(
'sortable' => TRUE,
),
);
$style_options['columns'] += array(
'comment_count' => 'comment_count',
'last_comment_timestamp' => 'last_comment_timestamp',
);
}
/* Individual users user/%/bookmarks tab. */
// Additional relationship for this view.
$relationships_tab = $relationships;
$relationships_tab['flag_content_rel']['user_scope'] = 'any';
$relationships_tab += array(
'uid' => array(
'label' => 'bookmarks_user',
'required' => 1,
'id' => 'uid',
'table' => 'flagging',
'field' => 'uid',
'relationship' => 'flag_content_rel',
),
);
// Additional argument for this view.
$arguments_tab = array(
'uid' => array(
'default_action' => 'empty',
'style_plugin' => 'default_summary',
'style_options' => array(),
'wildcard' => 'all',
'wildcard_substitution' => 'All',
'title' => '%1\'s bookmarks',
'default_argument_type' => 'fixed',
'default_argument' => '',
'validate_type' => 'none',
'validate_fail' => 'not found',
'break_phrase' => 0,
'not' => 0,
'id' => 'uid',
'table' => 'users',
'field' => 'uid',
'override' => array(
'button' => 'Override',
),
'relationship' => 'uid',
'default_options_div_prefix' => '',
'default_argument_user' => 0,
'default_argument_fixed' => '',
'default_argument_php' => '',
),
);
$view = new view;
$view->name = 'flag_bookmarks_tab';
$view->description = "Provides a tab on all users' profile pages containing bookmarks for that user.";
$view->tag = 'flag';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE;
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('relationships', $relationships_tab);
$handler->override_option('fields', $fields);
$handler->override_option('arguments', $arguments_tab);
$handler->override_option('filters', $filters);
$handler->override_option('access', $access);
$handler->override_option('title', 'User bookmarks');
$handler->override_option('empty', 'This user has not yet bookmarked any content.');
$handler->override_option('empty_format', filter_fallback_format());
$handler->override_option('items_per_page', '25');
$handler->override_option('use_pager', TRUE);
$handler->override_option('style_plugin', 'table');
$handler->override_option('style_options', $style_options);
$handler = $view->new_display('page', 'Page', 'page');
$handler->override_option('path', 'user/%/bookmarks');
$handler->override_option('menu', array(
'type' => 'tab',
'title' => 'Bookmarks',
'weight' => '0',
'name' => 'navigation',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => NULL,
'weight' => NULL,
));
$views[$view->name] = $view;
/* User bookmarks page with Ops. */
// Add some unique options for this view.
$style_options['columns'] += array('ops' => 'ops');
$fields += array(
'ops' => array(
'label' => 'Ops',
'id' => 'ops',
'table' => 'flagging',
'field' => 'ops',
'relationship' => 'flag_content_rel',
),
);
$view = new view;
$view->name = 'flag_' . $flag->name;
$view->description = "A page listing the current user's bookmarks at /bookmarks.";
$view->tag = 'flag';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = '0';
$view->api_version = 2;
$view->disabled = FALSE;
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('relationships', $relationships);
$handler->override_option('fields', $fields);
$handler->override_option('filters', $filters);
$handler->override_option('access', $access);
$handler->override_option('title', t('My bookmarks'));
$handler->override_option('items_per_page', '25');
$handler->override_option('use_pager', TRUE);
$handler->override_option('empty', 'You have not yet bookmarked any content. Click the "' . $flag->flag_short . '" link when viewing a piece of content to add it to this list.');
$handler->override_option('empty_format', filter_fallback_format());
$handler->override_option('style_plugin', 'table');
$handler->override_option('style_options', $style_options);
$handler = $view->new_display('page', 'Page', 'page');
$handler->override_option('path', 'bookmarks');
$handler->override_option('menu', array(
'type' => 'normal',
'title' => t('My bookmarks'),
'weight' => '0',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => NULL,
'weight' => NULL,
));
$views[$view->name] = $view;
return $views;
}

View File

@@ -0,0 +1,243 @@
<?php
/**
* @file
* Hooks for flag actions.
*/
/**
* Implements hook_trigger_info().
*/
function flag_trigger_info() {
$hooks = array(
'flag' => array(
'flag_flag' => array(
'label' => t('Object has been flagged with any flag'),
),
'flag_unflag' => array(
'label' => t('Object has been unflagged with any flag')
),
),
);
foreach (flag_get_flags() as $flag) {
$hooks['flag']['flag_flag_' . $flag->name]['label'] = t('A %type has been flagged with %name', array('%type' => $flag->entity_type, '%name' => $flag->name));
$hooks['flag']['flag_unflag_' . $flag->name]['label'] = t('A %type has been unflagged with %name', array('%type' => $flag->entity_type, '%name' => $flag->name));
}
return $hooks;
}
/**
* Implements hook_action_info().
*/
function flag_action_info() {
return array(
'flag_node_action' => array(
'type' => 'node',
'label' => t('Flag (or unflag) a node'),
'configurable' => TRUE,
'triggers' => array(
'node_presave', 'node_insert', 'node_update', 'node_delete', 'node_view',
'comment_insert', 'comment_update', 'comment_delete', 'comment_view',
),
),
'flag_comment_action' => array(
'type' => 'comment',
'label' => t('Flag (or unflag) a comment'),
'configurable' => TRUE,
'triggers' => array(
'comment_insert', 'comment_update', 'comment_delete', 'comment_view',
),
),
'flag_user_action' => array(
'type' => 'user',
'label' => t('Flag (or unflag) a user'),
'configurable' => TRUE,
'triggers' => array(
'user_insert', 'user_update', 'user_delete', 'user_login', 'user_logout', 'user_view',
),
),
);
}
/**
* Implements hook_action_info_alter().
*
* Enable Flag actions on Node, Comment, and User hooks without
* the trigger_unlock.module.
*/
function flag_action_info_alter(&$actions) {
$node_flags = flag_get_flags('node');
$comment_flags = flag_get_flags('comment');
$user_flags = flag_get_flags('user');
foreach ($actions as $name => $action) {
if (strpos($name, 'node') === 0) {
$actions[$name]['triggers'][] = 'flag_flag';
$actions[$name]['triggers'][] = 'flag_unflag';
foreach ($node_flags as $flag) {
$actions[$name]['triggers'][] = 'flag_flag_' . $flag->name;
$actions[$name]['triggers'][] = 'flag_unflag_' . $flag->name;
}
}
if (strpos($name, 'comment') === 0) {
$actions[$name]['triggers'][] = 'flag_flag';
$actions[$name]['triggers'][] = 'flag_unflag';
foreach ($comment_flags as $flag) {
$actions[$name]['triggers'][] = 'flag_flag_' . $flag->name;
$actions[$name]['triggers'][] = 'flag_unflag_' . $flag->name;
}
}
if (strpos($name, 'user') === 0) {
$actions[$name]['triggers'][] = 'flag_flag';
$actions[$name]['triggers'][] = 'flag_unflag';
foreach ($user_flags as $flag) {
$actions[$name]['triggers'][] = 'flag_flag_' . $flag->name;
$actions[$name]['triggers'][] = 'flag_unflag_' . $flag->name;
}
}
}
}
/**
* Implements Drupal action. Flags a node.
*
* Note the first parameter is "object" because it may be a comment or a node.
*/
function flag_node_action(&$object, $context = array()) {
if ($flag = flag_get_flag($context['flag_action']['flag'])) {
$account = isset($context['account']) ? $context['account'] : $GLOBALS['user'];
$flag->flag($context['flag_action']['op'], $object->nid, $account, TRUE);
}
}
/**
* Form for configuring the Flag node action.
*/
function flag_node_action_form($context = array()) {
return flag_action_form($context, 'node');
}
/**
* Submit function for the Flag node action form.
*/
function flag_node_action_submit($form, $form_state) {
return flag_action_submit($form, $form_state);
}
/**
* Implements Drupal action. Flags a comment.
*/
function flag_comment_action(&$comment, $context = array()) {
if ($flag = flag_get_flag($context['flag_action']['flag'])) {
$account = isset($context['account']) ? $context['account'] : $GLOBALS['user'];
$flag->flag($context['flag_action']['op'], $comment->cid, $account, TRUE);
}
}
/**
* Form for configuring the Flag comment action.
*/
function flag_comment_action_form($context) {
return flag_action_form($context, 'comment');
}
/**
* Submit function for the Flag comment action form.
*/
function flag_comment_action_submit($form, $form_state) {
return flag_action_submit($form, $form_state);
}
/**
* Implements Drupal action. Flags a user.
*/
function flag_user_action(&$user, $context = array()) {
if ($flag = flag_get_flag($context['flag_action']['flag'])) {
$account = isset($context['account']) ? $context['account'] : $GLOBALS['user'];
$flag->flag($context['flag_action']['op'], $user->uid, $account, TRUE);
}
}
/**
* Form for configuring the Flag user action.
*/
function flag_user_action_form($context) {
return flag_action_form($context, 'user');
}
/**
* Submit function for the Flag user action form.
*/
function flag_user_action_submit($form, $form_state) {
return flag_action_submit($form, $form_state);
}
/**
* Generic form for configuring Flag actions.
*
* @param $context
* The current action context.
* @param $entity_type
* The entity type applicable to this action, such as "node" or "comment".
*/
function flag_action_form($context, $entity_type) {
$form = array();
$flags = flag_get_flags($entity_type);
// If this is a flag_action action, do not allow the triggering flag.
if (isset($context['actions_flag'])) {
unset($flags[$context['actions_flag']]);
}
$options = drupal_map_assoc(array_keys($flags));
$form['flag_action']['#tree'] = TRUE;
$form['flag_action']['warning'] = array(
'#markup' => '<div class="messages status">' . t("Note when setting a flag through actions, the selected flag will be flagged regardless of the user's permissions.") . '</div>',
);
$form['flag_action']['flag'] = array(
'#title' => t('Flag to affect'),
'#type' => 'radios',
'#options' => $options,
'#required' => TRUE,
'#description' => t('When this action is fired, which flag should be flagged (or unflagged)?'),
'#default_value' => isset($context['flag_action']['flag']) ? $context['flag_action']['flag'] : reset($options),
);
$form['flag_action']['op'] = array(
'#title' => t('Flag operation'),
'#type' => 'radios',
'#options' => array('flag' => t('Flag'), 'unflag' => t('Unflag')),
'#description' => t('When this action is fired, which operation should be performed on the flag?'),
'#default_value' => isset($context['flag_action']['op']) ? $context['flag_action']['op'] : 'flag',
);
if (empty($options)) {
$error = t('There are no available %type flags. Before you can create an action of this type, you need to <a href="!url">create a %type flag</a>.', array('%type' => $entity_type, '!url' => url(FLAG_ADMIN_PATH . '/add')));
$form['flag_action']['flag']['#type'] = 'item';
$form['flag_action']['flag']['#markup'] = $error;
$form['flag_action']['flag']['#element_validate'][] = 'flag_action_validate_flag';
$form['flag_action']['flag']['#flag_error'] = $error;
}
return $form;
}
/**
* Generic validation handler for validating Flag action configuration.
*/
function flag_action_validate_flag($element) {
if (isset($element['#flag_error'])) {
form_error($element, $element['#flag_error']);
}
}
/**
* Generic submission handler for saving Flag action configuration.
*/
function flag_action_submit($form, $form_state) {
return array(
'flag_action' => $form_state['values']['flag_action'],
);
}

View File

@@ -0,0 +1,817 @@
<?php
/**
* @file
* Contains administrative pages for creating, editing, and deleting flags.
*/
/**
* Flag administration page. Display a list of existing flags.
*/
function flag_admin_page() {
$flags = flag_get_flags();
$default_flags = flag_get_default_flags(TRUE);
$flag_admin_listing = drupal_get_form('flag_admin_listing', $flags);
return theme('flag_admin_page', array(
'flags' => $flags,
'default_flags' => $default_flags,
'flag_admin_listing' => $flag_admin_listing,
));
}
/**
* A form for ordering the weights of all the active flags in the system.
*/
function flag_admin_listing($form, &$form_state, $flags) {
$form['#flags'] = $flags;
$form['#tree'] = TRUE;
foreach ($flags as $flag) {
$form['flags'][$flag->name]['weight'] = array(
'#type' => 'weight',
'#delta' => count($flags) + 5,
'#default_value' => $flag->weight,
'#attributes' => array('class' => array('flag-weight')),
);
}
$form['actions'] = array(
'#type' => 'actions',
);
if (count($flags) == 1) {
// Don't show weights with only one flag.
unset($form['flags'][$flag->name]['weight']);
}
elseif (count($flags) > 1) {
// Only show the form button if there are several flags.
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save flag order'),
);
}
return $form;
}
/**
* Submit handler for the flag_admin_listing form. Save flag weight ordering.
*/
function flag_admin_listing_submit($form, &$form_state) {
foreach ($form['#flags'] as $flag) {
if ($flag->weight != $form_state['values']['flags'][$flag->name]['weight']) {
$flag->weight = $form_state['values']['flags'][$flag->name]['weight'];
$flag->save();
}
}
}
/**
* Theme the output of the normal, database flags into a table.
*/
function theme_flag_admin_listing($variables) {
$form = $variables['form'];
$flags = $form['#flags'];
$output = '';
foreach ($flags as $flag) {
$ops = array(
'flags_edit' => array('title' => t('edit'), 'href' => $flag->admin_path('edit')),
'flags_fields' => array('title' => t('manage fields'), 'href' => $flag->admin_path('fields')),
'flags_delete' => array('title' => t('delete'), 'href' => $flag->admin_path('delete')),
'flags_export' => array('title' => t('export'), 'href' => $flag->admin_path('export')),
);
if (!module_exists('field_ui')) {
unset($ops['flags_fields']);
}
$permission = "flag $flag->name";
$roles = user_roles(FALSE, $permission);
$row = array();
$row[] = check_plain($flag->title) . ' <small>(' . t('Machine name: @name', array('@name' => $flag->name)) . ')</small>';
if (count($flags) > 1) {
$row[] = drupal_render($form['flags'][$flag->name]['weight']);
}
$row[] = $flag->entity_type;
$row[] = empty($roles) ? '<em>' . t('No roles') . '</em>' : implode(', ', $roles);
$row[] = $flag->types ? implode(', ', $flag->types) : '-';
$row[] = $flag->global ? t('Yes') : t('No');
$row[] = theme('links', array('links' => $ops));
$rows[] = array(
'data' => $row,
'class' => array('draggable'),
);
}
if (!$flags) {
$rows[] = array(
array('data' => t('No flags are currently defined.'), 'colspan' => 7),
);
}
elseif (count($flags) > 1) {
drupal_add_tabledrag('flag-admin-listing-table', 'order', 'sibling', 'flag-weight');
}
$header = array(t('Flag'));
if (count($flags) > 1) {
$header[] = t('Weight');
}
$header = array_merge($header, array(t('Flag type'), t('Roles'), t('Entity bundles'), t('Global?'), t('Operations')));
$output .= theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => 'flag-admin-listing-table'),
));
$output .= drupal_render_children($form);
return $output;
}
/**
* Theme the list of disabled flags into a table.
*/
function theme_flag_admin_listing_disabled($variables) {
$flags = $variables['flags'];
$default_flags = $variables['default_flags'];
$output = '';
// Build a list of disabled, module-based flags.
$rows = array();
foreach ($default_flags as $name => $flag) {
if (!isset($flags[$name])) {
$ops = array();
if (!$flag->is_compatible()) {
$flag_updates_needed = TRUE;
$ops['flags_update'] = array('title' => '<strong>' . t('update code') . '</strong>', 'href' => $flag->admin_path('update'), 'html' => TRUE);
}
else {
$ops['flags_enable'] = array('title' => t('enable'), 'href' => $flag->admin_path('edit'));
}
// $flag->roles['flag'] not exist on older flags.
$roles = array_flip(array_intersect(array_flip(user_roles()), !empty($flag->roles['flag']) ? $flag->roles['flag'] : array()));
$rows[] = array(
$flag->name,
$flag->module,
$flag->entity_type ? $flag->entity_type : t('Unknown'),
theme('links', array('links' => $ops)),
);
}
}
if (isset($flag_updates_needed)) {
drupal_set_message(t('Some flags provided by modules need to be updated to a new format before they can be used with this version of Flag. See the disabled flags for a list of flags that need updating.'), 'warning');
}
if (!empty($rows)) {
$header = array(t('Disabled Flags'), t('Module'), t('Flag type'), t('Operations'));
$output .= theme('table', array('header' => $header, 'rows' => $rows));
}
return $output;
}
/**
* Theme the output for the main flag administration page.
*/
function theme_flag_admin_page($variables) {
$flags = $variables['flags'];
$default_flags = $variables['default_flags'];
$output = '';
$output .= drupal_render($variables['flag_admin_listing']);
$output .= theme('flag_admin_listing_disabled', array('flags' => $flags, 'default_flags' => $default_flags));
if (!module_exists('views')) {
$output .= '<p>' . t('The <a href="@views-url">Views</a> module is not installed, or not enabled. It is recommended that you install the Views module to be able to easily produce lists of flagged content.', array('@views-url' => url('http://drupal.org/project/views'))) . '</p>';
}
else {
$output .= '<p>';
$output .= t('Lists of flagged content can be displayed using views. You can configure these in the <a href="@views-url">Views administration section</a>.', array('@views-url' => url('admin/structure/views')));
if (flag_get_flag('bookmarks')) {
$output .= ' ' . t('Flag module automatically provides a few <a href="@views-url">default views for the <em>bookmarks</em> flag</a>. You can use these as templates by cloning these views and then customizing as desired.', array('@views-url' => url('admin/structure/views', array('query' => array('tag' => 'flag')))));
}
$output .= ' ' . t('The <a href="@flag-handbook-url">Flag module handbook</a> contains extensive <a href="@customize-url">documentation on creating customized views</a> using flags.', array('@flag-handbook-url' => 'http://drupal.org/handbook/modules/flag', '@customize-url' => 'http://drupal.org/node/296954'));
$output .= '</p>';
}
if (!module_exists('flag_actions')) {
$output .= '<p>' . t('Flagging an item may trigger <em>actions</em>. However, you don\'t have the <em>Flag actions</em> module <a href="@modules-url">enabled</a>, so you won\'t be able to enjoy this feature.', array('@actions-url' => url(FLAG_ADMIN_PATH . '/actions'), '@modules-url' => url('admin/modules'))) . '</p>';
}
else {
$output .= '<p>' . t('Flagging an item may trigger <a href="@actions-url">actions</a>.', array('@actions-url' => url(FLAG_ADMIN_PATH . '/actions'))) . '</p>';
}
if (!module_exists('rules')) {
$output .= '<p>' . t('Flagging an item may trigger <em>rules</em>. However, you don\'t have the <a href="@rules-url">Rules</a> module enabled, so you won\'t be able to enjoy this feature. The Rules module is a more extensive solution than Flag actions.', array('@rules-url' => url('http://drupal.org/node/407070'))) . '</p>';
}
else {
$output .= '<p>' . t('Flagging an item may trigger <a href="@rules-url">rules</a>.', array('@rules-url' => url('admin/config/workflow/rules'))) . '</p>';
}
$output .= '<p>' . t('To learn about the various ways to use flags, please check out the <a href="@handbook-url">Flag module handbook</a>.', array('@handbook-url' => 'http://drupal.org/handbook/modules/flag')) . '</p>';
return $output;
}
/**
* Menu callback for adding a new flag.
*
* @param $entity_type
* The entity type for the new flag, taken from the path argument. If not
* present (i.e., '/add'), a form showing all possible flag types is shown.
* Otherwise, this shows a form for adding af flag the given type.
*
* @see flag_add_form()
* @see flag_form()
*/
function flag_add_page($entity_type = NULL) {
if (isset($entity_type)) {
$flag = flag_flag::factory_by_entity_type($entity_type);
// Mark the flag as new.
$flag->is_new = TRUE;
$type_info = flag_fetch_definition($entity_type);
drupal_set_title(t('Add new @type flag', array('@type' => $type_info['title'])));
return drupal_get_form('flag_form', $flag);
}
drupal_set_title(t('Select flag type'));
return drupal_get_form('flag_add_form');
}
/**
* Present a form for creating a new flag, setting the type of flag.
*/
function flag_add_form($form, &$form_state) {
$types = array();
foreach (flag_fetch_definition() as $type => $info) {
$types[$type] = $info['title'] . '<div class="description">' . $info['description'] . '</div>';
}
$form['type'] = array(
'#type' => 'radios',
'#title' => t('Flag type'),
'#default_value' => 'node',
'#description' => t('The type of object this flag will affect. This cannot be changed once the flag is created.'),
'#required' => TRUE,
'#options' => $types,
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Continue'),
);
return $form;
}
function flag_add_form_validate($form, &$form_state) {
$flag = flag_flag::factory_by_entity_type($form_state['values']['type']);
if (get_class($flag) == 'flag_broken') {
form_set_error('type', t("This flag type, %type, isn't valid.", array('%type' => $form_state['values']['type'])));
}
}
function flag_add_form_submit($form, &$form_state) {
$form_state['redirect'] = FLAG_ADMIN_PATH . '/add/' . $form_state['values']['type'];
}
/**
* Add/Edit flag page.
*/
function flag_form($form, &$form_state, $flag) {
$form['#flag'] = $flag;
$form['#flag_name'] = $flag->name;
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $flag->title,
'#description' => t('A short, descriptive title for this flag. It will be used in administrative interfaces to refer to this flag, and in page titles and menu items of some <a href="@insite-views-url">views</a> this module provides (theses are customizable, though). Some examples could be <em>Bookmarks</em>, <em>Favorites</em>, or <em>Offensive</em>.', array('@insite-views-url' => url('admin/structure/views'))),
'#maxlength' => 255,
'#required' => TRUE,
'#weight' => -3,
);
$form['name'] = array(
'#type' => 'machine_name',
'#title' => t('Machine name'),
'#default_value' => $flag->name,
'#description' => t('The machine-name for this flag. It may be up to 32 characters long and may only contain lowercase letters, underscores, and numbers. It will be used in URLs and in all API calls.'),
'#maxlength' => 32,
'#weight' => -2,
'#machine_name' => array(
'exists' => 'flag_get_flag',
'source' => array('title'),
),
);
$form['global'] = array(
'#type' => 'checkbox',
'#title' => t('Global flag'),
'#default_value' => $flag->global,
'#description' => t('If checked, flag is considered "global" and each entity is either flagged or not. If unchecked, each user has individual flags on entities.'),
'#weight' => -1,
);
// Don't allow the 'global' checkbox to be changed when flaggings exist:
// there are too many unpleasant consequences in either direction.
// @todo: Allow this, but with a confirmation form, assuming anyone actually
// needs this feature.
if (!empty($flag->fid) && flag_get_flag_counts($flag->name)) {
$form['global']['#disabled'] = TRUE;
$form['global']['#description'] .= '<br />' . t('This setting cannot be changed when flaggings exist for this flag.');
}
$form['messages'] = array(
'#type' => 'fieldset',
'#title' => t('Messages'),
);
$form['messages']['flag_short'] = array(
'#type' => 'textfield',
'#title' => t('Flag link text'),
'#default_value' => !empty($flag->flag_short) ? $flag->flag_short : t('Flag this item'),
'#description' => t('The text for the "flag this" link for this flag.'),
'#required' => TRUE,
);
$form['messages']['flag_long'] = array(
'#type' => 'textfield',
'#title' => t('Flag link description'),
'#default_value' => $flag->flag_long,
'#description' => t('The description of the "flag this" link. Usually displayed on mouseover.'),
);
$form['messages']['flag_message'] = array(
'#type' => 'textfield',
'#title' => t('Flagged message'),
'#default_value' => $flag->flag_message,
'#description' => t('Message displayed after flagging content. If JavaScript is enabled, it will be displayed below the link. If not, it will be displayed in the message area.'),
);
$form['messages']['unflag_short'] = array(
'#type' => 'textfield',
'#title' => t('Unflag link text'),
'#default_value' => !empty($flag->unflag_short) ? $flag->unflag_short : t('Unflag this item'),
'#description' => t('The text for the "unflag this" link for this flag.'),
'#required' => TRUE,
);
$form['messages']['unflag_long'] = array(
'#type' => 'textfield',
'#title' => t('Unflag link description'),
'#default_value' => $flag->unflag_long,
'#description' => t('The description of the "unflag this" link. Usually displayed on mouseover.'),
);
$form['messages']['unflag_message'] = array(
'#type' => 'textfield',
'#title' => t('Unflagged message'),
'#default_value' => $flag->unflag_message,
'#description' => t('Message displayed after content has been unflagged. If JavaScript is enabled, it will be displayed below the link. If not, it will be displayed in the message area.'),
);
$form['messages']['tokens_help'] = array(
'#title' => t('Token replacement'),
'#type' => 'fieldset',
'#description' =>
'<p>' . t('The above six texts may contain any of the tokens listed below. For example, <em>"Flag link text"</em> could be entered as:') . '</p>' .
theme('item_list', array(
'items' => array(
t('Add &lt;em&gt;[node:title]&lt;/em&gt; to your favorites'),
t('Add this [node:type] to your favorites'),
t('Vote for this proposal ([node:flag-vote-count] people have already done so)'),
),
'attributes' => array('class' => 'token-examples'),
)) .
'<p>' . t('These tokens will be replaced with the appropriate fields from the node (or user, or comment).') . '</p>' .
theme('flag_tokens_browser', array('types' => $flag->get_labels_token_types())),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['access'] = array(
'#type' => 'fieldset',
'#title' => t('Flag access'),
'#tree' => FALSE,
'#weight' => 10,
);
// Flag classes will want to override this form element.
$form['access']['types'] = array(
'#type' => 'checkboxes',
'#title' => t('Flaggable types'),
'#options' => array(),
'#default_value' => $flag->types,
'#description' => t('Check any sub-types that this flag may be used on.'),
'#required' => TRUE,
'#weight' => 10,
);
// Disabled access breaks checkboxes unless #value is hard coded.
if (!empty($flag->locked['types'])) {
$form['access']['types']['#value'] = $flag->types;
}
// Load the user permissions into the flag.
if (isset($flag->fid)) {
$flag->fetch_roles();
}
elseif (isset($flag->import_roles)) {
// Convert the roles data from old API 2 flags that have been run through
// the update system.
// @see FlagUpdate_2::update()
$flag->roles = $flag->import_roles;
}
else {
// For new flags, provide a reasonable default value.
$flag->roles = array(
'flag' => array(DRUPAL_AUTHENTICATED_RID),
'unflag' => array(DRUPAL_AUTHENTICATED_RID),
);
}
$form['access']['roles'] = array(
'#title' => t('Roles that may use this flag'),
'#description' => t('Users may only unflag content if they have access to flag the content initially. Checking <em>authenticated user</em> will allow access for all logged-in users.'),
'#theme' => 'flag_form_roles',
'#theme_wrappers' => array('form_element'),
'#weight' => -2,
'#attached' => array(
'js' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.js'),
'css' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.css'),
),
);
if (module_exists('session_api')) {
$form['access']['roles']['#description'] .= ' ' . t('Support for anonymous users is being provided by <a href="http://drupal.org/project/session_api">Session API</a>.');
}
else {
$form['access']['roles']['#description'] .= ' ' . t('Anonymous users may flag content if the <a href="http://drupal.org/project/session_api">Session API</a> module is installed.');
}
$form['access']['roles']['flag'] = array(
'#type' => 'checkboxes',
'#options' => user_roles(!module_exists('session_api')),
'#default_value' => $flag->roles['flag'],
'#parents' => array('roles', 'flag'),
);
$form['access']['roles']['unflag'] = array(
'#type' => 'checkboxes',
'#options' => user_roles(!module_exists('session_api')),
'#default_value' => $flag->roles['unflag'],
'#parents' => array('roles', 'unflag'),
);
$form['access']['unflag_denied_text'] = array(
'#type' => 'textfield',
'#title' => t('Unflag not allowed text'),
'#default_value' => $flag->unflag_denied_text,
'#description' => t('If a user is allowed to flag but not unflag, this text will be displayed after flagging. Often this is the past-tense of the link text, such as "flagged".'),
'#weight' => -1,
);
$form['display'] = array(
'#type' => 'fieldset',
'#title' => t('Display options'),
'#description' => t('Flags are usually controlled through links that allow users to toggle their behavior. You can choose how users interact with flags by changing options here. It is legitimate to have none of the following checkboxes ticked, if, for some reason, you wish <a href="@placement-url">to place the the links on the page yourself</a>.', array('@placement-url' => 'http://drupal.org/node/295383')),
'#tree' => FALSE,
'#weight' => 20,
'#after_build' => array('flag_link_type_options_states'),
);
$form['display']['link_type'] = array(
'#type' => 'radios',
'#title' => t('Link type'),
'#options' => _flag_link_type_options(),
'#after_build' => array('flag_check_link_types'),
'#default_value' => $flag->link_type,
// Give this a high weight so additions by the flag classes for entity-
// specific options go above.
'#weight' => 18,
'#attached' => array(
'js' => array(drupal_get_path('module', 'flag') . '/theme/flag-admin.js'),
),
'#attributes' => array(
'class' => array('flag-link-options'),
),
);
// Add the descriptions to each ratio button element. These attach to the
// elements when FormAPI expands them.
foreach (_flag_link_type_descriptions() as $key => $description) {
$form['display']['link_type'][$key]['#description'] = $description;
}
$form['display']['link_options_intro'] = array(
// This is a hack to allow a markup element to use FormAPI states.
// @see http://www.bywombats.com/blog/06-25-2011/using-containers-states-enabled-markup-form-elements
'#type' => 'container',
'#children' => '<p id="link-options-intro">' . t('The selected link type may require these additional settings:') . '</p>',
'#weight' => 20,
);
$form['display']['link_options_confirm'] = array(
'#type' => 'fieldset',
'#title' => t('Options for the "Confirmation form" link type'),
// Any "link type" provider module must put its settings fields inside
// a fieldset whose HTML ID is link-options-LINKTYPE, where LINKTYPE is
// the machine-name of the link type. This is necessary for the
// radiobutton's JavaScript dependency feature to work.
'#id' => 'link-options-confirm',
'#weight' => 21,
);
$form['display']['link_options_confirm']['flag_confirmation'] = array(
'#type' => 'textfield',
'#title' => t('Flag confirmation message'),
'#default_value' => isset($flag->flag_confirmation) ? $flag->flag_confirmation : '',
'#description' => t('Message displayed if the user has clicked the "flag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to flag this content?"'),
);
$form['display']['link_options_confirm']['unflag_confirmation'] = array(
'#type' => 'textfield',
'#title' => t('Unflag confirmation message'),
'#default_value' => isset($flag->unflag_confirmation) ? $flag->unflag_confirmation : '',
'#description' => t('Message displayed if the user has clicked the "unflag this" link and confirmation is required. Usually presented in the form of a question such as, "Are you sure you want to unflag this content?"'),
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save flag'),
// We put this button on the form before calling $flag->options_form()
// to give the flag handler a chance to remove it (e.g. flag_broken).
'#weight' => 999,
);
// Add our process handler to disable access to locked properties.
$form['#process'][] = 'flag_form_locked_process';
// Allow the flag handler to make additions and changes to the form.
// Note that the flag_broken handler will completely empty the form array!
$flag->options_form($form);
return $form;
}
/**
* FormAPI after_build function set states on link type options fieldsets.
*
* We do this in an after build so we handle further link types fieldsets from
* other modules that provide link types.
*
* This expects a link type's fieldset to be $form['display'][link_options_TYPE]
* so that can be matched up with the radio button value.
*/
function flag_link_type_options_states($element) {
$intro_element_values_array = array();
foreach (element_children($element) as $key) {
if (isset($element[$key]['#type']) && $element[$key]['#type'] == 'fieldset' && substr($key, 0, 12) == 'link_options') {
// Trim the radio value from the fieldset key. This assumed the fieldset
// key is 'link_options_TYPE'.
$radio_value = substr($key, 13);
$element[$key]['#states'] = array(
'visible' => array(
':input[name="link_type"]' => array('value' => $radio_value),
),
);
// Gather up the radio values for the format we need for a multiple
// value state.
$intro_element_values_array[] = array('value' => $radio_value);
}
}
$element['link_options_intro']['#states'] = array(
'visible' => array(
':input[name="link_type"]' => $intro_element_values_array,
),
);
return $element;
}
/**
* Form process handler for locking flag properties.
*
* Flags defined in code may define an array of properties in $flag->locked that
* are to be locked and may not be edited by the user.
*/
function flag_form_locked_process($element, &$form_state, $form) {
$flag = $form['#flag'];
// Disable access to a form element whose name matches a locked flag property.
if (isset($element['#name']) && !empty($flag->locked[$element['#name']])) {
$element['#access'] = FALSE;
}
// Recurse into the form array.
foreach (element_children($element) as $key) {
// Workaround for Core inconvenience: setting #process here prevents an
// element's essential #process handlers from its hook_element_info()
// definition from being set in form_builder().
// @see http://drupal.org/node/1779496
if (isset($element[$key]['#type']) && ($info = element_info($element[$key]['#type']))) {
if (isset($info['#process'])) {
$element[$key]['#process'] = $info['#process'];
}
}
$element[$key]['#process'][] = 'flag_form_locked_process';
}
return $element;
}
/**
* Add/Edit flag form validate.
*/
function flag_form_validate($form, &$form_state) {
$form_state['values']['title'] = trim($form_state['values']['title']);
$form_values = $form_state['values'];
if ($form_values['link_type'] == 'confirm') {
if (empty($form_values['flag_confirmation'])) {
form_set_error('flag_confirmation', t('A flag confirmation message is required when using the confirmation link type.'));
}
if (empty($form_values['unflag_confirmation'])) {
form_set_error('unflag_confirmation', t('An unflag confirmation message is required when using the confirmation link type.'));
}
}
$flag = $form['#flag'];
$flag->form_input($form_values);
$errors = $flag->validate();
foreach ($errors as $field => $field_errors) {
foreach ($field_errors as $error) {
form_set_error($field, $error['message']);
}
}
}
/**
* Add/Edit flag form submit.
*/
function flag_form_submit($form, &$form_state) {
$flag = $form['#flag'];
$form_state['values']['title'] = trim($form_state['values']['title']);
$flag->form_input($form_state['values']);
$flag->save();
$flag->enable();
drupal_set_message(t('Flag @title has been saved.', array('@title' => $flag->get_title())));
// We clear caches more vigorously if the flag was new.
_flag_clear_cache($flag->entity_type, !empty($flag->is_new));
// Save permissions.
// This needs to be done after the flag cache has been cleared, so that
// the new permissions are picked up by hook_permission().
// This may need to move to the flag class when we implement extra permissions
// for different flag types: http://drupal.org/node/879988
// If the flag machine name as changed, clean up all the obsolete permissions.
if ($flag->name != $form['#flag_name']) {
$old_name = $form['#flag_name'];
$permissions = array("flag $old_name", "unflag $old_name");
foreach (array_keys(user_roles()) as $rid) {
user_role_revoke_permissions($rid, $permissions);
}
}
foreach (array_keys(user_roles(!module_exists('session_api'))) as $rid) {
// Create an array of permissions, based on the checkboxes element name.
$permissions = array(
"flag $flag->name" => $flag->roles['flag'][$rid],
"unflag $flag->name" => $flag->roles['unflag'][$rid],
);
user_role_change_permissions($rid, $permissions);
}
// @todo: when we add database caching for flags we'll have to clear the
// cache again here.
$form_state['redirect'] = FLAG_ADMIN_PATH;
}
/**
* Output the access options for roles in a table.
*/
function theme_flag_form_roles($variables) {
$element = $variables['element'];
$header = array(
array('class' => array('checkbox'), 'data' => t('Flag')),
array('class' => array('checkbox'), 'data' => t('Unflag')),
t('Role'),
);
$rows = array();
foreach (element_children($element['flag']) as $role) {
$row = array();
$role_name = $element['flag'][$role]['#title'];
unset($element['flag'][$role]['#title']);
unset($element['unflag'][$role]['#title']);
$element['flag'][$role]['#attributes']['class'] = array('flag-access');
$element['unflag'][$role]['#attributes']['class'] = array('unflag-access');
$row[] = array('class' => array('checkbox'), 'data' => drupal_render($element['flag'][$role]));
$row[] = array('class' => array('checkbox'), 'data' => drupal_render($element['unflag'][$role]));
$row[] = $role_name;
$rows[] = $row;
}
return theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array(
'class' => array('flag-admin-table'),
'id' => 'flag-roles',
),
));
}
/**
* Delete flag page.
*/
function flag_delete_confirm($form, &$form_state, $flag) {
$form['#flag'] = $flag;
return confirm_form($form,
t('Are you sure you want to delete %title?', array('%title' => $flag->get_title())),
!empty($_GET['destination']) ? $_GET['destination'] : FLAG_ADMIN_PATH,
isset($flag->module) ? t('This flag is provided by the %module module. It will lose any customizations and be disabled.', array('%module' => $flag->module)) : t('This action cannot be undone.'),
t('Delete'), t('Cancel')
);
}
function flag_delete_confirm_submit($form, &$form_state) {
$flag = $form['#flag'];
if ($form_state['values']['confirm']) {
$flag->delete();
$flag->disable();
_flag_clear_cache($flag->entity_type, TRUE);
}
drupal_set_message(t('Flag @name has been deleted.', array('@name' => $flag->get_title())));
$form_state['redirect'] = FLAG_ADMIN_PATH;
}
/**
* FormAPI after_build function to check that the link type exists.
*/
function flag_check_link_types($element) {
$link_types = flag_get_link_types();
if (!isset($link_types[$element['#value']])) {
drupal_set_message(t('This flag uses a link type of %type, which does not exist.', array('%type' => $element['#value'])), 'error');
}
return $element;
}
/**
* Clears various caches when one or more flags are modified.
*
* @param $entity_types
* The entity types for the flags. May be a single value or an array.
* @param $is_insert_or_delete
* Whether the modified flag is being inserted (saved for the first time) or
* deleted. This results in a more vigorous clearing of caches. In
* particular, when no flags exist yet, no Field admin UI paths exist and these
* need to be created.
*/
function _flag_clear_cache($entity_types, $is_insert_or_delete = FALSE) {
if (!is_array($entity_types)) {
$entity_types = array($entity_types);
}
// Reset our flags cache, thereby making the following code aware of the
// modifications.
drupal_static_reset('flag_get_flags');
if ($is_insert_or_delete) {
// A new or deleted flag means we are changing bundles on the Flagging
// entity, and thus need to clear the entity info cache.
entity_info_cache_clear();
}
// Clear FieldAPI's field_extra cache, so our changes to pseudofields are
// noticed. It's rather too much effort to both a) check whether the
// pseudofield setting has changed either way, and b) specifically clear just
// the bundles that are (or were!!) affected, so we just clear for all bundles
// on our entity type regardlesss.
foreach ($entity_types as $entity_type) {
cache_clear_all("field_info:bundle_extra:$entity_type:", 'cache_field', TRUE);
}
if (module_exists('views')) {
views_invalidate_cache();
}
// The title of a flag may appear in the menu (indirectly, via our "default
// views"), so we need to clear the menu cache. This call also clears the
// page cache, which is desirable too because the flag labels may have
// changed.
menu_rebuild();
}

View File

@@ -0,0 +1,170 @@
<?php
/**
* @file
* Contains the FlagCookieStorage class.
*/
/**
* Utility class to handle cookies.
*
* Cookies are used to record flaggings for anonymous users on cached pages.
*
* This class contains only two instance methods. Usage example:
* @code
* $storage = FlagCookieStorage::factory($flag);
* $storage->flag(145);
* $storage->unflag(17);
* @endcode
*
* You may delete all the cookies with <code>FlagCookieStorage::drop()</code>.
*/
abstract class FlagCookieStorage {
/**
* Returns the actual storage object compatible with the flag.
*/
static function factory($flag) {
if ($flag->global) {
return new FlagGlobalCookieStorage($flag);
}
else {
return new FlagNonGlobalCookieStorage($flag);
}
}
function __construct($flag) {
$this->flag = $flag;
}
/**
* "Flags" an item.
*
* It just records this fact in a cookie.
*/
abstract function flag($entity_id);
/**
* "Unflags" an item.
*
* It just records this fact in a cookie.
*/
abstract function unflag($entity_id);
/**
* Deletes all the cookies.
*
* (Etymology: "drop" as in "drop database".)
*/
static function drop() {
FlagGlobalCookieStorage::drop();
FlagNonGlobalCookieStorage::drop();
}
}
/**
* Storage handler for global flags.
*/
class FlagGlobalCookieStorage extends FlagCookieStorage {
function flag($entity_id) {
$cookie_key = $this->cookie_key($entity_id);
setcookie($cookie_key, 1, REQUEST_TIME + $this->get_lifetime(), base_path());
$_COOKIE[$cookie_key] = 1;
}
function unflag($entity_id) {
$cookie_key = $this->cookie_key($entity_id);
setcookie($cookie_key, 0, REQUEST_TIME + $this->get_lifetime(), base_path());
$_COOKIE[$cookie_key] = 0;
}
// Global flags persist for the length of the minimum cache lifetime.
protected function get_lifetime() {
$cookie_lifetime = variable_get('cache', 0) ? variable_get('cache_lifetime', 0) : -1;
// Do not let the cookie lifetime be 0 (which is the no cache limit on
// anonymous page caching), since it would expire immediately. Usually
// the no cache limit means caches are cleared on cron, which usually runs
// at least once an hour.
if ($cookie_lifetime == 0) {
$cookie_lifetime = 3600;
}
return $cookie_lifetime;
}
protected function cookie_key($entity_id) {
return 'flag_global_' . $this->flag->name . '_' . $entity_id;
}
/**
* Deletes all the global cookies.
*/
static function drop() {
foreach ($_COOKIE as $key => $value) {
if (strpos($key, 'flag_global_') === 0) {
setcookie($key, FALSE, 0, base_path());
unset($_COOKIE[$key]);
}
}
}
}
/**
* Storage handler for non-global flags.
*/
class FlagNonGlobalCookieStorage extends FlagCookieStorage {
// The anonymous per-user flaggings are stored in a single cookie, so that
// all of them persist as long as the Drupal cookie lifetime.
function __construct($flag) {
parent::__construct($flag);
$this->flaggings = isset($_COOKIE['flags']) ? explode(' ', $_COOKIE['flags']) : array();
}
function flag($entity_id) {
if (!$this->is_flagged($entity_id)) {
$this->flaggings[] = $this->cookie_key($entity_id);
$this->write();
}
}
function unflag($entity_id) {
if (($index = $this->index_of($entity_id)) !== FALSE) {
unset($this->flaggings[$index]);
$this->write();
}
}
protected function get_lifetime() {
return min((int) ini_get('session.cookie_lifetime'), (int) ini_get('session.gc_maxlifetime'));
}
protected function cookie_key($entity_id) {
return $this->flag->name . '_' . $entity_id;
}
protected function write() {
$serialized = implode(' ', array_filter($this->flaggings));
setcookie('flags', $serialized, REQUEST_TIME + $this->get_lifetime(), base_path());
$_COOKIE['flags'] = $serialized;
}
protected function is_flagged($entity_id) {
return $this->index_of($entity_id) !== FALSE;
}
protected function index_of($entity_id) {
return array_search($this->cookie_key($entity_id), $this->flaggings);
}
/**
* Deletes the cookie.
*/
static function drop() {
if (isset($_COOKIE['flags'])) {
setcookie('flags', FALSE, 0, base_path());
unset($_COOKIE['flags']);
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* @file
* Provides supporting code for the entity/fields system.
*
* Note: We're making the <em>flaggings</em> fieldable, not the <em>flags</em>.
* (In the same way that Drupal makes <em>nodes</em> fieldable, not <em>node
* types</em>).
*/
/**
* Controller class for flaggings.
*/
class FlaggingController extends DrupalDefaultEntityController {
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
$query = parent::buildQuery($ids, $conditions, $revision_id);
// Add the flag name, which determines the bundle.
$query->innerJoin('flag', 'flag', 'base.fid = flag.fid');
$query->addField('flag', 'name', 'flag_name');
return $query;
}
}

View File

@@ -0,0 +1,378 @@
<?php
/**
* @file
* Import/Export functionality provided by Flag module.
*/
/**
* Export a flag to code.
*
* @param $flags
* An array of flag objects, or flag name.
* @param $module
* Optional. The name of the module that will be created if exporting to use
* in hook_flag_default_flags().
*/
function flag_export_flags($flags = array(), $module = '', $indent = '') {
module_load_include('inc', 'features', 'features.export'); // For features_var_export() (optional).
$output = $indent . '$flags = array();' . "\n";
foreach ($flags as $item) {
if (is_object($item)) {
$flag = $item;
}
else {
// We got just the flag name, for example from the features
// implementation.
if (!($flag = flag_load($item, TRUE))) {
continue;
}
}
if (!$flag->is_compatible()) {
drupal_set_message(t('Could not export flag %flag-name: Your flag was created by a different version of the Flag module than is now being used.', array('%flag-name' => $flag->name)), 'error');
continue;
}
$flag->api_version = FLAG_API_VERSION;
$new_flag = (array) $flag;
if (!empty($module)) {
// Even though Flag adds the module name itself later, we add the module
// name here for reference by other modules (such as Features).
$new_flag['module'] = $module;
// Lock the flag name, as is normally desired by modules using
// hook_flag_default_flags(), and needed by Features.
$new_flag['locked'] = array('name');
}
// Allow other modules to change the exported flag.
drupal_alter('flag_export', $new_flag);
// Remove properties we don't export.
$unset_properties = array(
// Remove the flag ID.
'fid',
// The name is emitted as the key for the array.
'name',
// The entity info is just used as helper data.
'entity_info',
// Remove roles.
'roles',
// Remove errors.
'errors',
);
foreach ($unset_properties as $property) {
unset($new_flag[$property]);
}
$output .= $indent . '// Exported flag: "' . check_plain($flag->get_title()) . '"' . ".\n";
$output .= $indent . '$flags[\'' . $flag->name . '\'] = ' . (function_exists('features_var_export') ? features_var_export($new_flag, $indent) : var_export($new_flag, TRUE)) . ";\n";
}
$output .= $indent . 'return $flags;' . "\n";
return $output;
}
/**
* Form to import a flag.
*/
function flag_import_form() {
$form = array();
$form['import'] = array(
'#title' => t('Flag import code'),
'#type' => 'textarea',
'#default_value' => '',
'#rows' => 15,
'#required' => TRUE,
'#description' => t('Paste the code from a <a href="@export-url">flag export</a> here to import it into you site. Flags imported with the same name will update existing flags. Flags with a new name will be created.', array('@export-url' => url(FLAG_ADMIN_PATH . '/export'))),
);
$form['submit'] = array(
'#value' => t('Import'),
'#type' => 'submit',
);
return $form;
}
/**
* Validate handler; Import a flag.
*/
function flag_import_form_validate($form, &$form_state) {
$flags = array();
ob_start();
eval($form_state['values']['import']);
ob_end_clean();
if (!isset($flags) || !is_array($flags)) {
form_set_error('import', t('A valid list of flags could not be found in the import code.'));
return;
}
// Create the flag object.
foreach ($flags as $flag_name => $flag_info) {
// Backward compatibility: old exported flags have their names in $flag_info
// instead, so we use the += operator to not overwrite it.
$flag_info += array(
'name' => $flag_name,
);
$new_flag = flag_flag::factory_by_array($flag_info);
// Give new flags with the same name a matching FID, which tells Flag to
// update the existing flag, rather than creating a new one.
if ($existing_flag = flag_get_flag($new_flag->name)) {
$new_flag->fid = $existing_flag->fid;
}
if ($errors = $new_flag->validate()) {
$message = t('The import of the %flag flag failed because the following errors were encountered during the import:', array('%flag' => $new_flag->name));
$message_errors = array();
foreach ($errors as $field => $field_errors) {
foreach ($field_errors as $error) {
$message_errors[] = $error['message'];
}
}
form_set_error('import', $message . theme('item_list', array('items' => $message_errors)));
}
else {
// Save the new flag for the submit handler.
$form_state['flags'][] = $new_flag;
}
}
}
/**
* Submit handler; Import a flag.
*/
function flag_import_form_submit($form, &$form_state) {
module_load_include('inc', 'flag', 'includes/flag.admin');
// Build up values for the cache clear.
$entity_types = array();
$new = FALSE;
foreach ($form_state['flags'] as $flag) {
$flag->save();
if (!empty($flag->status)) {
$flag->enable();
}
if ($flag->is_new) {
drupal_set_message(t('Flag @name has been imported.', array('@name' => $flag->name)));
$new = TRUE;
}
else {
drupal_set_message(t('Flag @name has been updated.', array('@name' => $flag->name)));
}
$entity_types[] = $flag->entity_type;
}
_flag_clear_cache($entity_types, $new);
$form_state['redirect'] = FLAG_ADMIN_PATH;
}
/**
* Export a flag and display it in a form.
*/
function flag_export_form($form, &$form_state, $flag = NULL) {
// If we were passed a flag, use it as the list of flags to export.
if ($flag) {
$flags = array($flag);
}
// Display a list of flags to export.
if (!isset($flags)) {
if (isset($form_state['values']['flags'])) {
$flags = array();
foreach ($form_state['values']['flags'] as $flag_name) {
if ($flag_name && $flag = flag_get_flag($flag_name)) {
$flags[] = $flag;
}
}
}
else {
$form['flags'] = array(
'#type' => 'checkboxes',
'#title' => t('Flags to export'),
'#options' => drupal_map_assoc(array_keys(flag_get_flags())),
'#description' => t('Exporting your flags is useful for moving flags from one site to another, or when including your flag definitions in a module.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
}
}
if (isset($flags)) {
$code = flag_export_flags($flags);
// Link to the Features page if module is present, otherwise link to the
// Drupal project page.
$features_link = module_exists('features') ? url('admin/build/features') : url('http://drupal.org/project/features');
$form['export'] = array(
'#type' => 'textarea',
'#title' => t('Flag exports'),
'#description' => t('Use the exported code to later <a href="@import-flag">import</a> it. Exports can be included in modules using <a href="http://drupal.org/node/305086#default-flags">hook_flag_default_flags()</a> or using the <a href="@features-url">Features</a> module.', array('@import-flag' => url(FLAG_ADMIN_PATH . '/import'), '@features-url' => $features_link)),
'#value' => $code,
'#rows' => 15,
);
}
return $form;
}
/**
* Submit handler; Rebuild the export form after the list of flags has been set.
*/
function flag_export_form_submit($form, &$form_state) {
$form_state['rebuild'] = TRUE;
}
/**
* Page for displaying an upgrade message and export form for Flag 1.x flags.
*/
function flag_update_page($flag) {
if ($flag->is_compatible()) {
drupal_set_message(t('The flag %name is already up-to-date with the latest Flag API and does not need upgrading.', array('%name' => $flag->name)));
drupal_goto(FLAG_ADMIN_PATH);
}
drupal_set_message(t('The flag %name is currently using the Flag API version @version, which is not compatible with the current version of Flag. You can upgrade this flag by pasting the below code into <em>@module_flag_default_flags()</em> function in the @module.module file.', array('%name' => $flag->name, '@version' => $flag->api_version, '@module' => $flag->module)), 'warning');
flag_update_export($flag);
return drupal_get_form('flag_export_form', $flag);
}
/**
* Update a flag before export.
*
* @param $flag
* The flag object passed by reference.
*/
function flag_update_export(&$flag) {
// Set the API version to 1 by default: version 1 did not explicitly define
// the API version.
if (empty($flag->api_version)) {
$flag->api_version = 1;
}
// Get all our update classes.
// This is not terribly graceful, but the alternative is declaring our classes
// explicitly, or registering them with the Drupal autoloader and then running
// a database query, which seems a waste of space given we only ever need
// these here.
$classes = get_declared_classes();
$update_handlers = array();
foreach ($classes as $class) {
// Any class whose name is of the form 'FlagUpdate_foo' is one of ours, we
// assume. Should this prove problematic, we can add use of reflection here.
if (substr($class, 0, 11) == 'FlagUpdate_') {
// @todo: change this to work with the static class when we drop support
// for PHP 5.2: see commit d5b517.
$update_handler = new $class;
// Cast to string, as decimals as array keys seem to be rounded down to
// ints, WTF PHP?
$version = (string) $update_handler->old_api_version;
$update_handlers[$version] = $update_handler;
}
}
// Sort the classes by old version number.
uksort($update_handlers, 'version_compare');
// Work through each update handler.
foreach ($update_handlers as $old_api_version => $update_handler) {
// Skip update classes that are older than our current flag.
if (version_compare($old_api_version, $flag->api_version, '<')) {
continue;
}
// Run the update and change the API version on the flag.
$update_handler->update($flag);
$flag->api_version = $update_handler->new_api_version;
}
}
/**
* Flag update class for API 1 flags -> API 2.
*
* The class name after the prefix is immaterial, though we follow the Drupal
* system update convention whereby the number here is what we update to.
*/
class FlagUpdate_2 {
/**
* The API version this class updates a flag from.
*
* @todo: Change this to a class constant when we drop support for PHP 5.2.
*/
public $old_api_version = 1;
/**
* The API version this class updates a flag to.
*/
public $new_api_version = 2;
/**
* The update function for the flag.
*/
static function update(&$flag) {
if (isset($flag->roles) && !isset($flag->roles['flag'])) {
$flag->roles = array(
'flag' => $flag->roles,
'unflag' => $flag->roles,
);
}
}
}
/**
* Flag update class for API 2 flags -> API 3.
*/
class FlagUpdate_3 {
public $old_api_version = 2;
public $new_api_version = 3;
static function update(&$flag) {
// Change the content_type property to entity_type.
if (isset($flag->content_type)) {
$flag->entity_type = $flag->content_type;
unset($flag->content_type);
}
// We can't convert the flag roles data to user permissions at this point
// because the flag is disabled and hence hook_permission() doesn't see it
// to define its permissions.
// Instead, we copy it to import_roles, which the flag add form will handle
// on new flags (which this flag will behave as when it is re-enabled).
// @see flag_form()
if (isset($flag->roles)) {
$flag->import_roles = $flag->roles;
}
// Update show_on_teaser property to use new view mode settings.
if (!empty($flag->show_on_teaser)) {
$flag->show_in_links['teaser'] = TRUE;
unset($flag->show_on_teaser);
}
// Update show_on_page property to use new view mode settings.
if (!empty($flag->show_on_page)) {
$flag->show_in_links['full'] = TRUE;
unset($flag->show_on_page);
}
// Update show_on_comment and show_on_entity properties to use new view
// mode settings. Since the old logic was to show on all view modes, do that.
if (!empty($flag->show_on_entity) || !empty($flag->show_on_comment)) {
if ($entity_info = entity_get_info($flag->entity_type)) {
foreach ($entity_info['view modes'] as $view_mode => $value) {
$flag->show_in_links[$view_mode] = TRUE;
}
}
unset($flag->show_on_entity, $flag->show_on_comment);
}
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* @file
* Features integration for Flag module.
*/
/**
* Implements hook_features_export().
*/
function flag_features_export($data, &$export, $module_name = '') {
$pipe = array();
// Add flag module as a dependency.
$export['dependencies']['flag'] = 'flag';
// Ensure the modules that provide the flag are included as dependencies.
$modules = flag_features_providing_module();
foreach ($data as $key => $flag) {
$module = '';
if ($flag = flag_load($flag, TRUE)) {
// Try to get the module that provides the entity this flag is on.
// First pass: check whether there's a module that implements
// hook_flag_type_info() for this entity.
if (array_key_exists($flag->entity_type, $modules)) {
$module = $modules[$flag->entity_type];
}
else {
// Second pass: check whether this entity is defined using Entity API
// and therefore has an extra 'module' property in its information.
if ($entity_info = entity_get_info($flag->entity_type)) {
if (isset($entity_info['module'])) {
$module = $entity_info['module'];
}
}
}
if (!empty($module)) {
$export['dependencies'][$module] = $module;
}
$export['features']['flag'][$flag->name] = $flag->name;
}
}
return $pipe;
}
/**
* Implements hook_features_export_options().
*/
function flag_features_export_options() {
$options = array();
// Get all flags, including disabled defaults.
$flags = flag_get_flags() + flag_get_default_flags(TRUE);
foreach ($flags as $name => $flag) {
$options[$name] = drupal_ucfirst(check_plain($flag->entity_type)) . ': ' . check_plain($flag->title);
}
return $options;
}
/**
* Implements hook_features_export_render().
*/
function flag_features_export_render($module, $data) {
module_load_include('inc', 'flag', '/includes/flag.export');
$code = flag_export_flags($data, $module, ' ');
return array('flag_default_flags' => $code);
}
/**
* Implements hook_features_revert().
*
* @param $module
* The name of module for which to revert content.
*/
function flag_features_revert($module = NULL) {
// Get default flags from features.
if (module_hook($module, 'flag_default_flags')) {
module_load_include('inc', 'flag', '/includes/flag.admin');
$default_flags = module_invoke($module, 'flag_default_flags');
// Build up values for the cache clear.
$entity_types = array();
// Revert flags that are defined in code.
foreach ($default_flags as $flag_name => $flag_info) {
if (is_numeric($flag_name)) {
// Backward compatibility.
$flag_name = $flag_info['name'];
}
$flag = flag_load($flag_name, TRUE);
if ($flag && $flag->revert() === FALSE) {
drupal_set_message(t('Could not revert flag %flag-name to the state described in your code: Your flag was created by a different version of the Flag module than is now being used.', array('%flag-name' => $flag->name)), 'error');
}
$entity_types[] = $flag->entity_type;
}
_flag_clear_cache($entity_types);
}
}
/**
* Helper function; Retrieve the providing modules defining the flags.
*/
function flag_features_providing_module() {
$modules = array();
$hook = 'flag_type_info';
foreach (module_implements($hook) as $module) {
foreach (module_invoke($module, $hook) as $key => $value) {
$modules[$key] = isset($value['module']) ? $value['module'] : $module;
}
}
// Any entity type without a flag providing module will be provided by the
// flag module.
foreach (entity_get_info() as $entity_type => $entity) {
if (!isset($modules[$entity_type]) && empty($entity['configuration']) && $entity_type !== 'taxonomy_vocabulary') {
$modules[$entity_type] = 'flag';
}
}
return $modules;
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* @file
* Menu callbacks for the Flag module.
*/
/**
* Menu callback for (un)flagging a node.
*
* Used both for the regular callback as well as the JS version.
*
* @param $action
* Either 'flag' or 'unflag'.
*/
function flag_page($action, $flag, $entity_id) {
global $user;
// Shorten up the variables that affect the behavior of this page.
$js = isset($_REQUEST['js']);
$token = $_REQUEST['token'];
// Specifically $_GET to avoid getting the $_COOKIE variable by the same key.
$has_js = isset($_GET['has_js']);
// Check the flag token, and then javascript status.
if (!flag_check_token($token, $entity_id)) {
$flag->errors['token'] = t('Bad token. You seem to have followed an invalid link.');
}
elseif ($user->uid == 0 && !$has_js) {
$flag->errors['javascript'] = t('You must have JavaScript and cookies enabled in your browser to flag content.');
}
// If no errors have been detected thus far, perform the flagging.
// Further errors may still be detected during validation and prevent
// the operation from succeeding.
if (!$flag->errors) {
$flag->flag($action, $entity_id);
}
// If successful, return data according to the request type.
if ($js) {
drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');
$flag->link_type = 'toggle';
// Any errors that have been set will be output below
// the flag link with javascript.
print drupal_json_encode(flag_build_javascript_info($flag, $entity_id));
drupal_exit();
}
else {
$errors = $flag->get_errors();
if ($errors) {
// If an error was received, set a message and exit.
foreach ($errors as $error) {
drupal_set_message($error, 'error');
}
if (isset($errors['access-denied'])) {
return MENU_ACCESS_DENIED;
}
else {
drupal_goto();
}
}
else {
drupal_set_message($flag->get_label($action . '_message', $entity_id));
drupal_goto();
}
}
}
/**
* Form for confirming the (un)flagging of an entity.
*
* @param $action
* Either 'flag' or 'unflag'.
* @param $flag
* A loaded flag object.
* @param $entity_id
* The id of the entity to operate on. The type is implicit in the flag.
*
* @see flag_confirm_submit()
*/
function flag_confirm($form, &$form_state, $action, $flag, $entity_id) {
$form['#flag'] = $flag;
$form['action'] = array(
'#type' => 'value',
'#value' => $action,
);
$form['entity_id'] = array(
'#type' => 'value',
'#value' => $entity_id,
);
$question = $flag->get_label($action . '_confirmation', $entity_id);
$path = isset($_GET['destination']) ? $_GET['destination'] : '<front>';
$yes = $flag->get_label($action . '_short', $entity_id);
if ($action == 'flag') {
// If the action 'flag', we're potentially about to create a new
// flagging entity. We need an empty new entity to pass to FieldAPI.
$flagging = $flag->new_flagging($entity_id);
field_attach_form('flagging', $flagging, $form, $form_state);
$form['#flagging'] = $flagging;
}
return confirm_form($form, $question, $path, '', $yes);
}
/**
* Submit handler for the flag confirm form.
*
* Note that validating whether the user may perform the action is done here,
* rather than in a form validation handler.
*
* @see flag_confirm()
*/
function flag_confirm_submit(&$form, &$form_state) {
$flag = $form['#flag'];
$action = $form_state['values']['action'];
$entity_id = $form_state['values']['entity_id'];
if ($action == 'flag') {
// If the action 'flag', further build up the new entity from form values.
$flagging = $form['#flagging'];
entity_form_submit_build_entity('flagging', $flagging, $form, $form_state);
$result = $flag->flag($action, $entity_id, NULL, FALSE, $flagging);
}
else {
$result = $flag->flag($action, $entity_id, NULL, FALSE);
}
if (!$result) {
if ($errors = $flag->get_errors()) {
foreach ($errors as $error) {
drupal_set_message($error, 'error');
}
}
}
else {
drupal_set_message($flag->get_label($action . '_message', $entity_id));
}
}
/**
* Builds the JavaScript structure describing the flagging operation.
*/
function flag_build_javascript_info($flag, $entity_id) {
$errors = $flag->get_errors();
$info = array(
'status' => TRUE,
'newLink' => $flag->theme($flag->is_flagged($entity_id) ? 'unflag' : 'flag', $entity_id, array(
'after_flagging' => TRUE,
'errors' => $errors,
)),
// Further information for the benefit of custom JavaScript event handlers:
'flagSuccess' => !$errors,
'contentId' => $entity_id,
'entityType' => $flag->entity_type,
'flagName' => $flag->name,
'flagStatus' => $flag->is_flagged($entity_id) ? 'flagged' : 'unflagged',
);
drupal_alter('flag_javascript_info', $info);
return $info;
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* @file
* Contains the flag_comment class.
*/
/**
* Implements a comment flag.
*/
class flag_comment extends flag_entity {
function options() {
$options = parent::options();
$options += array(
'access_author' => '',
);
return $options;
}
/**
* Options form extras for comment flags.
*/
function options_form(&$form) {
parent::options_form($form);
$form['access']['access_author'] = array(
'#type' => 'radios',
'#title' => t('Flag access by content authorship'),
'#options' => array(
'' => t('No additional restrictions'),
'comment_own' => t('Users may only flag own comments'),
'comment_others' => t('Users may only flag comments by others'),
'node_own' => t('Users may only flag comments of nodes they own'),
'node_others' => t('Users may only flag comments of nodes by others'),
),
'#default_value' => $this->access_author,
'#description' => t("Restrict access to this flag based on the user's ownership of the content. Users must also have access to the flag through the role settings."),
);
}
function type_access_multiple($entity_ids, $account) {
$access = array();
// If all subtypes are allowed, we have nothing to say here.
if (empty($this->types)) {
return $access;
}
// Ensure node types are granted access. This avoids a
// node_load() on every type, usually done by applies_to_entity_id().
$query = db_select('comment', 'c');
$query->innerJoin('node', 'n', 'c.nid = n.nid');
$result = $query
->fields('c', array('cid'))
->condition('c.cid', $entity_ids, 'IN')
->condition('n.type', $this->types, 'NOT IN')
->execute();
foreach ($result as $row) {
$access[$row->nid] = FALSE;
}
return $access;
}
function get_entity_id($comment) {
// Store the comment object in the static cache, to avoid getting it
// again unneedlessly.
$this->remember_entity($comment->cid, $comment);
return $comment->cid;
}
function get_labels_token_types() {
return array_merge(array('comment', 'node'), parent::get_labels_token_types());
}
function replace_tokens($label, $contexts, $options, $entity_id) {
if ($entity_id) {
if (($comment = $this->fetch_entity($entity_id)) && ($node = node_load($comment->nid))) {
$contexts['node'] = $node;
$contexts['comment'] = $comment;
}
}
return parent::replace_tokens($label, $contexts, $options, $entity_id);
}
function get_flag_action($entity_id) {
$flag_action = parent::get_flag_action($entity_id);
$comment = $this->fetch_entity($entity_id);
$flag_action->content_title = $comment->subject;
$flag_action->content_url = $this->_flag_url("comment/$comment->cid", "comment-$comment->cid");
return $flag_action;
}
function get_relevant_action_objects($entity_id) {
$comment = $this->fetch_entity($entity_id);
return array(
'comment' => $comment,
'node' => node_load($comment->nid),
);
}
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* @file
* Contains the flag_entity class.
*/
/**
* Base entity flag handler.
*/
class flag_entity extends flag_flag {
/**
* Adds additional options that are common for all entity types.
*/
function options() {
$options = parent::options();
$options += array(
// Output the flag in the entity links.
// This is empty for now and will get overriden for different
// entities.
// @see hook_entity_view().
'show_in_links' => array(),
// Output the flag as individual pseudofields.
'show_as_field' => FALSE,
// Add a checkbox for the flag in the entity form.
// @see hook_field_attach_form().
'show_on_form' => FALSE,
'access_author' => '',
'show_contextual_link' => FALSE,
);
return $options;
}
/**
* Options form extras for the generic entity flag.
*/
function options_form(&$form) {
$bundles = array();
$entity_info = entity_get_info($this->entity_type);
foreach ($entity_info['bundles'] as $bundle_key => $bundle) {
$bundles[$bundle_key] = check_plain($bundle['label']);
}
$form['access']['types'] = array(
'#type' => 'checkboxes',
'#title' => t('Bundles'),
'#options' => $bundles,
'#description' => t('Select the bundles that this flag may be used on. Leave blank to allow on all bundles for the entity type.'),
'#default_value' => $this->types,
);
// Add checkboxes to show flag link on each entity view mode.
$options = array();
$defaults = array();
foreach ($entity_info['view modes'] as $name => $view_mode) {
$options[$name] = t('Display on @name view mode', array('@name' => $view_mode['label']));
$defaults[$name] = !empty($this->show_in_links[$name]) ? $name : 0;
}
$form['display']['show_in_links'] = array(
'#type' => 'checkboxes',
'#title' => t('Display in entity links'),
'#description' => t('Show the flag link with the other links on the entity.'),
'#options' => $options,
'#default_value' => $defaults,
);
$form['display']['show_as_field'] = array(
'#type' => 'checkbox',
'#title' => t('Display link as field'),
'#description' => t('Show the flag link as a pseudofield, which can be ordered among other entity elements in the "Manage display" settings for the entity type.'),
'#default_value' => isset($this->show_as_field) ? $this->show_as_field : TRUE,
);
if (empty($entity_info['fieldable'])) {
$form['display']['show_as_field']['#disabled'] = TRUE;
$form['display']['show_as_field']['#description'] = t("This entity type is not fieldable.");
}
$form['display']['show_on_form'] = array(
'#type' => 'checkbox',
'#title' => t('Display checkbox on entity edit form'),
'#default_value' => $this->show_on_form,
'#weight' => 5,
);
// We use FieldAPI to put the flag checkbox on the entity form, so therefore
// require the entity to be fielable. Since this is a potential DX
// headscratcher for a developer wondering where this option has gone,
// we disable it and explain why.
if (empty($entity_info['fieldable'])) {
$form['display']['show_on_form']['#disabled'] = TRUE;
$form['display']['show_on_form']['#description'] = t('This is only possible on entities which are fieldable.');
}
$form['display']['show_contextual_link'] = array(
'#type' => 'checkbox',
'#title' => t('Display in contextual links'),
'#default_value' => $this->show_contextual_link,
'#description' => t('Note that not all entity types support contextual links.'),
'#access' => module_exists('contextual'),
'#weight' => 10,
);
}
/**
* Loads the entity object.
*/
function _load_entity($entity_id) {
if (is_numeric($entity_id)) {
$entity = entity_load($this->entity_type, array($entity_id));
return reset($entity);
}
return NULL;
}
/**
* Checks whether the flag applies for the current entity bundle.
*/
function applies_to_entity($entity) {
$entity_info = entity_get_info($this->entity_type);
// The following conditions are applied:
// - if the types array is empty, the flag applies to all bundles and thus
// to this entity.
// - if the entity has no bundles, the flag applies to the entity.
// - if the entity's bundle is in the list of types.
if (empty($this->types) || empty($entity_info['entity keys']['bundle']) || in_array($entity->{$entity_info['entity keys']['bundle']}, $this->types)) {
return TRUE;
}
return FALSE;
}
/**
* Provides permissions for this flag.
*
* @return
* An array of permissions for hook_permission().
*/
function get_permissions() {
// For entity flags, use the human label of the entity.
$entity_info = entity_get_info($this->entity_type);
$entity_label = $entity_info['label'];
return array(
"flag $this->name" => array(
'title' => t('Flag %entity entities as %flag_title', array(
'%flag_title' => $this->title,
'%entity' => $entity_label,
)),
),
"unflag $this->name" => array(
'title' => t('Unflag %entity entities as %flag_title', array(
'%flag_title' => $this->title,
'%entity' => $entity_label,
)),
),
);
}
/**
* Returns the entity id, if it already exists.
*/
function get_entity_id($entity) {
$entity_info = entity_get_info($this->entity_type);
if ($entity && isset($entity->{$entity_info['entity keys']['id']})) {
return $entity->{$entity_info['entity keys']['id']};
}
}
/**
* Determine whether the flag should show a flag link in entity links.
*/
function shows_in_entity_links($view_mode) {
// Check for settings for the given view mode.
if (isset($this->show_in_links[$view_mode])) {
return (bool) $this->show_in_links[$view_mode];
}
return FALSE;
}
/**
* Returns token types for the current entity type.
*/
function get_labels_token_types() {
// The token type name might be different to the entity type name. If so,
// an own flag entity handler can be used for overriding this.
return array_merge(array($this->entity_type), parent::get_labels_token_types());
}
/**
* Replaces tokens.
*/
function replace_tokens($label, $contexts, $options, $entity_id) {
if ($entity_id && ($entity = $this->fetch_entity($entity_id))) {
$contexts[$this->entity_type] = $entity;
}
return parent::replace_tokens($label, $contexts, $options, $entity_id);
}
/**
* Returns a 'flag action' object.
*/
function get_flag_action($entity_id) {
$flag_action = parent::get_flag_action($entity_id);
$entity = $this->fetch_entity($entity_id);
$flag_action->content_title = entity_label($this->entity_type, $entity);
$flag_action->content_url = $this->_flag_url($this->entity_type . '/' . $this->get_entity_id($entity));
return $flag_action;
}
/**
* Returns objects the action may possible need.
*/
function get_relevant_action_objects($entity_id) {
return array(
$this->entity_type => $this->fetch_entity($entity_id),
);
}
/**
* Returns information for the Views integration.
*/
function get_views_info() {
$entity_info = entity_get_info($this->entity_type);
if (!isset($entity_info['base table'])) {
return NULL;
}
return array(
'views table' => $entity_info['base table'],
'join field' => $entity_info['entity keys']['id'],
'title field' => isset($entity_info['entity keys']['label']) ? $entity_info['entity keys']['label'] : '',
'title' => t('@entity_label flag', array('@entity_label' => $entity_info['label'])),
'help' => t('Limit results to only those entity flagged by a certain flag; Or display information about the flag set on a entity.'),
'counter title' => t('@entity_label flag counter', array('@entity_label' => $entity_info['label'])),
'counter help' => t('Include this to gain access to the flag counter field.'),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
<?php
/**
* @file
* Contains the flag_node class.
*/
/**
* Implements a node flag.
*/
class flag_node extends flag_entity {
function options() {
$options = parent::options();
// Use own display settings in the meanwhile.
$options += array(
'i18n' => 0,
);
return $options;
}
/**
* Options form extras for node flags.
*/
function options_form(&$form) {
parent::options_form($form);
$form['access']['access_author'] = array(
'#type' => 'radios',
'#title' => t('Flag access by content authorship'),
'#options' => array(
'' => t('No additional restrictions'),
'own' => t('Users may only flag content they own'),
'others' => t('Users may only flag content of others'),
),
'#default_value' => $this->access_author,
'#description' => t("Restrict access to this flag based on the user's ownership of the content. Users must also have access to the flag through the role settings."),
);
// Support for i18n flagging requires Translation helpers module.
$form['i18n'] = array(
'#type' => 'radios',
'#title' => t('Internationalization'),
'#options' => array(
'1' => t('Flag translations of content as a group'),
'0' => t('Flag each translation of content separately'),
),
'#default_value' => $this->i18n,
'#description' => t('Flagging translations as a group effectively allows users to flag the original piece of content regardless of the translation they are viewing. Changing this setting will <strong>not</strong> update content that has been flagged already.'),
'#access' => module_exists('translation_helpers'),
'#weight' => 5,
);
// Override the UI texts for nodes.
$form['display']['show_on_form'] = array(
'#title' => t('Display checkbox on node edit form'),
'#description' => t('If you elect to have a checkbox on the node edit form, you may specify its initial state in the settings form <a href="@content-types-url">for each content type</a>.', array('@content-types-url' => url('admin/structure/types'))),
) + $form['display']['show_on_form'];
}
function type_access_multiple($entity_ids, $account) {
$access = array();
// If all subtypes are allowed, we have nothing to say here.
if (empty($this->types)) {
return $access;
}
// Ensure that only flaggable node types are granted access. This avoids a
// node_load() on every type, usually done by applies_to_entity_id().
$result = db_select('node', 'n')->fields('n', array('nid'))
->condition('nid', array_keys($entity_ids), 'IN')
->condition('type', $this->types, 'NOT IN')
->execute();
foreach ($result as $row) {
$access[$row->nid] = FALSE;
}
return $access;
}
/**
* Adjust the Content ID to find the translation parent if i18n-enabled.
*
* @param $entity_id
* The nid for the content.
* @return
* The tnid if available, the nid otherwise.
*/
function get_translation_id($entity_id) {
if ($this->i18n) {
$node = $this->fetch_entity($entity_id);
if (!empty($node->tnid)) {
$entity_id = $node->tnid;
}
}
return $entity_id;
}
function flag($action, $entity_id, $account = NULL, $skip_permission_check = FALSE, $flagging = NULL) {
$entity_id = $this->get_translation_id($entity_id);
return parent::flag($action, $entity_id, $account, $skip_permission_check, $flagging);
}
// Instead of overriding is_flagged() we override get_flagging_record(),
// which is the underlying method.
function get_flagging_record($entity_id, $uid = NULL, $sid = NULL) {
$entity_id = $this->get_translation_id($entity_id);
return parent::get_flagging_record($entity_id, $uid, $sid);
}
/**
* This is overridden for no other purpose than to document that $entity_id
* can be one of the following fake IDs in certain contexts:
* - 'new': On a new node form.
* - 'fake': On the node type admin form.
*/
function replace_tokens($label, $contexts, $options, $entity_id) {
return parent::replace_tokens($label, $contexts, $options, $entity_id);
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* @file
* Contains the flag_user class.
*/
/**
* Implements a user flag.
*/
class flag_user extends flag_entity {
function options() {
$options = parent::options();
$options += array(
'show_on_profile' => TRUE,
'access_uid' => '',
);
return $options;
}
/**
* Options form extras for user flags.
*/
function options_form(&$form) {
parent::options_form($form);
$form['access']['types'] = array(
// A user flag doesn't support node types.
// TODO: Maybe support roles instead of node types.
'#type' => 'value',
'#value' => array(0 => 0),
);
$form['access']['access_uid'] = array(
'#type' => 'checkbox',
'#title' => t('Users may flag themselves'),
'#description' => t('Disabling this option may be useful when setting up a "friend" flag, when a user flagging themself does not make sense.'),
'#default_value' => $this->access_uid ? 0 : 1,
);
$form['display']['show_on_profile'] = array(
'#type' => 'checkbox',
'#title' => t('Display link on user profile page'),
'#description' => t('Show the link formatted as a user profile element.'),
'#default_value' => $this->show_on_profile,
// Put this above 'show on entity'.
'#weight' => -1,
);
}
function form_input($form_values) {
parent::form_input($form_values);
// The access_uid value is intentionally backwards from the UI, to avoid
// confusion caused by checking a box to disable a feature.
$this->access_uid = empty($form_values['access_uid']) ? 'others' : '';
}
function type_access($entity_id, $action, $account) {
// Prevent users from flagging themselves.
if ($this->access_uid == 'others' && $entity_id == $account->uid) {
return FALSE;
}
}
function type_access_multiple($entity_ids, $account) {
$access = array();
// Exclude anonymous.
if (array_key_exists(0, $entity_ids)) {
$access[0] = FALSE;
}
// Prevent users from flagging themselves.
if ($this->access_uid == 'others' && array_key_exists($account->uid, $entity_ids)) {
$access[$account->uid] = FALSE;
}
return $access;
}
function get_flag_action($entity_id) {
$flag_action = parent::get_flag_action($entity_id);
$user = $this->fetch_entity($entity_id);
$flag_action->content_title = $user->name;
$flag_action->content_url = $this->_flag_url('user/' . $user->uid);
return $flag_action;
}
function get_relevant_action_objects($entity_id) {
return array(
'user' => $this->fetch_entity($entity_id),
);
}
function get_views_info() {
$views_info = parent::get_views_info();
$views_info['title field'] = 'name';
return $views_info;
}
}

View File

@@ -0,0 +1,272 @@
<?php
/**
* @file
* Provides support for the Views module.
*/
/**
* Implements hook_views_plugins().
*/
function flag_views_plugins() {
return array(
'argument validator' => array(
'flag_flaggable_node' => array(
'title' => t('Flaggable node'),
'flag type' => 'node',
'handler' => 'flag_plugin_argument_validate_flaggability',
'path' => drupal_get_path('module', 'flag') . '/includes',
),
'flag_flaggable_user' => array(
'title' => t('Flaggable user'),
'flag type' => 'user',
'handler' => 'flag_plugin_argument_validate_flaggability',
'path' => drupal_get_path('module', 'flag') . '/includes',
),
// A comment validator won't be very useful. Moreover, having it will
// contribute to the argument object's $options polution, so let's skip
// it.
),
);
}
/**
* Implements hook_views_data().
*/
function flag_views_data() {
$data = array();
$data['flagging']['table']['group'] = t('Flags');
$data['flag_counts']['table']['group'] = t('Flags');
// Notify views from flag 2.x of our changes to views data.
// @see flag_update_7301().
$data['flag_content']['moved to'] = 'flagging';
// @see flag_update_7303().
$data['flag_content']['content_id']['moved to'] = array('flagging', 'entity_id');
$data['flagging']['uid'] = array(
'title' => t('User uid'),
'help' => t('The user that flagged an item. If you need more fields than the uid add the "Flags: User" relationship.'),
'relationship' => array(
'base' => 'users',
'title' => t('User'),
'help' => t('Relate an item to the user that flagged it.'),
'handler' => 'views_handler_relationship',
'label' => t('Flag user'),
),
'filter' => array(
'handler' => 'views_handler_filter_user_name',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
'field' => array(
'handler' => 'views_handler_field_user',
),
);
$data['flagging']['timestamp'] = array(
'title' => t('Flagged time'),
'help' => t('Display the time the content was flagged by a user.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
'argument' => array(
'handler' => 'views_handler_argument_date',
),
);
// Argument for content ID, used for "Who's flagged this" views.
$data['flagging']['entity_id'] = array(
'title' => t('Content ID'),
'help' => t('The unique ID of the object that has been flagged.'),
'argument' => array(
'handler' => 'flag_handler_argument_entity_id',
),
);
// Specialized is null/is not null filter.
$data['flagging']['flagged'] = array(
'title' => t('Flagged'),
'real field' => 'uid',
'field' => array(
'handler' => 'flag_handler_field_flagged',
'label' => t('Flagged'),
'help' => t('A boolean field to show whether the flag is set or not.'),
),
'filter' => array(
'handler' => 'flag_handler_filter_flagged',
'label' => t('Flagged'),
'help' => t('Filter to ensure content has or has not been flagged.'),
),
'sort' => array(
'handler' => 'flag_handler_sort_flagged',
'label' => t('Flagged'),
'help' => t('Sort by whether entities have or have not been flagged.'),
),
);
// Flag content links.
$data['flagging']['ops'] = array(
'title' => t('Flag link'),
'help' => t('Display flag/unflag link.'),
'field' => array(
'handler' => 'flag_handler_field_ops',
),
);
$data['flag_counts']['count'] = array(
'title' => t('Flag counter'),
'help' => t('The number of times a piece of content is flagged by any user.'),
'field' => array(
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
);
$data['flag_counts']['last_updated'] = array(
'title' => t('Time last flagged'),
'help' => t('The time a piece of content was most recently flagged by any user.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
'argument' => array(
'handler' => 'views_handler_argument_date',
),
);
return $data;
}
/**
* Implements hook_views_data_alter().
*/
function flag_views_data_alter(&$data) {
foreach (array_keys(flag_fetch_definition()) as $flag_type) {
$flag = flag_flag::factory_by_entity_type($flag_type);
$info = $flag->get_views_info();
if (!isset($info)) {
continue;
}
if (!empty($info['join field'])) {
// Add the flag relationship.
$data[$info['views table']]['flag_content_rel'] = array(
'group' => t('Flags'),
'title' => $info['title'],
'help' => $info['help'],
'relationship' => array(
'flag type' => $flag_type,
'handler' => 'flag_handler_relationship_content',
'label' => t('flag'),
'base' => 'flagging',
'base field' => 'entity_id',
'relationship field' => $info['join field'],
),
);
// Add the flag counter relationship.
$data[$info['views table']]['flag_count_rel'] = array(
'group' => t('Flags'),
'title' => $info['counter title'],
'help' => $info['counter help'],
'relationship' => array(
'flag type' => $flag_type,
'handler' => 'flag_handler_relationship_counts',
'label' => t('counter'),
'base' => 'flag_counts',
'base field' => 'entity_id',
'relationship field' => $info['join field'],
),
);
}
}
// Add a relationship for the user that flagged any type of content.
$data['users']['flag_user_content_rel'] = array(
'group' => t('Flags'),
'title' => t("User's flaggings"),
'help' => t('Relate users to the flaggings they have made on objects, using a particular flag.'),
'relationship' => array(
'base' => 'flagging',
'base field' => 'uid',
'relationship field' => 'uid',
'handler' => 'flag_handler_relationship_user_content',
'label' => t('user flagged content'),
),
);
}
/**
* Implements hook_views_query_substitutions().
*
* Allow replacement of current user's session id so we can cache these queries.
*/
function flag_views_query_substitutions() {
return array(
'***FLAG_CURRENT_USER_SID***' => flag_get_sid(),
);
}
/**
* A helper function that creates a radio list of available flags.
*
* This function is used to select the desired flag when setting up flag
* relationships and fields.
*/
function flag_views_flag_config_form($form_type, $entity_type, $current_flag) {
$flags = flag_get_flags($entity_type);
$options = array();
foreach ($flags as $flag) {
$options[$flag->name] = $flag->get_title();
}
$form = array(
'#type' => $form_type,
'#title' => t('Flag'),
'#options' => $options,
'#default_value' => $current_flag,
'#required' => TRUE,
);
return $form;
}
/**
* Helper function that gets the first defined flag and returns its name.
*/
function flag_views_flag_default($entity_type) {
$default_flag = &drupal_static(__FUNCTION__, array());
if (!array_key_exists($entity_type, $default_flag)) {
$flag = array_shift(flag_get_flags($entity_type));
$default_flag[$entity_type] = $flag ? $flag->name : NULL;
}
return $default_flag[$entity_type];
}

View File

@@ -0,0 +1,235 @@
<?php
/**
* @file
* Update Views 1 views provided by both views_bookmark and flag modules.
*/
/**
* Implements hook_views_convert().
*
* Intervene to convert field values from the Views 1 format to the
* Views 2 format. Intervene only if $view->add_item() won't produce
* the right results, usually needed to set field options or values.
*/
function flag_views_convert($display, $type, &$view, $field, $id) {
static $flag_name;
// First, replace any sign of views_bookmark with flag's Views 1 equivelant.
$key_search = array(
'^views_bookmark_ops_([0-9]+)' => 'flag_ops_',
'^views_bookmark_nodes_([0-9]+)' => 'flag_content_',
'^views_bookmark_users_([0-9]+)' => 'flag_users_',
'^views_bookmark_node_count_([0-9]+)' => 'flag_counts_',
);
foreach ($field as $property => $value) {
foreach ($key_search as $search => $replace) {
if (!empty($value) && is_string($value) && preg_match('/' . $search . '/', $value, $matches)) {
$flag = flag_get_flag(NULL, $matches[1]);
$replace = $replace . $flag->name;
$field[$property] = preg_replace('/' . $search . '/', $replace, $value);
}
}
}
// Create a table/field identifier for this field.
foreach (array('flag_ops_', 'flag_content_', 'flag_users_', 'flag_counts_') as $table) {
if (strpos($field['tablename'], $table) === 0) {
$name = str_replace($table, '', $field['tablename']);
if (!empty($name) && !isset($flag_name)) {
$flag_name = $name;
}
}
}
// Now update values, options, etc. to those selected in the imported view.
switch ($type) {
case 'field':
switch ($id) {
case 'ops':
$new_field = array(
'label' => $field['label'],
'id' => 'ops',
'table' => 'flagging',
'field' => 'ops',
'relationship' => 'flag_content_rel',
);
$new_rel = 'flag_content_rel';
$flag_content_rel_user = 'current';
break;
case 'count':
$new_field = array(
'label' => $field['label'],
'id' => 'count',
'table' => 'flag_counts',
'field' => 'count',
'relationship' => 'flag_counts_rel',
);
$new_rel = 'flag_counts_rel';
break;
case 'name':
$new_field = array(
'label' => $field['label'],
'link_to_user' => 1,
'id' => 'name',
'table' => 'users',
'field' => 'name',
'relationship' => 'uid',
);
$new_rel = 'uid';
break;
}
break;
case 'filter':
case 'exposed_filter':
switch ($id) {
case 'uid':
// The flagging uid filter means "Include content only flagged by
// the current user". In D6, this is no longer a filter, but instead
// part of the relationship. Make the relationship join on the uid.
if ($field['value'] == '***CURRENT_USER***') {
$new_rel = 'flag_content_rel';
$flag_content_rel_user = 'current';
}
// Remove the old filter.
$view->set_item('default', $type, $id, NULL);
break;
case 'timestamp':
$new_field = array(
'operator' => $field['operator'],
'value' => flag_views_convert_timestamp_value($field['value']),
'group' => 0,
'id' => 'timestamp',
'table' => 'flagging',
'field' => 'timestamp',
'relationship' => 'flag_content_rel',
'exposed' => $type == 'exposed_filter' ? 1 : 0,
);
$new_rel = 'flag_content_rel';
drupal_set_message(t('Flag is not able to convert the <em>Flagged time</em> filter. It\'s value is currently empty, but needs to be populated to work properly.'), 'warning');
break;
case 'count':
$new_field = array(
'operator' => $field['operator'],
'value' => array('value' => $field['value']),
'group' => 0,
'id' => 'count',
'table' => 'flag_counts',
'field' => 'count',
'relationship' => 'flag_counts_rel',
'exposed' => $type == 'exposed_filter' ? 1 : 0,
);
$new_rel = 'flag_counts_rel';
break;
}
break;
case 'argument':
// Flag in Drupal 5 only provides one argument, and in Views 1 arguments
// weren't given and ID, so we use the field type.
if (strpos($field['type'], 'flag_content_') === 0) {
$new_field = array(
'id' => 'uid',
'table' => 'users',
'field' => 'uid',
'relationship' => 'uid',
);
$new_rel = 'uid';
}
break;
case 'sort':
switch ($id) {
case 'count':
$new_field = array(
'order' => $field['sortorder'],
'id' => 'count',
'table' => 'flag_counts',
'field' => 'count',
'relationship' => 'flag_counts_rel',
);
$new_rel = 'flag_counts_rel';
break;
case 'timestamp':
$new_field = array(
'order' => $field['sortorder'],
'id' => 'timestamp',
'table' => 'flagging',
'field' => 'timestamp',
'relationship' => 'flag_content_rel',
);
$new_rel = 'flag_content_rel';
break;
}
break;
}
// Add any new fields.
if (isset($new_field)) {
// Exposed filters are now wrapped into filters.
$type = $type == 'exposed_filter' ? 'filter' : $type;
// Update the type in the view options.
$view->set_item('default', $type, $id, $new_field);
}
// Add any new relationships (but only once per view).
if (isset($new_rel) && !isset($view->display[$display]->display_options['relationships'][$new_rel])) {
if ($new_rel == 'flag_content_rel') {
$view->display[$display]->display_options['relationships'][$new_rel] = array(
'label' => $flag_name,
'required' => 1,
'flag' => $flag_name,
'user_scope' => 'any',
'id' => 'flag_content_rel',
'table' => 'node',
'field' => 'flag_content_rel',
'relationship' => 'none',
);
}
elseif ($new_rel == 'flag_counts_rel') {
$view->display[$display]->display_options['relationships'][$new_rel] = array(
'label' => $flag_name . ' counts',
'required' => 0,
'flag' => $flag_name,
'id' => 'flag_counts_rel',
'table' => 'node',
'field' => 'flag_counts_rel',
'relationship' => 'none',
);
}
elseif ($new_rel == 'uid') {
$view->display[$display]->display_options['relationships'][$new_rel] = array(
'label' => isset($flag_name) ? $flag_name . ' user' : 'user',
'required' => 0,
'id' => 'uid',
'table' => 'flagging',
'field' => 'uid',
'relationship' => 'flag_content_rel',
);
}
}
// Modify relationships if needed.
if (isset($flag_content_rel_user) && isset($view->display[$display]->display_options['relationships']['flag_content_rel'])) {
$view->display[$display]->display_options['relationships']['flag_content_rel']['user_scope'] = 'current';
}
}
/**
* In Views 1, dates were simply stored as a single string. In Views 2, the
* value is stored as an array of options.
*/
function flag_views_convert_timestamp_value($value) {
$type = 'date';
if (stripos($value, 'now') !== FALSE) {
$type = 'offset';
$value = trim(str_ireplace('now', '', $value));
}
return array(
'type' => $type,
'value' => $value,
'min' => '',
'max' => '',
);
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains the content ID argument handler.
*/
/**
* Handler to accept an argument of the content ID of any object.
*
* @ingroup views
*/
class flag_handler_argument_entity_id extends views_handler_argument_numeric {
/**
* Returns the flag object associated with our argument.
*
* An argument is in some relationship. This function reaches out for this
* relationship and reads its 'flag' option, which holds the flag name.
*/
function get_flag() {
return $this->view->relationship[$this->options['relationship']]->get_flag();
}
/**
* Override the behavior of title(). Get the title of the appropriate objects.
*/
function title_query() {
if (!($flag = $this->get_flag())) {
return array(); // Error message is printed by get_flag().
}
$views_info = $flag->get_views_info();
$titles = array();
$placeholders = implode(', ', array_fill(0, sizeof($this->value), '%d'));
$result = db_select($views_info['views table'], 'o')
->fields('o', array($views_info['title field']))
->condition('o.' . $views_info['join field'], $this->value, 'IN')
->execute();
foreach ($result as $title) {
$titles[] = check_plain($title->$views_info['title field']);
}
return $titles;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains the boolean flagged field handler.
*/
/**
* Views field handler for the flagged field.
*
* @ingroup views
*/
class flag_handler_field_flagged extends views_handler_field_boolean {
function init(&$view, &$options) {
parent::init($view, $options);
// Add our boolean labels.
$this->formats['flag'] = array(t('Flagged'), t('Not flagged'));
// TODO: We could probably lift the '(Un)Flagged message' strings from the
// flag object, but a) we need to lift that from the relationship we're on
// and b) they will not necessarily make sense in a static context.
}
/**
* Called to add the field to a query.
*/
function query() {
$this->ensure_my_table();
// Add the formula.
$this->field_alias = $this->query->add_field(NULL, "($this->table_alias.uid IS NOT NULL)", 'flagging_flagged');
$this->add_additional_fields();
}
}

View File

@@ -0,0 +1,163 @@
<?php
/**
* @file
* Contains the flag Ops field handler.
*/
/**
* Views field handler for the Flag operations links (flag/unflag).
*
* @ingroup views
*/
class flag_handler_field_ops extends views_handler_field {
/**
* Returns the flag object associated with our field.
*
* A field is in some relationship. This function reaches out for this
* relationship and reads its 'flag' option, which holds the flag name.
*/
function get_flag() {
// When editing a view it's possible to delete the relationship (either by
// error or to later recreate it), so we have to guard against a missing
// one.
if (isset($this->view->relationship[$this->options['relationship']])) {
return $this->view->relationship[$this->options['relationship']]->get_flag();
}
}
/**
* Return the the relationship we're linked to. That is, the alias for its
* table (which is suitbale for use with the various methods of the 'query'
* object).
*/
function get_parent_relationship() {
$parent = $this->view->relationship[$this->options['relationship']]->options['relationship'];
if (!$parent || $parent == 'none') {
return NULL; // Base query table.
}
else {
return $this->view->relationship[$parent]->alias;
}
}
function option_definition() {
$options = parent::option_definition();
$options['link_type'] = array('default' => '');
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['link_type'] = array(
'#type' => 'radios',
'#title' => t('Link type'),
'#options' => array('' => t('Use flag link settings')) + _flag_link_type_options(),
'#default_value' => $this->options['link_type'],
);
}
/**
* Override base ::query(). The purpose here is to make it possible for the
* render() method to know two things: what's the content ID, and whether
* it's flagged.
*/
function query() {
if (!($flag = $this->get_flag())) {
return; // Error message is printed in render().
}
$info = $flag->get_views_info();
$parent = $this->get_parent_relationship();
// Find out if the content is flagged. We can't just peek at some field in
// our loaded table because it doesn't always reflect the user browsing the
// page. So we explicitly add the flagging table to find this out.
// If the relationship is created with 'Current User' checked, we probably
// already have the appropriate join. We just need the appropriate table
// alias for that join. We look in the relationship settings to see if
// user_scope is set to 'current' to determine this.
$relationship = $this->view->relationship[$this->options['relationship']];
if (isset($relationship->options['user_scope']) && $relationship->options['user_scope'] == 'current') {
$table_alias = $relationship->alias;
}
// Otherwise, let's set up the alias, keeping it unique for this flag in
// case there are multiple flag relationships in a single view.
else {
$table_alias = 'flagging_current_user_' . $flag->fid;
}
// Now that we have the table alias, let's see if it's already been joined
// to. If it hasn't, we'll set up a join.
if (!isset($this->query->table_queue[$table_alias])) {
$join = new views_join();
$join->construct('flagging', $info['views table'], $info['join field'], 'entity_id');
$join->extra[] = array(
'field' => 'fid',
'value' => $flag->fid,
'numeric' => TRUE,
);
if (!$flag->global) {
$join->extra[] = array(
'field' => 'uid',
'value' => '***CURRENT_USER***',
'numeric' => TRUE,
);
$join->extra[] = array(
'field' => 'sid',
'value' => '***FLAG_CURRENT_USER_SID***',
'numeric' => TRUE,
);
}
$table_alias = $this->query->add_table($table_alias, $parent, $join);
}
$this->aliases['is_flagged'] = $this->query->add_field($table_alias, 'entity_id');
// Next, find out the content ID. We can't add_field() on this table
// (flagging), because its entity_id may be NULL (in case no user has
// flagged this content, and it's a LEFT JOIN). So we reach to the parent
// relationship and add_field() *its* content ID column.
$left_table = $this->view->relationship[$this->options['relationship']]->table_alias;
$this->aliases['entity_id'] = $this->query->add_field($left_table, $info['join field']);
}
/**
* Find out if the flag applies to each item seen on the page. It's done in a
* separate DB query to to avoid complexity and to make 'many to one' tests
* (e.g. checking user roles) possible without causing duplicate rows.
*/
function pre_render(&$values) {
if (!($flag = $this->get_flag())) {
return; // Error message is printed in render().
}
$ids = array();
foreach ($values as $row) {
$entity_id = $row->{$this->aliases['entity_id']};
$is_flagged = $row->{$this->aliases['is_flagged']};
if (isset($entity_id)) {
$ids[$entity_id] = $is_flagged ? 'unflag' : 'flag';
}
}
$this->flag_applies = $ids ? $flag->access_multiple($ids) : array();
}
function render($values) {
if (!($flag = $this->get_flag())) {
return t('Missing flag'); // get_flag() itself will print a more detailed message.
}
$entity_id = $values->{$this->aliases['entity_id']};
$is_flagged = $values->{$this->aliases['is_flagged']};
if (empty($this->flag_applies[$entity_id])) {
// Flag does not apply to this content.
return;
}
if (!empty($this->options['link_type'])) {
$flag->link_type = $this->options['link_type'];
}
return $flag->theme($is_flagged ? 'unflag' : 'flag', $entity_id);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains the flagged content filter handler.
*/
/**
* Handler to filter for content that has not been flagged.
*
* @ingroup views
*/
class flag_handler_filter_flagged extends views_handler_filter_boolean_operator {
function option_definition() {
$options = parent::option_definition();
$options['value'] = array('default' => 1);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['value']['#type'] = 'radios';
$form['value']['#title'] = t('Status');
$form['value']['#options'] = array(1 => t('Flagged'), 0 => t('Not flagged'), 'All' => t('All'));
$form['value']['#default_value'] = empty($this->options['value']) ? '0' : $this->options['value'];
$form['value']['#description'] = '<p>' . t('This filter is only needed if the relationship used has the "Include only flagged content" option <strong>unchecked</strong>. Otherwise, this filter is useless, because all records are already limited to flagged content.') . '</p><p>' . t('By choosing <em>Not flagged</em>, it is possible to create a list of content <a href="@unflagged-url">that is specifically not flagged</a>.', array('@unflagged-url' => 'http://drupal.org/node/299335')) . '</p>';
}
function query() {
$operator = $this->value ? 'IS NOT' : 'IS';
$this->query->add_where($this->options['group'], $this->relationship . '.uid', NULL, $operator . ' NULL');
}
}

View File

@@ -0,0 +1,267 @@
<?php
/**
* @file
* Contains various relationship handlers.
*/
/**
* Base class for all our relationship classes.
*
* @ingroup views
*/
abstract class flag_handler_relationship extends views_handler_relationship {
/**
* Every relationship has a 'flag' option.
*/
function option_definition() {
$options = parent::option_definition();
$options['flag'] = array('default' => NULL);
$options['required'] = array('default' => 1);
return $options;
}
/**
* Make sure the flag exists.
*
* When importing views, or when deleting flags, inconsistent views may
* result. This validator is called by Views before saving or previewing a
* view.
*/
function validate() {
$errors = array();
$tokens = array(
'@relationship-name' => $this->ui_name() . ' ' . $this->admin_summary(),
'@flag-name' => $this->options['flag'],
);
if (!$this->options['flag']) {
$errors[] = t('You must pick a flag to use for the relationship "@relationship-name".', $tokens);
}
elseif (!flag_get_flag($this->options['flag'])) {
$errors[] = t('This view is looking for a flag by the name "@flag-name", but there is no such flag. Perhaps it was deleted. Please update the relationship "@relationship-name" in this view to use an existing flag.', $tokens);
}
return $errors;
}
function get_flag_type() {
return isset($this->definition['flag type']) ? $this->definition['flag type'] : NULL;
}
/**
* Returns the flag object.
*/
function get_flag() {
// Backward compatibility: There may exist old views on the system whose
// 'flag' option isn't set. (This happens if the admin had skippped
// clicking the 'Update' button.) When run, these views should behave as
// if the first flag was selected.
if (!isset($this->options['flag'])) {
$this->options['flag'] = flag_views_flag_default($this->get_flag_type());
}
// Validation: Since validate() is called only when in Views's
// administrative UI, we need to do validation at "run time" ourselves.
if (($errors = $this->validate())) {
foreach ($errors as $error) {
drupal_set_message($error, 'error');
}
}
return flag_get_flag($this->options['flag']);
}
// @todo: It's logical that this class should also implement options_form(),
// to show the flag selector, and query(), to filter on the flag.
}
/**
* Specialized relationship handler associating flags and content.
*
* @ingroup views
*/
class flag_handler_relationship_content extends flag_handler_relationship {
function option_definition() {
$options = parent::option_definition();
$options['user_scope'] = array('default' => 'current');
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$entity_type = $this->definition['flag type'];
$form['label']['#description'] .= ' ' . t('The name of the selected flag makes a good label.');
$form['flag'] = flag_views_flag_config_form('radios', $entity_type, $this->options['flag']);
$form['user_scope'] = array(
'#type' => 'radios',
'#title' => t('By'),
'#options' => array('current' => t('Current user'), 'any' => t('Any user')),
'#default_value' => $this->options['user_scope'],
);
$form['required']['#title'] = t('Include only flagged content');
$form['required']['#description'] = t('If checked, only content that has this flag will be included. Leave unchecked to include all content; or, in combination with the <em>Flagged</em> filter, <a href="@unflagged-url">to limit the results to specifically unflagged content</a>.', array('@unflagged-url' => 'http://drupal.org/node/299335'));
if (!$form['flag']['#options']) {
$form = array(
'error' => array(
'#markup' => '<p class="error form-item">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use this relationship type.', array('%type' => $entity_type, '@create-url' => url(FLAG_ADMIN_PATH))) . '</p>',
),
);
$form_state['no flags exist'] = TRUE;
}
if (module_exists('session_api')) {
$form['session_warning'] = array(
'#markup' => '<p class="warning form-item">' . t('<strong>Warning</strong>: Adding this relationship for any flag that contains <strong>anonymous flagging access</strong> will disable page caching for anonymous users when this view is executed. (But this is only true when the relationship is constrained to "Current user", not to "Any user".) It is recommended to create a dedicated page for views containing anonymous user data.') . '</p>',
);
}
}
function options_validate(&$form, &$form_state) {
if (!empty($form_state['no flags exist'])) {
form_error($form, t('You must first create a flag'));
}
}
function admin_summary() {
return $this->options['user_scope'] == 'current' ? t('by current user') : t('by any user');
}
function ui_name($short = FALSE) {
// We put the bookmark name in the UI string to save space.
return t('!group: !title', array('!group' => $this->definition['group'], '!title' => empty($this->options['flag']) ? t('(Please select a flag)') : $this->options['flag']));
}
/**
* Called to implement a relationship in a query.
*/
function query() {
if (!($flag = $this->get_flag())) {
return;
}
$this->definition['extra'][] = array(
'field' => 'fid',
'value' => $flag->fid,
'numeric' => TRUE,
);
if ($this->options['user_scope'] == 'current' && !$flag->global) {
$this->definition['extra'][] = array(
'field' => 'uid',
'value' => '***CURRENT_USER***',
'numeric' => TRUE,
);
$flag_roles = user_roles(FALSE, "flag $flag->name");
if (isset($flag_roles[DRUPAL_ANONYMOUS_RID])) {
// Disable page caching for anonymous users.
drupal_page_is_cacheable(FALSE);
// Add in the SID from Session API for anonymous users.
$this->definition['extra'][] = array(
'field' => 'sid',
'value' => '***FLAG_CURRENT_USER_SID***',
'numeric' => TRUE,
);
}
}
parent::query();
}
}
/**
* Specialized relationship handler associating flag counts and content.
*
* @ingroup views
*/
class flag_handler_relationship_counts extends flag_handler_relationship {
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$entity_type = $this->definition['flag type'];
$form['flag'] = flag_views_flag_config_form('radios', $entity_type, $this->options['flag']);
$form['required']['#title'] = t('Include only flagged content');
$form['required']['#description'] = t('If checked, only content that is flagged will be included.');
}
function admin_summary() {
// Nothing to show.
}
function ui_name($short = FALSE) {
// We put the bookmark name in the UI string to save space.
return t('!group: !title counter', array('!group' => $this->definition['group'], '!title' => $this->options['flag']));
}
/**
* Called to implement a relationship in a query.
*/
function query() {
if (!($flag = $this->get_flag())) {
return;
}
$this->definition['extra'][] = array(
'field' => 'fid',
'value' => $flag->fid,
'numeric' => TRUE,
);
if (!empty($this->options['required'])) {
// Unfortunately, we may have zeros in our table, so having
// parent::query() do INNER JOIN doesn't suffice. We need to filter these
// zeros out.
// @todo Make sure zero records aren't written in the first place, and
// remove this code.
$this->definition['extra'][] = array(
'field' => 'count',
'operator' => '>',
'value' => '0',
'numeric' => TRUE,
);
}
parent::query();
}
}
/**
* Specialized relationship handler associating flags and users.
*
* @ingroup views
*/
class flag_handler_relationship_user_content extends flag_handler_relationship {
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['label']['#description'] .= ' ' . t('Including name of the selected flag helps identify this relationship.');
$form['flag'] = flag_views_flag_config_form('radios', NULL, $this->options['flag']);
$form['flag']['#title'] = t('Flagged');
$form['required']['#title'] = t('Include only users who have flagged content.');
$form['required']['#description'] = t('If checked, only users that have flagged any content with this flag will be included.');
}
function admin_summary() {
return $this->options['flag'];
}
/**
* Called to implement a relationship in a query.
*/
function query() {
if (!($flag = $this->get_flag())) {
return;
}
$this->definition['extra'][] = array(
'field' => 'fid',
'value' => $flag->fid,
'numeric' => TRUE,
);
parent::query();
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* @file
* Contains the flagged content sort handler.
*/
/**
* Handler to sort on whether objects are flagged or not.
*
* @ingroup views
*/
class flag_handler_sort_flagged extends views_handler_sort {
/**
* Provide a list of options for the default sort form.
* Should be overridden by classes that don't override sort_form
*/
function sort_options() {
return array(
'ASC' => t('Unflagged first'),
'DESC' => t('Flagged first'),
);
}
/**
* Display whether or not the sort order is ascending or descending
*/
function admin_summary() {
if (!empty($this->options['exposed'])) {
return t('Exposed');
}
// Get the labels defined in sort_options().
$sort_options = $this->sort_options();
return $sort_options[strtoupper($this->options['order'])];
}
function query() {
$this->ensure_my_table();
// Add the ordering.
// Using IS NOT NULL means that the ASC/DESC ordering work in the same
// direction as sorting by flagging date: empty results come first.
$this->query->add_orderby(NULL, "($this->table_alias.uid IS NOT NULL)", $this->options['order']);
}
}

View File

@@ -0,0 +1,226 @@
<?php
/**
* @file
* Contains the flaggability validator handler.
*/
/**
* Validates whether an argument is a flaggable/flagged object.
*
* @ingroup views
*/
class flag_plugin_argument_validate_flaggability extends views_plugin_argument_validate {
function construct() {
parent::construct();
$this->flag_type = $this->definition['flag type'];
}
function option_definition() {
$options = parent::option_definition();
$options['flag_name'] = array('default' => '*relationship*');
$options['flag_test'] = array('default' => 'flaggable');
$options['flag_id_type'] = array('default' => 'id');
return $options;
}
function options_form(&$form, &$form_state) {
// If there are no flag relationships set, skip this form of validation.
$flags = flag_get_flags($this->flag_type);
if (!$flags) {
return array();
}
$options = $this->flags_options();
$form['flag_name'] = array(
'#type' => 'radios',
'#title' => t('Flag'),
'#options' => $options,
'#default_value' => $this->options['flag_name'],
'#description' => t('Select the flag to validate against.'),
);
if (!$options) {
$form['flag_name']['#description'] = '<p class="warning">' . t('No %type flags exist. You must first <a href="@create-url">create a %type flag</a> before being able to use one.', array('%type' => $this->flag_type, '@create-url' => FLAG_ADMIN_PATH)) . '</p>';
}
$form['flag_test'] = array(
'#type' => 'select',
'#title' => t('Validate the @type only if', array('@type' => $this->flag_type)),
'#options' => $this->tests_options(),
'#default_value' => $this->options['flag_test'],
);
// This validator supports the "multiple IDs" syntax. It may not be
// extremely useful, but similar validators do support this and it's a good
// thing to be compatible.
$form['flag_id_type'] = array(
'#type' => 'select',
'#title' => t('Argument type'),
'#options' => array(
'id' => t('ID'),
'ids' => t('IDs separated by , or +'),
),
'#default_value' => $this->options['flag_id_type'],
);
}
/**
* Returns form #options for the flags. Returns empty array if no flags were
* found.
*/
function flags_options() {
$flags = flag_get_flags($this->flag_type);
if (!$flags) {
return array();
}
else {
foreach ($flags as $flag) {
$options[$flag->name] = $flag->get_title();
}
$options['*relationship*'] = t('<em>Pick the first flag mentioned in the relationships.</em>');
return $options;
}
}
/**
* Migrate existing Views 2 options to Views 3.
*/
function convert_options(&$options) {
$prefix = 'validate_argument_' . $this->flag_type . '_';
if (!isset($options['flag_name']) && !empty($this->argument->options[$prefix . 'flag_name'])) {
$options['flag_name'] = $this->argument->options[$prefix . 'flag_name'];
$options['flag_test'] = $this->argument->options[$prefix . 'flag_test'];
$options['flag_id_type'] = $this->argument->options[$prefix . 'flag_id_type'];
}
}
/**
* Declares all tests. This scheme makes it easy for derived classes to add
* and remove tests.
*/
function tests_info($which = NULL) {
return array(
'flaggable' => array(
'title' => t('It is flaggable'),
'callback' => 'test_flaggable',
),
'flagged' => array(
'title' => t('It is flagged at least once'),
'callback' => 'test_flagged',
),
'flagged_by_current_user' => array(
'title' => t('It is flagged by the current user'),
'callback' => 'test_flagged_by_current_user',
),
);
}
function tests_options() {
$options = array();
foreach ($this->tests_info() as $id => $info) {
$options[$id] = $info['title'];
}
return $options;
}
function get_flag() {
$flag_name = $this->options['flag_name'];
if ($flag_name == '*relationship*') {
// Pick the first flag mentioned in the relationships.
foreach ($this->view->relationship as $id => $handler) {
// Note: we can't do $handler->field, because the relationship handler's init() may overwrite it.
if (strpos($handler->options['field'], 'flag') !== FALSE && !empty($handler->options['flag'])) {
$flag = flag_get_flag($handler->options['flag']);
if ($flag && $flag->entity_type == $this->flag_type) {
return $flag;
}
}
}
}
return flag_get_flag($flag_name);
}
/**
* Tests whether the argument is flaggable, or flagged, or flagged by current
* user. These are three possible tests, and which of the three to actually
* carry out is determined by 'flag_test'.
*/
function validate_argument($argument) {
$flag_test = $this->options['flag_test'];
$id_type = $this->options['flag_id_type'];
$flag = $this->get_flag();
if (!$flag) {
// Validator is misconfigured somehow.
return TRUE;
}
if ($id_type == 'id') {
if (!is_numeric($argument)) {
// If a user is being smart and types several IDs where only one is
// expected, we invalidate this.
return FALSE;
}
}
$ids = views_break_phrase($argument, $this);
if ($ids->value == array(-1)) {
// Malformed argument syntax. Invalidate.
return FALSE;
}
$ids = $ids->value;
// Delegate the testing to the particual test method. $passed then
// holds the IDs that passed the test.
$tests_info = $this->tests_info();
$method = $tests_info[$flag_test]['callback'];
if (method_exists($this, $method)) {
$passed = $this->$method($ids, $flag);
}
else {
$passed = array();
}
// If some IDs exist that haven't $passed our test then validation fails.
$failed = array_diff($ids, $passed);
return empty($failed);
}
//
// The actual tests. They return the IDs that passed.
//
function test_flaggable($ids, $flag) {
return array_filter($ids, array($flag, 'applies_to_entity_id'));
}
function test_flagged($ids, $flag) {
// view_break_phrase() is guaranteed to return only integers, so this is SQL safe.
$flattened_ids = implode(',', $ids);
return $this->_test_by_sql("SELECT entity_id FROM {flag_counts} WHERE fid = :fid AND entity_id IN ($flattened_ids) AND count > 0", array(':fid' => $flag->fid));
}
function test_flagged_by_current_user($ids, $flag) {
global $user;
if (!$user->uid) {
// Anonymous user
return array();
}
$flattened_ids = implode(',', $ids);
return $this->_test_by_sql("SELECT entity_id FROM {flagging} WHERE fid = :fid AND entity_id IN ($flattened_ids) AND uid = :uid", array(':fid' => $flag->fid, ':uid' => $user->uid));
}
// Helper: executes an SQL query and returns all the entity_id's.
function _test_by_sql($sql, $args) {
$passed = array();
$result = db_query($sql, $args);
foreach ($result as $row) {
$passed[] = $row->entity_id;
}
return $passed;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Plugin definition.
*/
$plugin = array(
'title' => t("Entity is flagged"),
'description' => t('Control access by whether or not an entity is flagged.'),
'callback' => 'flag_flag_is_flagged_access_check',
'default' => array('flag_name' => ''),
'settings form' => 'flag_flag_is_flagged_access_settings',
'summary' => 'flag_flag_is_flagged_access_summary',
'get child' => 'flag_flag_is_flagged_access_get_child',
'get children' => 'flag_flag_is_flagged_access_get_children',
);
/*
* Implements plugin get_child callback.
*/
function flag_flag_is_flagged_access_get_child($plugin, $parent, $child) {
$plugins = flag_flag_is_flagged_access_get_children($plugin, $parent);
return $plugins[$parent . ':' . $child];
}
/*
* Implements plugin get_children callback.
*/
function flag_flag_is_flagged_access_get_children($plugin, $parent) {
$plugins = array();
$entities = entity_get_info();
foreach ($entities as $entity_type => $info) {
if (entity_type_supports($entity_type, 'view')) {
$plugin['title'] = t('@entity_type is flagged', array('@entity_type' => $info['label']));
$plugin['keyword'] = $entity_type;
$plugin['name'] = $parent . ':' . $entity_type;
$plugin['required context'] = new ctools_context_required(t('Entity'), $entity_type);
$plugins[$parent . ':' . $entity_type] = $plugin;
}
}
return $plugins;
}
/**
* Settings form.
*/
function flag_flag_is_flagged_access_settings(&$form, &$form_state, $conf) {
$options = array();
$plugin = $form_state['plugin'];
$entity_type = explode(':', $plugin['name']);
$entity_type = $entity_type[1];
foreach (flag_get_flags($entity_type) as $flag) {
$options[$flag->name] = check_plain($flag->title);
}
$form['settings']['flag_name'] = array(
'#title' => t('Flag name'),
'#type' => 'radios',
'#options' => $options,
'#description' => t('Include only flagged content.'),
'#default_value' => $conf['flag_name'],
);
return $form;
}
/**
* Check for access.
*/
function flag_flag_is_flagged_access_check($conf, $context) {
$flag = flag_get_flag($conf['flag_name']);
if (!$flag || empty($context->data)) {
return FALSE;
}
// Get the ID of the entity.
list($id) = entity_extract_ids($flag->entity_type, $context->data);
return $flag->is_flagged($id);
}
/**
* Provide a summary description based upon the specified context
*/
function flag_flag_is_flagged_access_summary($conf, $context) {
$flag = flag_get_flag($conf['flag_name']);
if ($flag) {
return t('@identifier is flagged with "@flag"', array('@flag' => check_plain($flag->title), '@identifier' => $context->identifier));
}
else {
return t('Missing/ deleted flag "@flag"', array('@flag' => $conf['flag_name']));
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* Plugin definition.
*/
$plugin = array(
'title' => t('Flag link'),
'description' => t('Add a flag link of a certain flag type for entities.'),
'defaults' => array('flag_name' => ''),
'content type' => 'flag_flag_link_content_type_info',
);
/**
* Get the entity content type info.
*/
function flag_flag_link_content_type_info($entity_type) {
$types = flag_flag_link_content_type_content_types();
if (isset($types[$entity_type])) {
return $types[$entity_type];
}
}
/**
* Implements hook_PLUGIN_content_type_content_types().
*/
function flag_flag_link_content_type_content_types() {
$types = array();
$entities = entity_get_info();
foreach ($entities as $entity_type => $info) {
if (entity_type_supports($entity_type, 'view')) {
$types[$entity_type] = array(
'title' => t('Flag for @entity_type', array('@entity_type' => $info['label'])),
'category' => t('Entity'),
'required context' => new ctools_context_required(t('Entity'), $entity_type),
);
}
}
return $types;
}
/**
* Render callback.
*/
function flag_flag_link_content_type_render($subtype, $conf, $args, $context) {
$flag = flag_get_flag($conf['flag_name']);
if (!$flag) {
return;
}
if (empty($context->data)) {
return;
}
// Get the ID of the entity.
list($id,,) = entity_extract_ids($flag->entity_type, $context->data);
$link = flag_create_link($flag->name, $id);
if (!$link) {
return;
}
$block = new stdClass();
$block->module = 'flag';
$block->title = t('Flag link');
$block->delta = $flag->name;
$block->content = $link;
return $block;
}
/**
* Form callback.
*/
function flag_flag_link_content_type_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$entity_type = $form_state['subtype_name'];
$options = array();
foreach (flag_get_flags($entity_type) as $flag) {
$options[$flag->name] = $flag->title;
}
$form['flag_name'] = array(
'#type' => 'select',
'#title' => t('Flag name'),
'#default_value' => $conf['flag_name'],
'#options' => $options,
'#description' => t('Select a flag.'),
'#required' => TRUE,
'#disabled' => !$options,
);
return $form;
}
/**
* Form submit.
*/
function flag_flag_link_content_type_edit_form_submit($form, &$form_state) {
// Copy everything from our defaults.
foreach (array_keys($form_state['plugin']['defaults']) as $key) {
$form_state['conf'][$key] = $form_state['values'][$key];
}
}
/**
* Returns the administrative title for a flag link.
*/
function flag_flag_link_content_type_admin_title($subtype, $conf) {
if ($flag = flag_get_flag($conf['flag_name'])) {
return t('Flag link of @flag', array('@flag' => $flag->title));
}
}

View File

@@ -0,0 +1,971 @@
<?php
/**
* @file
* Tests for the Flag module.
*/
/**
* Base class for our tests with common methods.
*/
abstract class FlagTestCaseBase extends DrupalWebTestCase {
/**
* Helper to create a flag from an array of data and clear caches etc.
*
* @param $flag_data
* An array of flag data.
*
* @return
* The flag object.
*/
function createFlag($flag_data) {
$flag = flag_flag::factory_by_array($flag_data);
$flag->save();
// Reset our cache so our permissions show up.
drupal_static_reset('flag_get_flags');
// Reset permissions so that permissions for this flag are available.
$this->checkPermissions(array(), TRUE);
return $flag;
}
}
/**
* Test CRUD operations on Flagging entities.
*/
class FlagFlaggingCRUDTestCase extends FlagTestCaseBase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flagging CRUD',
'description' => 'Basic CRUD operations on flagging entities.',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
$flag_data = array(
'entity_type' => 'node',
'name' => 'test_flag',
'title' => 'Test Flag',
'global' => 0,
'types' => array(
0 => 'article',
),
'flag_short' => 'Flag this item',
'flag_long' => '',
'flag_message' => '',
'unflag_short' => 'Unflag this item',
'unflag_long' => '',
'unflag_message' => '',
'unflag_denied_text' => 'You may not unflag this item',
'link_type' => 'normal',
'weight' => 0,
'show_on_form' => 0,
'access_author' => '',
'show_contextual_link' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'i18n' => 0,
'api_version' => 3,
);
$this->flag = $this->createFlag($flag_data);
// Create test user who can flag and unflag.
$this->flag_unflag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
$this->drupalLogin($this->flag_unflag_user);
}
/**
* Test creation of a flagging entity with flagging_save().
*/
function testFlaggingCreate() {
// Create an article node that we try to create a flagging entity for.
$title = $this->randomName(8);
$node = array(
'title' => $title,
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = node_submit((object) $node);
node_save($node);
// Create a flagging entity and save it.
$flagging = array(
'fid' => $this->flag->fid,
'entity_type' => 'node',
'entity_id' => $node->nid,
'uid' => $this->flag_unflag_user->uid,
);
$flagging = (object) $flagging;
flagging_save($flagging);
// Test flagging has a flagging_id
$this->assertTrue(!empty($flagging->flagging_id), 'The flagging entity has an entity id.');
// Test the database record exists.
$result = db_query("SELECT * FROM {flagging} WHERE fid = :fid AND entity_id = :nid AND uid = :uid", array(
':fid' => $this->flag->fid,
':nid' => $node->nid,
':uid' => $this->flag_unflag_user->uid,
));
$records = $result->fetchAll();
$this->assertTrue(count($records), 'The flagging record exists in the database.');
// Test node is flagged.
// The current user is not the same as the user logged into the internal
// browser, so we have to pass the UID param explicitly.
$this->assertTrue($this->flag->is_flagged($node->nid, $this->flag_unflag_user->uid), 'The node has been flagged by creating the flagging.');
}
/**
* Test throwing of exceptions with flagging_save().
*/
function testFlaggingCreateException() {
// Create an article node that we try to create a flagging entity for.
$title = $this->randomName(8);
$node = array(
'title' => $title,
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = node_submit((object) $node);
node_save($node);
// Create test user who can't use this flag.
$no_flag_user = $this->drupalCreateUser(array());
// Create a flagging entity with that tries to perform an flagging action
// that is not permitted.
$flagging = array(
'fid' => $this->flag->fid,
'entity_type' => 'node',
'entity_id' => $node->nid,
'uid' => $no_flag_user->uid,
);
$flagging = (object) $flagging;
try {
flagging_save($flagging);
$this->fail(t('Expected exception has not been thrown.'));
}
catch (Exception $e) {
$this->pass(t('Expected exception has been thrown.'));
}
}
/**
* Test creation of a flagging entity with flagging_save().
*/
function testFlaggingUpdate() {
// Create an article node that we try to create a flagging entity for.
$title = $this->randomName(8);
$node = array(
'title' => $title,
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = node_submit((object) $node);
node_save($node);
// Flag the node as the user.
$flag = flag_get_flag('test_flag');
$flag->flag('flag', $node->nid, $this->flag_unflag_user);
// Get the flagging record back from the database.
$result = db_query("SELECT * FROM {flagging} WHERE fid = :fid AND entity_id = :nid AND uid = :uid", array(
':fid' => $this->flag->fid,
':nid' => $node->nid,
':uid' => $this->flag_unflag_user->uid,
));
$record = $result->fetchObject();
// Load the flagging entity we just created.
$flagging = flagging_load($record->flagging_id);
// Save it, as if we were updating field values.
flagging_save($flagging);
// This should have no effect: the node should still be flagged.
$this->assertTrue($this->flag->is_flagged($node->nid, $this->flag_unflag_user->uid), 'The node is still flagged after updating the flagging.');
}
}
/**
* Test Flag admin UI.
*/
class FlagAdminTestCase extends FlagTestCaseBase {
var $_flag = FALSE;
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flag admin tests',
'description' => 'Add, edit and delete flags.',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
// Create and login user
$admin_user = $this->drupalCreateUser(array('access administration pages', 'administer flags'));
$this->drupalLogin($admin_user);
}
/**
* Create a flag through the UI and ensure that it is saved properly.
*/
function testFlagAdmin() {
// Add a new flag using the UI.
$edit = array(
'name' => drupal_strtolower($this->randomName()),
'title' => $this->randomName(),
'flag_short' => 'flag short [node:nid]',
'flag_long' => 'flag long [node:nid]',
'flag_message' => 'flag message [node:nid]',
'unflag_short' => 'unflag short [node:nid]',
'unflag_long' => 'unflag long [node:nid]',
'unflag_message' => 'unflag message [node:nid]',
'roles[flag][2]' => TRUE,
'roles[unflag][2]' => TRUE,
'types[article]' => FALSE,
'types[page]' => TRUE,
'show_in_links[full]' => FALSE,
'show_in_links[teaser]' => FALSE,
'show_in_links[rss]' => FALSE,
'show_in_links[search_index]' => FALSE,
'show_in_links[search_result]' => FALSE,
'show_on_form' => FALSE,
'link_type' => 'toggle',
);
$saved = $edit;
$saved['roles'] = array('flag' => array(2), 'unflag' => array(2));
$saved['types'] = array('page');
$saved['show_in_links'] = array('full' => 0, 'teaser' => 0, 'rss' => 0, 'search_index' => 0, 'search_result' => 0);
unset($saved['roles[flag][2]'], $saved['roles[unflag][2]'], $saved['types[article]'], $saved['types[page]'], $saved['show_in_links[full]'], $saved['show_in_links[teaser]'], $saved['show_in_links[rss]'], $saved['show_in_links[search_index]'], $saved['show_in_links[search_result]']);
$this->drupalPost(FLAG_ADMIN_PATH . '/add/node', $edit, t('Save flag'));
drupal_static_reset('flag_get_flags');
$flag = flag_get_flag($edit['name']);
// Load the roles array for checking it matches.
$flag->fetch_roles();
// Check that the flag object is in the database.
$this->assertTrue($flag != FALSE, t('Flag object found in database'));
// Check each individual property of the flag and make sure it was set.
foreach ($saved as $property => $value) {
$this->assertEqual($flag->$property, $value, t('Flag property %property properly saved.', array('%property' => $property)));
}
// Check permissions.
$permissions = user_role_permissions(user_roles());
foreach ($saved['roles'] as $action => $rids) {
foreach ($rids as $rid) {
$permission_string = "$action ". $saved['name'];
$this->assertTrue(isset($permissions[$rid][$permission_string]), t('Permission %perm set for flag.', array(
'%perm' => $permission_string,
)));
}
}
// Edit the flag through the UI.
$edit = array(
'name' => drupal_strtolower($this->randomName()),
'title' => $this->randomName(),
'flag_short' => 'flag 2 short [node:nid]',
'flag_long' => 'flag 2 long [node:nid]',
'flag_message' => 'flag 2 message [node:nid]',
'unflag_short' => 'unflag 2 short [node:nid]',
'unflag_long' => 'unflag 2 long [node:nid]',
'unflag_message' => 'unflag 2 message [node:nid]',
'roles[flag][2]' => TRUE,
'roles[unflag][2]' => TRUE,
'types[article]' => TRUE,
'types[page]' => FALSE,
'show_in_links[full]' => TRUE,
'show_in_links[teaser]' => TRUE,
'show_in_links[rss]' => FALSE,
'show_in_links[search_index]' => FALSE,
'show_in_links[search_result]' => FALSE,
'show_on_form' => TRUE,
'link_type' => 'normal',
);
$saved = $edit;
$saved['roles'] = array('flag' => array(2), 'unflag' => array(2));
$saved['types'] = array('article');
$saved['show_in_links'] = array('full' => TRUE, 'teaser' => TRUE, 'rss' => 0, 'search_index' => 0, 'search_result' => 0);
unset($saved['roles[flag][2]'], $saved['roles[unflag][2]'], $saved['types[article]'], $saved['types[page]'], $saved['show_in_links[full]'], $saved['show_in_links[teaser]'], $saved['show_in_links[rss]'], $saved['show_in_links[search_index]'], $saved['show_in_links[search_result]']);
$this->drupalPost(FLAG_ADMIN_PATH . '/manage/' . $flag->name, $edit, t('Save flag'));
drupal_static_reset('flag_get_flags');
$flag = flag_get_flag($edit['name']);
// Load the roles array for checking it matches.
$flag->fetch_roles();
// Check that the flag object is in the database.
$this->assertTrue($flag != FALSE, t('Flag object found in database'));
// Check each individual property of the flag and make sure it was set.
foreach ($saved as $property => $value) {
$this->assertEqual($flag->$property, $value, t('Flag property %property properly saved.', array('%property' => $property)));
}
// Clear the user access cache so our changes to permissions are noticed.
drupal_static_reset('user_access');
drupal_static_reset('user_role_permissions');
// Check permissions.
$permissions = user_role_permissions(user_roles());
foreach ($saved['roles'] as $action => $rids) {
foreach ($rids as $rid) {
$permission_string = "$action ". $saved['name'];
$this->assertTrue(isset($permissions[$rid][$permission_string]), t('Permission %perm set for flag.', array(
'%perm' => $permission_string,
)));
}
}
// Delete the flag through the UI.
$this->drupalPost(FLAG_ADMIN_PATH . '/manage/' . $flag->name . '/delete', array(), t('Delete'));
drupal_static_reset('flag_get_flags');
$this->assertFalse(flag_get_flag($flag->name), t('Flag successfully deleted.'));
}
}
/**
* Access to flags using the entity forms.
*
* @todo: complete this test class.
*/
class FlagAccessFormTestCase extends FlagTestCaseBase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flag access: entity forms',
'description' => 'Access to flag and unflag entities via entity forms.',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
}
/**
* Test scenarios with no access to a global flag.
*/
function testFlagAccessGlobalNone() {
// Create a global flag on article nodes.
$flag_data = array(
'entity_type' => 'node',
'name' => 'global_flag',
'title' => 'Global Flag',
'global' => 1,
'types' => array(
0 => 'article',
),
'flag_short' => 'Flag this item',
'flag_long' => '',
'flag_message' => '',
'unflag_short' => 'Unflag this item',
'unflag_long' => '',
'unflag_message' => '',
'unflag_denied_text' => 'You may not unflag this item',
'link_type' => 'normal',
'weight' => 0,
// Show the flag on the form.
'show_on_form' => 1,
'access_author' => '',
'show_contextual_link' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'i18n' => 0,
'api_version' => 3,
);
$flag = $this->createFlag($flag_data);
// Create test user who can't us this flag, but can create nodes.
$no_flag_user = $this->drupalCreateUser(array('create article content'));
$this->drupalLogin($no_flag_user);
$this->drupalGet('node/add/article');
// Check that the flag form element cannot be seen
$this->assertNoText('Flag this item', t('Flag form element was not found.'));
// Have the user create a node.
$edit = array(
'title' => 'node 1',
);
$this->drupalPost('node/add/article', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit["title"]);
// Check the new node has not been flagged.
$this->assertFalse($flag->is_flagged($node->nid), t('New node is not flagged.'));
// Now set the variable so that the flag is set by default on new nodes.
variable_set('flag_' . $flag->name . '_default_' . 'article', 1);
// Create another new node.
$edit = array(
'title' => 'node 2',
);
$this->drupalPost('node/add/article', $edit, t('Save'));
$node = $this->drupalGetNodeByTitle($edit["title"]);
// Check the new node has been flagged, despite the user not having access
// to the flag.
$this->assertTrue($flag->is_flagged($node->nid), t('New node is flagged.'));
}
}
/**
* Access to flags using the basic flag link.
*/
class FlagAccessLinkTestCase extends FlagTestCaseBase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flag access tests',
'description' => 'Access to flag and unflag entities using the basic link.',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
// Create a test flag on article nodes.
$flag_data = array(
'entity_type' => 'node',
'name' => 'test_flag',
'title' => 'Test Flag',
'global' => 0,
'types' => array(
0 => 'article',
),
'flag_short' => 'Flag this item',
'flag_long' => '',
'flag_message' => '',
'unflag_short' => 'Unflag this item',
'unflag_long' => '',
'unflag_message' => '',
'unflag_denied_text' => 'You may not unflag this item',
// Use the normal link type as it involves no intermediary page loads.
'link_type' => 'normal',
'weight' => 0,
'show_on_form' => 0,
'access_author' => '',
'show_contextual_link' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'i18n' => 0,
'api_version' => 3,
);
$flag = $this->createFlag($flag_data);
// Create an article node that various users will try to flag.
$title = $this->randomName(8);
$node = array(
'title' => $title,
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = node_submit((object) $node);
node_save($node);
$this->nid = $node->nid;
}
/**
* Test that a user without flag access can't see the flag.
*/
function testFlagAccessNone() {
// Create test user who can't flag at all.
$no_flag_user = $this->drupalCreateUser(array());
$this->drupalLogin($no_flag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertNoLink('Flag this item', 0, 'The flag link does not appear on the page');
}
/**
* Test that a user with only flag access can flag but not unflag.
*/
function testFlagAccessFlagOnly() {
// Create test user who can flag but not unflag.
$flag_user = $this->drupalCreateUser(array('flag test_flag',));
$this->drupalLogin($flag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
// Click the link to flag the node.
$this->clickLink(t('Flag this item'));
$this->assertText('You may not unflag this item', 0, 'The unflag denied text appears on the page after flagging.');
}
/**
* Test that a user with flag access can flag and unflag.
*/
function testFlagAccessFlagUnflag() {
// Create test user who can flag and unflag.
$flag_unflag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
$this->drupalLogin($flag_unflag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
// Click the link to flag the node.
$this->clickLink(t('Flag this item'));
$this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
// Click the link to unflag the node.
$this->clickLink(t('Unflag this item'));
$this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
}
}
/**
* Test the 'confirm form' link type.
*/
class FlagLinkTypeConfirmTestCase extends DrupalWebTestCase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flag confirm link tests',
'description' => 'Flag confirm form link type.',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
// Create a test flag on article nodes.
// Keep the original data so we can compare strings.
$this->flag_data = array(
'entity_type' => 'node',
'name' => 'test_flag',
'title' => 'Test Flag',
'global' => 0,
'types' => array(
0 => 'article',
),
'flag_short' => 'Flag this item',
'flag_long' => '',
'flag_message' => 'You have flagged this item.',
'unflag_short' => 'Unflag this item',
'unflag_long' => '',
'unflag_message' => 'You have unflagged this item',
'unflag_denied_text' => 'You may not unflag this item',
'link_type' => 'confirm',
'flag_confirmation' => 'Are you sure you want to flag this item?',
'unflag_confirmation' => 'Are you sure you want to unflag this item?',
'weight' => 0,
'show_on_form' => 0,
'access_author' => '',
'show_contextual_link' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'i18n' => 0,
'api_version' => 3,
);
$flag = flag_flag::factory_by_array($this->flag_data);
$flag->save();
// Reset our cache so our permissions show up.
drupal_static_reset('flag_get_flags');
// Reset permissions so that permissions for this flag are available.
$this->checkPermissions(array(), TRUE);
// Create test user who can flag and unflag.
$flag_unflag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
$this->drupalLogin($flag_unflag_user);
// Create an article node to flag and unflag.
$title = $this->randomName(8);
$node = array(
'title' => $title,
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = node_submit((object) $node);
node_save($node);
$this->nid = $node->nid;
}
/**
* Test that a user sees the flag confirm form.
*/
function testFlag() {
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertLink($this->flag_data['flag_short'], 0, 'The flag link appears on the page');
// Click the link to flag the node.
$this->clickLink($this->flag_data['flag_short']);
$this->assertUrl('flag/confirm/flag/test_flag/' . $this->nid, array(
'query' => array(
'destination' => 'node/' . $this->nid,
),
), 'On confirm flagging form page.');
$this->assertText($this->flag_data['flag_confirmation'], 'The flag confirmation text appears as the confirmation page title.');
// Click the button to confirm the flagging.
$this->drupalPost(NULL, array(), $this->flag_data['flag_short']);
$this->assertText($this->flag_data['flag_message'], 'The flag message appears once the item has been flagged.');
$this->assertLink($this->flag_data['unflag_short'], 0, 'The unflag link appears once the item has been flagged.');
// Click the link to unflag the node.
$this->clickLink($this->flag_data['unflag_short']);
$this->assertUrl('flag/confirm/unflag/test_flag/' . $this->nid, array(
'query' => array(
'destination' => 'node/' . $this->nid,
),
), t('On confirm unflagging form page.'));
$this->assertText($this->flag_data['unflag_confirmation'], 'The unflag confirmation text appears as the confirmation page title.');
// Click the button to confirm the flagging.
$this->drupalPost(NULL, array(), $this->flag_data['unflag_short']);
$this->assertText($this->flag_data['unflag_message'], 'The unflag message appears once the item has been flagged.');
}
}
/**
* Verifies the implementation of hook_flag_access().
*/
class FlagHookFlagAccessTestCase extends FlagTestCaseBase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flag hook_flag_access() tests',
'description' => 'Checks the ability of modules to use hook_flag_access().',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
$success = module_enable(array('flagaccesstest'), FALSE);
// Create a test flag on article nodes.
$flag_data = array(
'entity_type' => 'node',
'name' => 'test_flag',
'title' => 'Test Flag',
'global' => 0,
'types' => array(
0 => 'article',
),
'flag_short' => 'Flag this item',
'flag_long' => '',
'flag_message' => '',
'unflag_short' => 'Unflag this item',
'unflag_long' => '',
'unflag_message' => '',
'unflag_denied_text' => 'You may not unflag this item',
// Use the normal link type as it involves no intermediary page loads.
'link_type' => 'normal',
'weight' => 0,
'show_on_form' => 0,
'access_author' => '',
'show_contextual_link' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'i18n' => 0,
'api_version' => 3,
);
$flag = $this->createFlag($flag_data);
// Create an article node that various users will try to flag.
$title = $this->randomName(8);
$node = array(
'title' => $title,
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = node_submit((object) $node);
node_save($node);
$this->nid = $node->nid;
}
/**
* Verifies that the user sees the flag if a module returns NULL (Ignore).
*/
function testFlagAccessIgnore() {
variable_set('FlagHookFlagAccessTestCaseMode', 'ignore');
$flag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
$this->drupalLogin($flag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
// Click the link to flag the node.
$this->clickLink(t('Flag this item'));
$this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
// Click the link to unflag the node.
$this->clickLink(t('Unflag this item'));
$this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
}
/**
* Verifies that the user sees the flag if a module returns TRUE (Allow).
*/
function testFlagAccessAllow() {
variable_set('FlagHookFlagAccessTestCaseMode', 'allow');
$flag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
$this->drupalLogin($flag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
// Click the link to flag the node.
$this->clickLink(t('Flag this item'));
$this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
// Click the link to unflag the node.
$this->clickLink(t('Unflag this item'));
$this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
}
/**
* Verifies that the user sees the flag if a module returns TRUE (Allow) to override default access check.
*/
function testFlagAccessAllowOverride() {
variable_set('FlagHookFlagAccessTestCaseMode', 'allow');
$flag_user = $this->drupalCreateUser(array());
$this->drupalLogin($flag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertLink('Flag this item', 0, 'The flag link appears on the page.');
// Click the link to flag the node.
$this->clickLink(t('Flag this item'));
$this->assertLink('Unflag this item', 0, 'The unflag link appears on the page after flagging.');
// Click the link to unflag the node.
$this->clickLink(t('Unflag this item'));
$this->assertLink('Flag this item', 0, 'The flag link appears on the page after unflagging.');
}
/**
* Verifies that the user does not see the flag if a module returns FALSE (Deny).
*/
function testFlagAccessDeny() {
variable_set('FlagHookFlagAccessTestCaseMode', 'deny');
$flag_user = $this->drupalCreateUser(array('flag test_flag', 'unflag test_flag'));
$this->drupalLogin($flag_user);
// Look at our node.
$this->drupalGet('node/' . $this->nid);
$this->assertNoLink('Flag this item', 0, 'The flag link does not appear on the page.');
}
}
/**
* Test use of EntityFieldQueries with flagging entities.
*/
class FlagEntityFieldQueryTestCase extends FlagTestCaseBase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => 'Flagging Entity Field Query Extension',
'description' => 'Entity Field Query for flagging entities.',
'group' => 'Flag',
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp('flag');
$flag_data = array(
'entity_type' => 'node',
'name' => 'test_flag_1',
'title' => 'Test Flag',
'global' => 0,
'types' => array(
0 => 'article',
),
'flag_short' => 'Flag this item',
'flag_long' => '',
'flag_message' => '',
'unflag_short' => 'Unflag this item',
'unflag_long' => '',
'unflag_message' => '',
'unflag_denied_text' => 'You may not unflag this item',
// Use the normal link type as it involves no intermediary page loads.
'link_type' => 'normal',
'weight' => 0,
'show_on_form' => 0,
'access_author' => '',
'show_contextual_link' => 0,
'show_in_links' => array(
'full' => 1,
'teaser' => 1,
),
'i18n' => 0,
'api_version' => 3,
);
$this->flag1 = $this->createFlag($flag_data);
$flag_data['name'] = 'test_flag_2';
$this->flag2 = $this->createFlag($flag_data);
$flag_data['name'] = 'test_flag_3';
$this->flag3 = $this->createFlag($flag_data);
// Create test user who can flag and unflag.
$this->flag_unflag_user = $this->drupalCreateUser(array('flag test_flag_1', 'unflag test_flag_1', 'flag test_flag_2', 'unflag test_flag_2'));
$this->drupalLogin($this->flag_unflag_user);
}
/**
* Test use of EntityFieldQuery with flagging entities.
*/
function testEntityFieldQuery() {
$node_settings = array(
'title' => $this->randomName(),
'body' => array(LANGUAGE_NONE => array(array('value' => $this->randomName(32)))),
'uid' => 1,
'type' => 'article',
'is_new' => TRUE,
);
$node = $this->drupalCreateNode($node_settings);
flag('flag', 'test_flag_1', $node->nid, $this->flag_unflag_user);
flag('flag', 'test_flag_2', $node->nid, $this->flag_unflag_user);
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'flagging')
->entityCondition('bundle', 'test_flag_1');
$flagged = $query->execute();
$this->assertEqual(count($flagged['flagging']), 1);
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'flagging')
->entityCondition('bundle', 'test%', 'like');
$flagged = $query->execute();
$this->assertEqual(count($flagged['flagging']), 2);
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'flagging')
->entityCondition('bundle', array('test_flag_1', 'test_flag_2'), 'IN');
$this->assertEqual(count($flagged['flagging']), 2);
}
}

View File

@@ -0,0 +1,13 @@
name = Flag hook_flag_access test
description = Tests for hook_flag_access
core = 7.x
dependencies[] = flag
package = Flags
hidden = TRUE
; Information added by drupal.org packaging script on 2013-09-13
version = "7.x-3.2"
core = "7.x"
project = "flag"
datestamp = "1379063829"

View File

@@ -0,0 +1,26 @@
<?php
/**
* @file
* Module for testing hook_flag_access.
*/
/**
* Implements hook_flag_access.
*/
function flagaccesstest_flag_access($flag, $entity_id, $action, $account) {
$mode = variable_get('FlagHookFlagAccessTestCaseMode', 'ignore');
switch ($mode) {
case 'ignore':
drupal_set_message('hook_flag_access: ignore');
return NULL;
case 'allow':
drupal_set_message('hook_flag_access: allow');
return TRUE;
case 'deny':
drupal_set_message('hook_flag_access: deny');
return FALSE;
}
}

View File

@@ -0,0 +1,36 @@
Theming instructions
====================
You may want to visit the Theming Guide of this module, at:
http://drupal.org/node/295346
Template file
-------------
In order to customize flag theming:
- Copy the 'flag.tpl.php' template file into your theme's folder.[1]
- Clear your theme registry.[2]
- Edit that copy to your liking.
Template variants
-----------------
The theme layer will first look for the following templates in this order:
- flag--<FLAG_NAME>.tpl.php
- flag--<FLAG_LINK_TYPE>.tpl.php
- flag.tpl.php
These should also be placed in your theme's folder.[2][1]
Footnotes
---------
[1] Or to a sub-folder in your theme's folder.
[2] Clearing the theme registry makes Drupal aware of your new template
file. This step is needed if you create or rename template files. This
step *isn't* needed if you merely modify the contents of a file. Instructions
on how to clear you theme registry are at http://drupal.org/node/173880#theme-registry

View File

@@ -0,0 +1,25 @@
table.flag-admin-table {
width: auto;
margin: 0;
}
table.flag-admin-table th {
font-weight: normal;
padding-left: 2em;
padding-right: 2em;
}
table.flag-admin-table td {
padding-left: 2em;
padding-right: 2em;
}
#flag-form .token-examples {
margin-top: 0;
margin-bottom: 0;
padding-left: 4em;
}
#flag-form .token-examples li {
color: #999;
font-family: monospace;
line-height: 120%;
}

View File

@@ -0,0 +1,64 @@
(function ($) {
/**
* Behavior to disable the "unflag" option if "flag" is not available.
*/
Drupal.behaviors.flagRoles = {};
Drupal.behaviors.flagRoles.attach = function(context) {
$('#flag-roles input.flag-access', context).change(function() {
var unflagCheckbox = $(this).parents('tr:first').find('input.unflag-access').get(0);
if (this.checked) {
// If "flag" is available, restore the state of the "unflag" checkbox.
unflagCheckbox.disabled = false;
if (typeof(unflagCheckbox.previousFlagState) != 'undefined') {
unflagCheckbox.checked = unflagCheckbox.previousFlagState;
}
else {
unflagCheckbox.checked = true;
}
}
else {
// Remember if the "unflag" option was checked or unchecked, then disable.
unflagCheckbox.previousFlagState = unflagCheckbox.checked;
unflagCheckbox.disabled = true;
unflagCheckbox.checked = false;
}
});
$('#flag-roles input.unflag-access', context).change(function() {
if ($(this).parents('table:first').find('input.unflag-access:enabled:not(:checked)').size() == 0) {
$('div.form-item-unflag-denied-text').slideUp();
}
else {
$('div.form-item-unflag-denied-text').slideDown();
}
});
// Hide the link options by default if needed.
if ($('#flag-roles input.unflag-access:enabled:not(:checked)').size() == 0) {
$('div.form-item-unflag-denied-text').css('display', 'none');
}
};
/**
* Vertical tabs integration.
*/
Drupal.behaviors.flagSummary = {};
Drupal.behaviors.flagSummary.attach = function (context) {
$('fieldset.flag-fieldset', context).drupalSetSummary(function(context) {
var flags = [];
$('input:checkbox:checked', context).each(function() {
flags.push(this.title);
});
if (flags.length) {
return flags.join(', ');
}
else {
return Drupal.t('No flags');
}
});
};
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,44 @@
.flag-message {
position: absolute;
top: 1.7em;
line-height: normal;
left: 0;
text-align: left;
width: 300px;
font-size: .8em;
}
.flag-message.flag-failure-message {
border: 1px solid ;
border-color: #ed5;
color: #840;
background-color: #fffce5;
padding: 2px;
}
.flag-wrapper {
position: relative;
}
/* Better contextual link support, prevent line wrapping. */
ul.contextual-links li .flag-wrapper a {
display: inline-block;
}
ul.contextual-links li .flag-wrapper {
display: block;
}
/* The rest deals with indicating the waiting state. */
.flag-waiting a {
/* Give an impression of a disabled link. */
opacity: 0.5;
filter: alpha(opacity=50); /* IE */
}
.flag-waiting .flag-throbber {
background: url(flag-throbber.gif) no-repeat right center;
padding-right: 13px;
}

View File

@@ -0,0 +1,233 @@
(function ($) {
/**
* Terminology:
*
* "Link" means "Everything which is in flag.tpl.php" --and this may contain
* much more than the <A> element. On the other hand, when we speak
* specifically of the <A> element, we say "element" or "the <A> element".
*/
/**
* The main behavior to perform AJAX toggling of links.
*/
Drupal.flagLink = function(context) {
/**
* Helper function. Updates a link's HTML with a new one.
*
* @param element
* The <A> element.
* @return
* The new link.
*/
function updateLink(element, newHtml) {
var $newLink = $(newHtml);
// Initially hide the message so we can fade it in.
$('.flag-message', $newLink).css('display', 'none');
// Reattach the behavior to the new <A> element. This element
// is either whithin the wrapper or it is the outer element itself.
var $nucleus = $newLink.is('a') ? $newLink : $('a.flag', $newLink);
$nucleus.addClass('flag-processed').click(flagClick);
// Find the wrapper of the old link.
var $wrapper = $(element).parents('.flag-wrapper:first');
// Replace the old link with the new one.
$wrapper.after($newLink).remove();
Drupal.attachBehaviors($newLink.get(0));
$('.flag-message', $newLink).fadeIn();
setTimeout(function(){ $('.flag-message.flag-auto-remove', $newLink).fadeOut() }, 3000);
return $newLink.get(0);
}
/**
* A click handler that is attached to all <A class="flag"> elements.
*/
function flagClick(event) {
// Prevent the default browser click handler
event.preventDefault();
// 'this' won't point to the element when it's inside the ajax closures,
// so we reference it using a variable.
var element = this;
// While waiting for a server response, the wrapper will have a
// 'flag-waiting' class. Themers are thus able to style the link
// differently, e.g., by displaying a throbber.
var $wrapper = $(element).parents('.flag-wrapper');
if ($wrapper.is('.flag-waiting')) {
// Guard against double-clicks.
return false;
}
$wrapper.addClass('flag-waiting');
// Hide any other active messages.
$('span.flag-message:visible').fadeOut();
// Send POST request
$.ajax({
type: 'POST',
url: element.href,
data: { js: true },
dataType: 'json',
success: function (data) {
data.link = $wrapper.get(0);
$.event.trigger('flagGlobalBeforeLinkUpdate', [data]);
if (!data.preventDefault) { // A handler may cancel updating the link.
data.link = updateLink(element, data.newLink);
}
// Find all the link wrappers on the page for this flag, but exclude
// the triggering element because Flag's own javascript updates it.
var $wrappers = $('.flag-wrapper.flag-' + data.flagName.flagNameToCSS() + '-' + data.contentId).not(data.link);
var $newLink = $(data.newLink);
// Hide message, because we want the message to be shown on the triggering element alone.
$('.flag-message', $newLink).hide();
// Finally, update the page.
$wrappers = $newLink.replaceAll($wrappers);
Drupal.attachBehaviors($wrappers.parent());
$.event.trigger('flagGlobalAfterLinkUpdate', [data]);
},
error: function (xmlhttp) {
alert('An HTTP error '+ xmlhttp.status +' occurred.\n'+ element.href);
$wrapper.removeClass('flag-waiting');
}
});
};
$('a.flag-link-toggle:not(.flag-processed)', context).addClass('flag-processed').click(flagClick);
};
/**
* Prevent anonymous flagging unless the user has JavaScript enabled.
*/
Drupal.flagAnonymousLinks = function(context) {
$('a.flag:not(.flag-anonymous-processed)', context).each(function() {
this.href += (this.href.match(/\?/) ? '&' : '?') + 'has_js=1';
$(this).addClass('flag-anonymous-processed');
});
}
String.prototype.flagNameToCSS = function() {
return this.replace(/_/g, '-');
}
/**
* A behavior specifically for anonymous users. Update links to the proper state.
*/
Drupal.flagAnonymousLinkTemplates = function(context) {
// Swap in current links. Cookies are set by PHP's setcookie() upon flagging.
var templates = Drupal.settings.flag.templates;
// Build a list of user-flags.
var userFlags = Drupal.flagCookie('flags');
if (userFlags) {
userFlags = userFlags.split('+');
for (var n in userFlags) {
var flagInfo = userFlags[n].match(/(\w+)_(\d+)/);
var flagName = flagInfo[1];
var contentId = flagInfo[2];
// User flags always default to off and the JavaScript toggles them on.
if (templates[flagName + '_' + contentId]) {
$('.flag-' + flagName.flagNameToCSS() + '-' + contentId, context).after(templates[flagName + '_' + contentId]).remove();
}
}
}
// Build a list of global flags.
var globalFlags = document.cookie.match(/flag_global_(\w+)_(\d+)=([01])/g);
if (globalFlags) {
for (var n in globalFlags) {
var flagInfo = globalFlags[n].match(/flag_global_(\w+)_(\d+)=([01])/);
var flagName = flagInfo[1];
var contentId = flagInfo[2];
var flagState = (flagInfo[3] == '1') ? 'flag' : 'unflag';
// Global flags are tricky, they may or may not be flagged in the page
// cache. The template always contains the opposite of the current state.
// So when checking global flag cookies, we need to make sure that we
// don't swap out the link when it's already in the correct state.
if (templates[flagName + '_' + contentId]) {
$('.flag-' + flagName.flagNameToCSS() + '-' + contentId, context).each(function() {
if ($(this).find('.' + flagState + '-action').size()) {
$(this).after(templates[flagName + '_' + contentId]).remove();
}
});
}
}
}
}
/**
* Utility function used to set Flag cookies.
*
* Note this is a direct copy of the jQuery cookie library.
* Written by Klaus Hartl.
*/
Drupal.flagCookie = function(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
// NOTE Needed to parenthesize options.path and options.domain
// in the following expressions, otherwise they evaluate to undefined
// in the packed version for some reason...
var path = options.path ? '; path=' + (options.path) : '';
var domain = options.domain ? '; domain=' + (options.domain) : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};
Drupal.behaviors.flagLink = {};
Drupal.behaviors.flagLink.attach = function(context) {
// For anonymous users with the page cache enabled, swap out links with their
// current state for the user.
if (Drupal.settings.flag && Drupal.settings.flag.templates) {
Drupal.flagAnonymousLinkTemplates(context);
}
// For all anonymous users, require JavaScript for flagging to prevent spiders
// from flagging things inadvertently.
if (Drupal.settings.flag && Drupal.settings.flag.anonymous) {
Drupal.flagAnonymousLinks(context);
}
// On load, bind the click behavior for all links on the page.
Drupal.flagLink(context);
};
})(jQuery);

View File

@@ -0,0 +1,59 @@
<?php
/**
* @file
* Default theme implementation to display a flag link, and a message after the action
* is carried out.
*
* Available variables:
*
* - $flag: The flag object itself. You will only need to use it when the
* following variables don't suffice.
* - $flag_name_css: The flag name, with all "_" replaced with "-". For use in 'class'
* attributes.
* - $flag_classes: A space-separated list of CSS classes that should be applied to the link.
*
* - $action: The action the link is about to carry out, either "flag" or "unflag".
* - $status: The status of the item; either "flagged" or "unflagged".
*
* - $link_href: The URL for the flag link.
* - $link_text: The text to show for the link.
* - $link_title: The title attribute for the link.
*
* - $message_text: The long message to show after a flag action has been carried out.
* - $message_classes: A space-separated list of CSS classes that should be applied to
* the message.
* - $after_flagging: This template is called for the link both before and after being
* flagged. If displaying to the user immediately after flagging, this value
* will be boolean TRUE. This is usually used in conjunction with immedate
* JavaScript-based toggling of flags.
* - $needs_wrapping_element: Determines whether the flag displays a wrapping
* HTML DIV element.
*
* Template suggestions available, listed from the most specific template to
* the least. Drupal will use the most specific template it finds:
* - flag--name.tpl.php
* - flag--link-type.tpl.php
*
* NOTE: This template spaces out the <span> tags for clarity only. When doing some
* advanced theming you may have to remove all the whitespace.
*/
?>
<?php if ($needs_wrapping_element): ?>
<div class="flag-outer flag-outer-<?php print $flag_name_css; ?>">
<?php endif; ?>
<span class="<?php print $flag_wrapper_classes; ?>">
<?php if ($link_href): ?>
<a href="<?php print $link_href; ?>" title="<?php print $link_title; ?>" class="<?php print $flag_classes ?>" rel="nofollow"><?php print $link_text; ?></a><span class="flag-throbber">&nbsp;</span>
<?php else: ?>
<span class="<?php print $flag_classes ?>"><?php print $link_text; ?></span>
<?php endif; ?>
<?php if ($after_flagging): ?>
<span class="<?php print $message_classes; ?>">
<?php print $message_text; ?>
</span>
<?php endif; ?>
</span>
<?php if ($needs_wrapping_element): ?>
</div>
<?php endif; ?>

View File

@@ -0,0 +1,25 @@
From d4d5acf83713c2a8413e1182294e887159c0e445 Mon Sep 17 00:00:00 2001
From: Bachir Soussi Chiadmi <bachir@g-u-i.net>
Date: Wed, 25 Sep 2013 15:25:25 +0200
Subject: [PATCH] flag 3.2 compatibility
---
flag_lists.module | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flag_lists.module b/flag_lists.module
index 13a5dd7..8b25b24 100644
--- a/flag_lists.module
+++ b/flag_lists.module
@@ -1413,7 +1413,7 @@ function flag_lists_page($action = NULL, $flag_name = NULL, $entity_id = NULL) {
// $flag->link_type = 'toggle';
$sid = flag_get_sid($user->uid);
$new_action = _flag_lists_is_flagged($flag, $entity_id, $user->uid, $sid) ? 'unflag' : 'flag';
- $new_link = $flag->theme($new_action, $entity_id, TRUE);
+ $new_link = $flag->theme($new_action, $entity_id, array("after_flagging" => TRUE));
flag_lists_fix_link($new_link, $new_action);
drupal_json_output(array(
'status' => TRUE,
--
1.8.1.5

View File

@@ -0,0 +1,526 @@
diff --git a/flag_lists.admin.inc b/flag_lists.admin.inc
index a71a11cc511ebba08169b334407985c8f501bc30..71d532209d15f2171dce7d0906131572f22b97b3 100644
--- a/flag_lists.admin.inc
+++ b/flag_lists.admin.inc
@@ -110,7 +110,7 @@ function flag_lists_add_js($type = NULL) {
}
// New flag. Load the template row.
- $query = db_select('flags', 'f');
+ $query = db_select('flag', 'f');
$query->leftJoin('flag_lists_types', 'fl', 'f.name = fl.name');
$query->fields('f')
->fields('fl')
@@ -119,7 +119,7 @@ function flag_lists_add_js($type = NULL) {
$row = $query->execute()
->fetchObject();
- $newflag = flag_flag::factory_by_content_type('node');
+ $newflag = flag_flag::factory_by_entity_type('node');
$flag = $newflag->factory_by_row($row);
// The template fid becomes the flag_lists parent flag.
@@ -243,7 +243,7 @@ function flag_lists_form_submit($form, $form_state, $account = NULL) {
else {
// New flag. Load the template row.
$type = $form_state['values']['type'];
- $query = db_select('flags', 'f');
+ $query = db_select('flag', 'f');
$query->leftJoin('flag_lists_types', 'fl', 'f.name = fl.name');
$query->fields('f')
->fields('fl')
@@ -252,7 +252,7 @@ function flag_lists_form_submit($form, $form_state, $account = NULL) {
$row = $query->execute()
->fetchObject();
- $newflag = flag_flag::factory_by_content_type('node');
+ $newflag = flag_flag::factory_by_entity_type('node');
$flag = $newflag->factory_by_row($row);
// The template fid becomes the flag_lists parent flag.
diff --git a/flag_lists.install b/flag_lists.install
index bd290d10d849d116a8ca68fb6abf0dcc93136f53..a84fd496888fb65c6cdddacaf37828ede23def76 100644
--- a/flag_lists.install
+++ b/flag_lists.install
@@ -30,7 +30,7 @@ function flag_lists_schema() {
'unsigned' => TRUE,
'not null' => TRUE,
),
- 'content_type' => array(
+ 'entity_type' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
@@ -72,13 +72,13 @@ function flag_lists_schema() {
'not null' => TRUE,
'default' => 0,
),
- 'content_type' => array(
+ 'entity_type' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'default' => '',
),
- 'content_id' => array(
+ 'entity_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
@@ -106,11 +106,11 @@ function flag_lists_schema() {
),
'primary key' => array('fcid'),
'unique keys' => array(
- 'fid_content_id_uid_sid' => array('fid', 'content_id', 'uid', 'sid'),
+ 'fid_entity_id_uid_sid' => array('fid', 'entity_id', 'uid', 'sid'),
),
'indexes' => array(
- 'content_type_content_id' => array('content_type', 'content_id'),
- 'content_type_uid_sid' => array('content_type', 'uid', 'sid'),
+ 'entity_type_entity_id' => array('entity_type', 'entity_id'),
+ 'entity_type_uid_sid' => array('entity_type', 'uid', 'sid'),
),
);
$schema['flag_lists_counts'] = array(
@@ -122,13 +122,13 @@ function flag_lists_schema() {
'not null' => TRUE,
'default' => 0,
),
- 'content_type' => array(
+ 'entity_type' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'default' => '',
),
- 'content_id' => array(
+ 'entity_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
@@ -143,10 +143,10 @@ function flag_lists_schema() {
'disp-width' => '10',
)
),
- 'primary key' => array('fid', 'content_id'),
+ 'primary key' => array('fid', 'entity_id'),
'indexes' => array(
- 'fid_content_type' => array('fid', 'content_type'),
- 'content_type_content_id' => array('content_type', 'content_id'),
+ 'fid_entity_type' => array('fid', 'entity_type'),
+ 'entity_type_entity_id' => array('entity_type', 'entity_id'),
'count' => array('count'),
),
);
@@ -190,13 +190,13 @@ function flag_lists_install() {
function flag_lists_uninstall() {
// Remove our template flags.
$query = db_select('flag_lists_types', 'fl');
- $query->leftJoin('flags', 'f', 'fl.name = f.name');
+ $query->leftJoin('flag', 'f', 'fl.name = f.name');
$query->addField('fl', 'fid', 'fid');
$query->distinct();
$fids = $query->execute();
foreach ($fids as $fid) {
- db_delete('flags')->condition('fid', $fid->fid);
+ db_delete('flag')->condition('fid', $fid->fid);
db_delete('flag_content')->condition('fid', $fid->fid);
db_delete('flag_types')->condition('fid', $fid->fid);
db_delete('flag_counts')->condition('fid', $fid->fid);
diff --git a/flag_lists.module b/flag_lists.module
index 8fde9dd12b25e5e82ff79204fd800139460cd7e7..13a5dd77ead6832901a6fb0aac6511c0bc62eb3b 100644
--- a/flag_lists.module
+++ b/flag_lists.module
@@ -214,7 +214,7 @@ function theme_flag_lists_user_list($variables) {
$content = flag_lists_get_flagged_content($fid, $uid);
foreach ($content as $item) {
if ($item->content_type == 'node') {
- $node = node_load($item->content_id);
+ $node = node_load($item->entity_id);
$items[] = l($node->title, 'node/' . $node->nid);
}
}
@@ -811,7 +811,7 @@ function flag_lists_set_messages(&$flag) {
*
* Make sure a user can only see his/her own personal flags.
*/
-function flag_lists_flag_access($flag, $content_id, $action, $account) {
+function flag_lists_flag_access($flag, $entity_id, $action, $account) {
if (!empty($flag->module) && $flag->module == 'flag_lists') {
switch ($action) {
case 'flag':
@@ -936,7 +936,7 @@ function flag_lists_get_user_flags($content_type = NULL, $account = NULL, $use_f
$query = db_select('flag_lists_flags', 'fl')
->fields('fl')
->condition('fl.uid', $account->uid);
- $query->leftJoin('flags', 'f', 'fl.pfid = f.fid');
+ $query->leftJoin('flag', 'f', 'fl.pfid = f.fid');
$query->leftJoin('flag_lists_types', 'ft', 'ft.name = f.name');
$query->addField('ft', 'type');
if ($content_type) {
@@ -1011,12 +1011,12 @@ function theme_flag_lists_list($variables) {
// Make sure we have a node.
if (is_object($node) && user_access('create flag lists')) {
$content_type = $node->type;
- $content_id = $node->nid;
+ $entity_id = $node->nid;
}
// Or at least confirm we are on a node page and use has access.
elseif (arg(0) == 'node' && is_numeric(arg(1)) && user_access('create flag lists')) {
- $content_id = arg(1);
- $query = db_select('node')->condition('nid', $content_id);
+ $entity_id = arg(1);
+ $query = db_select('node')->condition('nid', $entity_id);
$query->addField('node', 'type');
$content_type = $query->execute()->fetchField();
}
@@ -1034,19 +1034,19 @@ function theme_flag_lists_list($variables) {
// Build the list of lists for this node.
foreach ($flags as $flag) {
if ($flag->module == 'flag_lists') {
- $action = _flag_lists_is_flagged($flag, $content_id, $user->uid, 0) ? 'unflag' : 'flag';
+ $action = _flag_lists_is_flagged($flag, $entity_id, $user->uid, 0) ? 'unflag' : 'flag';
}
else {
- $action = $flag->is_flagged($content_id) ? 'unflag' : 'flag';;
+ $action = $flag->is_flagged($entity_id) ? 'unflag' : 'flag';;
}
// Do we need the ops?
if ($ops && $flag->module == 'flag_lists') {
$ops_links = theme('flag_lists_ops', array('flag' => $flag));
- $link = $flag->theme($action, $content_id) . $ops_links;
+ $link = $flag->theme($action, $entity_id) . $ops_links;
}
else {
- $link = $flag->theme($action, $content_id);
+ $link = $flag->theme($action, $entity_id);
}
// If it's a list, fix the link.
@@ -1201,12 +1201,12 @@ function flag_lists_get_flagged_content($fid, $uid) {
* The full flag object of for the flag link being generated.
* @param $action
* The action this link will perform. Either 'flag' or 'unflag'.
- * @param $content_id
+ * @param $entity_id
* The ID of the node, comment, user, or other object being flagged.
* @return
* An array defining properties of the link.
*/
-function flag_lists_flag_link($flag, $action, $content_id) {
+function flag_lists_flag_link($flag, $action, $entity_id) {
return array();
}
@@ -1224,30 +1224,61 @@ function flag_lists_flag_link_types() {
function flag_lists_flag_default_flags($name = 'fl_template') {
- return array(
- array(
- 'api_version' => 2,
- 'name' => $name,
- 'module' => 'flag_lists',
- 'content_type' => 'node',
- 'global' => 0,
- 'show_on_page' => 0,
- 'show_on_teaser' => 0,
- 'show_on_form' => 0,
- // The following UI labels aren't wrapped in t() because they are written
- // to the DB in English. They are passed to t() later, thus allowing for
- // multilingual sites.
- 'title' => 'Flag lists template',
- 'flag_short' => 'Add to your [flag_lists:title] [flag_lists:term]',
- 'flag_long' => 'Add this post to your [flag_lists:title] [flag_lists:term]',
- 'flag_message' => 'This post has been added to your [flag_lists:title] [flag_lists:term]',
- 'unflag_short' => 'Remove this from your [flag_lists:title] [flag_lists:term]',
- 'unflag_long' => 'Remove this post from your [flag_lists:title] [flag_lists:term]',
- 'unflag_message' => 'This post has been removed from your [flag_lists:title] [flag_lists:term]',
- 'types' => array(),
- 'link_type' => 'toggle',
- ),
- );
+ // return array(
+ // array(
+ // 'api_version' => 2,
+ // 'name' => $name,
+ // 'module' => 'flag_lists',
+ // 'content_type' => 'node',
+ // 'global' => 0,
+ // 'show_on_page' => 0,
+ // 'show_on_teaser' => 0,
+ // 'show_on_form' => 0,
+ // // The following UI labels aren't wrapped in t() because they are written
+ // // to the DB in English. They are passed to t() later, thus allowing for
+ // // multilingual sites.
+ // 'title' => 'Flag lists template',
+ // 'flag_short' => 'Add to your [flag_lists:title] [flag_lists:term]',
+ // 'flag_long' => 'Add this post to your [flag_lists:title] [flag_lists:term]',
+ // 'flag_message' => 'This post has been added to your [flag_lists:title] [flag_lists:term]',
+ // 'unflag_short' => 'Remove this from your [flag_lists:title] [flag_lists:term]',
+ // 'unflag_long' => 'Remove this post from your [flag_lists:title] [flag_lists:term]',
+ // 'unflag_message' => 'This post has been removed from your [flag_lists:title] [flag_lists:term]',
+ // 'types' => array(),
+ // 'link_type' => 'toggle',
+ // ),
+ // );
+
+$flags = array();
+// Exported flag: "Flag lists template".
+$flags['fl_template'] = array(
+ 'entity_type' => 'node',
+ 'title' => 'Flag lists template',
+ 'global' => 0,
+ 'types' => array(),
+ 'flag_short' => 'Add to your [flag_lists:title] [flag_lists:term]',
+ 'flag_long' => 'Add this post to your [flag_lists:title] [flag_lists:term]',
+ 'flag_message' => 'This post has been added to your [flag_lists:title] [flag_lists:term]',
+ 'unflag_short' => 'Remove this from your [flag_lists:title] [flag_lists:term]',
+ 'unflag_long' => 'Remove this post from your [flag_lists:title] [flag_lists:term]',
+ 'unflag_message' => 'This post has been removed from your [flag_lists:title] [flag_lists:term]',
+ 'unflag_denied_text' => '',
+ 'link_type' => 'toggle',
+ 'weight' => 0,
+ 'api_version' => 3,
+ 'module' => 'flag_lists',
+ 'show_on_page' => 0,
+ 'show_on_teaser' => 0,
+ 'show_on_form' => 0,
+ 'status' => FALSE,
+ 'import_roles' => array(
+ 'flag' => array(),
+ 'unflag' => array(),
+ ),
+);
+return $flags;
+
+
}
/**
@@ -1294,7 +1325,7 @@ function flag_lists_insert($flag) {
->fields(array(
'pfid' => $flag->pfid,
'uid' => $flag->uid,
- 'content_type' => $flag->content_type,
+ 'entity_type' => $flag->entity_type,
'name' => $flag->name,
'title' => $flag->title,
'options' => $flag->get_serialized_options($flag),
@@ -1330,7 +1361,7 @@ function flag_lists_fl_delete($flag, $account = NULL) {
* Used both for the regular callback as well as the JS version. We use this
* instead of the flag module's because our flags are not in the flags table.
*/
-function flag_lists_page($action = NULL, $flag_name = NULL, $content_id = NULL) {
+function flag_lists_page($action = NULL, $flag_name = NULL, $entity_id = NULL) {
global $user;
// Shorten up the variables that affect the behavior of this page.
@@ -1341,7 +1372,7 @@ function flag_lists_page($action = NULL, $flag_name = NULL, $content_id = NULL)
$has_js = isset($_GET['has_js']);
// Check the flag token, then perform the flagging.
- if (!flag_check_token($token, $content_id)) {
+ if (!flag_check_token($token, $entity_id)) {
$error = t('Bad token. You seem to have followed an invalid link.');
}
elseif ($user->uid == 0 && !$has_js) {
@@ -1355,7 +1386,7 @@ function flag_lists_page($action = NULL, $flag_name = NULL, $content_id = NULL)
// Identify it as ours.
$flag->module = 'flag_lists';
- flag_lists_do_flag($flag, $action, $content_id);
+ flag_lists_do_flag($flag, $action, $entity_id);
}
// If an error was received, set a message and exit.
@@ -1381,14 +1412,14 @@ function flag_lists_page($action = NULL, $flag_name = NULL, $content_id = NULL)
// $flag = flag_lists_get_flag($flag_name);
// $flag->link_type = 'toggle';
$sid = flag_get_sid($user->uid);
- $new_action = _flag_lists_is_flagged($flag, $content_id, $user->uid, $sid) ? 'unflag' : 'flag';
- $new_link = $flag->theme($new_action, $content_id, TRUE);
+ $new_action = _flag_lists_is_flagged($flag, $entity_id, $user->uid, $sid) ? 'unflag' : 'flag';
+ $new_link = $flag->theme($new_action, $entity_id, TRUE);
flag_lists_fix_link($new_link, $new_action);
drupal_json_output(array(
'status' => TRUE,
'newLink' => $new_link,
// Further information for the benefit of custom JavaScript event handlers:
- 'contentId' => $content_id,
+ 'contentId' => $entity_id,
'contentType' => $flag->content_type,
'flagName' => $flag->name,
'flagStatus' => $action,
@@ -1397,7 +1428,7 @@ function flag_lists_page($action = NULL, $flag_name = NULL, $content_id = NULL)
}
else {
$flag = flag_lists_get_flag($flag->fid);
- drupal_set_message($flag->get_label($action . '_message', $content_id));
+ drupal_set_message($flag->get_label($action . '_message', $entity_id));
drupal_goto();
}
}
@@ -1413,7 +1444,7 @@ function flag_lists_fix_link(&$link, $action) {
*
* @param $action
* Either 'flag' or 'unflag'.
- * @param $content_id
+ * @param $entity_id
* The ID of the item to flag or unflag.
* @param $account
* The user on whose behalf to flag. Leave empty for the current user.
@@ -1423,7 +1454,7 @@ function flag_lists_fix_link(&$link, $action) {
* FALSE if some error occured (e.g., user has no permission, flag isn't
* applicable to the item, etc.), TRUE otherwise.
*/
- function flag_lists_do_flag($flag, $action, $content_id, $account = NULL, $skip_permission_check = FALSE) {
+ function flag_lists_do_flag($flag, $action, $entity_id, $account = NULL, $skip_permission_check = FALSE) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
@@ -1431,7 +1462,7 @@ function flag_lists_fix_link(&$link, $action) {
return FALSE;
}
if (!$skip_permission_check) {
- if (!$flag->access($content_id, $action, $account)) {
+ if (!$flag->access($entity_id, $action, $account)) {
// User has no permission to flag/unflag this object.
return FALSE;
}
@@ -1439,7 +1470,7 @@ function flag_lists_fix_link(&$link, $action) {
else {
// We are skipping permission checks. However, at a minimum we must make
// sure the flag applies to this content type:
- if (!$flag->applies_to_content_id($content_id)) {
+ if (!$flag->applies_to_content_id($entity_id)) {
return FALSE;
}
}
@@ -1460,17 +1491,17 @@ function flag_lists_fix_link(&$link, $action) {
// Perform the flagging or unflagging of this flag. We invoke hook_flag here
// because we do our own flagging.
- $flagged = _flag_lists_is_flagged($flag, $content_id, $uid, $sid);
+ $flagged = _flag_lists_is_flagged($flag, $entity_id, $uid, $sid);
if ($action == 'unflag') {
if ($flagged) {
- $fcid = _flag_lists_unflag($flag, $content_id, $uid, $sid);
- module_invoke_all('flag', 'unflag', $flag, $content_id, $account, $fcid);
+ $fcid = _flag_lists_unflag($flag, $entity_id, $uid, $sid);
+ module_invoke_all('flag', 'unflag', $flag, $entity_id, $account, $fcid);
}
}
elseif ($action == 'flag') {
if (!$flagged) {
- $fcid = _flag_lists_flag($flag, $content_id, $uid, $sid);
- module_invoke_all('flag', 'flag', $flag, $content_id, $account, $fcid);
+ $fcid = _flag_lists_flag($flag, $entity_id, $uid, $sid);
+ module_invoke_all('flag', 'flag', $flag, $entity_id, $account, $fcid);
}
}
@@ -1487,12 +1518,12 @@ function flag_lists_fix_link(&$link, $action) {
* bypass it.
*
*/
- function _flag_lists_is_flagged($flag, $content_id, $uid, $sid) {
+ function _flag_lists_is_flagged($flag, $entity_id, $uid, $sid) {
$query = db_select('flag_lists_content')
->condition('fid', $flag->fid)
->condition('uid', $uid)
->condition('sid', $sid)
- ->condition('content_id', $content_id);
+ ->condition('entity_id', $entity_id);
$query->addField('flag_lists_content', 'fid');
return $query->execute()->fetchField();
}
@@ -1504,19 +1535,19 @@ function flag_lists_fix_link(&$link, $action) {
* flag_lists_do_flag() function instead.
*
*/
- function _flag_lists_flag($flag, $content_id, $uid, $sid) {
+ function _flag_lists_flag($flag, $entity_id, $uid, $sid) {
$fcid = db_insert('flag_lists_content')
->fields(array(
'fid' => $flag->fid,
- 'content_type' => $flag->content_type,
- 'content_id' => $content_id,
+ 'entity_type' => $flag->entity_type,
+ 'entity_id' => $entity_id,
'uid' => $uid,
'sid' => $sid,
'timestamp' => REQUEST_TIME,
))
->execute();
- _flag_lists_update_count($flag, $content_id);
+ _flag_lists_update_count($flag, $entity_id);
return $fcid;
}
@@ -1527,10 +1558,10 @@ function flag_lists_fix_link(&$link, $action) {
* flag_lists_do_flag() function instead.
*
*/
- function _flag_lists_unflag($flag, $content_id, $uid, $sid) {
+ function _flag_lists_unflag($flag, $entity_id, $uid, $sid) {
$query = db_select('flag_lists_content')
->condition('fid', $flag->fid)
- ->condition('content_id', $content_id)
+ ->condition('entity_id', $entity_id)
->condition('uid', $uid)
->condition('sid', $sid);
$query->addField('flag_lists_content', 'fcid');
@@ -1539,7 +1570,7 @@ function flag_lists_fix_link(&$link, $action) {
db_delete('flag_lists_content')
->condition('fcid', $fcid)
->execute();
- _flag_lists_update_count($flag, $content_id);
+ _flag_lists_update_count($flag, $entity_id);
}
return $fcid;
}
@@ -1547,11 +1578,11 @@ function flag_lists_fix_link(&$link, $action) {
/**
* Updates the flag count for this content
*/
- function _flag_lists_update_count($flag, $content_id) {
+ function _flag_lists_update_count($flag, $entity_id) {
$count = db_select('flag_lists_content', 'f')
->fields('f')
->condition('fid', $flag->fid)
- ->condition('content_id', $content_id)
+ ->condition('entity_id', $entity_id)
->countQuery()
->execute()
->fetchField();
@@ -1559,7 +1590,7 @@ function flag_lists_fix_link(&$link, $action) {
if (empty($count)) {
$num_deleted = db_delete('flag_lists_counts')
->condition('fid', $flag->fid)
- ->condition('content_id', $content_id)
+ ->condition('entity_id', $entity_id)
->execute();
}
else {
@@ -1568,14 +1599,14 @@ function flag_lists_fix_link(&$link, $action) {
'count' => $count,
))
->condition('fid', $flag->fid)
- ->condition('content_id', $content_id)
+ ->condition('entity_id', $entity_id)
->execute();
if (empty($num_updated)) {
db_insert('flag_lists_counts')
->fields(array(
'fid' => $flag->fid,
- 'content_type' => $flag->content_type,
- 'content_id' => $content_id,
+ 'entity_type' => $flag->entity_type,
+ 'entity_id' => $entity_id,
'count' => $count,
))
->execute();