first import
This commit is contained in:
339
sites/all/modules/views_bulk_operations/LICENSE.txt
Normal file
339
sites/all/modules/views_bulk_operations/LICENSE.txt
Normal 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.
|
13
sites/all/modules/views_bulk_operations/README.txt
Normal file
13
sites/all/modules/views_bulk_operations/README.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
Views Bulk Operations augments Views by allowing bulk operations
|
||||
(provided by Drupal core or Rules) to be executed on the displayed rows.
|
||||
It does so by showing a checkbox in front of each displayed row, and adding a
|
||||
select box on top of the View containing operations that can be applied.
|
||||
|
||||
Getting started
|
||||
-----------------
|
||||
1. Create a View.
|
||||
2. Add a "Bulk operations" field, available to all entity types.
|
||||
3. Configure the field by selecting at least one operation.
|
||||
4. Go to the View page. VBO functionality should be present.
|
||||
|
||||
Read the full documentation at http://drupal.org/node/1591342.
|
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides an action for creating a zip archive of selected files.
|
||||
* An entry in the {file_managed} table is created for the newly created archive,
|
||||
* and it is marked as permanent or temporary based on the operation settings.
|
||||
*/
|
||||
|
||||
function views_bulk_operations_archive_action_info() {
|
||||
$actions = array();
|
||||
if (function_exists('zip_open')) {
|
||||
$actions['views_bulk_operations_archive_action'] = array(
|
||||
'type' => 'file',
|
||||
'label' => t('Create an archive of selected files'),
|
||||
// This action only works when invoked through VBO. That's why it's
|
||||
// declared as non-configurable to prevent it from being shown in the
|
||||
// "Create an advanced action" dropdown on admin/config/system/actions.
|
||||
'configurable' => FALSE,
|
||||
'vbo_configurable' => TRUE,
|
||||
'triggers' => array('any'),
|
||||
);
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since Drupal's Archiver doesn't abstract properly the archivers it implements
|
||||
* (Archive_Tar and ZipArchive), it can't be used here.
|
||||
*/
|
||||
function views_bulk_operations_archive_action($file, $context) {
|
||||
global $user;
|
||||
static $archive_contents = array();
|
||||
|
||||
// Adding a non-existent file to the archive crashes ZipArchive on close().
|
||||
if (file_exists($file->uri)) {
|
||||
$destination = $context['destination'];
|
||||
$zip = new ZipArchive();
|
||||
// If the archive already exists, open it. If not, create it.
|
||||
if (file_exists($destination)) {
|
||||
$opened = $zip->open(drupal_realpath($destination));
|
||||
}
|
||||
else {
|
||||
$opened = $zip->open(drupal_realpath($destination), ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
|
||||
}
|
||||
|
||||
if ($opened) {
|
||||
// Create a list of all files in the archive. Used for duplicate checking.
|
||||
if (empty($archive_contents)) {
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$archive_contents[] = $zip->getNameIndex($i);
|
||||
}
|
||||
}
|
||||
// Make sure that the target filename is unique.
|
||||
$filename = _views_bulk_operations_archive_action_create_filename(basename($file->uri), $archive_contents);
|
||||
// Note that the actual addition happens on close(), hence the need
|
||||
// to open / close the archive each time the action runs.
|
||||
$zip->addFile(drupal_realpath($file->uri), $filename);
|
||||
$zip->close();
|
||||
$archive_contents[] = $filename;
|
||||
}
|
||||
}
|
||||
|
||||
// The operation is complete, create a file entity and provide a download
|
||||
// link to the user.
|
||||
if ($context['progress']['current'] == $context['progress']['total']) {
|
||||
$archive_file = new stdClass();
|
||||
$archive_file->uri = $destination;
|
||||
$archive_file->filename = basename($destination);
|
||||
$archive_file->filemime = file_get_mimetype($destination);
|
||||
$archive_file->uid = $user->uid;
|
||||
$archive_file->status = $context['settings']['temporary'] ? FALSE : FILE_STATUS_PERMANENT;
|
||||
file_save($archive_file);
|
||||
|
||||
$url = file_create_url($archive_file->uri);
|
||||
$url = l($url, $url, array('absolute' => TRUE));
|
||||
_views_bulk_operations_log(t('An archive has been created and can be downloaded from: !url', array('!url' => $url)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration form shown to the user before the action gets executed.
|
||||
*/
|
||||
function views_bulk_operations_archive_action_form($context) {
|
||||
// Pass the scheme as a value, so that the submit callback can access it.
|
||||
$form['scheme'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $context['settings']['scheme'],
|
||||
);
|
||||
|
||||
$form['filename'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Filename'),
|
||||
'#default_value' => 'vbo_archive_' . date('Ymd'),
|
||||
'#field_suffix' => '.zip',
|
||||
'#description' => t('The name of the archive file.'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles a sanitized and unique URI for the archive, and returns it for
|
||||
* usage by the action callback (views_bulk_operations_archive_action).
|
||||
*/
|
||||
function views_bulk_operations_archive_action_submit($form, $form_state) {
|
||||
// Validate the scheme, fallback to public if it's somehow invalid.
|
||||
$scheme = $form_state['values']['scheme'];
|
||||
if (!file_stream_wrapper_valid_scheme($scheme)) {
|
||||
$scheme = 'public';
|
||||
}
|
||||
$destination = $scheme . '://' . basename($form_state['values']['filename']) . '.zip';
|
||||
// If the chosen filename already exists, file_destination() will append
|
||||
// an integer to it in order to make it unique.
|
||||
$destination = file_destination($destination, FILE_EXISTS_RENAME);
|
||||
|
||||
return array(
|
||||
'destination' => $destination,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings form (embedded into the VBO field settings in the Views UI).
|
||||
*/
|
||||
function views_bulk_operations_archive_action_views_bulk_operations_form($options) {
|
||||
$scheme_options = array();
|
||||
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL) as $scheme => $stream_wrapper) {
|
||||
$scheme_options[$scheme] = $stream_wrapper['name'];
|
||||
}
|
||||
if (count($scheme_options) > 1) {
|
||||
$form['scheme'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Storage'),
|
||||
'#options' => $scheme_options,
|
||||
'#default_value' => !empty($options['scheme']) ? $options['scheme'] : variable_get('file_default_scheme', 'public'),
|
||||
'#description' => t('Select where the archive should be stored. Private file storage has significantly more overhead than public files, but allows restricted access.'),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$scheme_option_keys = array_keys($scheme_options);
|
||||
$form['scheme'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => reset($scheme_option_keys),
|
||||
);
|
||||
}
|
||||
|
||||
$form['temporary'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Temporary'),
|
||||
'#default_value' => isset($options['temporary']) ? $options['temporary'] : TRUE,
|
||||
'#description' => t('Temporary files older than 6 hours are removed when cron runs.'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sanitized and unique version of the provided filename.
|
||||
*
|
||||
* @param $filename
|
||||
* String filename
|
||||
*
|
||||
* @return
|
||||
* The new filename.
|
||||
*/
|
||||
function _views_bulk_operations_archive_action_create_filename($filename, $archive_list) {
|
||||
// Strip control characters (ASCII value < 32). Though these are allowed in
|
||||
// some filesystems, not many applications handle them well.
|
||||
$filename = preg_replace('/[\x00-\x1F]/u', '_', $filename);
|
||||
if (substr(PHP_OS, 0, 3) == 'WIN') {
|
||||
// These characters are not allowed in Windows filenames
|
||||
$filename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $filename);
|
||||
}
|
||||
|
||||
if (in_array($filename, $archive_list)) {
|
||||
// Destination file already exists, generate an alternative.
|
||||
$pos = strrpos($filename, '.');
|
||||
if ($pos !== FALSE) {
|
||||
$name = substr($filename, 0, $pos);
|
||||
$ext = substr($filename, $pos);
|
||||
}
|
||||
else {
|
||||
$name = $filename;
|
||||
$ext = '';
|
||||
}
|
||||
|
||||
$counter = 0;
|
||||
do {
|
||||
$filename = $name . '_' . $counter++ . $ext;
|
||||
} while (in_array($filename, $archive_list));
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Passes selected ids as arguments to a page.
|
||||
* The ids might be entity ids or revision ids, depending on the type of the
|
||||
* VBO field.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of hook_action_info().
|
||||
*/
|
||||
function views_bulk_operations_argument_selector_action_info() {
|
||||
return array(
|
||||
'views_bulk_operations_argument_selector_action' => array(
|
||||
'label' => t('Pass ids as arguments to a page'),
|
||||
'type' => 'entity',
|
||||
'aggregate' => TRUE,
|
||||
'configurable' => FALSE,
|
||||
'hooks' => array(),
|
||||
'triggers' => array('any'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of a Drupal action.
|
||||
* Passes selected ids as arguments to a view.
|
||||
*/
|
||||
function views_bulk_operations_argument_selector_action($entities, $context = array()) {
|
||||
$base_url = $context['settings']['url'];
|
||||
$arguments = implode(',', array_keys($entities));
|
||||
// Add a trailing slash if missing.
|
||||
if (substr($base_url, -1, 1) != '/') {
|
||||
$base_url .= '/';
|
||||
}
|
||||
drupal_goto($base_url . $arguments);
|
||||
}
|
||||
|
||||
function views_bulk_operations_argument_selector_action_views_bulk_operations_form($options) {
|
||||
$form['url'] = array(
|
||||
'#title' => t('URL'),
|
||||
'#type' => 'textfield',
|
||||
'#description' => t('Enter a URL that the user will be sent to. A comma-separated list of selected ids will be appended.'),
|
||||
'#default_value' => isset($options['url']) ? $options['url'] : '',
|
||||
'#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
|
||||
);
|
||||
return $form;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implements a generic entity delete action. Uses Entity API if available.
|
||||
*/
|
||||
|
||||
function views_bulk_operations_delete_action_info() {
|
||||
return array(
|
||||
'views_bulk_operations_delete_item' => array(
|
||||
'type' => 'entity',
|
||||
'label' => t('Delete item'),
|
||||
'configurable' => FALSE,
|
||||
'behavior' => array('deletes_property'),
|
||||
'triggers' => array('any'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function views_bulk_operations_delete_item($entity, $context) {
|
||||
$info = entity_get_info($context['entity_type']);
|
||||
$entity_id = $entity->{$info['entity keys']['id']};
|
||||
|
||||
entity_delete($context['entity_type'], $entity_id);
|
||||
}
|
@@ -0,0 +1,580 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file VBO action to modify entity values (properties and fields).
|
||||
*/
|
||||
|
||||
// Specifies that all available values should be shown to the user for editing.
|
||||
define('VBO_MODIFY_ACTION_ALL', '_all_');
|
||||
|
||||
function views_bulk_operations_modify_action_info() {
|
||||
return array('views_bulk_operations_modify_action' => array(
|
||||
'type' => 'entity',
|
||||
'label' => t('Modify entity values'),
|
||||
'behavior' => array('changes_property'),
|
||||
// This action only works when invoked through VBO. That's why it's
|
||||
// declared as non-configurable to prevent it from being shown in the
|
||||
// "Create an advanced action" dropdown on admin/config/system/actions.
|
||||
'configurable' => FALSE,
|
||||
'vbo_configurable' => TRUE,
|
||||
'triggers' => array('any'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action function.
|
||||
*
|
||||
* Goes through new values and uses them to modify the passed entity by either
|
||||
* replacing the existing values, or appending to them (based on user input).
|
||||
*/
|
||||
function views_bulk_operations_modify_action($entity, $context) {
|
||||
list(,,$bundle_name) = entity_extract_ids($context['entity_type'], $entity);
|
||||
|
||||
// Handle Field API fields.
|
||||
if (!empty($context['selected']['bundle_' . $bundle_name])) {
|
||||
// The pseudo entity is cloned so that changes to it don't get carried
|
||||
// over to the next execution.
|
||||
$pseudo_entity = clone $context['entities'][$bundle_name];
|
||||
foreach ($context['selected']['bundle_' . $bundle_name] as $key) {
|
||||
// Replace any tokens that might exist in the field columns.
|
||||
foreach ($pseudo_entity->{$key}[LANGUAGE_NONE] as $delta => &$item) {
|
||||
foreach ($item as $column => $value) {
|
||||
if (is_string($value)) {
|
||||
$item[$column] = token_replace($value, array($context['entity_type'] => $entity), array('sanitize' => FALSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($key, $context['append']['bundle_' . $bundle_name]) && !empty($entity->$key)) {
|
||||
$entity->{$key}[LANGUAGE_NONE] = array_merge($entity->{$key}[LANGUAGE_NONE], $pseudo_entity->{$key}[LANGUAGE_NONE]);
|
||||
|
||||
// Check if we breached cardinality, and notify the user.
|
||||
$field_info = field_info_field($key);
|
||||
$field_count = count($entity->{$key}[LANGUAGE_NONE]);
|
||||
if ($field_info['cardinality'] != FIELD_CARDINALITY_UNLIMITED && $field_count > $field_info['cardinality']) {
|
||||
$entity_label = entity_label($context['entity_type'], $entity);
|
||||
$warning = t('Tried to set !field_count values for field !field_name that supports a maximum of !cardinality.',
|
||||
array('!field_count' => $field_count,
|
||||
'!field_name' => $field_info['field_name'],
|
||||
'!cardinality' => $field_info['cardinality']));
|
||||
drupal_set_message($warning, 'warning', FALSE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$entity->$key = $pseudo_entity->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle properties.
|
||||
if (!empty($context['selected']['properties'])) {
|
||||
// Use the wrapper to set property values, since some properties need
|
||||
// additional massaging by their setter callbacks.
|
||||
// The wrapper will automatically modify $entity itself.
|
||||
$wrapper = entity_metadata_wrapper($context['entity_type'], $entity);
|
||||
foreach ($context['selected']['properties'] as $key) {
|
||||
if (in_array($key, $context['append']['properties'])) {
|
||||
$old_values = $wrapper->$key->value();
|
||||
$wrapper->$key->set($context['properties'][$key]);
|
||||
$new_values = $wrapper->{$key}->value();
|
||||
$all_values = array_merge($old_values, $new_values);
|
||||
$wrapper->$key->set($all_values);
|
||||
}
|
||||
else {
|
||||
$value = $context['properties'][$key];
|
||||
if (is_string($value)) {
|
||||
$value = token_replace($value, array($context['entity_type'] => $entity), array('sanitize' => FALSE));
|
||||
}
|
||||
$wrapper->$key->set($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action form function.
|
||||
*
|
||||
* Displays form elements for properties acquired through Entity Metadata
|
||||
* (hook_entity_property_info()), as well as field widgets for each
|
||||
* entity bundle, as provided by field_attach_form().
|
||||
*/
|
||||
function views_bulk_operations_modify_action_form($context, &$form_state) {
|
||||
// This action form uses admin-provided settings. If they were not set, pull the defaults now.
|
||||
if (!isset($context['settings'])) {
|
||||
$context['settings'] = views_bulk_operations_modify_action_views_bulk_operations_form_options();
|
||||
}
|
||||
|
||||
$form_state['entity_type'] = $entity_type = $context['entity_type'];
|
||||
// For Field API integration to work, a pseudo-entity is constructed for each
|
||||
// bundle that has fields available for editing.
|
||||
// The entities then get passed to Field API functions
|
||||
// (field_attach_form(), field_attach_form_validate(), field_attach_submit()),
|
||||
// and filled with form data.
|
||||
// After submit, the pseudo-entities get passed to the actual action
|
||||
// (views_bulk_operations_modify_action()) which copies the data from the
|
||||
// relevant pseudo-entity constructed here to the actual entity being modified.
|
||||
$form_state['entities'] = array();
|
||||
|
||||
$info = entity_get_info($entity_type);
|
||||
$properties = _views_bulk_operations_modify_action_get_properties($entity_type, $context['settings']['display_values']);
|
||||
$bundles = _views_bulk_operations_modify_action_get_bundles($entity_type, $context);
|
||||
|
||||
$form['#attached']['css'][] = drupal_get_path('module', 'views_bulk_operations') . '/css/modify.action.css';
|
||||
$form['#tree'] = TRUE;
|
||||
|
||||
if (!empty($properties)) {
|
||||
$form['properties'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => 'Properties',
|
||||
);
|
||||
$form['properties']['show_value'] = array(
|
||||
'#suffix' => '<div class="clearfix"></div>',
|
||||
);
|
||||
|
||||
foreach ($properties as $key => $property) {
|
||||
$form['properties']['show_value'][$key] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $property['label'],
|
||||
);
|
||||
|
||||
$determined_type = ($property['type'] == 'boolean') ? 'checkbox' : 'textfield';
|
||||
$form['properties'][$key] = array(
|
||||
'#type' => $determined_type,
|
||||
'#title' => $property['label'],
|
||||
'#description' => $property['description'],
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
'#edit-properties-show-value-' . str_replace('_', '-', $key) => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!empty($property['options list'])) {
|
||||
$form['properties'][$key]['#type'] = 'select';
|
||||
$form['properties'][$key]['#options'] = $property['options list']($key, array());
|
||||
|
||||
if ($property['type'] == 'list') {
|
||||
$form['properties'][$key]['#type'] = 'checkboxes';
|
||||
|
||||
$form['properties']['_append::' . $key] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Add new value(s) to %label, instead of overwriting the existing values.', array('%label' => $property['label'])),
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
'#edit-properties-show-value-' . $key => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($bundles as $bundle_name => $bundle) {
|
||||
$bundle_key = $info['entity keys']['bundle'];
|
||||
$default_values = array();
|
||||
// If the bundle key exists, it must always be set on an entity.
|
||||
if (!empty($bundle_key)) {
|
||||
$default_values[$bundle_key] = $bundle_name;
|
||||
}
|
||||
$entity = entity_create($context['entity_type'], $default_values);
|
||||
$form_state['entities'][$bundle_name] = $entity;
|
||||
|
||||
// Show the more detailed label only if the entity type has multiple bundles.
|
||||
// Otherwise, it would just be confusing.
|
||||
if (count($info['bundles']) > 1) {
|
||||
$label = t('Fields for @bundle_key @label', array('@bundle_key' => $bundle_key, '@label' => $bundle['label']));
|
||||
}
|
||||
else {
|
||||
$label = t('Fields');
|
||||
}
|
||||
|
||||
$form_key = 'bundle_' . $bundle_name;
|
||||
$form[$form_key] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $label,
|
||||
'#parents' => array($form_key),
|
||||
);
|
||||
field_attach_form($context['entity_type'], $entity, $form[$form_key], $form_state, LANGUAGE_NONE);
|
||||
// Now that all the widgets have been added, sort them by #weight.
|
||||
// This ensures that they will stay in the correct order when they get
|
||||
// assigned new weights.
|
||||
uasort($form[$form_key], 'element_sort');
|
||||
|
||||
$display_values = $context['settings']['display_values'];
|
||||
$instances = field_info_instances($entity_type, $bundle_name);
|
||||
$weight = 0;
|
||||
foreach (element_get_visible_children($form[$form_key]) as $field_name) {
|
||||
// For our use case it makes no sense for any field widget to be required.
|
||||
$language = $form[$form_key][$field_name]['#language'];
|
||||
_views_bulk_operations_modify_action_unset_required($form[$form_key][$field_name][$language]);
|
||||
|
||||
// The admin has specified which fields to display, but this field didn't
|
||||
// make the cut. Hide it with #access => FALSE and move on.
|
||||
if (empty($display_values[VBO_MODIFY_ACTION_ALL]) && empty($display_values[$bundle_name . '::' . $field_name])) {
|
||||
$form[$form_key][$field_name]['#access'] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
$field = $instances[$field_name];
|
||||
$form[$form_key]['show_value'][$field_name] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $field['label'],
|
||||
);
|
||||
$form[$form_key][$field_name]['#states'] = array(
|
||||
'visible' => array(
|
||||
'#edit-bundle-' . str_replace('_', '-', $bundle_name) . '-show-value-' . str_replace('_', '-', $field_name) => array('checked' => TRUE),
|
||||
),
|
||||
);
|
||||
// All field widgets get reassigned weights so that additional elements
|
||||
// added between them (such as "_append") can be properly ordered.
|
||||
$form[$form_key][$field_name]['#weight'] = $weight++;
|
||||
|
||||
$field_info = field_info_field($field_name);
|
||||
if ($field_info['cardinality'] != 1) {
|
||||
$form[$form_key]['_append::' . $field_name] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Add new value(s) to %label, instead of overwriting the existing values.', array('%label' => $field['label'])),
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
'#edit-bundle-' . str_replace('_', '-', $bundle_name) . '-show-value-' . str_replace('_', '-', $field_name) => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
'#weight' => $weight++,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a clearfix below the checkboxes so that the widgets are not floated.
|
||||
$form[$form_key]['show_value']['#suffix'] = '<div class="clearfix"></div>';
|
||||
$form[$form_key]['show_value']['#weight'] = -1;
|
||||
}
|
||||
|
||||
// If the form has only one group (for example, "Properties"), remove the
|
||||
// title and the fieldset, since there's no need to visually group values.
|
||||
$form_elements = element_get_visible_children($form);
|
||||
if (count($form_elements) == 1) {
|
||||
$element_key = reset($form_elements);
|
||||
unset($form[$element_key]['#type']);
|
||||
unset($form[$element_key]['#title']);
|
||||
|
||||
// Get a list of all elements in the group, and filter out the non-values.
|
||||
$values = element_get_visible_children($form[$element_key]);
|
||||
foreach ($values as $index => $key) {
|
||||
if ($key == 'show_value' || substr($key, 0, 1) == '_') {
|
||||
unset($values[$index]);
|
||||
}
|
||||
}
|
||||
// If the group has only one value, no need to hide it through #states.
|
||||
if (count($values) == 1) {
|
||||
$value_key = reset($values);
|
||||
$form[$element_key]['show_value'][$value_key]['#type'] = 'value';
|
||||
$form[$element_key]['show_value'][$value_key]['#value'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (module_exists('token') && $context['settings']['show_all_tokens']) {
|
||||
$token_type = str_replace('_', '-', $entity_type);
|
||||
$form['tokens'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => 'Available tokens',
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#weight' => 998,
|
||||
);
|
||||
$form['tokens']['tree'] = array(
|
||||
'#theme' => 'token_tree',
|
||||
'#token_types' => array($token_type, 'site'),
|
||||
'#global_types' => array(),
|
||||
'#dialog' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action form validate function.
|
||||
*
|
||||
* Checks that the user selected at least one value to modify, validates
|
||||
* properties and calls Field API to validate fields for each bundle.
|
||||
*/
|
||||
function views_bulk_operations_modify_action_validate($form, &$form_state) {
|
||||
// The form structure for "Show" checkboxes is a bit bumpy.
|
||||
$search = array('properties');
|
||||
foreach ($form_state['entities'] as $bundle => $entity) {
|
||||
$search[] = 'bundle_' . $bundle;
|
||||
}
|
||||
|
||||
$has_selected = FALSE;
|
||||
foreach ($search as $group) {
|
||||
// Store names of selected and appended entity values in a nicer format.
|
||||
$form_state['selected'][$group] = array();
|
||||
$form_state['append'][$group] = array();
|
||||
|
||||
// This group has no values, move on.
|
||||
if (!isset($form_state['values'][$group])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($form_state['values'][$group]['show_value'] as $key => $value) {
|
||||
if ($value) {
|
||||
$has_selected = TRUE;
|
||||
$form_state['selected'][$group][] = $key;
|
||||
}
|
||||
if (!empty($form_state['values'][$group]['_append::' . $key])) {
|
||||
$form_state['append'][$group][] = $key;
|
||||
unset($form_state['values'][$group]['_append::' . $key]);
|
||||
}
|
||||
}
|
||||
unset($form_state['values'][$group]['show_value']);
|
||||
}
|
||||
|
||||
if (!$has_selected) {
|
||||
form_set_error('', t('You must select at least one value to modify.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the wrapper to validate property values.
|
||||
if (!empty($form_state['selected']['properties'])) {
|
||||
// The entity used is irrelevant, and we can't rely on
|
||||
// $form_state['entities'] being non-empty, so a new one is created.
|
||||
$info = entity_get_info($form_state['entity_type']);
|
||||
$bundle_key = $info['entity keys']['bundle'];
|
||||
$default_values = array();
|
||||
// If the bundle key exists, it must always be set on an entity.
|
||||
if (!empty($bundle_key)) {
|
||||
$bundle_names = array_keys($info['bundles']);
|
||||
$bundle_name = reset($bundle_names);
|
||||
$default_values[$bundle_key] = $bundle_name;
|
||||
}
|
||||
$entity = entity_create($form_state['entity_type'], $default_values);
|
||||
$wrapper = entity_metadata_wrapper($form_state['entity_type'], $entity);
|
||||
|
||||
$properties = _views_bulk_operations_modify_action_get_properties($form_state['entity_type']);
|
||||
foreach ($form_state['selected']['properties'] as $key) {
|
||||
$value = $form_state['values']['properties'][$key];
|
||||
if (!$wrapper->$key->validate($value)) {
|
||||
$label = $properties[$key]['label'];
|
||||
form_set_error('properties][' . $key, t('%label contains an invalid value.', array('%label' => $label)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($form_state['entities'] as $bundle_name => $entity) {
|
||||
field_attach_form_validate($form_state['entity_type'], $entity, $form['bundle_' . $bundle_name], $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action form submit function.
|
||||
*
|
||||
* Fills each constructed entity with property and field values, then
|
||||
* passes them to views_bulk_operations_modify_action().
|
||||
*/
|
||||
function views_bulk_operations_modify_action_submit($form, $form_state) {
|
||||
foreach ($form_state['entities'] as $bundle_name => $entity) {
|
||||
field_attach_submit($form_state['entity_type'], $entity, $form['bundle_' . $bundle_name], $form_state);
|
||||
}
|
||||
|
||||
return array(
|
||||
'append' => $form_state['append'],
|
||||
'selected' => $form_state['selected'],
|
||||
'entities' => $form_state['entities'],
|
||||
'properties' => isset($form_state['values']['properties']) ? $form_state['values']['properties'] : array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all properties that can be modified.
|
||||
*
|
||||
* Properties that can't be changed are entity keys, timestamps, and the ones
|
||||
* without a setter callback.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type whose properties will be fetched.
|
||||
* @param $display_values
|
||||
* An optional, admin-provided list of properties and fields that should be
|
||||
* displayed for editing, used to filter the returned list of properties.
|
||||
*/
|
||||
function _views_bulk_operations_modify_action_get_properties($entity_type, $display_values = NULL) {
|
||||
$properties = array();
|
||||
$info = entity_get_info($entity_type);
|
||||
|
||||
// List of properties that can't be modified.
|
||||
$disabled_properties = array('created', 'changed');
|
||||
foreach (array('id', 'bundle', 'revision') as $key) {
|
||||
if (!empty($info['entity keys'][$key])) {
|
||||
$disabled_properties[] = $info['entity keys'][$key];
|
||||
}
|
||||
}
|
||||
// List of supported types.
|
||||
$supported_types = array('text', 'token', 'integer', 'decimal', 'date', 'duration',
|
||||
'boolean', 'uri', 'list');
|
||||
|
||||
$property_info = entity_get_property_info($entity_type);
|
||||
foreach ($property_info['properties'] as $key => $property) {
|
||||
if (in_array($key, $disabled_properties)) {
|
||||
continue;
|
||||
}
|
||||
// Filter out properties that can't be set (they are usually generated by a
|
||||
// getter callback based on other properties, and not stored in the DB).
|
||||
if (empty($property['setter callback'])) {
|
||||
continue;
|
||||
}
|
||||
// Determine the property type. If it's empty (permitted), default to text.
|
||||
// If it's a list type such as list<boolean>, extract the "boolean" part.
|
||||
$property['type'] = empty($property['type']) ? 'text' : $property['type'];
|
||||
$type = $property['type'];
|
||||
if ($list_type = entity_property_list_extract_type($type)) {
|
||||
$type = $list_type;
|
||||
$property['type'] = 'list';
|
||||
}
|
||||
// Filter out non-supported types (such as the Field API fields that
|
||||
// Commerce adds to its entities so that they show up in tokens).
|
||||
if (!in_array($type, $supported_types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$properties[$key] = $property;
|
||||
}
|
||||
|
||||
if (isset($display_values) && empty($display_values[VBO_MODIFY_ACTION_ALL])) {
|
||||
// Return only the properties that the admin specified.
|
||||
return array_intersect_key($properties, $display_values);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all bundles for which field widgets should be displayed.
|
||||
*
|
||||
* If the admin decided to limit the modify form to certain properties / fields
|
||||
* (through the action settings) then only bundles that have at least one field
|
||||
* selected are returned.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type whose bundles will be fetched.
|
||||
* @param $context
|
||||
* The VBO context variable.
|
||||
*/
|
||||
function _views_bulk_operations_modify_action_get_bundles($entity_type, $context) {
|
||||
$bundles = array();
|
||||
|
||||
$view = $context['view'];
|
||||
$display_values = $context['settings']['display_values'];
|
||||
$info = entity_get_info($entity_type);
|
||||
$bundle_key = $info['entity keys']['bundle'];
|
||||
// Check if this View has a filter on the bundle key and assemble a list
|
||||
// of allowed bundles according to the filter.
|
||||
$filtered_bundles = array();
|
||||
if (!empty($bundle_key) && isset($view->filter[$bundle_key]) && !empty($view->filter[$bundle_key]->value)) {
|
||||
$operator = $view->filter[$bundle_key]->operator;
|
||||
if ($operator == 'in') {
|
||||
$filtered_bundles = $view->filter[$bundle_key]->value;
|
||||
}
|
||||
elseif ($operator == 'not in') {
|
||||
$bundle_names = array_keys($info['bundles']);
|
||||
$filtered_bundles = array_diff($bundle_names, $view->filter[$bundle_key]->value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($info['bundles'] as $bundle_name => $bundle) {
|
||||
// The view is limited to specific bundles, but this bundle isn't one of
|
||||
// them. Ignore it.
|
||||
if (!empty($filtered_bundles) && !in_array($bundle_name, $filtered_bundles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$instances = field_info_instances($entity_type, $bundle_name);
|
||||
// Ignore bundles that don't have any field instances attached.
|
||||
if (empty($instances)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$has_enabled_fields = FALSE;
|
||||
foreach ($display_values as $key) {
|
||||
if (strpos($key, $bundle_name . '::') !== FALSE) {
|
||||
$has_enabled_fields = TRUE;
|
||||
}
|
||||
}
|
||||
// The admin has either specified that all values should be modifiable, or
|
||||
// selected at least one field belonging to this bundle.
|
||||
if (!empty($display_values[VBO_MODIFY_ACTION_ALL]) || $has_enabled_fields) {
|
||||
$bundles[$bundle_name] = $bundle;
|
||||
}
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that recursively strips #required from field widgets.
|
||||
*/
|
||||
function _views_bulk_operations_modify_action_unset_required(&$element) {
|
||||
unset($element['#required']);
|
||||
foreach (element_children($element) as $key) {
|
||||
_views_bulk_operations_modify_action_unset_required($element[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VBO settings form function.
|
||||
*/
|
||||
function views_bulk_operations_modify_action_views_bulk_operations_form_options() {
|
||||
$options['show_all_tokens'] = TRUE;
|
||||
$options['display_values'] = array(VBO_MODIFY_ACTION_ALL);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* The settings form for this action.
|
||||
*/
|
||||
function views_bulk_operations_modify_action_views_bulk_operations_form($options, $entity_type, $dom_id) {
|
||||
// Initialize default values.
|
||||
if (empty($options)) {
|
||||
$options = views_bulk_operations_modify_action_views_bulk_operations_form_options();
|
||||
}
|
||||
|
||||
$form['show_all_tokens'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Show available tokens'),
|
||||
'#description' => t('Check this to show a list of all available tokens in the bottom of the form. Requires the token module.'),
|
||||
'#default_value' => $options['show_all_tokens'],
|
||||
);
|
||||
|
||||
$info = entity_get_info($entity_type);
|
||||
$properties = _views_bulk_operations_modify_action_get_properties($entity_type);
|
||||
$values = array(VBO_MODIFY_ACTION_ALL => t('- All -'));
|
||||
foreach ($properties as $key => $property) {
|
||||
$label = t('Properties');
|
||||
$values[$label][$key] = $property['label'];
|
||||
}
|
||||
foreach ($info['bundles'] as $bundle_name => $bundle) {
|
||||
$bundle_key = $info['entity keys']['bundle'];
|
||||
// Show the more detailed label only if the entity type has multiple bundles.
|
||||
// Otherwise, it would just be confusing.
|
||||
if (count($info['bundles']) > 1) {
|
||||
$label = t('Fields for @bundle_key @label', array('@bundle_key' => $bundle_key, '@label' => $bundle['label']));
|
||||
}
|
||||
else {
|
||||
$label = t('Fields');
|
||||
}
|
||||
|
||||
$instances = field_info_instances($entity_type, $bundle_name);
|
||||
foreach ($instances as $field_name => $field) {
|
||||
$values[$label][$bundle_name . '::' . $field_name] = $field['label'];
|
||||
}
|
||||
}
|
||||
|
||||
$form['display_values'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Display values'),
|
||||
'#options' => $values,
|
||||
'#multiple' => TRUE,
|
||||
'#description' => t('Select which values the action form should present to the user.'),
|
||||
'#default_value' => $options['display_values'],
|
||||
);
|
||||
return $form;
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
function views_bulk_operations_script_action_info() {
|
||||
$actions = array();
|
||||
$actions['views_bulk_operations_script_action'] = array(
|
||||
'type' => 'entity',
|
||||
'label' => t('Execute arbitrary PHP script'),
|
||||
'configurable' => TRUE,
|
||||
'triggers' => array('any'),
|
||||
);
|
||||
// Provide a strict default permission if actions_permissions is disabled.
|
||||
if (!module_exists('actions_permissions')) {
|
||||
$actions['views_bulk_operations_script_action']['permissions'] = array('administer site configuration');
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
function views_bulk_operations_script_action($entity, $context) {
|
||||
$return = eval($context['script']);
|
||||
if ($return === FALSE) {
|
||||
$msg = 'Error in script.';
|
||||
$arg = array();
|
||||
$error = error_get_last();
|
||||
if ($error) {
|
||||
$msg = '!err in script: !msg in line \'%line\'.';
|
||||
$arg = array(
|
||||
'!msg' => $error['message'],
|
||||
'%line' => _views_bulk_operations_script_action_error_line($context['script'], $error['line']),
|
||||
'!err' => _views_bulk_operations_script_action_error_type($error['type']),
|
||||
);
|
||||
}
|
||||
drupal_set_message(t($msg, $arg), 'error', FALSE);
|
||||
watchdog('actions', $msg, $arg, WATCHDOG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
function views_bulk_operations_script_action_form($context) {
|
||||
$form['script'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('PHP script'),
|
||||
'#description' => t('Type the PHP snippet that will run upon execution of this action. You can use variables <code>$entity</code> and <code>$context</code> in your snippet.
|
||||
Note that it is up to the script to save the $entity once it\'s done modifying it.'),
|
||||
'#default_value' => @$context['script'],
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
function views_bulk_operations_script_action_validate($form, $form_state) {
|
||||
}
|
||||
|
||||
function views_bulk_operations_script_action_submit($form, $form_state) {
|
||||
return array(
|
||||
'script' => $form_state['values']['script'],
|
||||
);
|
||||
}
|
||||
|
||||
function _views_bulk_operations_script_action_error_line($script, $line) {
|
||||
$lines = preg_split("/(\r?\n)/", $script);
|
||||
if (isset($lines[$line-1])) {
|
||||
return $lines[$line-1];
|
||||
}
|
||||
else {
|
||||
return t('Line !line', array('!line' => $line));
|
||||
}
|
||||
}
|
||||
|
||||
function _views_bulk_operations_script_action_error_type($type) {
|
||||
$types = array(
|
||||
E_ERROR => 'Error',
|
||||
E_WARNING => 'Warning',
|
||||
E_PARSE => 'Parsing Error',
|
||||
E_NOTICE => 'Notice',
|
||||
E_CORE_ERROR => 'Core Error',
|
||||
E_CORE_WARNING => 'Core Warning',
|
||||
E_COMPILE_ERROR => 'Compile Error',
|
||||
E_COMPILE_WARNING => 'Compile Warning',
|
||||
E_USER_ERROR => 'User Error',
|
||||
E_USER_WARNING => 'User Warning',
|
||||
E_USER_NOTICE => 'User Notice',
|
||||
E_STRICT => 'Runtime Notice',
|
||||
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
||||
);
|
||||
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
|
||||
$types += array(
|
||||
E_DEPRECATED => 'Deprecated Notice',
|
||||
E_USER_DEPRECATED => 'User Deprecated Notice',
|
||||
);
|
||||
}
|
||||
|
||||
return t($types[$type]);
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
function views_bulk_operations_user_roles_action_info() {
|
||||
return array('views_bulk_operations_user_roles_action' => array(
|
||||
'type' => 'user',
|
||||
'label' => t('Modify user roles'),
|
||||
'configurable' => TRUE,
|
||||
'triggers' => array('any'),
|
||||
));
|
||||
}
|
||||
|
||||
function views_bulk_operations_user_roles_action_form($context) {
|
||||
$roles = user_roles(TRUE);
|
||||
unset($roles[DRUPAL_AUTHENTICATED_RID]); // Can't edit authenticated role.
|
||||
|
||||
$form['add_roles'] = array(
|
||||
'#type' => 'select',
|
||||
'#multiple' => TRUE,
|
||||
'#title' => t('Add roles'),
|
||||
'#description' => t('Choose one or more roles you would like to assign to the selected users.'),
|
||||
'#options' => $roles,
|
||||
'#size' => 5
|
||||
);
|
||||
$form['remove_roles'] = array(
|
||||
'#type' => 'select',
|
||||
'#multiple' => TRUE,
|
||||
'#title' => t('Remove roles'),
|
||||
'#description' => t('Choose one or more roles you would like to remove from the selected users.'),
|
||||
'#options' => $roles,
|
||||
'#size' => 5
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
function views_bulk_operations_user_roles_action_validate($form, $form_state) {
|
||||
if (!$form_state['values']['add_roles'] && !$form_state['values']['remove_roles']) {
|
||||
form_set_error('add_roles', t('You have not chosen any role to add or remove. Please select something to do.'));
|
||||
}
|
||||
}
|
||||
|
||||
function views_bulk_operations_user_roles_action_submit($form, $form_state) {
|
||||
return array(
|
||||
'add_roles' => array_filter($form_state['values']['add_roles']),
|
||||
'remove_roles' => array_filter($form_state['values']['remove_roles']),
|
||||
);
|
||||
}
|
||||
|
||||
function views_bulk_operations_user_roles_action(&$user, $context) {
|
||||
$roles = $user->roles;
|
||||
$selected = (is_array($context['add_roles']) ? $context['add_roles'] : array()) +
|
||||
(is_array($context['remove_roles']) ? $context['remove_roles'] : array());
|
||||
$result = db_query("SELECT rid, name FROM {role} WHERE rid IN (:selected)", array(':selected' => array_keys($selected)));
|
||||
foreach ($result as $role) {
|
||||
if (isset($context['add_roles'][$role->rid])) {
|
||||
$add_roles[$role->rid] = $role->name;
|
||||
}
|
||||
if (isset($context['remove_roles'][$role->rid])) {
|
||||
$remove_roles[$role->rid] = $role->name;
|
||||
}
|
||||
}
|
||||
if (!empty($add_roles)) {
|
||||
$roles += $add_roles;
|
||||
}
|
||||
if (!empty($remove_roles)) {
|
||||
$roles = array_diff($roles, $remove_roles);
|
||||
}
|
||||
user_save($user, array('roles' => $roles));
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
name = Actions permissions (VBO)
|
||||
description = Provides permission-based access control for actions. Used by Views Bulk Operations.
|
||||
package = Administration
|
||||
core = 7.x
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-12-03
|
||||
version = "7.x-3.1"
|
||||
core = "7.x"
|
||||
project = "views_bulk_operations"
|
||||
datestamp = "1354500015"
|
||||
|
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*/
|
||||
function actions_permissions_permission() {
|
||||
$permissions = array();
|
||||
$actions = actions_list() + _actions_permissions_advanced_actions_list();
|
||||
foreach ($actions as $key => $action) {
|
||||
$permission = actions_permissions_get_perm($action['label'], $key);
|
||||
|
||||
$permissions[$permission] = array(
|
||||
'title' => t('Execute %label', array('%label' => $action['label'])),
|
||||
);
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of advanced actions (created through the Action UI).
|
||||
*/
|
||||
function _actions_permissions_advanced_actions_list() {
|
||||
$actions = array();
|
||||
// Actions provided by Drupal that aren't usable in a VBO context.
|
||||
$hidden_actions = array(
|
||||
'system_block_ip_action',
|
||||
'system_goto_action',
|
||||
'system_message_action',
|
||||
);
|
||||
|
||||
$result = db_query("SELECT * FROM {actions} WHERE parameters > ''");
|
||||
foreach ($result as $action) {
|
||||
if (in_array($action->callback, $hidden_actions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameters = unserialize($action->parameters);
|
||||
$actions[$action->aid] = array(
|
||||
'label' => $action->label,
|
||||
'type' => $action->type,
|
||||
);
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the permission name used in user_access().
|
||||
*/
|
||||
function actions_permissions_get_perm($label, $key) {
|
||||
return "execute $key";
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
div[class*="show-value"] {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
.vbo-select-all-markup, .vbo-table-select-all-markup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* "Select all" for table styles. */
|
||||
.vbo-views-form thead .form-type-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
.vbo-table-select-all {
|
||||
display: none;
|
||||
}
|
||||
.views-table-row-select-all {
|
||||
display: none;
|
||||
}
|
||||
.views-table-row-select-all td {
|
||||
text-align: center;
|
||||
}
|
||||
.vbo-table-select-all-pages, .vbo-table-select-this-page {
|
||||
margin: 0 !important;
|
||||
padding: 2px 5px !important;
|
||||
}
|
||||
|
||||
/* Generic "select all" */
|
||||
.vbo-fieldset-select-all {
|
||||
text-align: center;
|
||||
width: 210px;
|
||||
padding: 0.6em 0;
|
||||
}
|
||||
.vbo-fieldset-select-all .form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.vbo-fieldset-select-all div {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
(function ($) {
|
||||
Drupal.behaviors.vbo = {
|
||||
attach: function(context) {
|
||||
$('.vbo-views-form', context).each(function() {
|
||||
Drupal.vbo.initTableBehaviors(this);
|
||||
Drupal.vbo.initGenericBehaviors(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Drupal.vbo = Drupal.vbo || {};
|
||||
Drupal.vbo.initTableBehaviors = function(form) {
|
||||
// If the table is not grouped, "Select all on this page / all pages"
|
||||
// markup gets inserted below the table header.
|
||||
var selectAllMarkup = $('.vbo-table-select-all-markup', form);
|
||||
if (selectAllMarkup.length) {
|
||||
$('.views-table > tbody', form).prepend('<tr class="views-table-row-select-all even">></tr>');
|
||||
var colspan = $('table th', form).length;
|
||||
$('.views-table-row-select-all', form).html('<td colspan="' + colspan + '">' + selectAllMarkup.html() + '</td>');
|
||||
|
||||
$('.vbo-table-select-all-pages', form).click(function() {
|
||||
Drupal.vbo.tableSelectAllPages(form);
|
||||
return false;
|
||||
});
|
||||
$('.vbo-table-select-this-page', form).click(function() {
|
||||
Drupal.vbo.tableSelectThisPage(form);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$('.vbo-table-select-all', form).show();
|
||||
// This is the "select all" checkbox in (each) table header.
|
||||
$('.vbo-table-select-all', form).click(function() {
|
||||
var table = $(this).closest('table')[0];
|
||||
$('input[id^="edit-views-bulk-operations"]:not(:disabled)', table).attr('checked', this.checked);
|
||||
|
||||
// Toggle the visibility of the "select all" row (if any).
|
||||
if (this.checked) {
|
||||
$('.views-table-row-select-all', table).show();
|
||||
}
|
||||
else {
|
||||
$('.views-table-row-select-all', table).hide();
|
||||
// Disable "select all across pages".
|
||||
Drupal.vbo.tableSelectThisPage(form);
|
||||
}
|
||||
});
|
||||
|
||||
// Set up the ability to click anywhere on the row to select it.
|
||||
$('.views-table tbody tr', form).click(function(event) {
|
||||
if (event.target.tagName.toLowerCase() != 'input' && event.target.tagName.toLowerCase() != 'a') {
|
||||
$('input[id^="edit-views-bulk-operations"]:not(:disabled)', this).each(function() {
|
||||
var checked = this.checked;
|
||||
// trigger() toggles the checkmark *after* the event is set,
|
||||
// whereas manually clicking the checkbox toggles it *beforehand*.
|
||||
// that's why we manually set the checkmark first, then trigger the
|
||||
// event (so that listeners get notified), then re-set the checkmark
|
||||
// which the trigger will have toggled. yuck!
|
||||
this.checked = !checked;
|
||||
$(this).trigger('click');
|
||||
this.checked = !checked;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Drupal.vbo.tableSelectAllPages = function(form) {
|
||||
$('.vbo-table-this-page', form).hide();
|
||||
$('.vbo-table-all-pages', form).show();
|
||||
// Modify the value of the hidden form field.
|
||||
$('.select-all-rows', form).val('1');
|
||||
}
|
||||
Drupal.vbo.tableSelectThisPage = function(form) {
|
||||
$('.vbo-table-all-pages', form).hide();
|
||||
$('.vbo-table-this-page', form).show();
|
||||
// Modify the value of the hidden form field.
|
||||
$('.select-all-rows', form).val('0');
|
||||
}
|
||||
|
||||
Drupal.vbo.initGenericBehaviors = function(form) {
|
||||
// Show the "select all" fieldset.
|
||||
$('.vbo-select-all-markup', form).show();
|
||||
|
||||
$('.vbo-select-this-page', form).click(function() {
|
||||
$('input[id^="edit-views-bulk-operations"]', form).attr('checked', this.checked);
|
||||
$('.vbo-select-all-pages', form).attr('checked', false);
|
||||
|
||||
// Toggle the "select all" checkbox in grouped tables (if any).
|
||||
$('.vbo-table-select-all', form).attr('checked', this.checked);
|
||||
});
|
||||
$('.vbo-select-all-pages', form).click(function() {
|
||||
$('input[id^="edit-views-bulk-operations"]', form).attr('checked', this.checked);
|
||||
$('.vbo-select-this-page', form).attr('checked', false);
|
||||
|
||||
// Toggle the "select all" checkbox in grouped tables (if any).
|
||||
$('.vbo-table-select-all', form).attr('checked', this.checked);
|
||||
|
||||
// Modify the value of the hidden form field.
|
||||
$('.select-all-rows', form).val(this.checked);
|
||||
});
|
||||
|
||||
$('.vbo-select', form).click(function() {
|
||||
// If a checkbox was deselected, uncheck any "select all" checkboxes.
|
||||
if (!this.checked) {
|
||||
$('.vbo-select-this-page', form).attr('checked', false);
|
||||
$('.vbo-select-all-pages', form).attr('checked', false);
|
||||
// Modify the value of the hidden form field.
|
||||
$('.select-all-rows', form).val('0')
|
||||
|
||||
var table = $(this).closest('table')[0];
|
||||
if (table) {
|
||||
// Uncheck the "select all" checkbox in the table header.
|
||||
$('.vbo-table-select-all', table).attr('checked', false);
|
||||
|
||||
// If there's a "select all" row, hide it.
|
||||
if ($('.vbo-table-select-this-page', table).length) {
|
||||
$('.views-table-row-select-all', table).hide();
|
||||
// Disable "select all across pages".
|
||||
Drupal.vbo.tableSelectThisPage(form);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Defines the class for core actions.
|
||||
* Belongs to the "action" operation type plugin.
|
||||
*/
|
||||
|
||||
class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation {
|
||||
|
||||
/**
|
||||
* Contains the options provided by the user in the configuration form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $formOptions = array();
|
||||
|
||||
/**
|
||||
* Returns the access bitmask for the operation, used for entity access checks.
|
||||
*/
|
||||
public function getAccessMask() {
|
||||
// Assume edit by default.
|
||||
if (!isset($this->operationInfo['behavior'])) {
|
||||
$this->operationInfo['behavior'] = array('changes_property');
|
||||
}
|
||||
|
||||
$mask = 0;
|
||||
if (in_array('views_property', $this->operationInfo['behavior'])) {
|
||||
$mask |= VBO_ACCESS_OP_VIEW;
|
||||
}
|
||||
if (in_array('changes_property', $this->operationInfo['behavior'])) {
|
||||
$mask |= VBO_ACCESS_OP_UPDATE;
|
||||
}
|
||||
if (in_array('creates_property', $this->operationInfo['behavior'])) {
|
||||
$mask |= VBO_ACCESS_OP_CREATE;
|
||||
}
|
||||
if (in_array('deletes_property', $this->operationInfo['behavior'])) {
|
||||
$mask |= VBO_ACCESS_OP_DELETE;
|
||||
}
|
||||
return $mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided account has access to execute the operation.
|
||||
*
|
||||
* @param $account
|
||||
*/
|
||||
public function access($account) {
|
||||
// Use actions_permissions if enabled.
|
||||
if (module_exists('actions_permissions')) {
|
||||
$perm = actions_permissions_get_perm($this->operationInfo['label'], $this->operationInfo['key']);
|
||||
if (!user_access($perm, $account)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// Check against additional permissions.
|
||||
if (!empty($this->operationInfo['permissions'])) {
|
||||
foreach ($this->operationInfo['permissions'] as $perm) {
|
||||
if (!user_access($perm, $account)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Access granted.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration form for the operation.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
* @param $context
|
||||
* An array of related data provided by the caller.
|
||||
*/
|
||||
public function form($form, &$form_state, array $context) {
|
||||
// Some modules (including this one) place their action callbacks
|
||||
// into separate files. At this point those files might no longer be
|
||||
// included due to an #ajax rebuild, so we call actions_list() to trigger
|
||||
// inclusion. The same thing is done by actions_do() on execute.
|
||||
actions_list();
|
||||
|
||||
$context['settings'] = $this->getAdminOption('settings', array());
|
||||
$form_callback = $this->operationInfo['callback'] . '_form';
|
||||
return $form_callback($context, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the configuration form.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
*/
|
||||
public function formValidate($form, &$form_state) {
|
||||
// Some modules (including this one) place their action callbacks
|
||||
// into separate files. At this point those files might no longer be
|
||||
// included due to a page reload, so we call actions_list() to trigger
|
||||
// inclusion. The same thing is done by actions_do() on execute.
|
||||
actions_list();
|
||||
|
||||
$validation_callback = $this->operationInfo['callback'] . '_validate';
|
||||
if (function_exists($validation_callback)) {
|
||||
$validation_callback($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submitted configuration form.
|
||||
* This is where the operation can transform and store the submitted data.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
*/
|
||||
public function formSubmit($form, &$form_state) {
|
||||
// Some modules (including this one) place their action callbacks
|
||||
// into separate files. At this point those files might no longer be
|
||||
// included due to a page reload, so we call actions_list() to trigger
|
||||
// inclusion. The same thing is done by actions_do() on execute.
|
||||
actions_list();
|
||||
|
||||
$submit_callback = $this->operationInfo['callback'] . '_submit';
|
||||
$this->formOptions = $submit_callback($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the admin options form for the operation.
|
||||
*
|
||||
* The admin options form is embedded into the VBO field settings and used
|
||||
* to configure operation behavior. The options can later be fetched
|
||||
* through the getAdminOption() method.
|
||||
*
|
||||
* @param $dom_id
|
||||
* The dom path to the level where the admin options form is embedded.
|
||||
* Needed for #dependency.
|
||||
*/
|
||||
public function adminOptionsForm($dom_id) {
|
||||
$form = parent::adminOptionsForm($dom_id);
|
||||
|
||||
$settings_form_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form';
|
||||
if (function_exists($settings_form_callback)) {
|
||||
$settings = $this->getAdminOption('settings', array());
|
||||
|
||||
$form['settings'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Operation settings'),
|
||||
'#collapsible' => TRUE,
|
||||
'#dependency' => array(
|
||||
$dom_id . '-selected' => array(1),
|
||||
),
|
||||
);
|
||||
$settings_dom_id = $dom_id . '-settings';
|
||||
$form['settings'] += $settings_form_callback($settings, $this->entityType, $settings_dom_id);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the admin options form.
|
||||
*
|
||||
* @param $form
|
||||
* The admin options form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form. Note that this array
|
||||
* is constructed by the VBO views field handler, so it's not a real form
|
||||
* state, it contains only the 'values' key.
|
||||
* @param $error_element_base
|
||||
* The base to prepend to field names when using form_set_error().
|
||||
* Needed because the admin settings form is embedded into a bigger form.
|
||||
*/
|
||||
public function adminOptionsFormValidate($form, &$form_state, $error_element_base) {
|
||||
parent::adminOptionsFormValidate($form, $form_state, $error_element_base);
|
||||
|
||||
if (!empty($form['settings'])) {
|
||||
$settings_validation_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_validate';
|
||||
if (function_exists($settings_validation_callback)) {
|
||||
$fake_form = $form['settings'];
|
||||
$fake_form_state = array('values' => &$form_state['values']['settings']);
|
||||
$error_element_base .= 'settings][';
|
||||
|
||||
$settings_validation_callback($fake_form, $fake_form_state, $error_element_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submitted admin options form.
|
||||
* Note that there is no need to handle saving the options, that is done
|
||||
* by the VBO views field handler, which also injects the options into the
|
||||
* operation object upon instantiation.
|
||||
*
|
||||
* @param $form
|
||||
* The admin options form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form. Note that this array
|
||||
* is constructed by the VBO views field handler, so it's not a real form
|
||||
* state, it contains only the 'values' key.
|
||||
*/
|
||||
public function adminOptionsFormSubmit($form, &$form_state) {
|
||||
parent::adminOptionsFormSubmit($form, $form_state);
|
||||
|
||||
if (!empty($form['settings'])) {
|
||||
$settings_submit_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_submit';
|
||||
if (function_exists($settings_submit_callback)) {
|
||||
$fake_form = $form['settings'];
|
||||
$fake_form_state = array('values' => &$form_state['values']['settings']);
|
||||
|
||||
$settings_submit_callback($form, $form_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the operation needs the full selected views rows to be
|
||||
* passed to execute() as a part of $context.
|
||||
*/
|
||||
public function needsRows() {
|
||||
return !empty($this->operationInfo['pass rows']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the selected operation on the provided data.
|
||||
*
|
||||
* @param $data
|
||||
* The data to to operate on. An entity or an array of entities.
|
||||
* @param $context
|
||||
* An array of related data (selected views rows, etc).
|
||||
*/
|
||||
public function execute($data, array $context) {
|
||||
$context['entity_type'] = $this->entityType;
|
||||
$context['settings'] = $this->getAdminOption('settings', array());
|
||||
$context += $this->formOptions;
|
||||
$context += $this->operationInfo['parameters'];
|
||||
// Actions provided by the Drupal system module require the entity to be
|
||||
// present in $context, keyed by entity type.
|
||||
if (is_object($data)) {
|
||||
$context[$this->entityType] = $data;
|
||||
}
|
||||
|
||||
actions_do($this->operationInfo['callback'], $data, $context);
|
||||
|
||||
// The action might need to have its entities saved after execution.
|
||||
if (in_array('changes_property', $this->operationInfo['behavior'])) {
|
||||
$data = is_array($data) ? $data : array($data);
|
||||
foreach ($data as $entity) {
|
||||
entity_save($this->entityType, $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* CTools plugin. Provides support for core actions.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'title' => t('Action'),
|
||||
'list callback' => 'views_bulk_operations_operation_action_list',
|
||||
'handler' => array(
|
||||
'file' => 'action.class.php',
|
||||
'class' => 'ViewsBulkOperationsAction',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a prepared list of available actions.
|
||||
*
|
||||
* Actions are fetched by invoking hook_action_info() and by loading
|
||||
* advanced actions from the database.
|
||||
*
|
||||
* @param $operation_id
|
||||
* The full, prefixed operation_id of the operation (in this case, action)
|
||||
* to return, or NULL to return an array with all operations.
|
||||
*/
|
||||
function views_bulk_operations_operation_action_list($operation_id = NULL) {
|
||||
$operations = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($operations)) {
|
||||
// Combined list of all actions and advanced actions.
|
||||
$actions_list = actions_list() + views_bulk_operations_operation_advanced_action_list();
|
||||
// Actions provided by Drupal that aren't usable in a VBO context.
|
||||
$hidden_actions = array(
|
||||
'system_block_ip_action',
|
||||
'system_goto_action',
|
||||
'system_message_action',
|
||||
);
|
||||
|
||||
$operations = array();
|
||||
foreach ($actions_list as $key => $action) {
|
||||
// Actions are keyed by callback.
|
||||
// Advanced actions are keyed by aid and store the callback separately.
|
||||
$callback = isset($action['callback']) ? $action['callback'] : $key;
|
||||
// This action needs to be skipped.
|
||||
if (in_array($callback, $hidden_actions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// All operations must be prefixed with the operation type.
|
||||
$new_operation_id = 'action::' . $key;
|
||||
|
||||
$operations[$new_operation_id] = array(
|
||||
'operation_type' => 'action',
|
||||
'type' => $action['type'],
|
||||
// Keep the unprefixed key around, for internal use.
|
||||
'key' => $key,
|
||||
'callback' => $callback,
|
||||
'label' => isset($action['label']) ? $action['label'] : '',
|
||||
'parameters' => isset($action['parameters']) ? $action['parameters'] : array(),
|
||||
'configurable' => !empty($action['configurable']) || !empty($action['vbo_configurable']),
|
||||
'aggregate' => !empty($action['aggregate']),
|
||||
'behavior' => isset($action['behavior']) ? $action['behavior'] : array(),
|
||||
'permissions' => isset($action['permissions']) ? $action['permissions'] : NULL,
|
||||
'pass rows' => !empty($action['pass rows']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($operation_id)) {
|
||||
return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE;
|
||||
}
|
||||
else {
|
||||
return $operations;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of advanced actions (created through the Action UI).
|
||||
*/
|
||||
function views_bulk_operations_operation_advanced_action_list() {
|
||||
$actions = array();
|
||||
$static_actions = actions_list();
|
||||
$result = db_query("SELECT * FROM {actions} WHERE parameters > ''");
|
||||
foreach ($result as $action) {
|
||||
$parameters = unserialize($action->parameters);
|
||||
$actions[$action->aid] = array(
|
||||
'label' => isset($action->label) ? $action->label : '',
|
||||
'callback' => $action->callback,
|
||||
'type' => $action->type,
|
||||
'configurable' => FALSE,
|
||||
'parameters' => $parameters,
|
||||
);
|
||||
foreach (array('aggregate', 'behavior', 'permissions', 'pass rows') as $attribute) {
|
||||
if (isset($static_actions[$action->callback][$attribute])) {
|
||||
$actions[$action->aid][$attribute] = $static_actions[$action->callback][$attribute];
|
||||
}
|
||||
}
|
||||
if (isset($static_actions[$action->callback]['parameters'])) {
|
||||
$actions[$action->aid]['parameters'] = array_merge($actions[$action->aid]['parameters'], $static_actions[$action->callback]['parameters']);
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Defines the base class for operations.
|
||||
*/
|
||||
|
||||
abstract class ViewsBulkOperationsBaseOperation {
|
||||
|
||||
/**
|
||||
* The id of the operation.
|
||||
*
|
||||
* Composed of the operation_type plugin name and the operation key,
|
||||
* divided by '::'. For example: action::node_publish_action.
|
||||
*/
|
||||
public $operationId;
|
||||
|
||||
/**
|
||||
* The entity type that the operation is operating on.
|
||||
*
|
||||
* Not the same as $operationInfo['type'] since that value can be just
|
||||
* "entity", which means "available to every entity type".
|
||||
*/
|
||||
public $entityType;
|
||||
|
||||
/**
|
||||
* Contains information about the current operation, as generated
|
||||
* by the "list callback" function in the plugin file.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $operationInfo;
|
||||
|
||||
/**
|
||||
* Contains the options set by the admin for the current operation.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $adminOptions;
|
||||
|
||||
/**
|
||||
* Constructs an operation object.
|
||||
*
|
||||
* @param $operationId
|
||||
* The id of the operation.
|
||||
* @param $entityType
|
||||
* The entity type that the operation is operating on.
|
||||
* @param $operationInfo
|
||||
* An array of information about the operation.
|
||||
* @param $adminOptions
|
||||
* An array of options set by the admin.
|
||||
*/
|
||||
public function __construct($operationId, $entityType, array $operationInfo, array $adminOptions) {
|
||||
$this->operationId = $operationId;
|
||||
$this->entityType = $entityType;
|
||||
$this->operationInfo = $operationInfo;
|
||||
$this->adminOptions = $adminOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an admin option.
|
||||
*/
|
||||
public function getAdminOption($key, $default = NULL) {
|
||||
return isset($this->adminOptions[$key]) ? $this->adminOptions[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the access bitmask for the operation, used for entity access checks.
|
||||
*/
|
||||
public function getAccessMask() {
|
||||
// Assume edit by default.
|
||||
return VBO_ACCESS_OP_UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the operation.
|
||||
*/
|
||||
public function id() {
|
||||
return $this->operationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the operation_type plugin that provides the operation.
|
||||
*/
|
||||
public function type() {
|
||||
return $this->operationInfo['operation_type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human-readable name of the operation, meant to be shown
|
||||
* to the user.
|
||||
*/
|
||||
public function label() {
|
||||
$admin_label = $this->getAdminOption('label');
|
||||
if (!empty($admin_label)) {
|
||||
$label = t($admin_label);
|
||||
}
|
||||
else {
|
||||
// If the admin didn't specify any label, fallback to the default one.
|
||||
$label = $this->operationInfo['label'];
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human-readable name of the operation, meant to be shown
|
||||
* to the admin.
|
||||
*/
|
||||
public function adminLabel() {
|
||||
return $this->operationInfo['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the operation is configurable. Used to determine
|
||||
* whether the operation's form methods should be invoked.
|
||||
*/
|
||||
public function configurable() {
|
||||
return !empty($this->operationInfo['configurable']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided account has access to execute the operation.
|
||||
*
|
||||
* @param $account
|
||||
*/
|
||||
public function access($account) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration form for the operation.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
* @param $context
|
||||
* An array of related data provided by the caller.
|
||||
*/
|
||||
abstract function form($form, &$form_state, array $context);
|
||||
|
||||
/**
|
||||
* Validates the configuration form.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
*/
|
||||
abstract function formValidate($form, &$form_state);
|
||||
|
||||
/**
|
||||
* Handles the submitted configuration form.
|
||||
* This is where the operation can transform and store the submitted data.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
*/
|
||||
abstract function formSubmit($form, &$form_state);
|
||||
|
||||
/**
|
||||
* Returns the admin options form for the operation.
|
||||
*
|
||||
* The admin options form is embedded into the VBO field settings and used
|
||||
* to configure operation behavior. The options can later be fetched
|
||||
* through the getAdminOption() method.
|
||||
*
|
||||
* @param $dom_id
|
||||
* The dom path to the level where the admin options form is embedded.
|
||||
* Needed for #dependency.
|
||||
*/
|
||||
public function adminOptionsForm($dom_id) {
|
||||
$label = $this->getAdminOption('label', '');
|
||||
|
||||
$form = array();
|
||||
$form['override_label'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Override label'),
|
||||
'#default_value' => $label !== '',
|
||||
'#dependency' => array(
|
||||
$dom_id . '-selected' => array(1),
|
||||
),
|
||||
);
|
||||
$form['label'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Provide label'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $label,
|
||||
'#dependency' => array(
|
||||
$dom_id . '-selected' => array(1),
|
||||
$dom_id . '-override-label' => array(1),
|
||||
),
|
||||
'#dependency_count' => 2,
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the admin options form.
|
||||
*
|
||||
* @param $form
|
||||
* The admin options form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form. Note that this array
|
||||
* is constructed by the VBO views field handler, so it's not a real form
|
||||
* state, it contains only the 'values' key.
|
||||
* @param $error_element_base
|
||||
* The base to prepend to field names when using form_set_error().
|
||||
* Needed because the admin options form is embedded into a bigger form.
|
||||
*/
|
||||
public function adminOptionsFormValidate($form, &$form_state, $error_element_base) {
|
||||
// No need to do anything, but make the function have a body anyway
|
||||
// so that it's callable by overriding methods.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submitted admin options form.
|
||||
* Note that there is no need to handle saving the options, that is done
|
||||
* by the VBO views field handler, which also injects the options into the
|
||||
* operation object upon instantiation.
|
||||
*
|
||||
* @param $form
|
||||
* The admin options form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form. Note that this array
|
||||
* is constructed by the VBO views field handler, so it's not a real form
|
||||
* state, it contains only the 'values' key.
|
||||
*/
|
||||
public function adminOptionsFormSubmit($form, &$form_state) {
|
||||
// If the "Override label" checkbox was deselected, clear the entered value.
|
||||
if (empty($form_state['values']['override_label'])) {
|
||||
$form_state['values']['label'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the selected entities should be aggregated
|
||||
* (loaded in bulk and passed in together).
|
||||
* To be avoided if possible, since aggregation makes it impossible to use
|
||||
* Batch API or the Drupal Queue for execution.
|
||||
*/
|
||||
public function aggregate() {
|
||||
return !empty($this->operationInfo['aggregate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the operation needs the full selected views rows to be
|
||||
* passed to execute() as a part of $context.
|
||||
*/
|
||||
public function needsRows() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the selected operation on the provided data.
|
||||
*
|
||||
* @param $data
|
||||
* The data to to operate on. An entity or an array of entities.
|
||||
* @param $context
|
||||
* An array of related data (selected views rows, etc).
|
||||
*/
|
||||
abstract function execute($data, array $context);
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Defines the class for rules components (rule, ruleset, action).
|
||||
* Belongs to the "rules_component" operation type plugin.
|
||||
*/
|
||||
|
||||
class ViewsBulkOperationsRulesComponent extends ViewsBulkOperationsBaseOperation {
|
||||
|
||||
/**
|
||||
* Returns the access bitmask for the operation, used for entity access checks.
|
||||
*
|
||||
* Rules has its own permission system, so the lowest bitmask is enough.
|
||||
*/
|
||||
public function getAccessMask() {
|
||||
return VBO_ACCESS_OP_VIEW;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided account has access to execute the operation.
|
||||
*
|
||||
* @param $account
|
||||
*/
|
||||
public function access($account) {
|
||||
return rules_action('component_' . $this->operationInfo['key'])->access();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration form for the operation.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
* @param $context
|
||||
* An array of related data provided by the caller.
|
||||
*/
|
||||
public function form($form, &$form_state, array $context) {
|
||||
$entity_key = $this->operationInfo['parameters']['entity_key'];
|
||||
// List types need to match the original, so passing list<node> instead of
|
||||
// list<entity> won't work. However, passing 'node' instead of 'entity'
|
||||
// will work, and is needed in order to get the right tokens.
|
||||
$list_type = 'list<' . $this->operationInfo['type'] . '>';
|
||||
$entity_type = $this->aggregate() ? $list_type : $this->entityType;
|
||||
$info = entity_get_info($this->entityType);
|
||||
|
||||
// The component action is wrapped in an action set using the entity, so
|
||||
// that the action configuration form can make use of the entity e.g. for
|
||||
// tokens.
|
||||
$set = rules_action_set(array($entity_key => array('type' => $entity_type, 'label' => $info['label'])));
|
||||
$action = rules_action('component_' . $this->operationInfo['key'], array($entity_key . ':select' => $entity_key));
|
||||
$set->action($action);
|
||||
|
||||
// Embed the form of the component action, but default to "input" mode for
|
||||
// all parameters if available.
|
||||
foreach ($action->parameterInfo() as $name => $info) {
|
||||
$form_state['parameter_mode'][$name] = 'input';
|
||||
}
|
||||
$action->form($form, $form_state);
|
||||
|
||||
// Remove the configuration form element for the "entity" param, as it
|
||||
// should just use the passed in entity.
|
||||
unset($form['parameter'][$entity_key]);
|
||||
|
||||
// Tweak direct input forms to be more end-user friendly.
|
||||
foreach ($action->parameterInfo() as $name => $info) {
|
||||
// Remove the fieldset and move its title to the form element.
|
||||
if (isset($form['parameter'][$name]['settings'][$name]['#title'])) {
|
||||
$form['parameter'][$name]['#type'] = 'container';
|
||||
$form['parameter'][$name]['settings'][$name]['#title'] = $form['parameter'][$name]['#title'];
|
||||
}
|
||||
// Hide the switch button if it's there.
|
||||
if (isset($form['parameter'][$name]['switch_button'])) {
|
||||
$form['parameter'][$name]['switch_button']['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the configuration form.
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
*/
|
||||
public function formValidate($form, &$form_state) {
|
||||
rules_ui_form_rules_config_validate($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the rules element added to the form state in form(), so that it
|
||||
* can be used in execute().
|
||||
* Only called if the operation is declared as configurable.
|
||||
*
|
||||
* @param $form
|
||||
* The views form.
|
||||
* @param $form_state
|
||||
* An array containing the current state of the form.
|
||||
*/
|
||||
public function formSubmit($form, &$form_state) {
|
||||
$this->rulesElement = $form_state['rules_element']->root();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the selected operation on the provided data.
|
||||
*
|
||||
* @param $data
|
||||
* The data to to operate on. An entity or an array of entities.
|
||||
* @param $context
|
||||
* An array of related data (selected views rows, etc).
|
||||
*/
|
||||
public function execute($data, array $context) {
|
||||
// If there was a config form, there's a rules_element.
|
||||
// If not, fallback to the component key.
|
||||
if ($this->configurable()) {
|
||||
$element = $this->rulesElement;
|
||||
}
|
||||
else {
|
||||
$element = rules_action('component_' . $this->operationInfo['parameters']['component_key']);
|
||||
}
|
||||
$element->execute($data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* CTools plugin. Provides support for rules components (rule, ruleset, action).
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'title' => t('Rules component'),
|
||||
'list callback' => 'views_bulk_operations_operation_rules_component_list',
|
||||
'handler' => array(
|
||||
'file' => 'rules_component.class.php',
|
||||
'class' => 'ViewsBulkOperationsRulesComponent',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a prepared list of available rules components.
|
||||
*
|
||||
* @param $operation_id
|
||||
* The full, prefixed operation_id of the operation (in this case, rules
|
||||
* component) to return, or NULL to return an array with all operations.
|
||||
*/
|
||||
function views_bulk_operations_operation_rules_component_list($operation_id = NULL) {
|
||||
if (!module_exists('rules')) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$entity_info = entity_get_info();
|
||||
$entity_types = array_keys($entity_info);
|
||||
$supported_types = array('entity', 'list<entity>');
|
||||
$list_types = array('list<entity>');
|
||||
foreach ($entity_types as $type) {
|
||||
$supported_types[] = $type;
|
||||
$supported_types[] = "list<$type>";
|
||||
$list_types[] = "list<$type>";
|
||||
}
|
||||
|
||||
$components = array();
|
||||
if (isset($operation_id)) {
|
||||
$id_fragments = explode('::', $operation_id);
|
||||
$components[$id_fragments[1]] = rules_config_load($id_fragments[1]);
|
||||
// No need to go any further if the component no longer exists.
|
||||
if (!$components[$id_fragments[1]]) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$components = rules_get_components(FALSE, 'action');
|
||||
}
|
||||
|
||||
$operations = array();
|
||||
foreach ($components as $name => $component) {
|
||||
$parameter_info = $component->parameterInfo();
|
||||
$first_parameter = reset($parameter_info);
|
||||
$parameter_keys = array_keys($parameter_info);
|
||||
$entity_key = reset($parameter_keys);
|
||||
// If the first param is not an entity type, skip the component.
|
||||
if (!in_array($first_parameter['type'], $supported_types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the first parameter is a list type (list<node>, list<entity>, etc)
|
||||
// then turn aggregation on, and set the correct entity type.
|
||||
if (in_array($first_parameter['type'], $list_types)) {
|
||||
$type = str_replace(array('list<', '>'), '', $first_parameter['type']);
|
||||
$aggregate = TRUE;
|
||||
}
|
||||
else {
|
||||
$type = $first_parameter['type'];
|
||||
$aggregate = FALSE;
|
||||
}
|
||||
|
||||
// All operations must be prefixed with the operation type.
|
||||
$new_operation_id = 'rules_component::' . $name;
|
||||
$operations[$new_operation_id] = array(
|
||||
'operation_type' => 'rules_component',
|
||||
// Keep the unprefixed key around, for internal use.
|
||||
'key' => $name,
|
||||
'label' => $component->label,
|
||||
'parameters' => array('component_key' => $name, 'entity_key' => $entity_key),
|
||||
'configurable' => count($parameter_info) > 1,
|
||||
'type' => $type,
|
||||
'aggregate' => $aggregate,
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($operation_id)) {
|
||||
return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE;
|
||||
}
|
||||
else {
|
||||
return $operations;
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_views_data_alter().
|
||||
*/
|
||||
function views_bulk_operations_views_data_alter(&$data) {
|
||||
foreach (entity_get_info() as $entity_type => $info) {
|
||||
if (isset($info['base table']) && isset($data[$info['base table']]['table'])) {
|
||||
$data[$info['base table']]['views_bulk_operations'] = array(
|
||||
'title' => $data[$info['base table']]['table']['group'],
|
||||
'group' => t('Bulk operations'),
|
||||
'help' => t('Provide a checkbox to select the row for bulk operations.'),
|
||||
'real field' => $info['entity keys']['id'],
|
||||
'field' => array(
|
||||
'handler' => 'views_bulk_operations_handler_field_operations',
|
||||
'click sortable' => FALSE,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (isset($info['revision table']) && isset($data[$info['revision table']]['table'])) {
|
||||
$data[$info['revision table']]['views_bulk_operations'] = array(
|
||||
'title' => $data[$info['revision table']]['table']['group'],
|
||||
'group' => t('Bulk operations'),
|
||||
'help' => t('Provide a checkbox to select the row for bulk operations.'),
|
||||
'real field' => $info['entity keys']['revision'],
|
||||
'field' => array(
|
||||
'handler' => 'views_bulk_operations_handler_field_operations',
|
||||
'click sortable' => FALSE,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Views field handler. Contains all relevant VBO options and related logic.
|
||||
* Implements the Views Form API.
|
||||
*/
|
||||
|
||||
class views_bulk_operations_handler_field_operations extends views_handler_field {
|
||||
var $revision = FALSE;
|
||||
|
||||
function init(&$view, &$options) {
|
||||
parent::init($view, $options);
|
||||
|
||||
// Update old settings
|
||||
if (!empty($options['vbo']) && empty($this->options['vbo_operations'])) {
|
||||
$this->options['vbo_operations'] = $options['vbo']['operations'];
|
||||
unset($options['vbo']['operations']);
|
||||
$this->options['vbo_settings'] = $options['vbo'] + $this->options['vbo_settings'];
|
||||
}
|
||||
// When updating old Views it is possible for this value to stay empty.
|
||||
if (empty($this->options['vbo_settings']['entity_load_capacity'])) {
|
||||
$this->options['vbo_settings']['entity_load_capacity'] = 10;
|
||||
}
|
||||
|
||||
foreach ($this->options['vbo_operations'] as $operation_id => &$operation_options) {
|
||||
// Prefix all un-prefixed operations.
|
||||
if (strpos($operation_id, '::') === FALSE) {
|
||||
$operations = views_bulk_operations_get_operation_info();
|
||||
// Basically, guess.
|
||||
foreach (array('action', 'rules_component') as $operation_type) {
|
||||
$new_operation_id = $operation_type . '::' . $operation_id;
|
||||
if (isset($operations[$new_operation_id])) {
|
||||
$this->options['vbo_operations'][$new_operation_id] = $operation_options;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old operation in any case.
|
||||
unset($this->options['vbo_operations'][$operation_id]);
|
||||
}
|
||||
|
||||
// Rename the use_queue setting.
|
||||
if (isset($operation_options['use_queue']) && !isset($operation_options['postpone_processing'])) {
|
||||
$operation_options['postpone_processing'] = $operation_options['use_queue'];
|
||||
unset($operation_options['use_queue']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['vbo_settings'] = array(
|
||||
'contains' => array(
|
||||
'display_type' => array('default' => 0),
|
||||
'enable_select_all_pages' => array('default' => TRUE),
|
||||
'force_single' => array('default' => FALSE),
|
||||
'entity_load_capacity' => array('default' => 10),
|
||||
),
|
||||
);
|
||||
$options['vbo_operations'] = array(
|
||||
'default' => array(),
|
||||
);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
|
||||
$form['vbo_settings'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Bulk operations settings'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['vbo_settings']['display_type'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Display operations as'),
|
||||
'#default_value' => $this->options['vbo_settings']['display_type'],
|
||||
'#options' => array(
|
||||
t('Dropdown selectbox with Submit button'),
|
||||
t('Each action as a separate button'),
|
||||
),
|
||||
);
|
||||
$form['vbo_settings']['enable_select_all_pages'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable "Select all items on all pages"'),
|
||||
'#default_value' => $this->options['vbo_settings']['enable_select_all_pages'],
|
||||
'#description' => t('Check this box to enable the ability to select all items on all pages.'),
|
||||
);
|
||||
$form['vbo_settings']['force_single'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Force single'),
|
||||
'#default_value' => $this->options['vbo_settings']['force_single'],
|
||||
'#description' => t('Check this box to restrict selection to a single value.'),
|
||||
);
|
||||
$form['vbo_settings']['entity_load_capacity'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Number of entities to load at once'),
|
||||
'#description' => t("Improve execution performance at the cost of memory usage. Set to '1' if you're having problems."),
|
||||
'#default_value' => $this->options['vbo_settings']['entity_load_capacity'],
|
||||
);
|
||||
|
||||
// Display operations and their settings.
|
||||
$form['vbo_operations'] = array(
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Selected bulk operations'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => FALSE,
|
||||
);
|
||||
|
||||
$entity_type = $this->get_entity_type();
|
||||
$options = $this->options['vbo_operations'];
|
||||
foreach (views_bulk_operations_get_applicable_operations($entity_type, $options) as $operation_id => $operation) {
|
||||
$operation_options = !empty($options[$operation_id]) ? $options[$operation_id] : array();
|
||||
|
||||
$dom_id = 'edit-options-vbo-operations-' . str_replace(array('_', ':'), array('-', ''), $operation_id);
|
||||
$form['vbo_operations'][$operation_id]['selected'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $operation->adminLabel(),
|
||||
'#default_value' => !empty($operation_options['selected']),
|
||||
);
|
||||
if (!$operation->aggregate()) {
|
||||
$form['vbo_operations'][$operation_id]['postpone_processing'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enqueue the operation instead of executing it directly'),
|
||||
'#default_value' => !empty($operation_options['postpone_processing']),
|
||||
'#dependency' => array(
|
||||
$dom_id . '-selected' => array(1),
|
||||
),
|
||||
);
|
||||
}
|
||||
$form['vbo_operations'][$operation_id]['skip_confirmation'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Skip confirmation step'),
|
||||
'#default_value' => !empty($operation_options['skip_confirmation']),
|
||||
'#dependency' => array(
|
||||
$dom_id . '-selected' => array(1),
|
||||
),
|
||||
);
|
||||
|
||||
$form['vbo_operations'][$operation_id] += $operation->adminOptionsForm($dom_id);
|
||||
}
|
||||
}
|
||||
|
||||
function options_validate(&$form, &$form_state) {
|
||||
parent::options_validate($form, $form_state);
|
||||
|
||||
$entity_type = $this->get_entity_type();
|
||||
foreach ($form_state['values']['options']['vbo_operations'] as $operation_id => &$options) {
|
||||
if (empty($options['selected'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
|
||||
$fake_form = $form['vbo_operations'][$operation_id];
|
||||
$fake_form_state = array('values' => &$options);
|
||||
$error_element_base = 'vbo_operations][' . $operation_id . '][';
|
||||
$operation->adminOptionsFormValidate($fake_form, $fake_form_state, $error_element_base);
|
||||
}
|
||||
}
|
||||
|
||||
function options_submit(&$form, &$form_state) {
|
||||
parent::options_submit($form, $form_state);
|
||||
|
||||
$entity_type = $this->get_entity_type();
|
||||
foreach ($form_state['values']['options']['vbo_operations'] as $operation_id => &$options) {
|
||||
if (empty($options['selected'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
|
||||
$fake_form = $form['vbo_operations'][$operation_id];
|
||||
$fake_form_state = array('values' => &$options);
|
||||
$operation->adminOptionsFormSubmit($fake_form, $fake_form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a vbo option.
|
||||
*/
|
||||
function get_vbo_option($key, $default = NULL) {
|
||||
return isset($this->options['vbo_settings'][$key]) ? $this->options['vbo_settings'][$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the view is using a table style, provide a
|
||||
* placeholder for a "select all" checkbox.
|
||||
*/
|
||||
function label() {
|
||||
if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof views_plugin_style_table && !$this->options['vbo_settings']['force_single']) {
|
||||
return '<!--views-bulk-operations-select-all-->';
|
||||
}
|
||||
else {
|
||||
return parent::label();
|
||||
}
|
||||
}
|
||||
|
||||
function render($values) {
|
||||
return '<!--form-item-' . $this->options['id'] . '--' . $this->view->row_index . '-->';
|
||||
}
|
||||
|
||||
/**
|
||||
* The form which replaces the placeholder from render().
|
||||
*/
|
||||
function views_form(&$form, &$form_state) {
|
||||
// The view is empty, abort.
|
||||
if (empty($this->view->result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form[$this->options['id']] = array(
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
// At this point, the query has already been run, so we can access the results
|
||||
// in order to get the base key value (for example, nid for nodes).
|
||||
foreach ($this->view->result as $row_index => $row) {
|
||||
$entity_id = $this->get_value($row);
|
||||
|
||||
if ($this->options['vbo_settings']['force_single']) {
|
||||
$form[$this->options['id']][$row_index] = array(
|
||||
'#type' => 'radio',
|
||||
'#parents' => array($this->options['id']),
|
||||
'#return_value' => $entity_id,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form[$this->options['id']][$row_index] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#return_value' => $entity_id,
|
||||
'#default_value' => FALSE,
|
||||
'#attributes' => array('class' => array('vbo-select')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_selected_operations() {
|
||||
global $user;
|
||||
$selected = drupal_static(__FUNCTION__);
|
||||
if (!isset($selected)) {
|
||||
$entity_type = $this->get_entity_type();
|
||||
$selected = array();
|
||||
foreach ($this->options['vbo_operations'] as $operation_id => $options) {
|
||||
if (empty($options['selected'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
|
||||
if (!$operation || !$operation->access($user)) {
|
||||
continue;
|
||||
}
|
||||
$selected[$operation_id] = $operation;
|
||||
}
|
||||
}
|
||||
|
||||
return $selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options stored for the provided operation id.
|
||||
*/
|
||||
public function get_operation_options($operation_id) {
|
||||
$options = $this->options['vbo_operations'];
|
||||
return isset($options[$operation_id]) ? $options[$operation_id] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the base table of the VBO field, and then use it to determine
|
||||
* the entity type that VBO is operating on.
|
||||
*/
|
||||
public function get_entity_type() {
|
||||
$base_table = $this->view->base_table;
|
||||
|
||||
// If the current field is under a relationship you can't be sure that the
|
||||
// base table of the view is the base table of the current field.
|
||||
// For example a field from a node author on a node view does have users as base table.
|
||||
if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
|
||||
$relationships = $this->view->display_handler->get_option('relationships');
|
||||
$options = $relationships[$this->options['relationship']];
|
||||
$data = views_fetch_data($options['table']);
|
||||
$base_table = $data[$options['field']]['relationship']['base'];
|
||||
}
|
||||
// The base table is now known, use it to determine the entity type.
|
||||
foreach (entity_get_info() as $entity_type => $info) {
|
||||
if (isset($info['base table']) && $info['base table'] == $base_table) {
|
||||
return $entity_type;
|
||||
}
|
||||
elseif (isset($info['revision table']) && $info['revision table'] == $base_table) {
|
||||
$this->revision = TRUE;
|
||||
return $entity_type;
|
||||
}
|
||||
}
|
||||
// This should never happen.
|
||||
_views_bulk_operations_report_error("Could not determine the entity type for VBO field on views base table %table", array('%table' => $base_table));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by Views Bulk Operations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Perform alterations on the VBO form before it is rendered.
|
||||
*
|
||||
* Usually, if a module wanted to alter the VBO form through hook_form_alter(),
|
||||
* it would need to duplicate the views form checks from
|
||||
* views_bulk_operations_form_alter(), while making sure that the hook
|
||||
* runs after VBO's hook (by increasing the weight of the altering module's
|
||||
* system entry). In order to reduce that complexity, VBO provides this hook.
|
||||
*
|
||||
* @param $form
|
||||
* A step of the VBO form to be altered.
|
||||
* @param $form_state
|
||||
* Form state. Contains the name of the current step in $form_state['step'].
|
||||
* @param $vbo
|
||||
* The VBO views field. Contains a reference to the view in $vbo->view.
|
||||
*/
|
||||
function hook_views_bulk_operations_form_alter(&$form, &$form_state, $vbo) {
|
||||
if ($form_state['step'] == 'views_form_views_form') {
|
||||
// Alter the first step of the VBO form (the selection page).
|
||||
$form['select']['#title'] = t('Bulk operations');
|
||||
}
|
||||
elseif ($form_state['step'] == 'views_bulk_operations_config_form') {
|
||||
// Alter the configuration step of the VBO form.
|
||||
}
|
||||
elseif ($form_state['step'] == 'views_bulk_operations_confirm_form') {
|
||||
// Alter the confirmation step of the VBO form.
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implementation of hook_drush_help().
|
||||
*/
|
||||
function views_bulk_operations_drush_help($section) {
|
||||
switch ($section) {
|
||||
case 'drush:vbo-list':
|
||||
return dt('List all Views Bulk Operations (VBO) views, along with the operations associated with each.');
|
||||
case 'drush:vbo-execute':
|
||||
return dt('Execute a bulk operation based on a Views Bulk Operations (VBO) view.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_drush_command().
|
||||
*/
|
||||
function views_bulk_operations_drush_command() {
|
||||
$items['vbo-list'] = array(
|
||||
'callback' => 'views_bulk_operations_drush_list',
|
||||
'description' => 'List all Views Bulk Operations (VBO) views, along with the operations associated with each.',
|
||||
);
|
||||
$items['vbo-execute'] = array(
|
||||
'callback' => 'views_bulk_operations_drush_execute',
|
||||
'description' => 'Execute a bulk operation based on a Views Bulk Operations (VBO) view.',
|
||||
'arguments' => array(
|
||||
'vid' => 'ID or name of the view to be executed.',
|
||||
'operation_id' => 'ID of the operation to be applied on the view results.',
|
||||
'type:[name=]value ...' => 'Parameters to be passed as view input filters, view arguments or operation arguments, where type is respectively {input, argument, operation}.',
|
||||
),
|
||||
'examples' => array(
|
||||
'$ drush vbo-execute action::admin_content node_publish_action' =>
|
||||
'Publish nodes returned by view admin_content.',
|
||||
'$ drush vbo-execute 44 action::node_assign_owner_action operation:owner_uid=3' =>
|
||||
'Change node ownership on nodes returned by view #44, passing argument owner_uid=3 to the action.',
|
||||
'$ drush vbo-execute admin_content action::node_unpublish_action input:type=expense argument:3' =>
|
||||
'Unpublish nodes returned by view admin_content, filtering results of type expense and passing value 3 as first view argument.',
|
||||
),
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of 'vbo list' command.
|
||||
*/
|
||||
function views_bulk_operations_drush_list() {
|
||||
// Impersonate admin.
|
||||
global $user;
|
||||
$user = user_load(1);
|
||||
drupal_save_session(FALSE);
|
||||
|
||||
// Find all VBO views and their associated operations.
|
||||
$rows = array(array(sprintf('%5s', dt('View ID')), dt('Name'), dt('Description'), dt('Operations')));
|
||||
foreach (views_get_all_views() as $name => $view) {
|
||||
$view->build();
|
||||
$vbo = _views_bulk_operations_get_field($view);
|
||||
if ($vbo) {
|
||||
$operations = array();
|
||||
foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
|
||||
$operations[] = $operation->label() .' ('. $operation_id .')';
|
||||
}
|
||||
$operations[] = "---------------";
|
||||
$rows[] = array(
|
||||
sprintf('%5d', $view->vid),
|
||||
$view->name,
|
||||
$view->description,
|
||||
implode("\n", $operations),
|
||||
);
|
||||
}
|
||||
}
|
||||
drush_print_table($rows, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of 'vbo execute' command.
|
||||
*/
|
||||
function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL) {
|
||||
// Parse arguments.
|
||||
if (is_null($vid)) {
|
||||
drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_VID', dt('Please specify a view ID to execute.'));
|
||||
return;
|
||||
}
|
||||
if (is_null($operation_id)) {
|
||||
drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_OPERATION', dt('Please specify an operation to execute.'));
|
||||
return;
|
||||
}
|
||||
$args = func_get_args();
|
||||
$view_exposed_input = array();
|
||||
$operation_arguments = array();
|
||||
$view_arguments = array();
|
||||
if (count($args) > 2) for ($i=2; $i<count($args); $i++) {
|
||||
$parts = array();
|
||||
if (FALSE === preg_match('/^(?<type>\w+):(?:(?<name>\w+)=)?(?<value>(.*?))$/', $args[$i], $parts)) {
|
||||
drush_set_error('VIEWS_BULK_OPERATIONS_INVALID_PARAMETER', dt('The parameter %arg should be of the form type:[name=]value where type in {input, argument, operation}.', array('%arg' => $args[$i])));
|
||||
return;
|
||||
}
|
||||
switch ($parts['type']) {
|
||||
case 'input':
|
||||
$view_exposed_input[$parts['name']] = $parts['value'];
|
||||
break;
|
||||
case 'operation':
|
||||
$operation_arguments[$parts['name']] = $parts['value'];
|
||||
break;
|
||||
case 'argument':
|
||||
$view_arguments[] = $parts['value'];
|
||||
break;
|
||||
default:
|
||||
drush_set_error('VIEWS_BULK_OPERATIONS_UNKNOWN_PARAMETER', dt('The parameter type %type is unknown. Please specify either input, argument or operation.', array('%type' => $parts['type'])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Impersonate admin.
|
||||
global $user;
|
||||
$user = user_load(1);
|
||||
drupal_save_session(FALSE);
|
||||
|
||||
// Load the view.
|
||||
$view = views_get_view($vid);
|
||||
if (!is_object($view)) {
|
||||
_views_bulk_operations_report_error('Could not find view %vid.', array('%vid' => $vid));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the view, so that the VBO field can be found.
|
||||
$view->set_exposed_input($view_exposed_input);
|
||||
$view->set_arguments($view_arguments);
|
||||
$view->build();
|
||||
$view->query->set_limit(NULL); // reset the work done by the pager
|
||||
$view->query->set_offset(NULL);
|
||||
|
||||
// Find the VBO field.
|
||||
$vbo = _views_bulk_operations_get_field($view);
|
||||
if (!$vbo) {
|
||||
_views_bulk_operations_report_error('Could not find a VBO field in view %vid.', array('%vid' => $vid));
|
||||
return;
|
||||
}
|
||||
|
||||
$view->execute();
|
||||
|
||||
// Find the selected operation.
|
||||
$operations = $vbo->get_selected_operations();
|
||||
if (!isset($operations[$operation_id])) {
|
||||
_views_bulk_operations_report_error('Could not find operation %operation_id in view %vid.', array('%operation_id' => $operation_id, '%vid' => $vid));
|
||||
return;
|
||||
}
|
||||
$operation = views_bulk_operations_get_operation($operation_id, $vbo->get_entity_type(), $vbo->get_operation_options($operation_id));
|
||||
if ($operation_arguments) {
|
||||
$operation->formOptions = $operation_arguments;
|
||||
}
|
||||
|
||||
// Select all rows.
|
||||
$rows = array();
|
||||
$current = 1;
|
||||
foreach ($view->result as $row_index => $result) {
|
||||
$rows[$row_index] = array(
|
||||
'entity_id' => $vbo->get_value($result),
|
||||
'views_row' => array(),
|
||||
'position' => array(
|
||||
'current' => $current++,
|
||||
'total' => $view->total_rows,
|
||||
),
|
||||
);
|
||||
// Some operations require full selected rows.
|
||||
if ($operation->needsRows()) {
|
||||
$rows[$row_index]['views_row'] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue the fetched rows.
|
||||
$queue_name = 'views_bulk_operations_active_queue_' . db_next_id();
|
||||
$options = array(
|
||||
'revision' => $vbo->revision,
|
||||
'entity_load_capacity' => $vbo->get_vbo_option('entity_load_capacity', 10),
|
||||
);
|
||||
views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options);
|
||||
|
||||
// Process the queue using Batch API.
|
||||
$batch = array(
|
||||
'file' => drupal_get_path('module', 'views_bulk_operations') . '/views_bulk_operations.module',
|
||||
'operations' => array(
|
||||
array('views_bulk_operations_active_queue_process', array($queue_name, $operation, $vbo->view->total_rows)),
|
||||
),
|
||||
'finished' => '_views_bulk_operations_execute_finished',
|
||||
'title' => t('Performing %operation on the selected items...', array('%operation' => $operation->label())),
|
||||
);
|
||||
batch_set($batch);
|
||||
drush_backend_batch_process();
|
||||
|
||||
// Looks like drush has no way to show messages set in subprocesses,
|
||||
// so all batch messages get lost. Setting a success message manually here.
|
||||
drush_log(dt('Performed "!operation" on @items.', array(
|
||||
'!operation' => $operation->label(),
|
||||
'@items' => format_plural($vbo->view->total_rows, '1 item', '@count items'),
|
||||
)), 'ok');
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
name = Views Bulk Operations
|
||||
description = Provides a way of selecting multiple rows and applying operations to them.
|
||||
dependencies[] = entity
|
||||
dependencies[] = views
|
||||
package = Views
|
||||
core = 7.x
|
||||
|
||||
files[] = plugins/operation_types/base.class.php
|
||||
files[] = views/views_bulk_operations_handler_field_operations.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2012-12-03
|
||||
version = "7.x-3.1"
|
||||
core = "7.x"
|
||||
project = "views_bulk_operations"
|
||||
datestamp = "1354500015"
|
||||
|
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation and update functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function views_bulk_operations_uninstall() {
|
||||
// Remove VBO actions that are now orphaned.
|
||||
actions_synchronize(TRUE);
|
||||
}
|
1223
sites/all/modules/views_bulk_operations/views_bulk_operations.module
Normal file
1223
sites/all/modules/views_bulk_operations/views_bulk_operations.module
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Views Bulk Operations conditions and actions for Rules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_rules_condition_info().
|
||||
*/
|
||||
function views_bulk_operations_rules_condition_info() {
|
||||
$conditions = array();
|
||||
$conditions['views_bulk_operations_condition_result_count'] = array(
|
||||
'label' => t('Check number of results returned by a VBO View'),
|
||||
'parameter' => array(
|
||||
'view' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('View and display'),
|
||||
'options list' => 'views_bulk_operations_views_list',
|
||||
'description' => t('Select the VBO view and display you want to check'),
|
||||
'restriction' => 'input',
|
||||
),
|
||||
'args' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Arguments'),
|
||||
'description' => t('Any arguments to pass to the view, one per line.
|
||||
You may use token replacement patterns.'),
|
||||
'optional' => TRUE,
|
||||
),
|
||||
'minimum' => array(
|
||||
'type' => 'integer',
|
||||
'label' => t('Minimum number of results'),
|
||||
'description' => t('This condition returns TRUE if the view has at
|
||||
least the given number of results.'),
|
||||
),
|
||||
),
|
||||
'group' => t('Views Bulk Operations'),
|
||||
);
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_rules_action_info().
|
||||
*/
|
||||
function views_bulk_operations_rules_action_info() {
|
||||
$actions = array();
|
||||
$actions['views_bulk_operations_action_load_list'] = array(
|
||||
'label' => t('Load a list of entity objects from a VBO View.'),
|
||||
'parameter' => array(
|
||||
'view' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('View and display'),
|
||||
'options list' => 'views_bulk_operations_views_list',
|
||||
'description' => t('Select the view and display you want to use to
|
||||
create a list.'),
|
||||
'restriction' => 'input',
|
||||
),
|
||||
'args' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Arguments'),
|
||||
'description' => t('Any arguments to pass to the view, one per line.
|
||||
You may use token replacement patterns.'),
|
||||
'optional' => TRUE,
|
||||
),
|
||||
),
|
||||
'provides' => array(
|
||||
'entity_list' => array(
|
||||
'type' => 'list<entity>',
|
||||
'label' => t('A list of entities'),
|
||||
),
|
||||
),
|
||||
'group' => t('Views Bulk Operations'),
|
||||
);
|
||||
$actions['views_bulk_operations_action_load_id_list'] = array(
|
||||
'label' => t('Load a list of entity ids from a VBO View.'),
|
||||
'parameter' => array(
|
||||
'view' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('View and display'),
|
||||
'options list' => 'views_bulk_operations_views_list',
|
||||
'description' => t('Select the view and display you want to use to
|
||||
create a list.'),
|
||||
'restriction' => 'input',
|
||||
),
|
||||
'args' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Arguments'),
|
||||
'description' => t('Any arguments to pass to the view, one per line.
|
||||
You may use token replacement patterns.'),
|
||||
'optional' => TRUE,
|
||||
),
|
||||
),
|
||||
'provides' => array(
|
||||
'entity_id_list' => array(
|
||||
'type' => 'list<integer>',
|
||||
'label' => t('A list of entity ids'),
|
||||
),
|
||||
),
|
||||
'group' => t('Views Bulk Operations'),
|
||||
);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all available VBO Views and their displays.
|
||||
* Naturally, only the displays that contain a VBO field are listed.
|
||||
*
|
||||
* @return array
|
||||
* An array of all views and their displays on the form 'view|display',
|
||||
* formatted to be used as an select list.
|
||||
*/
|
||||
function views_bulk_operations_views_list() {
|
||||
$selectable_displays = array();
|
||||
foreach (views_get_enabled_views() as $name => $view) {
|
||||
foreach ($view->display as $display_name => $display) {
|
||||
$view->build($display_name);
|
||||
$vbo = _views_bulk_operations_get_field($view);
|
||||
if ($vbo) {
|
||||
$selectable_displays[$view->name . '|' . $display_name] = check_plain($view->human_name) . ' | ' . check_plain($display->display_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $selectable_displays;
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'views_bulk_operations_condition_result_count' condition.
|
||||
*
|
||||
* @param $view
|
||||
* A string in the format "$view_name|$display_name".
|
||||
* @param $args
|
||||
* Arguments that should be passed to the View.
|
||||
* @param $minimum
|
||||
* An integer representing the minimum number of results that satisfies the
|
||||
* condition.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the view has more than $minimum results, FALSE otherwise.
|
||||
*/
|
||||
function views_bulk_operations_condition_result_count($view, $args, $minimum) {
|
||||
$vbo = _views_bulk_operations_rules_get_field($view, $args);
|
||||
return (count($vbo->view->result) >= $minimum);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'views_bulk_operations_action_views_load_list' action.
|
||||
*
|
||||
* @param $view
|
||||
* A string in the format "$view_name|$display_name".
|
||||
* @param $args
|
||||
* Arguments that should be passed to the View.
|
||||
* @return array
|
||||
* Array containing the entity_list, an array of entity objects.
|
||||
* - array('entity_list' => array(...))
|
||||
*/
|
||||
function views_bulk_operations_action_load_list($view, $args) {
|
||||
$vbo = _views_bulk_operations_rules_get_field($view, $args);
|
||||
|
||||
// Get all entity ids.
|
||||
$ids = array();
|
||||
foreach ($vbo->view->result as $row_index => $result) {
|
||||
$ids[] = $vbo->get_value($result);
|
||||
}
|
||||
// And load the entity objects.
|
||||
$entities = entity_load($vbo->get_entity_type(), $ids);
|
||||
|
||||
return array('entity_list' => $entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'views_bulk_operations_action_views_load_id_list' action.
|
||||
*
|
||||
* @param $view
|
||||
* A string in the format "$view_name|$display_name".
|
||||
* @param $args
|
||||
* Arguments that should be passed to the View.
|
||||
* @return array
|
||||
* Array containing the entity_id_list, an Array of entity ids as integer
|
||||
* values.
|
||||
* - array('entity_list' => array(...))
|
||||
*/
|
||||
function views_bulk_operations_action_load_id_list($view, $args) {
|
||||
$vbo = _views_bulk_operations_rules_get_field($view, $args);
|
||||
|
||||
// Get all entity ids.
|
||||
$ids = array();
|
||||
foreach ($vbo->view->result as $row_index => $result) {
|
||||
$ids[] = $vbo->get_value($result);
|
||||
}
|
||||
|
||||
return array('entity_id_list' => $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Info alteration callback for the 'views_bulk_operations_action_views_load_list' action.
|
||||
*
|
||||
* The info hook specifies that the action returns a generic list of entities
|
||||
* (list<entity>). All actions that require entities of specific type can't
|
||||
* use such entities, so this alter hook specifies the exact entity type
|
||||
* after the action has been configured, allowing the view to be loaded
|
||||
* and its entity type extracted.
|
||||
*/
|
||||
function views_bulk_operations_action_load_list_info_alter(&$element_info, RulesAbstractPlugin $element) {
|
||||
// The action hasn't been configured yet, hence no view. Abort.
|
||||
if (empty($element->settings['view'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity_type = _views_bulk_operations_rules_get_entity_type($element->settings['view']);
|
||||
if ($entity_type) {
|
||||
$element_info['provides']['entity_list']['type'] = "list<$entity_type>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that loads and builds (but doesn't execute) the specified view,
|
||||
* then determines the entity type on which the VBO field operates.
|
||||
*
|
||||
* @param $view_target
|
||||
* A string in the format "$view_name|$display_name".
|
||||
*
|
||||
* @return
|
||||
* The entity type on which the VBO field operates.
|
||||
*/
|
||||
function _views_bulk_operations_rules_get_entity_type($view_target) {
|
||||
$entity_types = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($entity_types[$view_target])) {
|
||||
$views_settings = explode('|', $view_target);
|
||||
if ($view = views_get_view($views_settings[0])) {
|
||||
$view->set_display($views_settings[1]);
|
||||
$view->build();
|
||||
|
||||
$vbo = _views_bulk_operations_get_field($view);
|
||||
}
|
||||
$entity_type = !empty($vbo) ? $vbo->get_entity_type() : '';
|
||||
$entity_types[$view_target] = $entity_type;
|
||||
}
|
||||
|
||||
return $entity_types[$view_target];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that loads, builds and executes the specified view,
|
||||
* then returns its VBO field.
|
||||
*
|
||||
* @param $view_target
|
||||
* A string in the format "$view_name|$display_name".
|
||||
* @param $args
|
||||
* Arguments that should be passed to the View.
|
||||
*
|
||||
* @return
|
||||
* The VBO field. Contains a reference to the View.
|
||||
*/
|
||||
function _views_bulk_operations_rules_get_field($view_target, $args) {
|
||||
$views = &drupal_static(__FUNCTION__);
|
||||
|
||||
$views_settings = explode('|', $view_target);
|
||||
$view_name = $views_settings[0];
|
||||
$display_name = $views_settings[1];
|
||||
// Create an array of arguments.
|
||||
$view_arguments = explode("\n", $args);
|
||||
$view_arguments = array_map('trim', $view_arguments);
|
||||
$view_arguments = array_filter($view_arguments, 'strlen');
|
||||
// Append the filtered list of arguments to $views_target, so that the correct
|
||||
// View is fetched from cache.
|
||||
if (!empty($view_arguments)) {
|
||||
$view_target .= '|' . implode('&', $view_arguments);
|
||||
}
|
||||
|
||||
// Don't execute the requested View more than once in a single page request.
|
||||
if (isset($views[$view_target])) {
|
||||
$vbo = _views_bulk_operations_get_field($views[$view_target]);
|
||||
return $vbo;
|
||||
}
|
||||
|
||||
// Load the view and set the properties.
|
||||
$view = views_get_view($view_name);
|
||||
$view->set_display($display_name);
|
||||
$view->set_arguments($view_arguments);
|
||||
$view->build();
|
||||
$vbo = _views_bulk_operations_get_field($view);
|
||||
// Unset every field except the VBO one (which holds the entity id).
|
||||
// That way the performance hit becomes much smaller, because there is no
|
||||
// chance of views_handler_field_field::post_execute() firing entity_load().
|
||||
foreach ($view->field as $field_name => $field) {
|
||||
if ($field_name != $vbo->options['id']) {
|
||||
unset($view->field[$field_name]);
|
||||
}
|
||||
}
|
||||
$view->execute($view->current_display);
|
||||
// Save the view in the static cache.
|
||||
$views[$view_target] = $view;
|
||||
|
||||
return $vbo;
|
||||
}
|
Reference in New Issue
Block a user