first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,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.

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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]);
}

View File

@@ -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));
}

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
div[class*="show-value"] {
float: left;
margin-right: 20px;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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,
),
);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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.
}
}

View File

@@ -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');
}

View File

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

View File

@@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}