enabled workflow module - remains to configure it
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
Changes since 8.x-1.x
|
||||
Issue #2351299 by johnv: Porting D7-version to D8 - numerous commits.
|
||||
|
||||
Issue #2585093 by johnv: Fixed lost error message on Workflow UI - Transitions page.
|
||||
Issue #2587165 by johnv: Port Workflow to D8: dropped features, clone support.
|
||||
Issue #297898: Move "Workflow tab permissions" to permissions page'
|
||||
Issue #2587171: Make permission "schedule workflow transition" specific per Workflow type
|
||||
@@ -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 @@
|
||||
workflow_states_per_page: 20
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- workflow
|
||||
theme:
|
||||
- bartik
|
||||
id: workflowtransitionform
|
||||
theme: bartik
|
||||
region: sidebar_second
|
||||
weight: -7
|
||||
provider: null
|
||||
plugin: workflow_transition_form_block
|
||||
settings:
|
||||
id: workflow_transition_form_block
|
||||
label: 'Workflow Transition form'
|
||||
provider: workflow
|
||||
label_display: visible
|
||||
visibility: { }
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
langcode: nl
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- action
|
||||
- node
|
||||
- workflow
|
||||
id: change_a_node_to_next_workflow_state
|
||||
label: 'Change a node to next Workflow state'
|
||||
type: node
|
||||
plugin: workflow_node_next_state_action
|
||||
configuration:
|
||||
label: 'Change a node to next Workflow state'
|
||||
id: change_a_node_to_next_workflow_state
|
||||
plugin: workflow_node_next_state_action
|
||||
type: node
|
||||
workflow_scheduling:
|
||||
scheduled: '0'
|
||||
comment: 'New state is set by a triggered Action.'
|
||||
force: 0
|
||||
actions: { }
|
||||
+713
@@ -0,0 +1,713 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- workflow
|
||||
id: workflow_entity_history
|
||||
label: 'Workflow Entity history'
|
||||
module: views
|
||||
description: 'Enable this View to configure the history tab.'
|
||||
tag: ''
|
||||
base_table: workflow_transition_history
|
||||
base_field: hid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
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: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
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
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
hid: hid
|
||||
timestamp: timestamp
|
||||
field_name: field_name
|
||||
from_sid: from_sid
|
||||
to_sid: to_sid
|
||||
uid: uid
|
||||
comment: comment
|
||||
operations: operations
|
||||
info:
|
||||
hid:
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
timestamp:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
field_name:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
from_sid:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
to_sid:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
uid:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
comment:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
operations:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: hid
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
hid:
|
||||
id: hid
|
||||
table: workflow_transition_history
|
||||
field: hid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Transition ID'
|
||||
exclude: true
|
||||
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
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: hid
|
||||
plugin_id: field
|
||||
timestamp:
|
||||
id: timestamp
|
||||
table: workflow_transition_history
|
||||
field: timestamp
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Timestamp
|
||||
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
|
||||
click_sort_column: value
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: medium
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: timestamp
|
||||
plugin_id: field
|
||||
field_name:
|
||||
id: field_name
|
||||
table: workflow_transition_history
|
||||
field: field_name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Field name'
|
||||
exclude: true
|
||||
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
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: false
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: field_name
|
||||
plugin_id: field
|
||||
from_sid:
|
||||
id: from_sid
|
||||
table: workflow_transition_history
|
||||
field: from_sid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'From state'
|
||||
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
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: from_sid
|
||||
plugin_id: field
|
||||
to_sid:
|
||||
id: to_sid
|
||||
table: workflow_transition_history
|
||||
field: to_sid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'To state'
|
||||
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
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: to_sid
|
||||
plugin_id: field
|
||||
uid:
|
||||
id: uid
|
||||
table: workflow_transition_history
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'User ID'
|
||||
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
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: uid
|
||||
plugin_id: field
|
||||
comment:
|
||||
id: comment
|
||||
table: workflow_transition_history
|
||||
field: comment
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Log message'
|
||||
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
|
||||
click_sort_column: value
|
||||
type: basic_string
|
||||
settings: { }
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: comment
|
||||
plugin_id: field
|
||||
operations:
|
||||
id: operations
|
||||
table: workflow_transition_history
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 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
|
||||
destination: true
|
||||
entity_type: null
|
||||
entity_field: null
|
||||
plugin_id: entity_operations
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments:
|
||||
entity_id:
|
||||
id: entity_id
|
||||
table: workflow_transition_history
|
||||
field: entity_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: ignore
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
default_argument_skip_url: false
|
||||
summary_options:
|
||||
base_path: ''
|
||||
count: true
|
||||
items_per_page: 25
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
break_phrase: false
|
||||
not: false
|
||||
entity_type: workflow_transition
|
||||
entity_field: entity_id
|
||||
plugin_id: numeric
|
||||
display_extenders: { }
|
||||
title: 'Workflow history'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
tags: { }
|
||||
workflow_history_tab:
|
||||
display_plugin: embed
|
||||
id: workflow_history_tab
|
||||
display_title: 'Workflow history tab'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: 'Replaces the transition list in the Workflow tab.'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
tags: { }
|
||||
@@ -0,0 +1,82 @@
|
||||
# Schema for the configuration files of the Workflow module.
|
||||
workflow.settings:
|
||||
type: config_object
|
||||
label: 'Workflow settings'
|
||||
mapping:
|
||||
workflow_states_per_page:
|
||||
type: integer
|
||||
label: 'Number of workflow states per page'
|
||||
|
||||
workflow.workflow.*:
|
||||
type: config_entity
|
||||
label: 'Workflow settings'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
status:
|
||||
type: boolean
|
||||
label: 'Status'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
module:
|
||||
type: string
|
||||
label: 'Module'
|
||||
options:
|
||||
type: mapping
|
||||
label: 'Options'
|
||||
mapping:
|
||||
name_as_title:
|
||||
type: integer
|
||||
label: 'Use the workflow name as the title of the workflow form'
|
||||
fieldset:
|
||||
type: integer
|
||||
label: 'Show the form in a fieldset'
|
||||
options:
|
||||
type: string
|
||||
label: 'How to show the available states'
|
||||
schedule_timezone:
|
||||
type: integer
|
||||
label: 'Show a timezone when scheduling a transition'
|
||||
comment_log_node:
|
||||
type: integer
|
||||
label: 'Show comment on the Content edit form'
|
||||
watchdog_log:
|
||||
type: integer
|
||||
label: 'Log watchdog messages upon state change'
|
||||
|
||||
field.storage_settings.workflow:
|
||||
type: mapping
|
||||
label: 'Workflow settings'
|
||||
mapping:
|
||||
workflow_type:
|
||||
type: string
|
||||
label: 'Workflow'
|
||||
allowed_values:
|
||||
type: sequence
|
||||
label: 'Allowed values list'
|
||||
sequence:
|
||||
type: mapping
|
||||
label: 'Allowed value with label'
|
||||
mapping:
|
||||
value:
|
||||
type: string
|
||||
label: 'Value'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
allowed_values_function:
|
||||
type: string
|
||||
label: 'Allowed values function'
|
||||
|
||||
field.field_settings.workflow:
|
||||
type: mapping
|
||||
label: 'Workflow settings'
|
||||
|
||||
workflow.settings:
|
||||
type: config_object
|
||||
mapping:
|
||||
workflow_states_per_page:
|
||||
type: integer
|
||||
label: 'Number of workflow states displayed per page.'
|
||||
@@ -0,0 +1,27 @@
|
||||
# Schema for the configuration files of the state from Workflow module.
|
||||
|
||||
workflow.state.*:
|
||||
type: config_entity
|
||||
label: 'Workflow State'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
status:
|
||||
type: boolean
|
||||
label: 'Status'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
module:
|
||||
type: string
|
||||
label: 'Module'
|
||||
wid:
|
||||
type: string
|
||||
label: 'Machine name of the attached Workflow'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
sysid:
|
||||
type: integer
|
||||
label: 'SysId'
|
||||
@@ -0,0 +1,27 @@
|
||||
# Schema for the configuration files of the transitions for Workflow module.
|
||||
|
||||
workflow.transition.*:
|
||||
type: config_entity
|
||||
label: 'Workflow Transition'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
module:
|
||||
type: string
|
||||
label: 'Module'
|
||||
from_sid:
|
||||
type: string
|
||||
label: 'From workflow state'
|
||||
to_sid:
|
||||
type: string
|
||||
label: 'To workflow state'
|
||||
roles:
|
||||
type: sequence
|
||||
label: 'User roles'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Role'
|
||||
@@ -0,0 +1,4 @@
|
||||
# Schema for the views plugins of the Workflow module.
|
||||
|
||||
views.filter.workflow_state:
|
||||
type: views.filter.many_to_one
|
||||
+1
@@ -0,0 +1 @@
|
||||
workflow_access_priority: 0
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
workflow_access.settings:
|
||||
type: config_object
|
||||
label: Settings
|
||||
mapping:
|
||||
workflow_access_priority:
|
||||
type: integer
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_access\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
use Drupal\workflow_ui\Form\WorkflowConfigTransitionFormBase;
|
||||
|
||||
/**
|
||||
* Provides the base form for workflow add and edit forms.
|
||||
*/
|
||||
class WorkflowAccessRoleForm extends WorkflowConfigTransitionFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entitiesKey = 'workflow_state';
|
||||
|
||||
/**
|
||||
* The WorkflowConfigTransition form type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'access';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_access_role';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['workflow_access.role'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = [
|
||||
'label_new' => t('State'),
|
||||
'view' => t('Roles who can view posts in this state'),
|
||||
'update' => t('Roles who can edit posts in this state'),
|
||||
'delete' => t('Roles who can delete posts in this state'),
|
||||
];
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row = [];
|
||||
|
||||
$workflow = $this->workflow;
|
||||
if ($workflow) {
|
||||
/* @var $state WorkflowState */
|
||||
$state = $entity;
|
||||
$sid = $state->id();
|
||||
|
||||
// A list of role names keyed by role ID, including the 'author' role.
|
||||
// Only get the roles with proper permission + Author role.
|
||||
$type_id = $workflow->id();
|
||||
$roles = workflow_get_user_role_names("create $type_id workflow_transition");
|
||||
|
||||
if ($state->isCreationState()) {
|
||||
// No need to set perms on creation.
|
||||
return [];
|
||||
}
|
||||
|
||||
$view = $update = $delete = [];
|
||||
$count = 0;
|
||||
foreach (workflow_access_get_workflow_access_by_sid($sid) as $rid => $access) {
|
||||
$count++;
|
||||
$view[$rid] = ($access['grant_view']) ? $rid : 0;
|
||||
$update[$rid] = ($access['grant_update']) ? $rid : 0;
|
||||
$delete[$rid] = ($access['grant_delete']) ? $rid : 0;
|
||||
}
|
||||
// Allow view grants by default for anonymous and authenticated users,
|
||||
// if no grants were set up earlier.
|
||||
if (!$count) {
|
||||
$view = [
|
||||
AccountInterface::ANONYMOUS_ROLE => AccountInterface::ANONYMOUS_ROLE,
|
||||
AccountInterface::AUTHENTICATED_ROLE => AccountInterface::AUTHENTICATED_ROLE,
|
||||
];
|
||||
}
|
||||
|
||||
$row['label_new'] = [
|
||||
'#type' => 'value',
|
||||
'#markup' => t('@label', ['@label' => $state->label()]),
|
||||
];
|
||||
$row['view'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $roles,
|
||||
'#default_value' => $view,
|
||||
];
|
||||
$row['update'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $roles,
|
||||
'#default_value' => $update,
|
||||
];
|
||||
$row['delete'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $roles,
|
||||
'#default_value' => $delete,
|
||||
];
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $sid => $access) {
|
||||
// @todo: not waterproof; can be done smarter, using elementchildren().
|
||||
if (!WorkflowState::load($sid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($access['view'] as $rid => $checked) {
|
||||
$data[$rid] = [
|
||||
'grant_view' => (!empty($access['view'][$rid])) ? (bool) $access['view'][$rid] : 0,
|
||||
'grant_update' => (!empty($access['update'][$rid])) ? (bool) $access['update'][$rid] : 0,
|
||||
'grant_delete' => (!empty($access['delete'][$rid])) ? (bool) $access['delete'][$rid] : 0,
|
||||
];
|
||||
}
|
||||
workflow_access_insert_workflow_access_by_sid($sid, $data);
|
||||
|
||||
// Update all nodes to reflect new settings.
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_access\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides the base form for workflow add and edit forms.
|
||||
*/
|
||||
class WorkflowAccessSettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_access_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['workflow_access.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('workflow_access.settings');
|
||||
$weight = $config->get('workflow_access_priority');
|
||||
|
||||
$form['workflow_access'] = [
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => t('Workflow Access Settings'),
|
||||
];
|
||||
$form['workflow_access']['#tree'] = TRUE;
|
||||
|
||||
$url = 'https://api.drupal.org/api/drupal/core%21modules%21node%21node.api.php/function/hook_node_access_records/8';
|
||||
$form['workflow_access']['workflow_access_priority'] = [
|
||||
'#type' => 'weight',
|
||||
'#delta' => 10,
|
||||
'#title' => t('Workflow Access Priority'),
|
||||
'#default_value' => $weight,
|
||||
'#description' => t('This sets the node access priority. Changing this
|
||||
setting can be dangerous. If there is any doubt, leave it at 0.
|
||||
<a href=":url" target="_blank">Read the manual</a>.', [':url' => $url]),
|
||||
];
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$weight = $form_state->getValues()['workflow_access']['workflow_access_priority'];
|
||||
|
||||
$this->config('workflow_access.settings')
|
||||
->set('workflow_access_priority', $weight)
|
||||
->save();
|
||||
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
name: 'Workflow access'
|
||||
description: 'Content access control based on workflows and roles. Depends on the node_access system, so only works for entities of type ''node''.'
|
||||
dependencies:
|
||||
- workflow
|
||||
package: Workflow
|
||||
# core: 8.x
|
||||
type: module
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Workflow access installation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function workflow_access_schema() {
|
||||
// The D7-table 'workflow_access' is not used anymore.
|
||||
// The data is no in table 'config'.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Force rebuild of node access.
|
||||
*/
|
||||
function workflow_access_uninstall() {
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force rebuild of node access.
|
||||
*/
|
||||
function workflow_access_update_8001(&$sandbox) {
|
||||
// Rebuild data.
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove table workflow_access. All current settings are lost!
|
||||
*/
|
||||
function workflow_access_update_8002(&$sandbox) {
|
||||
// Remove the table. Data is now in config.
|
||||
$schema = \Drupal\Core\Database\Database::getConnection()->schema();
|
||||
$schema->dropTable('workflow_access');
|
||||
// Rebuild data.
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Menu tasks for Workflow UI.
|
||||
|
||||
workflow_access_settings:
|
||||
route_name: workflow.access.settings
|
||||
base_route: entity.workflow_type.collection
|
||||
title: 'Access settings'
|
||||
weight: 5
|
||||
appears_on:
|
||||
- entity.workflow_type.collection
|
||||
|
||||
entity.workflow_type.access_form:
|
||||
title: 'Access'
|
||||
route_name: entity.workflow_type.access_form
|
||||
base_route: entity.workflow_type.edit_form
|
||||
weight: -1
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides node access permissions based on workflow states.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\workflow\Entity\WorkflowTransition;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function workflow_access_help($route_name, RouteMatchInterface $route_match) {
|
||||
$output = '';
|
||||
|
||||
switch ($route_name) {
|
||||
case 'entity.workflow_type.access_form':
|
||||
$url = \Drupal\Core\Url::fromRoute('workflow.access.settings');
|
||||
$output .= t("This page lets you refine the permissions per role and per
|
||||
workflow state. Although the workflow module allows you to add multiple
|
||||
workflows to per entity type, Workflow Access supports only one
|
||||
workflow per entity type."
|
||||
);
|
||||
$output .= "<br>";
|
||||
$output .= t("WARNING: Use of the 'Edit any', 'Edit own', and even 'View
|
||||
published content' permissions for the content type may override these
|
||||
access settings. You may disable those permissions or
|
||||
<a href=':url'>alter the priority of
|
||||
the Workflow access module</a>.", [':url' => $url->toString()]
|
||||
);
|
||||
if (\Drupal::moduleHandler()->moduleExists('og')) {
|
||||
$output .= '<br>';
|
||||
$output .= t('WARNING: Organic Groups (OG) is present and may interfere
|
||||
with these settings.');
|
||||
//$output .= ' ';
|
||||
//$url = \Drupal\Core\Url::fromUri('admin/config/group/settings'); // @todo D8: FIXME when OG module is ported.
|
||||
//$output .= t("In particular, if <a href=':url'>Strict node access
|
||||
// permissions</a> is enabled, since this may override Workflow access
|
||||
// settings.", [':url' => $url]);
|
||||
$output .= t("In particular, if <i>Strict node access
|
||||
permissions</i> is enabled, since this may override Workflow access
|
||||
settings.");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_workflow_operations().
|
||||
*
|
||||
* Create action link for access form on EntityWorkflowUIController::overviewForm.
|
||||
*/
|
||||
function workflow_access_workflow_operations($op, EntityInterface $entity = NULL) {
|
||||
$operations = [];
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_insert().
|
||||
*
|
||||
* We use the Role weight as an id.
|
||||
* In contrary to content_access module, that uses a 'content_access_roles_gids'
|
||||
* config setting.
|
||||
* @todo: determine the best way for D8. See also content_access.module
|
||||
* The problem is that node_access table uses Int, whereas the Role Id is string.
|
||||
*/
|
||||
function workflow_access_user_role_insert(EntityInterface $entity) {
|
||||
// Attend user to Rebuild data, because the weight of a role
|
||||
// is the key for workflow_Access.
|
||||
/** @var $entity \Drupal\user\RoleInterface */
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_access_entity_update().
|
||||
*
|
||||
* @param EntityInterface $entity
|
||||
*/
|
||||
function workflow_access_user_role_update(EntityInterface $entity) {
|
||||
// Attend user to Rebuild data, because the weight of a role
|
||||
// is the key for workflow_Access.
|
||||
/** @var $entity \Drupal\user\RoleInterface */
|
||||
if ($entity->getWeight() != $entity->original->getWeight()) {
|
||||
// Role's weight has changed.
|
||||
node_access_needs_rebuild(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_grants().
|
||||
*
|
||||
* Supply the workflow access grants. We are simply using
|
||||
* roles as access lists, so rids translate directly to gids.
|
||||
*/
|
||||
function workflow_access_node_grants(AccountInterface $account, $op) {
|
||||
$gids = [];
|
||||
$roles = $account->getRoles();
|
||||
foreach ($roles as $role) {
|
||||
// @todo D8: compare with content_access module.
|
||||
$gids[] = workflow_access_get_role_gid($role);
|
||||
}
|
||||
|
||||
return [
|
||||
'workflow_access' => $gids,
|
||||
'workflow_access_owner' => [$account->id()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper providing numeric id for role.
|
||||
* Copied from content_access.module
|
||||
*/
|
||||
function workflow_access_get_role_gid($rid) {
|
||||
// @todo D8: compare with content_access module.
|
||||
// $config = \Drupal::configFactory()->getEditable('content_access.settings');
|
||||
// $roles_gids = $config->get('content_access_roles_gids');
|
||||
// return $roles_gids[$role];
|
||||
|
||||
/* @var $role Role */
|
||||
$role = \Drupal\user\Entity\Role::load($rid);
|
||||
$weight = 100; // Avoid negative values.
|
||||
if ($role) {
|
||||
$weight += $role->getWeight();
|
||||
}
|
||||
else {
|
||||
// For Author, no role exists.
|
||||
$weight -= 20;
|
||||
}
|
||||
|
||||
return $weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_access_records().
|
||||
*
|
||||
* Returns a list of grant records for the passed in node object.
|
||||
* This hook is invoked by function node_access_acquire_grants().
|
||||
*/
|
||||
function workflow_access_node_access_records(\Drupal\node\NodeInterface $node) {
|
||||
$grants = [];
|
||||
$workflow_transitions_added = FALSE;
|
||||
|
||||
//if (!$node->isPublished()) {
|
||||
// // @todo: return;
|
||||
//}
|
||||
|
||||
$entity_type = $node->getEntityTypeId();
|
||||
|
||||
|
||||
// Only relevant for content with Workflow.
|
||||
if (!isset($node->workflow_transitions)) {
|
||||
$node->workflow_transitions = [];
|
||||
// Sometimes, a node is saved without going through workflow_transition_form.
|
||||
// E.g.,
|
||||
// - when saving a node with workflow_node programmatically with node_save();
|
||||
// - when changing a state on a node view page/history tab;
|
||||
// - when rebuilding permissions via batch for Workflow Fields.
|
||||
// In that case, we need to create the workflow_transitions ourselves to
|
||||
// calculate the grants.
|
||||
foreach (_workflow_info_fields($node, $entity_type) as $field) {
|
||||
$field_name = $field->getName(); // Do not use id().
|
||||
$old_sid = $new_sid = $node->$field_name->value;
|
||||
|
||||
// Create a dummy transition, just to set $node->workflow_transitions.
|
||||
if ($old_sid) {
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = WorkflowTransition::create([$old_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($node);
|
||||
$transition->setValues($new_sid, $node->getOwnerId(), \Drupal::time()->getRequestTime(), '');
|
||||
|
||||
// Store the transition, so it can be easily fetched later on.
|
||||
// Store in an array, to prepare for multiple workflow_fields per entity.
|
||||
// This is a.o. used in hook_entity_update to trigger 'transition post'.
|
||||
$node->workflow_transitions[$field_name] = $transition;
|
||||
$workflow_transitions_added = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get 'author' of this entity.
|
||||
// - Some entities (e.g., taxonomy_term) do not have a uid.
|
||||
// But then again: node_access is only for nodes...
|
||||
$uid = ($node->getOwnerId()) ? (int) $node->getOwnerId() : 0;
|
||||
$priority = workflow_access_get_setting('workflow_access_priority');
|
||||
|
||||
$count = 0;
|
||||
foreach ($node->workflow_transitions as $transition) {
|
||||
// @todo: add support for +1 workflows per entity.
|
||||
if ($count++ == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_name = $transition->getFieldName();
|
||||
if ($current_sid = workflow_node_current_state($node, $field_name)) {
|
||||
foreach (workflow_access_get_workflow_access_by_sid($current_sid) as $rid => $grant) {
|
||||
$realm = ($uid > 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) ? 'workflow_access_owner' : 'workflow_access';
|
||||
$gid = ($uid > 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) ? $uid : workflow_access_get_role_gid($rid);
|
||||
|
||||
// Anonymous ($uid == 0) author is not allowed for role 'author' (== -1).
|
||||
// Both logically (Anonymous having more rights then authenticated)
|
||||
// and technically: $gid must be a positive int.
|
||||
if ($uid == 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$grants[] = [
|
||||
'realm' => $realm,
|
||||
'gid' => $gid,
|
||||
'grant_view' => (int) $grant['grant_view'],
|
||||
'grant_update' => (int) $grant['grant_update'],
|
||||
'grant_delete' => (int) $grant['grant_delete'],
|
||||
'priority' => $priority,
|
||||
// 'langcode' => $node->language(),
|
||||
'field_name' => $field_name, // Just for analysis and info.
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($workflow_transitions_added == TRUE) {
|
||||
unset($node->workflow_transitions);
|
||||
}
|
||||
|
||||
return $grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_access_explain().
|
||||
*
|
||||
* This is a Devel Node Access hook.
|
||||
*/
|
||||
function workflow_access_node_access_explain($row) {
|
||||
static $interpretations = [];
|
||||
switch ($row->realm) {
|
||||
case 'workflow_access_owner':
|
||||
$interpretations[$row->gid] = t('Workflow access: author of the content may access');
|
||||
break;
|
||||
|
||||
case 'workflow_access':
|
||||
$roles = user_roles();
|
||||
$interpretations[$row->gid] = t('Workflow access: %role may access', ['%role' => $roles[$row->gid]]);
|
||||
break;
|
||||
}
|
||||
return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* DB functions - all DB interactions are isolated here to make for easy updating should our schema change.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given a sid, retrieve the access information and return the row(s).
|
||||
*/
|
||||
function workflow_access_get_workflow_access_by_sid($sid) {
|
||||
$result = \Drupal::config('workflow_access.role')->get($sid);
|
||||
if ($result == NULL) {
|
||||
// Avoid errors in calling function when no data available.
|
||||
$result = [];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a sid, delete all access data for this state.
|
||||
*/
|
||||
function workflow_access_delete_workflow_access_by_sid($sid) {
|
||||
\Drupal::configFactory()->getEditable('workflow_access.role')
|
||||
->clear($sid)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given data, insert into workflow access - we never update.
|
||||
*/
|
||||
function workflow_access_insert_workflow_access_by_sid($sid, &$data, Config $config = NULL) {
|
||||
$config = \Drupal::configFactory()->getEditable('workflow_access.role');
|
||||
$config
|
||||
->set($sid, $data)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module settings.
|
||||
*
|
||||
* @see WorkflowAccessSettingsForm::submitForm()
|
||||
*/
|
||||
function workflow_access_get_setting($value) {
|
||||
$config = \Drupal::config('workflow_access.settings');
|
||||
$priority = $config->get($value);
|
||||
return $priority;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Declaration of Workflow Access routings.
|
||||
|
||||
### Workflow settings form
|
||||
workflow.access.settings:
|
||||
path: '/admin/config/workflow/workflow/access'
|
||||
defaults:
|
||||
_form: '\Drupal\workflow_access\Form\WorkflowAccessSettingsForm'
|
||||
_title: 'Access settings'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
_module_dependencies: 'workflow_ui'
|
||||
|
||||
entity.workflow_type.access_form:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}/access'
|
||||
defaults:
|
||||
_title: 'Edit Workflow'
|
||||
_title_callback: 'workflow_url_get_title'
|
||||
_form: '\Drupal\workflow_access\Form\WorkflowAccessRoleForm'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
_module_dependencies: 'workflow_ui'
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_cleanup\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Entity\WorkflowConfigTransition;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
|
||||
/**
|
||||
* Class WorkflowCleanupSettingsForm
|
||||
*
|
||||
* @package Drupal\workflow_cleanup\Form
|
||||
*/
|
||||
class WorkflowCleanupSettingsForm extends FormBase {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_cleanup_settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = [];
|
||||
|
||||
// Get all of the states, indexed by sid.
|
||||
$orphans = $inactive = [];
|
||||
|
||||
/* @var $states WorkflowState[] */
|
||||
/* @var $state WorkflowState */
|
||||
$states = WorkflowState::loadMultiple();
|
||||
|
||||
foreach ($states as $state) {
|
||||
// Does the associated workflow exist?
|
||||
if (!$state->getWorkflow()) {
|
||||
$orphans[$state->id()] = $state;
|
||||
}
|
||||
else {
|
||||
// Is the state still active?
|
||||
if (!$state->isActive()) {
|
||||
$inactive[$state->id()] = $state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the relevant states in an indexed array.
|
||||
$form['#workflow_states'] = $orphans + $inactive;
|
||||
|
||||
$form['no_workflow'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Orphaned States'),
|
||||
'#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE.
|
||||
'#description' => t('These states no longer belong to an existing workflow.'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
foreach ($orphans as $sid => $state) {
|
||||
$form['no_workflow'][$sid]['check'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $state->label(),
|
||||
'#return_value' => $sid,
|
||||
];
|
||||
}
|
||||
|
||||
$form['inactive'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Inactive (Deleted) States'),
|
||||
'#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE.
|
||||
'#description' => t('These states belong to a workflow, but have been marked inactive (deleted).'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
foreach ($inactive as $sid => $state) {
|
||||
$form['inactive'][$sid]['check'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $state->label() . ' (' . $state->getWorkflow()->label() . ')',
|
||||
'#return_value' => $sid,
|
||||
];
|
||||
}
|
||||
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Delete selected states'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$states = $form['#workflow_states'];
|
||||
$values = $form_state->getValues();
|
||||
foreach (['no_workflow', 'inactive'] as $section) {
|
||||
if (!isset($values[$section])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($values[$section] as $sid => $data) {
|
||||
if ($data['check']) {
|
||||
/* @var $state WorkflowState */
|
||||
$state = $states[$sid];
|
||||
$state_name = $state->label();
|
||||
|
||||
// Delete any transitions this state is involved in.
|
||||
$count = 0;
|
||||
foreach (WorkflowConfigTransition::loadMultiple() as $config_transition) {
|
||||
/* @var $config_transition WorkflowConfigTransition */
|
||||
if ($config_transition->getFromSid() == $sid || $config_transition->getToSid() == $sid) {
|
||||
$config_transition->delete();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
if ($count) {
|
||||
drupal_set_message(t('@count transitions for the "@state" state have been deleted.',
|
||||
['@state' => $state_name, '@count' => $count]));
|
||||
}
|
||||
|
||||
// @todo: Remove history records too.
|
||||
$count = 0;
|
||||
// $count = db_delete('workflow_node_history')->condition('sid', $sid)->execute();
|
||||
if ($count) {
|
||||
drupal_set_message(t('@count history records for the "@state" state have been deleted.',
|
||||
['@state' => $state_name, '@count' => $count]));
|
||||
}
|
||||
|
||||
$state->delete();
|
||||
drupal_set_message(t('The "@state" state has been deleted.',
|
||||
['@state' => $state_name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
name: Workflow Clean Up
|
||||
type: module
|
||||
description: 'Cleans up Workflow cruft. (Do not enable in production sites.)'
|
||||
package: Workflow
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- workflow
|
||||
configure: workflow.cleanup.settings
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# Menu tasks for Workflow Cleanup.
|
||||
|
||||
workflow.cleanup.settings:
|
||||
title: 'Cleanup'
|
||||
route_name: workflow.cleanup.settings
|
||||
base_route: entity.workflow_type.collection
|
||||
weight: 7
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Cleans up Workflow cruft that may build up over time.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function workflow_cleanup_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match) {
|
||||
$output = [];
|
||||
|
||||
switch ($route_name) {
|
||||
case 'workflow.cleanup.settings':
|
||||
$output = t('This page allows you to delete orphaned and inactive states.
|
||||
States can be deleted freely in a development environment, but be
|
||||
careful if you have used a State in a production environment. The
|
||||
transition history of your content will loose the description of a
|
||||
previously used state. If your Workflow must comply to some auditing
|
||||
standards, you should NOT use this function.');
|
||||
break;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
workflow.cleanup.settings:
|
||||
path: '/admin/config/workflow/workflow/cleanup'
|
||||
defaults:
|
||||
_form: '\Drupal\workflow_cleanup\Form\WorkflowCleanupSettingsForm'
|
||||
_title: 'Clean up workflow'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
@@ -0,0 +1,3 @@
|
||||
The Workflow Devel module helps you to develop addons for Workflow.
|
||||
|
||||
When activating this module, you will get a message with every hook, Workflow provides.
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
name: 'Workflow Devel'
|
||||
description: 'Tools for developing Workflow add-ons. Calls every hook in workflow.api.php and generates a user message on each call. (Do not enable in production sites.)'
|
||||
package: Workflow
|
||||
type: module
|
||||
# core: 8.x
|
||||
|
||||
dependencies:
|
||||
- workflow
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Development tools for Workflow.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
module_load_include('php', 'workflow', 'workflow.api');
|
||||
|
||||
/**
|
||||
* Hooks defined by workflow_ui, workflow module.
|
||||
*/
|
||||
function workflow_devel_workflow_operations($op, EntityInterface $entity = NULL) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, $op, '');
|
||||
return hook_workflow_operations($op, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks defined by workflow module.
|
||||
*/
|
||||
function workflow_devel_workflow($op, WorkflowTransitionInterface $transition, UserInterface $user) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, $op, '');
|
||||
return hook_workflow($op, $transition, $user);
|
||||
}
|
||||
|
||||
function workflow_devel_workflow_comment_alter(&$comment, array &$context) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, $comment, '');
|
||||
return hook_workflow_comment_alter($comment, $context);
|
||||
}
|
||||
|
||||
function workflow_devel_workflow_history_alter(array &$context) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__);
|
||||
return hook_workflow_history_alter($context);
|
||||
}
|
||||
|
||||
function workflow_devel_workflow_permitted_state_transitions_alter(array &$transitions, array $context) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__);
|
||||
return hook_workflow_permitted_state_transitions_alter($transitions, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks defined by core Form API: Change the Workflow Transition Form.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter forms for field widgets provided by other modules.
|
||||
*
|
||||
* @param $element
|
||||
* The field widget form element as constructed by hook_field_widget_form().
|
||||
* @param $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
* @param $context
|
||||
* An associative array containing the following key-value pairs, matching the
|
||||
* arguments received by hook_field_widget_form():
|
||||
* - form: The form structure to which widgets are being attached. This may be
|
||||
* a full form structure, or a sub-element of a larger form.
|
||||
* - field: The field structure.
|
||||
* - instance: The field instance structure.
|
||||
* - langcode: The language associated with $items.
|
||||
* - items: Array of default values for this field.
|
||||
* - delta: The order of this item in the array of subelements (0, 1, 2, etc).
|
||||
*
|
||||
* @see hook_field_widget_form()
|
||||
* @see hook_field_widget_WIDGET_TYPE_form_alter()
|
||||
*/
|
||||
function workflow_devel_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
|
||||
// A hook for changing any widget. Better not use it: it is called on EVERY
|
||||
// Widget. (Even though the message is only shown once.)
|
||||
// D7: This hook is introduced in Drupal 7.8.
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
// dpm($context['widget']->getPluginId());
|
||||
return hook_field_widget_form_alter($element, $form_state, $context);
|
||||
}
|
||||
|
||||
function workflow_devel_field_widget_workflow_default_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
|
||||
// A hook specific for the 'workflow_default' widget.
|
||||
// D7: This hook is introduced in Drupal 7.8.
|
||||
// D8: This name is specified in the annotation of WorkflowDefaultWidget.
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
return hook_field_widget_workflow_default_form_alter($element, $form_state, $context);
|
||||
}
|
||||
|
||||
function workflow_devel_form_workflow_transition_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, $form_id, '');
|
||||
return hook_form_workflow_transition_form_alter($form, $form_state, $form_id);
|
||||
}
|
||||
|
||||
function workflow_devel_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
|
||||
if (substr($form_id, 0, 8) == 'workflow') {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, $form_id, '');
|
||||
}
|
||||
return hook_form_alter($form, $form_state, $form_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks defined by core: Change the operations column in an Entity list.
|
||||
*
|
||||
* @see EntityListBuilder::getOperations()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function workflow_devel_entity_operation($entities) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
$operations = [];
|
||||
return $operations;
|
||||
}
|
||||
|
||||
function workflow_devel_entity_operation_alter(array $operations, EntityInterface $entity) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, $entity->getEntityTypeId(), $entity->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks defined by core: hook_entity_CRUD.
|
||||
*
|
||||
* @see hook_entity_create(), hook_entity_update(), etc.
|
||||
* @see hook_ENTITY_TYPE_create(), hook_ENTITY_TYPE_update(), etc.
|
||||
*/
|
||||
function workflow_devel_entity_create(EntityInterface $entity) {
|
||||
//workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'create' , $entity->getEntityTypeId());
|
||||
}
|
||||
function workflow_devel_entity_presave(EntityInterface $entity) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'presave', $entity->getEntityTypeId());
|
||||
}
|
||||
function workflow_devel_entity_insert(EntityInterface $entity) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'insert', $entity->getEntityTypeId());
|
||||
}
|
||||
function workflow_devel_entity_update(EntityInterface $entity) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'update', $entity->getEntityTypeId());
|
||||
}
|
||||
function workflow_devel_entity_predelete(EntityInterface $entity) {
|
||||
if (substr($entity->getEntityTypeId(), 0, 8) == 'workflow') {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'predelete', $entity->getEntityTypeId());
|
||||
}
|
||||
return hook_entity_predelete($entity);
|
||||
}
|
||||
function workflow_devel_entity_delete(EntityInterface $entity) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'delete', $entity->getEntityTypeId());
|
||||
return hook_entity_delete($entity);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
name: 'Workflow Field'
|
||||
description: 'A residual submodule. No need to enable this module for installations with workflow 8.x-1.0-beta6 and later.'
|
||||
package: Workflow
|
||||
type: module
|
||||
# core: 8.x
|
||||
|
||||
hidden: TRUE
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_operations\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element\Url;
|
||||
use Drupal\workflow\Entity\WorkflowTransition;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
class WorkflowTransitionRevertForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_transition_revert_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $this->entity;
|
||||
$state = $transition->getFromState();
|
||||
|
||||
if ($state) {
|
||||
$question = t('Are you sure you want to revert %title to the "@state" state?', [
|
||||
'@state' => $state->label(),
|
||||
'%title' => $transition->label(),
|
||||
]);
|
||||
return $question;
|
||||
}
|
||||
else {
|
||||
\Drupal::logger('workflow_revert')->error('Invalid state', []);
|
||||
drupal_set_message(t('Invalid transition. Your information has been recorded.'), 'error');
|
||||
//drupal_goto($return_uri);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getCancelUrl() {
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $this->entity;
|
||||
return new Url('entity.node.workflow_history', ['node' => $transition->getTargetEntityId(), 'field_name' => $transition->getFieldname()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return t('Revert');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
//public function getDescription() {
|
||||
// return '';
|
||||
//}
|
||||
|
||||
/**
|
||||
* The fact that we need to overwrite this function, is an indicator that
|
||||
* the Transition is not completely a complete Entity.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
//return parent::copyFormValuesToEntity($entity, $form, $form_state);
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
|
||||
|
||||
// If Rules is available, signal the reversion.
|
||||
// @todo: move this Rules_invoke_event to hook outside this module.
|
||||
if (\Drupal::moduleHandler()->moduleExists('rules')) {
|
||||
rules_invoke_event('workflow_state_reverted', $this->entity);
|
||||
}
|
||||
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $this->prepareRevertedTransition($this->entity);
|
||||
|
||||
// The entity will be updated when the transition is executed. Keep the
|
||||
// original one for the confirmation message.
|
||||
$previous_sid = $transition->getToSid();
|
||||
|
||||
// Force the transition because it's probably not valid.
|
||||
$transition->force(TRUE);
|
||||
$new_sid = workflow_execute_transition($transition, TRUE);
|
||||
|
||||
$comment = ($previous_sid == $new_sid) ? 'State is reverted.' : 'State could not be reverted.';
|
||||
drupal_set_message(t($comment), 'warning');
|
||||
|
||||
$form_state->setRedirect('entity.node.workflow_history', [
|
||||
'node' => $transition->getTargetEntityId(),
|
||||
'field_name' => $transition->getFieldName(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a transition to be reverted.
|
||||
*
|
||||
* @param \Drupal\workflow\Entity\WorkflowTransitionInterface $transition
|
||||
* The transition to be reverted.
|
||||
*
|
||||
* @return \Drupal\workflow\Entity\WorkflowTransitionInterface
|
||||
* The prepared transition ready to be stored.
|
||||
*/
|
||||
protected function prepareRevertedTransition(WorkflowTransitionInterface $transition) {
|
||||
$user = \Drupal::currentUser();
|
||||
|
||||
$entity = $transition->getTargetEntity();
|
||||
$field_name = $transition->getFieldName();
|
||||
$current_sid = workflow_node_current_state($entity, $field_name);
|
||||
$previous_sid = $transition->getFromSid();
|
||||
$comment = t('State reverted.');
|
||||
|
||||
$transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($entity);
|
||||
$transition->setValues($previous_sid, $user->id(), \Drupal::time()->getRequestTime(), $comment);
|
||||
|
||||
return $transition;
|
||||
}
|
||||
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_operations;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the workflow entity type.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\Workflow
|
||||
* @ingroup workflow_access
|
||||
*/
|
||||
class WorkflowAccessControlHandler extends \Drupal\workflow\WorkflowAccessControlHandler {
|
||||
|
||||
/**
|
||||
* This is a hack.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type = NULL) {
|
||||
if ($entity_type) {
|
||||
parent::__construct($entity_type);
|
||||
}
|
||||
else {
|
||||
//$this->entityTypeId = $entity_type->id();
|
||||
$this->entityType = $entity_type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access check, to be called from
|
||||
* - module.routing.yml
|
||||
* - hook_entity_operation
|
||||
*
|
||||
* @param \Drupal\workflow_operations\WorkflowTransitionInterface|null $transition
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
*/
|
||||
public function revertAccess(WorkflowTransitionInterface $transition = NULL, AccountInterface $account = NULL, $return_as_object = TRUE) {
|
||||
if ($transition) {
|
||||
// Called from hook_entity_operation
|
||||
}
|
||||
else {
|
||||
// Called from module.routing.yml
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$route_match = \Drupal::routeMatch();
|
||||
$transition = $route_match->getParameter('workflow_transition');
|
||||
}
|
||||
return $this->access($transition, 'revert', $account, $return_as_object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::neutral();
|
||||
|
||||
$account = $user = workflow_current_user($account);
|
||||
|
||||
// This is only for Edit/Delete transition. For Add/create, use createAccess.
|
||||
switch ($entity->getEntityTypeId()) {
|
||||
|
||||
case 'workflow_transition':
|
||||
case 'workflow_scheduled_transition':
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $entity;
|
||||
|
||||
switch ($operation) {
|
||||
case 'revert':
|
||||
$is_owner = WorkflowManager::isOwner($user, $transition);
|
||||
$type_id = $transition->getWorkflowId();
|
||||
if ($transition->getFromSid() == $transition->getToSid()) {
|
||||
// No access for same state transitions.
|
||||
$result = AccessResult::forbidden();
|
||||
}
|
||||
elseif ($user->hasPermission("revert any $type_id workflow_transition")) {
|
||||
// OK, add operation.
|
||||
$result = AccessResult::allowed();
|
||||
}
|
||||
elseif ($is_owner && $user->hasPermission("revert own $type_id workflow_transition")) {
|
||||
// OK, add operation.
|
||||
$result = AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
// No access.
|
||||
$result = AccessResult::forbidden();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$result = parent::access($entity, $operation, $account, $return_as_object)->cachePerPermissions();
|
||||
break;
|
||||
} // End of switch ($operation).
|
||||
|
||||
break; // case
|
||||
|
||||
default: // $entity_type
|
||||
$result = AccessResult::forbidden();
|
||||
} // End of switch($entity->getEntityTypeId()).
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = [], $return_as_object = FALSE) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
$result = parent::createAccess($entity_bundle, $account, $context, TRUE)->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $transition, $operation, AccountInterface $account) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
return parent::checkAccess($transition, $operation, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
return AccessResult::allowedIf($account->hasPermission('create ' . $entity_bundle . ' content'))->cachePerPermissions();
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_operations;
|
||||
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Provides dynamic permissions for workflows of different types.
|
||||
*/
|
||||
class WorkflowPermissions extends \Drupal\workflow\WorkflowPermissions {
|
||||
|
||||
/**
|
||||
* Returns an array of workflow type permissions.
|
||||
*
|
||||
* @return array
|
||||
* The workflow type permissions.
|
||||
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
|
||||
*/
|
||||
//public function workflowTypePermissions() {
|
||||
// return parent::workflowTypePermissions();
|
||||
//}
|
||||
|
||||
/**
|
||||
* Returns a list of workflow permissions for a given workflow type.
|
||||
*
|
||||
* @param Workflow|\Drupal\workflow\Entity\Workflow $type
|
||||
* The workflow type.
|
||||
* @return array An associative array of permission names and descriptions.
|
||||
* An associative array of permission names and descriptions.
|
||||
*/
|
||||
protected function buildPermissions(Workflow $type) {
|
||||
$type_id = $type->id();
|
||||
$type_params = ['%type_name' => $type->label()];
|
||||
|
||||
return [
|
||||
// D7->D8-Conversion of 'edit workflow comment' permission to "edit own $type_id transition" (@see NodePermissions::edit any/own content).
|
||||
// D7->D8-Conversion of 'edit workflow comment' permission to "edit any $type_id transition" (@see NodePermissions::edit any/own content).
|
||||
"edit own $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Edit own comments', $type_params),
|
||||
'description' => t('Edit the comment of own executed state transitions.'),
|
||||
'restrict access' => TRUE,
|
||||
],
|
||||
"edit any $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Edit any comments', $type_params),
|
||||
'description' => t('Edit the comment of any executed state transitions.'),
|
||||
'restrict access' => TRUE,
|
||||
],
|
||||
/*
|
||||
// Workflow has no 'delete' permissions.
|
||||
"delete own $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Delete own content', $type_params),
|
||||
],
|
||||
"delete any $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Delete any content', $type_params),
|
||||
],
|
||||
*/
|
||||
// D7->D8-Conversion of 'revert workflow' permission to "revert any/own $type_id transition"
|
||||
"revert own $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Revert own state transition', $type_params),
|
||||
'description' => t('Allow user to revert own last executed state transition on entity.'),
|
||||
'restrict access' => TRUE,
|
||||
],
|
||||
"revert any $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Revert any state transition', $type_params),
|
||||
'description' => t('Allow user to revert any last executed state transition on entity.'),
|
||||
'restrict access' => TRUE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
name: 'Workflow operations'
|
||||
description: "Adds lesser-used Transition permissions (edit, revert, delete permissions)."
|
||||
package: Workflow
|
||||
type: module
|
||||
# core: 8.x
|
||||
|
||||
dependencies:
|
||||
- workflow
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Adds an 'Revert' link to the first workflow history row.
|
||||
*/
|
||||
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
use Drupal\workflow_operations\WorkflowAccessControlHandler;
|
||||
|
||||
/**
|
||||
* Implements hook_rules_event_info().
|
||||
*
|
||||
* @todo: Add support for every entity_type in Revert rules.
|
||||
*/
|
||||
function workflow_operations_rules_event_info() {
|
||||
workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
$events = [
|
||||
'workflow_state_reverted' => [
|
||||
'group' => t('Workflow'),
|
||||
'label' => t('Workflow state reverted'),
|
||||
'variables' => rules_events_node_variables(t('updated content'), TRUE),
|
||||
],
|
||||
];
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core hooks: Change the operations column in a Entity list.
|
||||
* Add a 'revert' operation.
|
||||
*
|
||||
* @see EntityListBuilder::getOperations()
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function workflow_operations_entity_operation(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
$operations = [];
|
||||
|
||||
// Check correct entity type.
|
||||
if (!in_array($entity->getEntityTypeId(), ['workflow_transition'])) {
|
||||
return $operations;
|
||||
}
|
||||
|
||||
$user = workflow_current_user();
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $entity;
|
||||
|
||||
// Check access, to avoid that user sees a revert link, but is not allowed to
|
||||
// view the revert form. Use $first to check only once per page.
|
||||
static $first = TRUE;
|
||||
|
||||
if ($first) {
|
||||
$handler = new WorkflowAccessControlHandler();
|
||||
$is_allowed = $handler->access($transition, 'revert', $user, FALSE);
|
||||
if (!$is_allowed) {
|
||||
// No access. We may try at next row.
|
||||
return $operations;
|
||||
}
|
||||
}
|
||||
|
||||
// Only mark the first row.
|
||||
if ($first) {
|
||||
// Some states are not fit to revert to. In each of these cases, prohibit
|
||||
// to revert to an even older state.
|
||||
if ((!$from_state = $transition->getFromState()) || !$from_state->isActive() || $from_state->isCreationState()) {
|
||||
$first = FALSE;
|
||||
}
|
||||
else {
|
||||
// Let's ask other modules if the reversion is allowed. Reversing old and new sid!
|
||||
$permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition revert', $transition, $user]); // @todo: add revert operation.
|
||||
// Stop if a module says so.
|
||||
if (!in_array(FALSE, $permitted, TRUE)) {
|
||||
// Add the operation - it is not vetoed.
|
||||
// @todo: Pluggability problem: the revert_form route is fixed in WorkflowTransition Annotation. Add to some alter() function.
|
||||
$operations['revert'] = [
|
||||
'title' => t('Revert to last state'),
|
||||
'url' => \Drupal\Core\Url::fromRoute('entity.workflow_transition.revert_form', ['workflow_transition' => $transition->id()]),
|
||||
'query' => \Drupal::destination()->getAsArray(), // Add destination.
|
||||
'weight' => 50,
|
||||
];
|
||||
|
||||
$first = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_operation_alter
|
||||
* This hook is not needed in this case.
|
||||
*/
|
||||
//function workflow_operations_entity_operation_alter(array $operations, \Drupal\Core\Entity\EntityInterface $entity) {
|
||||
//}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
permission_callbacks:
|
||||
- \Drupal\workflow_operations\WorkflowPermissions::workflowTypePermissions
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
### Workflow Transition CRUD
|
||||
entity.workflow_transition.revert_form:
|
||||
path: '/workflow_transition/{workflow_transition}/revert'
|
||||
defaults:
|
||||
_entity_form: 'workflow_transition.revert'
|
||||
_title: 'Revert Workflow transition'
|
||||
requirements:
|
||||
# _entity_access: 'workflow_transition.revert'
|
||||
_custom_access: '\Drupal\workflow_operations\WorkflowAccessControlHandler::revertAccess'
|
||||
# _access: 'true'
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_ui\Controller;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of Workflow entities.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\Workflow
|
||||
*/
|
||||
class WorkflowListBuilder extends ConfigEntityListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
// return 'workflow_form';
|
||||
return parent::getFormId();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Building the header and content lines for the contact list.
|
||||
*
|
||||
* Calling the parent::buildHeader() adds a column for the possible actions
|
||||
* and inserts the 'edit' and 'delete' links as defined for the entity type.
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['id'] = $this->t('ID');
|
||||
$header['label'] = $this->t('Label');
|
||||
$header['status'] = $this->t('Status');
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
/* @var $entity \Drupal\workflow\Entity\Workflow */
|
||||
$row['id'] = $entity->id();
|
||||
$row['label'] = $this->getLabel($entity);
|
||||
$row['status'] = ''; // TODO $entity->getStatus();
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
|
||||
/* @var $workflow \Drupal\workflow\Entity\Workflow */
|
||||
$workflow = $entity;
|
||||
|
||||
// Do not delete a Workflow if it contains content.
|
||||
if (isset($operations['delete']) && !$workflow->isDeletable()) {
|
||||
unset ($operations['delete']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow modules to insert their own workflow operations to the list.
|
||||
*/
|
||||
// This is what EntityListBuilder::getOperations() does:
|
||||
// $operations = $this->getDefaultOperations($entity);
|
||||
// $operations += $this->moduleHandler()->invokeAll('entity_operation', [$entity]);
|
||||
// $this->moduleHandler->alter('entity_operation', $operations, $entity);
|
||||
|
||||
// In D8, the interface of below hook_workflow_operations has changed a bit.
|
||||
// @see EntityListBuilder::getOperations, workflow_operations, workflow.api.php.
|
||||
$operations += $this->moduleHandler()->invokeAll('workflow_operations', ['workflow', $workflow]);
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
|
||||
/**
|
||||
* Allow modules to insert their own top_action links to the list, like cleanup module.
|
||||
*
|
||||
* This is not done anymore via the workflow hook.
|
||||
* Instead, for an example:
|
||||
* @see workflow_ui.links.action.yml
|
||||
* @see workflow.api.php under 'hook_workflow_operations'.
|
||||
*/
|
||||
// $top_actions = \Drupal::moduleHandler()
|
||||
// ->invokeAll('workflow_operations', ['top_actions', NULL]);
|
||||
// $top_actions_args = [
|
||||
// 'links' => $top_actions,
|
||||
// 'attributes' => ['class' => ['inline', 'action-links']],
|
||||
// ];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_ui\Controller;
|
||||
|
||||
use Drupal\Core\Config\Entity\DraggableListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
|
||||
/**
|
||||
* Defines a class to build a draggable listing of Workflow State entities.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowState
|
||||
*/
|
||||
class WorkflowStateListBuilder extends DraggableListBuilder {
|
||||
|
||||
// @TODO D8-port WorkflowStateListBuilder: enable machine_name: interactive WorkflowState element.
|
||||
|
||||
/**
|
||||
* Load the Transitions, and filter for Workflow type.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load() {
|
||||
$entities = [];
|
||||
|
||||
// Get the Workflow from the page.
|
||||
/** @var $workflow \Drupal\workflow\Entity\Workflow */
|
||||
if (!$workflow = workflow_url_get_workflow()) {
|
||||
// @todo: Generate error message.
|
||||
return $entities;
|
||||
}
|
||||
$wid = $url_wid = $workflow->id();
|
||||
|
||||
$entities = parent::load();
|
||||
foreach ($entities as $key => $entity) {
|
||||
if (!isset($entity->wid) || $entity->wid != $wid) {
|
||||
unset($entities[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_state_form';
|
||||
//return parent::getFormId();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Building the header and content lines for the contact list.
|
||||
*
|
||||
* Calling the parent::buildHeader() adds a column for the possible actions
|
||||
* and inserts the 'edit' and 'delete' links as defined for the entity type.
|
||||
*/
|
||||
public function buildHeader() {
|
||||
// The column 'weight' is added magically in the draggable EntityList.
|
||||
// $header['weight'] = $this->t('Weight');
|
||||
// Some columns are not welcome in the list.
|
||||
// $header['module'] = $this->t('Module');
|
||||
// $header['wid'] = $this->t('Workflow');
|
||||
// $header['sysid'] = $this->t('Sysid');
|
||||
// Column 'label' is manipulated in parent::buildForm(). So, we use 'label_new'.
|
||||
// $header['label'] = $this->t('Label');
|
||||
$header['label_new'] = $this->t('Label');
|
||||
$header['id'] = $this->t('ID');
|
||||
$header['sysid'] = '';
|
||||
$header['status'] = $this->t('Active');
|
||||
$header['reassign'] = $this->t('Reassign');
|
||||
$header['count'] = $this->t('Count');
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row = [];
|
||||
|
||||
// Get the Workflow from the page.
|
||||
/** @var $workflow \Drupal\workflow\Entity\Workflow */
|
||||
if (!$workflow = workflow_url_get_workflow()) {
|
||||
workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
}
|
||||
$wid = $url_wid = $workflow->id();
|
||||
|
||||
/** @var $entity \Drupal\workflow\Entity\WorkflowState */
|
||||
$state = $entity;
|
||||
$sid = $state->id();
|
||||
$label = $state->label();
|
||||
$count = $state->count();
|
||||
|
||||
// Build select options for reassigning states.
|
||||
// We put a blank state first for validation.
|
||||
$state_options = ['' => ' '];
|
||||
$state_options += workflow_get_workflow_state_names($wid, FALSE);
|
||||
|
||||
// Make it impossible to reassign to the same state that is disabled.
|
||||
if ($state->isCreationState() || !$sid || !$state->isActive()) {
|
||||
$current_state_options = [];
|
||||
}
|
||||
else {
|
||||
$current_state = [$sid => $state_options[$sid]];
|
||||
$current_state_options = array_diff($state_options, $current_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build the Row.
|
||||
*/
|
||||
// The column 'weight' is added magically in the draggable EntityList.
|
||||
// $row['weight'] = $state->weight;
|
||||
// Some columns are not welcome in the list.
|
||||
// $row['module'] = $state->getModule();
|
||||
// $row['wid'] = $state->getWorkflow();
|
||||
// Column 'label' is manipulated in parent::buildForm(). So, we use 'label_new'.
|
||||
// $row['label'] = $state->label();
|
||||
$row['label_new'] = [
|
||||
'#markup' => $label,
|
||||
'#type' => 'textfield',
|
||||
'#size' => 30,
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $label,
|
||||
'#title' => NULL, // This hides the red 'required' asterisk.
|
||||
// '#required' => TRUE,
|
||||
];
|
||||
$row['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#title' => NULL, // This hides the red 'required' asterisk.
|
||||
'#size' => 30,
|
||||
'#description' => NULL,
|
||||
'#disabled' => TRUE, // !$state->isNew(),
|
||||
'#default_value' => $state->id(),
|
||||
// N.B.: Keep machine_name in WorkflowState and ~ListBuilder aligned.
|
||||
'#required' => FALSE,
|
||||
// @TODO D8-port: enable machine_name: interactive WorkflowState element.
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'], // Local helper function, at the bottom of this class.
|
||||
// 'source' => ['label_new'],
|
||||
'source' => ['states', $state->id(), 'label_new'],
|
||||
// 'replace_pattern' =>'([^a-z0-9_]+)|(^custom$)',
|
||||
'replace_pattern' => '[^a-z0-9_()]+', // Added '()' characters from exclusion list since creation state has it.
|
||||
'error' => $this->t('The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores.'),
|
||||
],
|
||||
];
|
||||
$row['sysid'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $state->sysid,
|
||||
];
|
||||
$row['status'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $state->isActive(),
|
||||
];
|
||||
// The new value of states that are inactivated.
|
||||
$row['reassign'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $current_state_options,
|
||||
];
|
||||
$row['count'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $count,
|
||||
'#markup' => $count,
|
||||
];
|
||||
|
||||
// Disable some properties for Creation state.
|
||||
if ($state->isCreationState()) {
|
||||
$row['status']['#disabled'] = TRUE;
|
||||
$row['reassign']['#type'] = 'hidden';
|
||||
$row['reassign']['#disabled'] = TRUE;
|
||||
}
|
||||
// New state and disabled states cannot be reassigned.
|
||||
if (!$sid || !$state->isActive() || ($count == 0) ) {
|
||||
$row['reassign']['#type'] = 'hidden';
|
||||
$row['reassign']['#disabled'] = TRUE;
|
||||
}
|
||||
// Disabled states cannot be renamed (and is a visual clue, too).
|
||||
if (!$state->isActive()) {
|
||||
$row['label_new']['#disabled'] = TRUE;
|
||||
}
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Get the Workflow from the page.
|
||||
/** @var $workflow \Drupal\workflow\Entity\Workflow */
|
||||
if (!$workflow = workflow_url_get_workflow()) {
|
||||
return $form;
|
||||
}
|
||||
$wid = $workflow->getWorkflowId();
|
||||
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
// Add a sticky header.
|
||||
$form[$this->entitiesKey] += [
|
||||
'#sticky' => TRUE,
|
||||
];
|
||||
|
||||
// Build select options for reassigning states.
|
||||
// We put a blank state first for validation.
|
||||
$state_options = workflow_get_workflow_state_names($wid, FALSE);
|
||||
// Is this the last state available?
|
||||
$form['#last_mohican'] = (count($state_options) == 1);
|
||||
|
||||
// Create a placeholder WorkflowState (It must NOT be saved to DB). Add it to the item list.
|
||||
$sid = '';
|
||||
$placeholder = $workflow->createState($sid, FALSE);
|
||||
$placeholder->set('label', '');
|
||||
$this->entities['placeholder'] = $placeholder;
|
||||
$form['entities']['placeholder'] = $this->buildRow($placeholder);
|
||||
|
||||
// Rename 'submit' button.
|
||||
$form['actions']['submit']['#value'] = t('Save');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
|
||||
/** @var $state \Drupal\workflow\Entity\WorkflowState */
|
||||
$state = $entity;
|
||||
|
||||
// $workflow = $state->getWorkflow();
|
||||
|
||||
/**
|
||||
* Allow modules to insert their own workflow operations to the list.
|
||||
*/
|
||||
// This is what EntityListBuilder::getOperations() does:
|
||||
// $operations = $this->getDefaultOperations($entity);
|
||||
// $operations += $this->moduleHandler()->invokeAll('entity_operation', [$entity]);
|
||||
// $this->moduleHandler->alter('entity_operation', $operations, $entity);
|
||||
|
||||
// In D8, the interface of below hook_workflow_operations has changed a bit.
|
||||
// @see EntityListBuilder::getOperations, workflow_operations, workflow.api.php.
|
||||
$operations += $this->moduleHandler()->invokeAll('workflow_operations', ['state', $state]);
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $sid => $value) {
|
||||
if (isset($this->entities[$sid])) {
|
||||
/** @var $state WorkflowState */
|
||||
$state = $this->entities[$sid];
|
||||
|
||||
// Does user want to deactivate the state (reassign current content)?
|
||||
if ($sid && $value['status'] == 0 && $state->isActive()) {
|
||||
workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', ''); // @todo D8-port: still test this snippet.
|
||||
$args = ['%state' => $state->label()];
|
||||
// Does that state have content in it?
|
||||
if ($value['count'] > 0 && empty($value['reassign'])) {
|
||||
if ($form['#last_mohican']) {
|
||||
$message = 'Since you are deleting the last available
|
||||
workflow state in this workflow, all content items
|
||||
which are in that state will have their workflow state
|
||||
removed.';
|
||||
drupal_set_message($this->t($message, $args), 'warning');
|
||||
}
|
||||
else {
|
||||
$message = 'The %state state has content; you must
|
||||
reassign the content to another state.';
|
||||
$form_state->setErrorByName("states'][$sid]['reassign'", $this->t($message, $args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the machine_name for new states.
|
||||
// N.B.: Keep machine_name in WorkflowState and ~ListBuilder aligned.
|
||||
if ($value['label_new'] && !$value['id']) {
|
||||
//$message = 'Machine name is required.';
|
||||
//$form_state->setErrorByName('machine_name', $this->t($message));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Overrides DraggableListBuilder::submitForm().
|
||||
* The WorkflowState entities are always saved.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// parent::submitForm($form, $form_state);
|
||||
|
||||
// Get the Workflow from the page.
|
||||
/** @var $workflow \Drupal\workflow\Entity\Workflow */
|
||||
if (!$workflow = workflow_url_get_workflow()) {
|
||||
return ;
|
||||
}
|
||||
|
||||
// The default min_weight is -10. Work with it.
|
||||
$creation_weight = -11;
|
||||
$max_weight = -9;
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $sid => $value) {
|
||||
if (isset($this->entities[$sid])) {
|
||||
/** @var $state WorkflowState */
|
||||
$state = $this->entities[$sid];
|
||||
|
||||
// Is the new state name empty?
|
||||
if (empty($value['label_new'])) {
|
||||
// No new state entered, so skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Does user want to deactivate the state (reassign current content)?
|
||||
if ($sid && $value['status'] == 0 && $state->isActive()) {
|
||||
$new_sid = $value['reassign'];
|
||||
$new_state = WorkflowState::load($new_sid);
|
||||
|
||||
$args = [
|
||||
'%workflow' => $workflow->label(),
|
||||
'%old_state' => $state->label(),
|
||||
'%new_state' => isset($new_state) ? $new_state->label() : '',
|
||||
];
|
||||
|
||||
if ($value['count'] > 0) {
|
||||
if ($form['#last_mohican']) {
|
||||
$new_sid = NULL; // Do not reassign to new state.
|
||||
$message = 'Removing workflow states from content in the %workflow.';
|
||||
drupal_set_message($this->t($message, $args));
|
||||
}
|
||||
else {
|
||||
// Prepare the state delete function.
|
||||
$message = 'Reassigning content from %old_state to %new_state.';
|
||||
drupal_set_message($this->t($message, $args));
|
||||
}
|
||||
}
|
||||
// Delete the old state without orphaning content, move them to the new state.
|
||||
$state->deactivate($new_sid);
|
||||
|
||||
$message = $this->t('Deactivated workflow state %old_state in %workflow.', $args);
|
||||
\Drupal::logger('workflow')->notice($message, []);
|
||||
drupal_set_message($message);
|
||||
}
|
||||
|
||||
// Set a proper weight to the new state.
|
||||
$max_weight = max($max_weight, $state->get($this->weightKey));
|
||||
|
||||
// Is this a new state?
|
||||
if ($sid == 'placeholder' && empty(!$value['label_new'])) {
|
||||
// New state, add it.
|
||||
$state->set('id', $value['id']);
|
||||
// Set a proper weight to the new state.
|
||||
$state->set($this->weightKey, $max_weight + 1);
|
||||
}
|
||||
elseif ($value['sysid'] == WORKFLOW_CREATION_STATE) {
|
||||
// Set a proper weight to the creation state.
|
||||
$state->set($this->weightKey, $creation_weight);
|
||||
}
|
||||
else {
|
||||
$state->set($this->weightKey, $value['weight']);
|
||||
}
|
||||
$state->set('label', $value['label_new']);
|
||||
$state->set('status', $value['status']);
|
||||
|
||||
$state->save();
|
||||
}
|
||||
|
||||
}
|
||||
drupal_set_message(t('The Workflow states have been updated.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate duplicate machine names. Function registered in 'machine_name'
|
||||
* form element.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $element
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function exists($name, array $element, FormStateInterface $form_state) {
|
||||
$state_names = [];
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $sid => $value) {
|
||||
$state_names[] = $value['id'];
|
||||
}
|
||||
$state_names = array_map('strtolower', $state_names);
|
||||
$result = array_unique(array_diff_assoc($state_names, array_unique($state_names)));
|
||||
|
||||
if (in_array($name, $result)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_ui\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
|
||||
/**
|
||||
* Returns responses for Workflow UI routes.
|
||||
*/
|
||||
class WorkflowUiController extends ControllerBase {
|
||||
/**
|
||||
* Returns the settings page.
|
||||
*
|
||||
* @return array
|
||||
* Renderable array.
|
||||
*/
|
||||
public function settingsForm() {
|
||||
$element = [
|
||||
'#markup' => 'Workflow settings form is not implemented yet.',
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_ui\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
|
||||
/**
|
||||
* Defines a class to build a draggable listing of Workflow Config Transitions entities.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
*/
|
||||
abstract class WorkflowConfigTransitionFormBase extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* The key to use for the form element containing the entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entitiesKey = 'entities';
|
||||
|
||||
/**
|
||||
* The WorkflowConfigTransition form type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The entities being listed.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface[]
|
||||
*/
|
||||
protected $entities = [];
|
||||
|
||||
/**
|
||||
* The workflow object.
|
||||
*
|
||||
* @var \Drupal\workflow\Entity\Workflow
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct() {
|
||||
// The $this->type and $this->entitiesKey must be set in the var section.
|
||||
|
||||
// Get the Workflow from the page.
|
||||
$this->workflow = workflow_url_get_workflow();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'workflow_config_transition_' . $this->type . '_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Create an $entity for every ConfigTransition.
|
||||
*/
|
||||
public function load() {
|
||||
$entities = [];
|
||||
|
||||
$entity_type = $this->entitiesKey;
|
||||
$workflow = $this->workflow;
|
||||
$states = $workflow->getStates($all = 'CREATION');
|
||||
|
||||
if ($states) {
|
||||
/* @var $from_state WorkflowState */
|
||||
/* @var $to_state WorkflowState */
|
||||
switch ($entity_type) {
|
||||
case 'workflow_state':
|
||||
foreach ($states as $from_state) {
|
||||
$from_sid = $from_state->id();
|
||||
$entities[$from_sid] = $from_state;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workflow_config_transition':
|
||||
foreach ($states as $from_state) {
|
||||
$from_sid = $from_state->id();
|
||||
foreach ($states as $to_state) {
|
||||
$to_sid = $to_state->id();
|
||||
|
||||
// Don't allow transition TO (creation).
|
||||
if ($to_state->isCreationState()) {
|
||||
continue;
|
||||
}
|
||||
// // Only allow transitions from $from_state.
|
||||
// if ($state->id() <> $from_state->id()) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// Load existing config_transitions. Create if not found.
|
||||
$config_transitions = $workflow->getTransitionsByStateId($from_sid, $to_sid);
|
||||
if (!$config_transition = reset($config_transitions)) {
|
||||
$config_transition = $workflow->createTransition($from_sid, $to_sid);
|
||||
}
|
||||
$entities[] = $config_transition;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
drupal_set_message(t('Improper type provided in load method.'), 'error');
|
||||
\Drupal::logger('workflow_ui')->notice('Improper type provided in load method.', []);
|
||||
return $entities;
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
// abstract public function buildHeader();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
// abstract public function buildRow(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = [];
|
||||
|
||||
if (!$this->workflow) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/*
|
||||
* Begin of copied code DraggableListBuilder::buildForm()
|
||||
*/
|
||||
$form[$this->entitiesKey] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $this->buildHeader(),
|
||||
'#sticky' => TRUE,
|
||||
'#empty' => t('There is no @label yet.', ['@label' => 'Transition']),
|
||||
'#tabledrag' => [['action' => 'order', 'relationship' => 'sibling', 'group' => 'weight', ], ],
|
||||
];
|
||||
|
||||
$this->entities = $this->load();
|
||||
$delta = 10;
|
||||
// Change the delta of the weight field if have more than 20 entities.
|
||||
if (!empty($this->weightKey)) {
|
||||
$count = count($this->entities);
|
||||
if ($count > 20) {
|
||||
$delta = ceil($count / 2);
|
||||
}
|
||||
}
|
||||
foreach ($this->entities as $entity) {
|
||||
$row = $this->buildRow($entity);
|
||||
if (isset($row['label'])) {
|
||||
$row['label'] = ['#markup' => $row['label']];
|
||||
}
|
||||
if (isset($row['weight'])) {
|
||||
$row['weight']['#delta'] = $delta;
|
||||
}
|
||||
$form[$this->entitiesKey][$entity->id()] = $row;
|
||||
}
|
||||
/*
|
||||
* End of copied code DraggableListBuilder::buildForm()
|
||||
*/
|
||||
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_ui\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a draggable listing of Workflow Config Transitions entities.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
*/
|
||||
class WorkflowConfigTransitionLabelForm extends WorkflowConfigTransitionFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entitiesKey = 'workflow_config_transition';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $type = 'label';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = [
|
||||
'from' => t('Transition from'),
|
||||
'to' => t('Transition to'),
|
||||
'label_new' => t('label'),
|
||||
'config_transition' => '',
|
||||
];
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row = [];
|
||||
|
||||
$workflow = $this->workflow;
|
||||
if ($workflow) {
|
||||
/* @var $entity \Drupal\workflow\Entity\WorkflowConfigTransition */
|
||||
$config_transition = $entity;
|
||||
|
||||
static $previous_from_sid = -1;
|
||||
// Get transitions, sorted by weight of the old state.
|
||||
$from_state = $config_transition->getFromState();
|
||||
$to_state = $config_transition->getToState();
|
||||
$from_sid = $from_state->id();
|
||||
|
||||
// Skip the transitions without any roles.
|
||||
$skip = TRUE;
|
||||
foreach ($config_transition->roles as $rid => $active) {
|
||||
if ($active) {
|
||||
$skip = FALSE;
|
||||
}
|
||||
}
|
||||
if ($skip == TRUE && ($from_state != $to_state)) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
$row['from'] = [
|
||||
'#type' => 'value',
|
||||
'#markup' => ($previous_from_sid != $from_sid) ? $from_state->label() : '"',
|
||||
];
|
||||
$row['to'] = [
|
||||
'#type' => 'value',
|
||||
'#markup' => $to_state->label(),
|
||||
];
|
||||
$row['label_new'] = [
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $config_transition->get('label'),
|
||||
];
|
||||
$row['config_transition'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $config_transition,
|
||||
];
|
||||
|
||||
$previous_from_sid = $from_sid;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $key => $value) {
|
||||
$new_label = trim($value['label_new']);
|
||||
$value['config_transition']
|
||||
->set('label', $new_label)
|
||||
->save();
|
||||
}
|
||||
|
||||
drupal_set_message(t('The transition labels have been saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow_ui\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Entity\WorkflowConfigTransition;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of Workflow Config Transitions entities.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
*/
|
||||
class WorkflowConfigTransitionRoleForm extends WorkflowConfigTransitionFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entitiesKey = 'workflow_state';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $type = 'permission';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = [];
|
||||
|
||||
$workflow = $this->workflow;
|
||||
$states = $workflow->getStates($all = 'CREATION');
|
||||
if ($states) {
|
||||
$header['label_new'] = t('From \ To');
|
||||
|
||||
/* @var $state WorkflowState */
|
||||
foreach ($states as $state) {
|
||||
// Don't allow transition TO (creation).
|
||||
if (!$state->isCreationState()) {
|
||||
$header[$state->id()] = t('@label', ['@label' => $state->label()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Builds a row for the following table:
|
||||
* Transitions, for example:
|
||||
* 18 => [
|
||||
* 20 => [
|
||||
* 'author' => 1,
|
||||
* 1 => 0,
|
||||
* 2 => 1,
|
||||
* ]
|
||||
* ]
|
||||
* means the transition from state 18 to state 20 can be executed by
|
||||
* the content author or a user in role 2. The $transitions array should
|
||||
* contain ALL transitions for the workflow.
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row = [];
|
||||
|
||||
$workflow = $this->workflow;
|
||||
if ($workflow) {
|
||||
// Each $entity is a from-state.
|
||||
/* @var $entity \Drupal\workflow\Entity\WorkflowState */
|
||||
$from_state = $entity;
|
||||
$from_sid = $from_state->id();
|
||||
|
||||
/* @var $states WorkflowState[] */
|
||||
$states = $workflow->getStates($all = 'CREATION');
|
||||
if ($states) {
|
||||
// Only get the roles with proper permission + Author role.
|
||||
$type_id = $workflow->id();
|
||||
$roles = workflow_get_user_role_names("create $type_id workflow_transition");
|
||||
// Prepare default value for 'stay_on_this_state'.
|
||||
$allow_all_roles = []; // array_combine (array_keys($roles) , array_keys($roles));
|
||||
|
||||
/* @var $state WorkflowState */
|
||||
foreach ($states as $state) {
|
||||
$row['to'] = [
|
||||
'#type' => 'value',
|
||||
'#markup' => t('@label', ['@label' => $from_state->label()]),
|
||||
];
|
||||
|
||||
/* @var $to_state WorkflowState */
|
||||
foreach ($states as $to_state) {
|
||||
// Don't allow transition TO (creation).
|
||||
if ($to_state->isCreationState()) {
|
||||
continue;
|
||||
}
|
||||
// Only allow transitions from $from_state.
|
||||
if ($state->id() <> $from_state->id()) {
|
||||
continue;
|
||||
}
|
||||
$to_sid = $to_state->id();
|
||||
$stay_on_this_state = ($to_sid == $from_sid);
|
||||
|
||||
// Load existing config_transitions. Create if not found.
|
||||
$config_transitions = $workflow->getTransitionsByStateId($from_sid, $to_sid);
|
||||
if (!$config_transition = reset($config_transitions)) {
|
||||
$config_transition = $workflow->createTransition($from_sid, $to_sid);
|
||||
}
|
||||
|
||||
$row[$to_sid]['workflow_config_transition'] = ['#type' => 'value', '#value' => $config_transition, ];
|
||||
$row[$to_sid]['roles'] = [
|
||||
'#type' => $stay_on_this_state ? 'checkboxes' : 'checkboxes',
|
||||
'#options' => $stay_on_this_state ? [] : $roles,
|
||||
'#disabled' => $stay_on_this_state,
|
||||
// When $stay_on_this_state, allow all roles.
|
||||
'#default_value' => $stay_on_this_state ? $allow_all_roles : $config_transition->roles,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$workflow = $this->workflow;
|
||||
// Make sure 'author' is checked for (creation) -> [something].
|
||||
$creation_state = $workflow->getCreationState();
|
||||
|
||||
if (empty($form_state->getValue($this->entitiesKey))) {
|
||||
$author_has_permission = TRUE;
|
||||
}
|
||||
else {
|
||||
$author_has_permission = FALSE;
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $from_sid => $to_data) {
|
||||
foreach ($to_data as $to_sid => $transition_data) {
|
||||
if ($from_sid == $to_sid) {
|
||||
// Same-state-transition do not count.
|
||||
}
|
||||
elseif (!empty($transition_data['roles'][WORKFLOW_ROLE_AUTHOR_RID])) {
|
||||
$author_has_permission = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$author_has_permission) {
|
||||
$form_state->setErrorByName('id', t('Please give the author permission to go from %creation to at least one state!',
|
||||
['%creation' => $creation_state->label()]));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
foreach ($form_state->getValue($this->entitiesKey) as $from_sid => $to_data) {
|
||||
foreach ($to_data as $transition_data) {
|
||||
/* @var $config_transition WorkflowConfigTransition */
|
||||
if (isset($transition_data['workflow_config_transition'])) {
|
||||
$config_transition = $transition_data['workflow_config_transition'];
|
||||
$config_transition->roles = $transition_data['roles'];
|
||||
$config_transition->save();
|
||||
}
|
||||
else {
|
||||
// Should not be possible.
|
||||
// $config_transition = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drupal_set_message(t('The workflow was updated.'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
name: 'Workflow UI'
|
||||
description: 'Administrative UI for Workflow.'
|
||||
package: Workflow
|
||||
type: module
|
||||
# core: 8.x
|
||||
# version: VERSION
|
||||
|
||||
configure: entity.workflow_type.collection
|
||||
|
||||
dependencies:
|
||||
- workflow
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the workflow_ui module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function workflow_ui_install() {
|
||||
$url = \Drupal\Core\Url::fromRoute('user.admin_permissions', [],
|
||||
['fragment' => 'module-workflow']);
|
||||
$message = t("Please review which roles may 'participate in workflows'
|
||||
<a href=':url'>on the Permissions page</a>.",
|
||||
[':url' => $url->toString()]);
|
||||
drupal_set_message($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal 8 updates.
|
||||
*/
|
||||
function workflow_ui_update_8001(&$sandbox) {
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Action links for Workflow UI.
|
||||
|
||||
### Workflow related actions.
|
||||
workflow_type_add_action:
|
||||
route_name: entity.workflow_type.add_form
|
||||
title: 'Add workflow'
|
||||
weight: 1
|
||||
appears_on:
|
||||
- entity.workflow_type.collection
|
||||
|
||||
### Workflow State related actions.
|
||||
|
||||
### Workflow Transition related actions.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# Menu links for Workflow UI.
|
||||
|
||||
workflow.ui.workflows:
|
||||
title: 'Workflow'
|
||||
route_name: entity.workflow_type.collection
|
||||
parent: system.admin_config_workflow
|
||||
description: 'Workflow administration overview.'
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Menu tasks for Workflow UI.
|
||||
|
||||
workflow.ui.settings:
|
||||
title: 'Settings'
|
||||
route_name: workflow.ui.settings
|
||||
base_route: entity.workflow_type.collection
|
||||
weight: 9
|
||||
workflow.ui.workflows:
|
||||
title: 'Workflows'
|
||||
route_name: entity.workflow_type.collection
|
||||
base_route: entity.workflow_type.collection
|
||||
weight: 1
|
||||
|
||||
workflow.ui.workflow_states:
|
||||
title: 'States'
|
||||
route_name: entity.workflow_state.collection
|
||||
base_route: entity.workflow_type.edit_form
|
||||
weight: -4
|
||||
workflow.ui.workflow_transitions:
|
||||
title: 'Transitions'
|
||||
route_name: entity.workflow_transition.collection
|
||||
base_route: entity.workflow_type.edit_form
|
||||
weight: -3
|
||||
workflow.ui.workflow_labels:
|
||||
title: 'Transition labels'
|
||||
route_name: entity.workflow_label.collection
|
||||
base_route: entity.workflow_type.edit_form
|
||||
weight: -2
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides administrative UI for workflow.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function workflow_ui_help($route_name, RouteMatchInterface $route_match) {
|
||||
|
||||
switch ($route_name) {
|
||||
case 'entity.workflow_transition.field_ui_fields':
|
||||
return t('This page allows you to add fields to the Workflow form.
|
||||
Normally this is an advanced action, which is not needed in
|
||||
regular use cases.');
|
||||
|
||||
case 'entity.workflow_type.collection':
|
||||
return t('This page allows you to maintain Workflows. Once a workflow is
|
||||
created, you can maintain your entity type and add a Field of type
|
||||
\'Workflow\'.');
|
||||
|
||||
case 'entity.workflow_type.add_form':
|
||||
return '';
|
||||
|
||||
case 'entity.workflow_state.collection':
|
||||
return t("To create a new state, enter its name in the last row of the
|
||||
'State' column. Check the 'Active' box to make it effective. You may
|
||||
also drag it to the appropriate position.") . '<br />'
|
||||
. t("A state must be marked as active, to be available in the
|
||||
workflow's transitions.") . '<br />'
|
||||
. t("If you wish to inactivate a state that has content (i.e. count is
|
||||
not zero), then you need to select a state to which to reassign that
|
||||
content.");
|
||||
|
||||
case 'entity.workflow_transition.collection':
|
||||
$url = \Drupal\Core\Url::fromRoute('user.admin_permissions', [],
|
||||
['fragment' => 'module-workflow']);
|
||||
return t('You are currently viewing the possible transitions to and from
|
||||
workflow states. The state is shown in the left column; the state to be
|
||||
moved to is to the right. For each transition, check the box next to
|
||||
the role(s) that may initiate the transition. For example, if only the
|
||||
"production editor" role may move content from Review state to the
|
||||
Published state, check the box next to "production editor". The author
|
||||
role is built in and refers to the user who authored the content.')
|
||||
. '<br /><i>'
|
||||
. t("If not all roles are in the list, please review which roles may
|
||||
'participate in workflows' on <a href=':url'>the Permissions page</a>.
|
||||
On that page, uncheck the 'Authenticated user' role temporarily to
|
||||
view the permissions of each separate role.</i>",
|
||||
[':url' => $url->toString()]);
|
||||
|
||||
case 'entity.workflow_label.collection':
|
||||
return t('You can add labels to transitions if you don\'t like the
|
||||
standard state labels. They will modify the Workflow form options, so
|
||||
specific workflow transitions can have their own labels, relative to
|
||||
the beginning and ending states. Rather than showing the user a
|
||||
workflow box containing options like "review required" as a state in
|
||||
the workflow, it could say "move to the editing department for grammar
|
||||
review".');
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine Workflow from Workflow UI URL.
|
||||
*
|
||||
* @deprecated : @see workflow_url_get_workflow .
|
||||
*/
|
||||
function workflow_ui_url_get_workflow($url = '' ) {
|
||||
return workflow_url_get_workflow($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine the title of the page.
|
||||
*
|
||||
* @deprecated : @see workflow_url_get_title .
|
||||
*/
|
||||
function workflow_ui_url_get_title() {
|
||||
return workflow_url_get_title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine Workflow from Workflow UI URL.
|
||||
*
|
||||
* @deprecated : @see workflow_url_get_form_type .
|
||||
*/
|
||||
function workflow_ui_url_get_form_type($url = '' ) {
|
||||
return workflow_url_get_form_type($url);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# Declaration of Workflow UI routings.
|
||||
|
||||
# copy from WorkflowUIController.php
|
||||
# https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/group/entity_api/8
|
||||
|
||||
### Workflow settings form
|
||||
workflow.ui.settings:
|
||||
path: '/admin/config/workflow/workflow/settings'
|
||||
defaults:
|
||||
_controller: '\Drupal\workflow_ui\Controller\WorkflowUiController::settingsForm'
|
||||
_title: 'Workflow settings'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
|
||||
|
||||
### Workflow States
|
||||
entity.workflow_state.collection:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}/states'
|
||||
defaults:
|
||||
_entity_list: 'workflow_state'
|
||||
# _controller: '\workflow\EntityWorkflowUIController::adminOverview'
|
||||
_title: 'Edit Workflow'
|
||||
_title_callback: 'workflow_url_get_title'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
|
||||
|
||||
### Workflow Transitions
|
||||
entity.workflow_transition.collection:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}/transition_roles'
|
||||
defaults:
|
||||
_form: 'Drupal\workflow_ui\Form\WorkflowConfigTransitionRoleForm'
|
||||
_title: 'Edit Workflow'
|
||||
_title_callback: 'workflow_url_get_title'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
|
||||
### Workflow Labels
|
||||
entity.workflow_label.collection:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}/transition_labels'
|
||||
defaults:
|
||||
_form: 'Drupal\workflow_ui\Form\WorkflowConfigTransitionLabelForm'
|
||||
_title: 'Edit Workflow'
|
||||
_title_callback: 'workflow_url_get_title'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Controller;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Datetime\DateFormatter;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\Controller\EntityListController;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Returns responses for Workflow routes.
|
||||
*/
|
||||
class WorkflowTransitionListController extends EntityListController implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatter
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs an object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
|
||||
* The date formatter service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
*/
|
||||
public function __construct(DateFormatter $date_formatter, RendererInterface $renderer) {
|
||||
// These parameters are taken from some random other controller.
|
||||
$this->dateFormatter = $date_formatter;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('date.formatter'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an overview table of older revisions of a node,
|
||||
* but only if this::historyAccess() allows it.
|
||||
*
|
||||
* @param EntityInterface $node
|
||||
* A node object.
|
||||
* @return array An array as expected by drupal_render().
|
||||
* An array as expected by drupal_render().
|
||||
*/
|
||||
public function historyOverview(EntityInterface $node = NULL) {
|
||||
$form = [];
|
||||
|
||||
/*
|
||||
* Get data from parameters.
|
||||
*/
|
||||
|
||||
// @todo D8-port: make Workflow History tab happen for every entity_type.
|
||||
// For workflow_tab_page with multiple workflows, use a separate view. See [#2217291].
|
||||
// @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController.
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
// ATM it only works for Nodes and Terms.
|
||||
// This is a hack. The Route should always pass an object.
|
||||
// On view tab, $entity is object,
|
||||
// On workflow tab, $entity is id().
|
||||
// Get the entity for this form.
|
||||
if (!$entity = workflow_url_get_entity($node)) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get derived data from parameters.
|
||||
*/
|
||||
if (!$field_name = workflow_get_field_name($entity, workflow_url_get_field_name())) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 1: generate the Transition Form.
|
||||
*/
|
||||
// Add the WorkflowTransitionForm to the page.
|
||||
$form = WorkflowManager::getWorkflowTransitionForm($entity, $field_name);
|
||||
|
||||
/*
|
||||
* Step 2: generate the Transition History List.
|
||||
*/
|
||||
$view = NULL;
|
||||
$moduleHandler = \Drupal::service('module_handler');
|
||||
if ($moduleHandler->moduleExists('views')){
|
||||
$view = Views::getView('workflow_entity_history');
|
||||
}
|
||||
if (is_object($view) && $view->storage->status()) {
|
||||
// Add the history list from configured Views display.
|
||||
$args = [$entity->id()];
|
||||
$view->setArguments($args);
|
||||
$view->setDisplay('workflow_history_tab');
|
||||
$view->preExecute();
|
||||
$view->execute();
|
||||
$form['table'] = $view->buildRenderable();
|
||||
}
|
||||
else {
|
||||
// @deprecated. Use the Views display above.
|
||||
// Add the history list from programmed WorkflowTransitionListController.
|
||||
$entity_type = 'workflow_transition';
|
||||
// $form = $this->listing('workflow_transition');
|
||||
$list_builder = $this->entityTypeManager()->getListBuilder($entity_type);
|
||||
// Add the Node explicitly, since $list_builder expects a Transition.
|
||||
$list_builder->workflow_entity = $entity;
|
||||
$form += $list_builder->render();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally: sort the elements (overriding their weight).
|
||||
*/
|
||||
// $form['#weight'] = 10;
|
||||
$form['actions']['#weight'] = 100;
|
||||
$form['table']['#weight'] = 201;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu access control callback. Checks access to Workflow tab.
|
||||
*
|
||||
* This used to be D7-function workflow_tab_access($user, $entity).
|
||||
*
|
||||
* The History tab should not be used with multiple workflows per entity.
|
||||
* Use the dedicated view for this use case.
|
||||
* @todo D8: remove this in favour of View 'Workflow history per entity'.
|
||||
* @todo D8-port: make this workflow for non-Node entity types.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* Run access checks for this account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
*/
|
||||
public function historyAccess(AccountInterface $account) {
|
||||
static $access = [];
|
||||
|
||||
$uid = ($account) ? $account->id() : -1;
|
||||
|
||||
// @todo D8-port: make Workflow History tab happen for every entity_type.
|
||||
// @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController.
|
||||
// ATM it only works for Nodes and Terms.
|
||||
// This is a hack. The Route should always pass an object.
|
||||
// On view tab, $entity is object,
|
||||
// On workflow tab, $entity is id().
|
||||
// Get the entity for this form.
|
||||
$entity = workflow_url_get_entity();
|
||||
|
||||
/* @var $entity EntityInterface */
|
||||
// Figure out the $entity's bundle and id.
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
$entity_bundle = $entity->bundle();
|
||||
$entity_id = ($entity) ? $entity->id() : '';
|
||||
$field_name = workflow_url_get_field_name();
|
||||
|
||||
if (isset($access[$uid][$entity_type][$entity_id][$field_name ? $field_name : 'no_field'])) {
|
||||
return $access[$uid][$entity_type][$entity_id][$field_name ? $field_name : 'no_field'];
|
||||
}
|
||||
|
||||
$access_result = AccessResult::forbidden();
|
||||
|
||||
// When having multiple workflows per bundle, use Views display
|
||||
// 'Workflow history per entity' instead!
|
||||
$fields = _workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name);
|
||||
if (!$fields) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
else {
|
||||
// @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController
|
||||
$uid = ($account) ? $account->id() : -1;
|
||||
$entity_id = ($entity) ? $entity->id() : '';
|
||||
// Determine if user is owner of the entity.
|
||||
$is_owner = WorkflowManager::isOwner($account, $entity);
|
||||
|
||||
/**
|
||||
* Determine if user has Access. Fill the cache.
|
||||
*/
|
||||
// @todo: what to do with multiple workflow_fields per bundle? Use Views instead! Or introduce a setting.
|
||||
// @todo D8-port: workflow_tab_access: use proper 'WORKFLOW_TYPE' permissions
|
||||
foreach ($fields as $definition) {
|
||||
$type_id = $definition->getSetting('workflow_type');
|
||||
if ($account->hasPermission("access any $type_id workflow_transion overview")) {
|
||||
$access_result = AccessResult::allowed();
|
||||
}
|
||||
elseif ($is_owner && $account->hasPermission("access own $type_id workflow_transion overview")) {
|
||||
$access_result = AccessResult::allowed();
|
||||
}
|
||||
elseif ($account->hasPermission('administer nodes')) {
|
||||
$access_result = AccessResult::allowed();
|
||||
}
|
||||
$access[$uid][$entity_type][$entity_id][$field_name ? $field_name : 'no_field'] = $access_result;
|
||||
}
|
||||
}
|
||||
return $access_result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Element;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\Element\FormElement;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Drupal\workflow\Entity\WorkflowScheduledTransition;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
/**
|
||||
* Provides a form element for the WorkflowTransitionForm and ~Widget.
|
||||
*
|
||||
* Properties:
|
||||
* - #return_value: The value to return when the checkbox is checked.
|
||||
*
|
||||
* @see \Drupal\Core\Render\Element\FormElement
|
||||
* @see https://www.drupal.org/node/169815 "Creating Custom Elements"
|
||||
*
|
||||
* @FormElement("workflow_transition")
|
||||
*/
|
||||
class WorkflowTransitionElement extends FormElement {
|
||||
|
||||
/**
|
||||
* Form element validation handler.
|
||||
*
|
||||
* Note that #maxlength is validated by _form_validate() already.
|
||||
*
|
||||
* This checks that the submitted value:
|
||||
* - Does not contain the replacement character only.
|
||||
* - Does not contain disallowed characters.
|
||||
* - Is unique; i.e., does not already exist.
|
||||
* - Does not exceed the maximum length (via #maxlength).
|
||||
* - Cannot be changed after creation (via #disabled).
|
||||
*
|
||||
* @param array $element Reference to the Form element
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* @param array $complete_form
|
||||
*/
|
||||
public static function validateTransition(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an element.
|
||||
*
|
||||
* This function is referenced in the Annotation for this class.
|
||||
*
|
||||
* @param $element
|
||||
* @param FormStateInterface $form_state
|
||||
* @param $complete_form
|
||||
*
|
||||
* @return array
|
||||
* The Workflow element
|
||||
*/
|
||||
public static function processTransition(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
return self::transitionElement($element, $form_state, $complete_form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an element.
|
||||
*
|
||||
* This function is an internal function, to be reused in:
|
||||
* - TransitionElement
|
||||
* - TransitionDefaultWidget
|
||||
*
|
||||
* Usage:
|
||||
* @example $element['#default_value'] = $transition;
|
||||
* @example $element += WorkflowTransitionElement::transitionElement($element, $form_state, $form);
|
||||
*
|
||||
* @param array $element Reference to the form element
|
||||
* @param FormStateInterface $form_state
|
||||
* @param array $complete_form
|
||||
*
|
||||
* @return array
|
||||
* The form element
|
||||
*/
|
||||
public static function transitionElement(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||
// $element = [];
|
||||
|
||||
/*
|
||||
* Input.
|
||||
*/
|
||||
// A Transition object must have been set explicitly.
|
||||
/** @var $transition WorkflowTransitionInterface */
|
||||
$transition = $element['#default_value'];
|
||||
/** @var $user \Drupal\Core\Session\AccountInterface */
|
||||
$user = \Drupal::currentUser();
|
||||
|
||||
/*
|
||||
* Derived input.
|
||||
*/
|
||||
$field_name = $transition->getFieldName();
|
||||
$workflow = $transition->getWorkflow();
|
||||
$wid = $transition->getWorkflowId();
|
||||
$force = $transition->isForced();
|
||||
$entity = $transition->getTargetEntity();
|
||||
$entity_type = $transition->getTargetEntityTypeId();
|
||||
$entity_id = $transition->getTargetEntityId();
|
||||
|
||||
if ($transition->isExecuted()) {
|
||||
// We are editing an existing/executed/not-scheduled transition.
|
||||
// Only the comments may be changed!
|
||||
|
||||
$current_sid = $from_sid = $transition->getFromSid();
|
||||
// The states may not be changed anymore.
|
||||
$to_state = $transition->getToState();
|
||||
$options = [$to_state->id() => $to_state->label()];
|
||||
// We need the widget to edit the comment.
|
||||
$show_widget = TRUE;
|
||||
$default_value = $transition->getToSid();
|
||||
}
|
||||
elseif ($entity) {
|
||||
// Normal situation: adding a new transition on an new/existing entity.
|
||||
|
||||
// Get the scheduling info, only when updating an existing entity.
|
||||
// This may change the $default_value on the Form.
|
||||
// Technically you could have more than one scheduled transition, but
|
||||
// this will only add the soonest one.
|
||||
// @todo: Read the history with an explicit langcode?
|
||||
$langcode = ''; // $entity->language()->getId();
|
||||
if ($entity_id && $scheduled_transition = WorkflowScheduledTransition::loadByProperties($entity_type, $entity_id, [], $field_name, $langcode)) {
|
||||
$transition = $scheduled_transition;
|
||||
}
|
||||
|
||||
$current_sid = $from_sid = $transition->getFromSid();
|
||||
$current_state = $from_state = $transition->getFromState();
|
||||
$options = ($current_state) ? $current_state->getOptions($entity, $field_name, $user, FALSE) : [];
|
||||
$show_widget = ($from_state) ? $from_state->showWidget($entity, $field_name, $user, FALSE) : [];
|
||||
$default_value = $from_sid;
|
||||
$default_value = ($from_state && $from_state->isCreationState()) ? $workflow->getFirstSid($entity, $field_name, $user, FALSE) : $default_value;
|
||||
$default_value = ($transition->isScheduled()) ? $transition->getToSid() : $default_value;
|
||||
}
|
||||
elseif (!$entity) {
|
||||
// Sometimes, no entity is given. We encountered the following cases:
|
||||
// - D7: the Field settings page,
|
||||
// - D7: the VBO action form;
|
||||
// - D7/D8: the Advance Action form on admin/config/system/actions;
|
||||
// If so, show all options for the given workflow(s).
|
||||
if (!$temp_state = $transition->getFromState()) {
|
||||
$temp_state = $transition->getToState();
|
||||
}
|
||||
$options = ($temp_state)
|
||||
? $temp_state->getOptions($entity, $field_name, $user, FALSE)
|
||||
: workflow_get_workflow_state_names($wid, $grouped = TRUE);
|
||||
$show_widget = TRUE;
|
||||
$current_sid = $transition->getToSid(); // @todo
|
||||
$default_value = $from_sid = $transition->getToSid(); // @todo
|
||||
}
|
||||
else {
|
||||
// We are in trouble! A message is already set in workflow_node_current_state().
|
||||
$options = [];
|
||||
$current_sid = 0;
|
||||
$show_widget = FALSE;
|
||||
$default_value = FALSE;
|
||||
}
|
||||
|
||||
// Fetch the form ID. This is unique for each entity, to allow multiple form per page (Views, etc.).
|
||||
// Make it uniquer by adding the field name, or else the scheduling of
|
||||
// multiple workflow_fields is not independent of each other.
|
||||
// If we are indeed on a Transition form (so, not a Node Form with widget)
|
||||
// then change the form id, too.
|
||||
$form_id = self::getFormId();
|
||||
|
||||
/*
|
||||
* Output: generate the element.
|
||||
*/
|
||||
// Get settings from workflow. @todo: implement default_settings.
|
||||
if ($workflow) {
|
||||
$workflow_settings = $workflow->options;
|
||||
}
|
||||
else {
|
||||
// @todo D8-port: now only tested with Action.
|
||||
$workflow_settings = [
|
||||
'name_as_title' => 0,
|
||||
'options' => "radios",
|
||||
'schedule_timezone' => 1,
|
||||
'comment_log_node' => "1",
|
||||
'watchdog_log' => TRUE,
|
||||
];
|
||||
}
|
||||
// Current sid and default value may differ in a scheduled transition.
|
||||
|
||||
$workflow_settings['comment'] = $workflow_settings['comment_log_node']; // 'comment_log_tab' is removed;
|
||||
|
||||
// Capture settings to format the form/widget.
|
||||
$settings_title_as_name = !empty($workflow_settings['name_as_title']);
|
||||
$settings_fieldset = isset($workflow_settings['fieldset']) ? $workflow_settings['fieldset'] : 0;
|
||||
$settings_options_type = $workflow_settings['options'];
|
||||
|
||||
// Display scheduling form if user has permission.
|
||||
// Not shown on new entity (not supported by workflow module, because that
|
||||
// leaves the entity in the (creation) state until scheduling time.)
|
||||
// Not shown when editing existing transition.
|
||||
$type_id = ($workflow) ? $workflow->id() : ''; // Might be empty on Action configuration.
|
||||
$settings_schedule = !$transition->isExecuted() && $user->hasPermission("schedule $type_id workflow_transition");
|
||||
if ($settings_schedule) {
|
||||
//@todo D8-port: check below code: form on VBO.
|
||||
// workflow_debug( __FILE__ , __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
$step = $form_state->getValue('step');
|
||||
if (isset($step) && ($form_state->getValue('step') == 'views_bulk_operations_config_form')) {
|
||||
// On VBO 'modify entity values' form, leave field settings.
|
||||
$settings_schedule = TRUE;
|
||||
}
|
||||
else {
|
||||
// ... and cannot be shown on a Content add page (no $entity_id),
|
||||
// ...but can be shown on a VBO 'set workflow state to..'page (no entity).
|
||||
$settings_schedule = !($entity && !$entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
$settings_schedule_timezone = !empty($workflow_settings['schedule_timezone']);
|
||||
// Show comment, when both Field and Instance allow this.
|
||||
$settings_comment = $workflow_settings['comment'];
|
||||
|
||||
$transition_is_scheduled = $transition->isScheduled();
|
||||
// Save the current value of the entity in the form, for later Workflow-module specific references.
|
||||
// We add prefix, since #tree == FALSE.
|
||||
$element['workflow_transition'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $transition,
|
||||
];
|
||||
|
||||
// Decide if we show a widget or a formatter.
|
||||
// There is no need for a widget when the only option is the current sid.
|
||||
|
||||
// Add a state formatter before the rest of the form,
|
||||
// when transition is scheduled or widget is hidden.
|
||||
if ( (!$show_widget) || $transition_is_scheduled || $transition->isExecuted()) {
|
||||
$element['workflow_current_state'] = workflow_state_formatter($entity, $field_name, $current_sid);
|
||||
// Set a proper weight, which works for Workflow Options in select list AND action buttons.
|
||||
$element['workflow_current_state']['#weight'] = -0.005;
|
||||
}
|
||||
|
||||
$element['#tree'] = TRUE;
|
||||
// Add class following node-form pattern (both on form and container).
|
||||
$workflow_type_id = ($workflow) ? $workflow->id() : '';
|
||||
$element['#attributes']['class'][] = 'workflow-transition-' . $workflow_type_id . '-container';
|
||||
$element['#attributes']['class'][] = 'workflow-transition-container';
|
||||
if (!$show_widget) {
|
||||
// Show no widget.
|
||||
$element['to_sid']['#type'] = 'value';
|
||||
$element['to_sid']['#value'] = $default_value;
|
||||
$element['to_sid']['#options'] = $options; // In case action buttons need them.
|
||||
$element['comment']['#type'] = 'value';
|
||||
$element['comment']['#value'] = '';
|
||||
|
||||
return $element; // <-- exit.
|
||||
}
|
||||
|
||||
// @todo: repair the usage of $settings_title_as_name: no container if no details (schedule/comment).
|
||||
// Prepare a UI wrapper. This might be a fieldset.
|
||||
if ($settings_fieldset == 0) { // Use 'container'.
|
||||
$element += [
|
||||
'#type' => 'container',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$element += [
|
||||
'#type' => 'details',
|
||||
'#collapsible' => TRUE,
|
||||
'#open' => ($settings_fieldset == 2) ? FALSE : TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
$element['field_name'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Field name'),
|
||||
'#description' => t('Choose the field name.'),
|
||||
'#access' => FALSE, // Only show on VBO/Actions screen.
|
||||
'#options' => workflow_get_workflow_field_names($entity),
|
||||
'#default_value' => $field_name,
|
||||
'#required' => TRUE,
|
||||
'#weight' => -20,
|
||||
];
|
||||
$element['force'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Force transition'),
|
||||
'#description' => t('If this box is checked, the new state will be assigned even if workflow permissions disallow it.'),
|
||||
'#access' => FALSE, // Only show on VBO/Actions screen.
|
||||
'#default_value' => $force,
|
||||
'#weight' => -19,
|
||||
];
|
||||
|
||||
// This overrides BaseFieldDefinition. @todo: apply for form and widget.
|
||||
// The 'options' widget. May be removed later if 'Action buttons' are chosen.
|
||||
// The help text is not available for container. Let's add it to the
|
||||
// State box. N.B. it is empty on Workflow Tab, Node View page.
|
||||
$help_text = isset($element['#description']) ? $element['#description'] : '';
|
||||
// This overrides BaseFieldDefinition. @todo: apply for form and widget.
|
||||
$element['to_sid'] = [
|
||||
'#type' => ($wid) ? $settings_options_type : 'select', // Avoid error with grouped options.
|
||||
'#title' => ($settings_title_as_name && !$transition->isExecuted())
|
||||
? t('Change @name state', ['@name' => $workflow->label()])
|
||||
: t('Target state'),
|
||||
'#access' => TRUE,
|
||||
'#options' => $options,
|
||||
// '#parents' => array('workflow'),
|
||||
'#default_value' => $default_value,
|
||||
'#description' => $help_text,
|
||||
];
|
||||
|
||||
// Display scheduling form under certain conditions.
|
||||
if ($settings_schedule == TRUE) {
|
||||
$timezone = $user->getTimeZone();
|
||||
|
||||
$timezone_options = array_combine(timezone_identifiers_list(), timezone_identifiers_list());
|
||||
$timestamp = $transition ? $transition->getTimestamp() : \Drupal::time()->getRequestTime();
|
||||
$hours = (!$transition_is_scheduled) ? '00:00' : \Drupal::service('date.formatter')->format($timestamp, 'custom', 'H:i', $timezone);
|
||||
// Add a container, so checkbox and time stay together in extra fields.
|
||||
$element['workflow_scheduling'] = [
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$element['workflow_scheduling']['scheduled'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Schedule'),
|
||||
'#options' => [
|
||||
'0' => t('Immediately'),
|
||||
'1' => t('Schedule for state change'),
|
||||
],
|
||||
'#default_value' => $transition_is_scheduled ? '1' : '0',
|
||||
'#attributes' => [
|
||||
// 'id' => 'scheduled_' . $form_id,
|
||||
'class' => [Html::getClass('scheduled_' . $form_id)],
|
||||
],
|
||||
];
|
||||
$element['workflow_scheduling']['date_time'] = [
|
||||
'#type' => 'details', // 'container',
|
||||
'#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE.
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
'#prefix' => '<div style="margin-left: 1em;">',
|
||||
'#suffix' => '</div>',
|
||||
'#states' => [
|
||||
//'visible' => array(':input[id="' . 'scheduled_' . $form_id . '"]' => array('value' => '1')),
|
||||
'visible' => ['input.' . Html::getClass('scheduled_' . $form_id) => ['value' => '1']],
|
||||
],
|
||||
];
|
||||
$element['workflow_scheduling']['date_time']['workflow_scheduled_date'] = [
|
||||
'#type' => 'date',
|
||||
'#prefix' => t('At'),
|
||||
'#default_value' => implode( '-', [
|
||||
'year' => date('Y', $timestamp),
|
||||
'month' => date('m', $timestamp),
|
||||
'day' => date('d', $timestamp),
|
||||
]
|
||||
)
|
||||
];
|
||||
$element['workflow_scheduling']['date_time']['workflow_scheduled_hour'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Time'),
|
||||
'#maxlength' => 7,
|
||||
'#size' => 6,
|
||||
'#default_value' => $hours,
|
||||
'#element_validate' => ['_workflow_transition_form_element_validate_time'], // @todo D8-port: this is not called.
|
||||
];
|
||||
$element['workflow_scheduling']['date_time']['workflow_scheduled_timezone'] = [
|
||||
'#type' => $settings_schedule_timezone ? 'select' : 'hidden',
|
||||
'#title' => t('Time zone'),
|
||||
'#options' => $timezone_options,
|
||||
'#default_value' => [$timezone => $timezone],
|
||||
];
|
||||
$element['workflow_scheduling']['date_time']['workflow_scheduled_help'] = [
|
||||
'#type' => 'item',
|
||||
'#prefix' => '<br />',
|
||||
'#description' => t('Please enter a time.
|
||||
If no time is included, the default will be midnight on the specified date.
|
||||
The current time is: @time.', ['@time' => \Drupal::service('date.formatter')->format(\Drupal::time()->getRequestTime(), 'custom', 'H:i', $timezone)]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// This overrides BaseFieldDefinition. @todo: apply for form and widget.
|
||||
$element['comment'] = [
|
||||
'#type' => 'textarea',
|
||||
'#required' => $settings_comment == '2',
|
||||
'#access' => $settings_comment != '0', // Align with action buttons.
|
||||
'#title' => t('Workflow comment'),
|
||||
'#description' => t('A comment to put in the workflow log.'),
|
||||
'#default_value' => $transition ? $transition->getComment() : '',
|
||||
'#rows' => 2,
|
||||
];
|
||||
|
||||
// In WorkflowTransitionForm, a default 'Submit' button is added over there.
|
||||
// In Entity Form, a button per permitted state is added in workflow_form_alter().
|
||||
if ($settings_options_type == 'buttons' || $settings_options_type == 'dropbutton') {
|
||||
// D7: How do action buttons work? See also d.o. issue #2187151.
|
||||
// D7: Create 'action buttons' per state option. Set $sid property on each button.
|
||||
// 1. Admin sets ['widget']['options']['#type'] = 'buttons'.
|
||||
// 2. This function formElement() creates 'action buttons' per state option;
|
||||
// sets $sid property on each button.
|
||||
// 3. User clicks button.
|
||||
// 4. Callback _workflow_transition_form_validate_buttons() sets proper State.
|
||||
// 5. Callback _workflow_transition_form_validate_buttons() sets Submit function.
|
||||
|
||||
// Performance: inform workflow_form_alter() to do its job.
|
||||
_workflow_use_action_buttons($settings_options_type);
|
||||
|
||||
// Make sure the '#type' is not set to the invalid 'buttons' value.
|
||||
// It will be replaced by action buttons, but sometimes, the select box
|
||||
// is still shown.
|
||||
// @see workflow_form_alter().
|
||||
$element['to_sid']['#type'] = 'select';
|
||||
$element['to_sid']['#access'] = FALSE;
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique string identifying the form.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFormId() {
|
||||
return 'workflow_transition_form'; //@todo D8-port: add $form_id for widget and History tab.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ContentEntityForm::copyFormValuesToEntity(), and is called from:
|
||||
* - WorkflowTransitionForm::copyFormValuesToEntity()
|
||||
* - WorkflowDefaultWidget
|
||||
*
|
||||
* N.B. in contrary to ContentEntityForm::copyFormValuesToEntity(),
|
||||
* - parameter 1 is returned as result, to be able to create a new Transition object.
|
||||
* - parameter 3 is not $form_state (from Form), but an $item array (from Widget).
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $transition
|
||||
* @param array $form
|
||||
* @param FormStateInterface $form_state
|
||||
* @param array $item
|
||||
*
|
||||
* @return WorkflowTransitionInterface
|
||||
*/
|
||||
static public function copyFormValuesToTransition(EntityInterface $transition, array $form, FormStateInterface $form_state, array $item) {
|
||||
/** @var WorkflowTransitionInterface $transition */
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = workflow_current_user(); // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed.
|
||||
|
||||
/**
|
||||
* Derived input
|
||||
*/
|
||||
// Make sure we have subset ['workflow_scheduled_date_time']
|
||||
if (!isset($item['to_sid'])) {
|
||||
$entity_id = $transition->getTargetEntityId();
|
||||
drupal_set_message(t('Error: content @id has no workflow attached. The data is not saved.', ['@id' => $entity_id]), 'error');
|
||||
// The new state is still the previous state.
|
||||
return $transition;
|
||||
}
|
||||
|
||||
// In WorkflowTransitionForm, we receive the complete $form_state.
|
||||
// Remember, the workflow_scheduled element is not set on 'add' page.
|
||||
$scheduled = !empty($item['workflow_scheduling']['scheduled']);
|
||||
$schedule_values = ($scheduled) ? $item['workflow_scheduling']['date_time'] : [];
|
||||
|
||||
// Get user input from element.
|
||||
$to_sid = $item['to_sid'];
|
||||
$comment = $item['comment'];
|
||||
$force = FALSE;
|
||||
|
||||
// @todo D8: add the VBO use case.
|
||||
/*
|
||||
// Determine if the transition is forced.
|
||||
// This can be set by a 'workflow_vbo action' in an additional form element.
|
||||
$force = isset($form_state['input']['workflow_force']) ? $form_state['input']['workflow_force'] : FALSE;
|
||||
if (!$entity) {
|
||||
// E.g., on VBO form.
|
||||
}
|
||||
*/
|
||||
|
||||
// @todo D8-port: add below exception.
|
||||
// Extract the data from $items, depending on the type of widget.
|
||||
// @todo D8: use MassageFormValues($item, $form, $form_state).
|
||||
/*
|
||||
$old_sid = workflow_node_previous_state($entity, $entity_type, $field_name);
|
||||
if (!$old_sid) {
|
||||
// At this moment, $old_sid should have a value. If the content does not
|
||||
// have a state yet, old_sid contains '(creation)' state. But if the
|
||||
// content is not associated to a workflow, old_sid is now 0. This may
|
||||
// happen in workflow_vbo, if you assign a state to non-relevant nodes.
|
||||
$entity_id = entity_id($entity_type, $entity);
|
||||
drupal_set_message(t('Error: content @id has no workflow attached. The data is not saved.', array('@id' => $entity_id)), 'error');
|
||||
// The new state is still the previous state.
|
||||
$new_sid = $old_sid;
|
||||
return $new_sid;
|
||||
}
|
||||
*/
|
||||
|
||||
$timestamp = \Drupal::time()->getRequestTime();
|
||||
if ($scheduled) {
|
||||
// Fetch the (scheduled) timestamp to change the state.
|
||||
// Override $timestamp.
|
||||
$scheduled_date_time = implode(' ', [
|
||||
$schedule_values['workflow_scheduled_date'],
|
||||
$schedule_values['workflow_scheduled_hour'],
|
||||
// $schedule_values['workflow_scheduled_timezone'],
|
||||
]);
|
||||
$timezone = $schedule_values['workflow_scheduled_timezone'];
|
||||
$old_timezone = date_default_timezone_get();
|
||||
date_default_timezone_set($timezone);
|
||||
$timestamp = strtotime($scheduled_date_time);
|
||||
date_default_timezone_set($old_timezone);
|
||||
if (!$timestamp) {
|
||||
// Time should have been validated in form/widget.
|
||||
$timestamp = \Drupal::time()->getRequestTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process
|
||||
*/
|
||||
|
||||
/*
|
||||
* Create a new ScheduledTransition.
|
||||
*/
|
||||
if ($scheduled) {
|
||||
$transition_entity = $transition->getTargetEntity();
|
||||
$field_name = $transition->getFieldName();
|
||||
$from_sid = $transition->getFromSid();
|
||||
/** @var $transition WorkflowTransitionInterface */
|
||||
$transition = WorkflowScheduledTransition::create([$from_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($transition_entity);
|
||||
$transition->setValues($to_sid, $user->id(), $timestamp, $comment);
|
||||
}
|
||||
if (!$transition->isExecuted()) {
|
||||
// Set new values.
|
||||
// When editing an existing Transition, only comments may change.
|
||||
$transition->setValues($to_sid, $user->id(), $timestamp, $comment);
|
||||
$transition->schedule($scheduled);
|
||||
$transition->force($force);
|
||||
}
|
||||
$transition->setComment($comment);
|
||||
|
||||
// Determine and add the attached fields.
|
||||
// Caveat: This works automatically on a Workflow Form,
|
||||
// but only with a hack on a widget.
|
||||
// @todo: Attached fields are not supported in ScheduledTransitions.
|
||||
$fields = WorkflowManager::getAttachedFields('workflow_transition', $transition->bundle());
|
||||
/** @var \Drupal\Core\Field\Entity\BaseFieldOverride $nodeField */
|
||||
foreach ($fields as $field_name => $field) {
|
||||
$user_input = isset($form_state->getUserInput()[$field_name]) ? $form_state->getUserInput()[$field_name] : [];
|
||||
if (isset($item[$field_name])) {
|
||||
// On Workflow Form (e.g., history tab, block).
|
||||
// @todo, in latest tests, this line seems not necessary.
|
||||
$transition->{$field_name} = $item[$field_name];
|
||||
}
|
||||
elseif ($user_input) {
|
||||
// On Workflow Widget (e.g., on node, comment).
|
||||
// @todo, some field types are not supported here.
|
||||
$transition->{$field_name} = $user_input;
|
||||
}
|
||||
}
|
||||
|
||||
return $transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
$class = get_class($this);
|
||||
return [
|
||||
'#input' => TRUE,
|
||||
'#return_value' => 1,
|
||||
'#process' => [
|
||||
[$class, 'processTransition'],
|
||||
[$class, 'processAjaxForm'],
|
||||
//array($class, 'processGroup'),
|
||||
],
|
||||
'#element_validate' => [
|
||||
[$class, 'validateTransition'],
|
||||
],
|
||||
'#pre_render' => [
|
||||
[$class, 'preRenderTransition'],
|
||||
//array($class, 'preRenderGroup'),
|
||||
],
|
||||
//'#theme' => 'input__checkbox',
|
||||
//'#theme' => 'input__textfield',
|
||||
'#theme_wrappers' => ['form_element'],
|
||||
//'#title_display' => 'after',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Workflow configuration entity to persistently store configuration.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "workflow_type",
|
||||
* label = @Translation("Workflow type"),
|
||||
* label_singular = @Translation("Workflow type"),
|
||||
* label_plural = @Translation("Workflow types"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count Workflow type",
|
||||
* plural = "@count Workflow types",
|
||||
* ),
|
||||
* module = "workflow",
|
||||
* static_cache = TRUE,
|
||||
* translatable = TRUE,
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\workflow\Entity\WorkflowStorage",
|
||||
* "list_builder" = "Drupal\workflow_ui\Controller\WorkflowListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\workflow\Form\WorkflowTypeForm",
|
||||
* "delete" = "Drupal\Core\Entity\EntityDeleteForm",
|
||||
* "edit" = "Drupal\workflow\Form\WorkflowTypeForm",
|
||||
* }
|
||||
* },
|
||||
* admin_permission = "administer workflow",
|
||||
* config_prefix = "workflow",
|
||||
* bundle_of = "workflow_transition",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "module",
|
||||
* "status",
|
||||
* "options",
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/admin/config/workflow/workflow/{workflow_type}",
|
||||
* "collection" = "/admin/config/workflow/workflow",
|
||||
* "delete-form" = "/admin/config/workflow/workflow/{workflow_type}/delete",
|
||||
* "edit-form" = "/admin/config/workflow/workflow/{workflow_type}",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class Workflow extends ConfigEntityBase implements WorkflowInterface {
|
||||
|
||||
/**
|
||||
* The machine name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human readable name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
// TODO D8-port Workflow: complete below variables. (Add get()-functions).
|
||||
// @see https://www.drupal.org/node/1809494
|
||||
// @see https://codedrop.com.au/blog/creating-custom-config-entities-drupal-8
|
||||
public $options = [];
|
||||
|
||||
/**
|
||||
* The workflow-specific creation state.
|
||||
*/
|
||||
private $creation_state;
|
||||
private $creation_sid = 0;
|
||||
|
||||
// Attached States and Transitions.
|
||||
public $states = [];
|
||||
public $transitions = [];
|
||||
|
||||
/**
|
||||
* CRUD functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given information, update or insert a new workflow.
|
||||
*
|
||||
* This also handles importing, rebuilding, reverting from Features,
|
||||
* as defined in workflow.features.inc.
|
||||
* TODO D8: clean up this function, since we are config entity now.
|
||||
* todo D7: reverting does not refresh States and transitions, since no
|
||||
* machine_name was present. As of 7.x-2.3, the machine_name exists in
|
||||
* Workflow and WorkflowConfigTransition, so rebuilding is possible.
|
||||
*
|
||||
* When changing this function, test with the following situations:
|
||||
* - maintain Workflow in Admin UI;
|
||||
* - clone Workflow in Admin UI;
|
||||
* - create/revert/rebuild Workflow with Features; @see workflow.features.inc
|
||||
* - save Workflow programmatic;
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function save() {
|
||||
$status = parent::save();
|
||||
// Are we saving a new Workflow?
|
||||
// Make sure a Creation state exists.
|
||||
if ($status == SAVED_NEW) {
|
||||
$this->getCreationState();
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
|
||||
/** @var Workflow $workflow */
|
||||
foreach($entities as &$workflow) {
|
||||
//Better performance, together with Annotation static_cache = TRUE.
|
||||
// Load the states, and set the creation state.
|
||||
$workflow->getStates();
|
||||
$workflow->getCreationState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a wid, delete the workflow and its data.
|
||||
*/
|
||||
public function delete() {
|
||||
if (!$this->isDeletable()) {
|
||||
// @todo: throw error if not workflow->isDeletable().
|
||||
}
|
||||
else {
|
||||
// Delete associated state (also deletes any associated transitions).
|
||||
foreach ($this->getStates($all = TRUE) as $state) {
|
||||
$state->deactivate('');
|
||||
$state->delete();
|
||||
}
|
||||
|
||||
// Delete the workflow.
|
||||
parent::delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValid() {
|
||||
$is_valid = TRUE;
|
||||
|
||||
// Don't allow Workflow without states. There should always be a creation state.
|
||||
$states = $this->getStates($all = FALSE);
|
||||
if (count($states) < 1) {
|
||||
// That's all, so let's remind them to create some states.
|
||||
$message = t('Workflow %workflow has no states defined, so it cannot be assigned to content yet.',
|
||||
['%workflow' => $this->label()]);
|
||||
drupal_set_message($message, 'warning');
|
||||
|
||||
// Skip allowing this workflow.
|
||||
$is_valid = FALSE;
|
||||
}
|
||||
|
||||
// Also check for transitions, at least out of the creation state. Don't filter for roles.
|
||||
$transitions = $this->getTransitionsByStateId($this->getCreationSid(), '');
|
||||
if (count($transitions) < 1) {
|
||||
// That's all, so let's remind them to create some transitions.
|
||||
$message = t('Workflow %workflow has no transitions defined, so it cannot be assigned to content yet.',
|
||||
['%workflow' => $this->label()]);
|
||||
drupal_set_message($message, 'warning');
|
||||
|
||||
// Skip allowing this workflow.
|
||||
$is_valid = FALSE;
|
||||
}
|
||||
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDeletable() {
|
||||
|
||||
// May not be deleted if assigned to a Field.
|
||||
foreach ($fields = _workflow_info_fields() as $field_info) {
|
||||
if ($field_info->getSetting('workflow_type') == $this->id()) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWorkflowId() {
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createState($sid, $save = TRUE) {
|
||||
$wid = $this->id();
|
||||
/* @var $state WorkflowState */
|
||||
$state = WorkflowState::load($sid);
|
||||
if (!$state || $wid != $state->getWorkflowId()) {
|
||||
$state = WorkflowState::create($values = ['id' => $sid, 'wid' => $wid]);
|
||||
if ($save) {
|
||||
$state->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Maintain the new object in the workflow.
|
||||
$this->states[$state->id()] = $state;
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCreationState() {
|
||||
// First, find it.
|
||||
if (!$this->creation_state) {
|
||||
foreach ($this->getStates($all = TRUE) as $state) {
|
||||
if ($state->isCreationState()) {
|
||||
$this->creation_state = $state;
|
||||
$this->creation_sid = $state->id();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First, then, create it.
|
||||
if (!$this->creation_state) {
|
||||
$state = $this->createState(WORKFLOW_CREATION_STATE_NAME);
|
||||
$this->creation_state = $state;
|
||||
$this->creation_sid = $state->id();
|
||||
}
|
||||
|
||||
return $this->creation_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCreationSid() {
|
||||
if (!$this->creation_sid) {
|
||||
$state = $this->getCreationState();
|
||||
return $state->id();
|
||||
}
|
||||
return $this->creation_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFirstSid(EntityInterface $entity, $field_name, AccountInterface $user, $force = FALSE) {
|
||||
$creation_state = $this->getCreationState();
|
||||
$options = $creation_state->getOptions($entity, $field_name, $user, $force);
|
||||
if ($options) {
|
||||
$keys = array_keys($options);
|
||||
$sid = $keys[0];
|
||||
}
|
||||
else {
|
||||
// This should never happen, but it did during testing.
|
||||
drupal_set_message(t('There are no workflow states available. Please notify your site administrator.'), 'error');
|
||||
$sid = 0;
|
||||
}
|
||||
return $sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNextSid(EntityInterface $entity, $field_name, AccountInterface $user, $force = FALSE) {
|
||||
$current_sid = WorkflowManager::getCurrentStateId($entity, $field_name);
|
||||
/* @var $current_state WorkflowState */
|
||||
$current_state = WorkflowState::load($current_sid);
|
||||
$options = $current_state->getOptions($entity, $field_name, $user, $force);
|
||||
// Loop over every option. To find the next one.
|
||||
$flag = $current_state->isCreationState();
|
||||
$new_sid = $current_state->id();
|
||||
|
||||
foreach ($options as $sid => $name) {
|
||||
if ($flag) {
|
||||
$new_sid = $sid;
|
||||
break;
|
||||
}
|
||||
if ($sid == $current_state->id()) {
|
||||
$flag = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStates($all = FALSE, $reset = FALSE) {
|
||||
$wid = $this->id();
|
||||
|
||||
if ($reset) {
|
||||
$this->states = $wid ? WorkflowState::loadMultiple([], $wid, $reset) : [];
|
||||
}
|
||||
elseif ($this->states === NULL) {
|
||||
$this->states = $wid ? WorkflowState::loadMultiple([], $wid, $reset) : [];
|
||||
}
|
||||
elseif ($this->states === []) {
|
||||
$this->states = $wid ? WorkflowState::loadMultiple([], $wid, $reset) : [];
|
||||
}
|
||||
|
||||
// Do not unset, but add to array - you'll remove global objects otherwise.
|
||||
$states = [];
|
||||
foreach ($this->states as $state) {
|
||||
$id = $state->id();
|
||||
if ($all === TRUE) {
|
||||
$states[$id] = $state;
|
||||
}
|
||||
elseif (($all === FALSE) && ($state->isActive() && !$state->isCreationState())) {
|
||||
$states[$id] = $state;
|
||||
}
|
||||
elseif (($all == 'CREATION') && ($state->isActive() || $state->isCreationState())) {
|
||||
$states[$id] = $state;
|
||||
}
|
||||
else {
|
||||
// Do not add state.
|
||||
}
|
||||
}
|
||||
return $states;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getState($sid) {
|
||||
$wid = $this->id();
|
||||
$state = WorkflowState::load($sid);
|
||||
if (!$wid || $wid == $state->getWorkflowId()) {
|
||||
return $state;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createTransition($from_sid, $to_sid, $values = []) {
|
||||
$config_transition = NULL;
|
||||
|
||||
// First check if this transition already exists.
|
||||
$transitions = $this->getTransitionsByStateId($from_sid, $to_sid);
|
||||
if ($transitions) {
|
||||
$config_transition = reset($transitions);
|
||||
}
|
||||
else {
|
||||
$values['wid'] = $this->id();
|
||||
$values['from_sid'] = $from_sid;
|
||||
$values['to_sid'] = $to_sid;
|
||||
$config_transition = WorkflowConfigTransition::create($values);
|
||||
$config_transition->save();
|
||||
}
|
||||
// Maintain the new object in the workflow.
|
||||
$this->transitions[$config_transition->id()] = $config_transition;
|
||||
|
||||
return $config_transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sortTransitions() {
|
||||
// Sort the transitions on state weight.
|
||||
uasort($this->transitions, ['Drupal\workflow\Entity\WorkflowConfigTransition', 'sort'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitions(array $ids = NULL, array $conditions = []) {
|
||||
$config_transitions = [];
|
||||
|
||||
// Get filters on 'from' states, 'to' states, roles.
|
||||
$from_sid = isset($conditions['from_sid']) ? $conditions['from_sid'] : FALSE;
|
||||
$to_sid = isset($conditions['to_sid']) ? $conditions['to_sid'] : FALSE;
|
||||
|
||||
// Get valid states + creation state.
|
||||
$states = $this->getStates('CREATION');
|
||||
// Cache all transitions in the workflow.
|
||||
if (!$this->transitions) {
|
||||
$this->transitions = WorkflowConfigTransition::loadMultiple($ids);
|
||||
|
||||
$this->sortTransitions();
|
||||
}
|
||||
|
||||
/* @var $config_transition WorkflowConfigTransition */
|
||||
foreach ($this->transitions as &$config_transition) {
|
||||
if (!isset($states[$config_transition->getFromSid()])) {
|
||||
// Not a valid transition for this workflow. @todo: delete them.
|
||||
}
|
||||
elseif ($from_sid && $from_sid != $config_transition->getFromSid()) {
|
||||
// Not the requested 'from' state.
|
||||
}
|
||||
elseif ($to_sid && $to_sid != $config_transition->getToSid()) {
|
||||
// Not the requested 'to' state.
|
||||
}
|
||||
else {
|
||||
// Transition is allowed, permitted. Add to list.
|
||||
$config_transitions[$config_transition->id()] = $config_transition;
|
||||
}
|
||||
}
|
||||
return $config_transitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitionsById($tid) {
|
||||
return $this->getTransitions([$tid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTransitionsByStateId($from_sid, $to_sid) {
|
||||
$conditions = [
|
||||
'from_sid' => $from_sid,
|
||||
'to_sid' => $to_sid,
|
||||
];
|
||||
return $this->getTransitions(NULL, $conditions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Workflow configuration entity to persistently store configuration.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "workflow_config_transition",
|
||||
* label = @Translation("Workflow config transition"),
|
||||
* label_singular = @Translation("Workflow config transition"),
|
||||
* label_plural = @Translation("Workflow config transitions"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count Workflow config transition",
|
||||
* plural = "@count Workflow config transitions",
|
||||
* ),
|
||||
* module = "workflow",
|
||||
* translatable = FALSE,
|
||||
* handlers = {
|
||||
* "form" = {
|
||||
* "delete" = "\Drupal\Core\Entity\EntityDeleteForm",
|
||||
* }
|
||||
* },
|
||||
* admin_permission = "administer workflow",
|
||||
* config_prefix = "transition",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "module",
|
||||
* "from_sid",
|
||||
* "to_sid",
|
||||
* "roles",
|
||||
* },
|
||||
* links = {
|
||||
* "collection" = "/admin/config/workflow/workflow/{workflow_type}/transitions",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfigTransitionInterface {
|
||||
|
||||
// Transition data.
|
||||
public $id;
|
||||
public $from_sid;
|
||||
public $to_sid;
|
||||
public $roles = [];
|
||||
|
||||
// Extra fields.
|
||||
protected $wid;
|
||||
// The following must explicitly defined, and not be public, to avoid errors
|
||||
// when exporting with json_encode().
|
||||
protected $workflow = NULL;
|
||||
|
||||
/*
|
||||
* Entity class functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $values = [], $entityType = NULL) {
|
||||
// Please be aware that $entity_type and $entityType are different things!
|
||||
return parent::__construct($values, $entity_type = 'workflow_config_transition');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for __construct. Used for all children of WorkflowTransition (aka WorkflowScheduledTransition)
|
||||
* @param $from_sid
|
||||
* @param $to_sid
|
||||
*/
|
||||
public function setValues($from_sid, $to_sid) {
|
||||
$this->from_sid = $from_sid;
|
||||
$this->to_sid = $to_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save() {
|
||||
$workflow = $this->getWorkflow();
|
||||
|
||||
// To avoid double posting, check if this (new) transition already exist.
|
||||
if (empty($this->id())) {
|
||||
if ($workflow) {
|
||||
$config_transitions = $workflow->getTransitionsByStateId($this->from_sid, $this->to_sid);
|
||||
$config_transition = reset($config_transitions);
|
||||
if ($config_transition) {
|
||||
$this->set('id', $config_transition->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the machine_name. This can be used to rebuild/revert the Feature in a target system.
|
||||
if (empty($this->id())) {
|
||||
$wid = $workflow->id();
|
||||
$this->set('id', implode('', [$wid, substr($this->from_sid, strlen($wid)), substr($this->to_sid, strlen($wid))]));
|
||||
}
|
||||
|
||||
$status = parent::save();
|
||||
|
||||
if ($status) {
|
||||
// Save in current workflow for the remainder of this page request.
|
||||
// Keep in sync with Workflow::getTransitions() !
|
||||
if ($workflow) {
|
||||
$workflow->transitions[$this->id()] = $this;
|
||||
// $workflow->sortTransitions();
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/** @noinspection PhpMissingParentCallCommonInspection
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
|
||||
// Sort the entities using the entity class's sort() method.
|
||||
// See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
|
||||
|
||||
/** @var WorkflowTransitionInterface $a */
|
||||
/** @var WorkflowTransitionInterface $b */
|
||||
if (!$a->getFromSid() || !$b->getFromSid()) {
|
||||
return 0;
|
||||
}
|
||||
// First sort on From-State.
|
||||
$from_state_a = $a->getFromState();
|
||||
$from_state_b = $b->getFromState();
|
||||
if ($from_state_a->weight < $from_state_b->weight) return -1;
|
||||
if ($from_state_a->weight > $from_state_b->weight) return +1;
|
||||
|
||||
// Then sort on To-State.
|
||||
$to_state_a = $a->getToState();
|
||||
$to_state_b = $b->getToState();
|
||||
if ($to_state_a->weight < $to_state_b->weight) return -1;
|
||||
if ($to_state_a->weight > $to_state_b->weight) return +1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWorkflow() {
|
||||
if (!$this->workflow && $wid = $this->getWorkflowId()) {
|
||||
$this->workflow = Workflow::load($wid);
|
||||
}
|
||||
return $this->workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWorkflowId() {
|
||||
if (!$this->wid) {
|
||||
$from_sid = $this->getFromSid();
|
||||
$to_sid = $this->getToSid();
|
||||
$state = WorkflowState::load($to_sid ? $to_sid : $from_sid);
|
||||
$this->wid = $state->getWorkflowId();
|
||||
}
|
||||
return $this->wid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromState() {
|
||||
return WorkflowState::load($this->from_sid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getToState() {
|
||||
return WorkflowState::load($this->to_sid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFromSid() {
|
||||
return $this->from_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getToSid() {
|
||||
return $this->to_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAllowed(UserInterface $user, $force = FALSE) {
|
||||
|
||||
$type_id = $this->getWorkflowId();
|
||||
if ($user->hasPermission("bypass $type_id workflow_transition access")) {
|
||||
// Superuser is special. And $force allows Rules to cause transition.
|
||||
return TRUE;
|
||||
}
|
||||
if ($force) {
|
||||
return TRUE;
|
||||
}
|
||||
if ($this->getFromSid() == $this->getToSid()) {
|
||||
// Anyone may save an entity without changing state.
|
||||
return TRUE;
|
||||
}
|
||||
return TRUE == array_intersect($user->getRoles(), $this->roles);
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for Workflow*Transition* objects.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowScheduledTransition
|
||||
*/
|
||||
interface WorkflowConfigTransitionInterface {
|
||||
|
||||
/**
|
||||
* Determines if the current transition between 2 states is allowed:
|
||||
* - in settings;
|
||||
* - in permissions;
|
||||
* - by permission hooks, implemented by other modules.
|
||||
*
|
||||
* @param \Drupal\user\UserInterface $user
|
||||
* The user to act upon.
|
||||
* May have the custom WORKFLOW_ROLE_AUTHOR_RID role.
|
||||
* @param bool $force
|
||||
* Indicates if the transition must be forced(E.g., by cron, rules).
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if OK, else FALSE.
|
||||
*/
|
||||
public function isAllowed(UserInterface $user, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Returns the Workflow object of this State.
|
||||
*
|
||||
* @return Workflow
|
||||
* Workflow object.
|
||||
*/
|
||||
public function getWorkflow();
|
||||
|
||||
/**
|
||||
* Returns the Workflow ID of this Transition
|
||||
*
|
||||
* @return string
|
||||
* Workflow Id.
|
||||
*/
|
||||
public function getWorkflowId();
|
||||
|
||||
/**
|
||||
* @return WorkflowState
|
||||
*/
|
||||
public function getFromState();
|
||||
|
||||
/**
|
||||
* @return WorkflowState
|
||||
*/
|
||||
public function getToState();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFromSid();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getToSid();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for Workflow*Transition* objects.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowScheduledTransition
|
||||
*/
|
||||
interface WorkflowInterface {
|
||||
|
||||
/**
|
||||
* Returns the workflow id.
|
||||
*
|
||||
* @return string
|
||||
* $wid
|
||||
*/
|
||||
public function getWorkflowId();
|
||||
|
||||
/**
|
||||
* Validate the workflow. Generate a message if not correct.
|
||||
*
|
||||
* This function is used on the settings page of:
|
||||
* - Workflow field: WorkflowItem->settingsForm()
|
||||
*
|
||||
* @return bool
|
||||
* $is_valid
|
||||
*/
|
||||
public function isValid();
|
||||
|
||||
/**
|
||||
* Returns if the Workflow may be deleted.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a Workflow may safely be deleted.
|
||||
*/
|
||||
public function isDeletable();
|
||||
|
||||
/**
|
||||
* Create a new state for this workflow.
|
||||
*
|
||||
* @param string $sid
|
||||
* @param bool $save
|
||||
* Indicator if the new state must be saved. Normally, the new State is
|
||||
* saved directly in the database. This is because you can use States only
|
||||
* with Transitions, and they rely on State IDs which are generated
|
||||
* magically when saving the State. But you may need a temporary state.
|
||||
* @return \Drupal\workflow\Entity\WorkflowState
|
||||
* The new state.
|
||||
*/
|
||||
public function createState($sid, $save = TRUE);
|
||||
|
||||
/**
|
||||
* Gets the initial state for a newly created entity.
|
||||
*/
|
||||
public function getCreationState();
|
||||
|
||||
/**
|
||||
* Gets the ID of the initial state for a newly created entity.
|
||||
*/
|
||||
public function getCreationSid();
|
||||
|
||||
/**
|
||||
* Gets the first valid state ID, after the creation state.
|
||||
*
|
||||
* Uses WorkflowState::getOptions(), because this does an access check.
|
||||
* The first State ID is user-dependent!
|
||||
*
|
||||
* @param EntityInterface|null $entity
|
||||
* The entity at hand. May be NULL (E.g., on a Field settings page).
|
||||
* @param $field_name
|
||||
* @param AccountInterface $user
|
||||
* @param bool $force
|
||||
*
|
||||
* @return string
|
||||
* A State ID.
|
||||
*/
|
||||
public function getFirstSid(EntityInterface $entity, $field_name, AccountInterface $user, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Returns the next state for the current state.
|
||||
* Is used in VBO Bulk actions.
|
||||
*
|
||||
* @param EntityInterface $entity
|
||||
* The entity at hand.
|
||||
* @param $field_name
|
||||
* @param AccountInterface $user
|
||||
* @param bool $force
|
||||
*
|
||||
* @return string
|
||||
* A State ID.
|
||||
*/
|
||||
public function getNextSid(EntityInterface $entity, $field_name, AccountInterface $user, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Gets all states for a given workflow.
|
||||
*
|
||||
* @param mixed $all
|
||||
* Indicates to which states to return.
|
||||
* - TRUE = all, including Creation and Inactive;
|
||||
* - FALSE = only Active states, not Creation;
|
||||
* - 'CREATION' = only Active states, including Creation.
|
||||
* @param bool $reset
|
||||
*
|
||||
* @return WorkflowState[]
|
||||
* An array of WorkflowState objects.
|
||||
*/
|
||||
public function getStates($all = FALSE, $reset = FALSE);
|
||||
|
||||
/**
|
||||
* Gets a state for a given workflow.
|
||||
*
|
||||
* @param string $sid
|
||||
* A state ID.
|
||||
*
|
||||
* @return WorkflowState
|
||||
* A WorkflowState object.
|
||||
*/
|
||||
public function getState($sid);
|
||||
|
||||
/**
|
||||
* Creates a Transition for this workflow.
|
||||
*
|
||||
* @param string $from_sid
|
||||
* @param string $to_sid
|
||||
* @param array $values
|
||||
*
|
||||
* @return mixed|null|static
|
||||
*/
|
||||
public function createTransition($from_sid, $to_sid, $values = []);
|
||||
|
||||
/**
|
||||
* Sorts all Transitions for this workflow, according to State weight.
|
||||
*
|
||||
* This is only needed for the Admin UI.
|
||||
*/
|
||||
public function sortTransitions();
|
||||
|
||||
/**
|
||||
* Loads all allowed ConfigTransitions for this workflow.
|
||||
*
|
||||
* @param array|null $ids
|
||||
* Array of Transitions IDs. If NULL, show all transitions.
|
||||
* @param array $conditions
|
||||
* $conditions['from_sid'] : if provided, a 'from' State ID.
|
||||
* $conditions['to_sid'] : if provided, a 'to' state ID.
|
||||
*
|
||||
* @return \Drupal\workflow\Entity\WorkflowConfigTransition[]
|
||||
*/
|
||||
public function getTransitions(array $ids = NULL, array $conditions = []);
|
||||
|
||||
public function getTransitionsById($tid);
|
||||
|
||||
/**
|
||||
* Get a specific transition.
|
||||
*
|
||||
* @param string $from_sid
|
||||
* @param string $to_sid
|
||||
*
|
||||
* @return WorkflowConfigTransition[]
|
||||
*/
|
||||
public function getTransitionsByStateId($from_sid, $to_sid);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
||||
/**
|
||||
* Manages entity type plugin definitions.
|
||||
*/
|
||||
class WorkflowManager implements WorkflowManagerInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity_type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity query factory.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $queryFactory;
|
||||
|
||||
/**
|
||||
* The user settings config object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $userConfig;
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Construct the WorkflowManager object as a service.
|
||||
* @see workflow.services.yml
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity_type manager service.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* The entity query factory.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, QueryFactory $query_factory, ConfigFactoryInterface $config_factory, TranslationInterface $string_translation, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->queryFactory = $query_factory;
|
||||
$this->userConfig = $config_factory->get('user.settings');
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function executeScheduledTransitionsBetween($start = 0, $end = 0) {
|
||||
$clear_cache = FALSE;
|
||||
|
||||
// If the time now is greater than the time to execute a transition, do it.
|
||||
foreach (WorkflowScheduledTransition::loadBetween($start, $end) as $scheduled_transition) {
|
||||
$field_name = $scheduled_transition->getFieldName();
|
||||
$entity = $scheduled_transition->getTargetEntity();
|
||||
$from_sid = $scheduled_transition->getFromSid();
|
||||
|
||||
// Make sure transition is still valid: the entity must still be in
|
||||
// the state it was in, when the transition was scheduled.
|
||||
if (!$entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current_sid = workflow_node_current_state($entity, $field_name);
|
||||
if (!$current_sid || ($current_sid != $from_sid)) {
|
||||
// Entity is not in the same state it was when the transition
|
||||
// was scheduled. Defer to the entity's current state and
|
||||
// abandon the scheduled transition.
|
||||
$message = t('Scheduled Transition is discarded, since Entity has state ID %sid1, instead of expected ID %sid2.');
|
||||
$scheduled_transition->logError($message, 'error', $current_sid, $from_sid);
|
||||
$scheduled_transition->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
// If user didn't give a comment, create one.
|
||||
$comment = $scheduled_transition->getComment();
|
||||
if (empty($comment)) {
|
||||
$scheduled_transition->addDefaultComment();
|
||||
}
|
||||
|
||||
// Do transition. Force it because user who scheduled was checked.
|
||||
// The scheduled transition is not scheduled anymore, and is also deleted from DB.
|
||||
// A watchdog message is created with the result.
|
||||
$scheduled_transition->schedule(FALSE);
|
||||
$scheduled_transition->executeAndUpdateEntity(TRUE);
|
||||
|
||||
if (!$field_name) {
|
||||
$clear_cache = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($clear_cache) {
|
||||
// Clear the cache so that if the transition resulted in a entity
|
||||
// being published, the anonymous user can see it.
|
||||
Cache::invalidateTags(['rendered']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function executeTransitionsOfEntity(EntityInterface $entity) {
|
||||
|
||||
// Avoid this hook on workflow objects.
|
||||
if (in_array($entity->getEntityTypeId(), [
|
||||
'workflow_type',
|
||||
'workflow_state',
|
||||
'workflow_config_transition',
|
||||
'workflow_transition',
|
||||
'workflow_scheduled_transition',
|
||||
])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = workflow_current_user();
|
||||
|
||||
foreach (_workflow_info_fields($entity) as $field_info) {
|
||||
$field_name = $field_info->getName();
|
||||
|
||||
// Transition is created in widget or WorkflowTransitionForm.
|
||||
/** @var $transition WorkflowTransitionInterface */
|
||||
$transition = $entity->$field_name->__get('workflow_transition');
|
||||
if (!$transition) {
|
||||
// We come from creating/editing an entity via entity_form, with core widget or hidden Workflow widget.
|
||||
// @todo D8: from an Edit form with hidden widget.
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
if ($entity->original) {
|
||||
// Editing a Node with hidden Widget. State change not possible, so bail out.
|
||||
// $entity->$field_name->value = $entity->original->$field_name->value;
|
||||
// continue;
|
||||
}
|
||||
|
||||
// Creating a Node with hidden Workflow Widget. Generate valid first transition.
|
||||
// @todo D8: Test Creating a non-Node Entity with hidden Workflow widget.
|
||||
$comment = '';
|
||||
$old_sid = WorkflowManager::getPreviousStateId($entity, $field_name);
|
||||
$new_sid = $entity->$field_name->value;
|
||||
if ((!$new_sid) && $wid = $field_info->getSetting('workflow_type')) {
|
||||
/** @var Workflow $workflow */
|
||||
$workflow = Workflow::load($wid);
|
||||
$new_sid = $workflow->getFirstSid($entity, $field_name, $user);
|
||||
}
|
||||
$transition = WorkflowTransition::create([$old_sid, 'field_name' => $field_name]);
|
||||
$transition->setValues($new_sid, $user->id(), \Drupal::time()->getRequestTime(), $comment, TRUE);
|
||||
}
|
||||
|
||||
// We come from Content/Comment edit page, from widget.
|
||||
// Set the just-saved entity explicitly. Not necessary for update,
|
||||
// but upon insert, the old version didn't have an ID, yet.
|
||||
$transition->setTargetEntity($entity);
|
||||
|
||||
if ($transition->isScheduled()) {
|
||||
$executed = $transition->save(); // Returns a positive integer.
|
||||
}
|
||||
elseif ($entity->getEntityTypeId() == 'comment') {
|
||||
// If Transition is added via CommentForm, save Comment AND Entity.
|
||||
// Execute and check the result.
|
||||
$new_sid = $transition->executeAndUpdateEntity();
|
||||
$executed = ($new_sid == $transition->getToSid()) ? TRUE : FALSE;
|
||||
}
|
||||
else {
|
||||
// Execute and check the result.
|
||||
$new_sid = $transition->execute();
|
||||
$executed = ($new_sid == $transition->getToSid()) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
// If the transition failed, revert the entity workflow status.
|
||||
// For new entities, we do nothing: it has no original.
|
||||
if (!$executed && isset($entity->original)) {
|
||||
$originalValue = $entity->original->{$field_name}->value;
|
||||
$entity->{$field_name}->setValue($originalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getPreviousStateId(EntityInterface $entity, $field_name = '') {
|
||||
$sid = '';
|
||||
|
||||
if (!$entity) {
|
||||
return $sid;
|
||||
}
|
||||
|
||||
// If $field_name is not known, yet, determine it.
|
||||
$field_name = ($field_name) ? $field_name : workflow_get_field_name($entity, $field_name);
|
||||
// If $field_name is found, get more details.
|
||||
if (!$field_name) {
|
||||
// Return the initial value.
|
||||
return $sid;
|
||||
}
|
||||
|
||||
// A node may not have a Workflow attached.
|
||||
if ($entity->isNew()) {
|
||||
// A new Node. D7: $is_new is not set when saving terms, etc.
|
||||
$sid = self::getCreationStateId($entity, $field_name);
|
||||
}
|
||||
else {
|
||||
// @todo?: Read the history with an explicit langcode.
|
||||
// @todo D8: #2373383 add integration with older revisions via Revisioning module.
|
||||
$langcode = ''; // $entity->language()->getId();
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
$last_transition = WorkflowTransition::loadByProperties($entity_type, $entity->id(), [], $field_name, $langcode, 'DESC');
|
||||
if ($last_transition) {
|
||||
$sid = $last_transition->getToSid(); // @see #2637092, #2612702
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sid) {
|
||||
// No history found on an existing entity.
|
||||
$sid = self::getCreationStateId($entity, $field_name);
|
||||
}
|
||||
|
||||
return $sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the creation sid for a given $entity and $field_name.
|
||||
*
|
||||
* Is a helper function for:
|
||||
* - workflow_node_current_state()
|
||||
* - workflow_node_previous_state()
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* @param string $field_name
|
||||
*
|
||||
* @return string
|
||||
* The ID of the creation State for the Workflow of the field.
|
||||
*/
|
||||
private static function getCreationStateId($entity, $field_name) {
|
||||
$sid = '';
|
||||
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityBase $entity */
|
||||
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_config */
|
||||
$field_config = $entity->get($field_name)->getFieldDefinition();
|
||||
$field_storage = $field_config->getFieldStorageDefinition();
|
||||
$wid = $field_storage->getSetting('workflow_type');
|
||||
if ($wid) {
|
||||
/** @var Workflow $workflow */
|
||||
$workflow = Workflow::load($wid);
|
||||
if (!$workflow) {
|
||||
drupal_set_message(t('Workflow %wid cannot be loaded. Contact your system administrator.', ['%wid' => $wid]), 'error');
|
||||
}
|
||||
else {
|
||||
$sid = $workflow->getCreationSid();
|
||||
}
|
||||
}
|
||||
|
||||
return $sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function deleteUser(AccountInterface $account) {
|
||||
self::cancelUser([], $account, 'user_cancel_delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function cancelUser($edit, AccountInterface $account, $method) {
|
||||
|
||||
switch ($method) {
|
||||
case 'user_cancel_block': // Disable the account and keep its content.
|
||||
case 'user_cancel_block_unpublish': // Disable the account and unpublish its content.
|
||||
// Do nothing.
|
||||
break;
|
||||
case 'user_cancel_reassign': // Delete the account and make its content belong to the Anonymous user.
|
||||
case 'user_cancel_delete': // Delete the account and its content.
|
||||
|
||||
// Update tables for deleted account, move account to user 0 (anon.)
|
||||
// ALERT: This may cause previously non-Anonymous posts to suddenly
|
||||
// be accessible to Anonymous.
|
||||
|
||||
/**
|
||||
* Given a user id, re-assign history to the new user account. Called by user_delete().
|
||||
*/
|
||||
$uid = $account->id();
|
||||
$new_uid = 0;
|
||||
|
||||
db_update('workflow_transition_history')
|
||||
->fields(['uid' => $new_uid])
|
||||
->condition('uid', $uid, '=')
|
||||
->execute();
|
||||
db_update('workflow_transition_schedule')
|
||||
->fields(['uid' => $new_uid])
|
||||
->condition('uid', $uid, '=')
|
||||
->execute();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function participateUserRoles(EntityInterface $workflow) {
|
||||
$type_id = $workflow->id();
|
||||
foreach (user_roles() as $rid => $role) {
|
||||
$perms = ["create $type_id workflow_transition" => 1];
|
||||
user_role_change_permissions($rid, $perms); // <=== Enable Roles.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getWorkflowTransitionForm(EntityInterface $entity, string $field_name) {
|
||||
// Create a transition, to pass to the form. No need to use setValues().
|
||||
$current_sid = workflow_node_current_state($entity, $field_name);
|
||||
$transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($entity);
|
||||
// Create the WorkflowTransitionForm.
|
||||
/** @var \Drupal\Core\Entity\EntityFormBuilder $entity_form_builder */
|
||||
$entity_form_builder = \Drupal::getContainer()->get('entity.form_builder');
|
||||
$form = $entity_form_builder->getForm($transition, 'add');
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFields($entity_type_id) {
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow');
|
||||
return isset($map[$entity_type_id]) ? $map[$entity_type_id] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getAttachedFields($entity_type_id, $bundle) {
|
||||
$added_fields = [];
|
||||
|
||||
// Determine the fields added by Field UI.
|
||||
$entity_field_manager = \Drupal::service('entity_field.manager');
|
||||
//$extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle);
|
||||
//$base_fields = $entity_field_manager->getBaseFieldDefinitions($entity_type_id, $bundle);
|
||||
$fields = $entity_field_manager->getFieldDefinitions($entity_type_id, $bundle);
|
||||
foreach ($fields as $key => $field) {
|
||||
// Remove BaseFieldDefinition, BaseFieldOverride.
|
||||
if (($field instanceof FieldConfig)) {
|
||||
$added_fields[$key] = $field;
|
||||
}
|
||||
}
|
||||
return $added_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getCurrentStateId(EntityInterface $entity, $field_name = '') {
|
||||
$sid = '';
|
||||
|
||||
if (!$entity) {
|
||||
return $sid;
|
||||
}
|
||||
|
||||
// If $field_name is not known, yet, determine it.
|
||||
$field_name = ($field_name) ? $field_name : workflow_get_field_name($entity, $field_name);
|
||||
// If $field_name is found, get more details.
|
||||
if (!$field_name || !isset($entity->$field_name)) {
|
||||
// Return the initial value.
|
||||
return $sid;
|
||||
}
|
||||
|
||||
// Normal situation: get the value.
|
||||
$sid = $entity->$field_name->value;
|
||||
|
||||
// Entity is new or in preview or there is no current state. Use previous state.
|
||||
// (E.g., content was created before adding workflow.)
|
||||
if ( !$sid || !empty($entity->isNew()) || !empty($entity->in_preview) ) {
|
||||
$sid = self::getPreviousStateId($entity, $field_name);
|
||||
}
|
||||
|
||||
return $sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isOwner(AccountInterface $account, EntityInterface $entity = NULL) {
|
||||
$is_owner = FALSE;
|
||||
|
||||
// @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController
|
||||
// Determine if user is owner of the entity.
|
||||
$uid = ($account) ? $account->id() : -1;
|
||||
// Get the entity's ID and Author ID.
|
||||
$entity_id = ($entity) ? $entity->id() : '';
|
||||
// Some entities (e.g., taxonomy_term) do not have a uid.
|
||||
// $entity_uid = $entity->get('uid'); // isset($entity->uid) ? $entity->uid : 0;
|
||||
$entity_uid = (method_exists($entity, 'getOwnerId')) ? $entity->getOwnerId() : -1;
|
||||
|
||||
if (!$entity_id) {
|
||||
// This is a new entity. User is author. Add 'author' role to user.
|
||||
$is_owner = TRUE;
|
||||
}
|
||||
elseif (($entity_uid > 0) && ($uid > 0) && ($entity_uid == $uid)) {
|
||||
// This is an existing entity. User is author.
|
||||
// D8: use "access own" permission. D7: Add 'author' role to user.
|
||||
// N.B.: If 'anonymous' is the author, don't allow access to History Tab,
|
||||
// since anyone can access it, and it will be published in Search engines.
|
||||
$is_owner = TRUE;
|
||||
}
|
||||
else {
|
||||
// This is an existing entity. User is not the author. Do nothing.
|
||||
}
|
||||
return $is_owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isWorkflowEntityType(string $entity_type_id) {
|
||||
return in_array($entity_type_id, [
|
||||
'workflow_type',
|
||||
'workflow_state',
|
||||
'workflow_config_transition',
|
||||
'workflow_transition',
|
||||
'workflow_scheduled_transition',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for workflow manager.
|
||||
*
|
||||
* Contains lost of functions from D7 workflow.module file.
|
||||
*/
|
||||
interface WorkflowManagerInterface {
|
||||
|
||||
/**
|
||||
* Given a time frame, execute all scheduled transitions.
|
||||
*
|
||||
* Implements hook_cron().
|
||||
*
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
*/
|
||||
public static function executeScheduledTransitionsBetween($start = 0, $end = 0);
|
||||
|
||||
/**
|
||||
* Execute a single transition for the given entity.
|
||||
*
|
||||
* Implements hook_entity insert(), hook_entity_update().
|
||||
*
|
||||
* When inserting an entity with workflow field, the initial Transition is
|
||||
* saved without reference to the proper entity, since Id is not yet known.
|
||||
* So, we cannot save Transition in the Widget, but only(?) in a hook.
|
||||
* To keep things simple, this is done for both insert() and update().
|
||||
*
|
||||
* This is referenced in from WorkflowDefaultWidget::massageFormValues().
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*/
|
||||
public static function executeTransitionsOfEntity(EntityInterface $entity);
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Hook-implementing functions.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_WORKFLOW_insert().
|
||||
*
|
||||
* Make sure some roles are allowed to participate in a Workflow by default.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $workflow
|
||||
* @return
|
||||
*/
|
||||
public static function participateUserRoles(EntityInterface $workflow);
|
||||
|
||||
/**
|
||||
* Implements hook_user_delete().
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
*/
|
||||
public static function deleteUser(AccountInterface $account);
|
||||
|
||||
/**
|
||||
* Implements hook_user_cancel().
|
||||
* Implements deprecated workflow_update_workflow_transition_history_uid().
|
||||
*
|
||||
* " When cancelling the account
|
||||
* " - Disable the account and keep its content.
|
||||
* " - Disable the account and unpublish its content.
|
||||
* " - Delete the account and make its content belong to the Anonymous user.
|
||||
* " - Delete the account and its content.
|
||||
* "This action cannot be undone.
|
||||
*
|
||||
* @param $edit
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* @param string $method
|
||||
*/
|
||||
public static function cancelUser($edit, AccountInterface $account, $method);
|
||||
|
||||
/********************************************************************
|
||||
*
|
||||
* Helper functions.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility function to return an array of workflow fields.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The content entity type to which the workflow fields are attached.
|
||||
*
|
||||
* @return array
|
||||
* An array of workflow field map definitions, keyed by field name. Each
|
||||
* value is an array with two entries:
|
||||
* - type: The field type.
|
||||
* - bundles: The bundles in which the field appears, as an array with entity
|
||||
* types as keys and the array of bundle names as values.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface::getFieldMap()
|
||||
* @see \Drupal\comment\CommentManagerInterface::getFields()
|
||||
*/
|
||||
public function getFields($entity_type_id);
|
||||
|
||||
/**
|
||||
* Gets the TransitionWidget in a form (for e.g., Workflow History Tab)
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* @param string $field_name
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static function getWorkflowTransitionForm(EntityInterface $entity, string $field_name);
|
||||
|
||||
/**
|
||||
* Returns the attached fields (via Field UI)
|
||||
*
|
||||
* @param $entity_type_id
|
||||
* @param $bundle
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAttachedFields($entity_type_id, $bundle);
|
||||
|
||||
/**
|
||||
* Gets the current state ID of a given entity.
|
||||
*
|
||||
* There is no need to use a page cache.
|
||||
* The performance is OK, and the cache gives problems when using Rules.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to check.
|
||||
* @param string $field_name
|
||||
* The name of the field of the entity to check.
|
||||
* If empty, the field_name is determined on the spot. This must be avoided,
|
||||
* since it makes having multiple workflow per entity unpredictable.
|
||||
* The found field_name will be returned in the param.
|
||||
*
|
||||
* @return string
|
||||
* The ID of the current state.
|
||||
*/
|
||||
public static function getCurrentStateId(EntityInterface $entity, $field_name = '');
|
||||
|
||||
/**
|
||||
* Gets the previous state ID of a given entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* @param string $field_name
|
||||
*
|
||||
* @return string
|
||||
* The ID of the previous state.
|
||||
*/
|
||||
public static function getPreviousStateId(EntityInterface $entity, $field_name = '');
|
||||
|
||||
/**
|
||||
* Determine if User is owner/author of the entity.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isOwner(AccountInterface $account, EntityInterface $entity = NULL);
|
||||
|
||||
/**
|
||||
* Determine if the entity is Workflow* entity type.
|
||||
* Use it when a function should not operate on Workflow objects.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isWorkflowEntityType(string $entity_type_id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityConstraintViolationList;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
||||
/**
|
||||
* Implements a scheduled transition, as shown on Workflow form.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "workflow_scheduled_transition",
|
||||
* label = @Translation("Workflow scheduled transition"),
|
||||
* label_singular = @Translation("Workflow scheduled transition"),
|
||||
* label_plural = @Translation("Workflow scheduled transitions"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count Workflow scheduled transition",
|
||||
* plural = "@count Workflow scheduled transitions",
|
||||
* ),
|
||||
* bundle_label = @Translation("Workflow type"),
|
||||
* module = "workflow",
|
||||
* handlers = {
|
||||
* "access" = "Drupal\workflow\WorkflowAccessControlHandler",
|
||||
* "list_builder" = "Drupal\workflow\WorkflowTransitionListBuilder",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\workflow\Form\WorkflowTransitionForm",
|
||||
* "edit" = "Drupal\workflow\Form\WorkflowTransitionForm",
|
||||
* "delete" = "Drupal\Core\Entity\EntityDeleteForm",
|
||||
* },
|
||||
* "views_data" = "Drupal\workflow\WorkflowScheduledTransitionViewsData",
|
||||
* },
|
||||
* base_table = "workflow_transition_schedule",
|
||||
* translatable = FALSE,
|
||||
* entity_keys = {
|
||||
* "id" = "tid",
|
||||
* "bundle" = "wid",
|
||||
* "langcode" = "langcode",
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/workflow_transition/{workflow_transition}",
|
||||
* "delete-form" = "/workflow_transition/{workflow_transition}/delete",
|
||||
* "edit-form" = "/workflow_transition/{workflow_transition}/edit",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class WorkflowScheduledTransition extends WorkflowTransition {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(array $values = [], $entityType = 'workflow_scheduled_transition', $bundle = FALSE, $translations = []) {
|
||||
// Please be aware that $entity_type and $entityType are different things!
|
||||
parent::__construct($values, $entityType, $bundle, $translations);
|
||||
|
||||
// This transition is scheduled.
|
||||
$this->is_scheduled = TRUE;
|
||||
// This transition is not executed.
|
||||
$this->is_executed = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setValues($to_sid, $uid = NULL, $scheduled = NULL, $comment = '', $force_create = FALSE) {
|
||||
parent::setValues($to_sid, $uid, $scheduled, $comment, $force_create);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This is a hack to avoid the following error, because ScheduledTransition is not a bundle of Workflow:
|
||||
* Drupal\Component\Plugin\Exception\PluginNotFoundException: The "entity:workflow_scheduled_transition:eerste" plugin does not exist. in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 60 of core\lib\Drupal\Component\Plugin\Discovery\DiscoveryTrait.php).
|
||||
*/
|
||||
function validate() {
|
||||
// Since this function generates an error in one use case (using WorkflowTransitionForm)
|
||||
// and is not called in the other use case (using the Workflow Widget),
|
||||
// this function is disabled for now.
|
||||
// @todo: this function is only called in the WorkflowTransitionForm, not in the Widget.
|
||||
// @todo: repair https://www.drupal.org/node/2896650
|
||||
|
||||
// The following is from return parent::validate();
|
||||
$this->validated = TRUE;
|
||||
//$violations = $this->getTypedData()->validate();
|
||||
// return new EntityConstraintViolationList($this, iterator_to_array($violations));
|
||||
$violations = [];
|
||||
return new EntityConstraintViolationList($this, $violations);
|
||||
}
|
||||
|
||||
/**
|
||||
* CRUD functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Saves a scheduled transition. If the transition is executed, save in history.
|
||||
*/
|
||||
public function save() {
|
||||
|
||||
// If executed, save in history.
|
||||
if ($this->is_executed) {
|
||||
// Be careful, we are not a WorkflowScheduleTransition anymore!
|
||||
// No fuzzing around, just copy the ScheduledTransition to a normal one.
|
||||
$current_sid = $this->getFromSid();
|
||||
$field_name = $this->getFieldName();
|
||||
$executed_transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]);
|
||||
$executed_transition->setTargetEntity($this->getTargetEntity());
|
||||
$executed_transition->setValues($this->getToSid(), $this->getOwnerId(), \Drupal::time()->getRequestTime(), $this->getComment());
|
||||
return $executed_transition->save(); // <-- exit !!
|
||||
}
|
||||
|
||||
$hid = $this->id();
|
||||
if (!$hid) {
|
||||
// Insert the transition. Make sure it hasn't already been inserted.
|
||||
// @todo: Allow a scheduled transition per revision.
|
||||
$entity = $this->getTargetEntity();
|
||||
$found_transition = self::loadByProperties($entity->getEntityTypeId(), $entity->id(), [], $this->getFieldName(), $this->getLangcode());
|
||||
if ($found_transition) {
|
||||
// Avoid duplicate entries.
|
||||
$found_transition->delete();
|
||||
$result = parent::save();
|
||||
}
|
||||
else {
|
||||
$result = parent::save();
|
||||
}
|
||||
}
|
||||
else {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__ ); // @todo D8-port: still test this snippet.
|
||||
// Update the transition.
|
||||
$result = parent::save();
|
||||
}
|
||||
|
||||
// Create user message.
|
||||
if ($state = $this->getToState()) {
|
||||
$entity = $this->getTargetEntity();
|
||||
$message = '%entity_title scheduled for state change to %state_name on %scheduled_date';
|
||||
$args = [
|
||||
'%entity_title' => $entity->label(),
|
||||
'%state_name' => $state->label(),
|
||||
'%scheduled_date' => $this->getTimestampFormatted(),
|
||||
'link' => ($this->getTargetEntityId() && $this->getTargetEntity()->hasLinkTemplate('canonical')) ? $this->getTargetEntity()->toLink(t('View'))->toString() : '',
|
||||
];
|
||||
\Drupal::logger('workflow')->notice($message, $args);
|
||||
drupal_set_message(t($message, $args));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
// public static function loadMultiple(array $ids = NULL) {
|
||||
// return parent::loadMultiple($ids);
|
||||
// }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function loadByProperties($entity_type, $entity_id, array $revision_ids = [], $field_name = '', $langcode = '', $sort = 'ASC', $transition_type = 'workflow_scheduled_transition') {
|
||||
// N.B. $transition_type is set as parameter default.
|
||||
return parent::loadByProperties($entity_type, $entity_id, $revision_ids, $field_name, $langcode, $sort, $transition_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function loadMultipleByProperties($entity_type, array $entity_ids, array $revision_ids = [], $field_name = '', $langcode = '', $limit = NULL, $sort = 'ASC', $transition_type = 'workflow_scheduled_transition') {
|
||||
// N.B. $transition_type is set as parameter default.
|
||||
return parent::loadMultipleByProperties($entity_type, $entity_ids, $revision_ids, $field_name, $langcode, $limit, $sort, $transition_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a time frame, get all scheduled transitions.
|
||||
*
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
*
|
||||
* @return WorkflowScheduledTransition[]
|
||||
* An array of transitions.
|
||||
*/
|
||||
public static function loadBetween($start = 0, $end = 0) {
|
||||
$transition_type = 'workflow_scheduled_transition'; // @todo: get this from annotation.
|
||||
|
||||
/* @var $query \Drupal\Core\Entity\Query\QueryInterface */
|
||||
$query = \Drupal::entityQuery($transition_type)
|
||||
->sort('timestamp', 'ASC')
|
||||
->addTag($transition_type);
|
||||
if ($start) {
|
||||
$query->condition('timestamp', $start, '>');
|
||||
}
|
||||
if ($end) {
|
||||
$query->condition('timestamp', $end, '<');
|
||||
}
|
||||
|
||||
$ids = $query->execute();
|
||||
$transitions = self::loadMultiple($ids);
|
||||
return $transitions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If a scheduled transition has no comment, a default comment is added before executing it.
|
||||
*/
|
||||
public function addDefaultComment() {
|
||||
$this->setComment(t('Scheduled by user @uid.', ['@uid' => $this->getOwnerId()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the fields. Modify the parent fields.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = [];
|
||||
|
||||
// Add the specific ID-field : tid vs. hid.
|
||||
$fields['tid'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('Transition ID'))
|
||||
->setDescription(t('The transition ID.'))
|
||||
->setReadOnly(TRUE)
|
||||
->setSetting('unsigned', TRUE);
|
||||
|
||||
// Get the rest of the fields.
|
||||
$fields += parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// The timestamp has a different description.
|
||||
$fields['timestamp'] = []; // Reset old value.
|
||||
$fields['timestamp'] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Scheduled'))
|
||||
->setDescription(t('The date+time this transition is scheduled for.'))
|
||||
->setQueryable(FALSE)
|
||||
// ->setTranslatable(TRUE)
|
||||
// ->setDisplayOptions('view', array(
|
||||
// 'label' => 'hidden',
|
||||
// 'type' => 'timestamp',
|
||||
// 'weight' => 0,
|
||||
// ))
|
||||
// ->setDisplayOptions('form', array(
|
||||
// 'type' => 'datetime_timestamp',
|
||||
// 'weight' => 10,
|
||||
// ))
|
||||
// ->setDisplayConfigurable('form', TRUE);
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
|
||||
// Remove the specific ID-field : tid vs. hid.
|
||||
unset($fields['hid']);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,571 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\Entity;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Workflow configuration entity to persistently store configuration.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "workflow_state",
|
||||
* label = @Translation("Workflow state"),
|
||||
* label_singular = @Translation("Workflow state"),
|
||||
* label_plural = @Translation("Workflow states"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count Workflow state",
|
||||
* plural = "@count Workflow states",
|
||||
* ),
|
||||
* module = "workflow",
|
||||
* static_cache = TRUE,
|
||||
* translatable = FALSE,
|
||||
* handlers = {
|
||||
* "access" = "Drupal\workflow\WorkflowAccessControlHandler",
|
||||
* "list_builder" = "Drupal\workflow_ui\Controller\WorkflowStateListBuilder",
|
||||
* "form" = {
|
||||
* "delete" = "Drupal\Core\Entity\EntityDeleteForm",
|
||||
* }
|
||||
* },
|
||||
* config_prefix = "state",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label",
|
||||
* "weight" = "weight",
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "module",
|
||||
* "wid",
|
||||
* "weight",
|
||||
* "sysid",
|
||||
* "status",
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/admin/config/workflow/workflow/{workflow_type}",
|
||||
* "collection" = "/admin/config/workflow/workflow/{workflow_type}/states",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class WorkflowState extends ConfigEntityBase {
|
||||
|
||||
/**
|
||||
* The machine name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human readable name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The machine_name of the attached Workflow.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $wid;
|
||||
|
||||
/**
|
||||
* The weight of this Workflow state.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $weight;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $sysid = 0;
|
||||
public $status = 1;
|
||||
|
||||
/**
|
||||
* The attached Workflow.
|
||||
*
|
||||
* @var Workflow
|
||||
*/
|
||||
protected $workflow;
|
||||
|
||||
/**
|
||||
* CRUD functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $values
|
||||
* @param string $entityType
|
||||
* The name of the new State. If '(creation)', a CreationState is generated.
|
||||
*/
|
||||
public function __construct(array $values = [], $entityType = 'workflow_state') {
|
||||
// Please be aware that $entity_type_id and $entityType are different things!
|
||||
$sid = isset($values['id']) ? $values['id'] : '';
|
||||
|
||||
// Keep official name and external name equal. Both are required.
|
||||
// @todo: still needed? test import, manual creation, programmatic creation, etc.
|
||||
if (!isset($values['label']) && $sid) {
|
||||
$values['label'] = $sid;
|
||||
}
|
||||
|
||||
// Set default values for '(creation)' state.
|
||||
if ($sid == WORKFLOW_CREATION_STATE_NAME) {
|
||||
$values['sysid'] = WORKFLOW_CREATION_STATE;
|
||||
$values['weight'] = WORKFLOW_CREATION_DEFAULT_WEIGHT;
|
||||
// Do not translate the machine_name.
|
||||
$values['label'] = t('Creation');
|
||||
}
|
||||
parent::__construct($values, $entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save($create_creation_state = TRUE) {
|
||||
// Create the machine_name for new states.
|
||||
// N.B.: Keep machine_name in WorkflowState and ~ListBuilder aligned.
|
||||
$sid = $this->id();
|
||||
$wid = $this->wid;
|
||||
$label = $this->label();
|
||||
|
||||
// Set the workflow-including machine_name.
|
||||
if ($sid == WORKFLOW_CREATION_STATE_NAME) {
|
||||
// Set the initial sid.
|
||||
$sid = implode('_', [$wid, $sid]);
|
||||
$this->set('id', $sid);
|
||||
}
|
||||
elseif (empty($sid)) {
|
||||
if ($label) {
|
||||
$transliteration = \Drupal::service('transliteration');
|
||||
$value = $transliteration->transliterate($label, LanguageInterface::LANGCODE_DEFAULT, '_');
|
||||
$value = strtolower($value);
|
||||
$value = preg_replace('/[^a-z0-9_]+/', '_', $value);
|
||||
$sid = implode('_', [$wid, $value]);
|
||||
}
|
||||
else {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
$sid = 'state_' . $this->id();
|
||||
$sid = implode('_', [$wid, $sid]);
|
||||
}
|
||||
$this->set('id', $sid);
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all states in the system, with options to filter, only where a workflow exists.
|
||||
*
|
||||
* @_deprecated WorkflowState::getStates() ==> WorkflowState::loadMultiple()
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param $wid
|
||||
* The requested Workflow ID.
|
||||
* @param bool $reset
|
||||
* An option to refresh all caches.
|
||||
*
|
||||
* @return WorkflowState[]
|
||||
* An array of cached states, keyed by state_id.
|
||||
*/
|
||||
public static function loadMultiple(array $ids = NULL, $wid = '', $reset = FALSE) {
|
||||
$states = parent::loadMultiple();
|
||||
usort($states, ['Drupal\workflow\Entity\WorkflowState', 'sort'] );
|
||||
|
||||
// Filter onl Wid, if requested, E.g., by Workflow->getStates().
|
||||
// Set the ID as array key.
|
||||
$result = [];
|
||||
foreach ($states as $state) {
|
||||
/** @var WorkflowState $state */
|
||||
if ((!$wid) || ($wid == $state->wid)) {
|
||||
$result[$state->id()] = $state;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
|
||||
/** @var WorkflowState $a */
|
||||
/** @var WorkflowState $b */
|
||||
$a_wid = $a->wid;
|
||||
$b_wid = $b->wid;
|
||||
if ($a_wid == $b_wid) {
|
||||
$a_weight = $a->getWeight();
|
||||
$b_weight = $b->getWeight();
|
||||
return ($a_weight < $b_weight) ? -1 : 1;
|
||||
}
|
||||
return ($a_wid < $b_wid) ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate a Workflow State, moving existing content to a given State.
|
||||
*
|
||||
* @param int $new_sid
|
||||
* The state ID, to which all affected entities must be moved.
|
||||
*/
|
||||
public function deactivate($new_sid) {
|
||||
|
||||
$current_sid = $this->id();
|
||||
$force = TRUE;
|
||||
|
||||
// Notify interested modules. We notify first to allow access to data before we zap it.
|
||||
// - re-parents any entity that we don't want to orphan, whilst deactivating a State.
|
||||
// - delete any lingering entity to state values.
|
||||
// \Drupal::moduleHandler()->invokeAll('workflow', ['state delete', $current_sid, $new_sid, NULL, $force]);
|
||||
// Invoke the hook.
|
||||
\Drupal::moduleHandler()->invokeAll('entity_' . $this->getEntityTypeId() . '_predelete', [$this, $current_sid, $new_sid]);
|
||||
|
||||
// Re-parent any entity that we don't want to orphan, whilst deactivating a State.
|
||||
// TODO D8-port: State should not know about Transition: move this to Workflow->DeactivateState.
|
||||
if ($new_sid) {
|
||||
// A candidate for the batch API.
|
||||
// @TODO: Future updates should seriously consider setting this with batch.
|
||||
|
||||
$user = \Drupal::currentUser(); // We can use global, since deactivate() is a UI-only function.
|
||||
$comment = t('Previous state deleted');
|
||||
|
||||
foreach (_workflow_info_fields() as $field_info) {
|
||||
$entity_type_id = $field_info->getTargetEntityTypeId();
|
||||
$field_name = $field_info->getName(); // @todo: overwrites key?
|
||||
|
||||
$result = [];
|
||||
// CommentForm's are not re-parented upon Deactivate WorkflowState.
|
||||
if ($entity_type_id != 'comment') {
|
||||
$query = \Drupal::entityQuery($entity_type_id);
|
||||
$query->condition($field_name, $current_sid, '=');
|
||||
$result = $query->execute();
|
||||
}
|
||||
|
||||
foreach ($result as $entity_id) {
|
||||
$entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($entity_id);
|
||||
$transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($entity);
|
||||
$transition->setValues($new_sid, $user->id(), \Drupal::time()->getRequestTime(), $comment, TRUE);
|
||||
$transition->force($force);
|
||||
|
||||
// Execute Transition, invoke 'pre' and 'post' events, save new state in Field-table, save also in workflow_transition_history.
|
||||
// For Workflow Node, only {workflow_node} and {workflow_transition_history} are updated. For Field, also the Entity itself.
|
||||
// Execute transition and update the attached entity.
|
||||
$new_sid = $transition->executeAndUpdateEntity($force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the transitions this state is involved in.
|
||||
$workflow = Workflow::load($this->wid);
|
||||
/** @var WorkflowInterface $workflow */
|
||||
/** @var WorkflowTransitionInterface $transition */
|
||||
foreach ($workflow->getTransitionsByStateId($current_sid, '') as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
foreach ($workflow->getTransitionsByStateId('', $current_sid) as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
|
||||
// Delete the state. -- We don't actually delete, just deactivate.
|
||||
// This is a matter up for some debate, to delete or not to delete, since this
|
||||
// causes name conflicts for states. In the meantime, we just stick with what we know.
|
||||
// If you really want to delete the states, use workflow_cleanup module, or delete().
|
||||
$this->status = FALSE;
|
||||
$this->save();
|
||||
|
||||
// Clear the cache.
|
||||
self::loadMultiple([], 0, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Workflow ID of this State.
|
||||
*
|
||||
* @return string
|
||||
* Workflow Id.
|
||||
*/
|
||||
public function getWorkflowId() {
|
||||
return $this->wid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Workflow object of this State.
|
||||
*
|
||||
* @return Workflow
|
||||
* Workflow object.
|
||||
*/
|
||||
public function getWorkflow() {
|
||||
if (!isset($this->workflow)) {
|
||||
$this->workflow = Workflow::load($this->wid);
|
||||
}
|
||||
return $this->workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Workflow $workflow
|
||||
*/
|
||||
public function setWorkflow(Workflow $workflow) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
|
||||
$this->wid = $workflow->id();
|
||||
$this->workflow = $workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Workflow object of this State.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if state is active, else FALSE.
|
||||
*/
|
||||
public function isActive() {
|
||||
return (bool) $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given state is the 'Create' state.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCreationState() {
|
||||
return $this->sysid == WORKFLOW_CREATION_STATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the Workflow Form must be shown.
|
||||
*
|
||||
* If not, a formatter must be shown, since there are no valid options.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* @param string $field_name
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* @param bool $force
|
||||
*
|
||||
* @return bool
|
||||
* TRUE = a form (a.k.a. widget) must be shown; FALSE = no form, a formatter must be shown instead.
|
||||
*/
|
||||
public function showWidget(EntityInterface $entity, $field_name, AccountInterface $account, $force) {
|
||||
$options = $this->getOptions($entity, $field_name, $account, $force);
|
||||
$count = count($options);
|
||||
// The easiest case first: more then one option: always show form.
|
||||
if ($count > 1) {
|
||||
return TRUE;
|
||||
}
|
||||
// #2226451: Even in Creation state, we must have 2 visible states to show the widget.
|
||||
// // Only when in creation phase, one option is sufficient,
|
||||
// // since the '(creation)' option is not included in $options.
|
||||
// // When in creation state,
|
||||
// if ($this->isCreationState()) {
|
||||
// return TRUE;
|
||||
// }
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed transitions for the current state.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface|null $entity
|
||||
* The entity at hand. May be NULL (E.g., on a Field settings page).
|
||||
* @param string $field_name
|
||||
* @param \Drupal\Core\Session\AccountInterface|null $account
|
||||
* @param bool|false $force
|
||||
*
|
||||
* @return \Drupal\workflow\Entity\WorkflowConfigTransition[]
|
||||
* An array of id=>transition pairs with allowed transitions for State.
|
||||
*/
|
||||
public function getTransitions(EntityInterface $entity = NULL, $field_name = '', AccountInterface $account = NULL, $force = FALSE) {
|
||||
$transitions = [];
|
||||
|
||||
if (!$workflow = $this->getWorkflow()) {
|
||||
// No workflow, no options ;-)
|
||||
return $transitions;
|
||||
}
|
||||
|
||||
// @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController
|
||||
|
||||
/**
|
||||
* Get permissions of user, adding a Role to user, depending on situation.
|
||||
*/
|
||||
// Load a User object, since we cannot add Roles to AccountInterface.
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = workflow_current_user($account);
|
||||
// Determine if user is owner of the entity.
|
||||
$is_owner = WorkflowManager::isOwner($user, $entity);
|
||||
|
||||
// Check allow-ability of state change if user is not superuser (might be cron)
|
||||
$type_id = $this->getWorkflowId();
|
||||
if ($user->hasPermission("bypass $type_id workflow_transition access")) {
|
||||
// Superuser is special. And $force allows Rules to cause transition.
|
||||
$force = TRUE;
|
||||
}
|
||||
|
||||
if ($is_owner) {
|
||||
$user->addRole(WORKFLOW_ROLE_AUTHOR_RID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object and its permissions.
|
||||
*/
|
||||
/** @var WorkflowConfigTransition[] $transitions */
|
||||
$transitions = $workflow->getTransitionsByStateId($this->id(), '');
|
||||
|
||||
/**
|
||||
* Determine if user has Access.
|
||||
*/
|
||||
// Use default module permissions.
|
||||
foreach ($transitions as $key => $transition) {
|
||||
if (!$transition->isAllowed($user, $force)) {
|
||||
unset($transitions[$key]);
|
||||
}
|
||||
}
|
||||
// Let custom code add/remove/alter the available transitions,
|
||||
// using the new drupal_alter.
|
||||
// Modules may veto a choice by removing a transition from the list.
|
||||
// Lots of data can be fetched via the $transition object.
|
||||
$context = [
|
||||
'entity' => $entity, // ConfigEntities do not have entity attached
|
||||
'field_name' => $field_name, // or field.
|
||||
'user' => $user, // user may have the custom role AUTHOR.
|
||||
'workflow' => $workflow,
|
||||
'state' => $this,
|
||||
'force' => $force,
|
||||
];
|
||||
\Drupal::moduleHandler()->alter('workflow_permitted_state_transitions', $transitions, $context);
|
||||
|
||||
/**
|
||||
* Determine if user has Access.
|
||||
*/
|
||||
// As of 8.x-1.x, below hook() is removed, in favour of above alter().
|
||||
// Let custom code change the options, using old_style hook.
|
||||
// Above drupal_alter() calls hook_workflow_permitted_state_transitions_alter() only once.
|
||||
// foreach ($transitions as $transition) {
|
||||
// $to_sid = $transition->to_sid;
|
||||
// $permitted = [];
|
||||
//
|
||||
// // We now have a list of config_transitions. Check each against the Entity.
|
||||
// // Invoke a callback indicating that we are collecting state choices.
|
||||
// // Modules may veto a choice by returning FALSE.
|
||||
// // In this case, the choice is never presented to the user.
|
||||
// if (!$force) {
|
||||
// // @todo: D8-port: simplify interface for workflow_hook. Remove redundant context.
|
||||
// $permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition permitted', $transition, $user]);
|
||||
// }
|
||||
//
|
||||
// // If vetoed by a module, remove from list.
|
||||
// if (in_array(FALSE, $permitted, TRUE)) {
|
||||
// unset($transitions[$transition->id()]);
|
||||
// }
|
||||
// }
|
||||
|
||||
return $transitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed values for the current state.
|
||||
*
|
||||
* @param object $entity
|
||||
* The entity at hand. May be NULL (E.g., on a Field settings page).
|
||||
* @param string $field_name
|
||||
* @param \Drupal\Core\Session\AccountInterface|null $account
|
||||
* @param bool $force
|
||||
*
|
||||
* @return array
|
||||
* An array of sid=>label pairs.
|
||||
* If $this->id() is set, returns the allowed transitions from this state.
|
||||
* If $this->id() is 0 or FALSE, then labels of ALL states of the State's
|
||||
* Workflow are returned.
|
||||
*/
|
||||
public function getOptions($entity, $field_name, AccountInterface $account = NULL, $force = FALSE) {
|
||||
$options = [];
|
||||
|
||||
// Define an Entity-specific cache per page load.
|
||||
static $cache = [];
|
||||
|
||||
$entity_id = ($entity) ? $entity->id() : '';
|
||||
$entity_type_id = ($entity) ? $entity->getEntityTypeId() : '';
|
||||
$current_sid = $this->id();
|
||||
|
||||
// Get options from page cache, using a non-empty index (just to be sure).
|
||||
$entity_index = (!$entity) ? 'x' : $entity_id;
|
||||
if (isset($cache[$entity_type_id][$entity_index][$force][$current_sid])) {
|
||||
$options = $cache[$entity_type_id][$entity_index][$force][$current_sid];
|
||||
return $options;
|
||||
}
|
||||
|
||||
$workflow = $this->getWorkflow();
|
||||
if (!$workflow) {
|
||||
// No workflow, no options ;-)
|
||||
$options = [];
|
||||
}
|
||||
elseif (!$current_sid) {
|
||||
// If no State ID is given, we return all states.
|
||||
// We cannot use getTransitions, since there are no ConfigTransitions
|
||||
// from State with ID 0, and we do not want to repeat States.
|
||||
/** @var WorkflowState $state */
|
||||
foreach ($workflow->getStates() as $state) {
|
||||
$options[$state->id()] = html_entity_decode(t('@label', ['@label' => $state->label()]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$transitions = $this->getTransitions($entity, $field_name, $account, $force);
|
||||
foreach ($transitions as $transition) {
|
||||
// Get the label of the transition, and if empty of the target state.
|
||||
// Beware: the target state may not exist, since it can be invented
|
||||
// by custom code in the above drupal_alter() hook.
|
||||
if (!$label = $transition->label()) {
|
||||
$to_state = $transition->getToState();
|
||||
$label = $to_state ? $to_state->label() : '';
|
||||
}
|
||||
$to_sid = $transition->to_sid;
|
||||
$options[$to_sid] = html_entity_decode(t('@label', ['@label' => $label]));
|
||||
}
|
||||
|
||||
// Save to entity-specific cache.
|
||||
$cache[$entity_type_id][$entity_index][$force][$current_sid] = $options;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entities with this state.
|
||||
*
|
||||
* @return int
|
||||
* Counted number.
|
||||
*
|
||||
* @todo: add $options to select on entity type, etc.
|
||||
*/
|
||||
public function count() {
|
||||
$count = 0;
|
||||
$sid = $this->id();
|
||||
|
||||
foreach ($fields = _workflow_info_fields() as $field_info) {
|
||||
$field_name = $field_info->getName();
|
||||
$query = \Drupal::entityQuery($field_info->getTargetEntityTypeId());
|
||||
// @see #2285983 for using SQLite on D7.
|
||||
$count += $query
|
||||
->condition($field_name, $sid, '=')
|
||||
->count() // We only need the count.
|
||||
->execute();
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Storage handler for workflow config entities.
|
||||
*/
|
||||
class WorkflowStorage extends ConfigEntityStorage {
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $stateService;
|
||||
|
||||
/**
|
||||
* Constructs a WorkflowStorage object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory service.
|
||||
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
|
||||
* The UUID service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\State\StateInterface $state_service
|
||||
* The state service.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, StateInterface $state_service) {
|
||||
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager);
|
||||
|
||||
$this->stateService = $state_service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('config.factory'),
|
||||
$container->get('uuid'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('state')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of event names that are used by active workflows.
|
||||
*
|
||||
* @return string[]
|
||||
* The list of event names keyed by event name.
|
||||
*/
|
||||
protected function getRegisteredEvents() {
|
||||
// $events = [];
|
||||
// foreach ($this->loadMultiple() as $rules_config) {
|
||||
// $event = $rules_config->getEvent();
|
||||
// if ($event && !isset($events[$event])) {
|
||||
// $events[$event] = $event;
|
||||
// }
|
||||
// }
|
||||
// return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function doLoadMultiple(array $ids = NULL) {
|
||||
$return = parent::doLoadMultiple($ids);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
$return = parent::save($entity);
|
||||
|
||||
// // Update the state of registered events.
|
||||
// // @todo Should we trigger a container rebuild here as well? Might be a bit
|
||||
// // expensive on every save?
|
||||
// $this->stateService->set('rules.registered_events', $this->getRegisteredEvents());
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(array $entities) {
|
||||
$return = parent::delete($entities);
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for Workflow*Transition* objects.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowScheduledTransition
|
||||
*/
|
||||
interface WorkflowTransitionInterface extends WorkflowConfigTransitionInterface, EntityInterface, EntityOwnerInterface {
|
||||
|
||||
/**
|
||||
* Helper function for __construct. Used for all children of WorkflowTransition (aka WorkflowScheduledTransition)
|
||||
*
|
||||
* Usage:
|
||||
* $transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]);
|
||||
* $transition->setTargetEntity($entity);
|
||||
* $transition->setValues($new_sid, $user->id(), REQUEST_TIME, $comment);
|
||||
*
|
||||
* @param string $to_sid
|
||||
* @param int $uid
|
||||
* @param int $timestamp
|
||||
* @param string $comment
|
||||
* @param bool $force_create
|
||||
*/
|
||||
public function setValues($to_sid, $uid = NULL, $timestamp = NULL, $comment = '', $force_create = FALSE);
|
||||
|
||||
/**
|
||||
* Load (Scheduled) WorkflowTransitions, most recent first.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @param int $entity_id
|
||||
* @param array $revision_ids
|
||||
* @param string $field_name
|
||||
* @param string $langcode
|
||||
* @param string $sort
|
||||
* @param string $transition_type
|
||||
*
|
||||
* @return \Drupal\workflow\Entity\WorkflowTransitionInterface
|
||||
* Object representing one row from the {workflow_transition_history} table.
|
||||
*/
|
||||
public static function loadByProperties($entity_type, $entity_id, array $revision_ids = [], $field_name = '', $langcode = '', $sort = 'ASC', $transition_type = '');
|
||||
|
||||
/**
|
||||
* Given an entity, get all transitions for it.
|
||||
*
|
||||
* Since this may return a lot of data, a limit is included to allow for only one result.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @param int[] $entity_ids
|
||||
* @param int[] $revision_ids
|
||||
* @param string $field_name
|
||||
* Optional. Can be NULL, if you want to load any field.
|
||||
* @param string $langcode
|
||||
* Optional. Can be empty, if you want to load any language.
|
||||
* @param int $limit
|
||||
* Optional. Can be NULL, if you want to load all transitions.
|
||||
* @param string $sort
|
||||
* Optional sort order. {'ASC'|'DESC'}
|
||||
* @param string $transition_type
|
||||
* The type of the transition to be fetched.
|
||||
*
|
||||
* @return WorkflowTransitionInterface[]
|
||||
* An array of transitions.
|
||||
*/
|
||||
public static function loadMultipleByProperties($entity_type, array $entity_ids, array $revision_ids = [], $field_name = '', $langcode = '', $limit = NULL, $sort = 'ASC', $transition_type = '');
|
||||
|
||||
/**
|
||||
* Execute a transition (change state of an entity).
|
||||
*
|
||||
* A Scheduled Transition shall only be saved, unless the
|
||||
* 'schedule' property is set.
|
||||
* @usage
|
||||
* $transition->schedule(FALSE);
|
||||
* $to_sid = $transition->execute(TRUE);
|
||||
*
|
||||
* @param bool $force
|
||||
* If set to TRUE, workflow permissions will be ignored.
|
||||
*
|
||||
* @return
|
||||
* New state ID. If execution failed, old state ID is returned,
|
||||
*/
|
||||
public function execute($force = FALSE);
|
||||
|
||||
/**
|
||||
* Executes a transition (change state of an entity), from OUTSIDE the entity.
|
||||
*
|
||||
* Use $transition->executeAndUpdateEntity() to start a State Change from
|
||||
* outside an entity, e.g., workflow_cron().
|
||||
* Use $transition->execute() to start a State Change from within an entity.
|
||||
*
|
||||
* A Scheduled Transition ($transition->isScheduled() == TRUE) will be
|
||||
* un-scheduled and saved in the history table.
|
||||
* The entity will not be updated.
|
||||
* If $transition->isScheduled() == FALSE, the Transition will be
|
||||
* removed from the {workflow_transition_scheduled} table (if necessary),
|
||||
* and added to {workflow_transition_history} table.
|
||||
* Then the entity wil be updated to reflect the new status.
|
||||
*
|
||||
* @usage
|
||||
* $to_sid = $transition->->executeAndUpdateEntity($force);
|
||||
*
|
||||
* @see workflow_execute_transition()
|
||||
*
|
||||
* @param bool $force
|
||||
* If set to TRUE, workflow permissions will be ignored.
|
||||
*
|
||||
* @return string
|
||||
* The resulting WorkflowState id.
|
||||
*/
|
||||
public function executeAndUpdateEntity($force = FALSE);
|
||||
|
||||
/**
|
||||
* Invokes 'transition post'.
|
||||
* Adds the possibility to invoke the hook from elsewhere.
|
||||
*
|
||||
* @param bool $force
|
||||
*/
|
||||
public function post_execute($force = FALSE);
|
||||
|
||||
/**
|
||||
* Set the Entity, that is added to the Transition.
|
||||
* Also set all dependent fields, that will be saved in tables {workflow_transition_*}
|
||||
*
|
||||
* @param EntityInterface $entity
|
||||
* The Entity ID or the Entity object, to add to the Transition.
|
||||
*
|
||||
* @return object
|
||||
* The Entity, that is added to the Transition.
|
||||
*/
|
||||
public function setTargetEntity($entity);
|
||||
|
||||
/**
|
||||
* Returns the entity to which the workflow is attached.
|
||||
*
|
||||
* @return EntityInterface
|
||||
* The entity to which the workflow is attached.
|
||||
*/
|
||||
public function getTargetEntity();
|
||||
|
||||
/**
|
||||
* Returns the ID of the entity to which the workflow is attached.
|
||||
*
|
||||
* @return int
|
||||
* The ID of the entity to which the workflow is attached.
|
||||
*/
|
||||
public function getTargetEntityId();
|
||||
|
||||
/**
|
||||
* Returns the type of the entity to which the workflow is attached.
|
||||
*
|
||||
* @return string
|
||||
* An entity type.
|
||||
*/
|
||||
public function getTargetEntityTypeId();
|
||||
|
||||
/**
|
||||
* Get the field_name for which the Transition is valid.
|
||||
*
|
||||
* @return string
|
||||
* The field_name, that is added to the Transition.
|
||||
*/
|
||||
public function getFieldName();
|
||||
|
||||
/**
|
||||
* Get the language code for which the Transition is valid.
|
||||
*
|
||||
* @todo: OK?? Shouldn't we use entity's language() method for langcode?
|
||||
*
|
||||
* @return string $langcode
|
||||
*/
|
||||
public function getLangcode();
|
||||
|
||||
/**
|
||||
* Get the comment of the Transition.
|
||||
*
|
||||
* @return
|
||||
* The comment
|
||||
*/
|
||||
public function getComment();
|
||||
|
||||
/**
|
||||
* Get the comment of the Transition.
|
||||
*
|
||||
* @param $value
|
||||
* The new comment.
|
||||
*
|
||||
* @return WorkflowTransitionInterface
|
||||
*/
|
||||
public function setComment($value);
|
||||
|
||||
/**
|
||||
* Returns the time on which the transitions was or will be executed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public function getTimestamp();
|
||||
|
||||
/**
|
||||
* Returns the human-readable time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTimestampFormatted();
|
||||
|
||||
/**
|
||||
* Returns the time on which the transitions was or will be executed.
|
||||
*
|
||||
* @param $value
|
||||
* The new timestamp.
|
||||
* @return WorkflowTransitionInterface
|
||||
*/
|
||||
public function setTimestamp($value);
|
||||
|
||||
/**
|
||||
* Returns if this is a Scheduled Transition.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScheduled();
|
||||
|
||||
/**
|
||||
* Sets the Transition to be scheduled or not.
|
||||
*
|
||||
* @param bool $schedule
|
||||
* @return WorkflowTransitionInterface
|
||||
*/
|
||||
public function schedule($schedule = TRUE);
|
||||
|
||||
/**
|
||||
* Set the 'is_executed' property.
|
||||
*
|
||||
* @param bool $is_executed
|
||||
*
|
||||
* @return WorkflowTransitionInterface
|
||||
*/
|
||||
public function setExecuted($is_executed = TRUE);
|
||||
|
||||
/**
|
||||
* Returns if this is an Executed Transition.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExecuted();
|
||||
|
||||
/**
|
||||
* A transition may be forced skipping checks.
|
||||
*
|
||||
* @return bool
|
||||
* If the transition is forced. (Allow not-configured transitions).
|
||||
*/
|
||||
public function isForced();
|
||||
|
||||
/**
|
||||
* Set if a transition must be executed, even if transition is invalid
|
||||
* or user not authorized.
|
||||
*
|
||||
* @param bool $force
|
||||
*
|
||||
* @return object
|
||||
* The transition itself
|
||||
*/
|
||||
public function force($force = TRUE);
|
||||
|
||||
/**
|
||||
* Helper/debugging function. Shows simple contents of Transition.
|
||||
*
|
||||
* @param string $function
|
||||
*/
|
||||
public function dpm($function = '');
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Form;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Element\WorkflowTransitionElement;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
/**
|
||||
* Provides a Transition Form to be used in the Workflow Widget.
|
||||
*/
|
||||
class WorkflowTransitionForm extends ContentEntityForm {
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* Implementation of interface FormInterface.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
// We need a proprietary Form ID, to identify the unique forms
|
||||
// when multiple fields or entities are shown on 1 page.
|
||||
// Test this f.i. by checking the'scheduled' box. It wil
|
||||
// not unfold.
|
||||
$form_id = parent::getFormId();
|
||||
|
||||
/** @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
$transition = $this->entity;
|
||||
$field_name = $transition->getFieldName();
|
||||
|
||||
/** @var $entity \Drupal\Core\Entity\EntityInterface */
|
||||
// Entity may be empty on VBO bulk form.
|
||||
// $entity = $transition->getTargetEntity();
|
||||
// Compose Form Id from string + Entity Id + Field name.
|
||||
// Field ID contains entity_type, bundle, field_name.
|
||||
// The Form Id is unique, to allow for multiple forms per page.
|
||||
// $workflow_type_id = $transition->getWorkflowId();
|
||||
// Field name contains implicit entity_type & bundle (since 1 field per entity)
|
||||
// $entity_type = $transition->getTargetEntityTypeId();
|
||||
// $entity_id = $transition->getTargetEntityId();;
|
||||
|
||||
// Emulate nodeForm convention.
|
||||
if ($transition->id()) {
|
||||
$suffix = 'edit_form';
|
||||
}
|
||||
else {
|
||||
$suffix = 'form';
|
||||
}
|
||||
$form_id = implode('_', ['workflow_transition', $field_name, $suffix]);
|
||||
$form_id = Html::getUniqueId($form_id);
|
||||
|
||||
return $form_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* N.B. The D8-version of this form is stripped. If any use case is missing:
|
||||
* - compare with the D7-version of WorkflowTransitionForm::submitForm()
|
||||
* - compare with the D8-version of WorkflowTransitionElement::copyFormValuesToEntity()
|
||||
*/
|
||||
/*
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
}
|
||||
*/
|
||||
|
||||
/* *************************************************************************
|
||||
*
|
||||
* Implementation of interface EntityFormInterface (extends FormInterface).
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function is called by buildForm().
|
||||
* Caveat: !! It is not declared in the EntityFormInterface !!
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
// Call parent to get (extra) fields.
|
||||
// This might cause baseFieldDefinitions to appear twice.
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
$transition = $this->entity;
|
||||
|
||||
// Do not pass the element, but the form.
|
||||
// $element['#default_value'] = $transition;
|
||||
// $form += WorkflowTransitionElement::transitionElement($element, $form_state, $form);
|
||||
//
|
||||
// Pass the form via parameter
|
||||
$form['#default_value'] = $transition;
|
||||
$form = WorkflowTransitionElement::transitionElement($form, $form_state, $form);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the action form element for the current entity form.
|
||||
* Caveat: !! It is not declared in the EntityFormInterface !!
|
||||
*
|
||||
* {@inheritdoc}
|
||||
protected function actionsElement(array $form, FormStateInterface $form_state) {
|
||||
$element = parent::actionsElement($form, $form_state);
|
||||
return $element;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an array of supported actions for the current entity form.
|
||||
* Caveat: !! It is not declared in the EntityFormInterface !!
|
||||
* @param array $form
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* @return array
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
// N.B. Keep code aligned: workflow_form_alter(), WorkflowTransitionForm::actions().
|
||||
$actions = parent::actions($form, $form_state);
|
||||
|
||||
// A default button is provided by core. Override it.
|
||||
$actions['submit']['#value'] = t('Update workflow');
|
||||
$actions['submit']['#attributes'] = ['class' => ['form-save-default-button']];
|
||||
|
||||
if (!_workflow_use_action_buttons()) {
|
||||
// Change the default submit button on the Workflow History tab.
|
||||
return $actions;
|
||||
}
|
||||
|
||||
// Find the first workflow.
|
||||
// (So this won't work with multiple workflows per entity.)
|
||||
// Quit if there is no Workflow on this page.
|
||||
if (!$workflow_form = &$form) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
// Quit if there are no Workflow Action buttons.
|
||||
// (If user has only 1 workflow option, there are no Action buttons.)
|
||||
if (count($workflow_form['to_sid']['#options']) <= 1) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
// Place the buttons. Remove the default 'Save' button.
|
||||
// $actions += _workflow_transition_form_get_action_buttons($form, $workflow_form);
|
||||
// Remove the default submit button from the form.
|
||||
// unset($actions['submit']);
|
||||
$default_submit_action = $actions['submit'];
|
||||
$actions = _workflow_transition_form_get_action_buttons($form, $workflow_form, $default_submit_action);
|
||||
foreach ($actions as $key => &$action) {
|
||||
$action['#submit'] = $default_submit_action['#submit'];
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildEntity(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
|
||||
$entity = clone $this->entity;
|
||||
// Update the entity.
|
||||
$entity = $this->copyFormValuesToEntity($entity, $form, $form_state);
|
||||
// Mark the entity as NOT requiring validation. (Used in validateForm().)
|
||||
$entity->setValidationRequired(FALSE);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
parent::copyFormValuesToEntity($entity, $form, $form_state);
|
||||
// Use a proprietary version of copyFormValuesToEntity(), passing $entity by reference...
|
||||
$values = $form_state->getValues();
|
||||
// ... but only the returning object is OK (!).
|
||||
return WorkflowTransitionElement::copyFormValuesToTransition($entity, $form, $form_state, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This is called from submitForm().
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
// Execute transition and update the attached entity.
|
||||
/** @var WorkflowTransitionInterface $transition */
|
||||
$transition = $this->getEntity();
|
||||
return $transition->executeAndUpdateEntity();
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
* Implementation of interface ContentEntityFormInterface (extends EntityFormInterface).
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
//public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// return parent::validateForm($form, $form_state);
|
||||
//}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Provides the base form for workflow add and edit forms.
|
||||
*/
|
||||
class WorkflowTypeForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$noyes = [0 => t('No'), 1 => t('Yes')];
|
||||
$fieldset_options = [0 => t('No fieldset'), 1 => t('Collapsible fieldset'), 2 => t('Collapsed fieldset')];
|
||||
/* @var $workflow Workflow */
|
||||
$workflow = $this->entity;
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#description' => t('The human-readable label of the workflow. This will be used as a label when
|
||||
the workflow status is shown during editing of content.'),
|
||||
'#title' => $this->t('Label'),
|
||||
'#default_value' => $this->entity->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
|
||||
'#disabled' => !$this->entity->isNew(),
|
||||
'#default_value' => $this->entity->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
|
||||
'error' => $this->t('The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".'),
|
||||
],
|
||||
];
|
||||
|
||||
$form['permissions'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Workflow permissions'),
|
||||
'#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE.
|
||||
'#description' => t("To enable further Workflow functionality, go to the
|
||||
/admin/people/permissions page and select any roles that should have
|
||||
access to below and other functionalities."),
|
||||
];
|
||||
$form['permissions']['transition_execute'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => t('Participate in workflow (create, execute transitions)'),
|
||||
'#markup' => t("You can determine which roles are enabled on the
|
||||
'Workflow Transitions & roles' configuration page. Use this to enable
|
||||
only the relevant roles. Some sites have too many roles to show on
|
||||
the configuration page."),
|
||||
];
|
||||
$form['permissions']['transition_schedule'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => t('Schedule state transition'),
|
||||
'#markup' => t("Workflow transitions may be scheduled to a moment in the
|
||||
future. Soon after the desired moment, the transition is executed by
|
||||
Cron. This may be hidden by settings in widgets, formatters or permissions."
|
||||
),
|
||||
];
|
||||
$form['permissions']['history_tab'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => t('Access Workflow history tab'),
|
||||
'#markup' => t("You can determine if a tab 'Workflow history' is
|
||||
shown on the entity view page, which gives access to the History of
|
||||
the workflow.
|
||||
If you have multiple workflows per bundle, better disable this feature,
|
||||
and use, clone & adapt the Views display 'Workflow history per Entity'."),
|
||||
];
|
||||
|
||||
$form['basic'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Workflow form settings'),
|
||||
// '#description' => t('Lorem ipsum.'),
|
||||
'#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE.
|
||||
];
|
||||
|
||||
$form['basic']['fieldset'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $fieldset_options,
|
||||
'#title' => t('Show the form in a fieldset?'),
|
||||
'#default_value' => isset($workflow->options['fieldset']) ? $workflow->options['fieldset'] : 0,
|
||||
];
|
||||
$form['basic']['options'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('How to show the available states'),
|
||||
'#required' => FALSE,
|
||||
'#default_value' => isset($workflow->options['options']) ? $workflow->options['options'] : 'radios',
|
||||
// '#multiple' => TRUE / FALSE,
|
||||
'#options' => [
|
||||
// These options are taken from options.module
|
||||
'select' => 'Select list',
|
||||
'radios' => 'Radio buttons',
|
||||
'buttons' => 'Action buttons',
|
||||
'dropbutton' => 'Drop button',
|
||||
],
|
||||
'#description' => t("The Widget shows all available states. Decide which
|
||||
is the best way to show them."
|
||||
),
|
||||
];
|
||||
|
||||
$form['basic']['name_as_title'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
'#title' => t('Use the workflow name as the title of the workflow form'),
|
||||
'#default_value' => isset($workflow->options['name_as_title']) ? $workflow->options['name_as_title'] : 0,
|
||||
'#description' => t(
|
||||
'The workflow section of the editing form is in its own fieldset.
|
||||
Checking the box will add the workflow name as the title of workflow
|
||||
section of the editing form.'
|
||||
),
|
||||
];
|
||||
|
||||
$form['basic']['schedule_timezone'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Show a timezone when scheduling a transition.'),
|
||||
'#required' => FALSE,
|
||||
'#default_value' => isset($workflow->options['schedule_timezone']) ? $workflow->options['schedule_timezone'] : 1,
|
||||
];
|
||||
|
||||
$form['basic']['comment_log_node'] = [
|
||||
'#type' => 'select',
|
||||
'#required' => FALSE,
|
||||
'#options' => [
|
||||
// Use 0/1/2 to stay compatible with previous checkbox.
|
||||
0 => t('hidden'),
|
||||
1 => t('optional'),
|
||||
2 => t('required'),
|
||||
],
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
'#title' => t('How to show the Comment subfield'),
|
||||
'#default_value' => isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 1,
|
||||
'#description' => t(
|
||||
'A Comment area can be shown on the Workflow Transition form so that the person
|
||||
making a state change can record reasons for doing so. The comment is
|
||||
then included in the content\'s workflow history.'
|
||||
),
|
||||
];
|
||||
|
||||
$form['watchdog'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Watchdog'),
|
||||
'#description' => t('Informational watchdog messages can be logged when a transition is executed (state of a node is changed).'),
|
||||
'#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE.
|
||||
];
|
||||
|
||||
$form['watchdog']['watchdog_log'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Log watchdog messages upon state change'),
|
||||
'#default_value' => isset($workflow->options['watchdog_log']) ? $workflow->options['watchdog_log'] : 0,
|
||||
'#description' => '',
|
||||
];
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
// $actions['submit']['#value'] = $this->t('Save');
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
/* @var $entity \Drupal\workflow\Entity\Workflow */
|
||||
$entity = $this->entity;
|
||||
|
||||
// Prevent leading and trailing spaces.
|
||||
$entity->set('label', trim($entity->label()));
|
||||
|
||||
$entity->set('options', [
|
||||
'name_as_title' => $form_state->getValue('name_as_title'),
|
||||
'fieldset' => $form_state->getValue('fieldset'),
|
||||
'options' => $form_state->getValue('options'),
|
||||
'schedule_timezone' => $form_state->getValue('schedule_timezone'),
|
||||
'comment_log_node' => $form_state->getValue('comment_log_node'),
|
||||
'watchdog_log' => $form_state->getValue('watchdog_log'),
|
||||
]
|
||||
);
|
||||
|
||||
$status = parent::save($form, $form_state);
|
||||
$action = $status == SAVED_UPDATED ? 'updated' : 'added';
|
||||
|
||||
// Tell the user we've updated the data.
|
||||
$args = [
|
||||
'%label' => $entity->label(),
|
||||
'%action' => $action,
|
||||
'link' => $entity->toLink(t('Edit'))->toString(),
|
||||
];
|
||||
drupal_set_message($this->t('Workflow %label has been %action. Please maintain the permissions, states and transitions.', $args));
|
||||
$this->logger('workflow')->notice('Workflow %label has been %action.', $args);
|
||||
|
||||
if ($status == SAVED_NEW) {
|
||||
$form_state->setRedirect('entity.workflow_type.edit_form', ['workflow_type' => $entity->id()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation handler.
|
||||
*
|
||||
* @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 validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$workflow = $this->entity;
|
||||
$name = $workflow->id();
|
||||
|
||||
// Make sure workflow name is not numeric.
|
||||
// TODO: this was a prerequisite in D7. Remove in D8?
|
||||
if (ctype_digit($name)) {
|
||||
$form_state->setErrorByName('id', t('Please choose a non-numeric name for your workflow.'));
|
||||
}
|
||||
|
||||
parent::validateForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Machine name exists callback.
|
||||
*
|
||||
* @param string $id
|
||||
* The machine name ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if an entity with the same name already exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($id) {
|
||||
$type = $this->entity->getEntityTypeId();
|
||||
|
||||
return (bool) $this->entityTypeManager->getStorage($type)->load($id);
|
||||
}
|
||||
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Sets an entity to a new, given state.
|
||||
*
|
||||
* The only change is the 'type' in tha Annotation, so it works on Nodes,
|
||||
* and can be seen on admin/content page.
|
||||
*
|
||||
* @Action(
|
||||
* id = "workflow_node_given_state_action",
|
||||
* label = @Translation("Change a node to new Workflow state"),
|
||||
* type = "node"
|
||||
* )
|
||||
*/
|
||||
class WorkflowNodeGivenStateAction extends WorkflowStateActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies(){
|
||||
return [
|
||||
'module' => ['workflow', 'node'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
/*
|
||||
* D7: As advanced action with Trigger 'node':
|
||||
* - $entity is empty;
|
||||
* - $context['group'] = 'node'
|
||||
* - $context['hook'] = 'node_insert / _update / _delete'
|
||||
* - $context['node'] = (Object) stdClass
|
||||
* - $context['entity_type'] = NULL
|
||||
*
|
||||
* D7: As advanced action with Trigger 'taxonomy':
|
||||
* - $entity is (Object) stdClass;
|
||||
* - $context['type'] = 'entity'
|
||||
* - $context['group'] = 'taxonomy'
|
||||
* - $context['hook'] = 'taxonomy_term_insert / _update / _delete'
|
||||
* - $context['node'] = (Object) stdClass
|
||||
* - $context['entity_type'] = NULL
|
||||
*
|
||||
* D7: As advanced action with Trigger 'workflow API':
|
||||
* ...
|
||||
*
|
||||
* D7: As VBO action:
|
||||
* - $entity is (Object) stdClass;
|
||||
* - $context['type'] = NULL
|
||||
* - $context['group'] = NULL
|
||||
* - $context['hook'] = NULL
|
||||
* - $context['node'] = (Object) stdClass
|
||||
* - $context['entity_type'] = 'node'
|
||||
*/
|
||||
|
||||
if (!$transition = $this->getTransitionForExecution($object)) {
|
||||
drupal_set_message('The entity is not valid for this action.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire the transition.
|
||||
workflow_execute_transition($transition, $force);
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Sets an entity to the next state.
|
||||
*
|
||||
* The only change is the 'type' in tha Annotation, so it works on Nodes,
|
||||
* and can be seen on admin/content page.
|
||||
*
|
||||
* @Action(
|
||||
* id = "workflow_node_next_state_action",
|
||||
* label = @Translation("Change a node to next Workflow state"),
|
||||
* type = "node"
|
||||
* )
|
||||
*/
|
||||
class WorkflowNodeNextStateAction extends WorkflowStateActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies(){
|
||||
return [
|
||||
'module' => ['workflow', 'node'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
|
||||
// Remove to_sid. User can't set it, since we want a dynamic 'next' state.
|
||||
unset($form['to_sid']);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
|
||||
if (!$transition = $this->getTransitionForExecution($object)) {
|
||||
drupal_set_message('The object is not valid for this action.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the new next state.
|
||||
*/
|
||||
$entity = $transition->getTargetEntity();
|
||||
$field_name = $transition->getFieldName();
|
||||
$user = $transition->getOwner();
|
||||
$force = $this->configuration['force'];
|
||||
// $comment = $transition->getComment();
|
||||
|
||||
// Get the node's new State Id (which is the next available state).
|
||||
$to_sid = $transition->getWorkflow()->getNextSid($entity, $field_name, $user, $force);
|
||||
|
||||
// Add actual data.
|
||||
$transition->to_sid = $to_sid;
|
||||
|
||||
// Fire the transition.
|
||||
workflow_execute_transition($transition, $force);
|
||||
}
|
||||
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflow\Element\WorkflowTransitionElement;
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
use Drupal\workflow\Entity\WorkflowTransition;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Sets an entity to a new, given state.
|
||||
*
|
||||
* Example Annotation @ Action(
|
||||
* id = "workflow_given_state_action",
|
||||
* label = @Translation("Change a node to new Workflow state"),
|
||||
* type = "workflow"
|
||||
* )
|
||||
*/
|
||||
abstract class WorkflowStateActionBase extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return parent::calculateDependencies() + [
|
||||
'module' => ['workflow',],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
$configuration = parent::defaultConfiguration();
|
||||
$configuration += $this->configuration;
|
||||
$configuration += [
|
||||
'field_name' => '',
|
||||
'to_sid' => '',
|
||||
'comment' => "New state is set by a triggered Action.",
|
||||
'force' => 0,
|
||||
];
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityInterface $entity
|
||||
* @return \Drupal\workflow\Entity\WorkflowTransitionInterface
|
||||
*/
|
||||
protected function getTransitionForExecution(EntityInterface $entity) {
|
||||
$user = workflow_current_user();
|
||||
|
||||
if (!$entity) {
|
||||
\Drupal::logger('workflow_action')->notice('Unable to get current entity - entity is not defined.', []);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the entity type and numeric ID.
|
||||
$entity_id = $entity->id();
|
||||
if (!$entity_id) {
|
||||
\Drupal::logger('workflow_action')->notice('Unable to get current entity ID - entity is not yet saved.', []);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// In 'after saving new content', the node is already saved. Avoid second insert.
|
||||
// @todo: clone?
|
||||
$entity->enforceIsNew(FALSE);
|
||||
|
||||
$config = $this->configuration;
|
||||
$field_name = workflow_get_field_name($entity, $config['field_name']);
|
||||
$current_sid = workflow_node_current_state($entity, $field_name);
|
||||
if (!$current_sid) {
|
||||
\Drupal::logger('workflow_action')->notice('Unable to get current workflow state of entity %id.', ['%id' => $entity_id]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$to_sid = isset($config['to_sid']) ? $config['to_sid'] : '';
|
||||
// Get the Comment. Parse the $comment variables.
|
||||
$comment_string = $this->configuration['comment'];
|
||||
$comment = t($comment_string, [
|
||||
'%title' => $entity->label(),
|
||||
// "@" and "%" will automatically run check_plain().
|
||||
'%state' => workflow_get_sid_name($to_sid),
|
||||
'%user' => $user->getDisplayName(),
|
||||
]);
|
||||
$force = $this->configuration['force'];
|
||||
|
||||
$transition = WorkflowTransition::create([$current_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($entity);
|
||||
$transition->setValues($to_sid, $user->id(), \Drupal::time()->getRequestTime(), $comment);
|
||||
$transition->force($force);
|
||||
|
||||
return $transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = [];
|
||||
|
||||
// If we are on admin/config/system/actions and use CREATE AN ADVANCED ACTION
|
||||
// Then $context only contains:
|
||||
// - $context['actions_label'] = "Change workflow state of post to new state"
|
||||
// - $context['actions_type'] = "entity"
|
||||
//
|
||||
// If we are on a VBO action form, then $context only contains:
|
||||
// - $context['entity_type'] = "node"
|
||||
// - $context['view'] = "(Object) view"
|
||||
// - $context['settings'] = "[]"
|
||||
|
||||
$config = $this->configuration;
|
||||
$field_name = $config['field_name'];
|
||||
$wids = workflow_get_workflow_names();
|
||||
|
||||
if (empty($field_name) && count($wids) > 1) {
|
||||
drupal_set_message('You have more then one workflow in the system. Please first select the field name
|
||||
and save the form. Then, revisit the form to set the correct state value.', 'warning');
|
||||
}
|
||||
if (empty($field_name)) {
|
||||
$wid = count($wids) ? array_keys($wids)[0] : '';
|
||||
}
|
||||
else {
|
||||
$fields = _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name);
|
||||
$wid = count($fields) ? reset($fields)->getSetting('workflow_type') : '';
|
||||
}
|
||||
|
||||
// Get the common Workflow, or create a dummy Workflow.
|
||||
$workflow = $wid ? Workflow::load($wid) : Workflow::create(['id' => 'dummy_action', 'label' => 'dummy_action']);
|
||||
$current_state = $workflow->getCreationState();
|
||||
|
||||
/*
|
||||
// @todo D8-port for VBO
|
||||
// Show the current state and the Workflow form to allow state changing.
|
||||
// N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo.
|
||||
if ($workflow) {
|
||||
$field = _workflow_info_field($field_name, $workflow);
|
||||
$field_name = $field['field_name'];
|
||||
$field_id = $field['id'];
|
||||
$instance = field_info_instance($entity_type, $field_name, $entity_bundle);
|
||||
|
||||
// Hide the submit button. VBO has its own 'next' button.
|
||||
$instance['widget']['settings']['submit_function'] = '';
|
||||
if (!$field_id) {
|
||||
// This is a Workflow Node workflow. Set widget options as in v7.x-1.2
|
||||
$field['settings']['widget']['comment'] = $workflow->options['comment_log_node']; // 'comment_log_tab' is removed;
|
||||
$field['settings']['widget']['current_status'] = TRUE;
|
||||
// As stated above, the options list is probably very long, so let's use select list.
|
||||
$field['settings']['widget']['options'] = 'select';
|
||||
// Do not show the default [Update workflow] button on the form.
|
||||
$instance['widget']['settings']['submit_function'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the form/widget to the formatter, and include the nid and field_id in the form id,
|
||||
// to allow multiple forms per page (in listings, with hook_forms() ).
|
||||
// Ultimately, this is a wrapper for WorkflowDefaultWidget.
|
||||
// $form['workflow_current_state'] = workflow_state_formatter($entity_type, $entity, $field, $instance);
|
||||
$form_id = implode('_', [
|
||||
'workflow_transition_form',
|
||||
$entity_type,
|
||||
$entity_id,
|
||||
$field_id
|
||||
]);
|
||||
*/
|
||||
$user = workflow_current_user();
|
||||
$transition = WorkflowTransition::create([$current_state, 'field_name' => $field_name]);
|
||||
$transition->setValues(
|
||||
$to_sid = $config['to_sid'],
|
||||
$user->id(),
|
||||
\Drupal::time()->getRequestTime(),
|
||||
$comment = $config['comment'],
|
||||
$force = $config['force']
|
||||
);
|
||||
|
||||
// Add the WorkflowTransitionForm to the page. @todo
|
||||
|
||||
$element = []; // Just to be explicit.
|
||||
$element['#default_value'] = $transition;
|
||||
|
||||
// Avoid Action Buttons. That removes the options box&more. No Buttons in config screens!
|
||||
$original_options = $transition->getWorkflow()->options['options'];
|
||||
$transition->getWorkflow()->options['options'] = 'select';
|
||||
// Generate and add the Workflow form element.
|
||||
$element = WorkflowTransitionElement::transitionElement($element, $form_state, $form);
|
||||
// Just to be sure, reset the options box setting.
|
||||
$transition->getWorkflow()->options['options'] = $original_options;
|
||||
|
||||
// Make adaptations for VBO-form:
|
||||
$element['field_name']['#access'] = TRUE;
|
||||
$element['force']['#access'] = TRUE;
|
||||
$element['to_sid']['#description'] = t('Please select the state that should be assigned when this action runs.');
|
||||
$element['comment']['#title'] = $this->t('Message');
|
||||
$element['comment']['#description'] = $this->t('This message will be written into the workflow history log when the action
|
||||
runs. You may include the following variables: %state, %title, %user.');
|
||||
|
||||
$form['workflow_transition_action_config'] = $element;
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$configuration = $form_state->getValue('workflow_transition_action_config');
|
||||
// Remove the transition: generates an error upon saving the action definition.
|
||||
unset($configuration['workflow_transition']);
|
||||
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access = AccessResult::allowed();
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
|
||||
/**
|
||||
* Provides a 'Workflow Transition form' block.
|
||||
* Credits to workflow_extensions module.
|
||||
*
|
||||
* @todo D8-port: add cache options in configuration.
|
||||
* 'cache' => DRUPAL_NO_CACHE, // DRUPAL_CACHE_PER_ROLE will be assumed.
|
||||
*
|
||||
* @Block(
|
||||
* id = "workflow_transition_form_block",
|
||||
* admin_label = @Translation("Workflow Transition form"),
|
||||
* category = @Translation("Forms")
|
||||
* )
|
||||
*/
|
||||
class WorkflowTransitionBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
/* @var $entity \Drupal\Core\Entity\EntityInterface */
|
||||
if (!$entity = workflow_url_get_entity()) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
// Only show block on entity view page (when default operation = '').
|
||||
if ($operation = workflow_url_get_operation()) {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
// Only show block if entity has workflow, and user has permission.
|
||||
foreach (_workflow_info_fields($entity) as $definition) {
|
||||
$type_id = $definition->getSetting('workflow_type');
|
||||
if ($account->hasPermission("access $type_id workflow_transition form")) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$form = [];
|
||||
|
||||
// Get the entity for this form.
|
||||
/* @var $entity \Drupal\Core\Entity\EntityInterface */
|
||||
if (!$entity = workflow_url_get_entity()) {
|
||||
return $form;
|
||||
}
|
||||
// Get the field name. Avoid error on Node Add page.
|
||||
if (!$field_name = workflow_get_field_name($entity)) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
/*
|
||||
* Output: generate the Transition Form.
|
||||
*/
|
||||
// Add the WorkflowTransitionForm to the page.
|
||||
$form = WorkflowManager::getWorkflowTransitionForm($entity, $field_name);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity form builder.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityFormBuilderInterface
|
||||
* The entity form builder.
|
||||
*/
|
||||
protected function entityFormBuilder() {
|
||||
return \Drupal::getContainer()->get('entity.form_builder');
|
||||
}
|
||||
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Entity\EntityFormBuilderInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a default workflow formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "workflow_default",
|
||||
* module = "workflow",
|
||||
* label = @Translation("Workflow form"),
|
||||
* field_types = {
|
||||
* "workflow"
|
||||
* },
|
||||
* quickedit = {
|
||||
* "editor" = "disabled"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class WorkflowDefaultFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The workflow storage.
|
||||
*
|
||||
* @var \Drupal\workflow\Entity\WorkflowStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The render controller.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityViewBuilderInterface
|
||||
*/
|
||||
protected $viewBuilder;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity form builder.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFormBuilderInterface
|
||||
*/
|
||||
protected $entityFormBuilder;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$configuration['field_definition'],
|
||||
$configuration['settings'],
|
||||
$configuration['label'],
|
||||
$configuration['view_mode'],
|
||||
$configuration['third_party_settings'],
|
||||
$container->get('current_user'),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('entity.form_builder')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new WorkflowDefaultFormatter.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the formatter.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* The definition of the field to which the formatter is associated.
|
||||
* @param array $settings
|
||||
* The formatter settings.
|
||||
* @param string $label
|
||||
* The formatter label display setting.
|
||||
* @param string $view_mode
|
||||
* The view mode.
|
||||
* @param array $third_party_settings
|
||||
* Third party settings.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
|
||||
* The entity manager
|
||||
* @param \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder
|
||||
* The entity form builder.
|
||||
*/
|
||||
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityTypeManagerInterface $entity_manager, EntityFormBuilderInterface $entity_form_builder) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
|
||||
$this->viewBuilder = $entity_manager->getViewBuilder('workflow_transition');
|
||||
$this->storage = $entity_manager->getStorage('workflow_transition');
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_manager;
|
||||
$this->entityFormBuilder = $entity_form_builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* N.B. A large part of this function is taken from CommentDefaultFormatter.
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$output = [];
|
||||
|
||||
$field_name = $this->fieldDefinition->getName();
|
||||
$entity = $items->getEntity();
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
|
||||
$user = \Drupal::currentUser(); // @todo #2287057: OK?
|
||||
// @todo: Perhaps global user is not always the correct user.
|
||||
// E.g., on ScheduledTransition->execute()? But this function is mostly used in UI.
|
||||
|
||||
$current_sid = WorkflowManager::getCurrentStateId($entity, $field_name);
|
||||
/* @var $current_state WorkflowState */
|
||||
$current_state = WorkflowState::load($current_sid);
|
||||
|
||||
// First compose the current value with the normal formatter from list.module.
|
||||
$elements = workflow_state_formatter($entity, $field_name, $current_sid);
|
||||
|
||||
// The state must not be deleted, or corrupted.
|
||||
if (!$current_state) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
// Check permission, so that even with state change rights,
|
||||
// the form can be suppressed from the entity view (#1893724).
|
||||
$type_id = $current_state->getWorkflowId();
|
||||
if (!\Drupal::currentUser()->hasPermission("access $type_id workflow_transition form")) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
// Workflows are added to the search results and search index by
|
||||
// workflow_node_update_index() instead of by this formatter, so don't
|
||||
// return anything if the view mode is search_index or search_result.
|
||||
if (in_array($this->viewMode, ['search_result', 'search_index'])) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
if ($entity_type == 'comment') {
|
||||
// No Workflow form allowed on a comment display.
|
||||
// (Also, this avoids a lot of error messages.)
|
||||
return $elements;
|
||||
}
|
||||
|
||||
// Only build form if user has possible target state(s).
|
||||
if (!$current_state->showWidget($entity, $field_name, $user, FALSE)) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
// Remove the default formatter. We are now building the widget.
|
||||
$elements = [];
|
||||
|
||||
// BEGIN Copy from CommentDefaultFormatter
|
||||
$elements['#cache']['contexts'][] = 'user.permissions';
|
||||
// Add the WorkflowTransitionForm to the page.
|
||||
$output['workflows'] = WorkflowManager::getWorkflowTransitionForm($entity, $field_name);
|
||||
|
||||
// Only show the add workflow form if the user has permission.
|
||||
$elements['#cache']['contexts'][] = 'user.roles';
|
||||
// Do not show the form for the print view mode.
|
||||
$elements[] = $output + [
|
||||
'#workflow_type' => $this->getFieldSetting('workflow_type'),
|
||||
'#workflow_display_mode' => $this->getFieldSetting('default_mode'),
|
||||
'workflows' => [],
|
||||
];
|
||||
// END Copy from CommentDefaultFormatter
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity form builder.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityFormBuilderInterface
|
||||
* The entity form builder.
|
||||
*/
|
||||
protected function entityFormBuilder() {
|
||||
if (!$this->entityFormBuilder) {
|
||||
$this->entityFormBuilder = $this->container()->get('entity.form_builder');
|
||||
}
|
||||
return $this->entityFormBuilder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\OptGroup;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\options\Plugin\Field\FieldType\ListItemBase;
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'workflow' field type.
|
||||
*
|
||||
* @FieldType(
|
||||
* id = "workflow",
|
||||
* label = @Translation("Workflow state"),
|
||||
* description = @Translation("This field stores Workflow values for a certain Workflow type from a list of allowed 'value => label' pairs, i.e. 'Publishing': 1 => unpublished, 2 => draft, 3 => published."),
|
||||
* category = @Translation("Workflow"),
|
||||
* default_widget = "workflow_default",
|
||||
* default_formatter = "list_default",
|
||||
* constraints = {
|
||||
* "WorkflowField" = {}
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class WorkflowItem extends ListItemBase {
|
||||
//class WorkflowItem extends FieldItemBase implements OptionsProviderInterface {
|
||||
// @todo D8-port: perhaps even:
|
||||
//class WorkflowItem extends FieldStringItem {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
$schema = [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'description' => 'The {workflow_states}.sid that this entity is currently in.',
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'value' => ['value'],
|
||||
],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
|
||||
/**
|
||||
* Property definitions of the contained properties.
|
||||
*
|
||||
* @see FileItem::getPropertyDefinitions()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $propertyDefinitions;
|
||||
|
||||
|
||||
$definition['settings']['target_type'] = 'workflow_transition';
|
||||
// Definitions vary by entity type and bundle, so key them accordingly.
|
||||
$key = $definition['settings']['target_type'] . ':';
|
||||
$key .= isset($definition['settings']['target_bundle']) ? $definition['settings']['target_bundle'] : '';
|
||||
|
||||
if (!isset($propertyDefinitions[$key])) {
|
||||
|
||||
$propertyDefinitions[$key]['value'] = DataDefinition::create('string') // TODO D8-port : or 'any'
|
||||
->setLabel(t('Workflow state'))
|
||||
->addConstraint('Length', ['max' => 128])
|
||||
->setRequired(TRUE);
|
||||
|
||||
//workflow_debug( __FILE__ , __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
/*
|
||||
// @todo D8-port: test this.
|
||||
$propertyDefinitions[$key]['workflow_transition'] = DataDefinition::create('any')
|
||||
// $properties['workflow_transition'] = DataDefinition::create('WorkflowTransition')
|
||||
->setLabel(t('Transition'))
|
||||
->setDescription(t('The computed WorkflowItem object.'))
|
||||
->setComputed(TRUE)
|
||||
->setClass('\Drupal\workflow\Entity\WorkflowTransition')
|
||||
->setSetting('date source', 'value');
|
||||
|
||||
$propertyDefinitions[$key]['display'] = array(
|
||||
'type' => 'boolean',
|
||||
'label' => t('Flag to control whether this file should be displayed when viewing content.'),
|
||||
);
|
||||
$propertyDefinitions[$key]['description'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('A description of the file.'),
|
||||
);
|
||||
|
||||
$propertyDefinitions[$key]['display'] = array(
|
||||
'type' => 'boolean',
|
||||
'label' => t('Flag to control whether this file should be displayed when viewing content.'),
|
||||
);
|
||||
$propertyDefinitions[$key]['description'] = array(
|
||||
'type' => 'string',
|
||||
'label' => t('A description of the file.'),
|
||||
);
|
||||
*/
|
||||
}
|
||||
return $propertyDefinitions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntity() {
|
||||
$entity = parent::getEntity();
|
||||
|
||||
// For Workflow on CommentForm, get the CommentedEntity.
|
||||
if ($entity->getEntityTypeId() == 'comment') {
|
||||
/** @var \Drupal\comment\CommentInterface $entity */
|
||||
$entity = $entity->getCommentedEntity();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
$is_empty = empty($this->value);
|
||||
return $is_empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onChange($property_name, $notify = TRUE) {
|
||||
//workflow_debug( __FILE__ , __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
|
||||
// TODO D8: use this function onChange for adding a line in table workflow_transition_*
|
||||
// Enforce that the computed date is recalculated.
|
||||
//if ($property_name == 'value') {
|
||||
// $this->date = NULL;
|
||||
//}
|
||||
parent::onChange($property_name, $notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultStorageSettings() {
|
||||
|
||||
return [
|
||||
'workflow_type' => '',
|
||||
'allowed_values_function' => 'workflow_state_allowed_values',
|
||||
] + parent::defaultStorageSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_settings_form() -> ConfigFieldItemInterface::settingsForm().
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
$element = [];
|
||||
|
||||
// Create list of all Workflow types. Include an initial empty value.
|
||||
// Validate each workflow, and generate a message if not complete.
|
||||
$workflows = workflow_get_workflow_names(FALSE);
|
||||
|
||||
// @todo D8: add this to WorkflowFieldConstraintValidator.
|
||||
// Set message, if no 'validated' workflows exist.
|
||||
if (count($workflows) == 1) {
|
||||
drupal_set_message(
|
||||
$this->t('You must <a href=":create">create at least one workflow</a>
|
||||
before content can be assigned to a workflow.',
|
||||
[':create' => Url::fromRoute('entity.workflow_type.collection')->toString(),]
|
||||
), 'warning'
|
||||
);
|
||||
}
|
||||
|
||||
// Validate via annotation WorkflowFieldConstraint. Show a message for each error.
|
||||
$violation_list = $this->validate();
|
||||
/** @var \Symfony\Component\Validator\ConstraintViolation $violation */
|
||||
foreach ($violation_list->getIterator() as $violation) {
|
||||
switch ($violation->getPropertyPath()) {
|
||||
case 'fieldnameOnComment':
|
||||
// @todo D8: CommentForm & constraints on storageSettingsForm()
|
||||
// A 'comment' field name MUST be equal to content field name.
|
||||
// @todo: Still not waterproof. You could have a field on a non-relevant entity_type.
|
||||
drupal_set_message($violation->getMessage(), 'error');
|
||||
$workflows = [];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @todo D8: CommentForm & constraints on storageSettingsForm
|
||||
// Set the required workflow_type on 'comment' fields.
|
||||
// N.B. the following must BELOW the (count($workflows) == 1) snippet.
|
||||
/** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage */
|
||||
$field_storage = $this->getFieldDefinition()->getFieldStorageDefinition();
|
||||
if (!$this->getSetting('workflow_type') && $field_storage->getTargetEntityTypeId() == 'comment') {
|
||||
$field_name = $field_storage->get('field_name');
|
||||
$workflows = [];
|
||||
foreach (_workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name) as $key => $info) {
|
||||
if ($info->getName() == $field_name && ($info->getTargetEntityTypeId() !== 'comment')) {
|
||||
$wid = $info->getSetting('workflow_type');
|
||||
$workflow = Workflow::load($wid);
|
||||
$workflows[$wid] = $workflow->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let the user choose between the available workflow types.
|
||||
$wid = $this->getSetting('workflow_type');
|
||||
$url = Url::fromRoute('entity.workflow_type.collection')->toString();
|
||||
$element['workflow_type'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Workflow type'),
|
||||
'#options' => $workflows,
|
||||
'#default_value' => $wid,
|
||||
'#required' => TRUE,
|
||||
'#disabled' => $has_data,
|
||||
'#description' => $this->t('Choose the Workflow type. Maintain workflows
|
||||
<a href=":url">here</a>.', [':url' => $url]),
|
||||
];
|
||||
|
||||
// Get a string representation to show all options.
|
||||
|
||||
/*
|
||||
* Overwrite ListItemBase::storageSettingsForm().
|
||||
*/
|
||||
if ($wid) {
|
||||
$allowed_values = WorkflowState::loadMultiple([], $wid);
|
||||
// $allowed_values_function = $this->getSetting('allowed_values_function');
|
||||
|
||||
$element['allowed_values'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Allowed values for the selected Workflow type'),
|
||||
'#default_value' => ($wid) ? $this->allowedValuesString($allowed_values) : [],
|
||||
'#rows' => count($allowed_values),
|
||||
'#access' => ($wid) ? TRUE : FALSE, // User can see the data,
|
||||
'#disabled' => TRUE, // .. but cannot change them.
|
||||
'#element_validate' => [[get_class($this), 'validateAllowedValues']],
|
||||
|
||||
'#field_has_data' => $has_data,
|
||||
'#field_name' => $this->getFieldDefinition()->getName(),
|
||||
'#entity_type' => ($this->getEntity()) ? $this->getEntity()->getEntityTypeId() : '',
|
||||
'#allowed_values' => $allowed_values,
|
||||
'#description' => $this->allowedValuesDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function allowedValuesDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of an array of 'allowed values'.
|
||||
*
|
||||
* This string format is suitable for edition in a textarea.
|
||||
*
|
||||
* @param WorkflowState[] $states
|
||||
* An array of WorkflowStates, where array keys are values and array values are
|
||||
* labels.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the $states array:
|
||||
* - Values are separated by a carriage return.
|
||||
* - Each value is in the format "value|label" or "value".
|
||||
*/
|
||||
protected function allowedValuesString($states) {
|
||||
$lines = [];
|
||||
|
||||
$wid = $this->getSetting('workflow_type');
|
||||
|
||||
$previous_wid = -1;
|
||||
/** @var \Drupal\workflow\Entity\WorkflowState $state */
|
||||
foreach ($states as $key => $state) {
|
||||
// Only show enabled states.
|
||||
if ($state->isActive()) {
|
||||
// Show a Workflow name between Workflows, if more then 1 in the list.
|
||||
if ((!$wid) && ($previous_wid <> $state->getWorkflowId())) {
|
||||
$previous_wid = $state->getWorkflowId();
|
||||
$lines[] = $state->getWorkflow()->label() . "'s states: ";
|
||||
}
|
||||
$label = $this->t('@label', ['@label' => $state->label()]);
|
||||
|
||||
$lines[] = " $key|$label";
|
||||
}
|
||||
}
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * {@inheritdoc}
|
||||
// */
|
||||
// public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
// // @todo Implement this once https://www.drupal.org/node/2238085 lands.
|
||||
// $values['value'] = rand(pow(10, 8), pow(10, 9)-1);
|
||||
// return $values;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of TypedDataInterface
|
||||
*
|
||||
* @see folder \workflow\src\Plugin\Validation\Constraint
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see folder \workflow\src\Plugin\Validation\Constraint
|
||||
*/
|
||||
// public function getConstraints() {
|
||||
// $constraints = parent::getConstraints();
|
||||
// return $constraints;
|
||||
// }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see folder \workflow\src\Plugin\Validation\Constraint
|
||||
*/
|
||||
// public function validate() {
|
||||
// $result = parent::validate();
|
||||
// return $result;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Implementation of OptionsProviderInterface
|
||||
*
|
||||
* An array of settable options for the object that may be used in an
|
||||
* Options widget, usually when new data should be entered. It may either be
|
||||
* a flat array of option labels keyed by values, or a two-dimensional array
|
||||
* of option groups (array of flat option arrays, keyed by option group
|
||||
* label). Note that labels should NOT be sanitized.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPossibleValues(AccountInterface $account = NULL) {
|
||||
// Flatten options first, because SettableOptions may have 'group' arrays.
|
||||
$flatten_options = OptGroup::flattenOptions($this->getPossibleOptions($account));
|
||||
return array_keys($flatten_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPossibleOptions(AccountInterface $account = NULL) {
|
||||
$allowed_options = [];
|
||||
|
||||
// When we are initially on the Storage settings form, no wid is set, yet.
|
||||
if (!$wid = $this->getSetting('workflow_type')) {
|
||||
return $allowed_options;
|
||||
}
|
||||
|
||||
// Create an empty State. This triggers to show all possible states for the Workflow.
|
||||
if ($workflow = Workflow::load($wid)) {
|
||||
// There is no entity, E.g., on the Rules action "Set a data value".
|
||||
$user = workflow_current_user($account); // @todo #2287057: OK?
|
||||
/** @var \Drupal\workflow\Entity\WorkflowState $state */
|
||||
$state = WorkflowState::create(['wid' => $wid, 'id' => '']);
|
||||
$allowed_options = $state->getOptions(NULL, '', $user, FALSE);
|
||||
}
|
||||
|
||||
return $allowed_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSettableValues(AccountInterface $account = NULL) {
|
||||
// Flatten options first, because SettableOptions may have 'group' arrays.
|
||||
$flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
|
||||
return array_keys($flatten_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSettableOptions(AccountInterface $account = NULL) {
|
||||
$allowed_options = [];
|
||||
|
||||
// When we are initially on the Storage settings form, no wid is set, yet.
|
||||
if (!$wid = $this->getSetting('workflow_type')) {
|
||||
return $allowed_options;
|
||||
}
|
||||
|
||||
// Use the 'allowed_values_function' to calculate the options.
|
||||
$definition = $this->getFieldDefinition()->getFieldStorageDefinition();
|
||||
$entity = $this->getEntity();
|
||||
$field_name = $definition->getName();
|
||||
$user = workflow_current_user($account); // @todo #2287057: OK?
|
||||
|
||||
// Get the allowed new states for the entity's current state.
|
||||
// @todo D8-port: overwrite getValue().
|
||||
// $sid = $value = $this->getValue();
|
||||
/** @var \Drupal\workflow\Entity\WorkflowState $state */
|
||||
$sid = workflow_node_current_state($entity, $field_name);
|
||||
$state = WorkflowState::load($sid);
|
||||
$allowed_options = ($state) ? $state->getOptions($entity, $field_name, $user, FALSE) : [];
|
||||
|
||||
return $allowed_options;
|
||||
}
|
||||
|
||||
}
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\workflow\Element\WorkflowTransitionElement;
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Drupal\workflow\Entity\WorkflowTransition;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'workflow_default' widget.
|
||||
*
|
||||
* @FieldWidget(
|
||||
* id = "workflow_default",
|
||||
* label = @Translation("Workflow transition form"),
|
||||
* field_types = {
|
||||
* "workflow"
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class WorkflowDefaultWidget extends WidgetBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
/*
|
||||
'workflow_default' => array(
|
||||
'label' => t('Workflow'),
|
||||
'field types' => array('workflow'),
|
||||
'settings' => array(
|
||||
'fieldset' => 0,
|
||||
'name_as_title' => 1,
|
||||
'comment' => 1,
|
||||
),
|
||||
),
|
||||
*/
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Be careful: Widget may be shown in very different places. Test carefully!!
|
||||
* - On a entity add/edit page
|
||||
* - On a entity preview page
|
||||
* - On a entity view page
|
||||
* - On a entity 'workflow history' tab
|
||||
* - On a comment display, in the comment history
|
||||
* - On a comment form, below the comment history
|
||||
*
|
||||
* @todo D8: change "array $items" to "FieldInterface $items"
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$wid = $this->getFieldSetting('workflow_type');
|
||||
/** @var $workflow Workflow */
|
||||
if (!$workflow = Workflow::load($wid)) {
|
||||
// @todo: add error message.
|
||||
return $element;
|
||||
}
|
||||
|
||||
if ($this->isDefaultValueWidget($form_state)) {
|
||||
// On the Field settings page, User may not set a default value
|
||||
// (this is done by the Workflow module).
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var $item \Drupal\workflow\Plugin\Field\FieldType\WorkflowItem */
|
||||
$item = $items[$delta];
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = $item->getFieldDefinition();
|
||||
/** @var $field_storage \Drupal\field\Entity\FieldStorageConfig */
|
||||
$field_storage = $field_config->getFieldStorageDefinition();
|
||||
|
||||
$entity = $item->getEntity();
|
||||
$field_name = $field_storage->get('field_name');
|
||||
|
||||
// Create a transition, to pass to the form. No need to use setValues().
|
||||
$from_sid = workflow_node_current_state($entity, $field_name);
|
||||
/** @var $transition WorkflowTransition */
|
||||
$transition = WorkflowTransition::create([$from_sid, 'field_name' => $field_name]);
|
||||
$transition->setTargetEntity($entity);
|
||||
|
||||
// Here, on entity form, not the $element is added, but the entity form.
|
||||
// Problem 1: adding the element, does not add added fields.
|
||||
// Problem 2: adding the form, generates wrong UI.
|
||||
// Problem 3: does not work on ScheduledTransition.
|
||||
|
||||
// Step 1: use the Element.
|
||||
$element['#default_value'] = $transition;
|
||||
$element += WorkflowTransitionElement::transitionElement($element, $form_state, $form);
|
||||
// Step 2: use the Form, in order to get extra fields.
|
||||
$workflow_form = WorkflowManager::getWorkflowTransitionForm($entity, $field_name);
|
||||
// Determine and add the attached fields.
|
||||
$attached_fields = WorkflowManager::getAttachedFields('workflow_transition', $wid);
|
||||
foreach ($attached_fields as $key => $attached_field) {
|
||||
$element[$key] = $workflow_form[$key];
|
||||
}
|
||||
|
||||
// Option 3: use the true Element.
|
||||
// $form = $this->element($form, $form_state, $transition);
|
||||
//$element['workflow_transition'] = array(
|
||||
// '#type' => 'workflow_transition',
|
||||
// '#title' => t('Workflow transition'),
|
||||
// '#default_value' => $transition,
|
||||
// );
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Implements workflow_transition() -> WorkflowDefaultWidget::submit().
|
||||
*
|
||||
* Overrides submit(array $form, array &$form_state).
|
||||
* Contains 2 extra parameters for D7
|
||||
*
|
||||
* @param array $form
|
||||
* @param array $form_state
|
||||
* @param array $items
|
||||
* The value of the field.
|
||||
* @param bool $force
|
||||
* TRUE if all access must be overridden, e.g., for Rules.
|
||||
*
|
||||
* @return int
|
||||
* If update succeeded, the new State Id. Else, the old Id is returned.
|
||||
*
|
||||
* This is called from function _workflow_form_submit($form, &$form_state)
|
||||
* It is a replacement of function workflow_transition($entity, $to_sid, $force, $field)
|
||||
* It performs the following actions;
|
||||
* - save a scheduled action
|
||||
* - update history
|
||||
* - restore the normal $items for the field.
|
||||
* @todo: remove update of {node_form} table. (separate task, because it has features, too)
|
||||
*/
|
||||
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
|
||||
|
||||
$user = workflow_current_user(); // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed.
|
||||
|
||||
// Set the new value.
|
||||
// Beware: We presume cardinality = 1 !!
|
||||
// The widget form element type has transformed the value to a
|
||||
// WorkflowTransition object at this point. We need to convert it
|
||||
// back to the regular 'value' string format.
|
||||
foreach ($values as &$item) {
|
||||
if (!empty($item)) { // } && $item['value'] instanceof DrupalDateTime) {
|
||||
|
||||
// The following can NOT be retrieved from the WorkflowTransition.
|
||||
/** @var $entity \Drupal\Core\Entity\EntityInterface */
|
||||
$entity = $form_state->getFormObject()->getEntity();
|
||||
/** @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
$transition = $item['workflow_transition'];
|
||||
// N.B. Use a proprietary version of copyFormValuesToEntity,
|
||||
// where $entity/$transition is passed by reference.
|
||||
/** @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
$transition = WorkflowTransitionElement::copyFormValuesToTransition($transition, $form, $form_state, $item);
|
||||
|
||||
// Try to execute the transition. Return $from_sid when error.
|
||||
if (!$transition) {
|
||||
// This should not be possible (perhaps when testing/developing).
|
||||
drupal_set_message(t('Error: the transition from %from_sid to %to_sid could not be generated.'), 'error');
|
||||
// The current value is still the previous state.
|
||||
$to_sid = $from_sid = 0;
|
||||
}
|
||||
else {
|
||||
// The transition may be scheduled or not. Save the result, and
|
||||
// rely upon hook workflow_entity_insert/update($entity) in
|
||||
// file workflow.module to save/execute the transition.
|
||||
|
||||
// - validate option; add hook to let other modules change comment.
|
||||
// - add to history; add to watchdog
|
||||
// Return the new State ID. (Execution may fail and return the old Sid.)
|
||||
|
||||
// Get the new value from an action button if set in the workflow settings.
|
||||
$action_info = _workflow_transition_form_get_triggering_button($form_state);
|
||||
$field_name = $transition->getFieldName();
|
||||
if ($field_name == $action_info['field_name']) {
|
||||
$transition->set('to_sid', $action_info['to_sid']);
|
||||
}
|
||||
|
||||
$force = FALSE; // @TODO D8-port: add to form for usage in VBO.
|
||||
|
||||
// Now, save/execute the transition.
|
||||
$from_sid = $transition->getFromSid();
|
||||
$force = $force || $transition->isForced();
|
||||
|
||||
if (!$transition->isAllowed($user, $force)) {
|
||||
// Transition is not allowed.
|
||||
$to_sid = $from_sid;
|
||||
}
|
||||
elseif (!$entity || !$entity->id()) {
|
||||
// Entity is inserted. The Id is not yet known.
|
||||
// So we can't yet save the transition right now, but must rely on
|
||||
// function/hook workflow_entity_insert($entity) in file workflow.module.
|
||||
// $to_sid = $transition->execute($force);
|
||||
$to_sid = $transition->getToSid();
|
||||
}
|
||||
else {
|
||||
// Entity is updated. To stay in sync with insert, we rely on
|
||||
// function/hook workflow_entity_update($entity) in file workflow.module.
|
||||
// $to_sid = $transition->execute($force);
|
||||
$to_sid = $transition->getToSid();
|
||||
}
|
||||
}
|
||||
|
||||
// Now the data is captured in the Transition, and before calling the
|
||||
// Execution, restore the default values for Workflow Field.
|
||||
// For instance, workflow_rules evaluates this.
|
||||
//
|
||||
// N.B. Align the following functions:
|
||||
// - WorkflowDefaultWidget::massageFormValues();
|
||||
// - WorkflowManager::executeTransition().
|
||||
// Set the transition back, to be used in hook_entity_update().
|
||||
$item['workflow_transition'] = $transition;
|
||||
// Set the value at the proper location.
|
||||
if ($transition && $transition->isScheduled()) {
|
||||
$item['value'] = $from_sid;
|
||||
}
|
||||
else {
|
||||
$item['value'] = $to_sid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
|
||||
|
||||
/**
|
||||
* Supports validating Workflow Field names.
|
||||
*
|
||||
* @see https://drupalwatchdog.com/volume-5/issue-2/introducing-drupal-8s-entity-validation-api
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "WorkflowField",
|
||||
* label = @Translation("Workflow field name", context = "Validation"),
|
||||
* )
|
||||
*/
|
||||
class WorkflowFieldConstraint extends CompositeConstraintBase {
|
||||
|
||||
/**
|
||||
* Message shown when a comment fieldname doesn't match an entity field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
// @todo D8: CommentForm & constraints on Field
|
||||
public $messageFieldname = 'A workflow field on a comment must have
|
||||
the same field_name as the commented Entity. Please maintain the entity
|
||||
first, or choose another field name.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function coversFields() {
|
||||
return ['field_name', ];
|
||||
}
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\user\UserStorageInterface;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Validates the CommentName constraint.
|
||||
*
|
||||
* @see https://drupalwatchdog.com/volume-5/issue-2/introducing-drupal-8s-entity-validation-api
|
||||
*/
|
||||
class WorkflowFieldConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* Validator 2.5 and upwards compatible execution context.
|
||||
*
|
||||
* @var \Symfony\Component\Validator\Context\ExecutionContextInterface
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* User storage handler.
|
||||
*
|
||||
* @var \Drupal\user\UserStorageInterface
|
||||
*/
|
||||
protected $userStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new Validator.
|
||||
*
|
||||
* @param \Drupal\user\UserStorageInterface $user_storage
|
||||
* The user storage handler.
|
||||
*/
|
||||
public function __construct(UserStorageInterface $user_storage) {
|
||||
$this->userStorage = $user_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity.manager')->getStorage('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($entity, Constraint $constraint) {
|
||||
// Workflow field name on CommentForm has special requirements.
|
||||
$field_storage = $entity->getFieldDefinition()->getFieldStorageDefinition();
|
||||
if ($field_storage->getTargetEntityTypeId() == 'comment') {
|
||||
if (!$this->isValidFieldname($field_storage)) {
|
||||
$this->context->buildViolation($constraint->messageFieldname)
|
||||
->atPath('fieldnameOnComment')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FieldStorageConfig $field_storage
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidFieldname(FieldStorageConfig $field_storage) {
|
||||
$comment_field_name_ok = FALSE;
|
||||
|
||||
if ($field_storage->getTargetEntityTypeId() !== 'comment') {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$field_name = $field_storage->get('field_name');
|
||||
|
||||
// Check if the 'comment' field name exists on the 'commented' entity type.
|
||||
// @todo: Still not waterproof. You could have a field on a non-relevant entity_type.
|
||||
foreach (_workflow_info_fields() as $key => $info) {
|
||||
if (($info->getName() == $field_name) && ($info->getTargetEntityTypeId() !== 'comment')) {
|
||||
$comment_field_name_ok = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return $comment_field_name_ok;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow\Plugin\views\filter;
|
||||
|
||||
use Drupal\views\FieldAPIHandlerTrait;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\filter\ManyToOne;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Filter handler which uses workflow_state as options.
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*
|
||||
* @ViewsFilter("workflow_state")
|
||||
*/
|
||||
class WorkflowState extends ManyToOne {
|
||||
|
||||
use FieldAPIHandlerTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
$wid = isset($this->definition['wid']) ? $this->definition['wid'] : '';
|
||||
$grouped = isset($options['group_info']['widget']) ? $options['group_info']['widget'] == 'select' : false;
|
||||
$this->valueOptions = workflow_get_workflow_state_names($wid, $grouped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Child classes should be used to override this function and set the
|
||||
* 'value options', unless 'options callback' is defined as a valid function
|
||||
* or static public method to generate these values.
|
||||
*
|
||||
* This can use a guard to be used to reduce database hits as much as
|
||||
* possible.
|
||||
*
|
||||
* @return array|null
|
||||
* The stored values from $this->valueOptions.
|
||||
*/
|
||||
public function getValueOptions() {
|
||||
|
||||
if (isset($this->valueOptions)) {
|
||||
return $this->valueOptions;
|
||||
}
|
||||
|
||||
//@todo: implement the below code, and remove the line from init.
|
||||
// @todo: follow Options patterns
|
||||
// @see callback_allowed_values_function()
|
||||
// @see options_allowed_values()
|
||||
|
||||
return parent::getValueOptions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the workflow entity type.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\Workflow
|
||||
* @ingroup workflow_access
|
||||
*/
|
||||
class WorkflowAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access(EntityInterface $entity, $operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = parent::access($entity, $operation, $account, TRUE);
|
||||
|
||||
$account = $this->prepareUser($account);
|
||||
|
||||
// This is only for Edit/Delete transition. For Add/create, use createAccess.
|
||||
switch ($entity->getEntityTypeId()) {
|
||||
case 'workflow_scheduled_transition':
|
||||
switch ($operation) {
|
||||
case 'update':
|
||||
// This operation is not defined for Scheduled Transitions.
|
||||
$result = AccessResult::forbidden();
|
||||
break;
|
||||
case 'delete':
|
||||
// This operation is not defined for Scheduled Transitions.
|
||||
$result = AccessResult::forbidden();
|
||||
break;
|
||||
case 'revert':
|
||||
// This operation is not defined for Scheduled Transitions.
|
||||
$result = AccessResult::forbidden();
|
||||
break;
|
||||
default:
|
||||
$result = parent::access($entity, $operation, $account, $return_as_object);
|
||||
break;
|
||||
} // End of switch ($operation).
|
||||
|
||||
break;
|
||||
|
||||
case 'workflow_transition':
|
||||
/* @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
$transition = $entity;
|
||||
switch ($operation) {
|
||||
case 'update':
|
||||
$is_owner = WorkflowManager::isOwner($account, $transition);
|
||||
$type_id = $transition->getWorkflowId();
|
||||
if ($account->hasPermission("bypass $type_id workflow_transition access")) {
|
||||
$result = AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
elseif ($account->hasPermission("edit any $type_id workflow_transition")) {
|
||||
$result = AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
elseif ($is_owner && $account->hasPermission("edit own $type_id workflow_transition")) {
|
||||
$result = AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
case 'delete':
|
||||
// The delete operation is not defined for Transitions.
|
||||
$result = AccessResult::forbidden();
|
||||
break;
|
||||
case 'revert':
|
||||
// @see workflow_operations.
|
||||
default:
|
||||
$result = parent::access($entity, $operation, $account, $return_as_object);
|
||||
//if ($account->hasPermission("bypass $type_id workflow_transition access")) {
|
||||
// $result = AccessResult::allowed()->cachePerPermissions();
|
||||
//}
|
||||
break;
|
||||
} // End of switch ($operation).
|
||||
|
||||
break;
|
||||
|
||||
case 'workflow_config_transition':
|
||||
// This is not (yet) configured.
|
||||
break;
|
||||
|
||||
case 'workflow_state':
|
||||
switch ($operation) {
|
||||
case 'view label':
|
||||
// The following two lines are copied from below, and need to be reviewed carefully.
|
||||
$result = AccessResult::allowed();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
default:
|
||||
// E.g., operation 'update' on the WorkflowStates config page.
|
||||
break;
|
||||
} // End of switch ($operation).
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/** @var $result AccessResult $result */
|
||||
// $result = parent::createAccess($entity_bundle, $account, $context, TRUE);
|
||||
$result = $result->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createAccess($entity_bundle = NULL, AccountInterface $account = NULL, array $context = [], $return_as_object = FALSE) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
/** @var $result AccessResult $result */
|
||||
$result = parent::createAccess($entity_bundle, $account, $context, TRUE);
|
||||
$result = $result->cachePerPermissions();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $transition, $operation, AccountInterface $account) {
|
||||
return parent::checkAccess($transition, $operation, $account);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet.
|
||||
return AccessResult::allowedIf($account->hasPermission('create ' . $entity_bundle . ' content'))->cachePerPermissions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
|
||||
/**
|
||||
* Provides dynamic permissions for workflows of different types.
|
||||
*/
|
||||
class WorkflowPermissions {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Returns an array of workflow type permissions.
|
||||
*
|
||||
* @return array
|
||||
* The workflow type permissions.
|
||||
* @see \Drupal\user\PermissionHandlerInterface::getPermissions()
|
||||
*/
|
||||
public function workflowTypePermissions() {
|
||||
$perms = [];
|
||||
// Generate workflow permissions for all workflow types.
|
||||
foreach (Workflow::loadMultiple() as $type) {
|
||||
$perms += $this->buildPermissions($type);
|
||||
}
|
||||
return $perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of workflow permissions for a given workflow type.
|
||||
*
|
||||
* @param \Drupal\workflow\Entity\Workflow $type
|
||||
* The workflow type.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of permission names and descriptions.
|
||||
*/
|
||||
protected function buildPermissions(Workflow $type) {
|
||||
$type_id = $type->id();
|
||||
$type_params = ['%type_name' => $type->label()];
|
||||
|
||||
return [
|
||||
// D7->D8-Conversion of the 'User 1 is special' permission (@see NodePermissions::bypass node access).
|
||||
"bypass $type_id workflow_transition access" => [
|
||||
'title' => $this->t('%type_name: Bypass transition access control', $type_params),
|
||||
'description' => $this->t('View, edit and delete all transitions regardless of permission restrictions.'),
|
||||
'restrict access' => TRUE,
|
||||
],
|
||||
// D7->D8-Conversion of 'participate in workflow' permission to "create $type_id transition" (@see NodePermissions::create content).
|
||||
"create $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Participate in workflow', $type_params),
|
||||
'description' => $this->t("Role is enabled to create state transitions. (Determines transition-specific permission on the workflow admin page.
|
||||
<i> Warning: For better control, uncheck the 'Authenticated user', and manage the permissions per separate role.</i>)"),
|
||||
],
|
||||
// D7->D8-Conversion of 'schedule workflow transitions' permission to "schedule $type_id transition" (@see NodePermissions::create content).
|
||||
"schedule $type_id workflow_transition" => [
|
||||
'title' => $this->t('%type_name: Schedule state transition', $type_params),
|
||||
'description' => $this->t('Role is enabled to schedule state transitions.'),
|
||||
],
|
||||
// D7->D8-Conversion of 'workflow history' permission on Workflow settings to "access $type_id overview" (@see NodePermissions::access content overview).
|
||||
"access own $type_id workflow_transion overview" => [
|
||||
'title' => $this->t('%type_name: Access Workflow history tab of own content', $type_params),
|
||||
'description' => $this->t('Role is enabled to view the "Workflow state transition history" tab on own entity.'),
|
||||
],
|
||||
"access any $type_id workflow_transion overview" => [
|
||||
'title' => $this->t('%type_name: Access Workflow history tab of any content', $type_params),
|
||||
'description' => $this->t('Role is enabled to view the "Workflow state transition history" tab on any entity.'),
|
||||
],
|
||||
// D7->D8-Conversion of 'show workflow transition form' permission. @see #1893724
|
||||
"access $type_id workflow_transition form" => [
|
||||
'title' => $this->t('%type_name: Access the Workflow state transition form on entity view page', $type_params),
|
||||
'description' => $this->t('Role is enabled to view a "Workflow state transition" block/widget and add a state transition on the entity page.'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the workflow entity type.
|
||||
*/
|
||||
class WorkflowScheduledTransitionViewsData extends WorkflowTransitionViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
// @todo D8-port: Add some data from D7 function workflow_views_views_data_alter() ??
|
||||
// @see http://cgit.drupalcode.org/workflow/tree/workflow_views/workflow_views.views.inc
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\EntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\workflow\Entity\WorkflowTransition;
|
||||
|
||||
/**
|
||||
* Defines a class to build a draggable listing of Workflow State entities.
|
||||
*
|
||||
* @deprecated use View 'Workflow Entity history' in WorkflowTransitionListController.
|
||||
* @see \Drupal\workflow\Entity\WorkflowState
|
||||
*/
|
||||
class WorkflowTransitionListBuilder extends EntityListBuilder {
|
||||
|
||||
const WORKFLOW_MARK_STATE_IS_DELETED = '*';
|
||||
|
||||
/**
|
||||
* A variable to pass the entity of a transition to the ListBuilder.
|
||||
* @todo: There should be a better way.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
public $workflow_entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $limit = 50;
|
||||
|
||||
/**
|
||||
* Indicates if a column 'Field name' must be generated.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $show_column_fieldname = NULL;
|
||||
|
||||
/**
|
||||
* Indicates if a footer must be generated.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $footer_needed = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load() {
|
||||
/** @var $entity \Drupal\Core\Entity\EntityInterface */
|
||||
$entity = $this->workflow_entity; // N.B. This is a custom variable.
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
$entity_id = $entity->id();
|
||||
$field_name = workflow_url_get_field_name();
|
||||
|
||||
// @todo D8-port: document $limit. Should be used in pager, not in load().
|
||||
$this->limit = \Drupal::config('workflow.settings')->get('workflow_states_per_page');
|
||||
$limit = $this->limit;
|
||||
// Get Transitions with highest timestamp first.
|
||||
$entities = WorkflowTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name, '', $limit, 'DESC');
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Building the header and content lines for the contact list.
|
||||
*
|
||||
* Calling the parent::buildHeader() adds a column for the possible actions
|
||||
* and inserts the 'edit' and 'delete' links as defined for the entity type.
|
||||
*/
|
||||
public function buildHeader() {
|
||||
|
||||
$entity = $this->workflow_entity; // N.B. This is a custom variable.
|
||||
|
||||
$header['timestamp'] = $this->t('Date');
|
||||
if ($this->showColumnFieldname($entity)) {
|
||||
$header['field_name'] = $this->t('Field name');
|
||||
}
|
||||
$header['from_state'] = $this->t('From State');
|
||||
$header['to_state'] = $this->t('To State');
|
||||
$header['user_name'] = $this->t('By');
|
||||
$header['comment'] = $this->t('Comment');
|
||||
|
||||
// column 'Operations' is now added by core.
|
||||
//$header['operations'] = $this->t('Operations');
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $transition) {
|
||||
// Show the history table.
|
||||
$current_themed = FALSE;
|
||||
/** @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
$entity = $transition->getTargetEntity();
|
||||
$field_name = $transition->getFieldName();
|
||||
$current_sid = workflow_node_current_state($entity, $field_name);
|
||||
|
||||
$to_state = $transition->getToState();
|
||||
if (!$to_state) {
|
||||
// This is an invalid/deleted state.
|
||||
$to_label = self::WORKFLOW_MARK_STATE_IS_DELETED;
|
||||
// Add a footer to explain the addition.
|
||||
$this->footer_needed = TRUE;
|
||||
}
|
||||
else {
|
||||
$label = Html::escape($this->t($to_state->label()));
|
||||
if ($transition->getToSid() == $current_sid && $to_state->isActive() && !$current_themed) {
|
||||
$to_label = $label;
|
||||
|
||||
// Make a note that we have themed the current state; other times in the history
|
||||
// of this entity where the entity was in this state do not need to be specially themed.
|
||||
$current_themed = TRUE;
|
||||
}
|
||||
elseif (!$to_state->isActive()) {
|
||||
$to_label = $label . self::WORKFLOW_MARK_STATE_IS_DELETED;
|
||||
// Add a footer to explain the addition.
|
||||
$this->footer_needed = TRUE;
|
||||
}
|
||||
else {
|
||||
// Regular state.
|
||||
$to_label = $label;
|
||||
}
|
||||
}
|
||||
unset($to_state); // Not needed anymore.
|
||||
|
||||
$from_state = $transition->getFromState();
|
||||
if (!$from_state) {
|
||||
// This is an invalid/deleted state.
|
||||
$from_label = self::WORKFLOW_MARK_STATE_IS_DELETED;
|
||||
// Add a footer to explain the addition.
|
||||
$this->footer_needed = TRUE;
|
||||
}
|
||||
else {
|
||||
$label = Html::escape($this->t($from_state->label()));
|
||||
if (!$from_state->isActive()) {
|
||||
$from_label = $label . self::WORKFLOW_MARK_STATE_IS_DELETED;
|
||||
// Add a footer to explain the addition.
|
||||
$this->footer_needed = TRUE;
|
||||
}
|
||||
else {
|
||||
// Regular state.
|
||||
$from_label = $label;
|
||||
}
|
||||
}
|
||||
unset($from_state); // Not needed anymore.
|
||||
|
||||
$owner = $transition->getOwner();
|
||||
$field_label = $transition->getFieldName();
|
||||
$variables = [
|
||||
'transition' => $transition,
|
||||
'extra' => '',
|
||||
'from_label' => $from_label,
|
||||
'to_label' => $to_label,
|
||||
'user' => $owner,
|
||||
];
|
||||
// Allow other modules to modify the row.
|
||||
\Drupal::moduleHandler()->alter('workflow_history', $variables);
|
||||
|
||||
// 'class' => array('workflow_history_row'), // TODO D8-port
|
||||
$row['timestamp']['data'] = $transition->getTimestampFormatted(); // 'class' => array('timestamp')
|
||||
// html_entity_decode() transforms chars like '&' correctly.
|
||||
if ($this->showColumnFieldname($entity)) {
|
||||
$row['field_name']['data'] = html_entity_decode($field_label);
|
||||
}
|
||||
$row['from_state']['data'] = html_entity_decode($from_label); // 'class' => array('previous-state-name'))
|
||||
$row['to_state']['data'] = html_entity_decode($to_label); // 'class' => array('state-name'))
|
||||
$row['user_name']['data'] = $owner->toLink($owner->getDisplayName())->toString(); // 'class' => array('user-name')
|
||||
$row['comment']['data'] = html_entity_decode($transition->getComment()); // 'class' => array('log-comment')
|
||||
|
||||
// Column 'Operations' is now added by core.
|
||||
// D7: $row['operations']['data'] = $this->buildOperations($entity);
|
||||
$row += parent::buildRow($transition);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Builds the entity listing as renderable array for table.html.twig.
|
||||
*/
|
||||
public function render() {
|
||||
$build = [];
|
||||
|
||||
// @todo d8-port: get pager working.
|
||||
$this->limit = \Drupal::config('workflow.settings')->get('workflow_states_per_page'); // TODO D8-port
|
||||
// $output .= theme('pager', array('tags' => $limit)); // @todo D8-port
|
||||
|
||||
$build += parent::render();
|
||||
|
||||
// Add a footer. This is not yet added in EntityListBuilder::render()
|
||||
if ($this->footer_needed) { // @todo D8-port: test this.
|
||||
// Two variants. First variant is official, but I like 2nd better.
|
||||
/*
|
||||
$build['table']['#footer'] = [
|
||||
[
|
||||
'class' => ['footer-class'],
|
||||
'data' => [
|
||||
[
|
||||
'data' => WORKFLOW_MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.'),
|
||||
'colspan' => count($build['table']['#header']),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
*/
|
||||
$build['workflow_footer'] = [
|
||||
'#markup' => WORKFLOW_MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.'),
|
||||
'#weight' => 500, // @todo Make this better.
|
||||
];
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
|
||||
if (isset($operations['edit'])) {
|
||||
$destination = \Drupal::destination()->getAsArray();
|
||||
$operations['edit']['query'] = $destination;
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the page.
|
||||
*
|
||||
* @return string
|
||||
* A string title of the page.
|
||||
*/
|
||||
protected function getTitle() {
|
||||
return $this->t('Workflow history');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the column 'Field name' must be shown.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function showColumnFieldname(EntityInterface $entity) {
|
||||
if (is_null($this->show_column_fieldname)) {
|
||||
// @todo: also remove when field_name is set in route??
|
||||
if (count(_workflow_info_fields($entity)) > 1) {
|
||||
$this->show_column_fieldname = TRUE;
|
||||
}
|
||||
}
|
||||
return $this->show_column_fieldname;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\workflow;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the workflow entity type.
|
||||
* Partly taken from NodeViewsData.php.
|
||||
*/
|
||||
class WorkflowTransitionViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
// Use flexible $base_table, since both WorkflowTransition and
|
||||
// WorkflowScheduledTransition use this.
|
||||
$base_table = $this->entityType->getBaseTable();
|
||||
$base_field = $this->entityType->getKey('id');
|
||||
|
||||
// @todo D8-port: Add data from D7 function workflow_views_views_data_alter()
|
||||
// @see http://cgit.drupalcode.org/workflow/tree/workflow_views/workflow_views.views.inc
|
||||
|
||||
$data[$base_table]['table']['group'] = $this->t('Workflow');
|
||||
$data[$base_table]['table']['provider'] = 'workflow';
|
||||
|
||||
$data[$base_table]['table']['join'] = [
|
||||
// This is provided for the many_to_one argument.
|
||||
$base_table => [
|
||||
'field' => $base_field,
|
||||
'left_field' => $base_field,
|
||||
],
|
||||
];
|
||||
|
||||
// @todo: this relationship needs to be reversed. See also taxonomy/src/NodeTermData.php
|
||||
//$data[$base_table]['nid'] = [
|
||||
// 'title' => $this->t('Content with workflow'),
|
||||
// 'help' => $this->t('Relate all content with a workflow.'),
|
||||
// 'relationship' => [
|
||||
// 'id' => 'standard',
|
||||
// 'base' => 'node',
|
||||
// 'base field' => 'nid',
|
||||
// 'label' => $this->t('node'),
|
||||
// 'skip base' => 'node',
|
||||
// ],
|
||||
//];
|
||||
|
||||
$data[$base_table]['from_sid']['filter']['id'] = 'workflow_state';
|
||||
$data[$base_table]['from_sid']['help'] = $this->t('The name of the previous state of the transition.');
|
||||
|
||||
$data[$base_table]['to_sid']['filter']['id'] = 'workflow_state';
|
||||
$data[$base_table]['to_sid']['help'] = $this->t('The name of the new state of the transition. (For the latest transition, this is the current state.)');
|
||||
|
||||
$data[$base_table]['uid']['help'] = $this->t('The user who triggered the transition. If you need more fields than the uid add the content: author relationship');
|
||||
$data[$base_table]['uid']['filter']['id'] = 'user_name';
|
||||
$data[$base_table]['uid']['relationship']['title'] = $this->t('User');
|
||||
$data[$base_table]['uid']['relationship']['help'] = $this->t('The user who triggered the transition.');
|
||||
$data[$base_table]['uid']['relationship']['label'] = $this->t('User');
|
||||
|
||||
// @todo Add similar support to any date field
|
||||
// @see https://www.drupal.org/node/2337507
|
||||
$data[$base_table]['timestamp_fulldate'] = [
|
||||
'title' => $this->t('Created date'),
|
||||
'help' => $this->t('Date in the form of CCYYMMDD.'),
|
||||
'argument' => [
|
||||
'field' => 'timestamp',
|
||||
'id' => 'date_fulldate',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['timestamp_year_month'] = [
|
||||
'title' => $this->t('Created year + month'),
|
||||
'help' => $this->t('Date in the form of YYYYMM.'),
|
||||
'argument' => [
|
||||
'field' => 'timestamp',
|
||||
'id' => 'date_year_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['timestamp_year'] = [
|
||||
'title' => $this->t('Created year'),
|
||||
'help' => $this->t('Date in the form of YYYY.'),
|
||||
'argument' => [
|
||||
'field' => 'timestamp',
|
||||
'id' => 'date_year',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['timestamp_month'] = [
|
||||
'title' => $this->t('Created month'),
|
||||
'help' => $this->t('Date in the form of MM (01 - 12).'),
|
||||
'argument' => [
|
||||
'field' => 'timestamp',
|
||||
'id' => 'date_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['timestamp_day'] = [
|
||||
'title' => $this->t('Created day'),
|
||||
'help' => $this->t('Date in the form of DD (01 - 31).'),
|
||||
'argument' => [
|
||||
'field' => 'timestamp',
|
||||
'id' => 'date_day',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['timestamp_week'] = [
|
||||
'title' => $this->t('Created week'),
|
||||
'help' => $this->t('Date in the form of WW (01 - 53).'),
|
||||
'argument' => [
|
||||
'field' => 'timestamp',
|
||||
'id' => 'date_week',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['changed_fulldate'] = [
|
||||
'title' => $this->t('Updated date'),
|
||||
'help' => $this->t('Date in the form of CCYYMMDD.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_fulldate',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['changed_year_month'] = [
|
||||
'title' => $this->t('Updated year + month'),
|
||||
'help' => $this->t('Date in the form of YYYYMM.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_year_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['changed_year'] = [
|
||||
'title' => $this->t('Updated year'),
|
||||
'help' => $this->t('Date in the form of YYYY.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_year',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['changed_month'] = [
|
||||
'title' => $this->t('Updated month'),
|
||||
'help' => $this->t('Date in the form of MM (01 - 12).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['changed_day'] = [
|
||||
'title' => $this->t('Updated day'),
|
||||
'help' => $this->t('Date in the form of DD (01 - 31).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_day',
|
||||
],
|
||||
];
|
||||
|
||||
$data[$base_table]['changed_week'] = [
|
||||
'title' => $this->t('Updated week'),
|
||||
'help' => $this->t('Date in the form of WW (01 - 53).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_week',
|
||||
],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the workflow module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
use Drupal\workflow\Entity\WorkflowConfigTransition;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_workflow_operations().
|
||||
*
|
||||
* Adds extra operations to ListBuilders.
|
||||
* - workflow_ui: Workflow, State;
|
||||
* - workflow: WorkflowTransition;
|
||||
*
|
||||
* @param string $op
|
||||
* 'top_actions': Allow modules to insert their own front page action links.
|
||||
* 'operations': Allow modules to insert their own workflow operations.
|
||||
* 'workflow': Allow modules to insert workflow operations.
|
||||
* 'state': Allow modules to insert state operations.
|
||||
* @param EntityInterface|null $entity
|
||||
* The current workflow/state/transition object.
|
||||
*
|
||||
* @return array
|
||||
* The new actions, to be added to the entity list.
|
||||
*/
|
||||
function hook_workflow_operations($op, EntityInterface $entity = NULL) {
|
||||
$operations = [];
|
||||
|
||||
switch ($op) {
|
||||
case 'top_actions':
|
||||
// As of D8, below hook_workflow_operations is removed, in favour of core hooks.
|
||||
// @see file workflow_ui.links.action.yml for an example top action.
|
||||
return $operations;
|
||||
|
||||
case 'operations':
|
||||
break;
|
||||
|
||||
case 'workflow':
|
||||
// This example adds an operation to the 'operations column' of the Workflow List.
|
||||
/* @var $workflow Workflow */
|
||||
$workflow = $entity;
|
||||
|
||||
$alt = t('Control content access for @wf', ['@wf' => $workflow->label()]);
|
||||
$attributes = ['alt' => $alt, 'title' => $alt];
|
||||
$operations['workflow_access_form'] = [
|
||||
'title' => t('Access'),
|
||||
'weight' => 50,
|
||||
'url' => \Drupal\Core\Url::fromRoute('entity.workflow_type.access_form', ['workflow_type' => $workflow->id()]),
|
||||
'query' => \Drupal::destination()->getAsArray(), // Add destination.
|
||||
];
|
||||
return $operations;
|
||||
|
||||
case 'state':
|
||||
/* @var $state WorkflowState */
|
||||
$state = $entity;
|
||||
break;
|
||||
|
||||
case 'workflow_transition':
|
||||
// As of D8, below hook_workflow_operations is removed, in favour of core hooks.
|
||||
// @see EntityListBuilder::getOperations, workflow_operations, workflow.api.php.
|
||||
|
||||
// Your module may add operations to the Entity list.
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $entity;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_workflow().
|
||||
*
|
||||
* NOTE: This hook may reside in the implementing module
|
||||
* or in a module.workflow.inc file.
|
||||
*
|
||||
* @param string $op
|
||||
* The current workflow operation.
|
||||
* E.g., 'transition pre', 'transition post'.
|
||||
* @param WorkflowTransitionInterface $transition
|
||||
* The transition, that contains all of the above.
|
||||
* @param UserInterface $user
|
||||
*
|
||||
* @return bool|void
|
||||
*/
|
||||
function hook_workflow($op, WorkflowTransitionInterface $transition, UserInterface $user) {
|
||||
switch ($op) {
|
||||
case 'transition permitted':
|
||||
// As of version 8.x-1.x, this operation is never called to check if transition is permitted.
|
||||
// This was called in the following situations:
|
||||
// case 1. when building a workflow widget with list of available transitions;
|
||||
// case 2. when executing a transition, just before the 'transition pre';
|
||||
// case 3. when showing a 'revert state' link in a Views display.
|
||||
// Your module's implementation may return FALSE here and disallow
|
||||
// the execution, or avoid the presentation of the new State.
|
||||
// This may be user-dependent.
|
||||
// As of version 8.x-1.x:
|
||||
// case 1: use hook_workflow_permitted_state_transitions_alter();
|
||||
// case 2: use the 'transition pre' operation;
|
||||
// case 3: use the 'transition pre' operation;
|
||||
return TRUE;
|
||||
|
||||
case 'transition revert':
|
||||
// Hook is called when showing the Transition Revert form.
|
||||
// Implement this hook if you need to control this.
|
||||
// If you return FALSE here, you will veto the transition.
|
||||
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $op, '');
|
||||
break;
|
||||
|
||||
case 'transition pre':
|
||||
// The workflow module does nothing during this operation.
|
||||
// Implement this hook if you need to change/do something BEFORE anything
|
||||
// is saved to the database.
|
||||
// If you return FALSE here, you will veto the transition.
|
||||
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $op, '');
|
||||
break;
|
||||
|
||||
case 'transition post':
|
||||
// In D7, this is called by Workflow Node during update of the state, directly
|
||||
// after updating the Workflow. Workflow Field does not call this,
|
||||
// since you can call a hook_entity_* event after saving the entity.
|
||||
// @see https://api.drupal.org/api/drupal/includes%21module.inc/group/hooks/7
|
||||
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $op, '');
|
||||
break;
|
||||
|
||||
case 'transition delete':
|
||||
case 'state delete':
|
||||
case 'workflow delete':
|
||||
// These hooks are removed in D8, in favour of the core hooks:
|
||||
// - workflow_entity_predelete(EntityInterface $entity)
|
||||
// - workflow_entity_delete(EntityInterface $entity)
|
||||
// See examples at the bottom of this file.
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_workflow_history_alter().
|
||||
*
|
||||
* In D8, hook_workflow_history_alter() is removed, in favour
|
||||
* of ListBuilder::getDefaultOperations
|
||||
* and hook_workflow_operations('workflow_transition').
|
||||
*
|
||||
* Allow other modules to add Operations to the most recent history change.
|
||||
* E.g., Workflow Revert implements an 'undo' operation.
|
||||
*
|
||||
* @param array $variables
|
||||
* The current workflow history information as an array.
|
||||
* 'old_sid' - The state ID of the previous state.
|
||||
* 'old_state_name' - The state name of the previous state.
|
||||
* 'sid' - The state ID of the current state.
|
||||
* 'state_name' - The state name of the current state.
|
||||
* 'history' - The row from the workflow_transition_history table.
|
||||
* 'transition' - a WorkflowTransition object, containing all of the above.
|
||||
*/
|
||||
function hook_workflow_history_alter(array &$variables) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
|
||||
// The Workflow module does nothing with this hook.
|
||||
// For an example implementation, see the Workflow Revert add-on.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_workflow_comment_alter().
|
||||
*
|
||||
* Allow other modules to change the user comment when saving a state change.
|
||||
*
|
||||
* @param string $comment
|
||||
* The comment of the current state transition.
|
||||
* @param array $context
|
||||
* 'transition' - The current transition itself.
|
||||
*/
|
||||
function hook_workflow_comment_alter(&$comment, array &$context) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $context['transition'];
|
||||
//$comment = $transition->getOwner()->getUsername() . ' says: ' . $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_workflow_permitted_state_transitions_alter().
|
||||
*
|
||||
* @param array $transitions
|
||||
* An array of allowed transitions from the current state (as provided in
|
||||
* $context). They are already filtered by the settings in Admin UI.
|
||||
* @param array $context
|
||||
* An array of relevant objects. Currently:
|
||||
* $context = array(
|
||||
* 'user' => $user,
|
||||
* 'workflow' => $workflow,
|
||||
* 'state' => $current_state,
|
||||
* 'force' => $force,
|
||||
* );
|
||||
*
|
||||
* This hook allows you to add custom filtering of allowed target states, add
|
||||
* new custom states, change labels, etc.
|
||||
* It is invoked in WorkflowState::getOptions().
|
||||
*/
|
||||
function hook_workflow_permitted_state_transitions_alter(array &$transitions, array $context) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
|
||||
$user = $context['user']; // user may have the custom role AUTHOR.
|
||||
// The following could be fetched from each transition.
|
||||
$workflow = $context['workflow'];
|
||||
$current_state = $context['state'];
|
||||
// The following could be fetched from the $user and $transition objects.
|
||||
$force = $context['force'];
|
||||
|
||||
// Implement here own permission logic.
|
||||
foreach ($transitions as $key => $transition) {
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
if (!$transition->isAllowed($user, $force)) {
|
||||
//unset($transitions[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// This example creates a new custom target state.
|
||||
$values = [
|
||||
// Fixed values for new transition.
|
||||
'wid' => $context['workflow']->id(),
|
||||
'from_sid' => $context['state']->id(),
|
||||
|
||||
// Custom values for new transition.
|
||||
// The ID must be an integer, due to db-table constraints.
|
||||
'to_sid' => '998',
|
||||
'label' => 'go to my new fantasy state',
|
||||
];
|
||||
$new_transition = WorkflowConfigTransition::create($values);
|
||||
// $transitions[] = $new_transition;
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Hooks defined by core Form API: hooks to to alter the Workflow Form/Widget.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter forms for field widgets provided by other modules.
|
||||
*
|
||||
* @param $element
|
||||
* The field widget form element as constructed by hook_field_widget_form().
|
||||
* @param $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
* @param $context
|
||||
* An associative array containing the following key-value pairs, matching the
|
||||
* arguments received by hook_field_widget_form():
|
||||
* - form: The form structure to which widgets are being attached. This may be
|
||||
* a full form structure, or a sub-element of a larger form.
|
||||
* - field: The field structure.
|
||||
* - instance: The field instance structure.
|
||||
* - langcode: The language associated with $items.
|
||||
* - items: Array of default values for this field.
|
||||
* - delta: The order of this item in the array of subelements (0, 1, 2, etc).
|
||||
*
|
||||
* @see hook_field_widget_form()
|
||||
* @see hook_field_widget_WIDGET_TYPE_form_alter()
|
||||
*/
|
||||
function hook_field_widget_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
|
||||
// A hook for changing any widget. Better not use it: it is called on EVERY
|
||||
// Widget. (Even though the message is only shown once.)
|
||||
// D7: This hook is introduced in Drupal 7.8.
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
// dpm($context['widget']->getPluginId());
|
||||
}
|
||||
|
||||
function hook_field_widget_workflow_default_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
|
||||
// A hook specific for the 'workflow_default' widget.
|
||||
// D7: This hook is introduced in Drupal 7.8.
|
||||
// D8: This name is specified in the annotation of WorkflowDefaultWidget.
|
||||
workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
|
||||
|
||||
// A widget on an entity form.
|
||||
if ('workflow_default' == $context['widget']->getPluginId()) {
|
||||
// This object contains all you need. You may find it in one of two locations.
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $element['#default_value'];
|
||||
|
||||
// An example of customizing/overriding the workflow widget.
|
||||
// Beware, until now, you must do this twice: on the widget and on the form.
|
||||
if ($transition->getOwnerId() == 1) {
|
||||
drupal_set_message('(Test/Devel message) I got you, user 1, you will never schedule again,
|
||||
and you WILL document each state change!', 'warning');
|
||||
// Let's prohibit scheduling for user 1.
|
||||
$element['workflow_scheduling']['#access'] = FALSE;
|
||||
// Let's prohibit scheduling for user 1.
|
||||
if ($element['comment']['#access'] == TRUE) {
|
||||
$element['comment']['#required'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_BASE_FORM_ID_alter().
|
||||
*
|
||||
* Use this hook to alter the form.
|
||||
* It is only suited if you only use View Page or Workflow Tab.
|
||||
* If you change the state on the Entity Edit page (form), you need the hook
|
||||
* hook_form_alter(). See below for more info.
|
||||
*/
|
||||
function hook_form_workflow_transition_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $form_id, '');
|
||||
|
||||
// The WorkflowTransitionForm (E.g., Workflow History tab, Block).
|
||||
// It has its own handling.
|
||||
// @todo: Populate the WorkflowTransitionForm with a Widget, so we have 1 way-of-working.
|
||||
|
||||
// Let's take a (changeable) reference to the element.
|
||||
$workflow_element = &$form;
|
||||
|
||||
// This object contains all you need. You may find it in one of two locations.
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $form['workflow_transition']['#value'];
|
||||
// dpm($transition);
|
||||
|
||||
// An example of customizing/overriding the workflow widget.
|
||||
// Beware, until now, you must do this twice: on the widget and on the form.
|
||||
if ($transition->getOwnerId() == 1) {
|
||||
drupal_set_message('(Test/Devel message) I got you, user 1, you will never schedule again,
|
||||
and you WILL document each state change!', 'warning');
|
||||
// Let's prohibit scheduling for user 1.
|
||||
$workflow_element['workflow_scheduling']['#access'] = FALSE;
|
||||
// Let's prohibit scheduling for user 1.
|
||||
if ( $workflow_element['comment']['#access'] == TRUE) {
|
||||
$workflow_element['comment']['#required'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the Entity.
|
||||
/* @var $entity \Drupal\Core\Entity\EntityInterface */
|
||||
$entity = NULL;
|
||||
//$entity = $form['workflow_entity']['#value'];
|
||||
$entity_type = 'node'; // $form['workflow_entity_type']['#value'];
|
||||
$entity_bundle = ''; // $entity->bundle();
|
||||
$sid = '';
|
||||
if ($entity) {
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
$entity_bundle = $entity->bundle();
|
||||
|
||||
// Get the current State ID.
|
||||
$sid = workflow_node_current_state($entity, $field_name = NULL);
|
||||
// Get the State object, if needed.
|
||||
$state = WorkflowState::load($sid);
|
||||
}
|
||||
|
||||
// Change the form, depending on the state ID.
|
||||
// In the upcoming version 7.x-2.4, States should have a machine_name, too.
|
||||
if ($entity_type == 'node' && $entity_bundle == 'MY_NODE_TYPE') {
|
||||
switch ($sid) {
|
||||
case '2':
|
||||
// Change form element, form validate and form submit for state '2'.
|
||||
break;
|
||||
|
||||
case '3':
|
||||
// Change form element, form validate and form submit for state '3'.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_alter().
|
||||
*
|
||||
* Use this hook to alter the form on an Entity Form, Comment Form (Edit page).
|
||||
*
|
||||
* @see hook_form_workflow_transition_form_alter() for example code.
|
||||
*/
|
||||
function hook_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
if (substr($form_id, 0, 8) == 'workflow') {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, $form_id, '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hooks defined by core Entity: hook_entity_CRUD.
|
||||
*
|
||||
* Instead of using hook_entity_OPERATION, better use hook_ENTITY_TYPE_OPERATION.
|
||||
*
|
||||
* @see hook_entity_create(), hook_entity_update(), etc.
|
||||
* @see hook_ENTITY_TYPE_create(), hook_ENTITY_TYPE_update(), etc.
|
||||
*/
|
||||
function hook_entity_predelete(EntityInterface $entity) {
|
||||
if (substr($entity->getEntityTypeId(), 0, 8) == 'workflow') {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'pre-delete' , $entity->getEntityTypeId());
|
||||
}
|
||||
switch ($entity->getEntityTypeId()) {
|
||||
case 'workflow_config_transition':
|
||||
case 'workflow_state':
|
||||
case 'workflow_type':
|
||||
// Better use hook_ENTITY_TYPE_OPERATION.
|
||||
// E.g., hook_workflow_type_predelete
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function hook_entity_delete(EntityInterface $entity) {
|
||||
if (substr($entity->getEntityTypeId(), 0, 8) == 'workflow') {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'delete' , $entity->getEntityTypeId());
|
||||
}
|
||||
}
|
||||
|
||||
function hook_workflow_type_delete(EntityInterface $entity) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'delete' , $entity->getEntityTypeId());
|
||||
}
|
||||
|
||||
function hook_workflow_config_transition_delete(EntityInterface $entity) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'delete' , $entity->getEntityTypeId());
|
||||
}
|
||||
|
||||
function hook_workflow_state_delete(EntityInterface $entity) {
|
||||
// workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'delete' , $entity->getEntityTypeId());
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Defines a Workflow field, widget and formatter. (copied from list field).
|
||||
*/
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Changes the hook_field_settings_form.
|
||||
* Fixes some Field settings and Field Instance settings, and makes sure users cannot change it.
|
||||
*
|
||||
* @todo: perhaps this is core functionality, but these values are only saved
|
||||
* when the site builder explicitly save the instance settings. :-(
|
||||
*/
|
||||
function workflow_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
$field_name = $form_state->getFormObject()->getEntity()->getType();
|
||||
|
||||
if ($field_name == 'workflow') {
|
||||
// Make sure only 1 value can be entered in the Workflow field.
|
||||
$form['cardinality_container']['cardinality']['#default_value'] = 'number';
|
||||
$form['cardinality_container']['cardinality']['#disabled'] = TRUE;
|
||||
$form['cardinality_container']['cardinality_number']['#default_value'] = 1;
|
||||
$form['cardinality_container']['cardinality_number']['#disabled'] = TRUE;
|
||||
$form['cardinality_container']['cardinality_number']['#states'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function workflow_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
|
||||
$field_name = $form_state->getFormObject()->getEntity()->getType();
|
||||
|
||||
if ($field_name == 'workflow') {
|
||||
// The Workflow field must have a value, so set to required.
|
||||
$form['required']['#default_value'] = 1;
|
||||
$form['required']['#disabled'] = TRUE;
|
||||
|
||||
// There are alterations on the widget, too.
|
||||
// @see WorkflowDefaultWidget::formElement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We will be using some default formatters and widgets from the List and Options modules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_info_alter().
|
||||
*
|
||||
* The module reuses the formatters defined in list.module.
|
||||
*/
|
||||
function workflow_field_formatter_info_alter(&$info) {
|
||||
$info['list_key']['field_types'][] = 'workflow';
|
||||
$info['list_default']['field_types'][] = 'workflow';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_info_alter().
|
||||
*
|
||||
* The module does not implement widgets of its own, but reuses the
|
||||
* widgets defined in options.module.
|
||||
*
|
||||
* @see workflow_options_list()
|
||||
*/
|
||||
function workflow_field_widget_info_alter(&$info) {
|
||||
$info['options_select']['field_types'][] = 'workflow';
|
||||
$info['options_buttons']['field_types'][] = 'workflow';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form element to show the current value of a Workflow state.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity this field is on.
|
||||
* @param string $field_name
|
||||
* The field_name
|
||||
* @param string $current_sid
|
||||
* The current State Id.
|
||||
*
|
||||
* @return array
|
||||
* Form element, resembling the formatter of List module.
|
||||
* If state 0 is given, return an empty form element.
|
||||
*/
|
||||
function workflow_state_formatter($entity, $field_name, $current_sid = '') {
|
||||
$element = [];
|
||||
|
||||
if (!$current_sid) {
|
||||
$current_sid = workflow_node_current_state($entity, $field_name);
|
||||
}
|
||||
// If user creates a node, and only 1 option is available, the formatter
|
||||
// is shown with key, not value, because creation state does not count.
|
||||
// In this case, hide the formatter.
|
||||
$state = \Drupal\workflow\Entity\WorkflowState::load($current_sid);
|
||||
if ($state->isCreationState()) {
|
||||
return $element;
|
||||
}
|
||||
// Clone the entity and restore old value, in case you want to show an
|
||||
// executed transition.
|
||||
if ($entity->$field_name->value != $current_sid) {
|
||||
$entity = clone $entity;
|
||||
$entity->$field_name->value = $current_sid;
|
||||
}
|
||||
// Generate a renderable array for the field. Use default language determination ($langcode = NULL).
|
||||
// First, add the 'current value' formatter for this field.
|
||||
// $list_display = $instance['display']['default'];
|
||||
$list_display['type'] = 'list_default';
|
||||
$element = $entity->$field_name->view($list_display);
|
||||
|
||||
// @todo D8: make weight better (even better: hook_field_extra_fields).
|
||||
// Make sure the current value is before the form. (which has weight = 0.005)
|
||||
// $element['#weight'] = 0;
|
||||
|
||||
return $element;
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains helper functions for WorkflowTransitionForm.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\workflow\Entity\WorkflowTransitionInterface;
|
||||
|
||||
/**
|
||||
* Getter/Setter to tell if the action buttons are used.
|
||||
*
|
||||
* @param string $button_type
|
||||
* Options. If empty, value is only getted, else the button type.
|
||||
*
|
||||
* @return string
|
||||
* Previous value. If 'dropbutton'||'buttons', action buttons must be created.
|
||||
*
|
||||
* @see workflow_form_alter()
|
||||
* @see WorkflowDefaultWidget::formElement()
|
||||
*
|
||||
* Used to save some expensive operations on every form.
|
||||
*/
|
||||
function _workflow_use_action_buttons($button_type = '') {
|
||||
global $_workflow_use_actions_buttons;
|
||||
|
||||
// Reset value if requested.
|
||||
if ($button_type) {
|
||||
$_workflow_use_actions_buttons = $button_type;
|
||||
}
|
||||
return $_workflow_use_actions_buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_alter().
|
||||
*
|
||||
* Form builder. Move action buttons next to the 'Save'/'Delete' buttons.
|
||||
*
|
||||
* This is only used if the set the 'options widget' to 'action buttons'.
|
||||
* Do not use with multiple workflows per entity: confusing UX.
|
||||
* ATM this works for:
|
||||
* - Workflow Field: create, edit, view, workflow tab, comment;
|
||||
* - Workflow Node: view, workflow tab;
|
||||
* (For forms with Workflow Node, the form_alter() is AFTER formElement(). )
|
||||
*
|
||||
* @todo: move this to WorkflowTransitionForm::_addActionButtons();
|
||||
*/
|
||||
function workflow_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
|
||||
// N.B. Keep code aligned: workflow_form_alter(), WorkflowTransitionForm::actions().
|
||||
|
||||
// Use a fast, custom way to check if we need to do this.
|
||||
// @todo: Make this work with multiple workflows per entity.
|
||||
if (!_workflow_use_action_buttons()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($form_state->getBuildInfo()['base_form_id']) && $form_state->getBuildInfo()['base_form_id'] == 'workflow_transition_form') {
|
||||
// The WorkflowTransitionForm::actions() has its own handling.
|
||||
// E.g., Workflow History tab, Block.
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first workflow.
|
||||
// (So this won't work with multiple workflows per entity.)
|
||||
$workflow_element = _workflow_transition_form_get_first_workflow_element($form);
|
||||
|
||||
// Quit if there is no Workflow on this page.
|
||||
if (!$workflow_element ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Quit if there are no Workflow Action buttons.
|
||||
// (If user has only 1 workflow option, there are no Action buttons.)
|
||||
if (count($workflow_element['to_sid']['#options']) <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the default submit button and replace with our own action buttons.
|
||||
if (isset($form['actions']['submit'])) {
|
||||
$default_submit_action = $form['actions']['submit'];
|
||||
// @todo D8-port: on Node-form, this is not sufficient.
|
||||
unset($form['actions']['submit']);
|
||||
}
|
||||
elseif (isset($form['actions']['save'])) {
|
||||
$default_submit_action = $form['actions']['save'];
|
||||
unset($form['actions']['save']);
|
||||
}
|
||||
else {
|
||||
// @todo test: when does this happen?
|
||||
$default_submit_action = [];
|
||||
}
|
||||
if (isset($default_submit_action)) {
|
||||
$actions = _workflow_transition_form_get_action_buttons($form, $workflow_element, $default_submit_action);
|
||||
// Place the button with the other action buttons.
|
||||
$form['actions'] = isset($form['actions']) ? $form['actions'] : [];
|
||||
$form['actions'] += $actions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the first workflow_element from one of the Workflow Fields.
|
||||
*
|
||||
* @param array $form
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function _workflow_transition_form_get_first_workflow_element(&$form) {
|
||||
$workflow_element = [];
|
||||
|
||||
// Find the first workflow.
|
||||
// (So this won't work with multiple workflows per entity.)
|
||||
foreach (\Drupal\Core\Render\Element::children($form) as $key) {
|
||||
if (isset($form[$key]['widget'][0]['#default_value'])) {
|
||||
$transition = $form[$key]['widget'][0]['#default_value'];
|
||||
if (is_object($transition) && $transition instanceof WorkflowTransitionInterface ) {
|
||||
$workflow_element = $form[$key]['widget'][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $workflow_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action buttons from the options widget.
|
||||
*
|
||||
* @param array $form
|
||||
* @param array $workflow_element
|
||||
* @param array $default_submit_action
|
||||
*
|
||||
* @return array $actions
|
||||
*/
|
||||
function _workflow_transition_form_get_action_buttons(array $form, array $workflow_element, array $default_submit_action) {
|
||||
$actions = [];
|
||||
$current_sid = $workflow_element['to_sid']['#default_value'];
|
||||
/* @var $transition WorkflowTransitionInterface */
|
||||
$transition = $workflow_element['workflow_transition']['#value'];
|
||||
$field_name = $transition->getFieldName();
|
||||
|
||||
// Find the default submit button and add our action buttons before it.
|
||||
// Get the min weight for our buttons.
|
||||
$option_weight = isset($default_submit_action['#weight']) ? $default_submit_action['#weight'] : 0;
|
||||
$option_weight = $option_weight - count($workflow_element['to_sid']['#options']);
|
||||
$min_weight = $option_weight;
|
||||
foreach ($workflow_element['to_sid']['#options'] as $sid => $option_name) {
|
||||
// Make the workflow button act exactly like the original submit button.
|
||||
|
||||
$same_state_button = ($sid == $current_sid);
|
||||
$workflow_submit_action = $default_submit_action;
|
||||
// Add target State ID and Field name, to set correct value in validate_buttons callback.
|
||||
$workflow_submit_action['#workflow'] = [
|
||||
'field_name' => $field_name,
|
||||
'to_sid' => $sid,
|
||||
];
|
||||
// Keep option order. Put current state first.
|
||||
$workflow_submit_action['#weight'] = ($same_state_button) ? $min_weight : ++$option_weight;
|
||||
// Add/Overwrite some other settings.
|
||||
$workflow_submit_action['#access'] = TRUE;
|
||||
$workflow_submit_action['#value'] = $option_name;
|
||||
// Use one drop button, instead of several action buttons.
|
||||
if ('dropbutton' == _workflow_use_action_buttons()) {
|
||||
$workflow_submit_action['#dropbutton'] = 'save';
|
||||
}
|
||||
$workflow_submit_action['#attributes'] = ($same_state_button) ? ['class' => ['form-save-default-button']] : [];
|
||||
$workflow_submit_action['#button_type'] = ($same_state_button) ? 'primary' : ''; // @todo: works for node form and workflow tab, not for workflow block.
|
||||
//$workflow_submit_action['#executes_submit_callback'] = TRUE;
|
||||
// Add class to workflow button.
|
||||
$workflow_submit_action['#attributes']['class'][] = Html::getClass('workflow_button_' . $option_name);
|
||||
// Append the form's #validate function, or it won't be called upon submit,
|
||||
// because the workflow buttons have its own #validate.
|
||||
$workflow_submit_action['#validate'] = [];
|
||||
$workflow_submit_action['#validate'][] = '_workflow_transition_form_validate_buttons';
|
||||
if (isset($default_submit_action['#validate'])) {
|
||||
$workflow_submit_action['#validate'] = $default_submit_action['#validate'];
|
||||
}
|
||||
elseif (isset($form['#validate'])) {
|
||||
$workflow_submit_action['#validate'] = $form['#validate'];
|
||||
}
|
||||
// Append the submit-buttons's #submit function, or it won't be called upon submit.
|
||||
if (isset($default_submit_action['#submit'])) {
|
||||
$workflow_submit_action['#submit'] = $default_submit_action['#submit'];
|
||||
}
|
||||
elseif (isset($form['#submit'])) {
|
||||
$workflow_submit_action['#submit'] = $form['#submit'];
|
||||
}
|
||||
|
||||
// Hide the same-state button in some cases.
|
||||
if ($same_state_button) {
|
||||
if (isset($form['#form_id']) && substr($form['#form_id'], 0, 24) == 'workflow_transition_form') {
|
||||
// Hide same-state-button on the transition-form (that is:
|
||||
// view page or workflow history tab) if there is nothing to do.
|
||||
// However, a Transition may be fieldable.
|
||||
if ($form['comment']['#access'] == FALSE) {
|
||||
$workflow_submit_action['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
elseif (isset($form['#id']) && $form['#id'] == 'comment-form') {
|
||||
// On comment-form, the button must stay, since you can comment to same state.
|
||||
}
|
||||
else {
|
||||
// On a entity edit page, the button must stay.
|
||||
}
|
||||
}
|
||||
|
||||
// Place the button with the other action buttons.
|
||||
$actions['workflow_' . $sid] = $workflow_submit_action;
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Workflow parameter from the button, pressed by the user.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
* @return array
|
||||
* A $field_name => $to_sid array.
|
||||
*/
|
||||
function _workflow_transition_form_get_triggering_button(\Drupal\Core\Form\FormStateInterface $form_state) {
|
||||
$result = ['field_name' => '', 'to_sid' => ''];
|
||||
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
if (isset($triggering_element['#workflow'])) {
|
||||
$result['field_name'] = $triggering_element['#workflow']['field_name'];
|
||||
$result['to_sid'] = $triggering_element['#workflow']['to_sid'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback function for the Workflow Form / DefaultWidget.
|
||||
*
|
||||
* Validate form data for 'time' element.
|
||||
* @param $element
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* @param $form
|
||||
*/
|
||||
function _workflow_transition_form_element_validate_time($element, \Drupal\Core\Form\FormStateInterface &$form_state, $form) {
|
||||
if (!strtotime($element['#value'])) {
|
||||
$form_state->setError($element, t('Please enter a valid value for time.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback function for the Workflow Form / DefaultWidget.
|
||||
*
|
||||
* This is only used when using action buttons in the widget.
|
||||
* It sets the new state to proper
|
||||
* element and sets a submit function if needed, making sure the action is
|
||||
* executed, influencing function core/includes/form.inc/form_execute_handlers().
|
||||
* (While constructing the Workflow form, we were not yet aware of the submit
|
||||
* buttons of the complete form. We try to correct this here, without adding
|
||||
* another hook_form_alter. We guess the first button is the Save button.
|
||||
*/
|
||||
function _workflow_transition_form_validate_buttons($form, \Drupal\Core\Form\FormStateInterface &$form_state) {
|
||||
// Retrieve the data from the form.
|
||||
$transition = $form_state->getValue('workflow_transition');
|
||||
if ($transition) {
|
||||
// On WorkflowTransitionForm :
|
||||
// D7: $form_state['input']['to_sid'] = $new_sid;
|
||||
// D7: $form_state['values'][$field_name][$langcode][0]['to_sid'] = $new_sid;
|
||||
|
||||
$values = $form_state->getValues();
|
||||
|
||||
$to_sid = $form_state->getTriggeringElement()['#workflow']['to_sid'];
|
||||
$values['to_sid'] = $to_sid;
|
||||
|
||||
// Update the form_state.
|
||||
$form_state->setValues($values);
|
||||
}
|
||||
else {
|
||||
// On edit form : See $form_state->getTriggeringElement() in WorkflowDefaultWidget;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
name: 'Workflow'
|
||||
description: 'Defines a field type with Workflows, containing customizable state transitions.'
|
||||
package: Workflow
|
||||
type: module
|
||||
# core: 8.x
|
||||
# version: VERSION
|
||||
|
||||
dependencies:
|
||||
- field
|
||||
- options
|
||||
- user
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-17
|
||||
version: '8.x-1.0'
|
||||
core: '8.x'
|
||||
project: 'workflow'
|
||||
datestamp: 1513552400
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the workflow module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function workflow_install() {
|
||||
$url = \Drupal\Core\Url::fromRoute('system.modules_list', [], ['fragment' => 'edit-modules-workflow']);
|
||||
$message = t('Thanks for using Workflow. To maintain workflows,
|
||||
<a href=":url">install the Workflow UI module</a>. To start using
|
||||
a Workflow, add a Workflow Field to your entity.',
|
||||
[':url' => $url->toString()]
|
||||
);
|
||||
drupal_set_message($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function workflow_uninstall() {
|
||||
// \Drupal::config('workflow.settings')->clear('workflow_states_per_page')->save(); // @FIXME
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*
|
||||
* Let admins know that Workflow is in use.
|
||||
*
|
||||
* @todo: extend workflow_requirements() for use with Workflow Field API.
|
||||
*
|
||||
* @param $phase
|
||||
* @return array
|
||||
*/
|
||||
function workflow_requirements($phase) {
|
||||
$requirements = [];
|
||||
switch ($phase) {
|
||||
case 'install':
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
break;
|
||||
|
||||
case 'runtime':
|
||||
// workflow_debug(__FILE__), __FUNCTION__, __LINE__, $form_id); // @todo D8-port: still test this snippet.
|
||||
|
||||
/*
|
||||
// Show info on admin/reports/status.
|
||||
$type_list = 'Not yet determined.';
|
||||
|
||||
// $types = db_query('SELECT wid, type FROM {workflow_type_map} WHERE wid <> 0 ORDER BY type')->fetchAllKeyed();
|
||||
// If there are no types, then just bail.
|
||||
if (count($types) == 0) {
|
||||
return;
|
||||
}
|
||||
// Let's make it look nice.
|
||||
if (count($types) == 1) {
|
||||
$type_list = current($types);
|
||||
} else {
|
||||
$last = array_pop($types);
|
||||
if (count($types) > 2) {
|
||||
$type_list = implode(', ', $types) . ', and ' . $last;
|
||||
} else {
|
||||
$type_list = current($types) . ' and ' . $last;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
$requirements['workflow'] = array(
|
||||
'title' => $t('Workflow'),
|
||||
'value' => $t('Workflow is active on the @types content types.', array('@types' => $type_list)),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
);
|
||||
*/
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*
|
||||
* The D8-schema's have moved to:
|
||||
* - Workflow annotation (it is a Config Entity now);
|
||||
* - WorkfowState annotation (it is a Config Entity now);
|
||||
* - WorkfowConfigTransition annotation (it is a Config Entity now);
|
||||
* - WorkflowTransition::baseFieldDefinitions();
|
||||
* - WorkflowScheduledTransition::baseFieldDefinitions().
|
||||
*/
|
||||
function workflow_schema() {
|
||||
return $schema = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal 8 updates.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update from version beta1 to beta2 are not possible. Please re-install this module.
|
||||
*/
|
||||
function workflow_update_8001(&$sandbox) {
|
||||
drupal_set_message("Update from version beta1 to beta2 is not possible. Please re-install this module.");
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#workflow.entities:
|
||||
# class: \Drupal\Core\Menu\LocalTaskDefault
|
||||
# deriver: \Drupal\workflow\Plugin\Derivative\WorkflowLocalTask
|
||||
|
||||
#entity.workflow_type.canonical:
|
||||
# route_name: entity.workflow_type.canonical
|
||||
# base_route: entity.workflow_type.edit_form
|
||||
# title: 'Edit'
|
||||
# weight: -5
|
||||
entity.workflow_type.edit_form:
|
||||
route_name: entity.workflow_type.canonical
|
||||
base_route: entity.workflow_type.edit_form
|
||||
title: 'Edit'
|
||||
weight: -5
|
||||
entity.workflow_type.delete_form:
|
||||
route_name: entity.workflow_type.delete_form
|
||||
base_route: entity.workflow_type.edit_form
|
||||
title: 'Delete'
|
||||
weight: 10
|
||||
|
||||
# @todo D8-port: make Workflow History tab happen for every entity_type.
|
||||
# @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController.
|
||||
entity.node.workflow_history:
|
||||
route_name: entity.node.workflow_history
|
||||
base_route: entity.node.canonical
|
||||
title: 'Workflow'
|
||||
weight: 30
|
||||
entity.taxonomy_term.edit_form:
|
||||
route_name: entity.taxonomy_term.workflow_history
|
||||
base_route: entity.taxonomy_term.canonical
|
||||
title: 'Workflow'
|
||||
weight: 30
|
||||
@@ -0,0 +1,705 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Support workflows made up of arbitrary states.
|
||||
*/
|
||||
|
||||
define('WORKFLOW_CREATION_STATE', 1);
|
||||
define('WORKFLOW_CREATION_DEFAULT_WEIGHT', -50);
|
||||
define('WORKFLOW_DELETION', 0);
|
||||
// Couldn't find a more elegant way to preserve translation.
|
||||
define('WORKFLOW_CREATION_STATE_NAME', 'creation');
|
||||
|
||||
/**
|
||||
* Role ID for anonymous users.
|
||||
*/
|
||||
// #2657072 brackets are added later to indicate a special role, and distinguish from frequently used 'author' role.
|
||||
define('WORKFLOW_ROLE_AUTHOR_NAME', 'Author');
|
||||
define('WORKFLOW_ROLE_AUTHOR_RID', 'workflow_author');
|
||||
|
||||
use Drupal\workflow\Entity\Workflow;
|
||||
use Drupal\workflow\Entity\WorkflowManager;
|
||||
use Drupal\workflow\Entity\WorkflowScheduledTransition;
|
||||
use Drupal\workflow\Entity\WorkflowState;
|
||||
use Drupal\workflow\Entity\WorkflowTransition;
|
||||
|
||||
module_load_include('inc', 'workflow', 'workflow.form');
|
||||
module_load_include('inc', 'workflow', 'workflow.field');
|
||||
|
||||
/**********************************************************************
|
||||
*
|
||||
* Info hooks.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*
|
||||
* @param $route_name
|
||||
* @return string
|
||||
*/
|
||||
function workflow_help($route_name) {
|
||||
$output = '';
|
||||
|
||||
switch ($route_name) {
|
||||
case 'help.page.workflow':
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Workflow module adds a field to Entities to
|
||||
store field values as Workflow states. You can control "state transitions"
|
||||
and add action to specific transitions.') . '</p>';
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hook_info().
|
||||
*
|
||||
* Allow adopters to place their hook implementations in either
|
||||
* their main module or in a module.workflow.inc file.
|
||||
*/
|
||||
function workflow_hook_info() {
|
||||
$hooks['workflow'] = ['group' => 'workflow'];
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
*
|
||||
* CRUD hooks.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_user_cancel().
|
||||
*
|
||||
* Update tables for deleted account, move account to user 0 (anon.)
|
||||
* ALERT: This may cause previously non-Anonymous posts to suddenly
|
||||
* be accessible to Anonymous.
|
||||
*
|
||||
* @see hook_user_cancel()
|
||||
*
|
||||
* @param $edit
|
||||
* @param $account
|
||||
* @param $method
|
||||
*/
|
||||
function workflow_user_cancel($edit, $account, $method) {
|
||||
WorkflowManager::cancelUser($edit, $account, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_user_delete().
|
||||
* @todo: hook_user_delete does not exist. hook_ENTITY_TYPE_delete?
|
||||
*
|
||||
* @param $account
|
||||
*/
|
||||
function workflow_user_delete($account) {
|
||||
WorkflowManager::deleteUser($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_insert().
|
||||
*
|
||||
* Is called when adding a new Workflow type.
|
||||
* The technical name for the Workflow entity is 'workflow_type'.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*/
|
||||
function workflow_workflow_type_insert(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
WorkflowManager::participateUserRoles($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_insert().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*/
|
||||
function workflow_entity_insert(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// Execute updates in hook_presave() to revert executions,
|
||||
// Execute inserts in hook_insert, to have the Entity ID determined.
|
||||
_workflow_execute_transitions($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_presave().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*/
|
||||
function workflow_entity_presave(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
if (!$entity->isNew()) {
|
||||
// Avoid a double call by hook_entity_presave and hook_entity_insert.
|
||||
_workflow_execute_transitions($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute transitions. if prohibited, restore original field value.
|
||||
* - insert: use hook_insert(), to have the Entity ID determined when saving transitions.
|
||||
* - update: use hook_presave() to revert executions,
|
||||
* - so, do not use hook_update().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*/
|
||||
function _workflow_execute_transitions(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// Avoid this hook on workflow objects.
|
||||
if (WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute/save the transitions fom the widgets in the entity form.
|
||||
WorkflowManager::executeTransitionsOfEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_delete().
|
||||
*
|
||||
* Delete the corresponding workflow table records.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
*/
|
||||
function workflow_entity_delete(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
// @todo D8: test with multiple workflows.
|
||||
if (get_class($entity) == 'Drupal\field\Entity\FieldConfig'
|
||||
|| get_class($entity) == 'Drupal\field\Entity\FieldStorageConfig') {
|
||||
// A Workflow Field is removed from an entity.
|
||||
$field_config = $entity;
|
||||
/** @var \Drupal\Core\Entity\ContentEntityBase $field_config */
|
||||
$entity_type = $field_config->get('entity_type');
|
||||
$field_name = $field_config->get('field_name');
|
||||
/** @var $transition Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [], [], $field_name) as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
foreach (WorkflowTransition::loadMultipleByProperties($entity_type, [], [], $field_name) as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
}
|
||||
elseif (!WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) {
|
||||
// A 'normal' entity is deleted.
|
||||
foreach ($fields = _workflow_info_fields($entity) as $field_id => $field_storage) {
|
||||
$entity_type = $field_storage->getTargetEntityTypeId();
|
||||
$entity_id = $entity->id();
|
||||
$field_name = $field_storage->get('field_name');
|
||||
/** @var $transition Drupal\workflow\Entity\WorkflowTransitionInterface */
|
||||
foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
foreach (WorkflowTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*
|
||||
* Given a time frame, execute all scheduled transitions.
|
||||
*/
|
||||
function workflow_cron() {
|
||||
WorkflowManager::executeScheduledTransitionsBetween(0, \Drupal::time()->getRequestTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Business related functions, the API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated D8: @see WorkflowManager::executeTransition().
|
||||
*
|
||||
* @param \Drupal\workflow\Entity\WorkflowTransitionInterface $transition
|
||||
* @param bool $force
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function workflow_execute_transition(Drupal\workflow\Entity\WorkflowTransitionInterface $transition, $force = FALSE) {
|
||||
// Execute transition and update the attached entity.
|
||||
return $transition->executeAndUpdateEntity($force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions to get an options list (to show in a Widget).
|
||||
* To be used in non-OO modules, like workflow_rules, workflow_views.
|
||||
*
|
||||
* The naming convention is workflow_get_<entity_type>_names.
|
||||
* (A bit different from 'user_role_names'.)
|
||||
* Can be used for hook_allowed_values from list.module:
|
||||
* - user_role
|
||||
* - workflow
|
||||
* - workflow_state
|
||||
* - sid
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the names of roles matching specified conditions.
|
||||
*
|
||||
* deprecated D8: workflow_get_roles --> workflow_get_user_role_names
|
||||
*
|
||||
* Usage:
|
||||
* D7: $roles = workflow_get_user_role_names('participate in workflow');
|
||||
* D8: $type_id = $workflow->id();
|
||||
* D8: $roles = workflow_get_user_role_names("create $type_id workflow_transition");
|
||||
*
|
||||
* @param string $permission
|
||||
* (optional) A string containing a permission. If set, only roles
|
||||
* containing that permission are returned. Defaults to NULL, which
|
||||
* returns all roles.
|
||||
* Normal usage for filtering roles that are enabled in a workflow_type
|
||||
* would be: $permission = 'create $type_id transition'.
|
||||
*
|
||||
* @return array
|
||||
* Array of role names keyed by role ID, including the 'author' role.
|
||||
*/
|
||||
function workflow_get_user_role_names($permission) {
|
||||
static $roles = NULL;
|
||||
if ($roles[$permission]) {
|
||||
return $roles[$permission];
|
||||
}
|
||||
|
||||
// Copied from AccountForm::form().
|
||||
$roles[$permission] = array_map(['\Drupal\Component\Utility\Html', 'escape'],
|
||||
[WORKFLOW_ROLE_AUTHOR_RID => '(' . t(WORKFLOW_ROLE_AUTHOR_NAME) . ')']
|
||||
+ user_role_names(FALSE, $permission));
|
||||
|
||||
return $roles[$permission];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an options list for workflow states.
|
||||
*
|
||||
* @param mixed $wid
|
||||
* The Workflow ID.
|
||||
* @param bool $grouped
|
||||
* Indicates if the value must be grouped per workflow.
|
||||
* This influences the rendering of the select_list options.
|
||||
*
|
||||
* @return array
|
||||
* An array of $sid => state->label(), grouped per Workflow.
|
||||
*/
|
||||
function workflow_get_workflow_state_names($wid = '', $grouped = FALSE) {
|
||||
$options = [];
|
||||
|
||||
// @todo: implement $add parameter.
|
||||
//
|
||||
// @todo: follow Options pattern
|
||||
// @see callback_allowed_values_function()
|
||||
// @see options_allowed_values()
|
||||
|
||||
|
||||
// Get the (user-dependent) options.
|
||||
// Since this function is only used in UI, it is save to use the global $user.
|
||||
$user = workflow_current_user();
|
||||
|
||||
/** @var $workflows Workflow[] */
|
||||
$workflows = Workflow::loadMultiple($wid ? [$wid] : NULL);
|
||||
// Do not group if only 1 Workflow is configured or selected.
|
||||
$grouped = count($workflows) == 1 ? FALSE : $grouped;
|
||||
|
||||
foreach ($workflows as $wid => $workflow) {
|
||||
/** @var $state WorkflowState */
|
||||
$state = WorkflowState::create(['wid' => $wid]);
|
||||
$workflow_options = $state->getOptions(NULL, '', $user, FALSE);
|
||||
if (!$grouped) {
|
||||
$options += $workflow_options;
|
||||
}
|
||||
else {
|
||||
// Make a group for each Workflow.
|
||||
$options[$workflow->label()] = $workflow_options;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an options list for workflows. Include an initial empty value
|
||||
* if requested. Validate each workflow, and generate a message if not complete.
|
||||
*
|
||||
* @param bool $required
|
||||
* Indicates if the resulting list contains a options value.
|
||||
* @return array
|
||||
* An array of $wid => workflow->label().
|
||||
*/
|
||||
function workflow_get_workflow_names($required = TRUE) {
|
||||
$options = [];
|
||||
|
||||
if (!$required) {
|
||||
$options[''] = t('- Select a value -');
|
||||
}
|
||||
foreach (Workflow::loadMultiple() as $wid => $workflow) {
|
||||
/** @var $workflow Workflow */
|
||||
if ($workflow->isValid()) {
|
||||
$options[$wid] = $workflow->label();
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an Options list of field names.
|
||||
*
|
||||
* @param null $entity
|
||||
* @param string $entity_type
|
||||
* @param string $entity_bundle
|
||||
* @param string $field_name
|
||||
* @return array
|
||||
*/
|
||||
function workflow_get_workflow_field_names($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') {
|
||||
$result = [];
|
||||
foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name) as $definition) {
|
||||
$field_name = $definition->getName();
|
||||
$result[$field_name] = $definition->getName();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function, to get the label of a given State Id.
|
||||
*
|
||||
* @param $sid
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* deprecated: workflow_get_sid_label() --> workflow_get_sid_name()
|
||||
*/
|
||||
function workflow_get_sid_name($sid) {
|
||||
|
||||
if (empty($sid)) {
|
||||
$label = 'No state';
|
||||
}
|
||||
/** @noinspection PhpAssignmentInConditionInspection */
|
||||
elseif ($state = WorkflowState::load($sid)) {
|
||||
$label = $state->label();
|
||||
}
|
||||
else {
|
||||
$label = 'Unknown state';
|
||||
}
|
||||
return t($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the Workflow field_name of an entity.
|
||||
* If an entity has multiple workflows, only returns the first one.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity at hand.
|
||||
* @param string $field_name
|
||||
* The field name. If given, will be passed as return value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function workflow_get_field_name(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') {
|
||||
if (!$entity) {
|
||||
// $entity may be empty on Entity Add page.
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($field_name) {
|
||||
return $field_name;
|
||||
}
|
||||
|
||||
$fields = _workflow_info_fields($entity);
|
||||
$field = reset($fields);
|
||||
$field_name = $field->getName();
|
||||
return $field_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions to get the state of an entity.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wrapper function to get a UserInterface object.
|
||||
* We use UserInterface to check permissions.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface|null $account
|
||||
*
|
||||
* @return \Drupal\user\UserInterface
|
||||
*/
|
||||
function workflow_current_user(\Drupal\Core\Session\AccountInterface $account = NULL) {
|
||||
$account = ($account) ? $account : \Drupal::currentUser();
|
||||
return \Drupal\user\Entity\User::load($account->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current state ID of a given entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* @param string $field_name
|
||||
*
|
||||
* @return string $current_sid
|
||||
*
|
||||
* @deprecated : use WorkflowManager::getCurrentStateId()
|
||||
*/
|
||||
function workflow_node_current_state(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') {
|
||||
return WorkflowManager::getCurrentStateId($entity, $field_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous state ID of a given entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* @param string $field_name
|
||||
*
|
||||
* @return string $previous_sid
|
||||
*
|
||||
* @deprecated : use WorkflowManager::getPreviousStateId()
|
||||
*/
|
||||
function workflow_node_previous_state(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') {
|
||||
return WorkflowManager::getPreviousStateId($entity, $field_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific workflow, given an entity type. Only one workflow is possible per node type.
|
||||
*
|
||||
* @param string $entity_bundle
|
||||
* An entity bundle.
|
||||
* @param string $entity_type
|
||||
* An entity type. This is passed when also the Field API must be checked.
|
||||
*
|
||||
* @return Workflow
|
||||
* A Workflow object, or NULL if no workflow is retrieved.
|
||||
*
|
||||
* Caveat: gives undefined results with multiple workflows per entity.
|
||||
*
|
||||
* @todo: support multiple workflows per entity.
|
||||
*/
|
||||
function workflow_get_workflows_by_type($entity_bundle, $entity_type) {
|
||||
static $map = [];
|
||||
|
||||
if (isset($map[$entity_type][$entity_bundle])) {
|
||||
return $map[$entity_type][$entity_bundle];
|
||||
}
|
||||
|
||||
$wid = FALSE;
|
||||
if (isset($entity_type)) {
|
||||
foreach (_workflow_info_fields(NULL, $entity_type, $entity_bundle) as $field_info) {
|
||||
$wid = $field_info->getSetting('workflow_type');
|
||||
}
|
||||
}
|
||||
// Set the cache with a workflow object.
|
||||
$map[$entity_type][$entity_bundle] = NULL;
|
||||
if ($wid) {
|
||||
$map[$entity_type][$entity_bundle] = Workflow::load($wid);
|
||||
}
|
||||
|
||||
return $map[$entity_type][$entity_bundle];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the workflow field names, if not known already.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* Object to work with. May be empty, e.g., on menu build.
|
||||
* @param string $entity_type
|
||||
* Entity type of object. Optional, but required if $entity provided.
|
||||
* @param string $entity_bundle
|
||||
* Bundle of entity. Optional.
|
||||
* @param string $field_name
|
||||
* Field name. Optional.
|
||||
*
|
||||
* @return Drupal\field\Entity\FieldStorageConfig[]
|
||||
* An array of FieldStorageConfig objects.
|
||||
*/
|
||||
function _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') {
|
||||
$field_info = [];
|
||||
|
||||
// Figure out the $entity's bundle and id.
|
||||
if ($entity) {
|
||||
$entity_type = $entity->getEntityTypeId();
|
||||
$entity_bundle = $entity->bundle();
|
||||
}
|
||||
else {
|
||||
// Entity type and bundle should be specified.
|
||||
}
|
||||
|
||||
$field_list = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow');
|
||||
foreach ($field_list as $e_type => $data) {
|
||||
if (!$entity_type || ($entity_type == $e_type)) {
|
||||
foreach ($data as $f_name => $value) {
|
||||
if (!$entity_bundle || isset($value['bundles'][$entity_bundle])) {
|
||||
if (!$field_name || ($field_name == $f_name)) {
|
||||
// Do not use the field_name as ID, but the unique <entity_type>.<field_name>
|
||||
// since you cannot share the same field on multiple entity_types (unlike D7).
|
||||
$field_config = \Drupal\field\Entity\FieldStorageConfig::loadByName($e_type, $f_name);
|
||||
$field_info[$field_config->id()] = $field_config;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $field_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the entity from a route.
|
||||
*
|
||||
* This is a hack. It should be solved by using $route_match.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface|null $entity
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = NULL) {
|
||||
if ($entity) {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
$route_match = \Drupal::routeMatch();
|
||||
|
||||
// @todo: get entity for any route.
|
||||
// On node pages, we'd get an object.
|
||||
$entity = $route_match->getParameter('node');
|
||||
if ($entity && is_object($entity)) {
|
||||
return $entity;
|
||||
}
|
||||
if ($entity && !is_object($entity)) {
|
||||
// On workflow tab, we'd get an id.
|
||||
$entity = \Drupal\node\Entity\Node::load($entity);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// It was not a Node, try a Term.
|
||||
// On term pages, we get objects, or id's.
|
||||
$entity = $route_match->getParameter('taxonomy_term');
|
||||
if ($entity && is_object($entity)) {
|
||||
return $entity;
|
||||
}
|
||||
elseif ($entity && !is_object($entity)) {
|
||||
$entity = \Drupal\taxonomy\Entity\Term::load($entity);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
if (!$entity) {
|
||||
// We may be on a entity add page/
|
||||
// Or we may be on a page of some unknown entity.
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the field name from a route.
|
||||
*
|
||||
* For now only used for ../{entity_id}/workflow history tab.
|
||||
*
|
||||
* @return string|null $field_name
|
||||
*/
|
||||
function workflow_url_get_field_name() {
|
||||
/** @var $route_match \Drupal\Core\Routing\RouteMatchInterface */
|
||||
$route_match = \Drupal::routeMatch();
|
||||
$field_name = $route_match->getParameter('field_name');
|
||||
return $field_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the entity from a route.
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
function workflow_url_get_operation() {
|
||||
$url = \Drupal\Core\Url::fromRoute('<current>');
|
||||
// The last part of the path is the operation: edit, workflow, devel.
|
||||
$url_parts = explode('/', $url->toString());
|
||||
$operation = array_pop($url_parts);
|
||||
// Except for view pages.
|
||||
if (is_numeric($operation) || $operation == 'view') {
|
||||
$operation = '';
|
||||
}
|
||||
return $operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine Workflow from Workflow UI URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @return Workflow
|
||||
*/
|
||||
function workflow_url_get_workflow($url = '' ) {
|
||||
/** @var $workflows \Drupal\workflow\Entity\Workflow[] */
|
||||
static $workflows = [];
|
||||
|
||||
// For some reason, $_SERVER is not allowed as default.
|
||||
$url = ($url == '') ? $_SERVER['REQUEST_URI'] : $url;
|
||||
|
||||
// The URL may have prefixes: /
|
||||
// ex.1: /en/admin/config/workflow/workflow/MY_WORKFLOW/states
|
||||
// ex.2: /admin/config/workflow/workflow/MY_WORKFLOW/states
|
||||
$base_url = '/config/workflow/workflow/';
|
||||
$string = substr($url, strpos($url, $base_url) + strlen($base_url));
|
||||
$wid = explode('/', $string)[0];
|
||||
if (!isset($workflows[$wid])) {
|
||||
$workflows[$wid] = Workflow::load($wid);
|
||||
}
|
||||
|
||||
return $workflows[$wid];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to determine the title of the page.
|
||||
*
|
||||
* Used in file workflow_ui.routing.yml.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
function workflow_url_get_title() {
|
||||
$label = '';
|
||||
|
||||
// Get the Workflow from the page.
|
||||
/** @var $workflow \Drupal\workflow\Entity\Workflow */
|
||||
/** @noinspection PhpAssignmentInConditionInspection */
|
||||
if ($workflow = workflow_url_get_workflow()) {
|
||||
$label = $workflow->label();
|
||||
}
|
||||
|
||||
$title = t('Edit @entity %label', ['@entity' => 'Workflow', '%label' => $label]);
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine Workflow from Workflow UI URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @return mixed
|
||||
*/
|
||||
function workflow_url_get_form_type($url = '' ) {
|
||||
// For some reason, $_SERVER is not allowed as default.
|
||||
$url = ($url == '') ? $_SERVER['REQUEST_URI'] : $url;
|
||||
|
||||
$base_url = '/config/workflow/workflow/';
|
||||
$string = substr($url, strpos($url, $base_url) + strlen($base_url));
|
||||
$type = explode('/', $string)[1];
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for D8-port: Get some info on screen.
|
||||
* @see workflow_devel-module
|
||||
*
|
||||
* Usage:
|
||||
* workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', ''); // @todo D8-port: still test this snippet.
|
||||
*
|
||||
* @param string $class_name
|
||||
* @param string $function_name
|
||||
* @param string $line
|
||||
* @param string $value1
|
||||
* @param string $value2
|
||||
*/
|
||||
function workflow_debug($class_name, $function_name, $line = '', $value1 = '', $value2 = '') {
|
||||
$debug_switch = FALSE;
|
||||
// $debug_switch = TRUE;
|
||||
|
||||
if (!$debug_switch) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class_name_elements = explode( "\\", $class_name);
|
||||
$output = 'Testing... function ' . end($class_name_elements) . '::' . $function_name . '/' . $line;
|
||||
if ($value1) {
|
||||
$output .= ' = ' . $value1;
|
||||
}
|
||||
if ($value2) {
|
||||
$output .= ' > ' . $value2;
|
||||
}
|
||||
drupal_set_message($output, 'warning', TRUE);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Permissions are declared in Workflow (not Workflow UI) so other contrib
|
||||
# modules can make use of it.
|
||||
|
||||
administer workflow:
|
||||
title: 'Administer workflows'
|
||||
description: 'Administer all Workflow configurations and settings.'
|
||||
restrict access: true
|
||||
|
||||
permission_callbacks:
|
||||
- \Drupal\workflow\WorkflowPermissions::workflowTypePermissions
|
||||
@@ -0,0 +1,144 @@
|
||||
# All other routes for Workflow maintenance are declared in Workflow UI.
|
||||
|
||||
### Workflow CRUD
|
||||
entity.workflow_type.collection:
|
||||
path: '/admin/config/workflow/workflow'
|
||||
defaults:
|
||||
_entity_list: 'workflow_type'
|
||||
# _controller: '\workflow\EntityWorkflowUIController::adminOverview'
|
||||
_title: 'Workflows'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
|
||||
entity.workflow_type.canonical:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}'
|
||||
defaults:
|
||||
_entity_form: 'workflow_type.edit'
|
||||
_title: 'Edit Workflow'
|
||||
_title_callback: 'workflow_url_get_title'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
# _entity_access: 'workflow.update'
|
||||
|
||||
entity.workflow_type.add_form:
|
||||
path: '/admin/config/workflow/workflow/add'
|
||||
defaults:
|
||||
_entity_form: 'workflow_type.add'
|
||||
_title: 'Add Workflow'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
# _entity_access: 'workflow.update'
|
||||
|
||||
entity.workflow_type.edit_form:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}'
|
||||
defaults:
|
||||
_entity_form: 'workflow_type.edit'
|
||||
_title: 'Edit Workflow'
|
||||
_title_callback: 'workflow_url_get_title'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
# _entity_access: 'workflow.update'
|
||||
|
||||
entity.workflow_type.delete_form:
|
||||
path: '/admin/config/workflow/workflow/{workflow_type}/delete'
|
||||
defaults:
|
||||
_entity_form: 'workflow_type.delete'
|
||||
_title: 'Delete Workflow'
|
||||
requirements:
|
||||
_permission: 'administer workflow'
|
||||
# _entity_access: 'workflow.update'
|
||||
|
||||
### Workflow Executed Transition CRUD
|
||||
entity.workflow_transition.canonical:
|
||||
path: '/workflow_transition/{workflow_transition}'
|
||||
defaults:
|
||||
# Calls the view controller, defined in the annotation of the contact entity
|
||||
# _entity_view: 'workflow_transition'
|
||||
_title: 'Workflow transition'
|
||||
#requirements:
|
||||
# Calls the access controller of the entity, $operation 'view'
|
||||
# _entity_access: 'workflow_transition.view'
|
||||
|
||||
entity.workflow_transition.edit_form:
|
||||
path: '/workflow_transition/{workflow_transition}/edit'
|
||||
defaults:
|
||||
_entity_form: 'workflow_transition.edit'
|
||||
_title: 'Edit Workflow transition'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'workflow_transition.update'
|
||||
|
||||
entity.workflow_transition.delete_form:
|
||||
path: '/workflow_transition/{workflow_transition}/delete'
|
||||
defaults:
|
||||
_entity_form: 'workflow_transition.delete'
|
||||
_title: 'Delete Workflow transition'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'workflow_transition.delete'
|
||||
|
||||
### Workflow Scheduled Transition CRUD
|
||||
entity.workflow_scheduled_transition.canonical:
|
||||
path: '/workflow_transition/{workflow_transition}'
|
||||
defaults:
|
||||
# Calls the view controller, defined in the annotation of the contact entity
|
||||
# _entity_view: 'workflow_transition'
|
||||
_title: 'Workflow transition'
|
||||
#requirements:
|
||||
# Calls the access controller of the entity, $operation 'view'
|
||||
# _entity_access: 'workflow_transition.view'
|
||||
|
||||
entity.workflow_scheduled_transition.edit_form:
|
||||
path: '/workflow_transition/{workflow_transition}/edit'
|
||||
defaults:
|
||||
_entity_form: 'workflow_transition.edit'
|
||||
_title: 'Edit Workflow transition'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'workflow_transition.update'
|
||||
|
||||
entity.workflow_scheduled_transition.delete_form:
|
||||
path: '/workflow_transition/{workflow_transition}/delete'
|
||||
defaults:
|
||||
_entity_form: 'workflow_transition.delete'
|
||||
_title: 'Delete Workflow transition'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'workflow_transition.delete'
|
||||
|
||||
### The Workflow Transition History List (Tab)
|
||||
# @todo D8-port: make Workflow History tab happen for every entity_type.
|
||||
# @see workflow.routing.yml, workflow.links.task.yml, WorkflowTransitionListController.
|
||||
# A route for showing the Workflow history tab.
|
||||
entity.node.workflow_history:
|
||||
path: '/node/{node}/workflow/{field_name}'
|
||||
defaults:
|
||||
_title: 'Workflow history'
|
||||
_controller: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyOverview'
|
||||
field_name: ~
|
||||
requirements:
|
||||
_custom_access: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyAccess'
|
||||
_module_dependencies: 'node'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
entity:
|
||||
type: entity:{entity_type}
|
||||
|
||||
entity.taxonomy_term.workflow_history:
|
||||
path: '/taxonomy/term/{taxonomy_term}/workflow/{field_name}'
|
||||
defaults:
|
||||
_title: 'Workflow history'
|
||||
_controller: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyOverview'
|
||||
field_name: ~
|
||||
requirements:
|
||||
_custom_access: '\Drupal\workflow\Controller\WorkflowTransitionListController::historyAccess'
|
||||
_module_dependencies: 'taxonomy'
|
||||
options:
|
||||
parameters:
|
||||
entity:
|
||||
type: entity:{entity_type}
|
||||
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
workflow.manager:
|
||||
class: Drupal\workflow\Entity\WorkflowManager
|
||||
arguments: ['@entity_type.manager', '@entity.query', '@config.factory', '@string_translation', '@module_handler', '@current_user']
|
||||
|
||||
# workflow.route_subscriber:
|
||||
# class: Drupal\workflow\Routing\RouteSubscriber
|
||||
# arguments: ['@entity.manager']
|
||||
# tags:
|
||||
# - { name: event_subscriber }
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provide Views data for workflow.module.
|
||||
*
|
||||
* @ingroup views_module_handlers
|
||||
*/
|
||||
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data().
|
||||
*/
|
||||
function workflow_field_views_data(FieldStorageConfigInterface $field) {
|
||||
$data = views_field_default_views_data($field);
|
||||
$settings = $field->getSettings();
|
||||
|
||||
foreach ($data as $table_name => $table_data) {
|
||||
foreach ($table_data as $field_name => $field_data) {
|
||||
if (isset($field_data['filter']) && $field_name != 'delta') {
|
||||
$data[$table_name][$field_name]['filter']['wid'] = (array_key_exists('workflow_type', $settings)) ? $settings['workflow_type'] : '';
|
||||
$data[$table_name][$field_name]['filter']['id'] = 'workflow_state';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_views_data_alter().
|
||||
*/
|
||||
function workflow_views_data_alter(array &$data) {
|
||||
// Provide an integration for each entity type except workflow entities.
|
||||
// copied from comment.views.inc
|
||||
foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
if (\Drupal\workflow\Entity\WorkflowManager::isWorkflowEntityType($entity_type_id)) {
|
||||
continue;
|
||||
}
|
||||
if (!$entity_type->entityClassImplements(\Drupal\Core\Entity\ContentEntityInterface::class)) {
|
||||
continue;
|
||||
}
|
||||
if (!$entity_type->getBaseTable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields = \Drupal::service('workflow.manager')->getFields($entity_type_id);
|
||||
if ($fields) {
|
||||
$base_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
|
||||
$args = ['@entity_type' => $entity_type_id];
|
||||
foreach ($fields as $field_name => $field) {
|
||||
$data[$base_table][$field_name . '_tid'] = [
|
||||
'title' => t('Workflow transitions on @entity_type using field: @field_name', $args + ['@field_name' => $field_name]),
|
||||
'help' => t('Relate all transitions ongit status @entity_type. This will create 1 duplicate record for every transition. Usually if you need this it is better to create a Transition view.', $args),
|
||||
'relationship' => [
|
||||
'group' => t('Workflow transition'),
|
||||
'label' => t('workflow transition'),
|
||||
'base' => 'workflow_transition_history',
|
||||
'base field' => 'entity_id',
|
||||
'relationship field' => $entity_type->getKey('id'),
|
||||
'id' => 'standard',
|
||||
'extra' => [
|
||||
[
|
||||
'field' => 'entity_type',
|
||||
'value' => $entity_type_id,
|
||||
],
|
||||
[
|
||||
'field' => 'field_name',
|
||||
'value' => $field_name,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ module:
|
||||
user: 0
|
||||
views: 0
|
||||
views_ui: 0
|
||||
workflow: 0
|
||||
filefield_sources: 1
|
||||
menu_admin_per_menu: 1
|
||||
menu_link_content: 1
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
uuid: 4fd977c2-10cc-46c8-b870-a375f4df2ac3
|
||||
langcode: nl
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- workflow
|
||||
_core:
|
||||
default_config_hash: M98hvuYlwD42D6FTasB41hR2kTUpVtEB407qW4Ne1Ww
|
||||
id: change_a_node_to_next_workflow_state
|
||||
label: 'Change a node to next Workflow state'
|
||||
type: node
|
||||
plugin: workflow_node_next_state_action
|
||||
configuration:
|
||||
label: 'Change a node to next Workflow state'
|
||||
id: change_a_node_to_next_workflow_state
|
||||
plugin: workflow_node_next_state_action
|
||||
type: node
|
||||
workflow_scheduling:
|
||||
scheduled: '0'
|
||||
comment: 'New state is set by a triggered Action.'
|
||||
force: 0
|
||||
actions: { }
|
||||
field_name: ''
|
||||
to_sid: ''
|
||||
@@ -0,0 +1,716 @@
|
||||
uuid: 8f1952a2-102b-4873-ac33-1a585844b45a
|
||||
langcode: fr
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- workflow
|
||||
_core:
|
||||
default_config_hash: WVVYWpOBeHeRgm3vCHP0lY65aX-UZx-E_8g6qpFrsHA
|
||||
id: workflow_entity_history
|
||||
label: 'Workflow Entity history'
|
||||
module: views
|
||||
description: 'Enable this View to configure the history tab.'
|
||||
tag: ''
|
||||
base_table: workflow_transition_history
|
||||
base_field: hid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
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: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
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
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
hid: hid
|
||||
timestamp: timestamp
|
||||
field_name: field_name
|
||||
from_sid: from_sid
|
||||
to_sid: to_sid
|
||||
uid: uid
|
||||
comment: comment
|
||||
operations: operations
|
||||
info:
|
||||
hid:
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
timestamp:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
field_name:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
from_sid:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
to_sid:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
uid:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
comment:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
operations:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: hid
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
hid:
|
||||
id: hid
|
||||
table: workflow_transition_history
|
||||
field: hid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Transition ID'
|
||||
exclude: true
|
||||
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
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: true
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: hid
|
||||
plugin_id: field
|
||||
timestamp:
|
||||
id: timestamp
|
||||
table: workflow_transition_history
|
||||
field: timestamp
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Timestamp
|
||||
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
|
||||
click_sort_column: value
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: medium
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: timestamp
|
||||
plugin_id: field
|
||||
field_name:
|
||||
id: field_name
|
||||
table: workflow_transition_history
|
||||
field: field_name
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Field name'
|
||||
exclude: true
|
||||
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
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: false
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: field_name
|
||||
plugin_id: field
|
||||
from_sid:
|
||||
id: from_sid
|
||||
table: workflow_transition_history
|
||||
field: from_sid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'From state'
|
||||
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
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: from_sid
|
||||
plugin_id: field
|
||||
to_sid:
|
||||
id: to_sid
|
||||
table: workflow_transition_history
|
||||
field: to_sid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'To state'
|
||||
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
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: to_sid
|
||||
plugin_id: field
|
||||
uid:
|
||||
id: uid
|
||||
table: workflow_transition_history
|
||||
field: uid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'User ID'
|
||||
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
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: true
|
||||
group_column: target_id
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: uid
|
||||
plugin_id: field
|
||||
comment:
|
||||
id: comment
|
||||
table: workflow_transition_history
|
||||
field: comment
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Log message'
|
||||
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
|
||||
click_sort_column: value
|
||||
type: basic_string
|
||||
settings: { }
|
||||
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
|
||||
entity_type: workflow_transition
|
||||
entity_field: comment
|
||||
plugin_id: field
|
||||
operations:
|
||||
id: operations
|
||||
table: workflow_transition_history
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 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
|
||||
destination: true
|
||||
entity_type: null
|
||||
entity_field: null
|
||||
plugin_id: entity_operations
|
||||
filters: { }
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments:
|
||||
entity_id:
|
||||
id: entity_id
|
||||
table: workflow_transition_history
|
||||
field: entity_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: ignore
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
default_argument_skip_url: false
|
||||
summary_options:
|
||||
base_path: ''
|
||||
count: true
|
||||
items_per_page: 25
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
break_phrase: false
|
||||
not: false
|
||||
entity_type: workflow_transition
|
||||
entity_field: entity_id
|
||||
plugin_id: numeric
|
||||
display_extenders: { }
|
||||
title: 'Workflow history'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
tags: { }
|
||||
workflow_history_tab:
|
||||
display_plugin: embed
|
||||
id: workflow_history_tab
|
||||
display_title: 'Workflow history tab'
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
display_description: 'Replaces the transition list in the Workflow tab.'
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
tags: { }
|
||||
@@ -0,0 +1,4 @@
|
||||
workflow_states_per_page: 20
|
||||
_core:
|
||||
default_config_hash: ne-hLr6xAfj-y7himkP4sQpLeAsTLAzt9YuxlQCk8N8
|
||||
langcode: fr
|
||||
Reference in New Issue
Block a user