updated migration to add workflow state and also skipe notice pages, added favicon, improved admin views
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,31 @@
|
||||
CONTENTS OF THIS FILE
|
||||
---------------------
|
||||
|
||||
* Introduction
|
||||
* Installation
|
||||
* Configuration
|
||||
|
||||
|
||||
INTRODUCTION
|
||||
------------
|
||||
|
||||
This module will delete any nodes of a certain node-type using the batch api.
|
||||
It is advised to use the Views Batch Operations module (VBO) for a small number
|
||||
of nodes. But if you have to delete 10.000 nodes this module might be a better
|
||||
option.
|
||||
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
|
||||
The bulkdelete.module is very similar to other Drupal modules. For installation
|
||||
of the bulkdelete module please follow the below mentioned steps:
|
||||
|
||||
• Install as usual
|
||||
• Enable the bulkdelete module.
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
|
||||
• There is no configuration for this module.
|
||||
@@ -0,0 +1,11 @@
|
||||
name: Bulk Delete
|
||||
type: module
|
||||
description: 'Adds an administrative option to bulk delete nodes by content type using the batch API.'
|
||||
# core: 8.x
|
||||
configure: bulkdelete.admin
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-05-23
|
||||
version: '8.x-1.x-dev'
|
||||
core: '8.x'
|
||||
project: 'bulkdelete'
|
||||
datestamp: 1495565589
|
||||
@@ -0,0 +1,6 @@
|
||||
# bulkdelete.links.task.yml snippet
|
||||
bulkdelete.admin:
|
||||
route_name: bulkdelete.admin
|
||||
title: Bulk delete nodes
|
||||
base_route: system.admin_content
|
||||
weight: 11
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Bulkdelete module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function bulkdelete_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.bulkdelete':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('This module will delete any nodes of a certain node-type
|
||||
using the batch api. It is advised to use the Views Batch Operations module (VBO)
|
||||
for a small number of nodes. But if you have to delete 10.000 nodes this module
|
||||
might be a better option.') . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
bulkdelete nodes:
|
||||
title: 'Access bulk delete nodes'
|
||||
description: 'Access bulkdelete.'
|
||||
restrict access: true
|
||||
@@ -0,0 +1,8 @@
|
||||
# bulkdelete.routing.yml snippet
|
||||
bulkdelete.admin:
|
||||
path: '/admin/content/bulkdelete'
|
||||
defaults:
|
||||
_form: '\Drupal\bulkdelete\Form\BulkDeleteForm'
|
||||
_title: 'Bulk delete nodes'
|
||||
requirements:
|
||||
_permission: 'bulkdelete nodes'
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\bulkdelete\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
|
||||
/**
|
||||
* BackupDatabaseForm class.
|
||||
*/
|
||||
class BulkDeleteForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'bulkdelete_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @todo, displays last backup timestamp
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$options = [];
|
||||
// Get list of content type.
|
||||
$types = node_type_get_types();
|
||||
ksort($types);
|
||||
foreach ($types as $key => $values) {
|
||||
$query = \Drupal::entityQuery('node')
|
||||
// Filter by content type.
|
||||
->condition('type', $key)
|
||||
// Count.
|
||||
->count();
|
||||
$count = $query->execute();
|
||||
if ($count > 0) {
|
||||
$options[$key] = $values->get('name') . " ($count)";
|
||||
}
|
||||
}
|
||||
if (empty($options)) {
|
||||
$form['default_msg'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('Node not available.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$form['types'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Content types for deletion'),
|
||||
'#options' => $options,
|
||||
'#description' => $this->t('All nodes of these types will be deleted using the batch API.'),
|
||||
];
|
||||
|
||||
$form['actions']['#type'] = 'actions';
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Delete'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
$types = array_filter($values['types']);
|
||||
if (count($types) > 0) {
|
||||
try {
|
||||
foreach ($types as $bundle) {
|
||||
$result = db_select('node');
|
||||
$query = $result->fields('node', ['nid']);
|
||||
$query = $result->condition('type', $bundle);
|
||||
$query = $result->execute()->fetchAll();
|
||||
$last_row = count($query);
|
||||
$operations = [];
|
||||
if (!empty($last_row)) {
|
||||
$message = t('All nodes of type @content mark for deletion', ['@content' => $bundle]);
|
||||
\Drupal::logger('bulkdelete')->notice($message);
|
||||
// Create batch of 20 nodes.
|
||||
$count = 1;
|
||||
foreach ($query as $row) {
|
||||
$nids[] = $row->nid;
|
||||
if ($count % 20 === 0 || $count === $last_row) {
|
||||
$operations[] = [
|
||||
[get_class($this),
|
||||
'processBatch',
|
||||
],
|
||||
[$nids],
|
||||
];
|
||||
$nids = [];
|
||||
}
|
||||
++$count;
|
||||
}
|
||||
// Set up the Batch API.
|
||||
$batch = [
|
||||
'operations' => $operations,
|
||||
'finished' => [get_class($this), 'bulkDeleteFinishedBatch'],
|
||||
'title' => $this->t('Node bulk delete'),
|
||||
'init_message' => $this->t('Starting nodes deletion.'),
|
||||
'progress_message' => $this->t('Completed @current step of @total.'),
|
||||
'error_message' => $this->t('Bulk node deletion has encountered an error.'),
|
||||
];
|
||||
batch_set($batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
foreach ($e->getErrors() as $error_message) {
|
||||
drupal_set_message($error_message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the bulk node deletion.
|
||||
*
|
||||
* @param array $nids
|
||||
* Node nid.
|
||||
* @param array $context
|
||||
* The batch context.
|
||||
*/
|
||||
public static function processBatch(array $nids, array &$context) {
|
||||
entity_delete_multiple('node', $nids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch finish function.
|
||||
*
|
||||
* This function is called by the batch 'finished' parameter.
|
||||
* The cache must not be cleared as the last batch operation,
|
||||
* but after the batch is finished.
|
||||
*
|
||||
* @param bool $success
|
||||
* Indicates if the batch was successfully finished.
|
||||
* @param array $results
|
||||
* The value of the results item from the context variable used in the batch
|
||||
* processing.
|
||||
* @param array $operations
|
||||
* If the success parameter is false then this is a list of the operations
|
||||
* that haven't completed yet.
|
||||
*/
|
||||
public static function bulkDeleteFinishedBatch($success, array $results, array $operations) {
|
||||
drupal_flush_all_caches();
|
||||
$message = $success ? t('Bulkdelete performed successfully.') : t('Bulkdelete has not been finished successfully.');
|
||||
\Drupal::logger('bulkdelete')->notice($message);
|
||||
drupal_set_message($message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "drupal/views_bulk_edit",
|
||||
"type": "drupal-module",
|
||||
"description": "Allows bulk edition of entity field values.",
|
||||
"homepage": "http://drupal.org/project/views_bulk_edit",
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"drupal/views_bulk_operations": "~1.0 | ~2.0"
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.views-bulk-edit-form .form-type-checkbox,
|
||||
.views-bulk-edit-form .js-form-type-checkbox {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
}
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_edit\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsPreconfigurationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewData;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessor;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* Modify entity field values.
|
||||
*
|
||||
* @Action(
|
||||
* id = "views_bulk_edit",
|
||||
* label = @Translation("Modify field values"),
|
||||
* type = ""
|
||||
* )
|
||||
*/
|
||||
class ModifyEntityValues extends ViewsBulkOperationsActionBase implements ContainerFactoryPluginInterface, ViewsBulkOperationsPreconfigurationInterface {
|
||||
|
||||
/**
|
||||
* Database conection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* Plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin Id.
|
||||
* @param mixed $plugin_definition
|
||||
* Plugin definition.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewData $viewDataService
|
||||
* The VBO view data service.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessor $actionProcessor
|
||||
* The VBO action processor.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* Entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundleInfo
|
||||
* Bundle info object.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ViewsbulkOperationsViewData $viewDataService, ViewsBulkOperationsActionProcessor $actionProcessor, EntityTypeManagerInterface $entityTypeManager, EntityTypeBundleInfoInterface $bundleInfo, Connection $database) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->viewDataService = $viewDataService;
|
||||
$this->actionProcessor = $actionProcessor;
|
||||
$this->entityTypeManager = $entityTypeManager;
|
||||
$this->bundleInfo = $bundleInfo;
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('views_bulk_operations.data'),
|
||||
$container->get('views_bulk_operations.processor'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('database')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPreConfigurationForm(array $form, array $values, FormStateInterface $form_state) {
|
||||
$form['get_bundles_from_results'] = [
|
||||
'#title' => $this->t('Get entity bundles from results'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => isset($values['get_bundles_from_results']) ? $values['get_bundles_from_results'] : TRUE,
|
||||
'#description' => $this->t('NOTE: If performance issues are observed when using "All results in this view" selector in case of large result sets, uncheck this and use a bundle filter (node type, taxonomy vocabulary etc.) on the view.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
// Get view bundles.
|
||||
$bundle_data = $this->getViewBundles();
|
||||
|
||||
// Store entity data.
|
||||
$storage = $form_state->getStorage();
|
||||
$storage['vbe_entity_bundles_data'] = $bundle_data;
|
||||
$form_state->setStorage($storage);
|
||||
|
||||
$form['#attributes']['class'] = ['views-bulk-edit-form'];
|
||||
$form['#attached']['library'][] = 'views_bulk_edit/views_bulk_edit.edit_form';
|
||||
|
||||
foreach ($bundle_data as $entity_type_id => $bundles) {
|
||||
foreach ($bundles as $bundle => $label) {
|
||||
$form = $this->getBundleForm($entity_type_id, $bundle, $label, $form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get bundles displayed by the view.
|
||||
*
|
||||
* @return array
|
||||
* Array of entity bundles returned by the current view
|
||||
* keyed by entity type IDs.
|
||||
*/
|
||||
protected function getViewBundles() {
|
||||
|
||||
// Get a list of all entity types and bundles of the view.
|
||||
$bundle_data = [];
|
||||
$bundle_info = $this->bundleInfo->getAllBundleInfo();
|
||||
|
||||
// If the list of selected results is available,
|
||||
// query db for selected bundles.
|
||||
if (!empty($this->context['list'])) {
|
||||
$query_data = [];
|
||||
foreach ($this->context['list'] as $item) {
|
||||
list(,, $entity_type_id, $id,) = $item;
|
||||
$query_data[$entity_type_id][$id] = $id;
|
||||
}
|
||||
foreach ($query_data as $entity_type_id => $entity_ids) {
|
||||
$entityTypeDefinition = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
if ($bundle_key = $entityTypeDefinition->getKey('bundle')) {
|
||||
$id_key = $entityTypeDefinition->getKey('id');
|
||||
|
||||
$results = $this->database->select($entityTypeDefinition->getBaseTable(), 'base')
|
||||
->fields('base', [$bundle_key])
|
||||
->condition($id_key, $entity_ids, 'IN')
|
||||
->execute()
|
||||
->fetchCol();
|
||||
|
||||
foreach ($results as $bundle_id) {
|
||||
if (!isset($bundle_data[$entity_type_id][$bundle_id])) {
|
||||
$bundle_data[$entity_type_id][$bundle_id] = $bundle_info[$entity_type_id][$bundle_id]['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$bundle_data[$entity_type_id][$entity_type_id] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not, fallback to other methods.
|
||||
else {
|
||||
// Initialize view and VBO view data service.
|
||||
$view = Views::getView($this->context['view_id']);
|
||||
$view->setDisplay($this->context['display_id']);
|
||||
if (!empty($this->context['arguments'])) {
|
||||
$view->setArguments($this->context['arguments']);
|
||||
}
|
||||
if (!empty($this->context['exposed_input'])) {
|
||||
$view->setExposedInput($this->context['exposed_input']);
|
||||
}
|
||||
$view->build();
|
||||
|
||||
$this->viewDataService->init($view, $view->getDisplay(), $this->context['relationship_id']);
|
||||
|
||||
// If administrator chose this method, get bundles from actual
|
||||
// view results.
|
||||
// NOTE: This can cause performance problems in case of large result sets!
|
||||
if (!empty($this->context['preconfiguration']['get_bundles_from_results'])) {
|
||||
$entities = [];
|
||||
if (empty($this->context['list'])) {
|
||||
$view->query->setLimit(0);
|
||||
$view->query->setOffset(0);
|
||||
$view->query->execute($view);
|
||||
|
||||
foreach ($view->result as $row) {
|
||||
$entities[] = $this->viewDataService->getEntity($row);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
foreach ($this->context['list'] as $item) {
|
||||
$entities[] = $this->actionProcessor->getEntity($item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($entities)) {
|
||||
foreach ($entities as $entity) {
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$bundle = $entity->bundle();
|
||||
if (!isset($bundle_data[$entity_type_id][$bundle])) {
|
||||
$bundle_data[$entity_type_id][$bundle] = $bundle_info[$entity_type_id][$bundle]['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The painless way, may display form for more bundles
|
||||
// than the actual result set has.
|
||||
else {
|
||||
// Try to get possible bundles from a bundle filter, fixed or exposed,
|
||||
// if exists (hopefully).
|
||||
foreach ($this->viewDataService->getEntityTypeIds() as $entity_type_id) {
|
||||
$entityTypeDefinition = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
$bundle_key = $entityTypeDefinition->getKey('bundle');
|
||||
if (isset($view->filter[$bundle_key]) && !empty($view->filter[$bundle_key]->value)) {
|
||||
foreach ($view->filter[$bundle_key]->value as $bundle) {
|
||||
$bundle_data[$entity_type_id][$bundle] = $bundle_info[$entity_type_id][$bundle]['label'];
|
||||
}
|
||||
}
|
||||
|
||||
// If previous failed and admin did not set to get bundles
|
||||
// from view results, get all bundles of displayed entity types.
|
||||
elseif (empty($this->context['preconfiguration']['get_bundles_from_results'])) {
|
||||
if (isset($bundle_info[$entity_type_id])) {
|
||||
foreach ($bundle_info[$entity_type_id] as $bundle => $label) {
|
||||
$bundle_data[$entity_type_id][$bundle] = $bundle_info[$entity_type_id][$bundle]['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $bundle_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the form for this entity display.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The bundle ID.
|
||||
* @param mixed $bundle_label
|
||||
* Bundle label.
|
||||
* @param array $form
|
||||
* Form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form_state object.
|
||||
*
|
||||
* @return array
|
||||
* Edit form for the current entity bundle.
|
||||
*/
|
||||
protected function getBundleForm($entity_type_id, $bundle, $bundle_label, array $form, FormStateInterface $form_state) {
|
||||
$entityType = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
$entity = $this->entityTypeManager->getStorage($entity_type_id)->create([
|
||||
$entityType->getKey('bundle') => $bundle,
|
||||
]);
|
||||
|
||||
if (!isset($form[$entity_type_id])) {
|
||||
$form[$entity_type_id] = [
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
// If there is no bundle label, the entity has no bundles.
|
||||
if (empty($bundle_label)) {
|
||||
$bundle_label = $entityType->getLabel();
|
||||
}
|
||||
$form[$entity_type_id][$bundle] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $entityType->getLabel() . ' - ' . $bundle_label,
|
||||
'#parents' => [$entity_type_id, $bundle],
|
||||
];
|
||||
|
||||
$form_display = EntityFormDisplay::collectRenderDisplay($entity, 'bulk_edit');
|
||||
$form_display->buildForm($entity, $form[$entity_type_id][$bundle], $form_state);
|
||||
|
||||
$form[$entity_type_id][$bundle] += $this->getSelectorForm($entity_type_id, $bundle, $form[$entity_type_id][$bundle]);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the selector form.
|
||||
*
|
||||
* Given an entity form, create a selector form to provide options to update
|
||||
* values.
|
||||
*
|
||||
* @param string $bundle
|
||||
* The bundle machine name.
|
||||
* @param array $form
|
||||
* The form we're building the selection options for.
|
||||
*
|
||||
* @return array
|
||||
* The new selector form.
|
||||
*/
|
||||
protected function getSelectorForm($entity_type_id, $bundle, array &$form) {
|
||||
$selector['_field_selector'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Select fields to change'),
|
||||
'#weight' => -50,
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
foreach (Element::children($form) as $key) {
|
||||
if (isset($form[$key]['#access']) && !$form[$key]['#access']) {
|
||||
continue;
|
||||
}
|
||||
if ($key == '_field_selector' || !$element = &$this->findFormElement($form[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$element['#required'] = FALSE;
|
||||
$element['#tree'] = TRUE;
|
||||
|
||||
// Add the toggle field to the form.
|
||||
$selector['_field_selector'][$key] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $element['#title'],
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
// Force the original value to be hidden unless the checkbox is enabled.
|
||||
$form[$key]['#states'] = [
|
||||
'visible' => [
|
||||
sprintf('[name="%s[%s][_field_selector][%s]"]', $entity_type_id, $bundle, $key) => ['checked' => TRUE],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if (empty(Element::children($selector['_field_selector']))) {
|
||||
$selector['_field_selector']['#title'] = $this->t('There are no fields available to modify');
|
||||
}
|
||||
|
||||
return $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the deepest most form element and returns it.
|
||||
*
|
||||
* @param array $form
|
||||
* The form element we're searching.
|
||||
*
|
||||
* @return array|null
|
||||
* The deepest most element if we can find it.
|
||||
*/
|
||||
protected function &findFormElement(array &$form) {
|
||||
foreach (Element::children($form) as $key) {
|
||||
if (isset($form[$key]['#title']) && isset($form[$key]['#type'])) {
|
||||
return $form[$key];
|
||||
}
|
||||
elseif (is_array($form[$key])) {
|
||||
$element = &$this->findFormElement($form[$key]);
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides same functionality as ARRAY_FILTER_USE_KEY for PHP 5.5.
|
||||
*
|
||||
* @param array $array
|
||||
* The array of data to filter.
|
||||
* @param callable $callback
|
||||
* The function we're going to use to determine the filtering.
|
||||
*
|
||||
* @return array
|
||||
* The filtered data.
|
||||
*/
|
||||
protected function filterOnKey(array $array, callable $callback) {
|
||||
$filtered_values = [];
|
||||
foreach ($array as $key => $value) {
|
||||
if ($callback($key)) {
|
||||
$filtered_values[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $filtered_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save modified entity field values to action configuration.
|
||||
*
|
||||
* @param array $form
|
||||
* Form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form_state object.
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$storage = $form_state->getStorage();
|
||||
$bundle_data = $storage['vbe_entity_bundles_data'];
|
||||
|
||||
foreach ($bundle_data as $entity_type_id => $bundles) {
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
foreach ($bundles as $bundle => $label) {
|
||||
$field_data = $form_state->getValue([$entity_type_id, $bundle]);
|
||||
$modify = array_filter($field_data['_field_selector']);
|
||||
if (!empty($modify)) {
|
||||
$form_clone = $form;
|
||||
$form_clone['#parents'] = [$entity_type_id, $bundle];
|
||||
$entity = $this->entityTypeManager->getStorage($entity_type_id)->create([
|
||||
$entity_type->getKey('bundle') => $bundle,
|
||||
]);
|
||||
$form_display = EntityFormDisplay::collectRenderDisplay($entity, 'bulk_edit');
|
||||
$form_display->extractFormValues($entity, $form_clone, $form_state);
|
||||
|
||||
foreach (array_keys($modify) as $field) {
|
||||
$this->configuration[$entity_type_id][$bundle][$field] = $entity->{$field}->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$type_id = $entity->getEntityTypeId();
|
||||
$bundle = $entity->bundle();
|
||||
|
||||
$result = $this->t('Skip (field is not present on this bundle)');
|
||||
if (isset($this->configuration[$type_id][$bundle])) {
|
||||
foreach ($this->configuration[$type_id][$bundle] as $field => $value) {
|
||||
$entity->{$field}->setValue($value);
|
||||
}
|
||||
$entity->save();
|
||||
$result = $this->t('Modify filed values');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access = $object->access('update', $account, TRUE);
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
type: module
|
||||
name: 'Views Bulk Edit'
|
||||
description: 'Allows bulk edition of entity field values.'
|
||||
package: 'Views Bulk Operations'
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:views_bulk_operations
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-02-06
|
||||
version: '8.x-2.0-beta4'
|
||||
core: '8.x'
|
||||
project: 'views_bulk_edit'
|
||||
datestamp: 1517947086
|
||||
@@ -0,0 +1,4 @@
|
||||
views_bulk_edit.edit_form:
|
||||
css:
|
||||
theme:
|
||||
css/edit_form.css: {}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,53 @@
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Views Bulk Operations augments Views by allowing actions
|
||||
(provided by Drupal core or contrib modules) to be executed
|
||||
on the selected view 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 with a page or block display.
|
||||
2. Add a "Views bulk operations" field (global), available on
|
||||
all entity types.
|
||||
3. Configure the field by selecting at least one operation.
|
||||
4. Go to the View page. VBO functionality should be present.
|
||||
|
||||
|
||||
Creating custom actions
|
||||
-----------------------
|
||||
|
||||
Example that covers different possibilities is available in
|
||||
modules/views_bulk_operatios_example/.
|
||||
|
||||
In a module, create an action plugin (check the included example module,
|
||||
test actions in /tests/views_bulk_operations_test/src/Plugin/Action
|
||||
or \core\modules\node\src\Plugin\Action namespace for simple implementations).
|
||||
|
||||
Available annotation parameters:
|
||||
- id: The action ID (required),
|
||||
- label: Action label (required),
|
||||
- type: Entity type for the action, if left empty, action will be
|
||||
applicable to all entity types (required),
|
||||
- confirm: If set to TRUE and the next parameter is empty,
|
||||
the module default confirmation form will be used (default: FALSE),
|
||||
- confirm_form_route_name: Route name of the action confirmation form.
|
||||
If left empty and the previous parameter is empty, there will be
|
||||
no confirmation step (default: empty string).
|
||||
- requirements: an array of requirements an action must meet
|
||||
to be displayed on the action selection form. At the moment
|
||||
only one possible requirement is supported: '_permission', if
|
||||
the current user has that permission, the action execution will
|
||||
be possible.
|
||||
|
||||
|
||||
Additional notes
|
||||
----------------
|
||||
|
||||
Full documentation with examples is available at
|
||||
https://www.drupal.org/docs/8/modules/views-bulk-operations-vbo.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "drupal/views_bulk_operations",
|
||||
"description": "Adds an ability to perform bulk operations on selected entities from view results. Provides an API to create such operations.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "https://www.drupal.org/project/views_bulk_operations",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Marcin Grabias",
|
||||
"homepage": "https://www.drupal.org/u/graber"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/views_bulk_operations?version=8.x",
|
||||
"docs": "https://www.drupal.org/docs/8/modules/views-bulk-operations-vbo"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"minimum-stability": "dev",
|
||||
"require": {},
|
||||
"extra": {
|
||||
"drush": {
|
||||
"services": {
|
||||
"drush.services.yml": "^9"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
views.field.views_bulk_operations_bulk_form:
|
||||
type: views_field
|
||||
label: 'Views Bulk Operations'
|
||||
mapping:
|
||||
batch:
|
||||
type: boolean
|
||||
label: 'Process selected entities in a batch operation'
|
||||
batch_size:
|
||||
type: integer
|
||||
label: 'Size of the processing batch'
|
||||
form_step:
|
||||
type: boolean
|
||||
label: 'Display configuration form on a separate page'
|
||||
buttons:
|
||||
type: boolean
|
||||
label: 'Display action options as buttons'
|
||||
action_title:
|
||||
type: string
|
||||
label: 'Title of the action selector form element'
|
||||
selected_actions:
|
||||
type: ignore
|
||||
label: 'Selected actions array'
|
||||
preconfiguration:
|
||||
type: ignore
|
||||
label: 'Preliminary configuration array'
|
||||
@@ -0,0 +1,6 @@
|
||||
.views-table-row-vbo-select-all div {
|
||||
text-align: center;
|
||||
}
|
||||
.views-field-views-bulk-operations-bulk-form.empty {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
views_bulk_operations.commands:
|
||||
class: \Drupal\views_bulk_operations\Commands\ViewsBulkOperationsCommands
|
||||
arguments:
|
||||
- '@current_user'
|
||||
- '@views_bulk_operations.data'
|
||||
- '@plugin.manager.views_bulk_operations_action'
|
||||
tags:
|
||||
- { name: drush.command }
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @file
|
||||
* Views admin UI functionality.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @type {Drupal~behavior}
|
||||
*/
|
||||
Drupal.behaviors.views_bulk_operations = {
|
||||
attach: function (context, settings) {
|
||||
$('.views-bulk-operations-ui').once('views-bulk-operations-ui').each(Drupal.viewsBulkOperationsUi);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback used in {@link Drupal.behaviors.views_bulk_operations}.
|
||||
*/
|
||||
Drupal.viewsBulkOperationsUi = function () {
|
||||
var uiElement = $(this);
|
||||
|
||||
// Show / hide actions' preliminary configuration.
|
||||
uiElement.find('.vbo-action-state').each(function () {
|
||||
var matches = $(this).attr('name').match(/.*\[.*?\]\[(.*?)\]\[.*?\]/);
|
||||
if (typeof (matches[1]) != 'undefined') {
|
||||
var preconfigurationElement = uiElement.find('*[data-for="' + matches[1] + '"]');
|
||||
$(this).change(function (event) {
|
||||
if ($(this).is(':checked')) {
|
||||
preconfigurationElement.show('fast');
|
||||
}
|
||||
else {
|
||||
preconfigurationElement.hide('fast');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Select / deselect all functionality.
|
||||
var actionsElementWrapper = uiElement.find('details.vbo-actions-widget > .details-wrapper');
|
||||
if (actionsElementWrapper.length) {
|
||||
var checked = false;
|
||||
var allHandle = $('<a href="#" class="vbo-all-switch">' + Drupal.t('Select / deselect all') + '</a>');
|
||||
actionsElementWrapper.prepend(allHandle);
|
||||
allHandle.on('click', function (event) {
|
||||
event.preventDefault();
|
||||
checked = !checked;
|
||||
actionsElementWrapper.find('.vbo-action-state').each(function () {
|
||||
$(this).prop('checked', checked);
|
||||
$(this).trigger('change');
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* @file
|
||||
* Select-All Button functionality.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @type {Drupal~behavior}
|
||||
*/
|
||||
Drupal.behaviors.views_bulk_operations = {
|
||||
attach: function (context, settings) {
|
||||
$('.vbo-view-form').once('vbo-init').each(Drupal.viewsBulkOperationsFrontUi);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Views Bulk Operation selection object.
|
||||
*/
|
||||
Drupal.viewsBulkOperationsSelection = {
|
||||
view_id: '',
|
||||
display_id: '',
|
||||
list: {},
|
||||
$placeholder: null,
|
||||
|
||||
/**
|
||||
* Bind event handlers to an element.
|
||||
*
|
||||
* @param {jQuery} element
|
||||
*/
|
||||
bindEventHandlers: function ($element, index) {
|
||||
if ($element.length) {
|
||||
var selectionObject = this;
|
||||
$element.on('keypress', function (event) {
|
||||
// Emulate click action for enter key.
|
||||
if (event.which === 13) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
selectionObject.update(this.checked, index, $(this).val());
|
||||
$(this).trigger('click');
|
||||
}
|
||||
if (event.which === 32) {
|
||||
selectionObject.update(this.checked, index, $(this).val());
|
||||
}
|
||||
});
|
||||
$element.on('mousedown', function (event) {
|
||||
// Act only on left button click.
|
||||
if (event.which === 1) {
|
||||
selectionObject.update(this.checked, index, $(this).val());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an AJAX request to update selection.
|
||||
*
|
||||
* @param {bool} state
|
||||
* @param {string} value
|
||||
*/
|
||||
update: function (state, index, value = null) {
|
||||
if (this.view_id.length && this.display_id.length) {
|
||||
var list = {};
|
||||
if (value && value != 'on') {
|
||||
list[value] = this.list[index][value];
|
||||
}
|
||||
else {
|
||||
list = this.list[index];
|
||||
}
|
||||
var op = state ? 'remove' : 'add';
|
||||
|
||||
var $placeholder = this.$placeholder;
|
||||
var target_uri = '/' + drupalSettings.path.pathPrefix + 'views-bulk-operations/ajax/' + this.view_id + '/' + this.display_id;
|
||||
$.ajax(target_uri, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
list: list,
|
||||
op: op
|
||||
},
|
||||
success: function (data) {
|
||||
var count = parseInt($placeholder.text());
|
||||
count += data.change;
|
||||
$placeholder.text(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used in {@link Drupal.behaviors.views_bulk_operations}.
|
||||
*/
|
||||
Drupal.viewsBulkOperationsFrontUi = function () {
|
||||
var $vboForm = $(this);
|
||||
var $viewsTables = $('.vbo-table', $vboForm);
|
||||
var $primarySelectAll = $('.vbo-select-all', $vboForm);
|
||||
var tableSelectAll = [];
|
||||
|
||||
// When grouping is enabled, there can be multiple tables.
|
||||
if ($viewsTables.length) {
|
||||
$viewsTables.each(function (index) {
|
||||
tableSelectAll[index] = $(this).find('.select-all input').first();
|
||||
});
|
||||
var $tableSelectAll = $(tableSelectAll);
|
||||
}
|
||||
|
||||
// Add AJAX functionality to table checkboxes.
|
||||
var $multiSelectElement = $vboForm.find('.vbo-multipage-selector').first();
|
||||
if ($multiSelectElement.length) {
|
||||
|
||||
Drupal.viewsBulkOperationsSelection.$placeholder = $multiSelectElement.find('.placeholder').first();
|
||||
Drupal.viewsBulkOperationsSelection.view_id = $multiSelectElement.attr('data-view-id');
|
||||
Drupal.viewsBulkOperationsSelection.display_id = $multiSelectElement.attr('data-display-id');
|
||||
|
||||
// Get the list of all checkbox values and add AJAX callback.
|
||||
Drupal.viewsBulkOperationsSelection.list = [];
|
||||
|
||||
var $contentWrappers;
|
||||
if ($viewsTables.length) {
|
||||
$contentWrappers = $viewsTables;
|
||||
}
|
||||
else {
|
||||
$contentWrappers = $([$vboForm]);
|
||||
}
|
||||
|
||||
$contentWrappers.each(function (index) {
|
||||
var $contentWrapper = $(this);
|
||||
Drupal.viewsBulkOperationsSelection.list[index] = {};
|
||||
|
||||
$contentWrapper.find('.views-field-views-bulk-operations-bulk-form input[type="checkbox"]').each(function () {
|
||||
var value = $(this).val();
|
||||
if (value != 'on') {
|
||||
Drupal.viewsBulkOperationsSelection.list[index][value] = $(this).parent().find('label').first().text();
|
||||
Drupal.viewsBulkOperationsSelection.bindEventHandlers($(this), index);
|
||||
}
|
||||
});
|
||||
|
||||
// Bind event handlers to select all checkbox.
|
||||
if ($viewsTables.length && tableSelectAll.length) {
|
||||
Drupal.viewsBulkOperationsSelection.bindEventHandlers(tableSelectAll[index], index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize all selector if the primary select all and
|
||||
// view table elements exist.
|
||||
if ($primarySelectAll.length && $viewsTables.length) {
|
||||
var strings = {
|
||||
selectAll: $('label', $primarySelectAll.parent()).html(),
|
||||
selectRegular: Drupal.t('Select only items on this page')
|
||||
};
|
||||
|
||||
$primarySelectAll.parent().hide();
|
||||
|
||||
if ($viewsTables.length == 1) {
|
||||
var colspan = $('thead th', $viewsTables.first()).length;
|
||||
var $allSelector = $('<tr class="views-table-row-vbo-select-all even" style="display: none"><td colspan="' + colspan + '"><div><input type="submit" class="form-submit" value="' + strings.selectAll + '"></div></td></tr>');
|
||||
$('tbody', $viewsTables.first()).prepend($allSelector);
|
||||
}
|
||||
else {
|
||||
var $allSelector = $('<div class="views-table-row-vbo-select-all" style="display: none"><div><input type="submit" class="form-submit" value="' + strings.selectAll + '"></div></div>');
|
||||
$($viewsTables.first()).before($allSelector);
|
||||
}
|
||||
|
||||
if ($primarySelectAll.is(':checked')) {
|
||||
$('input', $allSelector).val(strings.selectRegular);
|
||||
$allSelector.show();
|
||||
}
|
||||
else {
|
||||
var show_all_selector = true;
|
||||
$tableSelectAll.each(function () {
|
||||
if (!$(this).is(':checked')) {
|
||||
show_all_selector = false;
|
||||
}
|
||||
});
|
||||
if (show_all_selector) {
|
||||
$allSelector.show();
|
||||
}
|
||||
}
|
||||
|
||||
$('input', $allSelector).on('click', function (event) {
|
||||
event.preventDefault();
|
||||
if ($primarySelectAll.is(':checked')) {
|
||||
$multiSelectElement.show('fast');
|
||||
$primarySelectAll.prop('checked', false);
|
||||
$allSelector.removeClass('all-selected');
|
||||
$(this).val(strings.selectAll);
|
||||
}
|
||||
else {
|
||||
$multiSelectElement.hide('fast');
|
||||
$primarySelectAll.prop('checked', true);
|
||||
$allSelector.addClass('all-selected');
|
||||
$(this).val(strings.selectRegular);
|
||||
}
|
||||
});
|
||||
|
||||
$(tableSelectAll).each(function () {
|
||||
$(this).on('change', function (event) {
|
||||
var show_all_selector = true;
|
||||
$tableSelectAll.each(function () {
|
||||
if (!$(this).is(':checked')) {
|
||||
show_all_selector = false;
|
||||
}
|
||||
});
|
||||
if (show_all_selector) {
|
||||
$allSelector.show();
|
||||
}
|
||||
else {
|
||||
$allSelector.hide();
|
||||
if ($primarySelectAll.is(':checked')) {
|
||||
$('input', $allSelector).trigger('click');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
$primarySelectAll.first().on('change', function (event) {
|
||||
if (this.checked) {
|
||||
$multiSelectElement.hide('fast');
|
||||
}
|
||||
else {
|
||||
$multiSelectElement.show('fast');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery, Drupal);
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
type: module
|
||||
name: 'Actions Permissions'
|
||||
description: 'Adds access permissions on all actions allowing admins to restrict access on a per-role basis.'
|
||||
package: 'Views Bulk Operations'
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:views_bulk_operations
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-02-08
|
||||
version: '8.x-2.0-rc4+1-dev'
|
||||
core: '8.x'
|
||||
project: 'views_bulk_operations'
|
||||
datestamp: 1518076986
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
permission_callbacks:
|
||||
- \Drupal\actions_permissions\ActionsPermissions::permissions
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
actions_permissions.views_bulk_operations_actions:
|
||||
class: Drupal\actions_permissions\EventSubscriber\ActionsPermissionsEventSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\actions_permissions;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Create permissions for existing actions.
|
||||
*/
|
||||
class ActionsPermissions implements ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* VBO Action manager service.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
|
||||
* The action manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* Entity type manager.
|
||||
*/
|
||||
public function __construct(ViewsBulkOperationsActionManager $actionManager, EntityTypeManagerInterface $entityTypeManager) {
|
||||
$this->actionManager = $actionManager;
|
||||
$this->entityTypeManager = $entityTypeManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.views_bulk_operations_action'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permissions for Actions.
|
||||
*
|
||||
* @return array
|
||||
* Permissions array.
|
||||
*/
|
||||
public function permissions() {
|
||||
$permissions = [];
|
||||
$entity_type_definitions = $this->entityTypeManager->getDefinitions();
|
||||
|
||||
// Get definitions that will not be altered by actions_permissions.
|
||||
foreach ($this->actionManager->getDefinitions([
|
||||
'skip_actions_permissions' => TRUE,
|
||||
'nocache' => TRUE,
|
||||
]) as $definition) {
|
||||
|
||||
// Skip actions that define their own requirements.
|
||||
if (!empty($definition['requirements'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = 'execute ' . $definition['id'];
|
||||
$entity_type = NULL;
|
||||
if (empty($definition['type'])) {
|
||||
$entity_type = $this->t('all entity types');
|
||||
$id .= ' all';
|
||||
}
|
||||
elseif (isset($entity_type_definitions[$definition['type']])) {
|
||||
$entity_type = $entity_type_definitions[$definition['type']]->getLabel();
|
||||
$id .= ' ' . $definition['type'];
|
||||
}
|
||||
|
||||
if (isset($entity_type)) {
|
||||
$permissions[$id] = [
|
||||
'title' => $this->t('Execute the %action action on %type.', [
|
||||
'%action' => $definition['label'],
|
||||
'%type' => $entity_type,
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild VBO action definitions cache with
|
||||
// included action_permissions modifications.
|
||||
$this->actionManager->getDefinitions([
|
||||
'nocache' => TRUE,
|
||||
]);
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\actions_permissions\EventSubscriber;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
|
||||
|
||||
/**
|
||||
* Defines module event subscriber class.
|
||||
*
|
||||
* Alters actions to make use of permissions created by the module.
|
||||
*/
|
||||
class ActionsPermissionsEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
// Subscribe to the VBO event with low priority
|
||||
// to let other modules alter requirements first.
|
||||
const PRIORITY = -999;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[ViewsBulkOperationsActionManager::ALTER_ACTIONS_EVENT][] = ['alterActions', static::PRIORITY];
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the actions' definitions.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\Event $event
|
||||
* The event to respond to.
|
||||
*/
|
||||
public function alterActions(Event $event) {
|
||||
|
||||
// Don't alter definitions if this is invoked by the
|
||||
// own permissions creating method.
|
||||
if (!empty($event->alterParameters['skip_actions_permissions'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($event->definitions as $action_id => $definition) {
|
||||
|
||||
// Only process actions that don't define their own requirements.
|
||||
if (empty($definition['requirements'])) {
|
||||
$permission_id = 'execute ' . $definition['id'];
|
||||
if (empty($definition['type'])) {
|
||||
$permission_id .= ' all';
|
||||
}
|
||||
else {
|
||||
$permission_id .= ' ' . $definition['type'];
|
||||
}
|
||||
$definition['requirements']['_permission'] = $permission_id;
|
||||
$event->definitions[$action_id] = $definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations_example\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsPreconfigurationInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* An example action covering most of the possible options.
|
||||
*
|
||||
* If type is left empty, action will be selectable for all
|
||||
* entity types.
|
||||
*
|
||||
* @Action(
|
||||
* id = "views_bulk_operations_example",
|
||||
* label = @Translation("VBO example action"),
|
||||
* type = "",
|
||||
* confirm = TRUE,
|
||||
* )
|
||||
*/
|
||||
class ViewsBulkOperationExampleAction extends ViewsBulkOperationsActionBase implements ViewsBulkOperationsPreconfigurationInterface, PluginFormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
/*
|
||||
* All config resides in $this->configuration.
|
||||
* Passed view rows will be available in $this->context.
|
||||
* Data about the view used to select results and optionally
|
||||
* the batch context are available in $this->context or externally
|
||||
* through the public getContext() method.
|
||||
* The entire ViewExecutable object with selected result
|
||||
* rows is available in $this->view or externally through
|
||||
* the public getView() method.
|
||||
*/
|
||||
|
||||
// Do some processing..
|
||||
// ...
|
||||
drupal_set_message($entity->label());
|
||||
return sprintf('Example action (configuration: %s)', print_r($this->configuration, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPreConfigurationForm(array $form, array $values, FormStateInterface $form_state) {
|
||||
$form['example_preconfig_setting'] = [
|
||||
'#title' => $this->t('Example setting'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($values['example_preconfig_setting']) ? $values['example_preconfig_setting'] : '',
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration form builder.
|
||||
*
|
||||
* If this method has implementation, the action is
|
||||
* considered to be configurable.
|
||||
*
|
||||
* @param array $form
|
||||
* Form array.
|
||||
* @param Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*
|
||||
* @return array
|
||||
* The configuration form.
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['example_config_setting'] = [
|
||||
'#title' => t('Example setting pre-execute'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $form_state->getValue('example_config_setting'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the action configuration form.
|
||||
*
|
||||
* If not implemented, the cleaned form values will be
|
||||
* passed direclty to the action $configuration parameter.
|
||||
*
|
||||
* @param array $form
|
||||
* Form array.
|
||||
* @param Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
// This is not required here, when this method is not defined,
|
||||
// form values are assigned to the action configuration by default.
|
||||
// This function is a must only when user input processing is needed.
|
||||
$this->configuration['example_config_setting'] = $form_state->getValue('example_config_setting');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
if ($object->getEntityType() === 'node') {
|
||||
$access = $object->access('update', $account, TRUE)
|
||||
->andIf($object->status->access('edit', $account, TRUE));
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
// Other entity types may have different
|
||||
// access methods and properties.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
type: module
|
||||
name: 'Views Bulk Operations example'
|
||||
description: 'Defines an example action with all possible options.'
|
||||
package: 'Examples'
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:views_bulk_operations
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-02-08
|
||||
version: '8.x-2.0-rc4+1-dev'
|
||||
core: '8.x'
|
||||
project: 'views_bulk_operations'
|
||||
datestamp: 1518076986
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Defines module access rules.
|
||||
*/
|
||||
class ViewsBulkOperationsAccess implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Temporary user storage object.
|
||||
*
|
||||
* @var \Drupal\user\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*/
|
||||
public function __construct(PrivateTempStoreFactory $tempStoreFactory) {
|
||||
$this->tempStoreFactory = $tempStoreFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom access check.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* Run access checks for this account.
|
||||
* @param \Drupal\Core\Routing\RouteMatch $routeMatch
|
||||
* The matched route.
|
||||
*/
|
||||
public function access(AccountInterface $account, RouteMatch $routeMatch) {
|
||||
$parameters = $routeMatch->getParameters()->all();
|
||||
|
||||
if ($view = Views::getView($parameters['view_id'])) {
|
||||
if ($view->access($parameters['display_id'], $account)) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Action;
|
||||
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action plugin base.
|
||||
*
|
||||
* Provides a base implementation for a configurable
|
||||
* and preconfigurable VBO Action plugin.
|
||||
*/
|
||||
abstract class ViewsBulkOperationsActionBase extends ActionBase implements ViewsBulkOperationsActionInterface, ConfigurablePluginInterface {
|
||||
|
||||
/**
|
||||
* Action context.
|
||||
*
|
||||
* @var array
|
||||
* Contains view data and optionally batch operation context.
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* The processed view.
|
||||
*
|
||||
* @var \Drupal\views\ViewExecutable
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* Configuration array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setContext(array &$context) {
|
||||
$this->context['sandbox'] = &$context['sandbox'];
|
||||
foreach ($context as $key => $item) {
|
||||
if ($key === 'sandbox') {
|
||||
continue;
|
||||
}
|
||||
$this->context[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setView(ViewExecutable $view) {
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $objects) {
|
||||
$results = [];
|
||||
foreach ($objects as $entity) {
|
||||
$results[] = $this->execute($entity);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration form validator.
|
||||
*
|
||||
* This method will be needed if a child class will implement
|
||||
* \Drupal\Core\Plugin\PluginFormInterface. Code saver.
|
||||
*
|
||||
* @param array &$form
|
||||
* Form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration form submit handler.
|
||||
*
|
||||
* This method will be needed if a child class will implement
|
||||
* \Drupal\Core\Plugin\PluginFormInterface. Code saver.
|
||||
*
|
||||
* @param array &$form
|
||||
* Form array.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->cleanValues();
|
||||
foreach ($form_state->getValues() as $key => $value) {
|
||||
$this->configuration[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Action;
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Defines Views Bulk Operations action interface.
|
||||
*/
|
||||
interface ViewsBulkOperationsActionInterface {
|
||||
|
||||
/**
|
||||
* Set action context.
|
||||
*
|
||||
* Implementation should have an option to add data to the
|
||||
* context, not overwrite it on every method execution.
|
||||
*
|
||||
* @param array $context
|
||||
* The context array.
|
||||
*
|
||||
* @see ViewsBulkOperationsActionBase::setContext
|
||||
*/
|
||||
public function setContext(array &$context);
|
||||
|
||||
/**
|
||||
* Set view object.
|
||||
*
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* The processed view.
|
||||
*/
|
||||
public function setView(ViewExecutable $view);
|
||||
|
||||
/**
|
||||
* Execute action on multiple entities.
|
||||
*
|
||||
* Can return an array of results of processing, if no return value
|
||||
* is provided, action label will be used for each result.
|
||||
*
|
||||
* @param array $objects
|
||||
* An array of entities.
|
||||
*
|
||||
* @return array
|
||||
* An array of translatable markup objects or strings (optional)
|
||||
*/
|
||||
public function executeMultiple(array $objects);
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Action;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Defines methods for a preconfigurable Views Bulk Operations action.
|
||||
*/
|
||||
interface ViewsBulkOperationsPreconfigurationInterface {
|
||||
|
||||
/**
|
||||
* Build preconfigure action form elements.
|
||||
*
|
||||
* @param array $element
|
||||
* Element of the views API form where configuration resides.
|
||||
* @param array $values
|
||||
* Current values of the plugin pre-configuration.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* Form state interface object.
|
||||
*
|
||||
* @return array
|
||||
* The action configuration form element.
|
||||
*/
|
||||
public function buildPreConfigurationForm(array $element, array $values, FormStateInterface $form_state);
|
||||
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Commands;
|
||||
|
||||
use Drush\Commands\DrushCommands;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;
|
||||
|
||||
/**
|
||||
* Defines Drush commands for the module.
|
||||
*/
|
||||
class ViewsBulkOperationsCommands extends DrushCommands {
|
||||
|
||||
/**
|
||||
* The current user object.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Object that gets the current view data.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\ViewsbulkOperationsViewDataInterface
|
||||
*/
|
||||
protected $viewData;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action manager.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* ViewsBulkOperationsCommands object constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $currentUser
|
||||
* The current user object.
|
||||
* @param \Drupal\views_bulk_operations\ViewsbulkOperationsViewDataInterface $viewData
|
||||
* VBO View data service.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
|
||||
* VBO Action manager service.
|
||||
*/
|
||||
public function __construct(
|
||||
AccountInterface $currentUser,
|
||||
ViewsbulkOperationsViewDataInterface $viewData,
|
||||
ViewsBulkOperationsActionManager $actionManager
|
||||
) {
|
||||
$this->currentUser = $currentUser;
|
||||
$this->viewData = $viewData;
|
||||
$this->actionManager = $actionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an action on all results of the specified view.
|
||||
*
|
||||
* Use the --verbose parameter to see progress messages.
|
||||
*
|
||||
* @param string $view_id
|
||||
* The ID of the view to use.
|
||||
* @param string $action_id
|
||||
* The ID of the action to execute.
|
||||
* @param array $options
|
||||
* (optional) An array of options.
|
||||
*
|
||||
* @return string
|
||||
* The summary message.
|
||||
*
|
||||
* @command views-bulk-operations:execute
|
||||
*
|
||||
* @option display-id
|
||||
* ID of the display to use.
|
||||
* @option args
|
||||
* View arguments (slash is a delimeter).
|
||||
* @option exposed
|
||||
* Exposed filters (query string format).
|
||||
* @option batch-size
|
||||
* Processing batch size.
|
||||
* @option configuration
|
||||
* Action configuration (query string format).
|
||||
* @option user-id
|
||||
* The ID of the user account used for performing the operation.
|
||||
*
|
||||
* @usage drush views-bulk-operations:execute some_view some_action
|
||||
* Execute some action on some view.
|
||||
* @usage drush vbo-execute some_view some_action --args=arg1/arg2 --batch-size=50
|
||||
* Execute some action on some view with arg1 and arg2 as
|
||||
* the view arguments and 50 entities processed per batch.
|
||||
* @usage drush vbo-exec some_view some_action --configuration="key1=value1&key2=value2"
|
||||
* Execute some action on some view with the specified action configuration.
|
||||
*
|
||||
* @aliases vbo-execute, vbo-exec
|
||||
*/
|
||||
public function vboExecute(
|
||||
$view_id,
|
||||
$action_id,
|
||||
array $options = [
|
||||
'display-id' => 'default',
|
||||
'args' => '',
|
||||
'exposed' => '',
|
||||
'batch-size' => 100,
|
||||
'configuration' => '',
|
||||
'user-id' => 1,
|
||||
]
|
||||
) {
|
||||
|
||||
if (empty($view_id) || empty($action_id)) {
|
||||
throw new \Exception($this->t('You must specify the view ID and the action ID parameters.'));
|
||||
}
|
||||
|
||||
$this->timer($options['verbose']);
|
||||
|
||||
// Prepare options.
|
||||
if ($options['args']) {
|
||||
$options['args'] = explode('/', $options['args']);
|
||||
}
|
||||
else {
|
||||
$options['args'] = [];
|
||||
}
|
||||
|
||||
// Decode query string format options.
|
||||
foreach (['configuration', 'exposed'] as $name) {
|
||||
if (!empty($options[$name]) && !is_array($options[$name])) {
|
||||
parse_str($options[$name], $options[$name]);
|
||||
}
|
||||
else {
|
||||
$options[$name] = [];
|
||||
}
|
||||
}
|
||||
|
||||
$vbo_data = [
|
||||
'list' => [],
|
||||
'view_id' => $view_id,
|
||||
'display_id' => $options['display-id'],
|
||||
'action_id' => $action_id,
|
||||
'preconfiguration' => $options['configuration'],
|
||||
'batch' => TRUE,
|
||||
'arguments' => $options['args'],
|
||||
'exposed_input' => $options['exposed'],
|
||||
'batch_size' => $options['batch-size'],
|
||||
'relationship_id' => 'none',
|
||||
];
|
||||
|
||||
// Login as superuser, as drush 9 doesn't support the
|
||||
// --user parameter.
|
||||
$account = User::load($options['user-id']);
|
||||
$this->currentUser->setAccount($account);
|
||||
|
||||
// Initialize the view to check if parameters are correct.
|
||||
if (!$view = Views::getView($vbo_data['view_id'])) {
|
||||
throw new \Exception($this->t('Incorrect view ID provided.'));
|
||||
}
|
||||
if (!$view->setDisplay($vbo_data['display_id'])) {
|
||||
throw new \Exception($this->t('Incorrect view display ID provided.'));
|
||||
}
|
||||
if (!empty($vbo_data['arguments'])) {
|
||||
$view->setArguments($vbo_data['arguments']);
|
||||
}
|
||||
if (!empty($vbo_data['exposed_input'])) {
|
||||
$view->setExposedInput($vbo_data['exposed_input']);
|
||||
}
|
||||
|
||||
// We need total rows count for proper progress message display.
|
||||
$view->get_total_rows = TRUE;
|
||||
$view->execute();
|
||||
|
||||
// Get relationship ID if VBO field exists.
|
||||
$vbo_data['relationship_id'] = 'none';
|
||||
foreach ($view->field as $field) {
|
||||
if ($field->options['id'] === 'views_bulk_operations_bulk_form') {
|
||||
$vbo_data['relationship_id'] = $field->options['relationship'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get total rows count.
|
||||
$this->viewData->init($view, $view->getDisplay(), $vbo_data['relationship_id']);
|
||||
$vbo_data['total_results'] = $this->viewData->getTotalResults();
|
||||
|
||||
// Get action definition and check if action ID is correct.
|
||||
$action_definition = $this->actionManager->getDefinition($action_id);
|
||||
$vbo_data['action_label'] = (string) $action_definition['label'];
|
||||
|
||||
$this->timer($options['verbose'], 'init');
|
||||
|
||||
// Populate entity list.
|
||||
$context = [];
|
||||
do {
|
||||
$context['finished'] = 1;
|
||||
$context['message'] = '';
|
||||
ViewsBulkOperationsBatch::getList($vbo_data, $context);
|
||||
if (!empty($context['message'])) {
|
||||
$this->logger->info($context['message']);
|
||||
}
|
||||
} while ($context['finished'] < 1);
|
||||
$vbo_data = $context['results'];
|
||||
|
||||
$this->timer($options['verbose'], 'list');
|
||||
|
||||
// Execute the selected action.
|
||||
$context = [];
|
||||
do {
|
||||
$context['finished'] = 1;
|
||||
$context['message'] = '';
|
||||
ViewsBulkOperationsBatch::operation($vbo_data, $context);
|
||||
if (!empty($context['message'])) {
|
||||
$this->logger->info($context['message']);
|
||||
}
|
||||
} while ($context['finished'] < 1);
|
||||
|
||||
// Output a summary message.
|
||||
$operations = array_count_values($context['results']['operations']);
|
||||
$details = [];
|
||||
foreach ($operations as $op => $count) {
|
||||
$details[] = $op . ' (' . $count . ')';
|
||||
}
|
||||
|
||||
// Display debug information.
|
||||
if ($options['verbose']) {
|
||||
$this->timer($options['verbose'], 'execute');
|
||||
$this->logger->info($this->t('Initialization time: @time ms.', ['@time' => $this->timer($options['verbose'], 'init')]));
|
||||
$this->logger->info($this->t('Entity list generation time: @time ms.', ['@time' => $this->timer($options['verbose'], 'list')]));
|
||||
$this->logger->info($this->t('Execution time: @time ms.', ['@time' => $this->timer($options['verbose'], 'execute')]));
|
||||
}
|
||||
|
||||
return $this->t('Action processing results: @results.', ['@results' => implode(', ', $details)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set / get timer.
|
||||
*
|
||||
* @param bool $debug
|
||||
* Should the function do anything at all?
|
||||
* @param string $id
|
||||
* ID of a specific timer span.
|
||||
*
|
||||
* @return mixed
|
||||
* NULL or value of a specific timer if set.
|
||||
*/
|
||||
protected function timer($debug = TRUE, $id = NULL) {
|
||||
if (!$debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $timers = [];
|
||||
|
||||
if (!isset($id)) {
|
||||
$timers['start'] = microtime(TRUE);
|
||||
}
|
||||
else {
|
||||
if (isset($timers[$id])) {
|
||||
end($timers);
|
||||
do {
|
||||
if (key($timers) === $id) {
|
||||
return round((current($timers) - prev($timers)) * 1000, 3);
|
||||
}
|
||||
else {
|
||||
$result = prev($timers);
|
||||
}
|
||||
} while ($result);
|
||||
}
|
||||
else {
|
||||
$timers[$id] = microtime(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string using the dt function.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to translate.
|
||||
* @param array $arguments
|
||||
* (optional) The translation arguments.
|
||||
*
|
||||
* @return string
|
||||
* The translated message.
|
||||
*/
|
||||
protected function t($message, array $arguments = []) {
|
||||
return dt($message, $arguments);
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\views_bulk_operations\Form\ViewsBulkOperationsFormTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
|
||||
/**
|
||||
* Defines VBO controller class.
|
||||
*/
|
||||
class ViewsBulkOperationsController extends ControllerBase implements ContainerInjectionInterface {
|
||||
|
||||
use ViewsBulkOperationsFormTrait;
|
||||
|
||||
/**
|
||||
* User private temporary storage factory.
|
||||
*
|
||||
* @var \Drupal\user\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action processor.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface
|
||||
*/
|
||||
protected $actionProcessor;
|
||||
|
||||
/**
|
||||
* Constructs a new controller object.
|
||||
*
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
|
||||
* User private temporary storage factory.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
* Views Bulk Operations action processor.
|
||||
*/
|
||||
public function __construct(
|
||||
PrivateTempStoreFactory $tempStoreFactory,
|
||||
ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
) {
|
||||
$this->tempStoreFactory = $tempStoreFactory;
|
||||
$this->actionProcessor = $actionProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('user.private_tempstore'),
|
||||
$container->get('views_bulk_operations.processor')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual page callback.
|
||||
*
|
||||
* @param string $view_id
|
||||
* The current view ID.
|
||||
* @param string $display_id
|
||||
* The display ID of the current view.
|
||||
*/
|
||||
public function execute($view_id, $display_id) {
|
||||
$view_data = $this->getTempstoreData($view_id, $display_id);
|
||||
if (empty($view_data)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$this->deleteTempstoreData();
|
||||
|
||||
$this->actionProcessor->executeProcessing($view_data);
|
||||
return batch_process($view_data['redirect_url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback to update selection (multipage).
|
||||
*
|
||||
* @param string $view_id
|
||||
* The current view ID.
|
||||
* @param string $display_id
|
||||
* The display ID of the current view.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*/
|
||||
public function updateSelection($view_id, $display_id, Request $request) {
|
||||
$view_data = $this->getTempstoreData($view_id, $display_id);
|
||||
if (empty($view_data)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$list = $request->request->get('list');
|
||||
|
||||
$op = $request->request->get('op', 'add');
|
||||
$change = 0;
|
||||
|
||||
if ($op === 'add') {
|
||||
foreach ($list as $bulkFormKey => $label) {
|
||||
if (!isset($view_data['list'][$bulkFormKey])) {
|
||||
$view_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey, $label);
|
||||
$change++;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($op === 'remove') {
|
||||
foreach ($list as $bulkFormKey => $label) {
|
||||
if (isset($view_data['list'][$bulkFormKey])) {
|
||||
unset($view_data['list'][$bulkFormKey]);
|
||||
$change--;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->setTempstoreData($view_data);
|
||||
|
||||
$response = new AjaxResponse();
|
||||
$response->setData(['change' => $change]);
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\EventSubscriber;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsViewDataInterface;
|
||||
use Drupal\views_bulk_operations\ViewsBulkOperationsEvent;
|
||||
|
||||
/**
|
||||
* Defines module event subscriber class.
|
||||
*
|
||||
* Allows getting data of core entity views.
|
||||
*/
|
||||
class ViewsBulkOperationsEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
// Subscribe to the VBO event with high priority
|
||||
// to prepopulate the event data.
|
||||
const PRIORITY = 999;
|
||||
|
||||
/**
|
||||
* Object that gets the current view data.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\ViewsBulkOperationsViewDataInterface
|
||||
*/
|
||||
protected $viewData;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsViewDataInterface $viewData
|
||||
* The VBO View Data provider service.
|
||||
*/
|
||||
public function __construct(ViewsBulkOperationsViewDataInterface $viewData) {
|
||||
$this->viewData = $viewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[ViewsBulkOperationsEvent::NAME][] = ['provideViewData', self::PRIORITY];
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to view data request event.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\ViewsBulkOperationsEvent $event
|
||||
* The event to respond to.
|
||||
*/
|
||||
public function provideViewData(ViewsBulkOperationsEvent $event) {
|
||||
$view_data = $event->getViewData();
|
||||
if (!empty($view_data['table']['entity type'])) {
|
||||
$event->setEntityTypeIds([$view_data['table']['entity type']]);
|
||||
$event->setEntityGetter([
|
||||
'callable' => [$this->viewData, 'getEntityDefault'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Form;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
|
||||
|
||||
/**
|
||||
* Action configuration form.
|
||||
*/
|
||||
class ConfigureAction extends FormBase {
|
||||
|
||||
use ViewsBulkOperationsFormTrait;
|
||||
|
||||
/**
|
||||
* User private temporary storage factory.
|
||||
*
|
||||
* @var \Drupal\user\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action manager.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action processor.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface
|
||||
*/
|
||||
protected $actionProcessor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
|
||||
* User private temporary storage factory.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
|
||||
* Extended action manager object.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
* Views Bulk Operations action processor.
|
||||
*/
|
||||
public function __construct(
|
||||
PrivateTempStoreFactory $tempStoreFactory,
|
||||
ViewsBulkOperationsActionManager $actionManager,
|
||||
ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
) {
|
||||
$this->tempStoreFactory = $tempStoreFactory;
|
||||
$this->actionManager = $actionManager;
|
||||
$this->actionProcessor = $actionProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('user.private_tempstore'),
|
||||
$container->get('plugin.manager.views_bulk_operations_action'),
|
||||
$container->get('views_bulk_operations.processor')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'views_bulk_operations_configure_action';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $view_id = NULL, $display_id = NULL) {
|
||||
|
||||
$form_data = $this->getFormData($view_id, $display_id);
|
||||
|
||||
// TODO: display an error msg, redirect back.
|
||||
if (!isset($form_data['action_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form['#title'] = $this->t('Configure "%action" action applied to the selection', ['%action' => $form_data['action_label']]);
|
||||
|
||||
$selection = [];
|
||||
if (!empty($form_data['entity_labels'])) {
|
||||
$form['list'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $form_data['entity_labels'],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$form['list'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('All view results'),
|
||||
];
|
||||
}
|
||||
$form['list']['#title'] = $this->t('Selected @count entities:', ['@count' => $form_data['selected_count']]);
|
||||
|
||||
// :D Make sure the submit button is at the bottom of the form
|
||||
// and is editale from the action buildConfigurationForm method.
|
||||
$form['actions']['#weight'] = 666;
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Apply'),
|
||||
'#submit' => [
|
||||
[$this, 'submitForm'],
|
||||
],
|
||||
];
|
||||
|
||||
$action = $this->actionManager->createInstance($form_data['action_id']);
|
||||
|
||||
if (method_exists($action, 'setContext')) {
|
||||
$action->setContext($form_data);
|
||||
}
|
||||
|
||||
$form = $action->buildConfigurationForm($form, $form_state);
|
||||
|
||||
$form_state->set('views_bulk_operations', $form_data);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_data = $form_state->get('views_bulk_operations');
|
||||
|
||||
$action = $this->actionManager->createInstance($form_data['action_id']);
|
||||
if (method_exists($action, 'validateConfigurationForm')) {
|
||||
$action->validateConfigurationForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_data = $form_state->get('views_bulk_operations');
|
||||
|
||||
$action = $this->actionManager->createInstance($form_data['action_id']);
|
||||
if (method_exists($action, 'submitConfigurationForm')) {
|
||||
$action->submitConfigurationForm($form, $form_state);
|
||||
$form_data['configuration'] = $action->getConfiguration();
|
||||
}
|
||||
else {
|
||||
$form_state->cleanValues();
|
||||
$form_data['configuration'] = $form_state->getValues();
|
||||
}
|
||||
|
||||
$definition = $this->actionManager->getDefinition($form_data['action_id']);
|
||||
if (!empty($definition['confirm_form_route_name'])) {
|
||||
// Update tempStore data.
|
||||
$this->setTempstoreData($form_data, $form_data['view_id'], $form_data['display_id']);
|
||||
// Go to the confirm route.
|
||||
$form_state->setRedirect($definition['confirm_form_route_name'], [
|
||||
'view_id' => $form_data['view_id'],
|
||||
'display_id' => $form_data['display_id'],
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$this->deleteTempstoreData($form_data['view_id'], $form_data['display_id']);
|
||||
$this->actionProcessor->executeProcessing($form_data);
|
||||
$form_state->setRedirectUrl($form_data['redirect_url']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Form;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
|
||||
|
||||
/**
|
||||
* Default action execution confirmation form.
|
||||
*/
|
||||
class ConfirmAction extends FormBase {
|
||||
|
||||
use ViewsBulkOperationsFormTrait;
|
||||
|
||||
/**
|
||||
* User private temporary storage factory.
|
||||
*
|
||||
* @var \Drupal\user\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action manager.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action processor.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface
|
||||
*/
|
||||
protected $actionProcessor;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
|
||||
* User private temporary storage factory.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
|
||||
* Extended action manager object.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
* Views Bulk Operations action processor.
|
||||
*/
|
||||
public function __construct(
|
||||
PrivateTempStoreFactory $tempStoreFactory,
|
||||
ViewsBulkOperationsActionManager $actionManager,
|
||||
ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
) {
|
||||
$this->tempStoreFactory = $tempStoreFactory;
|
||||
$this->actionManager = $actionManager;
|
||||
$this->actionProcessor = $actionProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('user.private_tempstore'),
|
||||
$container->get('plugin.manager.views_bulk_operations_action'),
|
||||
$container->get('views_bulk_operations.processor')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'views_bulk_operations_confirm_action';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $view_id = NULL, $display_id = NULL) {
|
||||
|
||||
$form_data = $this->getFormData($view_id, $display_id);
|
||||
|
||||
// TODO: display an error msg, redirect back.
|
||||
if (!isset($form_data['action_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form_state->setStorage($form_data);
|
||||
|
||||
if (!empty($form_data['entity_labels'])) {
|
||||
$form['list'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $form_data['entity_labels'],
|
||||
];
|
||||
}
|
||||
|
||||
$form['#title'] = $this->formatPlural(
|
||||
$form_data['selected_count'],
|
||||
'Are you sure you wish to perform "%action" action on 1 entity?',
|
||||
'Are you sure you wish to perform "%action" action on %count entities?',
|
||||
[
|
||||
'%action' => $form_data['action_label'],
|
||||
'%count' => $form_data['selected_count'],
|
||||
]
|
||||
);
|
||||
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Execute action'),
|
||||
'#submit' => [
|
||||
[$this, 'submitForm'],
|
||||
],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_data = $form_state->getStorage();
|
||||
$this->deleteTempstoreData($form_data['view_id'], $form_data['display_id']);
|
||||
$this->actionProcessor->executeProcessing($form_data);
|
||||
$form_state->setRedirectUrl($form_data['redirect_url']);
|
||||
}
|
||||
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Defines common methods for Views Bulk Operations forms.
|
||||
*/
|
||||
trait ViewsBulkOperationsFormTrait {
|
||||
|
||||
/**
|
||||
* The tempstore object associated with the current view.
|
||||
*
|
||||
* @var \Drupal\user\PrivateTempStore
|
||||
*/
|
||||
protected $viewTempstore;
|
||||
|
||||
/**
|
||||
* The tempstore name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tempStoreName;
|
||||
|
||||
/**
|
||||
* Helper function to prepare data needed for proper form display.
|
||||
*
|
||||
* @param string $view_id
|
||||
* The current view ID.
|
||||
* @param string $display_id
|
||||
* The current view display ID.
|
||||
*
|
||||
* @return array
|
||||
* Array containing data for the form builder.
|
||||
*/
|
||||
protected function getFormData($view_id, $display_id) {
|
||||
|
||||
// Get tempstore data.
|
||||
$form_data = $this->getTempstoreData($view_id, $display_id);
|
||||
|
||||
// Get data needed for selected entities list.
|
||||
if (!empty($form_data['list'])) {
|
||||
$form_data['entity_labels'] = [];
|
||||
$form_data['selected_count'] = 0;
|
||||
foreach ($form_data['list'] as $item) {
|
||||
$form_data['selected_count']++;
|
||||
$form_data['entity_labels'][] = $item[4];
|
||||
}
|
||||
}
|
||||
elseif ($form_data['total_results']) {
|
||||
$form_data['selected_count'] = $form_data['total_results'];
|
||||
}
|
||||
else {
|
||||
$form_data['selected_count'] = (string) $this->t('all');
|
||||
}
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the bulk form key for an entity.
|
||||
*
|
||||
* This generates a key that is used as the checkbox return value when
|
||||
* submitting the bulk form.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to calculate a bulk form key for.
|
||||
* @param mixed $base_field_value
|
||||
* The value of the base field for this view result.
|
||||
*
|
||||
* @return string
|
||||
* The bulk form key representing the entity id, language and revision (if
|
||||
* applicable) as one string.
|
||||
*
|
||||
* @see self::loadEntityFromBulkFormKey()
|
||||
*/
|
||||
public static function calculateEntityBulkFormKey(EntityInterface $entity, $base_field_value) {
|
||||
// We don't really need the entity ID or type ID, since only the
|
||||
// base field value and language are used to select rows, but
|
||||
// other modules may need those values.
|
||||
$key_parts = [
|
||||
$base_field_value,
|
||||
$entity->language()->getId(),
|
||||
$entity->getEntityTypeId(),
|
||||
$entity->id(),
|
||||
];
|
||||
|
||||
// An entity ID could be an arbitrary string (although they are typically
|
||||
// numeric). JSON then Base64 encoding ensures the bulk_form_key is
|
||||
// safe to use in HTML, and that the key parts can be retrieved.
|
||||
$key = json_encode($key_parts);
|
||||
return base64_encode($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity list item from a bulk form key and label.
|
||||
*
|
||||
* @param string $bulkFormKey
|
||||
* A bulk form key.
|
||||
* @param mixed $label
|
||||
* Entity label, string or
|
||||
* \Drupal\Core\StringTranslation\TranslatableMarkup.
|
||||
*
|
||||
* @return array
|
||||
* Entity list item.
|
||||
*/
|
||||
protected function getListItem($bulkFormKey, $label) {
|
||||
$item = json_decode(base64_decode($bulkFormKey));
|
||||
$item[] = $label;
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current view tempstore object.
|
||||
*/
|
||||
protected function getTempstore($view_id = NULL, $display_id = NULL) {
|
||||
if (!isset($this->viewTempstore)) {
|
||||
$this->tempStoreName = 'views_bulk_operations_' . $view_id . '_' . $display_id;
|
||||
$this->viewTempstore = $this->tempStoreFactory->get($this->tempStoreName);
|
||||
}
|
||||
return $this->viewTempstore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current view user tempstore data.
|
||||
*
|
||||
* @param string $view_id
|
||||
* The current view ID.
|
||||
* @param string $display_id
|
||||
* The display ID of the current view.
|
||||
*/
|
||||
protected function getTempstoreData($view_id = NULL, $display_id = NULL) {
|
||||
$data = $this->getTempstore($view_id, $display_id)->get($this->currentUser()->id());
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current view user tempstore data.
|
||||
*
|
||||
* @param array $data
|
||||
* The data to set.
|
||||
* @param string $view_id
|
||||
* The current view ID.
|
||||
* @param string $display_id
|
||||
* The display ID of the current view.
|
||||
*/
|
||||
protected function setTempstoreData(array $data, $view_id = NULL, $display_id = NULL) {
|
||||
return $this->getTempstore($view_id, $display_id)->set($this->currentUser()->id(), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current view user tempstore data.
|
||||
*
|
||||
* @param string $view_id
|
||||
* The current view ID.
|
||||
* @param string $display_id
|
||||
* The display ID of the current view.
|
||||
*/
|
||||
protected function deleteTempstoreData($view_id = NULL, $display_id = NULL) {
|
||||
return $this->getTempstore($view_id, $display_id)->delete($this->currentUser()->id());
|
||||
}
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
|
||||
/**
|
||||
* Cancels a user account.
|
||||
*
|
||||
* @Action(
|
||||
* id = "vbo_cancel_user_action",
|
||||
* label = @Translation("Cancel the selected user accounts"),
|
||||
* type = "user",
|
||||
* )
|
||||
*/
|
||||
class CancelUserAction extends ViewsBulkOperationsActionBase implements ContainerFactoryPluginInterface, PluginFormInterface {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* User module config.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ImmutableConfig
|
||||
*/
|
||||
protected $userConfig;
|
||||
|
||||
/**
|
||||
* Module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* Plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin Id.
|
||||
* @param mixed $plugin_definition
|
||||
* Plugin definition.
|
||||
* @param Drupal\Core\Session\AccountInterface $currentUser
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
|
||||
* The config factory object.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
|
||||
* Module handler service.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
AccountInterface $currentUser,
|
||||
ConfigFactoryInterface $configFactory,
|
||||
ModuleHandlerInterface $moduleHandler
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->currentUser = $currentUser;
|
||||
$this->userConfig = $configFactory->get('user.settings');
|
||||
$this->moduleHandler = $moduleHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('current_user'),
|
||||
$container->get('config.factory'),
|
||||
$container->get('module_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($account = NULL) {
|
||||
if ($account->id() === $this->currentUser->id() && (empty($this->context['list']) || count($this->context['list'] > 1))) {
|
||||
drupal_set_message($this->t('The current user account cannot be canceled in a batch operation. Select your account only or cancel it from your account page.'), 'error');
|
||||
}
|
||||
elseif (intval($account->id()) === 1) {
|
||||
drupal_set_message($this->t('The user 1 account (%label) cannot be canceled.', [
|
||||
'%label' => $account->label(),
|
||||
]), 'error');
|
||||
}
|
||||
else {
|
||||
// Allow other modules to act.
|
||||
if ($this->configuration['user_cancel_method'] != 'user_cancel_delete') {
|
||||
$this->moduleHandler->invokeAll('user_cancel', [
|
||||
$this->configuration,
|
||||
$account,
|
||||
$this->configuration['user_cancel_method'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Cancel the account.
|
||||
_user_cancel($this->configuration, $account, $this->configuration['user_cancel_method']);
|
||||
|
||||
// If current user was cancelled, logout.
|
||||
if ($account->id() == $this->currentUser->id()) {
|
||||
_user_cancel_session_regenerate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['user_cancel_method'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('When cancelling these accounts'),
|
||||
];
|
||||
|
||||
$form['user_cancel_method'] += user_cancel_methods();
|
||||
|
||||
// Allow to send the account cancellation confirmation mail.
|
||||
$form['user_cancel_confirm'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Require email confirmation to cancel account'),
|
||||
'#default_value' => FALSE,
|
||||
'#description' => $this->t('When enabled, the user must confirm the account cancellation via email.'),
|
||||
];
|
||||
// Also allow to send account canceled notification mail, if enabled.
|
||||
$form['user_cancel_notify'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Notify user when account is canceled'),
|
||||
'#default_value' => FALSE,
|
||||
'#access' => $this->userConfig->get('notify.status_canceled'),
|
||||
'#description' => $this->t('When enabled, the user will receive an email notification after the account has been canceled.'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
/** @var \Drupal\user\UserInterface $object */
|
||||
return $object->access('delete', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Delete entity action with default confirmation form.
|
||||
*
|
||||
* @Action(
|
||||
* id = "views_bulk_operations_delete_entity",
|
||||
* label = @Translation("Delete selected entities"),
|
||||
* type = "",
|
||||
* confirm = TRUE,
|
||||
* )
|
||||
*/
|
||||
class EntityDeleteAction extends ViewsBulkOperationsActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->delete();
|
||||
return $this->t('Delete entities');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access = $object->access('delete', $account, TRUE);
|
||||
if ($object->getEntityType() === 'node') {
|
||||
$access->andIf($object->status->access('delete', $account, TRUE));
|
||||
}
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
+891
@@ -0,0 +1,891 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationTrait;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
|
||||
use Drupal\views\Plugin\views\field\UncacheableFieldHandlerTrait;
|
||||
use Drupal\views\Plugin\views\style\Table;
|
||||
use Drupal\views\ResultRow;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager;
|
||||
use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface;
|
||||
use Drupal\views_bulk_operations\Form\ViewsBulkOperationsFormTrait;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines the Views Bulk Operations field plugin.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("views_bulk_operations_bulk_form")
|
||||
*/
|
||||
class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDependencyInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
use RedirectDestinationTrait;
|
||||
use UncacheableFieldHandlerTrait;
|
||||
use ViewsBulkOperationsFormTrait;
|
||||
|
||||
/**
|
||||
* Object that gets the current view data.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\ViewsbulkOperationsViewDataInterface
|
||||
*/
|
||||
protected $viewData;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action manager.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* Views Bulk Operations action processor.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface
|
||||
*/
|
||||
protected $actionProcessor;
|
||||
|
||||
/**
|
||||
* User private temporary storage factory.
|
||||
*
|
||||
* @var \Drupal\user\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStoreFactory;
|
||||
|
||||
/**
|
||||
* The current user object.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The current URL object.
|
||||
*
|
||||
* @var \Drupal\Core\Url
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* An array of actions that can be executed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $actions = [];
|
||||
|
||||
/**
|
||||
* An array of bulk form options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bulkOptions;
|
||||
|
||||
/**
|
||||
* Tempstore data.
|
||||
*
|
||||
* This gets passed to the next requests if needed
|
||||
* or used in the views form submit handler directly.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tempStoreData = [];
|
||||
|
||||
/**
|
||||
* Constructs a new BulkForm object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface $viewData
|
||||
* The VBO View Data provider service.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
|
||||
* Extended action manager object.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface $actionProcessor
|
||||
* Views Bulk Operations action processor.
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
|
||||
* User private temporary storage factory.
|
||||
* @param \Drupal\Core\Session\AccountInterface $currentUser
|
||||
* The current user object.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
ViewsbulkOperationsViewDataInterface $viewData,
|
||||
ViewsBulkOperationsActionManager $actionManager,
|
||||
ViewsBulkOperationsActionProcessorInterface $actionProcessor,
|
||||
PrivateTempStoreFactory $tempStoreFactory,
|
||||
AccountInterface $currentUser,
|
||||
RequestStack $requestStack
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->viewData = $viewData;
|
||||
$this->actionManager = $actionManager;
|
||||
$this->actionProcessor = $actionProcessor;
|
||||
$this->tempStoreFactory = $tempStoreFactory;
|
||||
$this->currentUser = $currentUser;
|
||||
$this->url = Url::createFromRequest($requestStack->getCurrentRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('views_bulk_operations.data'),
|
||||
$container->get('plugin.manager.views_bulk_operations_action'),
|
||||
$container->get('views_bulk_operations.processor'),
|
||||
$container->get('user.private_tempstore'),
|
||||
$container->get('current_user'),
|
||||
$container->get('request_stack')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
// Don't initialize if view has been built from VBO action processor.
|
||||
if (!empty($this->view->views_bulk_operations_processor_built)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set this property to always have the total rows information.
|
||||
$this->view->get_total_rows = TRUE;
|
||||
|
||||
// Initialize VBO View Data object.
|
||||
$this->viewData->init($view, $display, $this->options['relationship']);
|
||||
|
||||
// Fetch actions.
|
||||
$this->actions = [];
|
||||
$entity_types = $this->viewData->getEntityTypeIds();
|
||||
|
||||
// Get actions only if there are any entity types set for the view.
|
||||
if (!empty($entity_types)) {
|
||||
foreach ($this->actionManager->getDefinitions() as $id => $definition) {
|
||||
if (empty($definition['type']) || in_array($definition['type'], $entity_types, TRUE)) {
|
||||
$this->actions[$id] = $definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force form_step setting to TRUE due to #2879310.
|
||||
$this->options['form_step'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tempstore data.
|
||||
*
|
||||
* This function must be called a bit later, when the view
|
||||
* query has been built. Also, no point doing this on the view
|
||||
* admin page.
|
||||
*/
|
||||
protected function updateTempstoreData() {
|
||||
// Initialize tempstore object and get data if available.
|
||||
$this->tempStoreData = $this->getTempstoreData($this->view->id(), $this->view->current_display);
|
||||
|
||||
// Parameters subject to change (either by an admin or user action).
|
||||
$variable = [
|
||||
'batch' => $this->options['batch'],
|
||||
'batch_size' => $this->options['batch'] ? $this->options['batch_size'] : 0,
|
||||
'total_results' => $this->viewData->getTotalResults(),
|
||||
'arguments' => $this->view->args,
|
||||
'redirect_url' => $this->url,
|
||||
'exposed_input' => $this->view->getExposedInput(),
|
||||
];
|
||||
|
||||
// Create tempstore data object if it doesn't exist.
|
||||
if (!is_array($this->tempStoreData)) {
|
||||
$this->tempStoreData = [];
|
||||
|
||||
// Add constant parameters.
|
||||
$this->tempStoreData += [
|
||||
'view_id' => $this->view->id(),
|
||||
'display_id' => $this->view->current_display,
|
||||
'list' => [],
|
||||
];
|
||||
|
||||
// Add variable parameters.
|
||||
$this->tempStoreData += $variable;
|
||||
|
||||
$this->setTempstoreData($this->tempStoreData);
|
||||
}
|
||||
|
||||
// Update some of the tempstore data parameters if required.
|
||||
else {
|
||||
$update = FALSE;
|
||||
|
||||
// Delete list if view arguments and optionally exposed filters changed.
|
||||
// NOTE: this should be subject to a discussion, maybe tempstore
|
||||
// should be arguments - specific?
|
||||
$clear_triggers = ['arguments'];
|
||||
if ($this->options['clear_on_exposed']) {
|
||||
$clear_triggers[] = 'exposed_input';
|
||||
}
|
||||
|
||||
foreach ($clear_triggers as $trigger) {
|
||||
if ($variable[$trigger] !== $this->tempStoreData[$trigger]) {
|
||||
$this->tempStoreData[$trigger] = $variable[$trigger];
|
||||
$this->tempStoreData['list'] = [];
|
||||
}
|
||||
unset($variable[$trigger]);
|
||||
$update = TRUE;
|
||||
}
|
||||
|
||||
foreach ($variable as $param => $value) {
|
||||
if (!isset($this->tempStoreData[$param]) || $this->tempStoreData[$param] != $value) {
|
||||
$update = TRUE;
|
||||
$this->tempStoreData[$param] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
$this->setTempstoreData($this->tempStoreData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current user.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountInterface
|
||||
* The current user.
|
||||
*/
|
||||
protected function currentUser() {
|
||||
return $this->currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
// @todo Consider making the bulk operation form cacheable. See
|
||||
// https://www.drupal.org/node/2503009.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntity(ResultRow $row) {
|
||||
return $this->viewData->getEntity($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['batch'] = ['default' => TRUE];
|
||||
$options['batch_size'] = ['default' => 10];
|
||||
$options['form_step'] = ['default' => TRUE];
|
||||
$options['buttons'] = ['default' => FALSE];
|
||||
$options['clear_on_exposed'] = ['default' => FALSE];
|
||||
$options['action_title'] = ['default' => $this->t('Action')];
|
||||
$options['selected_actions'] = ['default' => []];
|
||||
$options['preconfiguration'] = ['default' => []];
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
// If the view type is not supported, suppress form display.
|
||||
// Also display information note to the user.
|
||||
if (empty($this->actions)) {
|
||||
$form = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('NOTE'),
|
||||
'#markup' => $this->t('Views Bulk Operations will work only with normal entity views and contrib module views that are integrated. See \Drupal\views_bulk_operations\EventSubscriber\ViewsBulkOperationsEventSubscriber class for integration best practice.'),
|
||||
'#prefix' => '<div class="scroll">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
$form['#attributes']['class'][] = 'views-bulk-operations-ui';
|
||||
$form['#attached']['library'][] = 'views_bulk_operations/adminUi';
|
||||
|
||||
$form['batch'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Process in a batch operation'),
|
||||
'#default_value' => $this->options['batch'],
|
||||
];
|
||||
|
||||
$form['batch_size'] = [
|
||||
'#title' => $this->t('Batch size'),
|
||||
'#type' => 'number',
|
||||
'#min' => 1,
|
||||
'#step' => 1,
|
||||
'#description' => $this->t('Only applicable if results are processed in a batch operation.'),
|
||||
'#default_value' => $this->options['batch_size'],
|
||||
];
|
||||
|
||||
$form['form_step'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Configuration form on new page (configurable actions)'),
|
||||
'#default_value' => $this->options['form_step'],
|
||||
// Due to #2879310 this setting must always be at TRUE.
|
||||
'#access' => FALSE,
|
||||
];
|
||||
|
||||
$form['buttons'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Display selectable actions as buttons.'),
|
||||
'#default_value' => $this->options['buttons'],
|
||||
];
|
||||
|
||||
$form['clear_on_exposed'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Clear selection when exposed filters change.'),
|
||||
'#default_value' => $this->options['clear_on_exposed'],
|
||||
];
|
||||
|
||||
$form['action_title'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Action title'),
|
||||
'#default_value' => $this->options['action_title'],
|
||||
'#description' => $this->t('The title shown above the actions dropdown.'),
|
||||
];
|
||||
|
||||
$form['selected_actions'] = [
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->t('Selected actions'),
|
||||
'#attributes' => ['class' => ['vbo-actions-widget']],
|
||||
];
|
||||
|
||||
// Load values for display.
|
||||
$form_values = $form_state->getValue(['options', 'selected_actions']);
|
||||
if (is_null($form_values)) {
|
||||
$selected_actions = $this->options['selected_actions'];
|
||||
$preconfiguration = $this->options['preconfiguration'];
|
||||
}
|
||||
else {
|
||||
$selected_actions = [];
|
||||
$preconfiguration = [];
|
||||
foreach ($form_values as $id => $value) {
|
||||
$selected_actions[$id] = $value['state'] ? $id : 0;
|
||||
$preconfiguration[$id] = isset($value['preconfiguration']) ? $value['preconfiguration'] : [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->actions as $id => $action) {
|
||||
$form['selected_actions'][$id]['state'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $action['label'],
|
||||
'#default_value' => empty($selected_actions[$id]) ? 0 : 1,
|
||||
'#attributes' => ['class' => ['vbo-action-state']],
|
||||
];
|
||||
|
||||
// There are problems with AJAX on this form when adding
|
||||
// new elements (Views issue), a workaround is to render
|
||||
// all elements and show/hide them when needed.
|
||||
$form['selected_actions'][$id]['preconfiguration'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Preconfiguration for "@action"', [
|
||||
'@action' => $action['label'],
|
||||
]),
|
||||
'#attributes' => [
|
||||
'data-for' => $id,
|
||||
'style' => empty($selected_actions[$id]) ? 'display: none' : NULL,
|
||||
],
|
||||
];
|
||||
|
||||
// Default label_override element.
|
||||
$form['selected_actions'][$id]['preconfiguration']['label_override'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Override label'),
|
||||
'#description' => $this->t('Leave empty for the default label.'),
|
||||
'#default_value' => isset($preconfiguration[$id]['label_override']) ? $preconfiguration[$id]['label_override'] : '',
|
||||
];
|
||||
|
||||
// Load preconfiguration form if available.
|
||||
if (method_exists($action['class'], 'buildPreConfigurationForm')) {
|
||||
if (!isset($preconfiguration[$id])) {
|
||||
$preconfiguration[$id] = [];
|
||||
}
|
||||
$actionObject = $this->actionManager->createInstance($id);
|
||||
$form['selected_actions'][$id]['preconfiguration'] = $actionObject->buildPreConfigurationForm($form['selected_actions'][$id]['preconfiguration'], $preconfiguration[$id], $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$options = &$form_state->getValue('options');
|
||||
foreach ($options['selected_actions'] as $id => $action) {
|
||||
if (!empty($action['state'])) {
|
||||
if (isset($action['preconfiguration'])) {
|
||||
$options['preconfiguration'][$id] = $action['preconfiguration'];
|
||||
unset($options['selected_actions'][$id]['preconfiguration']);
|
||||
}
|
||||
$options['selected_actions'][$id] = $id;
|
||||
}
|
||||
else {
|
||||
unset($options['preconfiguration'][$id]);
|
||||
$options['selected_actions'][$id] = 0;
|
||||
}
|
||||
}
|
||||
parent::submitOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(&$values) {
|
||||
parent::preRender($values);
|
||||
|
||||
// Add empty classes if there are no actions available.
|
||||
if (empty($this->getBulkOptions())) {
|
||||
$this->options['element_label_class'] .= 'empty';
|
||||
$this->options['element_class'] .= 'empty';
|
||||
$this->options['element_wrapper_class'] .= 'empty';
|
||||
$this->options['label'] = '';
|
||||
}
|
||||
// If the view is using a table style, provide a placeholder for a
|
||||
// "select all" checkbox.
|
||||
elseif (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
|
||||
// Add the tableselect css classes.
|
||||
$this->options['element_label_class'] .= 'select-all';
|
||||
// Hide the actual label of the field on the table header.
|
||||
$this->options['label'] = '';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue(ResultRow $row, $field = NULL) {
|
||||
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor for the bulk form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function viewsForm(array &$form, FormStateInterface $form_state) {
|
||||
// Make sure we do not accidentally cache this form.
|
||||
// @todo Evaluate this again in https://www.drupal.org/node/2503009.
|
||||
$form['#cache']['max-age'] = 0;
|
||||
|
||||
// Add VBO class to the form.
|
||||
$form['#attributes']['class'][] = 'vbo-view-form';
|
||||
|
||||
// Add VBO front UI and tableselect libraries for table display style.
|
||||
if ($this->view->style_plugin instanceof Table) {
|
||||
$form['#attached']['library'][] = 'core/drupal.tableselect';
|
||||
$this->view->style_plugin->options['views_bulk_operations_enabled'] = TRUE;
|
||||
}
|
||||
$form['#attached']['library'][] = 'views_bulk_operations/frontUi';
|
||||
// Only add the bulk form options and buttons if
|
||||
// there are results and any actions are available.
|
||||
$action_options = $this->getBulkOptions();
|
||||
if (!empty($this->view->result) && !empty($action_options)) {
|
||||
|
||||
// Update tempstore data.
|
||||
$this->updateTempstoreData();
|
||||
|
||||
$use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
|
||||
$form[$this->options['id']]['#tree'] = TRUE;
|
||||
|
||||
// Get pager data if available.
|
||||
if (!empty($this->view->pager) && method_exists($this->view->pager, 'hasMoreRecords')) {
|
||||
$pagerData = [
|
||||
'current' => $this->view->pager->getCurrentPage(),
|
||||
'more' => $this->view->pager->hasMoreRecords(),
|
||||
];
|
||||
}
|
||||
|
||||
// Render checkboxes for all rows.
|
||||
$page_selected = [];
|
||||
$base_field = $this->view->storage->get('base_field');
|
||||
foreach ($this->view->result as $row_index => $row) {
|
||||
$entity = $this->getEntity($row);
|
||||
$bulk_form_key = self::calculateEntityBulkFormKey(
|
||||
$entity,
|
||||
$row->{$base_field}
|
||||
);
|
||||
|
||||
$checked = isset($this->tempStoreData['list'][$bulk_form_key]);
|
||||
if ($checked) {
|
||||
$page_selected[] = $bulk_form_key;
|
||||
}
|
||||
$form[$this->options['id']][$row_index] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $entity->label(),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $checked,
|
||||
'#return_value' => $bulk_form_key,
|
||||
];
|
||||
}
|
||||
|
||||
// Ensure a consistent container for filters/operations
|
||||
// in the view header.
|
||||
$form['header'] = [
|
||||
'#type' => 'container',
|
||||
'#weight' => -100,
|
||||
];
|
||||
|
||||
// Build the bulk operations action widget for the header.
|
||||
// Allow themes to apply .container-inline on this separate container.
|
||||
$form['header'][$this->options['id']] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'id' => 'vbo-action-form-wrapper',
|
||||
],
|
||||
];
|
||||
|
||||
// Display actions buttons or selector.
|
||||
if ($this->options['buttons']) {
|
||||
unset($form['actions']['submit']);
|
||||
foreach ($action_options as $id => $label) {
|
||||
$form['actions'][$id] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $label,
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Replace the form submit button label.
|
||||
$form['actions']['submit']['#value'] = $this->t('Apply to selected items');
|
||||
|
||||
$form['header'][$this->options['id']]['action'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->options['action_title'],
|
||||
'#options' => ['' => $this->t('-- Select action --')] + $action_options,
|
||||
];
|
||||
}
|
||||
|
||||
// Add AJAX functionality if actions are configurable through this form.
|
||||
if (empty($this->options['form_step'])) {
|
||||
$form['header'][$this->options['id']]['action']['#ajax'] = [
|
||||
'callback' => [__CLASS__, 'viewsFormAjax'],
|
||||
'wrapper' => 'vbo-action-configuration-wrapper',
|
||||
];
|
||||
$form['header'][$this->options['id']]['configuration'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['id' => 'vbo-action-configuration-wrapper'],
|
||||
];
|
||||
|
||||
$action_id = $form_state->getValue('action');
|
||||
if (!empty($action_id)) {
|
||||
$action = $this->actions[$action_id];
|
||||
if ($this->isConfigurable($action)) {
|
||||
$actionObject = $this->actionManager->createInstance($action_id);
|
||||
$form['header'][$this->options['id']]['configuration'] += $actionObject->buildConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state);
|
||||
$form['header'][$this->options['id']]['configuration']['#config_included'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$display_select_all = isset($pagerData) && ($pagerData['more'] || $pagerData['current'] > 0);
|
||||
// Selection info: displayed if exposed filters are set and selection
|
||||
// is not cleared when they change or "select all" element display
|
||||
// conditions are met.
|
||||
if ((!$this->options['clear_on_exposed'] && !empty($this->view->getExposedInput())) || $display_select_all) {
|
||||
|
||||
$form['header'][$this->options['id']]['multipage'] = [
|
||||
'#type' => 'details',
|
||||
'#open' => FALSE,
|
||||
'#title' => $this->t('Selected %count items in this view', [
|
||||
'%count' => count($this->tempStoreData['list']),
|
||||
]),
|
||||
'#attributes' => [
|
||||
// Add view_id and display_id to be available for
|
||||
// js multipage selector functionality.
|
||||
'data-view-id' => $this->tempStoreData['view_id'],
|
||||
'data-display-id' => $this->tempStoreData['display_id'],
|
||||
'class' => ['vbo-multipage-selector'],
|
||||
'name' => 'somename',
|
||||
],
|
||||
];
|
||||
|
||||
// Display a list of items selected on other pages.
|
||||
$form['header'][$this->options['id']]['multipage']['list'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#title' => $this->t('Items selected on other pages:'),
|
||||
'#items' => [],
|
||||
'#empty' => $this->t('No selection'),
|
||||
];
|
||||
if (count($this->tempStoreData['list']) > count($page_selected)) {
|
||||
foreach ($this->tempStoreData['list'] as $bulk_form_key => $item) {
|
||||
if (!in_array($bulk_form_key, $page_selected)) {
|
||||
$form['header'][$this->options['id']]['multipage']['list']['#items'][] = $item[4];
|
||||
}
|
||||
}
|
||||
$form['header'][$this->options['id']]['multipage']['clear'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Clear'),
|
||||
'#submit' => [[$this, 'clearSelection']],
|
||||
'#limit_validation_errors' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Select all results checkbox.
|
||||
if ($display_select_all) {
|
||||
$form['header'][$this->options['id']]['select_all'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Select all@count results in this view', [
|
||||
'@count' => $this->tempStoreData['total_results'] ? ' ' . $this->tempStoreData['total_results'] : '',
|
||||
]),
|
||||
'#attributes' => ['class' => ['vbo-select-all']],
|
||||
];
|
||||
}
|
||||
|
||||
// Duplicate the form actions into the action container in the header.
|
||||
$form['header'][$this->options['id']]['actions'] = $form['actions'];
|
||||
}
|
||||
else {
|
||||
// Remove the default actions build array.
|
||||
unset($form['actions']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX callback for the views form.
|
||||
*
|
||||
* Currently not used due to #2879310.
|
||||
*/
|
||||
public static function viewsFormAjax(array $form, FormStateInterface $form_state) {
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
$plugin_id = $trigger['#array_parents'][1];
|
||||
return $form['header'][$plugin_id]['configuration'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available operations for this form.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of operations, suitable for a select element.
|
||||
*/
|
||||
protected function getBulkOptions() {
|
||||
if (!isset($this->bulkOptions)) {
|
||||
$this->bulkOptions = [];
|
||||
foreach ($this->actions as $id => $definition) {
|
||||
// Filter out actions that weren't selected.
|
||||
if (!in_array($id, $this->options['selected_actions'], TRUE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check access permission, if defined.
|
||||
if (!empty($definition['requirements']['_permission']) && !$this->currentUser->hasPermission($definition['requirements']['_permission'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Override label if applicable.
|
||||
if (!empty($this->options['preconfiguration'][$id]['label_override'])) {
|
||||
$this->bulkOptions[$id] = $this->options['preconfiguration'][$id]['label_override'];
|
||||
}
|
||||
else {
|
||||
$this->bulkOptions[$id] = $definition['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->bulkOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the bulk form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function viewsFormSubmit(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->get('step') == 'views_form_views_form') {
|
||||
|
||||
$action_id = $form_state->getValue('action');
|
||||
|
||||
$action = $this->actions[$action_id];
|
||||
|
||||
$this->tempStoreData['action_id'] = $action_id;
|
||||
$this->tempStoreData['action_label'] = empty($this->options['preconfiguration'][$action_id]['label_override']) ? (string) $action['label'] : $this->options['preconfiguration'][$action_id]['label_override'];
|
||||
$this->tempStoreData['relationship_id'] = $this->options['relationship'];
|
||||
$this->tempStoreData['preconfiguration'] = isset($this->options['preconfiguration'][$action_id]) ? $this->options['preconfiguration'][$action_id] : [];
|
||||
|
||||
if (!$form_state->getValue('select_all')) {
|
||||
// Update list data with the form selection.
|
||||
foreach ($form_state->getValue($this->options['id']) as $row_index => $bulkFormKey) {
|
||||
if ($bulkFormKey) {
|
||||
$this->tempStoreData['list'][$bulkFormKey] = $this->getListItem($bulkFormKey, $form[$this->options['id']][$row_index]['#title']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Unset the list completely.
|
||||
$this->tempStoreData['list'] = [];
|
||||
}
|
||||
|
||||
$configurable = $this->isConfigurable($action);
|
||||
|
||||
// Get configuration if using AJAX.
|
||||
if ($configurable && empty($this->options['form_step'])) {
|
||||
$actionObject = $this->actionManager->createInstance($action_id);
|
||||
if (method_exists($actionObject, 'submitConfigurationForm')) {
|
||||
$actionObject->submitConfigurationForm($form, $form_state);
|
||||
$this->tempStoreData['configuration'] = $actionObject->getConfiguration();
|
||||
}
|
||||
else {
|
||||
$form_state->cleanValues();
|
||||
$this->tempStoreData['configuration'] = $form_state->getValues();
|
||||
}
|
||||
}
|
||||
|
||||
// Routing - determine redirect route.
|
||||
if ($this->options['form_step'] && $configurable) {
|
||||
$redirect_route = 'views_bulk_operations.execute_configurable';
|
||||
}
|
||||
elseif ($this->options['batch']) {
|
||||
if (!empty($action['confirm_form_route_name'])) {
|
||||
$redirect_route = $action['confirm_form_route_name'];
|
||||
}
|
||||
}
|
||||
elseif (!empty($action['confirm_form_route_name'])) {
|
||||
$redirect_route = $action['confirm_form_route_name'];
|
||||
}
|
||||
|
||||
// Redirect if needed.
|
||||
if (!empty($redirect_route)) {
|
||||
$this->setTempstoreData($this->tempStoreData);
|
||||
|
||||
$form_state->setRedirect($redirect_route, [
|
||||
'view_id' => $this->view->id(),
|
||||
'display_id' => $this->view->current_display,
|
||||
]);
|
||||
}
|
||||
// Or process rows here and now.
|
||||
else {
|
||||
$this->deleteTempstoreData();
|
||||
$this->actionProcessor->executeProcessing($this->tempStoreData, $this->view);
|
||||
$form_state->setRedirectUrl($this->tempStoreData['redirect_url']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the form selection along with entire tempstore.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function clearSelection(array &$form, FormStateInterface $form_state) {
|
||||
$this->deleteTempstoreData();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewsFormValidate(&$form, FormStateInterface $form_state) {
|
||||
if ($this->options['buttons']) {
|
||||
$trigger = $form_state->getTriggeringElement();
|
||||
$action_id = end($trigger['#parents']);
|
||||
$form_state->setValue('action', $action_id);
|
||||
}
|
||||
|
||||
if (empty($form_state->getValue('action'))) {
|
||||
$form_state->setErrorByName('action', $this->t('Please select an action to perform.'));
|
||||
}
|
||||
|
||||
// This happened once, can't reproduce but here's a safety switch.
|
||||
if (!isset($this->actions[$form_state->getValue('action')])) {
|
||||
$form_state->setErrorByName('action', $this->t('Form error occurred, please try again.'));
|
||||
}
|
||||
|
||||
if (!$form_state->getValue('select_all')) {
|
||||
// Update tempstore data to make sure we have also
|
||||
// results selected in other requests and validate if
|
||||
// anything is selected.
|
||||
$this->tempStoreData = $this->getTempstoreData();
|
||||
$selected = array_filter($form_state->getValue($this->options['id']));
|
||||
if (empty($this->tempStoreData['list']) && empty($selected)) {
|
||||
$form_state->setErrorByName('', $this->t('No items selected.'));
|
||||
}
|
||||
}
|
||||
|
||||
// Action config validation (if implemented).
|
||||
if (empty($this->options['form_step']) && !empty($form['header'][$this->options['id']]['configuration']['#config_included'])) {
|
||||
$action_id = $form_state->getValue('action');
|
||||
$action = $this->actions[$action_id];
|
||||
if (method_exists($action['class'], 'validateConfigurationForm')) {
|
||||
$actionObject = $this->actionManager->createInstance($action_id);
|
||||
$actionObject->validateConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clickSortable() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an action is configurable.
|
||||
*/
|
||||
protected function isConfigurable($action) {
|
||||
return (in_array('Drupal\Core\Plugin\PluginFormInterface', class_implements($action['class']), TRUE) || method_exists($action['class'], 'buildConfigurationForm'));
|
||||
}
|
||||
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Service;
|
||||
|
||||
use Drupal\Core\Action\ActionManager;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
|
||||
/**
|
||||
* Defines Views Bulk Operations action manager.
|
||||
*
|
||||
* Extends the core Action Manager to allow VBO actions
|
||||
* define additional configuration.
|
||||
*/
|
||||
class ViewsBulkOperationsActionManager extends ActionManager {
|
||||
|
||||
const ALTER_ACTIONS_EVENT = 'views_bulk_operations.action_definitions';
|
||||
|
||||
/**
|
||||
* Event dispatcher service.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* Additional parameters passed to alter event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $alterParameters;
|
||||
|
||||
/**
|
||||
* Service constructor.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
|
||||
* The event dispatcher service.
|
||||
*/
|
||||
public function __construct(
|
||||
\Traversable $namespaces,
|
||||
CacheBackendInterface $cacheBackend,
|
||||
ModuleHandlerInterface $moduleHandler,
|
||||
EventDispatcherInterface $eventDispatcher
|
||||
) {
|
||||
parent::__construct($namespaces, $cacheBackend, $moduleHandler);
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->setCacheBackend($cacheBackend, 'views_bulk_operations_action_info');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function findDefinitions() {
|
||||
$definitions = $this->getDiscovery()->getDefinitions();
|
||||
|
||||
// Incompatible actions.
|
||||
$incompatible = [
|
||||
'node_delete_action',
|
||||
'user_cancel_user_action',
|
||||
];
|
||||
|
||||
foreach ($definitions as $plugin_id => &$definition) {
|
||||
$this->processDefinition($definition, $plugin_id);
|
||||
if (empty($definition) || in_array($definition['id'], $incompatible)) {
|
||||
unset($definitions[$plugin_id]);
|
||||
}
|
||||
}
|
||||
$this->alterDefinitions($definitions);
|
||||
foreach ($definitions as $plugin_id => $plugin_definition) {
|
||||
// If the plugin definition is an object, attempt to convert it to an
|
||||
// array, if that is not possible, skip further processing.
|
||||
if (is_object($plugin_definition) && !($plugin_definition = (array) $plugin_definition)) {
|
||||
continue;
|
||||
}
|
||||
// If this plugin was provided by a module that does not exist, remove the
|
||||
// plugin definition.
|
||||
if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], ['core', 'component']) && !$this->providerExists($plugin_definition['provider'])) {
|
||||
unset($definitions[$plugin_id]);
|
||||
}
|
||||
}
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $parameters
|
||||
* Parameters of the method. Passed to alter event.
|
||||
*/
|
||||
public function getDefinitions(array $parameters = []) {
|
||||
if (empty($parameters['nocache'])) {
|
||||
$definitions = $this->getCachedDefinitions();
|
||||
}
|
||||
if (!isset($definitions)) {
|
||||
$this->alterParameters = $parameters;
|
||||
$definitions = $this->findDefinitions($parameters);
|
||||
|
||||
$this->setCachedDefinitions($definitions);
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific plugin definition.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* A plugin id.
|
||||
* @param bool $exception_on_invalid
|
||||
* (optional) If TRUE, an invalid plugin ID will throw an exception.
|
||||
* @param array $parameters
|
||||
* Parameters of the method. Passed to alter event.
|
||||
*
|
||||
* @return mixed
|
||||
* A plugin definition, or NULL if the plugin ID is invalid and
|
||||
* $exception_on_invalid is FALSE.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
* Thrown if $plugin_id is invalid and $exception_on_invalid is TRUE.
|
||||
*/
|
||||
public function getDefinition($plugin_id, $exception_on_invalid = TRUE, array $parameters = []) {
|
||||
// Loading all definitions here will not hurt much, as they're cached,
|
||||
// and we need the option to alter a definition.
|
||||
$definitions = $this->getDefinitions($parameters);
|
||||
if (isset($definitions[$plugin_id])) {
|
||||
return $definitions[$plugin_id];
|
||||
}
|
||||
elseif (!$exception_on_invalid) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
throw new PluginNotFoundException($plugin_id, sprintf('The "%s" plugin does not exist.', $plugin_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
// Only arrays can be operated on.
|
||||
if (!is_array($definition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($this->defaults) && is_array($this->defaults)) {
|
||||
$definition = NestedArray::mergeDeep($this->defaults, $definition);
|
||||
}
|
||||
|
||||
// Merge in defaults.
|
||||
$definition += [
|
||||
'confirm' => FALSE,
|
||||
];
|
||||
|
||||
// Add default confirmation form if confirm set to TRUE
|
||||
// and not explicitly set.
|
||||
if ($definition['confirm'] && empty($definition['confirm_form_route_name'])) {
|
||||
$definition['confirm_form_route_name'] = 'views_bulk_operations.confirm';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterDefinitions(&$definitions) {
|
||||
// Let other modules change definitions.
|
||||
// Main purpose: Action permissions bridge.
|
||||
$event = new Event();
|
||||
$event->alterParameters = $this->alterParameters;
|
||||
$event->definitions = &$definitions;
|
||||
$this->eventDispatcher->dispatch(static::ALTER_ACTIONS_EVENT, $event);
|
||||
}
|
||||
|
||||
}
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Service;
|
||||
|
||||
use Drupal\views\Views;
|
||||
use Drupal\Core\Session\AccountProxyInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;
|
||||
|
||||
/**
|
||||
* Defines VBO action processor.
|
||||
*/
|
||||
class ViewsBulkOperationsActionProcessor implements ViewsBulkOperationsActionProcessorInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* View data provider service.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface
|
||||
*/
|
||||
protected $viewDataService;
|
||||
|
||||
/**
|
||||
* VBO action manager.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* Current user object.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountProxyInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Is the object initialized?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $initialized = FALSE;
|
||||
|
||||
/**
|
||||
* The processed action object.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* The current view object.
|
||||
*
|
||||
* @var \Drupal\views\ViewExecutable
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* View data from the bulk form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bulkFormData;
|
||||
|
||||
/**
|
||||
* Array of entities that will be processed in the current batch.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queue = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewDataInterface $viewDataService
|
||||
* View data provider service.
|
||||
* @param \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager $actionManager
|
||||
* VBO action manager.
|
||||
* @param \Drupal\Core\Session\AccountProxyInterface $currentUser
|
||||
* Current user object.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
|
||||
* Module handler service.
|
||||
*/
|
||||
public function __construct(
|
||||
ViewsbulkOperationsViewDataInterface $viewDataService,
|
||||
ViewsBulkOperationsActionManager $actionManager,
|
||||
AccountProxyInterface $currentUser,
|
||||
ModuleHandlerInterface $moduleHandler
|
||||
) {
|
||||
$this->viewDataService = $viewDataService;
|
||||
$this->actionManager = $actionManager;
|
||||
$this->currentUser = $currentUser;
|
||||
$this->moduleHandler = $moduleHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initialize(array $view_data, $view = NULL) {
|
||||
|
||||
// It may happen that the service was already initialized
|
||||
// in this request (e.g. multiple Batch API operation calls).
|
||||
// Clear the processing queue in such a case.
|
||||
if ($this->initialized) {
|
||||
$this->queue = [];
|
||||
}
|
||||
|
||||
if (!isset($view_data['configuration'])) {
|
||||
$view_data['configuration'] = [];
|
||||
}
|
||||
if (!empty($view_data['preconfiguration'])) {
|
||||
$view_data['configuration'] += $view_data['preconfiguration'];
|
||||
}
|
||||
|
||||
// Initialize action object.
|
||||
$this->action = $this->actionManager->createInstance($view_data['action_id'], $view_data['configuration']);
|
||||
|
||||
// Set action context.
|
||||
$this->setActionContext($view_data);
|
||||
|
||||
// Set entire view data as object parameter for future reference.
|
||||
$this->bulkFormData = $view_data;
|
||||
|
||||
// Set the current view.
|
||||
$this->setView($view);
|
||||
|
||||
$this->initialized = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current view object.
|
||||
*
|
||||
* @param mixed $view
|
||||
* The current view object or NULL.
|
||||
*/
|
||||
protected function setView($view = NULL) {
|
||||
if (!is_null($view)) {
|
||||
$this->view = $view;
|
||||
}
|
||||
else {
|
||||
$this->view = Views::getView($this->bulkFormData['view_id']);
|
||||
$this->view->setDisplay($this->bulkFormData['display_id']);
|
||||
}
|
||||
$this->view->get_total_rows = TRUE;
|
||||
$this->view->views_bulk_operations_processor_built = TRUE;
|
||||
if (!empty($this->bulkFormData['arguments'])) {
|
||||
$this->view->setArguments($this->bulkFormData['arguments']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPageList($page) {
|
||||
$list = [];
|
||||
|
||||
$this->viewDataService->init($this->view, $this->view->getDisplay(), $this->bulkFormData['relationship_id']);
|
||||
|
||||
// Set exposed filters and pager parameters.
|
||||
if (!empty($this->bulkFormData['exposed_input'])) {
|
||||
$this->view->setExposedInput($this->bulkFormData['exposed_input']);
|
||||
}
|
||||
$this->view->setItemsPerPage($this->bulkFormData['batch_size']);
|
||||
$this->view->setCurrentPage($page);
|
||||
$this->view->build();
|
||||
|
||||
$offset = $this->bulkFormData['batch_size'] * $page;
|
||||
// If the view doesn't start from the first result,
|
||||
// move the offset.
|
||||
if ($pager_offset = $this->view->pager->getOffset()) {
|
||||
$offset += $pager_offset;
|
||||
}
|
||||
$this->view->query->setLimit($this->bulkFormData['batch_size']);
|
||||
$this->view->query->setOffset($offset);
|
||||
$this->moduleHandler->invokeAll('views_pre_execute', [$this->view]);
|
||||
$this->view->query->execute($this->view);
|
||||
|
||||
$base_field = $this->view->storage->get('base_field');
|
||||
foreach ($this->view->result as $row) {
|
||||
$entity = $this->viewDataService->getEntity($row);
|
||||
|
||||
// We don't need entity label here.
|
||||
$list[] = [
|
||||
$row->{$base_field},
|
||||
$entity->language()->getId(),
|
||||
$entity->getEntityTypeId(),
|
||||
$entity->id(),
|
||||
];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function populateQueue(array $list, array &$context = []) {
|
||||
// Determine batch size and offset.
|
||||
if (!empty($context)) {
|
||||
$batch_size = $this->bulkFormData['batch_size'];
|
||||
if (!isset($context['sandbox']['current_batch'])) {
|
||||
$context['sandbox']['current_batch'] = 0;
|
||||
}
|
||||
$current_batch = &$context['sandbox']['current_batch'];
|
||||
$offset = $current_batch * $batch_size;
|
||||
}
|
||||
else {
|
||||
$batch_size = 0;
|
||||
$current_batch = 0;
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
if ($batch_size) {
|
||||
$batch_list = array_slice($list, $offset, $batch_size);
|
||||
}
|
||||
else {
|
||||
$batch_list = $list;
|
||||
}
|
||||
|
||||
$base_field_values = [];
|
||||
foreach ($batch_list as $item) {
|
||||
$base_field_values[] = $item[0];
|
||||
}
|
||||
if (empty($base_field_values)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->view->setItemsPerPage(0);
|
||||
$this->view->setCurrentPage(0);
|
||||
$this->view->setOffset(0);
|
||||
$this->view->initHandlers();
|
||||
|
||||
// Remove all exposed filters so we don't have any default filter
|
||||
// values that could make the actual selection out of range.
|
||||
if (!empty($this->view->filter)) {
|
||||
foreach ($this->view->filter as $id => $filter) {
|
||||
if (!empty($filter->options['exposed'])) {
|
||||
unset($this->view->filter[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the view query.
|
||||
$this->view->build();
|
||||
|
||||
// Modify the view query: determine and apply the base field condition.
|
||||
$base_field = $this->view->storage->get('base_field');
|
||||
if (isset($this->view->query->fields[$base_field])) {
|
||||
$base_field_alias = $this->view->query->fields[$base_field]['table'] . '.' . $this->view->query->fields[$base_field]['alias'];
|
||||
}
|
||||
else {
|
||||
$base_field_alias = $base_field;
|
||||
}
|
||||
$this->view->query->addWhere(0, $base_field_alias, $base_field_values, 'IN');
|
||||
|
||||
// Rebuild the view query.
|
||||
$this->view->query->build($this->view);
|
||||
|
||||
// Execute the view.
|
||||
$this->moduleHandler->invokeAll('views_pre_execute', [$this->view]);
|
||||
$this->view->query->execute($this->view);
|
||||
|
||||
// Get entities.
|
||||
$this->viewDataService->init($this->view, $this->view->getDisplay(), $this->bulkFormData['relationship_id']);
|
||||
foreach ($this->view->result as $row_index => $row) {
|
||||
// This may return rows for all possible languages.
|
||||
// Check if the current language is on the list.
|
||||
$found = FALSE;
|
||||
$entity = $this->viewDataService->getEntity($row);
|
||||
foreach ($batch_list as $delta => $item) {
|
||||
if ($row->{$base_field} === $item[0] && $entity->language()->getId() === $item[1]) {
|
||||
$this->queue[] = $entity;
|
||||
$found = TRUE;
|
||||
unset($batch_list[$delta]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
unset($this->view->result[$row_index]);
|
||||
}
|
||||
}
|
||||
|
||||
// Extra processing when executed in a Batch API operation.
|
||||
if (!empty($context)) {
|
||||
if (!isset($context['sandbox']['total'])) {
|
||||
if (empty($list)) {
|
||||
$context['sandbox']['total'] = $this->viewDataService->getTotalResults();
|
||||
}
|
||||
else {
|
||||
$context['sandbox']['total'] = count($list);
|
||||
}
|
||||
}
|
||||
// Add batch size to context array for potential use in actions.
|
||||
$context['sandbox']['batch_size'] = $batch_size;
|
||||
$this->setActionContext($context);
|
||||
}
|
||||
|
||||
if ($batch_size) {
|
||||
$current_batch++;
|
||||
}
|
||||
|
||||
$this->setActionView();
|
||||
|
||||
return count($this->queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set action context if action method exists.
|
||||
*
|
||||
* @param array $context
|
||||
* The context to be set.
|
||||
*/
|
||||
protected function setActionContext(array $context) {
|
||||
if (isset($this->action) && method_exists($this->action, 'setContext')) {
|
||||
$this->action->setContext($context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current view object as the executed action parameter.
|
||||
*/
|
||||
protected function setActionView() {
|
||||
if (isset($this->action) && method_exists($this->action, 'setView')) {
|
||||
$this->action->setView($this->view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process() {
|
||||
$output = [];
|
||||
|
||||
// Check if all queue items are actually Drupal entities.
|
||||
foreach ($this->queue as $delta => $entity) {
|
||||
if (!($entity instanceof EntityInterface)) {
|
||||
$output[] = $this->t('Skipped');
|
||||
unset($this->queue[$delta]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check entity type for multi-type views like search_api index.
|
||||
$action_definition = $this->actionManager->getDefinition($this->bulkFormData['action_id']);
|
||||
if (!empty($action_definition['type'])) {
|
||||
foreach ($this->queue as $delta => $entity) {
|
||||
if ($entity->getEntityTypeId() !== $action_definition['type']) {
|
||||
$output[] = $this->t('Entity type not supported');
|
||||
unset($this->queue[$delta]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check access.
|
||||
foreach ($this->queue as $delta => $entity) {
|
||||
if (!$this->action->access($entity, $this->currentUser)) {
|
||||
$output[] = $this->t('Access denied');
|
||||
unset($this->queue[$delta]);
|
||||
}
|
||||
}
|
||||
|
||||
// Process queue.
|
||||
$results = $this->action->executeMultiple($this->queue);
|
||||
|
||||
// Populate output.
|
||||
if (empty($results)) {
|
||||
$count = count($this->queue);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$output[] = $this->bulkFormData['action_label'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($results as $result) {
|
||||
$output[] = $result;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeProcessing(array &$data, $view = NULL) {
|
||||
if ($data['batch']) {
|
||||
$batch = ViewsBulkOperationsBatch::getBatch($data);
|
||||
batch_set($batch);
|
||||
}
|
||||
else {
|
||||
$list = $data['list'];
|
||||
|
||||
// Populate and process queue.
|
||||
if (!$this->initialized) {
|
||||
$this->initialize($data, $view);
|
||||
}
|
||||
if (empty($list)) {
|
||||
$list = $this->getPageList(0);
|
||||
}
|
||||
if ($this->populateQueue($list)) {
|
||||
$batch_results = $this->process();
|
||||
}
|
||||
|
||||
$results = ['operations' => []];
|
||||
foreach ($batch_results as $result) {
|
||||
$results['operations'][] = (string) $result;
|
||||
}
|
||||
ViewsBulkOperationsBatch::finished(TRUE, $results, []);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Service;
|
||||
|
||||
/**
|
||||
* Defines Views Bulk Operations action processor.
|
||||
*/
|
||||
interface ViewsBulkOperationsActionProcessorInterface {
|
||||
|
||||
/**
|
||||
* Set values.
|
||||
*
|
||||
* @param array $view_data
|
||||
* Data concerning the view that will be processed.
|
||||
* @param mixed $view
|
||||
* The current view object or NULL.
|
||||
*/
|
||||
public function initialize(array $view_data, $view = NULL);
|
||||
|
||||
/**
|
||||
* Get full list of items from a specific view page.
|
||||
*
|
||||
* @param int $page
|
||||
* Results page number.
|
||||
*
|
||||
* @return array
|
||||
* Array of result data arrays.
|
||||
*/
|
||||
public function getPageList($page);
|
||||
|
||||
/**
|
||||
* Populate entity queue for processing.
|
||||
*
|
||||
* @param array $list
|
||||
* Array of selected view results.
|
||||
* @param array $context
|
||||
* Batch API context.
|
||||
*/
|
||||
public function populateQueue(array $list, array &$context = []);
|
||||
|
||||
/**
|
||||
* Process results.
|
||||
*/
|
||||
public function process();
|
||||
|
||||
/**
|
||||
* Helper function for processing results from view data.
|
||||
*
|
||||
* @param array $data
|
||||
* Data concerning the view that will be processed.
|
||||
* @param mixed $view
|
||||
* The current view object or NULL.
|
||||
*/
|
||||
public function executeProcessing(array &$data, $view = NULL);
|
||||
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Service;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\ResultRow;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\views_bulk_operations\ViewsBulkOperationsEvent;
|
||||
|
||||
/**
|
||||
* Gets Views data needed by VBO.
|
||||
*/
|
||||
class ViewsBulkOperationsViewData implements ViewsBulkOperationsViewDataInterface {
|
||||
|
||||
/**
|
||||
* Event dispatcher service.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* The current view.
|
||||
*
|
||||
* @var \Drupal\views\ViewExecutable
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* The realtionship ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relationship;
|
||||
|
||||
/**
|
||||
* Views data concerning the current view.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Entity type ids returned by this view.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityTypeIds;
|
||||
|
||||
/**
|
||||
* Entity getter data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityGetter;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
|
||||
* The event dispatcher service.
|
||||
*/
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher) {
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, $relationship) {
|
||||
$this->view = $view;
|
||||
$this->displayHandler = $display;
|
||||
$this->relationship = $relationship;
|
||||
|
||||
// Get view entity types and results fetcher callable.
|
||||
$event = new ViewsBulkOperationsEvent($this->getViewProvider(), $this->getData(), $view);
|
||||
$this->eventDispatcher->dispatch(ViewsBulkOperationsEvent::NAME, $event);
|
||||
$this->entityTypeIds = $event->getEntityTypeIds();
|
||||
$this->entityGetter = $event->getEntityGetter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityTypeIds() {
|
||||
return $this->entityTypeIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get data of the current view.
|
||||
*
|
||||
* @return array
|
||||
* Part of views data that refers to the current view.
|
||||
*/
|
||||
protected function getData() {
|
||||
if (!$this->data) {
|
||||
$viewsData = Views::viewsData();
|
||||
if (!empty($this->relationship) && $this->relationship != 'none') {
|
||||
$relationship = $this->displayHandler->getOption('relationships')[$this->relationship];
|
||||
$table_data = $viewsData->get($relationship['table']);
|
||||
$this->data = $viewsData->get($table_data[$relationship['field']]['relationship']['base']);
|
||||
}
|
||||
else {
|
||||
$this->data = $viewsData->get($this->view->storage->get('base_table'));
|
||||
}
|
||||
}
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewProvider() {
|
||||
$views_data = $this->getData();
|
||||
if (isset($views_data['table']['provider'])) {
|
||||
return $views_data['table']['provider'];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewBaseField() {
|
||||
$views_data = $this->getData();
|
||||
if (isset($views_data['table']['base']['field'])) {
|
||||
return $views_data['table']['base']['field'];
|
||||
}
|
||||
throw new \Exception('Unable to get base field for the view.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntity(ResultRow $row) {
|
||||
if (!empty($this->entityGetter['file'])) {
|
||||
require_once $this->entityGetter['file'];
|
||||
}
|
||||
if (is_callable($this->entityGetter['callable'])) {
|
||||
return call_user_func($this->entityGetter['callable'], $row, $this->relationship, $this->view);
|
||||
}
|
||||
else {
|
||||
if (is_array($this->entityGetter['callable'])) {
|
||||
if (is_object($this->entityGetter['callable'][0])) {
|
||||
$info = get_class($this->entityGetter['callable'][0]);
|
||||
}
|
||||
else {
|
||||
$info = $this->entityGetter['callable'][0];
|
||||
}
|
||||
$info .= '::' . $this->entityGetter['callable'][1];
|
||||
}
|
||||
else {
|
||||
$info = $this->entityGetter['callable'];
|
||||
}
|
||||
throw new \Exception(sprintf("Entity getter method %s doesn't exist.", $info));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total count of results on all pages.
|
||||
*
|
||||
* @return int
|
||||
* The total number of results this view displays.
|
||||
*/
|
||||
public function getTotalResults() {
|
||||
$total_results = NULL;
|
||||
if (!empty($this->view->pager->total_items)) {
|
||||
$total_results = $this->view->pager->total_items;
|
||||
}
|
||||
elseif (!empty($this->view->total_rows)) {
|
||||
$total_results = $this->view->total_rows;
|
||||
}
|
||||
|
||||
return $total_results;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityDefault(ResultRow $row, $relationship_id, ViewExecutable $view) {
|
||||
if ($relationship_id == 'none') {
|
||||
if (!empty($row->_entity)) {
|
||||
$entity = $row->_entity;
|
||||
}
|
||||
}
|
||||
elseif (isset($row->_relationship_entities[$relationship_id])) {
|
||||
$entity = $row->_relationship_entities[$relationship_id];
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Unexpected view result row structure.');
|
||||
}
|
||||
|
||||
if ($entity->isTranslatable()) {
|
||||
|
||||
// Try to find a field alias for the langcode.
|
||||
// Assumption: translatable entities always
|
||||
// have a langcode key.
|
||||
$language_field = '';
|
||||
$langcode_key = $entity->getEntityType()->getKey('langcode');
|
||||
$base_table = $view->storage->get('base_table');
|
||||
foreach ($view->query->fields as $field) {
|
||||
if (
|
||||
$field['field'] === $langcode_key && (
|
||||
empty($field['base_table']) ||
|
||||
$field['base_table'] === $base_table
|
||||
)
|
||||
) {
|
||||
$language_field = $field['alias'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$language_field) {
|
||||
$language_field = $langcode_key;
|
||||
}
|
||||
|
||||
if ($entity instanceof TranslatableInterface && isset($row->{$language_field})) {
|
||||
return $entity->getTranslation($row->{$language_field});
|
||||
}
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations\Service;
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\ResultRow;
|
||||
|
||||
/**
|
||||
* Defines view data service for Views Bulk Operations.
|
||||
*/
|
||||
interface ViewsBulkOperationsViewDataInterface {
|
||||
|
||||
/**
|
||||
* Initialize additional variables.
|
||||
*
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* The view object.
|
||||
* @param \Drupal\views\Plugin\views\display\DisplayPluginBase $display
|
||||
* The current display plugin.
|
||||
* @param string $relationship
|
||||
* Relationship ID.
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, $relationship);
|
||||
|
||||
/**
|
||||
* Get entity type IDs.
|
||||
*
|
||||
* @return array
|
||||
* Array of entity type IDs.
|
||||
*/
|
||||
public function getEntityTypeIds();
|
||||
|
||||
/**
|
||||
* Get view provider.
|
||||
*
|
||||
* @return string
|
||||
* View provider ID.
|
||||
*/
|
||||
public function getViewProvider();
|
||||
|
||||
/**
|
||||
* Get base field for the current view.
|
||||
*
|
||||
* @return sting
|
||||
* The base field name.
|
||||
*/
|
||||
public function getViewBaseField();
|
||||
|
||||
/**
|
||||
* Get entity from views row.
|
||||
*
|
||||
* @param \Drupal\views\ResultRow $row
|
||||
* Views row object.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* An entity object.
|
||||
*/
|
||||
public function getEntity(ResultRow $row);
|
||||
|
||||
/**
|
||||
* Get the total count of results on all pages.
|
||||
*
|
||||
* @return int
|
||||
* The total number of results this view displays.
|
||||
*/
|
||||
public function getTotalResults();
|
||||
|
||||
/**
|
||||
* The default entity getter function.
|
||||
*
|
||||
* Must work well with standard Drupal core entity views.
|
||||
*
|
||||
* @param \Drupal\views\ResultRow $row
|
||||
* Views result row.
|
||||
* @param string $relationship_id
|
||||
* Id of the view relationship.
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* The current view object.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\FieldableEntityInterface
|
||||
* The translated entity.
|
||||
*/
|
||||
public function getEntityDefault(ResultRow $row, $relationship_id, ViewExecutable $view);
|
||||
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines module Batch API methods.
|
||||
*/
|
||||
class ViewsBulkOperationsBatch {
|
||||
|
||||
/**
|
||||
* Translation function wrapper.
|
||||
*
|
||||
* @see \Drupal\Core\StringTranslation\TranslationInterface:translate()
|
||||
*/
|
||||
public static function t($string, array $args = [], array $options = []) {
|
||||
return \Drupal::translation()->translate($string, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set message function wrapper.
|
||||
*
|
||||
* @see \drupal_set_message()
|
||||
*/
|
||||
public static function message($message = NULL, $type = 'status', $repeat = TRUE) {
|
||||
drupal_set_message($message, $type, $repeat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of entities to process.
|
||||
*
|
||||
* Used in "all results" batch operation.
|
||||
*
|
||||
* @param array $data
|
||||
* Processed view data.
|
||||
* @param array $context
|
||||
* Batch context.
|
||||
*/
|
||||
public static function getList(array $data, array &$context) {
|
||||
// Initialize batch.
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['sandbox']['processed'] = 0;
|
||||
$context['sandbox']['page'] = 0;
|
||||
$context['results'] = $data;
|
||||
}
|
||||
|
||||
$actionProcessor = \Drupal::service('views_bulk_operations.processor');
|
||||
$actionProcessor->initialize($data);
|
||||
|
||||
// Populate queue.
|
||||
$list = $actionProcessor->getPageList($context['sandbox']['page']);
|
||||
$count = count($list);
|
||||
|
||||
if ($count) {
|
||||
foreach ($list as $item) {
|
||||
$context['results']['list'][] = $item;
|
||||
}
|
||||
|
||||
$context['sandbox']['page']++;
|
||||
$context['sandbox']['processed'] += $count;
|
||||
|
||||
// There may be cases where we don't know the total number of
|
||||
// results (e.g. mini pager with a search_api view)
|
||||
$context['finished'] = 0;
|
||||
if ($data['total_results']) {
|
||||
$context['finished'] = $context['sandbox']['processed'] / $data['total_results'];
|
||||
$context['message'] = static::t('Prepared @count of @total entities for processing.', [
|
||||
'@count' => $context['sandbox']['processed'],
|
||||
'@total' => $data['total_results'],
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$context['message'] = static::t('Prepared @count entities for processing.', [
|
||||
'@count' => $context['sandbox']['processed'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save generated list to user tempstore.
|
||||
*
|
||||
* @param bool $success
|
||||
* Was the process successfull?
|
||||
* @param array $results
|
||||
* Batch process results array.
|
||||
* @param array $operations
|
||||
* Performed operations array.
|
||||
*/
|
||||
public static function saveList($success, array $results, array $operations) {
|
||||
if ($success) {
|
||||
$results['redirect_url'] = $results['redirect_after_processing'];
|
||||
unset($results['redirect_after_processing']);
|
||||
$tempstore_factory = \Drupal::service('user.private_tempstore');
|
||||
$current_user = \Drupal::service('current_user');
|
||||
$tempstore_name = 'views_bulk_operations_' . $results['view_id'] . '_' . $results['display_id'];
|
||||
$results['prepopulated'] = TRUE;
|
||||
$tempstore_factory->get($tempstore_name)->set($current_user->id(), $results);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch operation callback.
|
||||
*
|
||||
* @param array $data
|
||||
* Processed view data.
|
||||
* @param array $context
|
||||
* Batch context.
|
||||
*/
|
||||
public static function operation(array $data, array &$context) {
|
||||
// Initialize batch.
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['sandbox']['processed'] = 0;
|
||||
$context['results']['operations'] = [];
|
||||
}
|
||||
|
||||
// Get entities to process.
|
||||
$actionProcessor = \Drupal::service('views_bulk_operations.processor');
|
||||
$actionProcessor->initialize($data);
|
||||
|
||||
// Do the processing.
|
||||
$count = $actionProcessor->populateQueue($data['list'], $context);
|
||||
if ($count) {
|
||||
$batch_results = $actionProcessor->process();
|
||||
if (!empty($batch_results)) {
|
||||
// Convert translatable markup to strings in order to allow
|
||||
// correct operation of array_count_values function.
|
||||
foreach ($batch_results as $result) {
|
||||
$context['results']['operations'][] = (string) $result;
|
||||
}
|
||||
}
|
||||
$context['sandbox']['processed'] += $count;
|
||||
|
||||
$context['finished'] = 0;
|
||||
// There may be cases where we don't know the total number of
|
||||
// results (probably all of them were already eliminated but
|
||||
// leaving this code just in case).
|
||||
if ($context['sandbox']['total']) {
|
||||
$context['finished'] = $context['sandbox']['processed'] / $context['sandbox']['total'];
|
||||
$context['message'] = static::t('Processed @count of @total entities.', [
|
||||
'@count' => $context['sandbox']['processed'],
|
||||
'@total' => $context['sandbox']['total'],
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$context['message'] = static::t('Processed @count entities.', [
|
||||
'@count' => $context['sandbox']['processed'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch finished callback.
|
||||
*
|
||||
* @param bool $success
|
||||
* Was the process successfull?
|
||||
* @param array $results
|
||||
* Batch process results array.
|
||||
* @param array $operations
|
||||
* Performed operations array.
|
||||
*/
|
||||
public static function finished($success, array $results, array $operations) {
|
||||
if ($success) {
|
||||
$operations = array_count_values($results['operations']);
|
||||
$details = [];
|
||||
foreach ($operations as $op => $count) {
|
||||
$details[] = $op . ' (' . $count . ')';
|
||||
}
|
||||
$message = static::t('Action processing results: @operations.', [
|
||||
'@operations' => implode(', ', $details),
|
||||
]);
|
||||
static::message($message);
|
||||
}
|
||||
else {
|
||||
$message = static::t('Finished with an error.');
|
||||
static::message($message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch builder function.
|
||||
*
|
||||
* @param array $view_data
|
||||
* Processed view data.
|
||||
*/
|
||||
public static function getBatch(array &$view_data) {
|
||||
$current_class = get_called_class();
|
||||
|
||||
// Prepopulate results.
|
||||
if (empty($view_data['list'])) {
|
||||
// Redirect this batch to the processing URL and set
|
||||
// previous redirect under a different key for later use.
|
||||
$view_data['redirect_after_processing'] = $view_data['redirect_url'];
|
||||
$view_data['redirect_url'] = Url::fromRoute('views_bulk_operations.execute_batch', [
|
||||
'view_id' => $view_data['view_id'],
|
||||
'display_id' => $view_data['display_id'],
|
||||
]);
|
||||
|
||||
$batch = [
|
||||
'title' => static::t('Prepopulating entity list for processing.'),
|
||||
'operations' => [
|
||||
[
|
||||
[$current_class, 'getList'],
|
||||
[$view_data],
|
||||
],
|
||||
],
|
||||
'progress_message' => static::t('Prepopulating, estimated time left: @estimate, elapsed: @elapsed.'),
|
||||
'finished' => [$current_class, 'saveList'],
|
||||
];
|
||||
}
|
||||
|
||||
// Execute action.
|
||||
else {
|
||||
$batch = [
|
||||
'title' => static::t('Performing @operation on selected entities.', ['@operation' => $view_data['action_label']]),
|
||||
'operations' => [
|
||||
[
|
||||
[$current_class, 'operation'],
|
||||
[$view_data],
|
||||
],
|
||||
],
|
||||
'progress_message' => static::t('Processing, estimated time left: @estimate, elapsed: @elapsed.'),
|
||||
'finished' => [$current_class, 'finished'],
|
||||
];
|
||||
}
|
||||
|
||||
return $batch;
|
||||
}
|
||||
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Defines Views Bulk Operations event type.
|
||||
*/
|
||||
class ViewsBulkOperationsEvent extends Event {
|
||||
|
||||
const NAME = 'views_bulk_operations.view_data';
|
||||
|
||||
/**
|
||||
* The provider of the current view.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $provider;
|
||||
|
||||
/**
|
||||
* The views data of the current view.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $viewData;
|
||||
|
||||
/**
|
||||
* The current view object.
|
||||
*
|
||||
* @var \Drupal\views\ViewExecutable
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* IDs of entity types returned by the view.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityTypeIds;
|
||||
|
||||
/**
|
||||
* Row entity getter information.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityGetter;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
* @param string $provider
|
||||
* The provider of the current view.
|
||||
* @param array $viewData
|
||||
* The views data of the current view.
|
||||
* @param \Drupal\views\ViewExecutable $view
|
||||
* The current view.
|
||||
*/
|
||||
public function __construct($provider, array $viewData, ViewExecutable $view) {
|
||||
$this->provider = $provider;
|
||||
$this->viewData = $viewData;
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view provider.
|
||||
*
|
||||
* @return string
|
||||
* The view provider
|
||||
*/
|
||||
public function getProvider() {
|
||||
return $this->provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view data.
|
||||
*
|
||||
* @return string
|
||||
* The current view data
|
||||
*/
|
||||
public function getViewData() {
|
||||
return $this->viewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current view.
|
||||
*
|
||||
* @return \Drupal\views\ViewExecutable
|
||||
* The current view object
|
||||
*/
|
||||
public function getView() {
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type IDs displayed by the current view.
|
||||
*
|
||||
* @return array
|
||||
* Entity type IDs.
|
||||
*/
|
||||
public function getEntityTypeIds() {
|
||||
return $this->entityTypeIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity getter callable.
|
||||
*
|
||||
* @return array
|
||||
* Entity getter information.
|
||||
*/
|
||||
public function getEntityGetter() {
|
||||
return $this->entityGetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entity type IDs.
|
||||
*
|
||||
* @param array $entityTypeIds
|
||||
* Entity type IDs.
|
||||
*/
|
||||
public function setEntityTypeIds(array $entityTypeIds) {
|
||||
$this->entityTypeIds = $entityTypeIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set entity getter callable.
|
||||
*
|
||||
* @param array $entityGetter
|
||||
* Entity getter information.
|
||||
*/
|
||||
public function setEntityGetter(array $entityGetter) {
|
||||
if (!isset($entityGetter['callable'])) {
|
||||
throw new \Exception('Views Bulk Operations entity getter callable is not defined.');
|
||||
}
|
||||
$this->entityGetter = $entityGetter;
|
||||
}
|
||||
|
||||
}
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\views_bulk_operations\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\views_bulk_operations\Plugin\views\field\ViewsBulkOperationsBulkForm
|
||||
* @group views_bulk_operations
|
||||
*/
|
||||
class ViewsBulkOperationsBulkFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'views',
|
||||
'views_bulk_operations',
|
||||
'views_bulk_operations_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create some nodes for testing.
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
|
||||
$this->testNodes = [];
|
||||
$time = REQUEST_TIME;
|
||||
for ($i = 0; $i < 15; $i++) {
|
||||
// Ensure nodes are sorted in the same order they are inserted in the
|
||||
// array.
|
||||
$time -= $i;
|
||||
$this->testNodes[] = $this->drupalCreateNode([
|
||||
'type' => 'page',
|
||||
'title' => 'Title ' . $i,
|
||||
'sticky' => FALSE,
|
||||
'created' => $time,
|
||||
'changed' => $time,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test a batch process.
|
||||
*
|
||||
* After checking if we're on a Batch API page,
|
||||
* the iterations are executed, the finished page is opened
|
||||
* and browser redirects to the final destination.
|
||||
*
|
||||
* NOTE: As of Drupal 8.4, functional test
|
||||
* automatically redirects user through all Batch API pages,
|
||||
* so this function is not longer needed.
|
||||
*/
|
||||
protected function assertBatchProcess() {
|
||||
// Get the current batch ID.
|
||||
$current_url = $this->getUrl();
|
||||
$q = substr($current_url, strrpos($current_url, '/') + 1);
|
||||
$this->assertEquals('batch?', substr($q, 0, 6), 'We are on a Batch API page.');
|
||||
|
||||
preg_match('#id=([0-9]+)#', $q, $matches);
|
||||
$batch_id = $matches[1];
|
||||
|
||||
// Proceed with the operations.
|
||||
// Assumption: all operations will be completed within a single request.
|
||||
// TODO: modify code to include an option when the assumption is false.
|
||||
do {
|
||||
$this->drupalGet('batch', [
|
||||
'query' => [
|
||||
'id' => $batch_id,
|
||||
'op' => 'do_nojs',
|
||||
],
|
||||
]);
|
||||
} while (FALSE);
|
||||
|
||||
// Get the finished page.
|
||||
$this->drupalGet('batch', [
|
||||
'query' => [
|
||||
'id' => $batch_id,
|
||||
'op' => 'finished',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the VBO bulk form with simple test action.
|
||||
*/
|
||||
public function testViewsBulkOperationsBulkFormSimple() {
|
||||
|
||||
$assertSession = $this->assertSession();
|
||||
|
||||
$this->drupalGet('views-bulk-operations-test');
|
||||
|
||||
// Test that the views edit header appears first.
|
||||
$first_form_element = $this->xpath('//form/div[1][@id = :id]', [':id' => 'edit-header']);
|
||||
$this->assertTrue($first_form_element, 'The views form edit header appears first.');
|
||||
|
||||
// Make sure a checkbox appears on all rows.
|
||||
$edit = [];
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$assertSession->fieldExists('edit-views-bulk-operations-bulk-form-' . $i, NULL, format_string('The checkbox on row @row appears.', ['@row' => $i]));
|
||||
}
|
||||
|
||||
// The advanced action should not be shown on the form - no permission.
|
||||
$this->assertTrue(empty($this->cssSelect('select[name=views_bulk_operations_advanced_test_action]')), t('Advanced action is not selectable.'));
|
||||
|
||||
// Log in as a user with 'edit any page content' permission
|
||||
// to have access to perform the test operation.
|
||||
$admin_user = $this->drupalCreateUser(['edit any page content']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Execute the simple test action.
|
||||
$edit = [];
|
||||
$selected = [0, 2, 3];
|
||||
foreach ($selected as $index) {
|
||||
$edit["views_bulk_operations_bulk_form[$index]"] = TRUE;
|
||||
}
|
||||
|
||||
// Tests: actions as buttons, label override.
|
||||
$this->drupalPostForm('views-bulk-operations-test', $edit, t('Simple test action'));
|
||||
|
||||
$testViewConfig = \Drupal::service('config.factory')->get('views.view.views_bulk_operations_test');
|
||||
$configData = $testViewConfig->getRawData();
|
||||
$preconfig_setting = $configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['preconfiguration']['views_bulk_operations_simple_test_action']['preconfig'];
|
||||
|
||||
foreach ($selected as $index) {
|
||||
$assertSession->pageTextContains(
|
||||
sprintf('Test action (preconfig: %s, label: %s)',
|
||||
$preconfig_setting,
|
||||
$this->testNodes[$index]->label()
|
||||
),
|
||||
sprintf('Action has been executed on node "%s".',
|
||||
$this->testNodes[$index]->label()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Test the select all functionality.
|
||||
$edit = [
|
||||
'select_all' => 1,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Simple test action'));
|
||||
|
||||
$assertSession->pageTextContains(
|
||||
sprintf('Action processing results: Test (%d).', count($this->testNodes)),
|
||||
sprintf('Action has been executed on %d nodes.', count($this->testNodes))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* More advanced test.
|
||||
*
|
||||
* Uses the ViewsBulkOperationsAdvancedTestAction.
|
||||
*/
|
||||
public function testViewsBulkOperationsBulkFormAdvanced() {
|
||||
|
||||
$assertSession = $this->assertSession();
|
||||
|
||||
// Log in as a user with 'edit any page content' permission
|
||||
// to have access to perform the test operation.
|
||||
$admin_user = $this->drupalCreateUser(['edit any page content', 'execute advanced test action']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// First execute the simple action to test
|
||||
// the ViewsBulkOperationsController class.
|
||||
$edit = [
|
||||
'action' => 'views_bulk_operations_simple_test_action',
|
||||
];
|
||||
$selected = [0, 2];
|
||||
foreach ($selected as $index) {
|
||||
$edit["views_bulk_operations_bulk_form[$index]"] = TRUE;
|
||||
}
|
||||
$this->drupalPostForm('views-bulk-operations-test-advanced', $edit, t('Apply to selected items'));
|
||||
|
||||
$assertSession->pageTextContains(
|
||||
sprintf('Action processing results: Test (%d).', count($selected)),
|
||||
sprintf('Action has been executed on %d nodes.', count($selected))
|
||||
);
|
||||
|
||||
// Execute the advanced test action.
|
||||
$edit = [
|
||||
'action' => 'views_bulk_operations_advanced_test_action',
|
||||
];
|
||||
$selected = [0, 1, 3];
|
||||
foreach ($selected as $index) {
|
||||
$edit["views_bulk_operations_bulk_form[$index]"] = TRUE;
|
||||
}
|
||||
$this->drupalPostForm('views-bulk-operations-test-advanced', $edit, t('Apply to selected items'));
|
||||
|
||||
// Check if the configuration form is open and contains the
|
||||
// test_config field.
|
||||
$assertSession->fieldExists('edit-test-config', NULL, 'The configuration field appears.');
|
||||
|
||||
// Check if the configuration form contains selected entity labels.
|
||||
// NOTE: The view pager has an offset set on this view, so checkbox
|
||||
// indexes are not equal to test nodes array keys. Hence the $index + 1.
|
||||
foreach ($selected as $index) {
|
||||
$assertSession->pageTextContains($this->testNodes[$index + 1]->label());
|
||||
}
|
||||
|
||||
$config_value = 'test value';
|
||||
$edit = [
|
||||
'test_config' => $config_value,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply'));
|
||||
|
||||
// Execute action by posting the confirmation form
|
||||
// (also tests if the submit button exists on the page).
|
||||
$this->drupalPostForm(NULL, [], t('Execute action'));
|
||||
|
||||
// If all went well and Batch API did its job,
|
||||
// the next page should display results.
|
||||
$testViewConfig = \Drupal::service('config.factory')->get('views.view.views_bulk_operations_test_advanced');
|
||||
$configData = $testViewConfig->getRawData();
|
||||
$preconfig_setting = $configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['preconfiguration']['views_bulk_operations_advanced_test_action']['test_preconfig'];
|
||||
|
||||
// NOTE: The view pager has an offset set on this view, so checkbox
|
||||
// indexes are not equal to test nodes array keys. Hence the $index + 1.
|
||||
foreach ($selected as $index) {
|
||||
$assertSession->pageTextContains(sprintf('Test action (preconfig: %s, config: %s, label: %s)',
|
||||
$preconfig_setting,
|
||||
$config_value,
|
||||
$this->testNodes[$index + 1]->label()
|
||||
));
|
||||
}
|
||||
|
||||
// Test the select all functionality with batching and entity
|
||||
// property changes affecting view query results.
|
||||
$edit = [
|
||||
'action' => 'views_bulk_operations_advanced_test_action',
|
||||
'select_all' => 1,
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
|
||||
$this->drupalPostForm(NULL, ['test_config' => 'unpublish'], t('Apply'));
|
||||
$this->drupalPostForm(NULL, [], t('Execute action'));
|
||||
// Again, take offset into account (-1).
|
||||
$assertSession->pageTextContains(
|
||||
sprintf('Action processing results: Test (%d).', (count($this->testNodes) - 1)),
|
||||
sprintf('Action has been executed on all %d nodes.', (count($this->testNodes) - 1))
|
||||
);
|
||||
$this->assertTrue(empty($this->cssSelect('table.views-table tr')), t("The view doesn't show any results."));
|
||||
}
|
||||
|
||||
/**
|
||||
* View and context passing test.
|
||||
*
|
||||
* Uses the ViewsBulkOperationsPassTestAction.
|
||||
*/
|
||||
public function testViewsBulkOperationsBulkFormPassing() {
|
||||
|
||||
$assertSession = $this->assertSession();
|
||||
|
||||
// Log in as a user with 'administer content' permission
|
||||
// to have access to perform the test operation.
|
||||
$admin_user = $this->drupalCreateUser(['bypass node access']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Test with all selected and specific selection, with batch
|
||||
// size greater than items per page and lower than items per page,
|
||||
// using Batch API process and without it.
|
||||
$cases = [
|
||||
['batch' => FALSE, 'selection' => TRUE, 'page' => 1],
|
||||
['batch' => FALSE, 'selection' => FALSE],
|
||||
['batch' => TRUE, 'batch_size' => 3, 'selection' => TRUE, 'page' => 1],
|
||||
['batch' => TRUE, 'batch_size' => 7, 'selection' => TRUE],
|
||||
['batch' => TRUE, 'batch_size' => 3, 'selection' => FALSE],
|
||||
['batch' => TRUE, 'batch_size' => 7, 'selection' => FALSE],
|
||||
];
|
||||
|
||||
// Custom selection.
|
||||
$selected = [0, 1, 3, 4];
|
||||
|
||||
$testViewConfig = \Drupal::service('config.factory')->getEditable('views.view.views_bulk_operations_test_advanced');
|
||||
$configData = $testViewConfig->getRawData();
|
||||
$configData['display']['default']['display_options']['pager']['options']['items_per_page'] = 5;
|
||||
|
||||
foreach ($cases as $case) {
|
||||
|
||||
// Populate form values.
|
||||
$edit = [
|
||||
'action' => 'views_bulk_operations_passing_test_action',
|
||||
];
|
||||
if ($case['selection']) {
|
||||
foreach ($selected as $index) {
|
||||
$edit["views_bulk_operations_bulk_form[$index]"] = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$edit['select_all'] = 1;
|
||||
}
|
||||
|
||||
// Update test view configuration.
|
||||
$configData['display']['default']['display_options']['pager']['options']['items_per_page']++;
|
||||
$configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['batch'] = $case['batch'];
|
||||
if (isset($case['batch_size'])) {
|
||||
$configData['display']['default']['display_options']['fields']['views_bulk_operations_bulk_form']['batch_size'] = $case['batch_size'];
|
||||
}
|
||||
$testViewConfig->setData($configData);
|
||||
$testViewConfig->save();
|
||||
|
||||
$options = [];
|
||||
if (!empty($case['page'])) {
|
||||
$options['query'] = ['page' => $case['page']];
|
||||
}
|
||||
|
||||
$this->drupalGet('views-bulk-operations-test-advanced', $options);
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
|
||||
|
||||
// On batch-enabled processes check if provided context data is correct.
|
||||
if ($case['batch']) {
|
||||
if ($case['selection']) {
|
||||
$total = count($selected);
|
||||
}
|
||||
else {
|
||||
// Again, include offset.
|
||||
$total = count($this->testNodes) - 1;
|
||||
}
|
||||
$n_batches = ceil($total / $case['batch_size']);
|
||||
|
||||
for ($i = 0; $i < $n_batches; $i++) {
|
||||
$processed = $i * $case['batch_size'];
|
||||
$assertSession->pageTextContains(sprintf(
|
||||
'Processed %s of %s.',
|
||||
$processed,
|
||||
$total
|
||||
), 'The correct processed info message appears.');
|
||||
}
|
||||
}
|
||||
|
||||
// Passed view integrity check.
|
||||
$assertSession->pageTextContains('Passed view results match the entity queue.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\views_bulk_operations\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\simpletest\NodeCreationTrait;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\views_bulk_operations\Service\ViewsBulkOperationsViewData
|
||||
* @group views_bulk_operations
|
||||
*/
|
||||
class ViewsBulkOperationsDataServiceTest extends KernelTestBase {
|
||||
|
||||
use NodeCreationTrait {
|
||||
getNodeByTitle as drupalGetNodeByTitle;
|
||||
createNode as drupalCreateNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test nodes data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $testNodesData;
|
||||
|
||||
/**
|
||||
* The expected total number of view results.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $resultsCount = 0;
|
||||
|
||||
/**
|
||||
* The tested service.
|
||||
*
|
||||
* @var \Drupal\views_bulk_operations\Service\ViewsbulkOperationsViewData
|
||||
*/
|
||||
protected $vboDataService;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'user',
|
||||
'node',
|
||||
'field',
|
||||
'content_translation',
|
||||
'views_bulk_operations',
|
||||
'views_bulk_operations_test',
|
||||
'views',
|
||||
'filter',
|
||||
'language',
|
||||
'text',
|
||||
'action',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installSchema('node', 'node_access');
|
||||
$this->installSchema('system', 'sequences');
|
||||
$this->installSchema('system', 'key_value_expire');
|
||||
|
||||
$user = User::create();
|
||||
$user->setPassword('password');
|
||||
$user->enforceIsNew();
|
||||
$user->setEmail('email');
|
||||
$user->setUsername('user_name');
|
||||
$user->save();
|
||||
user_login_finalize($user);
|
||||
|
||||
$this->installConfig([
|
||||
'system',
|
||||
'filter',
|
||||
'views_bulk_operations_test',
|
||||
'language',
|
||||
]);
|
||||
|
||||
$languages = ['pl', 'es', 'it', 'fr', 'de'];
|
||||
$count_languages = count($languages);
|
||||
for ($i = 0; $i < $count_languages; $i++) {
|
||||
$language = ConfigurableLanguage::createFromLangcode($languages[$i]);
|
||||
$language->save();
|
||||
}
|
||||
|
||||
$type = NodeType::create([
|
||||
'type' => 'page',
|
||||
'name' => 'page',
|
||||
]);
|
||||
$type->save();
|
||||
|
||||
\Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE);
|
||||
\Drupal::entityManager()->clearCachedDefinitions();
|
||||
|
||||
// Create some test nodes with translations.
|
||||
$this->testNodesData = [];
|
||||
$time = REQUEST_TIME;
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$time -= $i;
|
||||
$title = 'Title ' . $i;
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => 'page',
|
||||
'title' => $title,
|
||||
'sticky' => FALSE,
|
||||
'created' => $time,
|
||||
'changed' => $time,
|
||||
]);
|
||||
$this->testNodesData[$node->id()]['en'] = $title;
|
||||
$this->resultsCount++;
|
||||
|
||||
$langcode = $languages[rand(0, $count_languages - 1)];
|
||||
$title = 'Translated title ' . $langcode . ' ' . $i;
|
||||
$translation = $node->addTranslation($langcode, [
|
||||
'title' => $title,
|
||||
]);
|
||||
$translation->save();
|
||||
$this->testNodesData[$node->id()][$langcode] = $title;
|
||||
$this->resultsCount++;
|
||||
}
|
||||
|
||||
// Initialize the tested service.
|
||||
$this->vboDataService = $this->container->get('views_bulk_operations.data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getEntityDefault() method.
|
||||
*
|
||||
* @covers ::getEntityDefault
|
||||
*/
|
||||
public function testViewsbulkOperationsViewDataEntityGetter() {
|
||||
$this->assertTrue(TRUE);
|
||||
// Initialize and execute the test view with all items displayed.
|
||||
$view = Views::getView('views_bulk_operations_test');
|
||||
$view->setDisplay('page_1');
|
||||
$view->setItemsPerPage(0);
|
||||
$view->setCurrentPage(0);
|
||||
$view->execute();
|
||||
|
||||
$test_data = $this->testNodesData;
|
||||
foreach ($view->result as $row) {
|
||||
$entity = $this->vboDataService->getEntityDefault($row, 'none', $view);
|
||||
|
||||
$expected_label = $test_data[$entity->id()][$entity->language()->getId()];
|
||||
|
||||
$this->assertEquals($expected_label, $entity->label(), 'Title matches');
|
||||
if ($expected_label === $entity->label()) {
|
||||
unset($test_data[$entity->id()][$entity->language()->getId()]);
|
||||
if (empty($test_data[$entity->id()])) {
|
||||
unset($test_data[$entity->id()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEmpty($test_data, 'All created entities and their translations were returned.');
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\views_bulk_operations\Unit;
|
||||
|
||||
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;
|
||||
|
||||
/**
|
||||
* Override some class methods for proper testing.
|
||||
*/
|
||||
class TestViewsBulkOperationsBatch extends ViewsBulkOperationsBatch {
|
||||
|
||||
/**
|
||||
* Override t method.
|
||||
*/
|
||||
public static function t($string, array $args = [], array $options = []) {
|
||||
return strtr($string, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override message method.
|
||||
*/
|
||||
public static function message($message = NULL, $type = 'status', $repeat = TRUE) {
|
||||
static $storage;
|
||||
if (isset($storage)) {
|
||||
$output = $storage;
|
||||
$storage = NULL;
|
||||
return $output;
|
||||
}
|
||||
else {
|
||||
$storage = (string) $message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\views_bulk_operations\Unit;
|
||||
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\views_bulk_operations\ViewsBulkOperationsBatch
|
||||
* @group views_bulk_operations
|
||||
*/
|
||||
class ViewsBulkOperationsBatchTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->container = new ContainerBuilder();
|
||||
\Drupal::setContainer($this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stub ViewsBulkOperationsActionProcessor that returns dummy data.
|
||||
*
|
||||
* @return \Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessor
|
||||
* A mocked action processor.
|
||||
*/
|
||||
public function getViewsBulkOperationsActionProcessorStub($entities_count) {
|
||||
$actionProcessor = $this->getMockBuilder('Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessor')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$actionProcessor->expects($this->any())
|
||||
->method('getEntity')
|
||||
->will($this->returnValue(new \stdClass()));
|
||||
|
||||
$actionProcessor->expects($this->any())
|
||||
->method('populateQueue')
|
||||
->will($this->returnValue($entities_count));
|
||||
|
||||
$actionProcessor->expects($this->any())
|
||||
->method('process')
|
||||
->will($this->returnCallback(function () use ($entities_count) {
|
||||
$return = [];
|
||||
for ($i = 0; $i < $entities_count; $i++) {
|
||||
$return[] = 'Some action';
|
||||
}
|
||||
return $return;
|
||||
}));
|
||||
|
||||
return $actionProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getBatch() method.
|
||||
*
|
||||
* @covers ::getBatch
|
||||
*/
|
||||
public function testGetBatch() {
|
||||
$data = [
|
||||
'list' => [[0, 'en', 'node', 1]],
|
||||
'some_data' => [],
|
||||
'action_label' => '',
|
||||
];
|
||||
$batch = TestViewsBulkOperationsBatch::getBatch($data);
|
||||
$this->assertArrayHasKey('title', $batch);
|
||||
$this->assertArrayHasKey('operations', $batch);
|
||||
$this->assertArrayHasKey('finished', $batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the operation() method.
|
||||
*
|
||||
* @covers ::operation
|
||||
*/
|
||||
public function testOperation() {
|
||||
$batch_size = 2;
|
||||
$entities_count = 10;
|
||||
|
||||
$this->container->set('views_bulk_operations.processor', $this->getViewsBulkOperationsActionProcessorStub($batch_size));
|
||||
|
||||
$view = new View(['id' => 'test_view'], 'view');
|
||||
$view_storage = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityStorage')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$view_storage->expects($this->any())
|
||||
->method('load')
|
||||
->with('test_view')
|
||||
->will($this->returnValue($view));
|
||||
|
||||
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
|
||||
$entity_manager->expects($this->any())
|
||||
->method('getStorage')
|
||||
->with('view')
|
||||
->will($this->returnValue($view_storage));
|
||||
$this->container->set('entity.manager', $entity_manager);
|
||||
|
||||
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$executable->result = [];
|
||||
|
||||
// We set only $batch_size entities because
|
||||
// $view->setItemsPerPage will not have effect.
|
||||
for ($i = 0; $i < $batch_size; $i++) {
|
||||
$row = new \stdClass();
|
||||
$row->_entity = new \stdClass();
|
||||
$executable->result[] = $row;
|
||||
}
|
||||
|
||||
$viewExecutableFactory = $this->getMockBuilder('Drupal\views\ViewExecutableFactory')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$viewExecutableFactory->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->returnValue($executable));
|
||||
$this->container->set('views.executable', $viewExecutableFactory);
|
||||
|
||||
$data = [
|
||||
'view_id' => 'test_view',
|
||||
'display_id' => 'test_display',
|
||||
'batch_size' => $batch_size,
|
||||
'list' => [],
|
||||
];
|
||||
$context = [
|
||||
'sandbox' => [
|
||||
'processed' => 0,
|
||||
'total' => $entities_count,
|
||||
],
|
||||
];
|
||||
|
||||
TestViewsBulkOperationsBatch::operation($data, $context);
|
||||
|
||||
$this->assertEquals(count($context['results']['operations']), $batch_size);
|
||||
$this->assertEquals($context['finished'], ($batch_size / $entities_count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the finished() method.
|
||||
*
|
||||
* @covers ::finished
|
||||
*/
|
||||
public function testFinished() {
|
||||
$results = ['operations' => ['Some operation', 'Some operation']];
|
||||
TestViewsBulkOperationsBatch::finished(TRUE, $results, []);
|
||||
$this->assertEquals(TestViewsBulkOperationsBatch::message(), 'Action processing results: Some operation (2).');
|
||||
|
||||
$results = ['operations' => ['Some operation1', 'Some operation2']];
|
||||
TestViewsBulkOperationsBatch::finished(TRUE, $results, []);
|
||||
$this->assertEquals(TestViewsBulkOperationsBatch::message(), 'Action processing results: Some operation1 (1), Some operation2 (1).');
|
||||
}
|
||||
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
- views_bulk_operations
|
||||
id: views_bulk_operations_test
|
||||
label: 'Views Bulk Operations Test'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 4
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
quantity: 9
|
||||
style:
|
||||
type: table
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Title
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
views_bulk_operations_bulk_form:
|
||||
id: views_bulk_operations_bulk_form
|
||||
table: views
|
||||
field: views_bulk_operations_bulk_form
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Views bulk operations'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
batch: false
|
||||
batch_size: 10
|
||||
form_step: true
|
||||
buttons: true
|
||||
action_title: Action
|
||||
selected_actions:
|
||||
views_bulk_operations_simple_test_action: views_bulk_operations_simple_test_action
|
||||
views_bulk_operations_advanced_test_action: views_bulk_operations_advanced_test_action
|
||||
preconfiguration:
|
||||
views_bulk_operations_simple_test_action:
|
||||
label_override: 'Simple test action'
|
||||
preconfig: 'Test setting'
|
||||
views_bulk_operations_advanced_test_action:
|
||||
label_override: ''
|
||||
preconfig: 'Test setting'
|
||||
plugin_id: views_bulk_operations_bulk_form
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: 'Views Bulk Operations Test'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: views-bulk-operations-test
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
- views_bulk_operations
|
||||
id: views_bulk_operations_test_advanced
|
||||
label: 'Views Bulk Operations Advanced Test'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
cache:
|
||||
type: none
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 4
|
||||
offset: 1
|
||||
id: 0
|
||||
total_pages: null
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
style:
|
||||
type: table
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Title
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
views_bulk_operations_bulk_form:
|
||||
id: views_bulk_operations_bulk_form
|
||||
table: views
|
||||
field: views_bulk_operations_bulk_form
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Views bulk operations'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
batch: true
|
||||
batch_size: 2
|
||||
form_step: true
|
||||
buttons: false
|
||||
action_title: Action
|
||||
selected_actions:
|
||||
views_bulk_operations_simple_test_action: views_bulk_operations_simple_test_action
|
||||
views_bulk_operations_advanced_test_action: views_bulk_operations_advanced_test_action
|
||||
views_bulk_operations_passing_test_action: views_bulk_operations_passing_test_action
|
||||
preconfiguration:
|
||||
views_bulk_operations_simple_test_action:
|
||||
label_override: 'Simple test action'
|
||||
preconfig: 'Test setting'
|
||||
views_bulk_operations_advanced_test_action:
|
||||
label_override: ''
|
||||
test_preconfig: 'Test setting'
|
||||
views_bulk_operations_passing_test_action:
|
||||
label_override: ''
|
||||
plugin_id: views_bulk_operations_bulk_form
|
||||
filters:
|
||||
status:
|
||||
id: status
|
||||
table: node_field_data
|
||||
field: status
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
plugin_id: boolean
|
||||
sorts: { }
|
||||
title: 'Views Bulk Operations Test'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: views-bulk-operations-test-advanced
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations_test\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsPreconfigurationInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Action for test purposes only.
|
||||
*
|
||||
* @Action(
|
||||
* id = "views_bulk_operations_advanced_test_action",
|
||||
* label = @Translation("VBO advanced test action"),
|
||||
* type = "",
|
||||
* confirm = TRUE,
|
||||
* requirements = {
|
||||
* "_permission" = "execute advanced test action",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class ViewsBulkOperationsAdvancedTestAction extends ViewsBulkOperationsActionBase implements ViewsBulkOperationsPreconfigurationInterface, PluginFormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
// Check if $this->view is an instance of ViewsExecutable.
|
||||
if (!($this->view instanceof ViewExecutable)) {
|
||||
throw new \Exception('View passed to action object is not an instance of \Drupal\views\ViewExecutable.');
|
||||
}
|
||||
|
||||
// Check if context array has been passed to the action.
|
||||
if (empty($this->context)) {
|
||||
throw new \Exception('Context array empty in action object.');
|
||||
}
|
||||
|
||||
drupal_set_message(sprintf('Test action (preconfig: %s, config: %s, label: %s)',
|
||||
$this->configuration['test_preconfig'],
|
||||
$this->configuration['test_config'],
|
||||
$entity->label()
|
||||
));
|
||||
|
||||
// Unpublish entity.
|
||||
if ($this->configuration['test_config'] === 'unpublish') {
|
||||
$entity->status = NODE_NOT_PUBLISHED;
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
return 'Test';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildPreConfigurationForm(array $element, array $values, FormStateInterface $form_state) {
|
||||
$element['test_preconfig'] = [
|
||||
'#title' => $this->t('Preliminary configuration'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($values['preconfig']) ? $values['preconfig'] : '',
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration form builder.
|
||||
*
|
||||
* @param array $form
|
||||
* Form array.
|
||||
* @param Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state object.
|
||||
*
|
||||
* @return array
|
||||
* The configuration form.
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['test_config'] = [
|
||||
'#title' => t('Config'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $form_state->getValue('config'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('update', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations_test\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Action for test purposes only.
|
||||
*
|
||||
* @Action(
|
||||
* id = "views_bulk_operations_passing_test_action",
|
||||
* label = @Translation("VBO parameters passing test action"),
|
||||
* type = "node",
|
||||
* )
|
||||
*/
|
||||
class ViewsBulkOperationsPassTestAction extends ViewsBulkOperationsActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $nodes) {
|
||||
if (!empty($this->context['sandbox'])) {
|
||||
drupal_set_message(sprintf(
|
||||
'Processed %s of %s.',
|
||||
$this->context['sandbox']['processed'],
|
||||
$this->context['sandbox']['total']
|
||||
));
|
||||
}
|
||||
|
||||
// Check if the passed view result rows contain the correct nodes.
|
||||
if (empty($this->context['sandbox']['result_pass_error'])) {
|
||||
$this->view->result = array_values($this->view->result);
|
||||
foreach ($nodes as $index => $node) {
|
||||
$result_node = $this->view->result[$index]->_entity;
|
||||
if (
|
||||
$node->id() !== $result_node->id() ||
|
||||
$node->label() !== $result_node->label()
|
||||
) {
|
||||
$this->context['sandbox']['result_pass_error'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$batch_size = isset($this->context['sandbox']['batch_size']) ? $this->context['sandbox']['batch_size'] : 0;
|
||||
$total = isset($this->context['sandbox']['total']) ? $this->context['sandbox']['total'] : 0;
|
||||
$processed = isset($this->context['sandbox']['processed']) ? $this->context['sandbox']['processed'] : 0;
|
||||
|
||||
// On last batch display message if passed rows match.
|
||||
if ($processed + $batch_size >= $total) {
|
||||
if (empty($this->context['sandbox']['result_pass_error'])) {
|
||||
drupal_set_message('Passed view results match the entity queue.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$this->executeMultiple([$entity]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('update', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\views_bulk_operations_test\Plugin\Action;
|
||||
|
||||
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Action for test purposes only.
|
||||
*
|
||||
* @Action(
|
||||
* id = "views_bulk_operations_simple_test_action",
|
||||
* label = @Translation("VBO simple test action"),
|
||||
* type = "node"
|
||||
* )
|
||||
*/
|
||||
class ViewsBulkOperationsSimpleTestAction extends ViewsBulkOperationsActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
drupal_set_message(sprintf('Test action (preconfig: %s, label: %s)',
|
||||
$this->configuration['preconfig'],
|
||||
$entity->label()
|
||||
));
|
||||
return 'Test';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('update', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
name: 'Views Bulk Operations test'
|
||||
type: module
|
||||
description: 'Support module for testing Views Bulk Operations.'
|
||||
package: Testing
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:views_bulk_operations
|
||||
- drupal:node
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-02-08
|
||||
version: '8.x-2.0-rc4+1-dev'
|
||||
core: '8.x'
|
||||
project: 'views_bulk_operations'
|
||||
datestamp: 1518076986
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
execute advanced test action:
|
||||
title: 'Execute advanced test action'
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains code providing drush commands functionality.
|
||||
*/
|
||||
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views_bulk_operations\ViewsBulkOperationsBatch;
|
||||
|
||||
/**
|
||||
* Implements hook_drush_command().
|
||||
*/
|
||||
function views_bulk_operations_drush_command() {
|
||||
return [
|
||||
'views-bulk-operations-execute' => [
|
||||
'description' => 'Execute an action on all results of the given view.',
|
||||
'aliases' => ['vbo-execute', 'vbo-exec'],
|
||||
'arguments' => [
|
||||
'view_id' => 'The ID of the view to use',
|
||||
'action_id' => 'The ID of the action to execute',
|
||||
],
|
||||
'options' => [
|
||||
'display-id' => 'ID of the display to use (default: default)',
|
||||
'args' => 'View arguments (slash is a delimeter, default: none)',
|
||||
'exposed' => 'Exposed filters (query string format)',
|
||||
'batch-size' => 'Processing batch size (default: 100)',
|
||||
'config' => 'Action configuration (query string format)',
|
||||
'debug' => 'Include additional debugging information.',
|
||||
],
|
||||
'examples' => [
|
||||
'drush vbo-execute some_view some_action --user=1' => 'Execute some action on some view as the superuser.',
|
||||
'drush vbo-execute some_view some_action --args=arg1/arg2 --batch-size=50' => 'Execute some action on some view with arg1 and arg2 as view arguments and 50 entities processed per batch.',
|
||||
'drush vbo-execute some_view some_action --config="key1=value1&key2=value2"' => 'Execute some action on some view with action configuration set.',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set / get timer.
|
||||
*
|
||||
* @param bool $debug
|
||||
* Should the function do anything at all?
|
||||
* @param string $id
|
||||
* ID of a specific timer span.
|
||||
*
|
||||
* @return mixed
|
||||
* NULL or value of a specific timer if set.
|
||||
*/
|
||||
function _views_bulk_operations_timer($debug = TRUE, $id = NULL) {
|
||||
if (!$debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
static $timers = [];
|
||||
|
||||
if (!isset($id)) {
|
||||
$timers['start'] = microtime(TRUE);
|
||||
}
|
||||
else {
|
||||
if (isset($timers[$id])) {
|
||||
end($timers);
|
||||
do {
|
||||
if (key($timers) === $id) {
|
||||
return round((current($timers) - prev($timers)) * 1000, 3);
|
||||
}
|
||||
else {
|
||||
$result = prev($timers);
|
||||
}
|
||||
} while ($result);
|
||||
}
|
||||
else {
|
||||
$timers[$id] = microtime(TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The vbo-exec command executtion function.
|
||||
*
|
||||
* @param string $view_id
|
||||
* The ID of the view to use.
|
||||
* @param string $action_id
|
||||
* The ID of the action to execute.
|
||||
*/
|
||||
function drush_views_bulk_operations_execute($view_id, $action_id) {
|
||||
|
||||
$debug = drush_get_option('debug', FALSE);
|
||||
_views_bulk_operations_timer($debug);
|
||||
|
||||
// Prepare parameters.
|
||||
$arguments = drush_get_option('args', FALSE);
|
||||
if ($arguments) {
|
||||
$arguments = explode('/', $arguments);
|
||||
}
|
||||
|
||||
$qs_config = [
|
||||
'config' => [],
|
||||
'exposed' => [],
|
||||
];
|
||||
foreach ($qs_config as $name => $value) {
|
||||
$config_data = drush_get_option($name, []);
|
||||
if (!empty($config_data)) {
|
||||
parse_str($config_data, $qs_config[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
$vbo_data = [
|
||||
'list' => [],
|
||||
'view_id' => $view_id,
|
||||
'display_id' => drush_get_option('display-id', 'default'),
|
||||
'action_id' => $action_id,
|
||||
'preconfiguration' => $qs_config['config'],
|
||||
'batch' => TRUE,
|
||||
'arguments' => $arguments,
|
||||
'exposed_input' => $qs_config['exposed'],
|
||||
'batch_size' => drush_get_option('batch-size', 100),
|
||||
'relationship_id' => 'none',
|
||||
];
|
||||
|
||||
// Initialize the view to check if parameters are correct.
|
||||
if (!$view = Views::getView($vbo_data['view_id'])) {
|
||||
drush_set_error('Incorrect view ID provided.');
|
||||
return;
|
||||
}
|
||||
if (!$view->setDisplay($vbo_data['display_id'])) {
|
||||
drush_set_error('Incorrect view display ID provided.');
|
||||
return;
|
||||
}
|
||||
if (!empty($vbo_data['arguments'])) {
|
||||
$view->setArguments($vbo_data['arguments']);
|
||||
}
|
||||
if (!empty($vbo_data['exposed_input'])) {
|
||||
$view->setExposedInput($vbo_data['exposed_input']);
|
||||
}
|
||||
|
||||
// We need total rows count for proper progress message display.
|
||||
$view->get_total_rows = TRUE;
|
||||
$view->execute();
|
||||
|
||||
// Get relationship ID if VBO field exists.
|
||||
$vbo_data['relationship_id'] = 'none';
|
||||
foreach ($view->field as $field) {
|
||||
if ($field->options['id'] === 'views_bulk_operations_bulk_form') {
|
||||
$vbo_data['relationship_id'] = $field->options['relationship'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get total rows count.
|
||||
$viewDataService = \Drupal::service('views_bulk_operations.data');
|
||||
$viewDataService->init($view, $view->getDisplay(), $vbo_data['relationship_id']);
|
||||
$vbo_data['total_results'] = $viewDataService->getTotalResults();
|
||||
|
||||
// Get action definition and check if action ID is correct.
|
||||
try {
|
||||
$action_definition = \Drupal::service('plugin.manager.views_bulk_operations_action')->getDefinition($action_id);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
drush_set_error($e->getMessage());
|
||||
return;
|
||||
}
|
||||
$vbo_data['action_label'] = (string) $action_definition['label'];
|
||||
|
||||
_views_bulk_operations_timer($debug, 'init');
|
||||
|
||||
// Populate entity list.
|
||||
$context = [];
|
||||
do {
|
||||
$context['finished'] = 1;
|
||||
$context['message'] = '';
|
||||
ViewsBulkOperationsBatch::getList($vbo_data, $context);
|
||||
if (!empty($context['message'])) {
|
||||
drush_log($context['message'], 'ok');
|
||||
}
|
||||
} while ($context['finished'] < 1);
|
||||
$vbo_data = $context['results'];
|
||||
|
||||
_views_bulk_operations_timer($debug, 'list');
|
||||
|
||||
// Execute the selected action.
|
||||
$context = [];
|
||||
do {
|
||||
$context['finished'] = 1;
|
||||
$context['message'] = '';
|
||||
ViewsBulkOperationsBatch::operation($vbo_data, $context);
|
||||
if (!empty($context['message'])) {
|
||||
drush_log($context['message'], 'ok');
|
||||
}
|
||||
} while ($context['finished'] < 1);
|
||||
|
||||
// Output a summary message.
|
||||
$operations = array_count_values($context['results']['operations']);
|
||||
$details = [];
|
||||
foreach ($operations as $op => $count) {
|
||||
$details[] = $op . ' (' . $count . ')';
|
||||
}
|
||||
drush_log(dt('Action processing results: @results.', ['@results' => implode(', ', $details)]), 'ok');
|
||||
|
||||
// Display debug information.
|
||||
if ($debug) {
|
||||
_views_bulk_operations_timer($debug, 'execute');
|
||||
drush_print(sprintf('Initialization time: %d ms.', _views_bulk_operations_timer($debug, 'init')));
|
||||
drush_print(sprintf('Entity list generation time: %d ms.', _views_bulk_operations_timer($debug, 'list')));
|
||||
drush_print(sprintf('Execution time: %d ms.', _views_bulk_operations_timer($debug, 'execute')));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
type: module
|
||||
name: 'Views Bulk Operations'
|
||||
description: 'Adds an ability to perform bulk operations on selected entities from view results.'
|
||||
package: 'Views'
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:views (>=8.4)
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-02-08
|
||||
version: '8.x-2.0-rc4+1-dev'
|
||||
core: '8.x'
|
||||
project: 'views_bulk_operations'
|
||||
datestamp: 1518076986
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
frontUi:
|
||||
version: 1.0
|
||||
js:
|
||||
js/frontUi.js: {}
|
||||
css:
|
||||
component:
|
||||
css/frontUi.css: {}
|
||||
dependencies:
|
||||
- core/drupal
|
||||
- core/jquery
|
||||
- core/jquery.once
|
||||
|
||||
adminUi:
|
||||
version: 1.0
|
||||
js:
|
||||
js/adminUi.js: {}
|
||||
dependencies:
|
||||
- core/drupal
|
||||
- core/jquery
|
||||
- core/jquery.once
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Allows operations to be performed on items selected in a view.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_views_data_alter().
|
||||
*/
|
||||
function views_bulk_operations_views_data_alter(&$data) {
|
||||
$data['views']['views_bulk_operations_bulk_form'] = [
|
||||
'title' => t('Views bulk operations'),
|
||||
'help' => t("Process entities returned by the view with Views Bulk Operations' actions."),
|
||||
'field' => [
|
||||
'id' => 'views_bulk_operations_bulk_form',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function views_bulk_operations_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.views_bulk_operations':
|
||||
$filepath = dirname(__FILE__) . '/README.txt';
|
||||
if (file_exists($filepath)) {
|
||||
$readme = file_get_contents($filepath);
|
||||
$output = '<pre>' . $readme . '</pre>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_THEME().
|
||||
*/
|
||||
function views_bulk_operations_preprocess_views_view_table(&$variables) {
|
||||
if (!empty($variables['view']->style_plugin->options['views_bulk_operations_enabled'])) {
|
||||
// Add module own class to improve resistance to theme overrides.
|
||||
$variables['attributes']['class'][] = 'vbo-table';
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
views_bulk_operations.execute_batch:
|
||||
path: '/views-bulk-operations/execute/{view_id}/{display_id}'
|
||||
defaults:
|
||||
_controller: '\Drupal\views_bulk_operations\Controller\ViewsBulkOperationsController::execute'
|
||||
_title: 'Views Bulk Operations batch starter'
|
||||
requirements:
|
||||
_views_bulk_operation_access: 'TRUE'
|
||||
views_bulk_operations.update_selection:
|
||||
path: '/views-bulk-operations/ajax/{view_id}/{display_id}'
|
||||
defaults:
|
||||
_controller: '\Drupal\views_bulk_operations\Controller\ViewsBulkOperationsController::updateSelection'
|
||||
_title: 'Views Bulk Operations multipage AJAX'
|
||||
requirements:
|
||||
_views_bulk_operation_access: 'TRUE'
|
||||
views_bulk_operations.execute_configurable:
|
||||
path: '/views-bulk-operations/configure/{view_id}/{display_id}'
|
||||
defaults:
|
||||
_form: '\Drupal\views_bulk_operations\Form\ConfigureAction'
|
||||
_title: 'Views Bulk Operations configure step'
|
||||
requirements:
|
||||
_views_bulk_operation_access: 'TRUE'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
views_bulk_operations.confirm:
|
||||
path: '/views-bulk-operations/confirm/{view_id}/{display_id}'
|
||||
defaults:
|
||||
_form: '\Drupal\views_bulk_operations\Form\ConfirmAction'
|
||||
_title: 'Views Bulk Operations confirm execution'
|
||||
requirements:
|
||||
_views_bulk_operation_access: 'TRUE'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
views_bulk_operations.data:
|
||||
class: Drupal\views_bulk_operations\Service\ViewsBulkOperationsViewData
|
||||
arguments: ['@event_dispatcher']
|
||||
views_bulk_operations.processor:
|
||||
class: Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessor
|
||||
arguments: ['@views_bulk_operations.data', '@plugin.manager.views_bulk_operations_action', '@current_user', '@module_handler']
|
||||
plugin.manager.views_bulk_operations_action:
|
||||
class: Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
|
||||
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@event_dispatcher']
|
||||
views_bulk_operations.access:
|
||||
class: Drupal\views_bulk_operations\Access\ViewsBulkOperationsAccess
|
||||
arguments: ['@user.private_tempstore']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _views_bulk_operation_access }
|
||||
views_bulk_operations.view_data_provider:
|
||||
class: Drupal\views_bulk_operations\EventSubscriber\ViewsBulkOperationsEventSubscriber
|
||||
arguments: ['@views_bulk_operations.data']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
Reference in New Issue
Block a user