Browse Source

enabled workflow module - remains to configure it

Bachir Soussi Chiadmi 6 years ago
parent
commit
d78ebfb03a
93 changed files with 13902 additions and 0 deletions
  1. 7 0
      sites/all/modules/contrib/admin/workflow/CHANGELOG.txt
  2. 339 0
      sites/all/modules/contrib/admin/workflow/LICENSE.txt
  3. 1 0
      sites/all/modules/contrib/admin/workflow/config/install/workflow.settings.yml
  4. 19 0
      sites/all/modules/contrib/admin/workflow/config/optional/block.block.workflowtransitionform.yml
  5. 21 0
      sites/all/modules/contrib/admin/workflow/config/optional/system.action.change_a_node_to_next_workflow_state.yml
  6. 713 0
      sites/all/modules/contrib/admin/workflow/config/optional/views.view.workflow_entity_history.yml
  7. 82 0
      sites/all/modules/contrib/admin/workflow/config/schema/workflow.schema.yml
  8. 27 0
      sites/all/modules/contrib/admin/workflow/config/schema/workflow.state.schema.yml
  9. 27 0
      sites/all/modules/contrib/admin/workflow/config/schema/workflow.transition.schema.yml
  10. 4 0
      sites/all/modules/contrib/admin/workflow/config/schema/workflow.views.schema.yml
  11. 1 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/config/install/workflow_access.settings.yml
  12. 6 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/config/schema/workflow_access.schema.yml
  13. 143 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessRoleForm.php
  14. 74 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessSettingsForm.php
  15. 13 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.info.yml
  16. 41 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.install
  17. 15 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.links.task.yml
  18. 300 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.module
  19. 21 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.routing.yml
  20. 138 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/src/Form/WorkflowCleanupSettingsForm.php
  21. 13 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.info.yml
  22. 7 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.links.task.yml
  23. 26 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.module
  24. 7 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.routing.yml
  25. 3 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_devel/README.txt
  26. 14 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.info.yml
  27. 143 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.module
  28. 12 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_field/workflowfield.info.yml
  29. 133 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/src/Form/WorkflowTransitionRevertForm.php
  30. 144 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/src/WorkflowAccessControlHandler.php
  31. 71 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/src/WorkflowPermissions.php
  32. 14 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.info.yml
  33. 97 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.module
  34. 2 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.permissions.yml
  35. 10 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.routing.yml
  36. 104 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowListBuilder.php
  37. 406 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowStateListBuilder.php
  38. 24 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowUiController.php
  39. 191 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Form/WorkflowConfigTransitionFormBase.php
  40. 103 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Form/WorkflowConfigTransitionLabelForm.php
  41. 181 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Form/WorkflowConfigTransitionRoleForm.php
  42. 17 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.info.yml
  43. 24 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.install
  44. 13 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.links.action.yml
  45. 7 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.links.menu.yml
  46. 28 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.links.task.yml
  47. 93 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.module
  48. 46 0
      sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.routing.yml
  49. 217 0
      sites/all/modules/contrib/admin/workflow/src/Controller/WorkflowTransitionListController.php
  50. 587 0
      sites/all/modules/contrib/admin/workflow/src/Element/WorkflowTransitionElement.php
  51. 444 0
      sites/all/modules/contrib/admin/workflow/src/Entity/Workflow.php
  52. 222 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransition.php
  53. 69 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransitionInterface.php
  54. 169 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowInterface.php
  55. 468 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManager.php
  56. 173 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManagerInterface.php
  57. 257 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowScheduledTransition.php
  58. 571 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowState.php
  59. 107 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowStorage.php
  60. 1085 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransition.php
  61. 277 0
      sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransitionInterface.php
  62. 211 0
      sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTransitionForm.php
  63. 247 0
      sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTypeForm.php
  64. 79 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Action/WorkflowNodeGivenStateAction.php
  65. 71 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Action/WorkflowNodeNextStateAction.php
  66. 232 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Action/WorkflowStateActionBase.php
  67. 85 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Block/WorkflowTransitionBlock.php
  68. 206 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldFormatter/WorkflowDefaultFormatter.php
  69. 421 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldType/WorkflowItem.php
  70. 233 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php
  71. 36 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Validation/Constraint/WorkflowFieldConstraint.php
  72. 92 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/Validation/Constraint/WorkflowFieldConstraintValidator.php
  73. 55 0
      sites/all/modules/contrib/admin/workflow/src/Plugin/views/filter/WorkflowState.php
  74. 146 0
      sites/all/modules/contrib/admin/workflow/src/WorkflowAccessControlHandler.php
  75. 79 0
      sites/all/modules/contrib/admin/workflow/src/WorkflowPermissions.php
  76. 22 0
      sites/all/modules/contrib/admin/workflow/src/WorkflowScheduledTransitionViewsData.php
  77. 262 0
      sites/all/modules/contrib/admin/workflow/src/WorkflowTransitionListBuilder.php
  78. 176 0
      sites/all/modules/contrib/admin/workflow/src/WorkflowTransitionViewsData.php
  79. 431 0
      sites/all/modules/contrib/admin/workflow/workflow.api.php
  80. 119 0
      sites/all/modules/contrib/admin/workflow/workflow.field.inc
  81. 277 0
      sites/all/modules/contrib/admin/workflow/workflow.form.inc
  82. 17 0
      sites/all/modules/contrib/admin/workflow/workflow.info.yml
  83. 109 0
      sites/all/modules/contrib/admin/workflow/workflow.install
  84. 32 0
      sites/all/modules/contrib/admin/workflow/workflow.links.task.yml
  85. 705 0
      sites/all/modules/contrib/admin/workflow/workflow.module
  86. 10 0
      sites/all/modules/contrib/admin/workflow/workflow.permissions.yml
  87. 144 0
      sites/all/modules/contrib/admin/workflow/workflow.routing.yml
  88. 10 0
      sites/all/modules/contrib/admin/workflow/workflow.services.yml
  89. 78 0
      sites/all/modules/contrib/admin/workflow/workflow.views.inc
  90. 1 0
      sites/default/config/sync/core.extension.yml
  91. 25 0
      sites/default/config/sync/system.action.change_a_node_to_next_workflow_state.yml
  92. 716 0
      sites/default/config/sync/views.view.workflow_entity_history.yml
  93. 4 0
      sites/default/config/sync/workflow.settings.yml

+ 7 - 0
sites/all/modules/contrib/admin/workflow/CHANGELOG.txt

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

+ 339 - 0
sites/all/modules/contrib/admin/workflow/LICENSE.txt

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

+ 1 - 0
sites/all/modules/contrib/admin/workflow/config/install/workflow.settings.yml

@@ -0,0 +1 @@
+workflow_states_per_page: 20

+ 19 - 0
sites/all/modules/contrib/admin/workflow/config/optional/block.block.workflowtransitionform.yml

@@ -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
sites/all/modules/contrib/admin/workflow/config/optional/system.action.change_a_node_to_next_workflow_state.yml

@@ -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
sites/all/modules/contrib/admin/workflow/config/optional/views.view.workflow_entity_history.yml

@@ -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: {  }

+ 82 - 0
sites/all/modules/contrib/admin/workflow/config/schema/workflow.schema.yml

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

+ 27 - 0
sites/all/modules/contrib/admin/workflow/config/schema/workflow.state.schema.yml

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

+ 27 - 0
sites/all/modules/contrib/admin/workflow/config/schema/workflow.transition.schema.yml

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

+ 4 - 0
sites/all/modules/contrib/admin/workflow/config/schema/workflow.views.schema.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/config/install/workflow_access.settings.yml

@@ -0,0 +1 @@
+workflow_access_priority: 0

+ 6 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_access/config/schema/workflow_access.schema.yml

@@ -0,0 +1,6 @@
+workflow_access.settings:
+  type: config_object
+  label: Settings
+  mapping:
+    workflow_access_priority:
+      type: integer

+ 143 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessRoleForm.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessSettingsForm.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.info.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.install

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.links.task.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.module

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.routing.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/src/Form/WorkflowCleanupSettingsForm.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.info.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.links.task.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.module

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.routing.yml

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

+ 3 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_devel/README.txt

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.info.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.module

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_field/workflowfield.info.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/src/Form/WorkflowTransitionRevertForm.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/src/WorkflowAccessControlHandler.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/src/WorkflowPermissions.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.info.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.module

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.permissions.yml

@@ -0,0 +1,2 @@
+permission_callbacks:
+  - \Drupal\workflow_operations\WorkflowPermissions::workflowTypePermissions

+ 10 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.routing.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowListBuilder.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowStateListBuilder.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowUiController.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Form/WorkflowConfigTransitionFormBase.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Form/WorkflowConfigTransitionLabelForm.php

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Form/WorkflowConfigTransitionRoleForm.php

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

+ 17 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.info.yml

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

+ 24 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.install

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.links.action.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.links.menu.yml

@@ -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
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.links.task.yml

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

+ 93 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.module

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

+ 46 - 0
sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.routing.yml

@@ -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
sites/all/modules/contrib/admin/workflow/src/Controller/WorkflowTransitionListController.php

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

+ 587 - 0
sites/all/modules/contrib/admin/workflow/src/Element/WorkflowTransitionElement.php

@@ -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',
+    ];
+  }
+
+}

+ 444 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/Workflow.php

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

+ 222 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransition.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransitionInterface.php

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

+ 169 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowInterface.php

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

+ 468 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManager.php

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

+ 173 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManagerInterface.php

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

+ 257 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowScheduledTransition.php

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

+ 571 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowState.php

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

+ 107 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowStorage.php

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

+ 1085 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransition.php

@@ -0,0 +1,1085 @@
+<?php
+
+namespace Drupal\workflow\Entity;
+
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Language\Language;
+use Drupal\user\Entity\User;
+use Drupal\user\UserInterface;
+
+/**
+ * Implements an actual, executed, Transition.
+ *
+ * If a transition is executed, the new state is saved in the Field.
+ * If a transition is saved, it is saved in table {workflow_transition_history}.
+ *
+ * @ContentEntityType(
+ *   id = "workflow_transition",
+ *   label = @Translation("Workflow transition"),
+ *   label_singular = @Translation("Workflow transition"),
+ *   label_plural = @Translation("Workflow transitions"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count Workflow transition",
+ *     plural = "@count Workflow transitions",
+ *   ),
+ *   bundle_label = @Translation("Workflow type"),
+ *   module = "workflow",
+ *   translatable = FALSE,
+ *   handlers = {
+ *     "access" = "Drupal\workflow\WorkflowAccessControlHandler",
+ *     "list_builder" = "Drupal\workflow\WorkflowTransitionListBuilder",
+ *     "form" = {
+ *        "add" = "Drupal\workflow\Form\WorkflowTransitionForm",
+ *        "delete" = "Drupal\Core\Entity\EntityDeleteForm",
+ *        "edit" = "Drupal\workflow\Form\WorkflowTransitionForm",
+ *        "revert" = "Drupal\workflow_operations\Form\WorkflowTransitionRevertForm",
+ *      },
+ *     "views_data" = "Drupal\workflow\WorkflowTransitionViewsData",
+ *   },
+ *   base_table = "workflow_transition_history",
+ *   entity_keys = {
+ *     "id" = "hid",
+ *     "bundle" = "wid",
+ *     "langcode" = "langcode",
+ *   },
+ *   permission_granularity = "bundle",
+ *   bundle_entity_type = "workflow_type",
+ *   field_ui_base_route = "entity.workflow_type.edit_form",
+ *   links = {
+ *     "canonical" = "/workflow_transition/{workflow_transition}",
+ *     "delete-form" = "/workflow_transition/{workflow_transition}/delete",
+ *     "edit-form" = "/workflow_transition/{workflow_transition}/edit",
+ *     "revert-form" = "/workflow_transition/{workflow_transition}/revert",
+ *   },
+ * )
+ */
+class WorkflowTransition extends ContentEntityBase implements WorkflowTransitionInterface {
+
+  /*
+   * Transition data: are provided via baseFieldDefinitions().
+   */
+
+  /*
+   * Cache data.
+   */
+  protected $workflow; // Use WorkflowTransition->getWorkflow() to fetch this.
+  protected $entity = NULL; // Use WorkflowTransition->getTargetEntity() to fetch this.
+  protected $user = NULL; // Use WorkflowTransition->getOwner() to fetch this.
+
+  /*
+   * Extra data: describe the state of the transition.
+   */
+  protected $is_scheduled;
+  protected $is_executed;
+  protected $is_forced = FALSE;
+
+  /**
+   * Entity class functions.
+   */
+
+  /**
+   * Creates a new entity.
+   *
+   * @param array $values
+   * @param string $entityType
+   *   The entity type of this Entity subclass.
+   *
+   * @param bool $bundle
+   * @param array $translations
+   * @internal param string $entity_type The entity type of the attached $entity.
+   * @see entity_create()
+   *
+   * No arguments passed, when loading from DB.
+   * All arguments must be passed, when creating an object programmatically.
+   * One argument $entity may be passed, only to directly call delete() afterwards.
+   */
+  public function __construct(array $values = [], $entityType = 'workflow_transition', $bundle = FALSE, $translations = []) {
+    // Please be aware that $entity_type and $entityType are different things!
+    parent::__construct($values, $entityType, $bundle, $translations);
+    // This transition is not scheduled.
+    $this->is_scheduled = FALSE;
+    // This transition is not executed, if it has no hid, yet, upon load.
+    $this->is_executed = ($this->id() > 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param array $values
+   *   $values[0] may contain a Workflow object or State object or State ID.
+   *
+   * @return static
+   *   The entity object.
+   */
+  public static function create(array $values = []) {
+    if (is_array($values) && isset($values[0])) {
+      $value = $values[0];
+      $values['wid'] = '';
+      $values['from_sid'] = '';
+      if (is_string($value) && $state = WorkflowState::load($value)) {
+        $values['wid'] = $state->getWorkflowId();
+        $values['from_sid'] = $state->id();
+      }
+      elseif (is_object($value) && $value instanceof WorkflowState) {
+        $state = $value;
+        $values['wid'] = $state->getWorkflowId();
+        $values['from_sid'] = $state->id();
+      }
+    }
+
+    // Add default values.
+    $values += [
+      'timestamp' => \Drupal::time()->getRequestTime(),
+      'uid' => \Drupal::currentUser()->id(),
+    ];
+    return parent::create($values);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValues($to_sid, $uid = NULL, $timestamp = NULL, $comment = '', $force_create = FALSE) {
+    // Normally, the values are passed in an array, and set in parent::__construct, but we do it ourselves.
+
+    $uid = ($uid === NULL) ? workflow_current_user()->id() : $uid;
+    $from_sid = $this->getFromSid();
+
+    $this->set('to_sid', $to_sid);
+    $this->setOwnerId($uid);
+    $this->setTimestamp($timestamp == NULL ? \Drupal::time()->getRequestTime() : $timestamp);
+    $this->setComment($comment);
+
+    // If constructor is called with new() and arguments.
+    if (!$from_sid && !$to_sid && !$this->getTargetEntity()) {
+      // If constructor is called without arguments, e.g., loading from db.
+    }
+    elseif ($from_sid && $this->getTargetEntity()) {
+      // Caveat: upon entity_delete, $to_sid is '0'.
+      // If constructor is called with new() and arguments.
+    }
+    elseif (!$from_sid) {
+      // Not all parameters are passed programmatically.
+      if (!$force_create) {
+        drupal_set_message(
+          t('Wrong call to constructor Workflow*Transition(%from_sid to %to_sid)',
+            ['%from_sid' => $from_sid, '%to_sid' => $to_sid]),
+          'error');
+      }
+    }
+
+    return $this;
+  }
+
+  /**
+   * CRUD functions.
+   */
+
+  /**
+   * Saves the entity.
+   * Mostly, you'd better use WorkflowTransitionInterface::execute();
+   *
+   * {@inheritdoc}
+   */
+  public function save() {
+    // return parent::save();
+
+    // Avoid custom actions for subclass WorkflowScheduledTransition.
+    if ($this->isScheduled()) {
+      return parent::save();
+    }
+    if ($this->getEntityTypeId() != 'workflow_transition') {
+      return parent::save();
+    }
+
+    $transition = $this;
+    $entity_type = $transition->getTargetEntityTypeId();
+    $entity_id = $transition->getTargetEntityId();
+    $field_name = $transition->getFieldName();
+
+    // Remove any scheduled state transitions.
+    foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [$entity_id], [], $field_name) as $scheduled_transition) {
+      /** @var WorkflowTransitionInterface $scheduled_transition */
+      $scheduled_transition->delete();
+    }
+
+    // Check for no transition.
+    if ($this->isEmpty()) {
+      return SAVED_UPDATED;
+    }
+
+    // Update the transition, if already exists.
+    if ($this->id()) {
+      return parent::save();
+    }
+
+    // Insert the transition. Make sure it hasn't already been inserted.
+    // @todo: Allow a scheduled transition per revision.
+    // @todo: Allow a state per language version (langcode).
+    $found_transition = self::loadByProperties($entity_type, $entity_id, [], $field_name);
+    if ($found_transition &&
+      $found_transition->getTimestamp() == \Drupal::time()->getRequestTime() &&
+      $found_transition->getToSid() == $this->getToSid()) {
+      return SAVED_UPDATED;
+    }
+    else {
+      return parent::save();
+    }
+
+  }
+
+  /**
+   * {@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_transition') {
+    $limit = 1;
+    $transitions = self::loadMultipleByProperties($entity_type, [$entity_id], $revision_ids, $field_name, $langcode, $limit, $sort, $transition_type);
+    if ($transitions) {
+      $transition = reset($transitions);
+      return $transition;
+    }
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function loadMultipleByProperties($entity_type, array $entity_ids, array $revision_ids = [], $field_name = '', $langcode = '', $limit = NULL, $sort = 'ASC', $transition_type = 'workflow_transition') {
+
+    /** @var $query \Drupal\Core\Entity\Query\QueryInterface */
+    $query = \Drupal::entityQuery($transition_type)
+      ->condition('entity_type', $entity_type)
+      ->sort('timestamp', $sort) // 'DESC' || 'ASC'
+      ->addTag($transition_type);
+    if (!empty($entity_ids)) {
+      $query->condition('entity_id', $entity_ids, 'IN');
+    }
+    if (!empty($revision_ids)) {
+      $query->condition('revision_id', $entity_ids, 'IN');
+    }
+    if ($field_name != '') {
+      $query->condition('field_name', $field_name, '=');
+    }
+    if ($langcode != '') {
+      $query->condition('langcode', $langcode, '=');
+    }
+    if ($limit) {
+      $query->range(0, $limit);
+    }
+    if ($transition_type == 'workflow_transition') {
+      // The timestamp is only granular to the second; on a busy site, we need the id.
+      // $query->orderBy('h.timestamp', 'DESC');
+      $query->sort('hid', 'DESC');
+    }
+    $ids = $query->execute();
+    $transitions = self::loadMultiple($ids);
+    return $transitions;
+  }
+
+  /**
+   * Implementing interface WorkflowTransitionInterface - properties.
+   */
+
+  /**
+   * Determines if the Transition is valid and can be executed.
+   * @todo: add to isAllowed() ?
+   * @todo: add checks to WorkflowTransitionElement ?
+   *
+   * @return bool
+   */
+  public function isValid() {
+    // Load the entity, if not already loaded.
+    // This also sets the (empty) $revision_id in Scheduled Transitions.
+    $entity = $this->getTargetEntity();
+
+    if (!$entity) {
+      // @todo: There is a watchdog error, but no UI-error. Is this OK?
+      $message = 'User tried to execute a Transition without an entity.';
+      $this->logError($message);
+      return FALSE;  // <-- exit !!!
+    }
+    if (!$this->getFromState()) {
+      // @todo: the page is not correctly refreshed after this error.
+      $message = t('You tried to set a Workflow State, but
+        the entity is not relevant. Please contact your system administrator.');
+      drupal_set_message($message, 'error');
+      $message = 'Setting a non-relevant Entity from state %sid1 to %sid2';
+      $this->logError($message);
+      return FALSE;  // <-- exit !!!
+    }
+
+    // The transition is OK.
+    return TRUE;
+  }
+
+  /**
+   * Check if anything has changed in this transition.
+   * @todo: check for attached fields.
+   *
+   * @return bool
+   */
+  protected function isEmpty() {
+    if ($this->getToSid() != $this->getFromSid()) {
+      return FALSE;
+    }
+    if ($this->getComment()) {
+      return FALSE;
+    }
+    // @todo: check for attached fields.
+    return true;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isAllowed(UserInterface $user, $force = FALSE) {
+    /**
+     * Get early permissions of user, and bail out to avoid extra hook-calls.
+     */
+
+    if ($force) {
+      // $force allows Rules to cause transition.
+      return TRUE;
+    }
+
+    // Determine if user is owner of the entity.
+    $is_owner = WorkflowManager::isOwner($user, $this->getTargetEntity());
+    // 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.
+      return TRUE;
+    }
+    // Determine if user is owner of the entity.
+    if ($is_owner) {
+      $user->addRole(WORKFLOW_ROLE_AUTHOR_RID);
+    }
+
+    // @todo: Keep below code aligned between WorkflowState, ~Transition, ~TransitionListController
+    /**
+     * Get the object and its permissions.
+     */
+    $config_transitions = $this->getWorkflow()->getTransitionsByStateId($this->getFromSid(), $this->getToSid());
+
+    /**
+     * Determine if user has Access.
+     */
+    $result = FALSE;
+    /** @var $config_transition WorkflowTransitionInterface */
+    foreach ($config_transitions as $config_transition) {
+      $result = $result || $config_transition->isAllowed($user, $force);
+    }
+
+    if ($result == FALSE) {
+      // @todo: There is a watchdog error, but no UI-error. Is this OK?
+      $message = t('Attempt to go to nonexistent transition (from %sid1 to %sid2)');
+      $this->logError($message);
+    }
+
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($force = FALSE) {
+    // Load the entity, if not already loaded.
+    // This also sets the (empty) $revision_id in Scheduled Transitions.
+    /** @var $entity \Drupal\Core\Entity\EntityInterface */
+    $entity = $this->getTargetEntity();
+    $entity_id = $entity->id();
+    // Load explicit User object (not via $transition) for adding Role later.
+    /** @var $user \Drupal\user\UserInterface */
+    $user = $this->getOwner();
+    $from_sid = $this->getFromSid();
+    $to_sid = $this->getToSid();
+    $field_name = $this->getFieldName();
+    $comment = $this->getComment();
+    // Create a label to identify this transition,
+    // even upon insert, when id() is not set, yet.
+    $label = $from_sid . '-' . $to_sid;
+
+    static $static_info = NULL;
+
+    if (isset($static_info[$entity_id][$field_name][$label])) {
+      // Error: this Transition is already executed.
+      // On the development machine, execute() is called twice, when
+      // on an Edit Page, the entity has a scheduled transition, and
+      // user changes it to 'immediately'.
+      // Why does this happen?? ( BTW. This happens with every submit.)
+      // Remedies:
+      // - search root cause of second call.
+      // - try adapting code of transition->save() to avoid second record.
+      // - avoid executing twice.
+      $message = 'Transition is executed twice in a call. The second call for
+        @entity_type %entity_id is not executed.';
+      $this->logError($message);
+
+      // Return the result of the last call.
+      return $static_info[$entity_id][$field_name][$label]; // <-- exit !!!
+    }
+
+    // OK. Prepare for next round. Do not set last_sid!!
+    $static_info[$entity_id][$field_name][$label] = $from_sid;
+
+    // Make sure $force is set in the transition, too.
+    if ($force) {
+      $this->force($force);
+    }
+    $force = $this->isForced();
+
+    // @todo D8-port: figure out usage of $entity->workflow_transitions[$field_name]
+    /*
+        // 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'.
+        $entity->workflow_transitions[$field_name] = $this;
+    */
+
+    if (!$this->isValid()) {
+      return $from_sid;  // <-- exit !!!
+    }
+
+    // @todo: move below code to $this->isAllowed().
+    // If the state has changed, check the permissions.
+    // No need to check if Comments or attached fields are filled.
+    $state_changed = ($from_sid != $to_sid);
+    if ($state_changed) {
+      // Make sure this transition is allowed by workflow module Admin UI.
+      if (!$force) {
+        $user->addRole(WORKFLOW_ROLE_AUTHOR_RID);
+      }
+      if (!$this->isAllowed($user, $force)) {
+        $message = 'User %user not allowed to go from state %sid1 to %sid2';
+        $this->logError($message);
+        return FALSE;  // <-- exit !!!
+      }
+
+      // Make sure this transition is valid and allowed for the current user.
+      // Invoke a callback indicating a transition is about to occur.
+      // Modules may veto the transition by returning FALSE.
+      // (Even if $force is TRUE, but they shouldn't do that.)
+      // P.S. The D7 hook_workflow 'transition permitted' is removed, in favour of below hook_workflow 'transition pre'.
+      $permitted = \Drupal::moduleHandler()->invokeAll('workflow', ['transition pre', $this, $user]);
+      // Stop if a module says so.
+      if (in_array(FALSE, $permitted, TRUE)) {
+        // @todo: There is a watchdog error, but no UI-error. Is this OK?
+        $message = 'Transition vetoed by module.';
+        $this->logError($message, 'notice');
+        return FALSE;  // <-- exit !!!
+      }
+    }
+
+    /**
+     * Output: process the transition.
+     */
+    if ($this->isScheduled()) {
+      /*
+       * Log the transition in {workflow_transition_scheduled}.
+       */
+      $this->save();
+    }
+    else {
+      // The transition is allowed, but not scheduled.
+      // Let other modules modify the comment. The transition (in context) contains all relevant data.
+      $context = ['transition' => $this];
+      \Drupal::moduleHandler()->alter('workflow_comment', $comment, $context);
+      $this->setComment($comment);
+
+      $this->is_executed = TRUE;
+
+      if (!$this->isEmpty()) {
+        /*
+         * Log the transition in {workflow_transition_history}.
+         */
+        $this->save();
+
+        // Register state change with watchdog.
+        if ($state_changed && !empty($this->getWorkflow()->options['watchdog_log'])) {
+          if ($this->getEntityTypeId() == 'workflow_scheduled_transition') {
+            $message = 'Scheduled state change of @entity_type_label %entity_label to %sid2 executed';
+          }
+          else {
+            $message = 'State of @entity_type_label %entity_label set to %sid2';
+          }
+          $this->logError($message, 'notice');
+        }
+
+        // Notify modules that transition has occurred.
+        // Action triggers should take place in response to this callback, not the 'transaction pre'.
+
+        //\Drupal::moduleHandler()->invokeAll('workflow', ['transition post', $this, $user]);
+        // We have a problem here with Rules, Trigger, etc. when invoking
+        // 'transition post': the entity has not been saved, yet. we are still
+        // IN the transition, not AFTER. Alternatives:
+        // 1. Save the field here explicitly, using field_attach_save;
+        // 2. Move the invoke to another place: hook_entity_insert(), hook_entity_update();
+        // 3. Rely on the entity hooks. This works for Rules, not for Trigger.
+        // --> We choose option 2:
+        // @todo D8-port: figure out usage of $entity->workflow_transitions[$field_name]
+        // - First, $entity->workflow_transitions[] is set for easy re-fetching.
+        // - Then, post_execute() is invoked via workflow_entity_insert(), _update().
+      }
+    }
+
+    // Save value in static from top of this function.
+    $static_info[$entity_id][$field_name][$label] = $to_sid;
+
+    return $to_sid;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function executeAndUpdateEntity($force = FALSE) {
+    $to_sid = $this->getToSid();
+
+
+    // Generate error and stop if transition has no new State.
+    if (!$to_sid) {
+      $t_args = [
+        '%sid2' => $this->getToState()->label(),
+        '%entity_label' => $this->getTargetEntity()->label(),
+      ];
+      $message = "Transition is not executed for %entity_label, since 'To' state %sid2 is invalid.";
+      $this->logError($message);
+      drupal_set_message(t($message, $t_args), 'error');
+
+      return $this->getFromSid();
+    }
+
+    // Save the (scheduled) transition.
+    $do_update_entity = (!$this->isScheduled() && !$this->isExecuted());
+    if ($do_update_entity) {
+      $this->_updateEntity();
+    }
+    else {
+      // We create a new transition, or update an existing one.
+      // Do not update the entity itself.
+      // Validate transition, save in history table and delete from schedule table.
+      $to_sid = $this->execute($force);
+    }
+
+    return $to_sid;
+  }
+
+  private function _updateEntity() {
+    // Update the workflow field of the entity.
+    $field_name = $this->getFieldName();
+    $entity = $this->getTargetEntity();
+    // N.B. Align the following functions:
+    // - WorkflowDefaultWidget::massageFormValues();
+    // - WorkflowManager::executeTransition().
+    $entity->$field_name->workflow_transition = $this;
+    $entity->$field_name->value = $this->getToSid();
+
+    $entity->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function post_execute($force = FALSE) {
+    // @todo D8-port: This function post_execute() is not yet used.
+    workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: Test this snippet.
+
+    if (!$this->isEmpty()) {
+      $user = $this->getOwner();
+      \Drupal::moduleHandler()->invokeAll('workflow', ['transition post', $this, $user]);
+    }
+  }
+
+  /**
+   * {@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->target_id && $state = $this->getFromState()) {
+      // Fallback.
+      $state = ($state) ? $state : $this->getToState();
+      $wid = ($state) ? $state->getWorkflowId() : '';
+
+      $this->set('wid', $wid);
+    }
+    return $this->wid->target_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTargetEntity($entity) {
+    $this->entity_type = '';
+    $this->entity_id = '';
+    $this->revision_id = '';
+    $this->delta = 0; // Only single value is supported.
+    $this->langcode = Language::LANGCODE_NOT_SPECIFIED;
+
+    if (!$entity) {
+      return $this;
+    }
+
+    // If Transition is added via CommentForm, use the Commented Entity.
+    if ($entity->getEntityTypeId() == 'comment') {
+      /** @var $entity \Drupal\comment\CommentInterface */
+      $entity = $entity->getCommentedEntity();
+    }
+
+    $this->entity = $entity;
+    /** @var \Drupal\Core\Entity\RevisionableContentEntityBase $entity */
+    $this->entity_type = $entity->getEntityTypeId();
+    $this->entity_id = $entity->id();
+    $this->revision_id = $entity->getRevisionId();
+    $this->delta = 0; // Only single value is supported.
+    $this->langcode = $entity->language()->getId();
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetEntity() {
+    // Use an explicit property, in case of adding new entities.
+    if (isset($this->entity)) {
+      return $this->entity;
+    }
+    // @todo D8: the following line only returns Node, not Term.
+    // return $this->entity = $this->get('entity_id')->entity;
+
+    $entity_type = $this->getTargetEntityTypeId();
+    if ($id = $this->getTargetEntityId()) {
+      $this->entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($id);
+    }
+    return $this->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetEntityId() {
+    return $this->get('entity_id')->target_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetEntityTypeId() {
+    return $this->get('entity_type')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldName() {
+    return $this->get('field_name')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLangcode() {
+    return $this->getTargetEntity()->language()->getId();
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFromState() {
+    $sid = $this->getFromSid();
+    return ($sid) ? WorkflowState::load($sid) : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getToState() {
+    $sid = $this->getToSid();
+    return ($sid) ? WorkflowState::load($sid) : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFromSid() {
+    $sid = $this->{'from_sid'}->target_id;
+    return $sid;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getToSid() {
+    $sid = $this->{'to_sid'}->target_id;
+    return $sid;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getComment() {
+    return $this->get('comment')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setComment($value) {
+    $this->set('comment', $value);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTimestamp() {
+    return $this->get('timestamp')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTimestampFormatted() {
+    $timestamp = $this->getTimestamp();
+    return \Drupal::service('date.formatter')->format($timestamp);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTimestamp($value) {
+    $this->set('timestamp', $value);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isScheduled() {
+    return $this->is_scheduled;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function schedule($schedule = TRUE) {
+//    // We do a tricky thing here. The id of the entity is altered, so
+//    // all functions of another subclass are called.
+//    $this->entityTypeId = ($schedule) ? 'workflow_scheduled_transition' : 'workflow_transition';
+
+    $this->is_scheduled = $schedule;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setExecuted($is_executed = TRUE) {
+    $this->is_executed = $is_executed;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExecuted() {
+    return (bool) $this->is_executed;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isForced() {
+    return (bool) $this->is_forced;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function force($force = TRUE) {
+    $this->is_forced = $force;
+    return $this;
+  }
+
+  /**
+   * Implementing interface EntityOwnerInterface. Copied from Comment.php.
+   */
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwner() {
+    /** @var $user UserInterface */
+    $user = $this->get('uid')->entity;
+    if (!$user || $user->isAnonymous()) {
+      $user = User::getAnonymousUser();
+      $user->setUsername(\Drupal::config('user.settings')->get('anonymous'));
+    }
+    return $user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerId() {
+    return $this->get('uid')->target_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwnerId($uid) {
+    $this->set('uid', $uid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOwner(UserInterface $account) {
+    $this->set('uid', $account->id());
+    return $this;
+  }
+
+  /**
+   * Implementing interface FieldableEntityInterface extends EntityInterface.
+   */
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = [];
+
+    $fields['hid'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Transition ID'))
+      ->setDescription(t('The transition ID.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+//    $fields['wid'] = BaseFieldDefinition::create('string')
+    $fields['wid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Workflow Type'))
+      ->setDescription(t('The workflow type the transition relates to.'))
+      ->setSetting('target_type', 'workflow_type')
+      ->setRequired(TRUE)
+      ->setTranslatable(FALSE)
+      ->setRevisionable(FALSE)
+//      ->setSetting('max_length', 32)
+//      ->setDisplayOptions('view', [
+//        'label' => 'hidden',
+//        'type' => 'string',
+//        'weight' => -5,
+//      ])
+//      ->setDisplayOptions('form', [
+//        'type' => 'string_textfield',
+//        'weight' => -5,
+//      ])
+//      ->setDisplayConfigurable('form', TRUE)
+    ;
+
+    $fields['entity_type'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Entity type'))
+      ->setDescription(t('The Entity type this transition belongs to.'))
+      ->setSetting('is_ascii', TRUE)
+      ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH)
+      ->setReadOnly(TRUE);
+
+    $fields['entity_id'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('Entity ID'))
+      ->setDescription(t('The Entity ID this record is for.'))
+      ->setRequired(TRUE)
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['revision_id'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Revision ID'))
+      ->setDescription(t('The current version identifier.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['field_name'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Field name'))
+      ->setDescription(t('The name of the field the transition relates to.'))
+      ->setRequired(TRUE)
+      ->setTranslatable(FALSE)
+      ->setRevisionable(FALSE)
+      ->setSetting('max_length', 32)
+//      ->setDisplayConfigurable('form', FALSE)
+//      ->setDisplayOptions('form', [
+//        'type' => 'string_textfield',
+//        'weight' => -5,
+//      ])
+//      ->setDisplayConfigurable('view', FALSE)
+//      ->setDisplayOptions('view', [
+//        'label' => 'hidden',
+//        'type' => 'string',
+//        'weight' => -5,
+//      ])
+    ;
+
+    $fields['langcode'] = BaseFieldDefinition::create('language')
+      ->setLabel(t('Language'))
+      ->setDescription(t('The entity language code.'))
+      ->setTranslatable(TRUE)
+      ->setDisplayOptions('view', [
+        'type' => 'hidden',
+      ])
+      ->setDisplayOptions('form', [
+        'type' => 'language_select',
+        'weight' => 2,
+      ]);
+
+    $fields['delta'] = BaseFieldDefinition::create('integer')
+      ->setLabel(t('Delta'))
+      ->setDescription(t('The sequence number for this data item, used for multi-value fields.'))
+      ->setReadOnly(TRUE)
+      ->setSetting('unsigned', TRUE);
+
+    $fields['from_sid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('From state'))
+      ->setDescription(t('The {workflow_states}.sid the entity started as.'))
+      ->setSetting('target_type', 'workflow_state')
+      ->setReadOnly(TRUE);
+
+    $fields['to_sid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('To state'))
+      ->setDescription(t('The {workflow_states}.sid the entity transitioned to.'))
+      ->setSetting('target_type', 'workflow_state')
+// @todo D8: activate this. Test with both Form and Widget.
+//      ->setDisplayOptions('form', [
+//        'type' => 'select',
+//        'weight' => -5,
+//      ])
+//      ->setDisplayConfigurable('form', TRUE)
+      ->setReadOnly(TRUE);
+
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('User ID'))
+      ->setDescription(t('The user ID of the transition author.'))
+      ->setTranslatable(TRUE)
+      ->setSetting('target_type', 'user')
+      ->setDefaultValue(0)
+//      ->setQueryable(FALSE)
+//      ->setSetting('handler', 'default')
+//      ->setDefaultValueCallback('Drupal\node\Entity\Node::getCurrentUserId')
+//      ->setTranslatable(TRUE)
+//      ->setDisplayOptions('view', [
+//        'label' => 'hidden',
+//        'type' => 'author',
+//        'weight' => 0,
+//      ])
+//      ->setDisplayOptions('form', [
+//        'type' => 'entity_reference_autocomplete',
+//        'weight' => 5,
+//        'settings' => [
+//          'match_operator' => 'CONTAINS',
+//          'size' => '60',
+//          'placeholder' => '',
+//        ],
+//      ])
+//      ->setDisplayConfigurable('form', TRUE),
+      ->setRevisionable(TRUE);
+
+    $fields['timestamp'] = BaseFieldDefinition::create('created')
+      ->setLabel(t('Timestamp'))
+      ->setDescription(t('The time that the current transition was executed.'))
+//      ->setQueryable(FALSE)
+//      ->setTranslatable(TRUE)
+//      ->setDisplayOptions('view', [
+//        'label' => 'hidden',
+//        'type' => 'timestamp',
+//        'weight' => 0,
+//      ])
+// @todo D8: activate this. Test with both Form and Widget.
+//      ->setDisplayOptions('form', [
+//        'type' => 'datetime_timestamp',
+//        'weight' => 10,
+//      ])
+//      ->setDisplayConfigurable('form', TRUE);
+      ->setRevisionable(TRUE);
+
+    $fields['comment'] = BaseFieldDefinition::create('string_long')
+      ->setLabel(t('Log message'))
+      ->setDescription(t('The comment explaining this transition.'))
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE)
+// @todo D8: activate this. Test with both Form and Widget.
+//      ->setDisplayOptions('form', [
+//        'type' => 'string_textarea',
+//        'weight' => 25,
+//        'settings' => [
+//          'rows' => 4,
+//        ],
+//      ])
+//      ->setDisplayConfigurable('form', FALSE)
+    ;
+
+    return $fields;
+  }
+
+  /**
+   * Generate a Watchdog error
+   *
+   * @param $message
+   * @param $type {'error' | 'notice'}
+   * @param $from_sid
+   * @param $to_sid
+   */
+  public function logError($message, $type = 'error', $from_sid = '', $to_sid = '') {
+
+    // Prepare an array of arguments for error messages.
+    $entity = $this->getTargetEntity();
+    $t_args = [
+      /** @var $user \Drupal\user\UserInterface */
+      '%user' => ($user = $this->getOwner()) ? $user->getDisplayName() : '',
+      '%sid1' => ($from_sid) ? $from_sid : $this->getFromState()->label(),
+      '%sid2' => ($to_sid) ? $to_sid : $this->getToState()->label(),
+      '%entity_id' => $this->getTargetEntityId(),
+      '%entity_label' => $entity ? $entity->label() : '',
+      '@entity_type' => ($entity) ? $entity->getEntityTypeId() : '',
+      '@entity_type_label' => ($entity) ? $entity->getEntityType()->getLabel() : '',
+      'link' => ($this->getTargetEntityId() && $this->getTargetEntity()->hasLinkTemplate('canonical')) ? $this->getTargetEntity()->toLink(t('View'))->toString() : '',
+    ];
+    ($type == 'error') ? \Drupal::logger('workflow')->error($message, $t_args)
+      : \Drupal::logger('workflow')->notice($message, $t_args);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dpm($function = '') {
+    $transition = $this;
+    $entity = $transition->getTargetEntity();
+    $time = \Drupal::service('date.formatter')->format($transition->getTimestamp());
+    // Do this extensive $user_name lines, for some troubles with Action.
+    $user = $transition->getOwner();
+    $user_name = ($user) ? $user->getAccountName() : 'unknown username';
+    $t_string = $this->getEntityTypeId() . ' ' . $this->id() . ' for workflow_type <i>' . $this->getWorkflowId() . '</i> ' . ($function ? ("in function '$function'") : '');
+    $output[] = 'Entity  = ' . $this->getTargetEntityTypeId() . '/' . (($entity) ? ($entity->bundle() . '/' . $entity->id()) : '___/0') ;
+    $output[] = 'Field   = ' . $transition->getFieldName();
+    $output[] = 'From/To = ' . $transition->getFromSid() . ' > ' . $transition->getToSid() . ' @ ' . $time;
+    $output[] = 'Comment = ' . $user_name . ' says: ' . $transition->getComment();
+    $output[] = 'Forced  = ' . ($transition->isForced() ? 'yes' : 'no') .'; ' . 'Scheduled = ' . ($transition->isScheduled() ? 'yes' : 'no');
+    if (function_exists('dpm')) { dpm($output, $t_string); }
+  }
+
+}

+ 277 - 0
sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransitionInterface.php

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

+ 211 - 0
sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTransitionForm.php

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

+ 247 - 0
sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTypeForm.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Action/WorkflowNodeGivenStateAction.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Action/WorkflowNodeNextStateAction.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Action/WorkflowStateActionBase.php

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

+ 85 - 0
sites/all/modules/contrib/admin/workflow/src/Plugin/Block/WorkflowTransitionBlock.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldFormatter/WorkflowDefaultFormatter.php

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

+ 421 - 0
sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldType/WorkflowItem.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Validation/Constraint/WorkflowFieldConstraint.php

@@ -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
sites/all/modules/contrib/admin/workflow/src/Plugin/Validation/Constraint/WorkflowFieldConstraintValidator.php

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

+ 55 - 0
sites/all/modules/contrib/admin/workflow/src/Plugin/views/filter/WorkflowState.php

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

+ 146 - 0
sites/all/modules/contrib/admin/workflow/src/WorkflowAccessControlHandler.php

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

+ 79 - 0
sites/all/modules/contrib/admin/workflow/src/WorkflowPermissions.php

@@ -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.'),
+      ],
+    ];
+  }
+
+}

+ 22 - 0
sites/all/modules/contrib/admin/workflow/src/WorkflowScheduledTransitionViewsData.php

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

+ 262 - 0
sites/all/modules/contrib/admin/workflow/src/WorkflowTransitionListBuilder.php

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

+ 176 - 0
sites/all/modules/contrib/admin/workflow/src/WorkflowTransitionViewsData.php

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

+ 431 - 0
sites/all/modules/contrib/admin/workflow/workflow.api.php

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

+ 119 - 0
sites/all/modules/contrib/admin/workflow/workflow.field.inc

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

+ 277 - 0
sites/all/modules/contrib/admin/workflow/workflow.form.inc

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

+ 17 - 0
sites/all/modules/contrib/admin/workflow/workflow.info.yml

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

+ 109 - 0
sites/all/modules/contrib/admin/workflow/workflow.install

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

+ 32 - 0
sites/all/modules/contrib/admin/workflow/workflow.links.task.yml

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

+ 705 - 0
sites/all/modules/contrib/admin/workflow/workflow.module

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

+ 10 - 0
sites/all/modules/contrib/admin/workflow/workflow.permissions.yml

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

+ 144 - 0
sites/all/modules/contrib/admin/workflow/workflow.routing.yml

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

+ 10 - 0
sites/all/modules/contrib/admin/workflow/workflow.services.yml

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

+ 78 - 0
sites/all/modules/contrib/admin/workflow/workflow.views.inc

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

+ 1 - 0
sites/default/config/sync/core.extension.yml

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

+ 25 - 0
sites/default/config/sync/system.action.change_a_node_to_next_workflow_state.yml

@@ -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: ''

+ 716 - 0
sites/default/config/sync/views.view.workflow_entity_history.yml

@@ -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: {  }

+ 4 - 0
sites/default/config/sync/workflow.settings.yml

@@ -0,0 +1,4 @@
+workflow_states_per_page: 20
+_core:
+  default_config_hash: ne-hLr6xAfj-y7himkP4sQpLeAsTLAzt9YuxlQCk8N8
+langcode: fr